Mô đun:WPSHIPS utilities

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
local error_map = {																-- 1 is error message; 2 is error category
	'synonymous' = {'has synonymous parameter', 'Pages using infobox ship with synonymous parameters'};
	}
------------------------------------------------------------------------------
assembles an error message from message text, help link, and error category.
local function make_error_msg (msg, cat, no_cat)
	local out = {};
	local category;
	
	table.insert (out, 'Error: ');
	table.insert (out, error_mapmsg1);
	table.insert (out, table.concat ({' (:Category:', error_mapmsg2, '|help)'}));
	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 ({'Category:', error_mapmsg2, }));
	end
	return table.concat (out);
end
----------------------------------------------------------------------------------------------
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
--------------------------------------------------------------------------
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 ------------------------------------------
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 -----------------------------------------
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
	|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 fragments1 .. ' class';								-- for infobox caption do noun form  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  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 -----------------------------------------------
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
	|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
--------------------------------------------------------------------------------------------------
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:
	 is the name of the page at http://hnsa.org/hnsa-ships/
	 (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:
	  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 '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.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 =  .. error_msg .. ;
	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
----------------------------------------------------------------------------------------
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.args1) 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.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 =  .. error_msg .. ;
	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
------------------------------------------------------------------------------------------------
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)
	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 = ' missing name';
	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 = ' missing disambiguator';
	elseif not is_set (prefix) and ('5' == control or '6' == control) then		-- prefix required when control value set to expect it
		error_msg = ' missing prefix';
	elseif '4' == control then													-- displaying only the prefix
		error_msg = 'invalid control parameter: ' .. control;
	elseif is_set (control) then
		if ('number' ~= type (tonumber (control))) or (1 > tonumber (control) or 6 ' .. error_msg ..  .. category;	-- return an error message; don't bother with making a link
	end
	local link_name;
	local link =  .. link_name .. ;
	end
end
--------------------------------------------------------------------------------------------------
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
local function ship (frame)														-- this version not supported from the template yet
	local prefix = mw.text.trim (frame.args1 or );							-- fetch positional parameters into named variables for readability
	local name =  mw.text.trim (frame.args2 or );							-- stripped of leading and trailing whitespace 
	local dab =  mw.text.trim (frame.args3 or );							-- and and set to empty string (is that needed?)
	local control = frame.args4;
	local unlinked_prefix = 'yes' == frame.args.up;
	local unlinked_whole = 'no' == frame.args.link;
	
	return _ship (prefix, name, dab, control, unlinked_prefix, unlinked_whole);
end
--------------------------------------------------------------------------------------
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 message:match ('BbRr%s/*>') then
		category = 'Thể loại:WPSHIPS:Bản mẫu có danh sách lỗi chờ sửa (bot)';
	elseif 0 == mw.title.getCurrentTitle().namespace then							-- only categorize pages in article space
		category = ;
	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
----------------------------------------------------------------------------
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 () then							-- if the parameter value has a list using variants of  tag
			return list_error (, ' list', , param, showerrs);	-- return an error message with maintenance category
		elseif param:match (, 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_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  is required
	local splats = 0;															-- number of splats that start each item in the list
	local item = ;															-- the item text
	
	table.insert (html_table, )	-- this for first  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, );		-- add a new sublist
			table.insert (html_table, List_item_otag .. item .. );		-- and the item
		elseif splats ');								-- close each sub  until level and splats match
				level = level - 1;
			end
			table.insert (html_table, List_item_otag .. item .. );		-- 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 ');										-- close each  until level counted down to zero
		level = level - 1;
	end
	return table.concat (html_table, '\n');										-- return the list as a string
end
----------------------------------------------------------------------------
external entry point
local function unbulleted_list (frame)
	return _unbulleted_list (frame.args1)
end
--=---------------------------------------------------------------------
Output of USA:
	link=
	
Image syntax:
	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
--=---------------------------------------------------------------------
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
--=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
--------------------------------------------------------------------------------
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
table of template parameters and values
UK English parameter name
US English parameter name
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 .. make_error_msg ('synonymous');		-- both are set so append error message with category
		args_tus_param = args_tus_param .. make_error_msg ('synonymous', 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 
end
--------------------------------------------------------------------------------------
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, );	-- open the line item row and cell
			table.insert (temp_t, v2);										-- 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_tk));				-- add the parameter's value; formatted as unordered list if appropriate
			table.insert (temp_t, '\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
--------------------------------------------------------------------
implements 
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, '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
--------------------------------------------------
implements 
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, '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
----------------------------------------------------
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 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
--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 = args_t1:match ('(%d%d)(%l)(%d%d%d%d)%.PpDdFf');	-- get the various parts
	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  scan) then return nil end
	return true;
end
--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_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
--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 ': ' .. subtitle;											-- not predefined so return whatever |subtitle= holds with leading ': '
	end
	
	return ': ' .. data.subtitles_tsubtitle;								-- return predefined subtitle with leading ': '
end
--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 
			args_t1);															-- append  onto the end, and done
	end
end
--------------------------------------------------------------------------------------------
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,
	set_plimsoll_subtitle = set_plimsoll_subtitle,
	set_plimsoll_date = set_plimsoll_date, 
	set_plimsoll_url = set_plimsoll_url,
	ship = ship,																-- experiment
	ship_name_format = ship_name_format,
	unbulleted_list = unbulleted_list,
	}