Permanently protected module
From Wikipedia, the free encyclopedia


require('strict')

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

local styles = require ('Module:WPMILHIST Infobox style');						-- infobox css

local data = mw.loadData ('Module:WPSHIPS utilities/data');

local namespace = mw.title.getCurrentTitle().namespace;							-- used for categorization





--[[--------------------------< E R R O R _ M A P _ T >--------------------------------------------------------

]]



local error_map_t = {															-- [1] is error message; [2] is error category

	synonymous = {'has synonymous parameter', 'Category:Pages using infobox ship with synonymous parameters'};

	missing = {'missing required parameter: %s', 'Category:WPSHIPS: sclass and sclass2 errors'},

	format = {'invalid format code: %s. Should be 0–5, or blank', 'Category:WPSHIPS: sclass and sclass2 errors'},

	missing_name = {'missing name', 'Category:WPSHIPS: Template Ship parameter errors'},

	missing_dab = {'missing disambiguator', 'Category:WPSHIPS: Template Ship parameter errors'},

	missing_prefix = {'missing prefix', 'Category:WPSHIPS: Template Ship parameter errors'},

	invalid_control = {'invalid control parameter: %s', 'Category:WPSHIPS: Template Ship parameter errors'},

	}





--[[--------------------------< E R R O R _ M S G _ M A K E >--------------------------------------------------



assembles an error message from message text and category in <error_map_t>.  creates a help link to the category

page; categorizes only main and template namespaces.



<no_cat> disables categorization for those templates that support it; to disable categorization set <no_cat> true



]]



local function error_msg_make (msg_idx, template, detail, no_cat, link_template_name)

	local out = {};

	local category;

	

	table.insert (out, '<span style=\"font-size: 100%; font-style: normal;\" class=\"error\">Error: ');	--TODO: simplify?

	if template then

		if link_template_name then

			table.insert (out, table.concat ({'{{[[Template:', template, '|', template, ']]}} '}));

		else

			table.insert (out, table.concat ({'{{', template, '}} '}));				-- TODO: get template names for synonymous parameter errors

		end

	end

	table.insert (out, string.format (error_map_tmsg_idx][1], detail));		-- build error message from base + <detail>

	table.insert (out, table.concat ({' ([[:', error_map_tmsg_idx][2], '|help]])'}));	-- help text on category pages; TODO: help text on template pages also?

	table.insert (out, '</span>');

	

	if (0 == namespace or 10 == namespace) and not no_cat then					-- categorize in article space (and template space to take care of broken usages)

		table.insert (out, table.concat ({'[[', error_map_tmsg_idx][2], ']]'}));

	end



	return table.concat (out);													-- make a big string and done

end





--[[--------------------------< I S _ S E T >------------------------------------------------------------------



Returns true if argument is set; false otherwise. Argument is 'set' when it exists (not nil) or when it is not an empty string.



]]



local function is_set( var )

	return not (var == nil or var == '');

end





--[[--------------------------< S I Z E O F _ S H I P _ T Y P E >----------------------------------------------



Returns the size in words of ship type.  Inputs are the fragment table, the number of elements in the fragment table,

and the number of words that make up nationality.



The number of fragments (words) in a ship name dictate the possible sizes of ship type.  If nationality takes one fragment,

and ship type takes four fragments, then the minimum number of fragments in a composite ship name is:

	5 = 1 (nationality) + 3 (ship type) + 1 (ship name) (same as 4 fragments (words) after nationality)



This function starts at the longest possible series of fragments that might be ship type.  This order is important becuase

some ship types might begin with similar fragments: 'ship' and 'ship of the line'.  Starting with the least possible

series of fragments (1) would find 'ship' and make 'of the line' part of the italicized name.



Returns 0 if there is no recognizable ship type.



]]



local function sizeof_ship_type (frag, frag_len, nat_len)

	local ship_type;

	

	if 5 <= (frag_len - nat_len) then											-- must have at least five fragments after nationality for four-word ship type

		ship_type = table.concat (frag, ' ', nat_len+1, nat_len+4);				-- four-word ship type

		if data.ship_type_tship_type then

			return 4;

		end

	end

	if 4 <= (frag_len - nat_len) then											-- must have at least four fragments after nationality for three-word ship type

		ship_type = table.concat (frag, ' ', nat_len+1, nat_len+3);				-- three-word ship type

		if data.ship_type_tship_type then

			return 3;

		end

	end

	if 3 <= (frag_len - nat_len) then											-- must have at least three fragments after nationality for two-word ship type

		ship_type = table.concat (frag, ' ', nat_len+1, nat_len+2);				-- two-word ship type

		if data.ship_type_tship_type then

			return 2;

		end

	end	

	if 2 <= (frag_len - nat_len) then											-- must have at least two fragments after nationality for one-word ship type

		if data.ship_type_tfragnat_len+1]] then								-- one-word ship type

			return 1;

		end

	end

	return 0;																	-- no recognizable ship type

end



--[[--------------------------< S I Z E O F _ N A T I O N A L I T Y >------------------------------------------



This function the size (in words) of the nationality from the fragments table.  Nationality may be one or two

words that occupy the first one or two positions in the table.



Returns the number of words that identify the nationality:

	1 for French or German, etc.

	2 for United States;

	0 when table doesn't have a recognizable nationality



]]



local function sizeof_nationality (frag, frag_len)

	local nat = '';



	if not data.nationality_t frag1]] then									-- if not a one-word nationality

		if 2 <= frag_len - 2 then												-- must have at least two fragments after nationality for minimal ship type and name

			nat = table.concat (frag, ' ', 1, 2);

			if data.nationality_t nat then									-- is it a two-word nationality?

				return 2;														-- yes

			else

				return 0;														-- no

			end

		end

		return 0;																-- not one-word and not enough fragments for two-word

	end

	return 1;																	-- one-word nationality

end





