Module:Jctint

local p = {} -- Package to be exported

-- Local version of string formatting function

local format = mw.ustring.format

-- Local version of string trimming function

local trim = mw.text.trim

-- Store this function in a local variable to avoid expensive table lookups.

local insert = table.insert

-- mw.html object for the generated row

local row

-- Default row span for all columns (`jspan` = "junction span")

local jspan

-- Any error messages produced that will be added to the output

local errorMsg = {}

-- A specification for self-closing HTML tag.

local selfClosing = {selfClosing = true}

---

-- Converts the distance specified in unit from `unit` specified in `unitdef`

-- to the other supported unit.

local function convert(unit, unitdef)

if unit == nil or unitdef == nil then return {} end

-- Import module to convert length.

local util = require("Module:Road data/util")

local lengths = util.convertLengths({[unitdef] = unit})

if lengths.error then -- An error occurred during conversion.

-- Add the transcluding page to an error tracking category.

local page = mw.title.getCurrentTitle() -- Get transcluding page's title

local pagename = page.prefixedText -- Extract page's full title as string

-- Create category string

local category = format("# %s", pagename)

insert(errorMsg, category) -- Add error category to error message table.

end

return lengths

end

--- Creates cells for the location columns.

local function locations(args)

-- Unitary, e.g., state line

local unitary = args.unitary -- Value to span all of the location columns

if unitary then

-- Text alignment of the cell contents, default to "left".

local align = args.unitary_align or 'left'

row:tag('td') -- Create unitary cell

:attr('colspan', 3) -- spanning three possible columns

:css('text-align', align)

:wikitext(unitary) -- Store the contents of unitary in the cell.

return

end

-- Create cells for regular location columns.

-- Region, for disambiguation and potentially for display

local region = args.region

if region or args.region_special then

-- Row span for region; must be specified to display a region cell.

local regionSpan = args.regionspan

if regionSpan then

row:tag('td') -- Create a region cell

:attr('rowspan', regionSpan)

-- Store region text in the cell.

-- `region_special` argument overrides wikilinked `region` argument.

:wikitext(args.region_special or format("%s", region))

end

end

-- Primary topic requires no specialization to supplied locations.

local primaryTopic = args.primary_topic ~= 'no'

-- Note below main text in the next column

local sub1note = args.sub1_note -- check existence later

-- Row span for the last location column, default to `jspan`

local sub2span = args.sub2span or jspan

-- Independent city

local indepCityText -- Value to span both subdivision columns.

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 cityLink -- Wikilink for independent city

if primaryTopic then

cityLink = format('%s', indepCity)

elseif region then

-- Specialize independent city to the region.

cityLink = format('%s', indepCity, region, indepCity)

end

if cityLink then

indepCityText = "City of " .. cityLink

end

end

if indepCityText then -- Display independent city.

-- Text alignment of the cell contents, default to "left".

local align = args.indep_city_align or 'left'

local indepCityCell = row:tag('td') -- Create independent city cell

:attr('colspan', 2) -- spanning two columns

:attr('rowspan', sub2span) -- with the calculated row span.

:css('text-align', align)

:wikitext(indepCityText) -- Store the independent city in the cell.

if sub1note then -- A note is provided.

indepCityCell:tag('br', selfClosing) -- Add a line break to the cell.

-- Add the note to the cell, within an HTML tag.

indepCityCell:tag('small'):wikitext(sub1note)

end

return

end

-- Create two cells for the first- and second-level subdivisions.

-- First-level subdivision, e.g., county

-- Name of the type of subdivision, e.g., "County" and "Parish"

local sub1name = args.sub1name -- check existence later

local sub1Text -- Value for first-level subdivision column.

if args.sub1_special then

sub1Text = args.sub1_special -- Overrides `sub1` argument.

elseif args.sub1 then

local sub1 = args.sub1

if primaryTopic then

-- Add type (if specified) to wikilink for first-level subdivision.

local sub1Link = sub1name and format("%s %s", sub1, sub1name) or sub1

sub1Text = format('%s', sub1Link, sub1)

elseif region and sub1name then

-- Add type to first-level subdivision.

local sub1Typed = trim(format('%s %s', sub1, sub1name))

-- Specialize first-level subdivision, with type added, to the region.

sub1Text = format('%s', sub1Typed, region, sub1)

end

end

if sub1Text then -- Display first-level subdivision.

-- Row span for first-level subdivision, default to `jspan`.

local sub1span = args.sub1span or jspan

local sub1Cell = row:tag('td') -- Create first-level subdivision cell

:attr('rowspan', sub1span) -- with the calculated row span.

:wikitext(sub1Text) -- Store the first-level subdivision in the cell.

if sub1note then -- A note is provided.

sub1Cell:tag('br', selfClosing) -- Add a line break to the cell.

