Theme: iWiki Log in Register

Diff: Module:Check isxn

Comparing revision #1 (2022-11-03 23:27:58) with revision #2 (2023-02-03 04:17:56).

OldNew
--[[
--[[
This code is derived from the ISXN validation code at Module:Citation/CS1.  It allows validating ISBN,
This code is derived from the ISXN validation code at Module:Citation/CS1.  It allows validating ISBN,
ISMN, and ISSN without invoking a citation template.
ISMN, and ISSN without invoking a citation template.
]]
]]
local p = {}
local p = {}
--[[--------------------------< E R R _ M S G _ S U P L _ T >--------------------------------------------------
--[[--------------------------< E R R _ M S G _ S U P L _ T >--------------------------------------------------
error message supplements for check_isbn(); adapted from a similarly named table at Module:Citation/CS1/Configuration
error message supplements for check_isbn(); adapted from a similarly named table at Module:Citation/CS1/Configuration
]]
]]
local err_msg_supl_t = {
local err_msg_supl_t = {
	['char'] = 'invalid character',
	['char'] = 'invalid character',
	['check'] = 'checksum',
	['check'] = 'checksum',
	['form'] = 'invalid form',
	['form'] = 'invalid form',
	['group'] = 'invalid group id',
	['group'] = 'invalid group id',
	['length'] = 'length',
	['length'] = 'length',
	['prefix'] = 'invalid prefix',
	['prefix'] = 'invalid prefix',
	}
	}
--[[--------------------------< IS _ V A L I D _ I S X N >-----------------------------------------------------
--[[--------------------------< IS _ V A L I D _ I S X N >-----------------------------------------------------
ISBN-10 and ISSN validator code calculates checksum across all isbn/issn digits including the check digit. ISBN-13 is checked in check_isbn().
ISBN-10 and ISSN validator code calculates checksum across all isbn/issn digits including the check digit. ISBN-13 is checked in check_isbn().
If the number is valid the result will be 0. Before calling this function, issbn/issn must be checked for length and stripped of dashes,
If the number is valid the result will be 0. Before calling this function, issbn/issn must be checked for length and stripped of dashes,
spaces and other non-isxn characters.
spaces and other non-isxn characters.
]]
]]
local function is_valid_isxn (isxn_str, len)
local function is_valid_isxn (isxn_str, len)
	local temp = 0;
	local temp = 0;
	isxn_str = { isxn_str:byte(1, len) };	-- make a table of byte values '0' → 0x30 .. '9'  → 0x39, 'X' → 0x58
	isxn_str = { isxn_str:byte(1, len) };	-- make a table of byte values '0' → 0x30 .. '9'  → 0x39, 'X' → 0x58
	len = len+1;							-- adjust to be a loop counter
	len = len+1;							-- adjust to be a loop counter
	for i, v in ipairs( isxn_str ) do		-- loop through all of the bytes and calculate the checksum
	for i, v in ipairs( isxn_str ) do		-- loop through all of the bytes and calculate the checksum
		if v == string.byte( "X" ) then		-- if checkdigit is X (compares the byte value of 'X' which is 0x58)
		if v == string.byte( "X" ) then		-- if checkdigit is X (compares the byte value of 'X' which is 0x58)
			temp = temp + 10*( len - i );	-- it represents 10 decimal
			temp = temp + 10*( len - i );	-- it represents 10 decimal
		else
		else
			temp = temp + tonumber( string.char(v) )*(len-i);
			temp = temp + tonumber( string.char(v) )*(len-i);
		end
		end
	end
	end
	return temp % 11 == 0;					-- returns true if calculation result is zero
	return temp % 11 == 0;					-- returns true if calculation result is zero
