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, 'Error: ');	--TODO: simplify? 	if template then 		if link_template_name then 			table.insert (out, table.concat ({'{{', template, '}} '})); 		else 			table.insert (out, table.concat ({' '}));				-- TODO: get template names for synonymous parameter errors 		end 	end 	table.insert (out, string.format (error_map_t[msg_idx][1], detail));		-- build error message from base + <detail> 	table.insert (out, table.concat ({' ([[:', error_map_t[msg_idx][2], '|help]])'}));	-- help text on category pages; TODO: help text on template pages also? 	table.insert (out, ''); 	 	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_t[msg_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_t[ship_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_t[ship_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_t[ship_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_t[frag[nat_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 [frag[1]] 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 , , 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  to provide a value for  and to provide a value for |infobox caption= 			 Optional arguments to support : 	|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  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 & and & in prefix 	args.name = args.name:gsub ("'", "\'");									-- replace html appostrophe with the character --	args.name = args.name:gsub ("&", "&"); --	args.name = args.name:gsub ("&", "&"); --	args.name = args.name:gsub ("&", "&");  	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 fragments[1] .. ' 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 "" .. fragments[1] .. " class";							-- for infobox caption do noun form <name> class (no hyphen, no ship type) 		end 		return "" .. fragments[1] .. "-class" .. fragments[2];				-- 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_t[fragments[1]] 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  to provide a value for  and to provide a value for |infobox caption= 			 Optional parameters to support : 	|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  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 = 'Empty name';	-- 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 = '' .. error_msg .. ''; 		else 			error_msg = ;														-- for concatenation 		end 	end  	return name .. error_msg;													-- return name and error message end  --[[--------------------------< H N S A >----------------------------------------------------------------------  Similar to , this code supports  by attempting to construct a link to a ship article at the the Historic Nava Ships Association website.  The template has the form: 	 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: 	<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.args[1]) then 		return 'missing hsna page'; 	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.args[2]) then 		fmt_params.name = pframe.args[2]; 	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.args[2]) then						-- if unable to format the name 		local escaped_name = pframe.args[2]:gsub("([%(%)%.%-])", "%%%1");		-- escape some of the Lua magic characters 		if pframe.args[2] == 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.args[2] .. "";						-- 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 = '' .. error_msg .. ''; 	else 		error_msg = ;															-- unset so it doesn't diplay 	end 	 	local output = { 		'[http://www.hnsa.org/hnsa-ships/', 		pframe.args[1], 		'/ ', 		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  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.args[1]) then 		return 'missing navsource URLcode'; 	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.args[2]) then 		fmt_params.name = pframe.args[2]; 	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.args[2]) then						-- if unable to format the name 		local escaped_name = pframe.args[2]:gsub("([%(%)%.%-])", "%%%1");		-- escape some of the Lua magic characters 		if pframe.args[2] == 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.args[2] .. "";						-- 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 = '' .. error_msg .. ''; 	else 		error_msg = ;															-- unset so it doesn't diplay 	end 	 	local output = { 		'[http://www.navsource.net/archives/', 		pframe.args[1], 		'.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 .  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  points to a redirect 	if target_object then 		link = target_object.fullText;											-- 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 = " \'\'" .. dab .. "\'\'";								-- for prefix with dab display HMS A1. italicize the dab 		else 			dab = ' (' .. 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 .. ' ' .. 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 .. ' ' .. 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 ('^.- ', , 1);						-- remove the prefix and nbsp 		return prefix ..  ' ' .. link_name .. '';		-- add prefix and nbsp to front and done 	else 		return '' .. link_name .. '';						-- construct the wikilink and done 	end end  --[[--------------------------< S H I P >----------------------------------------------------------------------  This is a possible replacement for the template .  It has better error detection and handling.  This function is the externally accessible entry point for template , , , etc  Parameters in the module frame are: 	there are no module frame parameters 	 Parameters in the template frame are: 	 – prefix (HMS, USS, Japanese submarine, etc) 	 – ship's name (required) 	 – disambiguator (year, hull or pennant number, etc) 	 – 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_t[1] or ;												-- fetch positional parameters into named variables for readability 	local name = args_t[2] or ;												-- stripped of leading and trailing whitespace  	local dab = args_t[3] or ;												-- empty positional parameters are nil so convert nil to empty string 	local control = args_t[4]; 	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 , , etc.  It has better error detection and handling.  This function is the externally accessible entry point for those templates  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: 	 – ship's name (required) 	 – disambiguator (year, hull or pennant number, etc) 	 – 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_t[1] or ;												-- stripped of leading and trailing whitespace  	local dab = args_t[2] or ;												-- empty positional parameters are nil so convert nil to empty string 	local control = args_t[3]; 	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 ''.  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 '' 	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 = '%slist error: %s (help)%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  does not render this correctly:  The above renders without proper indents for items marked ** and ***.  If the list is not wrapped in  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:     Name 1  Name 2    Subname 2a      Subname 2a1      Subname 2b    Name 3  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: 	  ]]  local function _unbulleted_list (param) 	local showerrs = true;														-- set to false to hide error messages 	local List_item_otag = '';	-- 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  tag 			return list_error (, '<br /> list', '', param, showerrs);	-- return an error message with maintenance category 		elseif param:match ('<div style="clear:') then 			return list_error (, ' list', '', 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', '', 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_table[1]:gsub ('^%*%s*', ));								-- trim off the splat and any following white space and done 	end 	 	if item_table[1]: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 .. ''); 		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.args[1]) end  --[=[-------------------------< _I N F O B O X _ S H I P _ F L A G >--------------------------------------------  Output of : 	100x35px|alt=|link= 	 Image syntax: 	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 .  ]=]  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 'malformed flag image' 	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.args[1]) then											-- if |Ship flag= not set 		return ;																-- return empty string 	end 	 	return _infobox_ship_flag (frame.args[1]);									-- 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 .  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=  ]=]  local function cite_danfs_title (frame) 	local name; 	local disambiguator; 	local hullnum; 	 	if not is_set (frame.args[1]) then											-- if |title= not set 		return ;																-- return empty string 	end  	name, disambiguator, hullnum = frame.args[1]:match ('(.-)( [XVI]+)( %([^%)]+%))$'); 	if not (name and disambiguator and hullnum) then 		disambiguator = ;														-- empty string for concatenation 		name, hullnum = frame.args[1]:match ('(.-)( %([^%)]+%))$'); 		if not (name and hullnum) then 			hullnum = ;														-- empty string for concatenation 			name, disambiguator = frame.args[1]:match ('(.-)( [XVI]+)$'); 			if not (name and disambiguator) then 				disambiguator = ; 				name = frame.args[1];											-- 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_t[uk_param] and args_t[us_param] then  		args_t[uk_param] = args_t[uk_param] .. error_msg_make ('synonymous');		-- both are set so append error message with category 		args_t[us_param] = args_t[us_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_t[k] then													-- if no assigned value then 			lines_t[v[1]] = ;													-- set to empty string for concatenation 		else 			table.insert (temp_t, '');	-- open the line item row and cell 			table.insert (temp_t, v[2]);										-- add parameter's label text 			table.insert (temp_t, '');									-- close that cell and open the parameter value cell 			table.insert (temp_t, _unbulleted_list (args_t[k]));				-- add the parameter's value; formatted as unordered list if appropriate 			table.insert (temp_t, '\n');								-- close that cell and this row 		end 		lines_t[v[1]] = 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 S _ E M P T Y _ I B O X _ T E M P L A T E >----------------------------------  temporary(?) code to add Category:WPSHIPS: articles with empty infobox templates when empty infobox templates are encountered.  A template is empty when no parameters have assigned values or when the only parameter with an assigned value is |Hide header=  when 'empty', returns the category name; nil else  ]]  local function is_empty_ibox_template (args_t) 	local i = 0; 	for k, v in pairs (args_t) do 		i = i + 1; 	end 	if ((1 == i) and args_t['Hide header']) or (0 == i) then 		return 'Category:WPSHIPS: articles with empty infobox templates'; 	end end  --[[--------------------------< I N F O B O X _ S H I P _ C A R E E R >----------------------------------------  implements   ]]  local function infobox_ship_career (frame) 	local args_t = get_args (frame);											-- get a table of all parameters in the template call  	local err_cat = is_empty_ibox_template (args_t);							-- look for and identify empty templates 	if err_cat then				 		return err_cat;															-- template is empty so return the category and done 	end 	 	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, 'History\n'); 		end  		if args_t['Ship country'] and args_t['Ship flag'] then 			table.insert (html_out_t, ''); 			table.insert (html_out_t, _infobox_ship_flag (args_t['Ship flag'])); 			table.insert (html_out_t, ''); 			table.insert (html_out_t, args_t['Ship country']); 			table.insert (html_out_t, ''); 			table.insert (html_out_t, '\n'); 		elseif args_t['Ship country'] then 			table.insert (html_out_t, ''); 			table.insert (html_out_t, args_t['Ship country']); 			table.insert (html_out_t, '\n'); 		elseif args_t['Ship flag'] then 			table.insert (html_out_t, ''); 			table.insert (html_out_t, _infobox_ship_flag (args_t['Ship flag'])); 			table.insert (html_out_t, '\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   ]]  local function infobox_ship_characteristics (frame) 	local args_t = get_args (frame);											-- get a table of all parameters in the template call  	local err_cat = is_empty_ibox_template (args_t);							-- look for and identify empty templates 	if err_cat then				 		return err_cat;															-- template is empty so return the category and done 	end 	 	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, '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, '\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   ]]  local function infobox_ship_class_overview (frame) 	local args_t = get_args (frame);											-- get a table of all parameters in the template call  	local err_cat = is_empty_ibox_template (args_t);							-- look for and identify empty templates 	if err_cat then				 		return err_cat;															-- template is empty so return the category and done 	end 	 	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, 'Class overview\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  	  ]]  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_t[1] then															-- this to avoid script errors when args_t[1] missing 		year, volume, scan = args_t[1]: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  	  ]]  local function set_plimsoll_date (frame) 	local args_t = get_args (frame);											-- get a table of all parameters in the invoke  	if not args_t[1] then 		return nil; 	end  	local year = args_t[1]: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   return appropriate subtitle string given |subtitle=<keyword> 		 	  ]]  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_t[subtitle] then 		return ': ' .. subtitle;											-- not predefined so return whatever |subtitle= holds with leading ': ' 	end 	 	return ': ' .. data.subtitles_t[subtitle];								-- 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>  	  ]]  local function set_plimsoll_url (frame) 	local args_t = get_args (frame);											-- get a table of all parameters in the invoke  	if args_t[1] then 		return string.format ('https://plimsoll.southampton.gov.uk/shipdata/pdfs/%s/%s', 			args_t[1]:match ('(%d%d)%l%d%d%d%d'),								-- get the year path portion from <filename> 			args_t[1]);															-- append <filename> onto the end, and done 	end end  --[[--------------------------< S C L A S S >------------------------------------------------------------------  implements  and   ]]  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_t[1];												-- names to make it easier to understand 	local ship_type = args_t[2]; 	local format = args_t[3]; 	local ship_type_dab = args_t[4]; 	local class_name_dab = args_t[5]; 	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, '-শ্রেণি '); 	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, '-শ্রেণির]] [[');									-- 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, '-শ্রেণি]]'); 	end 	 	if 1 == format then															-- no separate ship-type wikilink 		table.insert (out_t, '-শ্রেণির '); 		table.insert (out_t, ship_type); 		table.insert (out_t, ']]'); 	end 	 	if 2 == format then															-- ship-type is not wikilinked 		table.insert (out_t, '-শ্রেণির]] '); 		table.insert (out_t, ship_type); 	end 	 	if 4 == format then															-- noun form; no ship type 		table.insert (out_t, ' শ্রেণি]]'); 	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, 	}