--[[-------------------------< D O _ S H I P _ N A M E _ F O R M A T >-----------------------------------------



This function applies correct styling to ship and ship-class names.  These names are, for example, ship-article titles

used by templates {{navsource}}, {{Infobox ship begin}}, where the article title is to be rendered with proper

styling.



This function requires one argument:

	|name= (required): a name is required; if missing or empty, this function returns an error message (may or may not be visible depending on where it is used)

		used in {{infobox ship begin}} to provide a value for {{DISPLAYTITLE:}} and to provide a value for |infobox caption=

			{{#invoke:WPSHIPS_utilities|ship_name_format|name={{PAGENAME}}}}

Optional arguments to support {{infobox ship begin}}:

	|dab=none – displays ship name without parenthetical disambiguator; use when |infobox caption=nodab

	|sclass=2 – for ship classes only; displays class name without italics (parameter name is loosely similar to {{slcass2}} which does the same thing); use when |infobox caption=class

	|adj=off – for ship classes only; displays class name as a noun (no hyphen, no ship type); use when |infobox caption=class



Arguments are passed in a table.



to call this function locally:

	do_ship_name_format ({['name=name'], ['dab']=dab, ['sclass']=sclass, ['adj']=adj, ['showerrs']=showerrs}) or

	args = {['name=name'], ['dab']=dab, ['sclass']=sclass, ['adj']=adj, ['showerrs']=showerrs};

	do_ship_name_format (args)



The function returns the formatted name or, if unable to format the name, the original name and an unformatted error message.



]]



local function do_ship_name_format (args)

	local name_sans_dab;														-- the ship or class name without a trailing parenthetical dab

	local dab;																	-- the dab stripped from the name

	local fragments = {};														-- a table of words that make up name_sans_dab

	local ship_type;															-- a word or phrase that describes a ship

	local type_len;																-- the number of words that describe a ship

	local nat_len;																-- the number of words used to specify a ship's nationality

	local name = '';															-- the reassembles and formatted ship name

	local error_msg = '';														-- a repository for error messages if any



--	args.name = mw.text.decode (args.name);										-- replace html entities in title with their characters; doesn't work for &amp; and &#38; in prefix

	args.name = args.name:gsub ("&#39;", "\'");									-- replace html appostrophe with the character

--	args.name = args.name:gsub ("&amp;", "&");

--	args.name = args.name:gsub ("&#38;", "&");

--	args.name = args.name:gsub ("&amp;", "&#38;");





	if args.name:match ('.+%-class%s+%a+') then									-- if a ship-class

		fragments = mw.text.split (args.name, '-class' );						-- split at -class

		if '2' == args.sclass then												-- for DISPLAYTITLE and infobox caption when class not named for a member of the class

			if 'off' == args.adj then

				return fragments1 .. ' class';								-- for infobox caption do noun form <name> class (no hyphen, no ship type)

			end

			return args.name;													-- nothing to do so return original unformatted name

		end

		if 'off' == args.adj then

			return "''" .. fragments1 .. "'' class";							-- for infobox caption do noun form <name> class (no hyphen, no ship type)

		end

		return "''" .. fragments1 .. "''-class" .. fragments2];				-- and return formatted adjectival name

	end

																				-- not a ship class so try to format a ship name

	name_sans_dab, dab = args.name:match('^(.+)%s+(%b())%s*$');					-- split name into name_sans_dab and dab

	if is_set (dab) then

		dab = ' ' .. dab;														-- insert a space for later reassembly

	else

		name_sans_dab = args.name;												-- because without a dab, the string.match returns nil

		dab = '';																-- empty string for concatenation

	end

	

	fragments = mw.text.split (name_sans_dab, '%s' );							-- split into a table of separate words



	nat_len = sizeof_nationality (fragments, #fragments);						-- get the number of words in the ship's nationality

	if 0 < nat_len then															-- if not zero we have a valid nationality

		type_len = sizeof_ship_type (fragments, #fragments, nat_len);			-- get the number of words in the ship type

		if 0 < type_len then													-- if not zero, ship type is valid; nationality and type not italics, the rest is name

			name = "''" .. table.concat (fragments, ' ', nat_len + type_len + 1) .. "''";	-- format name

			if 'none' == args.dab then											-- for |infobox caption=nodab

				return name;													-- return the formatted name without the nationality or ship type or dab

			end

			name = table.concat (fragments, ' ', 1, nat_len + type_len) .. " " .. name;	-- assemble everything but dab

		else

			error_msg = ' unrecognized ship type;';								-- valid nationality, invalid ship type

		end

	elseif data.ship_prefix_tfragments1]] then								-- if the first fragment is a ship prefix

		name = table.remove (fragments, 1);										-- fetch it from the table

		name = name .. " ''" .. table.concat (fragments, ' ') .. "''";			-- assemble formatted name

	else

		error_msg = ' no nationality or prefix;';								-- invalid nationality and first word in ship name not a valid prefix

	end



	if is_set (name) then														-- name will be set if we were able to format it

		if 'none' == args.dab then												-- for |infobox caption=nodab

			return name;														-- return the formatted name without the dab

		end

		return name .. dab;														-- return the formatted name with the dab

	end

	

	if is_set (dab) then

		if dab:match ('%(%u+[%- ]?%d+%)') or									-- one or more uppercase letters, optional space or hyphen, one or more digits

			dab:match ('%(%d+[%- ]?%u+%)') or									-- one or more digits, optional space or hyphen, one or more uppercase letters

			dab:match ('%(%u[%u%-]*%-%d+%)') or									-- one or more uppercase letters with hyphens, a hyphen, one or more digits (e.g., T-AO-157)

			dab:match ('%([12]%d%d%d%)') then									-- four digits representing year in the range 1000–2999

				name = "''" .. table.concat (fragments, ' ') .. "''";			-- format the name

				if 'none' == args.dab then										-- for |infobox caption=nodab

					return name;												-- return the formatted name without the dab

				end

			return name .. dab;													-- return the formatted name with the dab

		end

																				-- last chance, is there a ship type in the dab?

		for key, _ in pairs (data.ship_type_t) do								-- spin through the ship type list and see if there is a ship type (key) in the dab

	    	if dab:find ('%f[%a]' .. key .. '%f[^%a]') then						-- avoid matches that are not whole word

	    		name = "''" .. table.concat (fragments, ' ') .. "''";			-- format the name

				if 'none' == args.dab then										-- for |infobox caption=nodab

					return name;												-- return the formatted name without the dab

				end

				return name .. dab;												-- return the formatted name with the dab

	    	end

		end

		error_msg = error_msg .. ' no ship type in dab;';

		

		if 'none' == args.dab then												-- for |infobox caption=nodab

			return table.concat (fragments, ' '), error_msg;					-- return the unformatted name without the dab, and an error message

		end

	end



	return args.name, error_msg;												-- return original un-formatted name with unformatted error message if any

end





--[[-------------------------< S H I P _ N A M E _ F O R M A T >-----------------------------------------------



This function is the external interface to do_ship_name_format().



The function requires one parameter:

	|name= (required): a name is required; if missing or empty, this function returns an error message (may or may not be visible depending on where it is used)

		used in {{infobox ship begin}} to provide a value for {{DISPLAYTITLE:}} and to provide a value for |infobox caption=

			{{#invoke:WPSHIPS_utilities|ship_name_format|name={{PAGENAME}}}}

Optional parameters to support {{infobox ship begin}}:

	|dab=none – displays ship name without parenthetical disambiguator; use when |infobox caption=nodab

	|sclass=2 – for ship classes only; displays class name without italics (parameter name is loosely similar to {{slcass2}} which does the same thing); use when |infobox caption=class

	|adj=off – for ship classes only; displays class name as a noun (no hyphen, no ship type); use when |infobox caption=class

Other optional parameters:

	|showerrs=yes – marginally useful; can display error messages if the module invocation is not buried in a template

	

Values from the above parameters are placed in a table and that table passed as an argument in the call to do_ship_name_format().



do_ship_name_format() returns two strings: a name and an error message.  If do_ship_name_format() could format the name, it returns the formatted name and 

an empty string for the error message.  If it could not format the name, do_ship_name_format() returns the original name and an error message.



Formatting of the error message, in response to |showerrs=yes is the responsibility of the calling function.



]]



local function ship_name_format(frame)

	local name = '';															-- destination of the formatted ship name

	local error_msg = '';														-- destination of any error message



	if not is_set (frame.args.name) then										-- if a ship name not provided

		if 'yes' == frame.args.showerrs then									-- and we're supposed to show errors

			error_msg = '<span style="font-size:100%; font-weight:normal" class="error">Empty name</span>';	-- return an empty string error message if there is no name

		end

	else

		name, error_msg = do_ship_name_format (frame.args);						-- get formatted name and error message

		if is_set (error_msg) and 'yes' == frame.args.showerrs then				-- if appropriate, show error message

			error_msg = '<span style="font-size:100%; font-weight:normal" class="error">' .. error_msg .. '</span>';

		else

			error_msg = '';														-- for concatenation

		end

	end



	return name .. error_msg;													-- return name and error message

end





--[[--------------------------< H N S A >----------------------------------------------------------------------



Similar to {{navsource}}, this code supports {{hnsa}} by attempting to construct a link to a ship article at the

the Historic Nava Ships Association website.



The template has the form:

	{{hnsa|<page>|<name>}}

where:

	<page> is the name of the page at http://hnsa.org/hnsa-ships/<page>

	<name> (optional) is the name of the ship; if left blank, the template uses the current page title; if a ship name, it is formatted

from which this code produces:

	[http://hnsa.org/hnsa-ships/<page> <name>] at Historic Naval Ships Association



]]



local function hnsa (frame)

	local pframe = frame:getParent()											-- get arguments from calling template frame

	local ship_name = '';

	local error_msg = '';

	local article_title = mw.title.getCurrentTitle().text;						-- fetch the article title

	

	if not is_set (pframe.args1]) then

		return '<span style="font-size:100%; font-weight:normal" class="error">missing hsna page</span>';

	end

	

	local fmt_params = {['name'='', 'showerrs'=nil};



	if is_set (pframe.args.showerrs) then										-- if showerrs set in template, override showerrs in #invoke:

		fmt_params.showerrs = pframe.args.showerrs;								-- template value

	else

		fmt_params.showerrs = frame.args.showerrs;								-- invoke value

	end



	if is_set (pframe.args2]) then

		fmt_params.name = pframe.args2];

	else

		fmt_params.name = article_title;										-- use article title

	end



	ship_name, error_msg = do_ship_name_format (fmt_params);

	

	if is_set (error_msg) and is_set (pframe.args2]) then						-- if unable to format the name

		local escaped_name = pframe.args2]:gsub("([%(%)%.%-])", "%%%1");		-- escape some of the Lua magic characters

		if pframe.args2 == article_title or									-- is name same as article title?

			nil ~= article_title:find ('%f[%a]' .. escaped_name .. '%f[%s]') or	-- is name a word or words substring of article title?

			nil ~= article_title:find ('%f[%a]' .. escaped_name .. '$') then	-- is name a word or words substring that ends article title?

				ship_name = "''" .. pframe.args2 .. "''";						-- non-standard 'name'; perhaps just the name without prefix and dab;

				error_msg = '';													-- unset because we think we have a name

		end

	end

	if is_set (error_msg) and 'yes' == fmt_params.showerrs then

		error_msg = '<span style="font-size:100%; font-weight:normal" class="error">' .. error_msg .. '</span>';

	else

		error_msg = '';															-- unset so it doesn't diplay

	end

	

	local output = {

		'[http://www.hnsa.org/hnsa-ships/',

		pframe.args1],

		'/ ',

		ship_name,

		'] at Historic Naval Ships Association',

		error_msg,

		}



	return table.concat (output);