end
end
--[[--------------------------< IS _ V A L I D _ I S X N  _ 1 3 >----------------------------------------------
--[[--------------------------< IS _ V A L I D _ I S X N  _ 1 3 >----------------------------------------------
ISBN-13 and ISMN validator code calculates checksum across all 13 isbn/ismn digits including the check digit.
ISBN-13 and ISMN validator code calculates checksum across all 13 isbn/ismn digits including the check digit.
If the number is valid, the result will be 0. Before calling this function, isbn-13/ismn must be checked for length
If the number is valid, the result will be 0. Before calling this function, isbn-13/ismn must be checked for length
and stripped of dashes, spaces and other non-isxn-13 characters.
and stripped of dashes, spaces and other non-isxn-13 characters.
]]
]]
local function is_valid_isxn_13 (isxn_str)
local function is_valid_isxn_13 (isxn_str)
	local temp=0;
	local temp=0;
	
	
	isxn_str = { isxn_str:byte(1, 13) };										-- make a table of byte values '0' → 0x30 .. '9'  → 0x39
	isxn_str = { isxn_str:byte(1, 13) };										-- make a table of byte values '0' → 0x30 .. '9'  → 0x39
	for i, v in ipairs( isxn_str ) do
	for i, v in ipairs( isxn_str ) do
		temp = temp + (3 - 2*(i % 2)) * tonumber( string.char(v) );				-- multiply odd index digits by 1, even index digits by 3 and sum; includes check digit
		temp = temp + (3 - 2*(i % 2)) * tonumber( string.char(v) );				-- multiply odd index digits by 1, even index digits by 3 and sum; includes check digit
	end
	end
	return temp % 10 == 0;														-- sum modulo 10 is zero when isbn-13/ismn is correct
	return temp % 10 == 0;														-- sum modulo 10 is zero when isbn-13/ismn is correct
end
end
--[[--------------------------< C H E C K _ I S B N >------------------------------------------------------------
--[[--------------------------< C H E C K _ I S B N >------------------------------------------------------------
Determines whether an ISBN string is valid.  Implements an ISBN validity check for {{ISBN}}, {{ISBNT}}, {{SBN}}, and
Determines whether an ISBN string is valid.  Implements an ISBN validity check for {{ISBN}}, {{ISBNT}}, {{SBN}}, and
{{Format ISBN}}.
{{Format ISBN}}.
]]
]]
local function check_isbn (isbn_str, frame)
local function check_isbn (isbn_str, frame)
	local function return_result (check, err_type)								-- local function to render the various error returns
	local function return_result (check, err_type)								-- local function to render the various error returns
		if not check then														-- <check> false when there is an error
		if not check then														-- <check> false when there is an error
			local template = ((frame.args.template_name and '' ~= frame.args.template_name) and frame.args.template_name) or nil;	-- get template name
			local template = ((frame.args.template_name and '' ~= frame.args.template_name) and frame.args.template_name) or nil;	-- get template name
			if not template then
			if not template then
				return '<span class="error" style="font-size:100%">&nbsp;calling template requires template_name parameter</span>';
				return '<span class="error" style="font-size:100%">&nbsp;calling template requires template_name parameter</span>';
			end
			end
			local out_t = {'<span class="error" style="font-size:100%">'};		-- open the error message span
			local out_t = {'<span class="error" style="font-size:100%">'};		-- open the error message span
			table.insert (out_t, '&nbsp;Parameter error in {{[[Template:');		-- open 'template' markup; open wikilink with Template namespace
			table.insert (out_t, '&nbsp;Parameter error in {{[[Template:');		-- open 'template' markup; open wikilink with Template namespace
			table.insert (out_t, template);										-- template name wikilink
			table.insert (out_t, template);										-- template name wikilink
			table.insert (out_t, '|');											-- its pipe
			table.insert (out_t, '|');											-- its pipe
			table.insert (out_t, template);										-- wikilink label
			table.insert (out_t, template);										-- wikilink label
			table.insert (out_t, ']]}}:&nbsp;');								-- close wikilink; close 'template' markup
			table.insert (out_t, ']]}}:&nbsp;');								-- close wikilink; close 'template' markup
			table.insert (out_t, err_type);										-- type of isbn error
			table.insert (out_t, err_type);										-- type of isbn error
			table.insert (out_t, '</span>')										-- close the error message span
			table.insert (out_t, '</span>')										-- close the error message span
			if 0 == mw.title.getCurrentTitle().namespace then					-- categorize only when this template is used in mainspace
			if 0 == mw.title.getCurrentTitle().namespace then					-- categorize only when this template is used in mainspace
				local category = table.concat ({'[[Category:Pages with ISBN errors]]'});
				local category = table.concat ({'[[Category:Pages with ISBN errors]]'});
				table.insert (out_t, category);
				table.insert (out_t, category);
			end
			end
			return table.concat (out_t);										-- make a big string and done
			return table.concat (out_t);										-- make a big string and done
		end
		end
	return '';																	-- no error, return an empty string
	return '';																	-- no error, return an empty string
	end
	end
	if nil ~= isbn_str:match ('[^%s-0-9X]') then
	if nil ~= isbn_str:match ('[^%s-0-9X]') then
		return return_result (false, err_msg_supl_t.char);						-- fail if isbn_str contains anything but digits, hyphens, or the uppercase X
		return return_result (false, err_msg_supl_t.char);						-- fail if isbn_str contains anything but digits, hyphens, or the uppercase X
	end
	end
	local id = isbn_str:gsub ('[%s-]', '');										-- remove hyphens and whitespace
	local id = isbn_str:gsub ('[%s-]', '');										-- remove hyphens and whitespace
	local len = id:len();
	local len = id:len();
 
 
	if len ~= 10 and len ~= 13 then
	if len ~= 10 and len ~= 13 then
		return return_result (false, err_msg_supl_t.length);					-- fail if incorrect length
		return return_result (false, err_msg_supl_t.length);					-- fail if incorrect length
	end
	end
	if len == 10 then
	if len == 10 then
		if id:match ('^%d*X?$') == nil then										-- fail if isbn_str has 'X' anywhere but last position
		if id:match ('^%d*X?$') == nil then										-- fail if isbn_str has 'X' anywhere but last position
			return return_result (false, err_msg_supl_t.form);									
			return return_result (false, err_msg_supl_t.form);									
		end
		end
		if id:find ('^63[01]') then												-- 630xxxxxxx and 631xxxxxxx are (apparently) not valid isbn group ids but are used by amazon as numeric identifiers (asin)
		if id:find ('^63[01]') then												-- 630xxxxxxx and 631xxxxxxx are (apparently) not valid isbn group ids but are used by amazon as numeric identifiers (asin)
			return return_result (false, err_msg_supl_t.group);					-- fail if isbn-10 begins with 630/1
			return return_result (false, err_msg_supl_t.group);					-- fail if isbn-10 begins with 630/1
		end
		end
		return return_result (is_valid_isxn (id, 10), err_msg_supl_t.check);	-- pass if isbn-10 is numerically valid (checksum)
		return return_result (is_valid_isxn (id, 10), err_msg_supl_t.check);	-- pass if isbn-10 is numerically valid (checksum)
	else
	else
		if id:match ('^%d+$') == nil then
		if id:match ('^%d+$') == nil then
			return return_result (false, err_msg_supl_t.char);					-- fail if ISBN-13 is not all digits
			return return_result (false, err_msg_supl_t.char);					-- fail if ISBN-13 is not all digits
		end
		end
		if id:match ('^97[89]%d*$') == nil then
		if id:match ('^97[89]%d*$') == nil then
			return return_result (false, err_msg_supl_t.prefix);				-- fail when ISBN-13 does not begin with 978 or 979
			return return_result (false, err_msg_supl_t.prefix);				-- fail when ISBN-13 does not begin with 978 or 979
		end
		end
		if id:match ('^9790') then
		if id:match ('^9790') then
			return return_result (false, err_msg_supl_t.group);					-- group identifier '0' is reserved to ISMN
			return return_result (false, err_msg_supl_t.group);					-- group identifier '0' is reserved to ISMN
		end
		end
		return return_result (is_valid_isxn_13 (id), err_msg_supl_t.check);		-- pass if isbn-10 is numerically valid (checksum)
		return return_result (is_valid_isxn_13 (id), err_msg_supl_t.check);		-- pass if isbn-10 is numerically valid (checksum)
	end
	end
