コンテンツにスキップ

モジュール:CS1 identifiers

半永久的に拡張半保護されているモジュール

require ('strict');

local get_args = require ('Module:Arguments').getArgs;
local identifiers = require ('Module:Citation/CS1/Identifiers');
local utilities = require ('Module:Citation/CS1/Utilities');
	local in_array = utilities.in_array;										-- import functions from Module:Citation/CS1/Utilities
	local is_set = utilities.is_set;
	local make_sep_list = utilities.make_sep_list;
	local set_message = utilities.set_message;
	local substitute = utilities.substitute;

local cfg = mw.loadData ('Module:CS1_identifiers/Configuration');

utilities.set_selected_modules (cfg);											-- so that functions in Utilities can see the selected cfg tables
identifiers.set_selected_modules (cfg, utilities);								-- so that functions in Identifiers can see the selected cfg tables and selected Utilities module

local Frame;																	-- local copy of <frame> from main(); nil else
local this_page = mw.title.getCurrentTitle();									-- used to limit categorization to certain namepsaces

local no_cat;																	-- used to limit categorization to certain namespaces
if cfg.uncategorized_namespaces[this_page.namespace] then						-- is this page's namespace id one of the uncategorized namespace ids?
	no_cat = true;																-- set no_cat; this page will not be categorized
end
for _, v in ipairs (cfg.uncategorized_subpages) do								-- cycle through page name patterns
	if this_page.text:match (v) then											-- test page name against each pattern
		no_cat = true;															-- set no_cat; this page will not be categorized
		break;																	-- bail out if one is found
	end
end


--[[--------------------------< E R R _ M E S S A G E _ C O N V E R T >----------------------------------------

converts cs1|2 error message to a message suitable for this module.

converted error messages do not name a parameter as is done in cs1|2.  The help link links to the template page
not to a help-namespace page.  The prefix is rewritten to name the offending template; not a cs1|2 template.

adds template specific category.

done this way because the identifier functions in Module:Citation/CS1/Identifiers create properly formatted
messages with correct html for styling.

]]

local function err_message_convert (message_prefix, messages, categories, _template)
	table.sort (messages);														-- sort the error messages list; sorting includes wrapping <span> and <code> tags; hidden-error sorts ahead of visible-error
	for i in ipairs(messages) do
		messages[i] = messages[i]:gsub (cfg.messages['help page link'] .. '#[^%|]+', substitute ('Template:$1', _template));	-- rewrite help text
	end
	messages[1] = messages[1]:gsub ('(%b<>)', substitute ('$1$2: ', {'%1', message_prefix}), 1);	-- %1 is the opening span tag; insert <message_prefix>
	
	for i in ipairs(categories) do
		categories[i] = substitute (cfg.messages['cat wikilink'], categories[i]);
	end
	local category = no_cat and '' or table.concat (categories);				-- limited to certain namespaces 
	return substitute ('$1$2', {table.concat (messages, '; '), category});		-- make a big string and done
end


--[[--------------------------< M A I N T _ M E S S A G E _ C O N V E R T >------------------------------------

converts cs1|2 maintenance message to a message suitable for this module.

converted maintenance messages have a prefix suitable for the rendered template.  The 'link' text links to the
an appropriate maintenance category

]]

local function maint_message_convert (message_prefix, messages, _template)
	--message_raw = message_raw:gsub ('CS1 maint: ', '');							-- strip cs1-specific prefix from cat name

	local messages2 = {};
	for i, message_raw in ipairs(messages) do									-- カテゴリ名が'no_cat'の場合は除外する
		if message_raw ~= 'no_cat' then table.insert(messages2, message_raw) end
	end
	if #messages2 == 0 then return '' end										-- 'no_cat'しかなければ、何もしない
	
	table.sort (messages2);														-- sort the maintenance messages list
	
	messages = {};
	for i, message_raw in ipairs(messages2) do
		table.insert(messages, substitute ('$1$2 ($3)', {
				message_raw,
				no_cat and '' or substitute (cfg.messages['cat wikilink'], message_raw),-- the category link; limited to certain namespaces
				substitute (cfg.messages[':cat wikilink'], message_raw)}				-- links to the maint cat, just as cs1|2 links to its maint cats
				));
	end
	messages[1] = substitute ('$1: $2', {message_prefix, messages[1]});		-- add the template prefix
	return substitute (cfg.presentation['hidden-maint'], table.concat (messages, ' '));	-- the maint message text