end





--[[--------------------------< N A V S O U R C E >------------------------------------------------------------



This version of the template {{navsource}} was added as a test vehicle for do_ship_name_format().



]]



local function navsource (frame)

	local pframe = frame:getParent()											-- get arguments from calling template frame

	local ship_name = '';

	local error_msg = '';

	local article_title = mw.title.getCurrentTitle().text;						-- fetch the article title

	

	if not is_set (pframe.args1]) then

		return '<span style="font-size:100%; font-weight:normal" class="error">missing navsource URLcode</span>';

	end

	

	local fmt_params = {['name'='', 'showerrs'=nil};



	if is_set (pframe.args.showerrs) then										-- if showerrs set in template, override showerrs in #invoke:

		fmt_params.showerrs = pframe.args.showerrs;								-- template value

	else

		fmt_params.showerrs = frame.args.showerrs;								-- invoke value

	end



	if is_set (pframe.args2]) then

		fmt_params.name = pframe.args2];

	else

		fmt_params.name = article_title;										-- use article title

	end



	ship_name, error_msg = do_ship_name_format (fmt_params);

	

	if is_set (error_msg) and is_set (pframe.args2]) then						-- if unable to format the name

		local escaped_name = pframe.args2]:gsub("([%(%)%.%-])", "%%%1");		-- escape some of the Lua magic characters

		if pframe.args2 == article_title or									-- is name same as article title?

			nil ~= article_title:find ('%f[%a]' .. escaped_name .. '%f[%s]') or	-- is name a word or words substring of article title?

			nil ~= article_title:find ('%f[%a]' .. escaped_name .. '$') then	-- is name a word or words substring that ends article title?

				ship_name = "''" .. pframe.args2 .. "''";						-- non-standard 'name'; perhaps just the name without prefix and dab;

				error_msg = '';													-- unset because we think we have a name

		end

	end

	if is_set (error_msg) and 'yes' == fmt_params.showerrs then

		error_msg = '<span style="font-size:100%; font-weight:normal" class="error">' .. error_msg .. '</span>';

	else

		error_msg = '';															-- unset so it doesn't diplay

	end

	

	local output = {

		'[http://www.navsource.org/archives/',

		pframe.args1],

		'.htm Photo gallery] of ',

		ship_name,

		' at NavSource Naval History',

		error_msg,

		}



	return table.concat (output);

end





--[[--------------------------< _ S H I P >--------------------------------------------------------------------



This is a possible replacement for the template {{ship}}.  It has better error detection and handling.



]]