-- Add the note to the cell, within an HTML tag.

sub1Cell:tag('small'):wikitext(sub1note)

end

end

-- Second-level subdivision, e.g., city and town

local sub2Text -- Value for second-level subdivision column.

if args.sub2_special then

sub2Text = args.sub2_special -- Overrides `sub2` argument.

elseif args.sub2 then

local sub2 = args.sub2

if sub2 == "none" or sub2 == " " then

sub2Text = "​" -- Zero-width space

elseif primaryTopic then

sub2Text = format("%s", sub2)

else

local sub2Link = {sub2}

local sub2Name = sub2

-- Type of area, e.g., city and village, as a form of disambiguation

local area = args.area

if area then

insert(sub2Link, format(' (%s)', area)) -- Add area to wikilink.

local areas = { -- table of different area types

borough = "Borough",

city = "City",

community = "Community",

CDP = "Community",

hamlet = "Hamlet",

town = "Town",

village = "Village",

["unorganized territory"] = "Unorganized Territory"

}

-- Add area name to displayed wikitext.

sub2Name = format("%s of %s", areas[area], sub2Name)

end

insert(sub2Link, ", ")

-- 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 and sub1name then

insert(sub2Link, trim(format('%s %s', sub1dab, sub1name)) .. ", ")

end

if region then

insert(sub2Link, region) -- Add region to wikilink

end

sub2Text = format("%s", table.concat(sub2Link), sub2Name)

end

end

if sub2Text then -- Display second-level subdivision.

row:tag('td') -- Create second-level subdivision cell

:attr('rowspan', sub2span) -- with the calculated row span.

:wikitext(sub2Text) -- Store the second-level subdivision in the cell.

end

end

--- Creates cells for the distance columns.

local function units(args)

-- Alternate units, e.g., California's postmiles.

local alt_unit = args.altunit

if alt_unit then -- Alternate units override standard units.

-- Row span (`auspan` = "alt[ernate] unit span")

local auspan = args.auspan or jspan

-- Create the alternate unit cell as a header cell for the row,

-- since it is usually unique within the table.

row:tag('th'):attr('scope', 'row')

:css('text-align', 'right')

:attr('rowspan', auspan)

:wikitext(alt_unit) -- Store the contents of alt_unit in the cell.

else

-- Convert numeric distances to a secondary unit, and display both units.

-- Distance in the primary unit, or 'none'

local unit = args.unit

-- If `unit` is "none", no cells are displayed.

if unit == "none" then return end

local unitdef = args.unitdef or "km" -- The primary unit ('mi' or 'km')

-- Convert and format the distance.

local lengths = convert(unit, unitdef)

-- Row span (`uspan` = "unit span")

local uspan = args.uspan or jspan

-- Create the primary unit cell as a header cell for the row,

-- since it is usually unique within the table.

local primary = row:tag('th'):attr('scope', 'row')

:css('text-align', 'right')

:attr('rowspan', uspan)

-- Store the primary distance and any conversion error message in the cell.

:wikitext(lengths[lengths.orig], lengths.error)

local secondary = row:tag('td') -- Create the secondary unit cell.

:css('text-align', 'right')

:css('background-color', '#eaecf0')

:attr('rowspan', uspan)

:wikitext(lengths[lengths.comp]) -- Store the secondary distance in the cell.

local unit_ref = args.unit_ref

if unit_ref then -- A reference is provided for the distance.

primary:wikitext(unit_ref) -- Add reference to the primary distance cell.

end

local unit2 = args.unit2

if unit2 then -- A second distance is provided.

local line = args.line -- A horizontal rule may be requested between the distances.

if line then

-- Add a horizontal rule to both cells.

primary:tag('hr', selfClosing)

secondary:tag('hr', selfClosing)

else

-- Add an en-dash and a line break to both cells.

primary:wikitext('–'):tag('br', selfClosing)

secondary:wikitext('–'):tag('br', selfClosing)

end

-- Convert and format the second distance.

local lengths2 = convert(unit2, unitdef)

-- Add the second distance and any conversion error message to the primary distance cell.

primary:wikitext(lengths2[lengths2.orig], lengths2.error)

-- Add the converted second distance to the secondary distance cell.

secondary:wikitext(lengths2[lengths2.comp])

end

local unit2_ref = args.unit2_ref

if unit2_ref then -- A reference is provided for the distance.

primary:wikitext(unit2_ref) -- Add reference to the primary distance cell.

end

end

end

-- Color specified by any supplied type

local color

-- Tooltip specified by any supplied type

local title

--- Apply any type-derived coloring and tooltip to the given cell.

local function applyTypeStyle(cell)

cell:attr('title', title):css('background-color', color)

end

--- Creates a cell for places, such as bridges and rest areas.

local function place(args)

local place = args.place -- Contents of the place cell