end


--[[--------------------------< P A R A M S _ G E T >----------------------------------------------------------

extract enumerated parameters from <args_t> where the enumerator is <i>.  enumerator is always the last character
of the parameter name (doi-broken-date1 not doi1-broken-date)

special case the enumerator is 1: prefer non-enumerated parameters

returns a table of same-enumerator parameters (without enumerator) 

note: <i> is a number

]]

local function params_get (args_t, i)
--mw.logObject (args_t, 'args_t')
	local params_t = {};														-- selected parameters go in this table
	if 1 == i then																-- special case when enumerator (<i>) is 1
		for k, v in pairs (args_t) do											-- for each parameter
			if 'number' == type (k) then										-- if this is a positional parameter
				if 1 == k then													-- and its the first positional parameter
					params_t[1] = v;											-- save it
				end
			else																-- here for named parameters
				local enum = k:match ('%d+$');									-- extract the enumerator from the parameter's name; nil else
				if (not enum) or (1 == tonumber (enum)) then					-- when not enumerated or when enumerator is 1
					k = k:gsub ('%d+$', '');									-- remove the enumerator from parameter name
					params_t[k] = v;											-- and save this parameter
				end
			end
		end
	else
		for k, v in pairs (args_t) do											-- for each parameter
			if 'number' == type (k) then										-- if this is a positional parameter
				if i == k then													-- and is the desired positional parameter
					params_t[1] = v;											-- save it (as index number 1; not as index <i>)
				end
			else																-- here for named parameters
				local enum = k:match ('%d+$');									-- extract the parameter's enumerator; nil else
				if enum and (i == tonumber (enum)) then							-- when enumerated and the enumerator is same as requested
					k = k:gsub ('%d+$', '');									-- remove the enumerator from parameter name
					params_t[k] = v;											-- and save this parameter
				end
			end
		end
	end
--mw.logObject (params_t, 'params_t')
	return params_t;															-- and done
end


--[[--------------------------< R E N D E R _ F I N A L >------------------------------------------------------

this function applies cs1|2 template style sheet to a rendered identifier or error message

]]

local function render_final (output)
	if not Frame then															-- not set when this module called from another module
		Frame = mw.getCurrentFrame();											-- get the calling module's frame so that we can call extensionTag()
	end

	return substitute ('$1$2', {
		Frame:extensionTag ('templatestyles', '', {src='Module:Citation/CS1/styles.css'}),	-- apply templatestyles
		output																	-- to the rendered identifier or error message
		});

end


--[[--------------------------< _ M A I N >--------------------------------------------------------------------

entry point when called from another module; example:
	local rendered_identifier = require ('Module:CS1 identifiers')._main ({'10.4231/sommat', _template = 'doi', ['doi-access'] = 'free', ['doi-broken-date'] = 'June 2025'});

supported identifier templates are:
	{{arxiv}}	{{asin}}	{{bibcode}}	{{biorxiv}}		{{citeseerx}}	{{doi}}
	{{hdl}}		{{isbn}}	{{ismn}}	{{issn}}		{{jfm}}			{{jstor}}
	{{medrxiv}}	{{mr}}		{{oclc}}	{{ol}}			{{osti}}		{{pmc}}
	{{pmid}}	{{sbn}}		{{ssrn}}	{{s2cid}}		{{zbl}}

<args_t> is a table of all parameters needed to properly render the identifier

]]