local function _ship (prefix, name, dab, control, unlinked_prefix, unlinked_whole, template, no_cat)

	local error_msg = '';

	local category = '';

	

	if not is_set (control) then

		control = '';															-- if not provided, ensure that control is empty string for comparisons

	elseif control:find ('%-') then												-- shortcut for |link=no when using a format control parameter ...|SSBN-659|-6}} same as ...|SSBN-659|6|link=no}}

		unlinked_whole = true;													-- set the unlinked flag

		control = control:match ('%d');											-- strip out the hyphen

	end



																				-- dispose of error conditions straight away

	if not is_set (name) then													-- this is the only required parameter

		error_msg = error_msg_make ('missing_name', template, '', no_cat, true);

	elseif not is_set (dab) and ('1' == control or '3' == control or '5' == control) then	-- dab required when control value set to expect it

		error_msg = error_msg_make ('missing_dab', template, '', no_cat, true);

	elseif not is_set (prefix) and ('5' == control or '6' == control) then		-- prefix required when control value set to expect it

		error_msg = error_msg_make ('missing_prefix', template, '', no_cat, true);

	elseif '4' == control then													-- displaying only the prefix

		error_msg = error_msg_make ('invalid_control', template, control, no_cat, true);

	elseif is_set (control) then

		if ('number' ~= type (tonumber (control))) or (1 ~= control:len()) or (1 > tonumber (control) or 6 < tonumber (control)) then	-- control must be a single-digit number 1 through 6

			error_msg = error_msg_make ('invalid_control', template, control, no_cat, true);

		end

	elseif not is_set (prefix) and unlinked_prefix then							-- prefix required when |up=yes

		error_msg = error_msg_make ('missing_prefix', template, '', no_cat, true);

	end



	if is_set (error_msg) then

		return error_msg;

	end



	local link_name;

	local link = '';



	if is_set (prefix) then

		link = prefix .. ' ' .. name;											-- begin assembling the article name (link) portion of the wikilink

	else

		link = name;

	end



	if is_set (dab) then

		link = link .. ' (' .. dab .. ')';										-- wrap dab in parentheses

	end



	local target_object = mw.title.new (link).redirectTarget;					-- if <link> points to a redirect

	if target_object then

		link = target_object.text;												-- get the target title to avoid linking through the redirect

	end



	name = "''" .. name .. "''";												-- name is always italicized so do it now



	if '1' == control then

		link_name = dab;														-- special case when displaying only the dab, don't wrap in parentheses

	end

	

	if is_set (dab) then														-- for all other cases that display dab

		if '5' == control then

			dab = "&nbsp;\'\'" .. dab .. "\'\'";								-- for prefix with dab display HMS A1. italicize the dab

		else

			dab = '&nbsp;(' .. dab .. ')';										-- except for dab-only, all others display with parentheses 

		end

	end

		

	if not is_set (control) then												-- when control not set: prefix, name, and dab

		if is_set (prefix) then

			link_name = prefix .. '&nbsp;' .. name .. dab;

		else

			link_name = name .. dab;

		end

	else																		-- when control is not 1 or none

		if '2' == control then													-- name only

			link_name = name;

		elseif '3' == control then												-- name and dab

			link_name = name .. dab;

		elseif '5' == control then												-- prefix and dab

				link_name = prefix .. dab;

		elseif '6' == control then												-- prefix and name

				link_name = prefix .. '&nbsp;' .. name;

		end

	end



	if '5' ~= control and'6' ~= control and is_set (control) then

		unlinked_prefix = false;												-- no prefix so don't try to unlink it

	end



	if unlinked_whole then

		return link_name;														-- no linking desired so done

	elseif unlinked_prefix and is_set (prefix) then								-- when there is a prefix to unlink

		link_name = link_name:gsub ('^.-&nbsp;', '', 1);						-- remove the prefix and nbsp

		return prefix ..  '&nbsp;[[' .. link .. '|' .. link_name .. ']]';		-- add prefix and nbsp to front and done

	else

		return '[[' .. link .. '|' .. link_name .. ']]';						-- construct the wikilink and done

	end

end