-- Do nothing if `place` is "none"

if place == "none" then return end

local colspan = 2 -- Initial column span

local exit = args[1] -- Whether this table has exit number columns

local named = args[2] -- Whether this table has named junction column

-- Adjust column span

if exit == "old" then colspan = colspan + 2

elseif exit == "exit" then colspan = colspan + 1

end

if named == "name" then colspan = colspan + 1 end

-- Row span (`pspan` = "place span")

local pspan = args.pspan or jspan

local placeCell = row:tag('td') -- Create place cell

:css('text-align', 'center')

:attr('colspan', colspan)

:attr('rowspan', pspan)

:wikitext(place) -- Store the place in the cell

applyTypeStyle(placeCell)

end

--- Creates cells for exit number and named junction columns.

local function exits(args)

local exit = args[1] -- 'exit', 'old', or nil

local named = args[2] -- 'name' or nil

if exit == 'old' then -- Add old exit number cell

-- Row span (`ospan` = "old span")

local ospan = args.ospan or jspan

row:tag('td') -- Create old exit number cell

:css('text-align', 'center')

:css('background-color', '#d3d3d3')

:attr('title', 'Former exit number')

:attr('rowspan', ospan)

:wikitext(args.old) -- Store the old exit number in the cell

end

if exit then -- "exit" or "old" is defined; add current exit number cell

-- Row span (`espan` = "exit span")

local espan = args.espan or jspan

local exitCell = row:tag('td') -- Create exit number cell

:css('text-align', 'center')

:attr('rowspan', espan)

:wikitext(args.exit) -- Store the exit number in the cell

applyTypeStyle(exitCell)

end

if named then -- Junction list has a junction name column

local namespan = args.namespan or jspan -- Row span

local nameCell = row:tag('td') -- Create junction name cell

:attr('rowspan', namespan)

:wikitext(args.name) -- Store the junction name in the cell

applyTypeStyle(nameCell)

end

end

--- Creates cell for the destinations column.

local function destinations(args)

local road = args.road -- Contents of the destinations cell

-- Do nothing if `road` is "none"

if road == "none" then return end

-- Column span (`rcspan` = "road column span"), default to 1

local rcspan = args.rcspan or 1

-- Row span (`rspan` = "road span")

local rspan = args.rspan or jspan

local destCell = row:tag('td') -- Create destination cell

:attr('colspan', rcspan)

:attr('rowspan', rspan)

:wikitext(road) -- Store the destination in the cell

applyTypeStyle(destCell)

end

--- Creates cell for the notes column.

local function notes(args)

local notes = args.notes -- Contents of the notes cell

-- Do nothing if `notes` is "none"

if notes == "none" then return end

-- Row span (`nspan` = "notes span")

local nspan = args.nspan or jspan

local notesCell = row:tag('td') -- Create notes cell

:attr('rowspan', nspan)

:wikitext(notes) -- Store the notes in the cell

applyTypeStyle(notesCell)

end

---

-- Returns a row in the junction list.

-- Accessible from other Lua modules

function p._jctint(args)

jspan = args.jspan or 1 -- Global row span for all columns; defaults to 1

-- {{{type}}} argument to determine color and tooltips

local argType = args.type

if argType then -- {{{type}}} was passed

-- Type-based data for colors and tooltips

if argType == 'mplex' then

local page = mw.title.getCurrentTitle() -- Get transcluding page's title

local pagename = page.prefixedText -- Extract page's full title as string

insert(errorMsg,

format("$ %s", pagename))

end

local types = mw.loadData("Module:Road data/RJL types")

local typeData = types[string.lower(argType)] -- Retrieve the type data

if typeData then

color = typeData.color -- Store the color globally

title = typeData.jctint -- Store the tooltip globally

else

-- Add error category to error message table.

local page = mw.title.getCurrentTitle() -- Get transcluding page's title

local pagename = page.prefixedText -- Extract page's full title as string

insert(errorMsg,

format("%s", pagename))

end

end

local root = mw.html.create() -- Create the root mw.html object to return

-- Create the table row and store it globally

row = root:tag('tr'):css('text-align', 'left')

locations(args) -- Handle location arguments

units(args) -- Handle distance arguments

if args.place then -- {{{place}}} spans all columns to the right of the distances

place(args) -- Create cell for place

else

exits(args) -- Handle exit/named junction arguments

destinations(args) -- Handle destinations

notes(args) -- Handle notes

end

-- Return the HTML code in the mw.html object as a string, plus any error messages

return tostring(root) .. table.concat(errorMsg)

end

--- Entry function for {{jctint/core}}

function p.jctint(frame)

-- Import module function to work with passed arguments

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

-- Gather passed arguments into easy-to-use table

local args = getArgs(frame)

return p._jctint(args)

end

return p -- Return package