local function _main (args_t)
	local ID_list_t = {};														-- sequence table of rendered identifiers
	local ID_list_coins_t = {};													-- table of identifiers and their values from args; key is same as cfg.id_handlers's key; COinS not supported in this module
	local template_name = args_t._template;
	local id_name = args_t._id or template_name
	local message_prefix;

	if not template_name then
		error ('|_template= にテンプレート名を入力してください');				-- a message for template writers; not seen by users
	end
	
	if not cfg.id_handlers[id_name:upper()] then
		error ('|_id= に有効な識別子名を入力してください');
	end

	id_name = id_name:lower();													-- force lower case
	message_prefix = substitute ('<code class="cs1-code">&#x7B;{[[Template:$1|$1]]}}</code>', template_name);

	local yesno = function () return nil end
	if args_t.plainlink or args_t.link or args_t['no-tracking'] or args_t['template-doc-demo'] then
		yesno = require('Module:Yesno');										-- plainlinkとlinkの読み取りに従来通りModule:Yesnoを使うが、必要なときのみ読み込む
		for k, v in pairs {plainlink = args_t.plainlink, link = args_t.link} do	-- yesnoで認識しない場合
			if v and type(yesno(v)) ~= type(true) then
				set_message ('err_invalid_param_val', {k, v});
			end
		end

		if yesno(args_t['no-tracking']) or yesno(args_t['template-doc-demo']) then no_cat = true end	-- 追跡カテゴリの抑制
		args_t['no-tracking'] = nil;
		args_t['template-doc-demo'] = nil;
	end
	
	local rendered_id;															-- a single rendered id worked on here
	local rendered_ids_t = {};													-- individual rendered ids go here
	
	local i = 1;																-- initialize the indexer for the repeat loop
	repeat																		-- loop until no 
		local params_t = params_get (args_t, i);								-- get the parameters associated with enumerator <i>
		params_t[id_name] = params_t[1] or params_t['id'];						-- assign value from {{{1}}} or |id= to |<identifier>=
		
		params_t._template = nil;												-- unset as no longer needed
		params_t._id = nil;
		params_t.plainlink = nil;
		params_t.link = nil;
		params_t.leadout = nil;
		
		if not params_t[id_name] then											-- in case params_t[1] and params_t.id are nil
			params_t[id_name] = '';												-- set <id> to empty string
			return render_final (												-- make an error message and done
				substitute ('<span class="cs1-visible-error citation-comment">$1: 識別子の値を入力してください ($2)</span>$3', {
					message_prefix,
					substitute ('[[Template:$1|' .. cfg.messages['help page label'] .. ']]', template_name),
					no_cat and '' or '[[Category:モジュール:CS1 identifiersのエラー]]'
				})
			);
		end

		do																		-- 重複引数
			local used_params_t = {};
			if args_t[i] then table.insert (used_params_t, utilities.wrap_style ('parameter', tostring(i))) end
			if args_t['id' .. i] then table.insert (used_params_t, utilities.wrap_style ('parameter', 'id' .. tostring(i))) end
			if i == 1 and args_t['id'] then table.insert (used_params_t, utilities.wrap_style ('parameter', 'id')) end

			if #used_params_t > 1 then
				set_message ('err_redundant_parameters', {make_sep_list (#used_params_t, used_params_t)});
			end
		end
		
		params_t[1] = nil;														-- unset these as no longer needed
		params_t.id = nil;
		
		for k, v in pairs (params_t) do											-- 不明な引数
			if not in_array (k, {'doi-broken-date', 'pmc-embargo-date', 'class', 'asin-tld', id_name, cfg.id_handlers[id_name:upper()].custom_access,}) then-- idに対応しない引数を指定した場合のエラーはID_supportにまかせる
				set_message ('err_parameter_ignored', {k});
				params_t[k] = nil;
			end
		end

		local DoiBroken = params_t['doi-broken-date'];							-- {{doi}} only
		local Embargo = params_t['pmc-embargo-date'];							-- {{pmc}} only
		local Class = params_t['class'];										-- {{arxiv}} only
		local AsinTLD = params_t['asin-tld'];									-- {{asin}} only
		
		local error_list = {};													-- 日付の変換: /Date_validationは必要なときのみ読み込む
		if id_name == 'doi' and is_set(DoiBroken) then
			local validation = require ('Module:Citation/CS1/Date_validation');
			validation.set_selected_modules (cfg, utilities);
			
			local date_parameters_list = {['doi-broken-date'] = {val = DoiBroken, name = 'doi-broken-date'}};
			validation.dates(date_parameters_list, {}, error_list);
			
			if 0 == #error_list then
				validation.reformat_dates (date_parameters_list, 'ja');
				DoiBroken = date_parameters_list['doi-broken-date'].val;
			end
		elseif id_name == 'pmc' and is_set(Embargo) then
			local validation = require ('Module:Citation/CS1/Date_validation');
			local _;
			validation.set_selected_modules (cfg, utilities);
			_, Embargo = validation.dates({['pmc-embargo-date'] = {val = Embargo, name = 'pmc-embargo-date'}}, {}, error_list);
		end
		if 0 ~= #error_list then
			set_message ('err_bad_date', {make_sep_list (#error_list, error_list)});	
		end
		
		local ID_support = {													-- エラーカテゴリは共通のものを使う
			{AsinTLD, 'ASIN', 'err_parameter_ignored', 'asin-tld'},				
			{DoiBroken, 'DOI', 'err_parameter_ignored', 'doi-broken-date'},
			{Embargo, 'PMC', 'err_parameter_ignored', 'pmc-embargo-date'},
			{Class, 'ARXIV', 'err_parameter_ignored', 'class'},
			}
		
		ID_list_t, ID_list_coins_t = identifiers.identifier_lists_get (params_t, {DoiBroken = DoiBroken, Embargo = Embargo, Class = Class, ASINTLD = AsinTLD}, ID_support);
		rendered_id = ID_list_t[1];

		if utilities.z.error_msgs_t[1] then
			rendered_id = substitute ('$1 $2', {
				rendered_id,
				err_message_convert (message_prefix, utilities.z.error_msgs_t, utilities.z.error_cats_t, template_name, no_cat),
				});
		end
		if utilities.z.maint_cats_t[1] then
			rendered_id = substitute ('$1 $2', {
				rendered_id,
				maint_message_convert (message_prefix, utilities.z.maint_cats_t, template_name, no_cat),
				});
		end
		
		if in_array (id_name, {'oclc', 'osti', 'pmc', 'pmid', 'rfc', 'ssrn', 's2cid'}) then	-- 識別子の上限を読み込めなかった場合
			if cfg.id_limits_data_load_fail and not no_cat then					-- boolean true when load failed
				rendered_id = rendered_id .. substitute (cfg.messages['cat wikilink'], cfg.error_conditions.maint_id_limit_load_fail.category);	-- done here because this maint cat emits no message
			end
		end
		
		utilities.z.error_msgs_t = {};											-- reset these
		utilities.z.error_cats_t = {};
		utilities.z.maint_cats_t = {};
		
		if (1 ~= i) or ((1 == i) and yesno(args_t.plainlink) ) then				-- no label for 2nd... identifiers; when |plainlink=yes then no label for first identifier; 
			local separator = cfg.id_handlers[id_name:upper()].separator;		-- get the identifier label separator
			rendered_id = rendered_id:gsub ('^%[%[.-|.-%]%]', '');				-- strip cs1-supplied label
			rendered_id = rendered_id:gsub ('^' .. separator, '');				-- and strip the label separator
		elseif 1 == i then														-- here for first identifier; |plainlink= not set
			if false == yesno(args_t.link) then										-- |link=no then do not link identifier label
				rendered_id = rendered_id:gsub ('^%[%[.-|(.-)%]%]', '%1');		-- strip wikilink markup from cs1-supplied label
			end
		end
		table.insert (rendered_ids_t, rendered_id);								-- save the rendered identifier
		
		i = i + 1;																-- bump the indexer
		until not (args_t[i] or args_t['id' .. i]);								-- end of repeat loop

	local list_separator = ', ';												-- identifier separator for a list of identifiers
	local leadout = args_t.leadout and mw.ustring.gsub (args_t.leadout, '^(%a.*)', ' %1 ') or nil;	-- insert leading space if first character is a letter; add trailing space

	if leadout then																-- extra text goes between last two identifiers in the list
		return render_final (mw.text.listToText (rendered_ids_t, list_separator, leadout));	-- make a list, add templatestyles, and done
	end

	return render_final (table.concat (rendered_ids_t, list_separator));		-- make a list, add templatestyles, and done
end


--[[--------------------------< M A I N >----------------------------------------------------------------------

entry point from an invoke

	{{#invoke:CS1 identifiers|main|_template=<identifier name>}}
	
]]

local function main (frame)
	Frame = frame;																-- make a copy for rendering final output
	local args_t = get_args (frame);											-- extract the arguments

	return _main (args_t);														-- render the identifier and done
end


--[[--------------------------< F R A M E O N L Y >----------------------------------------------------------------------

entry point from an invoke

	{{#invoke:CS1 identifiers|main|_template=<identifier name>|<identifier>}}

{{Citation/identifier}}で使用
	
]]

local function frameOnly (frame)
	Frame = frame;																-- make a copy for rendering final output
	local args_t = get_args (frame, {frameOnly = true});						-- extract the arguments

	return _main (args_t);														-- render the identifier and done
end


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

return {
	main = main,																-- entry point from an invoke (in a template usually)
	
	_main = _main,																-- entry point when called from another module
	
	frameOnly = frameOnly
	
}