--[[--------------------------< S H I P >----------------------------------------------------------------------



This is a possible replacement for the template {{ship}}.  It has better error detection and handling.



This function is the externally accessible entry point for template {{ship}}, {{HMS}}, {{USS}}, etc



{{#invoke:WPSHIPS_utilities|ship|_template=<template name>}}



{{#invoke:WPSHIPS_utilities|ship_pre|prefix=<prefix>|_template=<template name>}}



Parameters in the module frame are:

	there are no module frame parameters

	

Parameters in the template frame are:

	{{{1|}}} – prefix (HMS, USS, Japanese submarine, etc)

	{{{2|}}} – ship's name (required)

	{{{3|}}} – disambiguator (year, hull or pennant number, etc)

	{{{4|}}} – format control (1, 2, 3, 5, 6; 4 not allowed)

	|wl= – when set to 'no', rendering is not wikilinked

	|up= – when set to 'yes' prefix (if rendered) is not linked



]]



local function ship (frame)														-- this version not supported from the template yet

	local args_t = get_args (frame);



	local prefix = args_t1 or '';												-- fetch positional parameters into named variables for readability

	local name = args_t2 or '';												-- stripped of leading and trailing whitespace 

	local dab = args_t3 or '';												-- empty positional parameters are nil so convert nil to empty string

	local control = args_t4];

	local unlinked_prefix = 'yes' == args_t.up;									-- make boolean: true when |up=yes

	local unlinked_whole = 'no' == args_t.wl;									-- make boolean: true when |wl=no

	local no_cat = 'yes' == args_t'no-tracking'];								-- make boolean: true when |no-tracking=yes



	return _ship (prefix, name, dab, control, unlinked_prefix, unlinked_whole, 'Ship', no_cat);

end





--[[--------------------------< S H I P _ P R E F I X _ T E M P L A T E S >------------------------------------



This is a possible replacement for the template prefix templates {{USS}}, {{HMS}}, etc.  It has better error

detection and handling.



This function is the externally accessible entry point for those templates



{{#invoke:WPSHIPS_utilities|ship_prefix_templates|prefix=<prefix>|template=<template name>}}



Parameters in the module frame are:

	|_prefix= – (required) _prefix (HMS, USS, Japanese submarine, etc)

	|_template= template name for error messages;  optional when |prefix= same as template name

	

Parameters in the template frame are:

	{{{1|}}} – ship's name (required)

	{{{2|}}} – disambiguator (year, hull or pennant number, etc)

	{{{3|}}} – format control (1, 2, 3, 5, 6; 4 not allowed)

	|wl= – when set to 'no', rendering is not wikilinked

	|up= – when set to 'yes' prefix (if rendered) is not linked



]]



local function ship_prefix_templates (frame)									-- this version not supported from the templates yet

	local args_t = get_args (frame);



	local prefix = args_t.prefix or '';										-- fetch positional parameters into named variables for readability

	local name = args_t1 or '';												-- stripped of leading and trailing whitespace 

	local dab = args_t2 or '';												-- empty positional parameters are nil so convert nil to empty string

	local control = args_t3];

	local unlinked_prefix = 'yes' == args_t.up;									-- make boolean: true when |up=yes

	local unlinked_whole = 'no' == args_t.wl;									-- make boolean: true when |wl=no

	local no_cat = 'yes' == args_t'no-tracking'];								-- make boolean: true when |no-tracking=yes



	return _ship (prefix, name, dab, control, unlinked_prefix, unlinked_whole, args_t.template_name or prefix, no_cat);

end





--[[--------------------------< L I S T _ E R R O R >----------------------------------------------------------



Assembles an error message, the original parameter value and, if appropriate a category.  The error message precedes

the existing value of the parameter.  If the first non-whitespace character in the parameter is a '*', set prefix to

a '*' and sep to '\n'.  For line-break lists, set prefix to empty string and sep to '<br />'.



Category is appended to the end of the returned value only for pages in article space.



Inputs:

	prefix – a string of characters that precede the error message span; typically '' and '*'

	message – the error message to be displayed; goes inside the span

	sep – a string of characters that separates the span from the parameter value; typically '\n' and '<br />'

	param_val – the unmodified parameter value

	showerrs – a boolean, true to display the error message text



]]



local function list_error (prefix, message, sep, param_val, showerrs)

	local err_msg = '%s<span style="font-size:100%%" class="error">list error: %s ([[:Category:WPSHIPS:Infobox list errors|help]])</span>%s%s%s';

	local category = '';



	if 0 == mw.title.getCurrentTitle().namespace then							-- only categorize pages in article space

		category = '[[Category:WPSHIPS:Infobox list errors]]';

	end

	if true == showerrs then

		return string.format (err_msg, prefix, message, sep, param_val, category);			-- put it all together

	else

		return param_val .. category;

	end

end





--[[--------------------------< U N B U L L E T E D _ L I S T >------------------------------------------------



Mediawiki:Common.css imposes limitations on plain, unbulleted lists.  The template {{plainlist}} does not render this correctly:

{{plainlist|

*Item 1

*Item 2

**Item 2a

***Item 2a1

**Item 2b

*Item 3}}

The above renders without proper indents for items marked ** and ***.



If the list is not wrapped in {{plainlist}} then the above list is rendered with bullets which is contrary to the Infobox ship usage guide.



This code translates a bulleted list into an html unordered list:



<ul style="list-style:none; margin:0;"> 

 <li>Name 1</li>

 <li>Name 2</li>

 <ul style="list-style:none">

  <li>Subname 2a</li>

  <ul style="list-style:none">

   <li>Subname 2a1</li>

  </ul>

  <li>Subname 2b</li>

 </ul>

 <li>Name 3</li>

</ul>



There are rules:

	1. The parameter value must begin with a splat but may have leading and trailing whitespace.

	2. Each list item after the first must begin on a new line just as is required by normal bulleted lists.

	3. When adding a sublevel, the number of splats may increase by one and never more. This is illegal:

		*item

		***item

When any of these rules are violated, unbulleted_list() returns the original text and adds the article

to Category:WPSHIPS:Infobox list errors.  Error messaging in this function is somewhat sketchy so they are

disabled.  After initial adoption, better error messaging could/should be implemented.



This function receives the content of one parameter:

	{{#invoke:WPSHIPS utilities|unbulleted_list|{{{param|}}}}}



]]



local function _unbulleted_list (param)

	local showerrs = true;														-- set to false to hide error messages

	local List_item_otag = '<li style="padding-left: .4em; text-indent: -.4em;">';	-- hanging indent markup; everything moves right with padding-left; first line moved left by neg indent

	

	if nil == param:match ('^%s*%*') then										-- parameter value must begin with a splat (ignoring leading white space)

		if param:match ('<[%s/]*[Bb][Rr][%s/]*>') then							-- if the parameter value has a list using variants of <br /> tag

			return list_error ('', '&lt;br /> list', '<br />', param, showerrs);	-- return an error message with maintenance category

		elseif param:match ('<div style="clear:') then

			return list_error ('', '{{clear}} list', '<br />', param, showerrs);	-- return an error message with maintenance category

		elseif param:match ('.+\n%*') then										-- if the parameter value has text followed by an unordered list

			return list_error ('', 'mixed text and list', '<br />', param, showerrs);	-- return an error message with maintenance category

		end

		return param;															-- return the parameter as is

	end

	

	local item_table = mw.text.split (mw.text.trim (param), '\n');				-- trim white space from end then make a table of bulleted items by splitting on newlines

	if 1 == #item_table then													-- if only one bulleted item, no need for a list

		return (item_table1]:gsub ('^%*%s*', ''));								-- trim off the splat and any following white space and done

	end

	

	if item_table1]:match ('^%*%*+') then										-- if first list item uses more than one splat, that's an error

		return list_error ('*', 'too many * at start of list', '\n', param, showerrs);	-- return an error message with maintenance category

	end

	

	local html_table = {};														-- table to hold the html output

	local level = 1;															-- used to indicate when a new <ul> is required

	local splats = 0;															-- number of splats that start each item in the list

	local item = '';															-- the item text

	

	table.insert (html_table, '<ul style="list-style:none; margin:0;">')		-- this for first <ul> tag; sets no bullets and no indent

	

	for _,v in ipairs (item_table) do

		splats, item = v:match ('(%*+)%s*(.*)');								-- split the item into splats and item text

		if nil == splats then													-- nil if there is an extra line between items

			return list_error ('*', 'list item missing markup', '\n', param, showerrs);	-- return an error message with maintenance category

		elseif '' == item then

			return list_error ('*', 'empty list item', '\n', param, showerrs);	-- return an error message with maintenance category

		elseif item:match ('^[;:]') then										-- if the list item is mixed unordered list / description list markup (*:)

			return list_error ('*', 'mixed list type', '\n', param, showerrs);	-- return an error message with maintenance category

		end



		splats = splats:len();													-- change string of splats into a number indicating how many splats there are

		

		if splats == level then													-- if at the same level as previous item

			table.insert (html_table, List_item_otag .. item .. '</li>');

		elseif splats == level + 1 then											-- number of splats can only increase by one

			level = splats;														-- record the new level

			table.insert (html_table, '<ul style="list-style: none">');			-- add a new sublist

			table.insert (html_table, List_item_otag .. item .. '</li>');		-- and the item

		elseif splats < level then												-- from three splats to one splat for example

			while splats < level do

				table.insert (html_table, '</ul>');								-- close each sub <ul> until level and splats match

				level = level - 1;

			end

			table.insert (html_table, List_item_otag .. item .. '</li>');		-- add the item

		else																	-- jumping more than one level up – one splat to three splats for example – is an error

			return list_error ('*', 'too many asterisks', '\n', param, showerrs);	-- return an error message with maintenance category

		end

	end



	while 0 < level do

		table.insert (html_table, '</ul>');										-- close each <ul> until level counted down to zero

		level = level - 1;

	end



	return table.concat (html_table, '\n');										-- return the list as a string

end





--[[--------------------------< U N B U L L E T E D _ L I S T >------------------------------------------------



external entry point



]]



local function unbulleted_list (frame)

	return _unbulleted_list (frame.args1])

end





--[=[-------------------------< _I N F O B O X _ S H I P _ F L A G >--------------------------------------------



Output of {{shipboxflag|USA}}:

	[[File:Flag of the United States.svg|100x35px|alt=|link=]]

	

Image syntax:

	[[File:Name|Type|Border|Location|Alignment|Size|link=Link|alt=Alt|Caption]]



This function standardizes the size of flag images in the infobox ship career header by simply overwriting the Size

parameter value in the Image wikilink with |100x28px.  This size leave a 1px gap between the top and bottom of the flag image and

the header edge.  A similar left-side gap of 2px is supplied by {{infobox ship career}}.



]=]



local function _infobox_ship_flag (image)

	if image:match ('|[%s%dx]+px%s*') then										-- is there a size positional parameter?

		image = image:gsub('|[%s%dx]+px%s*', '%|100x28px');						-- overwrite it with |100x28px

	else

		return '<span style="font-size:100%" class="error">malformed flag image</span>'

	end

	return image;																-- return the modified image

end





--[=[-------------------------< I N F O B O X _ S H I P _ F L A G >--------------------------------------------



external entry point



]=]



local function infobox_ship_flag (frame)

	if not is_set (frame.args1]) then											-- if |Ship flag= not set

		return '';																-- return empty string

	end

	

	return _infobox_ship_flag (frame.args1]);									-- return the modified image