end
end
--[[--------------------------< C H E C K _ I S M N >------------------------------------------------------------
--[[--------------------------< C H E C K _ I S M N >------------------------------------------------------------
Determines whether an ISMN string is valid.  Similar to isbn-13, ismn is 13 digits begining 979-0-... and uses the
Determines whether an ISMN string is valid.  Similar to isbn-13, ismn is 13 digits begining 979-0-... and uses the
same check digit calculations.  See http://www.ismn-international.org/download/Web_ISMN_Users_Manual_2008-6.pdf
same check digit calculations.  See http://www.ismn-international.org/download/Web_ISMN_Users_Manual_2008-6.pdf
section 2, pages 9–12.
section 2, pages 9–12.
]]
]]
local function check_ismn (id, error_string)
local function check_ismn (id, error_string)
	local text;
	local text;
	local valid_ismn = true;
	local valid_ismn = true;
	id=id:gsub( "[%s-–]", "" );													-- strip spaces, hyphens, and endashes from the ismn
	id=id:gsub( "[%s-–]", "" );													-- strip spaces, hyphens, and endashes from the ismn
	if 13 ~= id:len() or id:match( "^9790%d*$" ) == nil then					-- ismn must be 13 digits and begin 9790
	if 13 ~= id:len() or id:match( "^9790%d*$" ) == nil then					-- ismn must be 13 digits and begin 9790
		valid_ismn = false;
		valid_ismn = false;
	else
	else
		valid_ismn=is_valid_isxn_13 (id);										-- validate ismn
		valid_ismn=is_valid_isxn_13 (id);										-- validate ismn
	end
	end
	return valid_ismn and '' or error_string
	return valid_ismn and '' or error_string
