Module:Road data
local p = {}
 
 -- Change to &quot;&quot; upon deployment.
 local moduleSuffix = &quot;&quot;
 
 local parserModuleName = &quot;Module:Road data/parser&quot; .. moduleSuffix
 local statenameModuleName = &quot;Module:Jct/statename&quot; .. moduleSuffix -- TODO transition
 
 local concat = table.concat
 local insert = table.insert
 local format = mw.ustring.format
 local trim = mw.text.trim
 
 local parserModule = require(parserModuleName)
 local parser = parserModule.parser
 local util = require(&quot;Module:Road data/util&quot;)
 local sizeModule = require(&quot;Module:Road data/size&quot;).size({style = &quot;jct&quot;})
 
 -- Shields
 local defaultShieldSize = 24
 
 local function addContextBanner(route, name, suffix, bannerSpec)
 	local bannerModule = 'Module:Road data/banners/' .. string.upper(route.country)
 	local shieldfield = name .. 'shield'
 	local shield = parser(route, shieldfield)
 	if shield == nil then
 		-- This route type does not define shield.
 		-- Find shield in the default banner table.
 		shield = parser(route, 'shield', name, bannerModule)
 		if shield and shield ~= '' then
 			if suffix == nil then
 				suffix = parser(route, 'shield', 'suffix', bannerModule)
 			end
 			if suffix and suffix ~= '' then
 				shield = shield .. &quot; &quot; .. suffix
 			end
 			shield = shield .. &quot;.svg&quot;
 		end
 	end
 	if shield and shield ~= '' then
 		local shieldSize = sizeModule
 		-- Add banner plate.
 		insert(bannerSpec, {shield, shieldSize})
 	end
 end
 
 local function bannerSpec(banner, bannerSize, bannerSuffix, route)
 	local banners = {}
 	if type(banner) == &quot;table&quot; then
 		local bannerSizeIsNotTable = type(bannerSize) ~= &quot;table&quot;
 		for i,filename in ipairs(banner) do
 			local bannersize = bannerSizeIsNotTable and bannerSize or bannerSize[i] or defaultShieldSize
 			insert(banners, {filename, bannersize})
 		end
 	elseif banner ~= '' then
 		insert(banners, {banner, bannerSize})
 	end
 
 	if route.dir then
 		addContextBanner(route, 'dir', bannerSuffix, banners)
 	end
 	if route.to then
 		addContextBanner(route, 'to', bannerSuffix, banners)
 	end
 
 	return banners
 end
 
 local function shieldSpec(route, mainShield, shieldList)
 	local shieldSpec = {}
 
 	local shield
 	if mainShield then shield = parser(route, &quot;shieldmain&quot;) end
 	if shieldList then shield = parser(route, &quot;shieldlist&quot;) end
 	if not shield then shield = parser(route, 'shield') or '' end
 	if shield == '' then return shieldSpec end
 	local orientation = parser(route, 'orientation')
 
 	local function size(route)
 		if orientation == &quot;upright&quot; then
 			return sizeModule
 			else return &quot;x&quot; .. sizeModule
 		end
 	end
 	
 	local shieldsize = sizeModule
 	
 	local banner = parser(route, 'banner') or {}
 	local bannersize = sizeModule
 	local bannersuffix = parser(route, 'bannersuffix')
 
 	local bannerIsNotTable = type(banner) ~= &quot;table&quot;
 	local bannersizeIsNotTable = type(bannersize) ~= &quot;table&quot;
 	local bannersuffixIsNotTable = type(bannersuffix) ~= &quot;table&quot;
 
 	if type(shield) == &quot;table&quot; then
 		for i,filename in ipairs(shield) do
 			local size = shieldsize or shieldsize[i]
 			if size == &quot;&quot; then size = nil end
 			-- banner.all describes banners that apply to all multiple shields.
 			local shieldBanner = bannerIsNotTable and banner or (banner[i] or banner.all or {})
 			-- Banner size is default if the corresponding entry
 			-- in bannerSize table is not set.
 			local shieldBannerSize =
 				bannersizeIsNotTable and bannersize
 				or (bannersize[i] or bannersize.all or defaultShieldSize)
 			local shieldBannerSuffix = bannersuffix and (bannersuffixIsNotTable and bannersuffix or bannersuffix[i])
 			insert(shieldSpec, {
 				shield = {filename, size},
 				banners = bannerSpec(shieldBanner, shieldBannerSize, shieldBannerSuffix, route)
 			})
 		end
 	elseif shield ~= '' then
 		if shieldsize == &quot;&quot; then shieldsize = nil end
 		insert(shieldSpec, {
 			shield = {shield, shieldsize},
 			banners = bannerSpec(banner, bannersize,  bannersuffix, route)
 		})
 	end
 
 	return shieldSpec
 end
 
 local missingShields
 
 local shieldExistsCache = {}
 
 -- Return up to two booleans.
 -- The first boolean is false if `shield` does not exist, and true otherwise.
 -- If the first boolean is true, the second boolean is true if the shield is
 -- landscape (width &gt;= height), and false otherwise.
 local function shieldExists(shield)
 	local result = shieldExistsCache[shield]
 	if result == nil then
 		local file = mw.title.new(shield, 'Media').file
 		-- Cache result.
 		local exists = file.exists
 		result = {exists}
 		if exists then result[2] = file.width &gt;= file.height end
 		shieldExistsCache[shield] = result
 	end
 	if result[1] then return true, result[2] end
 	insert(missingShields, shield)
 	return false
 end
 
 local function render(shieldEntry, scale, showLink)
 	local shield = shieldEntry.shield
 	local banners = shieldEntry.banners
 	local exists, landscape = shieldExists(shield[1])
 	if not exists then return '' end
 
 	local size
 	if shield[2] then
 		local width, height = mw.ustring.match(shield[2], &quot;(%d*)x?(%d*)&quot;)
 		width = tonumber(width)
 		height = tonumber(height)
 		local sizeparts = {}
 		if width then
 			insert(sizeparts, format(&quot;%d&quot;, width * scale))
 		end
 		if height then
 			insert(sizeparts, format(&quot;x%d&quot;, height * scale))
 		end
 		size = concat(sizeparts)
 	else
 		size = format(&quot;%s%d&quot;, landscape and &quot;x&quot; or &quot;&quot;, sizeModule)
 	end
 	local shieldCode = format(&quot;[[File:%s|%spx|link=|alt=]]&quot;, shield[1], size)
 	if not banners[1] then return shieldCode end
 
 	for _,banner in ipairs(banners) do
 		if shieldExists(banner[1]) then
 			shieldCode = format(&quot;[[File:%s|%s|link=|alt=]]&lt;br&gt;%s&quot;,
 				banner[1],
 				banner[2],
 				shieldCode)
 		end
 	end
 	return '&lt;span style=&quot;display: inline-block; vertical-align: baseline; line-height: 0; text-align: center;&quot;&gt;' .. shieldCode .. '&lt;/span&gt;'
 end
 
 function p.shield(route, scale, showLink, mainShield, shieldList)
 	missingShields = {}
 	if route.rdt then
 		local shieldSize = mw.ustring.match(route.rdt, '^(%d+)$') or 17
 		scale = shieldSize/defaultShieldSize
 	end
 	scale = scale or 1
 
 	local rendered = {}
 	for _,entry in ipairs(shieldSpec(route, mainShield, shieldList)) do
 		insert(rendered, render(entry, scale, showLink))
 	end
 	return concat(rendered), missingShields
 end
 
 function p.link(route)
 	local abbr, errMsg = parser(route, 'abbr')
 	if not abbr then
 		route.typeerror = true
 		return util.err(errMsg or format(&quot;Invalid type: %s&quot;, route.type or &quot;(nil)&quot;))
 	end
 	if route.nolink then return abbr, abbr end
 
 	local link = parser(route, 'link') or ''
 	if link == '' then return abbr, abbr end
 
 	return format(&quot;[[%s|%s]]&quot;, link, abbr), abbr
 end
 
 local function stateName(args)
 	-- TODO transition
 	local data = mw.loadData(statenameModuleName) 
 	local abbr = args.state or args.province
 	local countryData = data[args.country]
 	return countryData and countryData[abbr]
 end
 
 function p.locations(args, module, group)
 	module = module or &quot;&quot;
 	local modulearticle = module .. &quot;article&quot;
 	local moduleprefix = module .. &quot;prefix&quot;
 	local modulenameprefix = module .. &quot;nameprefix&quot;
 	local modulenamesuffix = module .. &quot;namesuffix&quot;
 
 	local warnings = {}
 
 	-- Region, for disambiguation
 	local region = parserModule.parser(args, &quot;region&quot;, &quot; common &quot;)
 	if not region then
 		-- TODO transition
 		if args.region then
 			warnings.region = &quot;region parameter is deprecated&quot;
 			region = args.region
 		elseif args.country and (args.state or args.province) then
 			warnings.region = &quot;Inferring region from country and state/province&quot;
 			region = stateName(args)
 		end
 	end
 	local regionName
 	local regionText
 	if type(region) == &quot;table&quot; then
 		regionName = region.name
 		regionText = format(&quot;[[%s|%s]]&quot;, region.link, regionName)
 	elseif region then
 		regionName = region
 		regionText = format(&quot;[[%s]]&quot;, regionName)
 	end
 	args.region = regionName
 
 	local locations = parserModule.parser(args, &quot;locations&quot;, &quot; common &quot;) or {}
 
 	-- Primary topic requires no specialization to supplied locations.
 	local primaryTopic = not locations and module == &quot;jctint&quot; and args.primary_topic ~= 'no'
 	if args.primary_topic then
 		-- TODO transition
 		warnings.primary_topic = &quot;primary_topic parameter is deprecated&quot;
 	end
 
 	-- Independent city
 	local indepCityText
 	if args.indep_city_special then
 		indepCityText = args.indep_city_special -- Overrides `indep_city` argument.
 	elseif args.indep_city then
 		local indepCity = args.indep_city
 		local spec = locations.indep_city
 		if spec then
 			local link = format(&quot;%s%s%s&quot;,
 				spec.linkprefix or &quot;&quot;, indepCity, spec.linksuffix or &quot;&quot;)
 			local name = format(&quot;%s%s%s&quot;,
 				spec[modulenameprefix] or spec.nameprefix or &quot;&quot;,
 				indepCity,
 				spec[modulenamesuffix] or spec.namesuffix or &quot;&quot;)
 			indepCityText = format(&quot;%s%s[[%s|%s]]&quot;,
 				spec[modulearticle] or spec.article or &quot;&quot;,
 				spec[moduleprefix] or spec.prefix or &quot;&quot;,
 				link, name)
 		else
 			-- TODO transition
 			warnings.indep_city = &quot;Spec for indep_city parameter undefined in road data module&quot;
 			local cityLink -- Wikilink for independent city
 			if primaryTopic then
 				cityLink = format('[[%s]]', indepCity)
 			else
 				-- Specialize independent city to the region.
 				cityLink = format('[[%s, %s|%s]]', indepCity, region, indepCity)
 			end
 			indepCityText = &quot;[[Independent city|City]] of &quot; .. cityLink
 		end
 	end
 	if indepCityText then
 		return {region = regionText, indep_city = indepCityText, warnings = warnings}
 	end
 
 	-- First-level subdivision, e.g., county
 	-- Name of the type of subdivision, e.g., &quot;County&quot; and &quot;Parish&quot;
 	local sub1name = args.sub1name -- TODO transition
 	local sub1Text
 	if args.sub1_special then
 		sub1Text = args.sub1_special -- Overrides `sub1` argument.
 	elseif args.sub1 then
 		local sub1 = args.sub1
 		local article
 		local link = sub1
 		local name = sub1
 		-- Type of first-level subdivision area, as a form of disambiguation
 		local sub1area = args.sub1area
 		if sub1area then
 			local sub1areaSpec = locations.sub1areas and locations.sub1areas[sub1area]
 			if sub1areaSpec then
 				article = sub1areaSpec[modulearticle] or sub1areaSpec.article or &quot;&quot;
 				link = format(&quot;%s%s%s&quot;,
 					sub1areaSpec.linkprefix or &quot;&quot;, link, sub1areaSpec.linksuffix or &quot;&quot;)
 				name = format(&quot;%s%s%s&quot;,
 					group and &quot;&quot; or sub1areaSpec[modulenameprefix] or sub1areaSpec.nameprefix or &quot;&quot;,
 					name,
 					group and &quot;&quot; or sub1areaSpec[modulenamesuffix] or sub1areaSpec.namesuffix or &quot;&quot;)
 			else
 				-- TODO report error
 				local errMsg = util.err(format(&quot;Undefined sub1area: %s&quot;, sub1area))
 				name = format(&quot;%s%s&quot;, name, errMsg)
 			end
 		end
 		if locations.sub1 then
 			local spec = locations.sub1
 			-- Prepend and append text from spec.
 			link = format(&quot;%s%s%s&quot;,
 				spec.linkprefix or &quot;&quot;, link, spec.linksuffix or &quot;&quot;)
 			name = format(&quot;%s%s%s&quot;,
 				spec[modulenameprefix] or spec.nameprefix or &quot;&quot;,
 				name,
 				spec[modulenamesuffix] or spec.namesuffix or &quot;&quot;)
 			sub1Text = format(&quot;%s[[%s|%s]]&quot;, article or &quot;&quot;, link, name)
 		else
 			-- TODO transition
 			warnings.sub1 = &quot;Spec for sub1 parameter undefined in road data module&quot;
 			-- Add type (if specified) to wikilink for first-level subdivision.
 			local sub1Link = sub1name and trim(format(&quot;%s %s&quot;, sub1, sub1name)) or sub1
 			local sub1Name = module == &quot;jcttop&quot; and sub1Link or sub1
 			if primaryTopic then
 				sub1Text = format('[[%s|%s]]', sub1Link, sub1Name)
 			else
 				-- Specialize first-level subdivision, with type added, to the region.
 				sub1Text = format('[[%s, %s|%s]]', sub1Link, region, sub1Name)
 			end
 		end
 	end
 
 	-- Second-level subdivision, e.g., city and town
 	local sub2Text
 	if args.sub2_special then
 		sub2Text = args.sub2_special -- Overrides `sub2` argument.
 	elseif args.sub2 then
 		local sub2 = args.sub2
 		if sub2 == &quot;none&quot; then
 			sub2Text = &quot;&amp;#8203;&quot; -- Zero-width space
 		elseif sub2 == &quot;&amp;nbsp;&quot; then
 			-- TODO transition
 			warnings.sub2 = &quot;&amp;nbsp; argument for sub2 parameter is deprecated&quot;
 			sub2Text = &quot;&amp;#8203;&quot; -- Zero-width space
 		elseif primaryTopic then
 			-- TODO transition
 			sub2Text = format(&quot;[[%s]]&quot;, sub2)
 		else
 			local article
 			local link = sub2
 			local name = sub2
 			-- Type of area, e.g., city and village, as a form of disambiguation
 			local sub2area = args.sub2area --[[TODO transition]] or args.area
 			if sub2area then
 				local sub2areaSpec = locations.sub2areas and locations.sub2areas[sub2area]
 				if not sub2areaSpec then
 					-- TODO transition
 					warnings.sub2 =
 						format(&quot;Spec for area parameter '%s' undefined in road data module&quot;, sub2area)
 					local sub2areas = { -- table of different area types
 						city = {
 							linksuffix = &quot; (city)&quot;,
 							jcttoparticle = &quot;the &quot;,
 							nameprefix = &quot;City of &quot;
 						},
 						town = {
 							linksuffix = &quot; (town)&quot;,
 							jcttoparticle = &quot;the &quot;,
 							nameprefix = &quot;Town of &quot;
 						},
 						village = {
 							linksuffix = &quot; (village)&quot;,
 							jcttoparticle = &quot;the &quot;,
 							nameprefix = &quot;Village of &quot;
 						},
 						community = {
 							linksuffix = &quot; (community)&quot;,
 							jcttoparticle = &quot;the &quot;,
 							nameprefix = &quot;Community of &quot;
 						},
 						CDP = {
 							linksuffix = &quot; (CDP)&quot;,
 							jcttoparticle = &quot;the &quot;,
 							nameprefix = &quot;Community of &quot;
 						},
 						hamlet = {
 							linksuffix = &quot; (hamlet)&quot;,
 							jcttoparticle = &quot;the &quot;,
 							nameprefix = &quot;Hamlet of &quot;
 						},
 						[&quot;unorganized territory&quot;] = {
 							linksuffix = &quot; (unorganized territory)&quot;,
 							jcttoparticle = &quot;the &quot;,
 							nameprefix = &quot;Unorganized Territory of &quot;
 						},
 						township = {
 							linksuffix = &quot; Township&quot;,
 							namesuffix = &quot; Township&quot;,
 						}
 					}
 					sub2areaSpec = sub2areas[sub2area]
 				end
 				if sub2areaSpec then
 					article = sub2areaSpec[modulearticle] or sub2areaSpec.article or &quot;&quot;
 					link = format(&quot;%s%s%s&quot;,
 						sub2areaSpec.linkprefix or &quot;&quot;, link, sub2areaSpec.linksuffix or &quot;&quot;)
 					name = format(&quot;%s%s%s&quot;,
 						group and &quot;&quot; or sub2areaSpec[modulenameprefix] or sub2areaSpec.nameprefix or &quot;&quot;,
 						name,
 						group and &quot;&quot; or sub2areaSpec[modulenamesuffix] or sub2areaSpec.namesuffix or &quot;&quot;)
 				else
 					-- TODO report error
 					local errMsg = util.err(format(&quot;Undefined sub2area: %s&quot;, sub2area))
 					name = format(&quot;%s%s&quot;, name, errMsg)
 				end
 			end
 			if locations.sub2 then
 				local spec = locations.sub2
 				-- Prepend and append text from spec.
 				link = format(&quot;%s%s%s&quot;,
 					spec.linkprefix or &quot;&quot;, link, spec.linksuffix or &quot;&quot;)
 				name = format(&quot;%s%s%s&quot;,
 					spec[modulenameprefix] or spec.nameprefix or &quot;&quot;,
 					name,
 					spec[modulenamesuffix] or spec.namesuffix or &quot;&quot;)
 			else
 				-- TODO transition
 				warnings.sub2 = &quot;Spec for sub2 parameter undefined in road data module&quot;
 				-- Some second-level subdivisions are not unique in a given region.
 				-- `sub1dab` is the first-level subdivision to be used for disambiguation.
 				local sub1dab = args.sub1dab
 				if sub1dab then
 					sub1dab = sub1name and trim(format(&quot;%s %s&quot;, sub1dab, sub1name)) or sub1dab
 					link = format(&quot;%s, %s&quot;, link, sub1dab)
 				end
 				link = format(&quot;%s, %s&quot;, link, region) -- Add region to wikilink
 			end
 			sub2Text = format(&quot;%s[[%s|%s]]&quot;, article or &quot;&quot;, link, name)
 		end
 	end
 	return {region = regionText, sub1 = sub1Text, sub2 = sub2Text, warnings = warnings}
 end