end





--[=[-------------------------< C I T E _ D A N F S _ T I T L E >----------------------------------------------



This function attempts to render a DANFS article title in more or less proper (per Wikipedia) format for the

template {{cite danfs}}.  DANFS titles typically take one of four forms:

	<ship name> <disambiguator> <hull number>

	<ship name> <hull number>

	<ship name> <disambiguator>

	<ship name>



Here, we extract the various parts, italicize the ship name and reassemble for use by the cite danfs |title= parameter.

To call this function:

	|title={{#invoke:WPSHIPS utilities|cite_danfs_title|{{{title}}}}}



]=]



local function cite_danfs_title (frame)

	local name;

	local disambiguator;

	local hullnum;

	

	if not is_set (frame.args1]) then											-- if |title= not set

		return '';																-- return empty string

	end



	name, disambiguator, hullnum = frame.args1]:match ('(.-)( [XVI]+)( %([^%)]+%))$');

	if not (name and disambiguator and hullnum) then

		disambiguator = '';														-- empty string for concatenation

		name, hullnum = frame.args1]:match ('(.-)( %([^%)]+%))$');

		if not (name and hullnum) then

			hullnum = '';														-- empty string for concatenation

			name, disambiguator = frame.args1]:match ('(.-)( [XVI]+)$');

			if not (name and disambiguator) then

				disambiguator = '';

				name = frame.args1];											-- just a name or something we don't recognize

			end

		end

	end



	return table.concat ({"''", name, "''", disambiguator, hullnum});			-- reassemble and done

end





--[[--------------------------< S Y N O N Y M _ C H E C K >----------------------------------------------------



support function for infoboxen functions



there are a handful of infoboxen parameters that are synonymous pairs.  This function is called to see if both

of the parameters in a synonymous pair have been assigned a value.  When both have assigned values, each gets an

error message appended to it.  Most of the synonymous pairs are UK Emglish v US English so the variable names



<args_t> table of template parameters and values

<uk_param> UK English parameter name

<us_param> US English parameter name

<error_flag> boolean that this function sets true when both of a pair are set; controls addition of maint category



]]



local function synonym_check (args_t, uk_param, us_param, error_flag)

	if args_tuk_param and args_tus_param then



		args_tuk_param = args_tuk_param .. error_msg_make ('synonymous');		-- both are set so append error message with category

		args_tus_param = args_tus_param .. error_msg_make ('synonymous', nil, nil, true);	-- but append error message without category

		return true;															-- inform the calling function that it needs to emit maint category

	end

	return error_flag;															-- no match so return unmodified <error_flag>

end



--[[--------------------------< L I N E _ I T E M S >----------------------------------------------------------



support function for infoboxen functions



This function handles all infobox ship parameters that are 'line items' (label followed by value) because all

of these sorts of parameters are rendered with exacty the same formatting.



params_t{} is a table of tables where the params_t{} keys are the template's parameter names.  The params_t{}

values are sequences where [1] is an index number that defines where in the rendering the label/value pair is

positioned and [2] is the label that will be rendered when the parameter has a value.



This indexing is used because params_t{} is not a sequence and because pairs() does not necessarily return the

'next' k/v pair.



This function spins through params_t{} and writes html for parameters that have assigned values into temp_t{}

according to the 'index' value in the associated sequance table.  When parameters are missing or empty, this

function writes an empty string into the associated location in lines_t{} so that lines_t{} can be concatenated

into a string value that is returned to the calling function.



args_t is the arguments table from the template frame.



]]



local function line_items (args_t, params_t)

	local lines_t = {};															-- a sequence table of rendered label/value html lines; one for each key in params_t{}



	for k, v in pairs (params_t) do												-- k is templat e parameter name; v is a sequence table with index and associated label

		local temp_t = {}														-- initialize/reinitialize for next line item

		if not args_tk then													-- if no assigned value then

			lines_tv1]] = '';													-- set to empty string for concatenation

		else

			table.insert (temp_t, '<tr style="vertical-align:top;"><td style="font-weight: bold">');	-- open the line item row and cell

			table.insert (temp_t, v2]);										-- add parameter's label text

			table.insert (temp_t, '</td><td>');									-- close that cell and open the parameter value cell

			table.insert (temp_t, _unbulleted_list (args_tk]));				-- add the parameter's value; formatted as unordered list if appropriate

			table.insert (temp_t, '</td></tr>\n');								-- close that cell and this row

		end

		lines_tv1]] = table.concat (temp_t);									-- concatenate and put the line item in the lines sequence table at the index position

	end



	return table.concat (lines_t);												-- make a big string of line items and done