end
end
--[[--------------------------< I S S N >----------------------------------------------------------------------
--[[--------------------------< I S S N >----------------------------------------------------------------------
Validate and format an issn.  This code fixes the case where an editor has included an ISSN in the citation but has separated the two groups of four
Validate and format an issn.  This code fixes the case where an editor has included an ISSN in the citation but has separated the two groups of four
digits with a space.  When that condition occurred, the resulting link looked like this:
digits with a space.  When that condition occurred, the resulting link looked like this:
	|issn=0819 4327 gives: [http://www.worldcat.org/issn/0819 4327 0819 4327]  -- can't have spaces in an external link
	|issn=0819 4327 gives: [http://www.worldcat.org/issn/0819 4327 0819 4327]  -- can't have spaces in an external link
	
	
This code now prevents that by inserting a hyphen at the issn midpoint.  It also validates the issn for length and makes sure that the checkdigit agrees
This code now prevents that by inserting a hyphen at the issn midpoint.  It also validates the issn for length and makes sure that the checkdigit agrees
with the calculated value.  Incorrect length (8 digits), characters other than 0-9 and X, or checkdigit / calculated value mismatch will all cause a check issn
with the calculated value.  Incorrect length (8 digits), characters other than 0-9 and X, or checkdigit / calculated value mismatch will all cause a check issn
error message.
error message.
]]
]]
local function check_issn(id, error_string)
local function check_issn(id, error_string)
	local issn_copy = id;		-- save a copy of unadulterated issn; use this version for display if issn does not validate
	local issn_copy = id;		-- save a copy of unadulterated issn; use this version for display if issn does not validate
	local text;
	local text;
	local valid_issn = true;
	local valid_issn = true;
	if not id:match ('^%d%d%d%d%-%d%d%d[%dX]$') then
	if not id:match ('^%d%d%d%d%-%d%d%d[%dX]$') then
		return error_string;
		return error_string;
	end
	end
	
	
	id=id:gsub( "[%s-–]", "" );									-- strip spaces, hyphens, and endashes from the issn
	id=id:gsub( "[%s-–]", "" );									-- strip spaces, hyphens, and endashes from the issn
	if 8 ~= id:len() or nil == id:match( "^%d*X?$" ) then		-- validate the issn: 8 digits long, containing only 0-9 or X in the last position
	if 8 ~= id:len() or nil == id:match( "^%d*X?$" ) then		-- validate the issn: 8 digits long, containing only 0-9 or X in the last position
		valid_issn=false;										-- wrong length or improper character
		valid_issn=false;										-- wrong length or improper character
	else
	else
		valid_issn=is_valid_isxn(id, 8);						-- validate issn
		valid_issn=is_valid_isxn(id, 8);						-- validate issn
	end
	end
	return valid_issn and '' or error_string
	return valid_issn and '' or error_string
end
end
------------------------------< E N T R Y   P O I N T S >--------------------------------------------------====
------------------------------< E N T R Y   P O I N T S >--------------------------------------------------====
function p.check_isbn(frame)
function p.check_isbn(frame)
	return check_isbn(frame.args[1] or frame:getParent().args[1], frame)
	return check_isbn(frame.args[1] or frame:getParent().args[1], frame)
end
end
function p.check_ismn(frame)
function p.check_ismn(frame)
	return check_ismn(frame.args[1] or frame:getParent().args[1], frame.args['error'] or frame:getParent().args['error'] or 'error')
	return check_ismn(frame.args[1] or frame:getParent().args[1], frame.args['error'] or frame:getParent().args['error'] or 'error')
end
end
function p.check_issn(frame)
function p.check_issn(frame)
	return check_issn(frame.args[1] or frame:getParent().args[1], frame.args['error'] or frame:getParent().args['error'] or 'error')
	return check_issn(frame.args[1] or frame:getParent().args[1], frame.args['error'] or frame:getParent().args['error'] or 'error')
end
end
return p
return p