end





--[[--------------------------< I N F O B O X _ S H I P _ C A R E E R >----------------------------------------



implements {{Infobox ship career}}



{{#invoke:WPSHIPS utilities|infobox_ship_career}}



]]



local function infobox_ship_career (frame)

	local args_t = get_args (frame);											-- get a table of all parameters in the template call

	local html_out_t = {};														-- html table text goes here

	local error_flag = false;													-- controls emission of maint category when synonymous parameters are both set

	

	args_t'Hide header' = args_t'Hide header' and args_t'Hide header']:lower();	-- set to lowercase if set

	

	error_flag = synonym_check (args_t, 'Ship stricken', 'Ship struck', error_flag);	-- error if both synonymous parameters set

	error_flag = synonym_check (args_t, 'Ship honours', 'Ship honors', error_flag);

	

	if 'yes' ~= args_t'Hide header' then										-- |Hide header=yes then no header

		if not ('title' == args_t'Hide header']) then							-- |Hide header=title then no title bar

			table.insert (html_out_t, '<tr><th colspan="2" ');

			table.insert (html_out_t, styles.header_bar);						-- style from WPMILHIST

			table.insert (html_out_t, '>History</th></tr>\n');

		end



		if args_t'Ship country' and args_t'Ship flag' then

			table.insert (html_out_t, '<tr><th height="30" colspan="2" style="background-color:#B0C4DE;text-align:left;padding-left:2px;vertical-align:middle;font-size:110%;">');

			table.insert (html_out_t, _infobox_ship_flag (args_t'Ship flag']));

			table.insert (html_out_t, '<span style="padding-left:1em">');

			table.insert (html_out_t, args_t'Ship country']);

			table.insert (html_out_t, '</span>');

			table.insert (html_out_t, '</th></tr>\n');

		elseif args_t'Ship country' then

			table.insert (html_out_t, '<tr><th height="30" colspan="2" style="background-color:#B0C4DE;text-align:center;vertical-align:middle;font-size:110%;">');

			table.insert (html_out_t, args_t'Ship country']);

			table.insert (html_out_t, '</th></tr>\n');

		elseif args_t'Ship flag' then

			table.insert (html_out_t, '<tr><th height="30" colspan="2" style="background-color:#B0C4DE;padding-left:2px;">');

			table.insert (html_out_t, _infobox_ship_flag (args_t'Ship flag']));

			table.insert (html_out_t, '</th></tr>\n');

		end

	end



	table.insert (html_out_t, line_items (args_t, data.infobox_career_params_t));	-- add all of the rest of the template's html



--mw.logObject (table.concat (html_out_t));

	return table.concat (html_out_t);											-- make a big string and done

end





--[[--------------------------< I N F O B O X _ S H I P _ C H A R A C T E R I S T I C S >----------------------



implements {{Infobox ship characteristics}}



{{#invoke:WPSHIPS utilities|infobox_ship_characteristics}}



]]



local function infobox_ship_characteristics (frame)

	local args_t = get_args (frame);											-- get a table of all parameters in the template call

	local html_out_t = {};														-- html table text goes here

	local error_flag = false;													-- controls emission of maint category when synonymous parameters are both set

	

	args_t'Hide header' = args_t'Hide header' and args_t'Hide header']:lower();	-- set to lowercase if set

	

	error_flag = synonym_check (args_t, 'Ship armour', 'Ship armor', error_flag);	-- error if both synonymous parameters set

	error_flag = synonym_check (args_t, 'Ship draught', 'Ship draft', error_flag);



	if 'yes' ~= args_t'Hide header' then										-- |Hide header=yes then no header

		table.insert (html_out_t, '<tr><th colspan="2" ');

		table.insert (html_out_t, styles.header_bar);							-- style from WPMILHIST

		table.insert (html_out_t, '>General characteristics');

		if args_t'Header caption' then

			table.insert (html_out_t, ' ');

			table.insert (html_out_t, args_t'Header caption']);

		end

		table.insert (html_out_t, '</th></tr>\n');

	end



	table.insert (html_out_t, line_items (args_t, data.infobox_characteristics_params_t));	-- add all of the rest of the template's html



--mw.logObject (table.concat (html_out_t));

	return table.concat (html_out_t);											-- make a big string and done

end





--[[--------------------------< I N F O B O X _ S H I P _ C L A S S _ O V E R V I E W >------------------------



implements {{Infobox ship class overview}}



{{#invoke:WPSHIPS utilities|infobox_ship_class_overview}}



]]



local function infobox_ship_class_overview (frame)

	local args_t = get_args (frame);											-- get a table of all parameters in the template call

	local html_out_t = {};														-- html table text goes here

	

	args_t'Hide header' = args_t'Hide header' and args_t'Hide header']:lower();

	

	if 'yes' ~= args_t'Hide header' then										-- |Hide header=yes then no header

		table.insert (html_out_t, '<tr><th colspan="2" ');

		table.insert (html_out_t, styles.header_bar);							-- style from WPMILHIST

		table.insert (html_out_t, '>Class overview</th></tr>\n');

	end



	table.insert (html_out_t, line_items (args_t, data.infobox_class_overview_params_t));	-- add all of the rest of the template's html



--mw.logObject (table.concat (html_out_t));

	return table.concat (html_out_t);											-- make a big string and done

end





--[[--------------------------< I S _ P L I M S O L L _ F I L E N A M E >--------------------------------------



validate cite plimsoll |filename=<filename>



<filename> format is: YYvssss.pdf where

	YY - least significant two digits of four-digit first year in a two-year range

		30 -> 1930–1931

		allowed values are all integers between and including 30–45

	v - a single lowercase letter 'a' or 'b'

		'a' -> volume I

		'b' -> volume II

	ssss - scan number begins with 0001

		odd numbered scans have English headings

		even numbered scans have French headings



	{{#invoke:WPSHIPS utilities|is_plimsoll_filename|{{{filename}}}}}



]]



local function is_plimsoll_filename (frame)

	local args_t = get_args (frame);											-- get a table of all parameters in the invoke



	local year, volume, scan;

	if args_t1 then															-- this to avoid script errors when args_t[1] missing

		year, volume, scan = args_t1]:match ('(%d%d)(%l)(%d%d%d%d)%.[Pp][Dd][Ff]');	-- get the various parts

	end

	if not year then return nil end												-- nil when no match so we're done

	year = tonumber (year);

	scan = tonumber (scan);

	if (30 > year) or (45 < year) then return nil end

	if not (('a' == volume) or ('b' == volume)) then return nil end

	if (1 > scan) then return nil end



	return true;

end





--[[--------------------------< S E T _ P L I M S O L L _ D A T E >--------------------------------------------



create two-year range from first two digits in |filename=<filename>

	30 -> 1930–1931



	{{#invoke:WPSHIPS utilities|set_plimsoll_date|{{{filename}}}}}



]]



local function set_plimsoll_date (frame)

	local args_t = get_args (frame);											-- get a table of all parameters in the invoke



	if not args_t1 then

		return nil;

	end



	local year = args_t1]:match ('(%d%d)%l%d%d%d%d');							-- get the intial year

	

	year = 1900 + tonumber (year);												-- make it a four-digit year

	return string.format ('%d–%d', year, year + 1);								-- and then add one for the second year in the range

end





--[[--------------------------< S E T _ P L I M S O L L _ S U B T I T L E >-----------------------------------1



used in {{cite plimsoll}}



return appropriate subtitle string given |subtitle=<keyword>

		

	{{#invoke:WPSHIPS utilities|set_plimsoll_subtitle|{{{subtitle}}}}}



]]



local function set_plimsoll_subtitle (frame)

	local subtitle = get_args (frame)[1];										-- get the subtitle parameter

	

	if not subtitle then

		return nil;

	end

	

	if not data.subtitles_tsubtitle then

		return '&#58; ' .. subtitle;											-- not predefined so return whatever |subtitle= holds with leading ': '

	end

	

	return '&#58; ' .. data.subtitles_tsubtitle];								-- return predefined subtitle with leading ': '

end





--[[--------------------------< S E T _ P L I M S O L L _ U R L >----------------------------------------------



create plimsoll url from |filename=<filename>



	{{#invoke:WPSHIPS utilities|set_plimsoll_url|{{{filename}}}}}



]]



local function set_plimsoll_url (frame)

	local args_t = get_args (frame);											-- get a table of all parameters in the invoke



	if args_t1 then

		return string.format ('https://plimsoll.southampton.gov.uk/shipdata/pdfs/%s/%s',

			args_t1]:match ('(%d%d)%l%d%d%d%d'),								-- get the year path portion from <filename>

			args_t1]);															-- append <filename> onto the end, and done

	end

end





--[[--------------------------< S C L A S S >------------------------------------------------------------------



implements {{sclass}} and {{sclass2}}



{{#invoke:WPSHIPS utilities|sclass}}



]]



local function sclass (frame)

	local args_t = get_args (frame);

	local parent = frame:getParent();

	local template = parent:getTitle():gsub ('^Template:', ''):lower();			-- get the name of the template that called this module (includes namespace so strip that)

	

	local class_name = args_t1];												-- names to make it easier to understand

	local ship_type = args_t2];

	local format = args_t3];

	local ship_type_dab = args_t4];

	local class_name_dab = args_t5];

	local no_cat = 'yes' == args_t'no-cat'];									-- make a boolean



	if not class_name then														-- when omitted, abandon with error message

		return error_msg_make ('missing', template, 'class name', no_cat);

	end



	if not ship_type then														-- when omitted, abandon with error message

		return error_msg_make ('missing', template, 'ship type', no_cat);

	end



	if format then

		if tonumber (format) then												-- if <format> has a value that is a number

			format = tonumber (format);											-- make it a number for comparisons

			if 5 < format then													-- is <format> outside of allowed range

				return error_msg_make ('format', template, format, no_cat);

			end

		else																	-- <format> could not be converted to a number

			return error_msg_make ('format', template, format, no_cat);

		end

	end

			

	local out_t = {};															-- output goes here

	table.insert (out_t, '[[');													-- open the wikilink

	table.insert (out_t, class_name);											-- build the wikilink to the class article

	table.insert (out_t, '-class ');

	table.insert (out_t, ship_type);											-- add ship type

	if class_name_dab then														-- when class article is disambiguated

		table.insert (out_t, ' (');												-- add the disambiguator

		table.insert (out_t, class_name_dab);

		table.insert (out_t, ')');

	end

	table.insert (out_t, '|');

	if 'sclass' == template:gsub ('/sandbox', '') then							-- strip '/sandbox' if present; class named for a member of the class

		table.insert (out_t, '\'\'');											-- class name is italicized

		table.insert (out_t, class_name);

		table.insert (out_t, '\'\'');

	else

		table.insert (out_t, class_name);										-- class name is a common attribute; plain text

	end



	if not format or (3 == format) then											-- when format is omitted, same as format #3

		table.insert (out_t, '-class]] [[');									-- open ship-type wikilink

		if ship_type_dab then													-- when ship-type article is disambiguated

			table.insert (out_t, ship_type);									-- add ship type

			table.insert (out_t, ' (');											-- and the disambiguator

			table.insert (out_t, ship_type_dab);

			table.insert (out_t, ')|');											-- dab is not displayed so insert a pipe and

		end

		table.insert (out_t, ship_type);										-- add ship type

		table.insert (out_t, ']]');												-- close ship-type wikilink

	end



	if 0 == format then															-- no separate ship-type wikilink

		table.insert (out_t, '-class]]');

	end

	

	if 1 == format then															-- no separate ship-type wikilink

		table.insert (out_t, '-class ');

		table.insert (out_t, ship_type);

		table.insert (out_t, ']]');

	end

	

	if 2 == format then															-- ship-type is not wikilinked

		table.insert (out_t, '-class]] ');

		table.insert (out_t, ship_type);

	end

	

	if 4 == format then															-- noun form; no ship type

		table.insert (out_t, ' class]]');

	end

	

	if 5 == format then															-- class name only; no '-class' annotation

		table.insert (out_t, ']]');

	end

	

	return table.concat (out_t);

end





--[[--------------------------< E X P O R T S >----------------------------------------------------------------

]]



return {

	cite_danfs_title = cite_danfs_title,										-- external entry points for templates and invokes

	hnsa = hnsa,

	infobox_ship_career = infobox_ship_career,

	infobox_ship_characteristics = infobox_ship_characteristics,

	infobox_ship_class_overview = infobox_ship_class_overview,

	infobox_ship_flag = infobox_ship_flag,

	is_plimsoll_filename = is_plimsoll_filename,

	navsource = navsource,

	sclass = sclass,

	set_plimsoll_subtitle = set_plimsoll_subtitle,

	set_plimsoll_date = set_plimsoll_date, 

	set_plimsoll_url = set_plimsoll_url,

	ship = ship,																-- experiment

	ship_prefix_templates = ship_prefix_templates,								-- experiment

	ship_name_format = ship_name_format,

	unbulleted_list = unbulleted_list,

	

	_infobox_ship_flag = _infobox_ship_flag,									-- external entry points from another module

	_ship_name_format = do_ship_name_format,

	_synonym_check = synonym_check,

	_unbulleted_list = _unbulleted_list,

	}