Jump to content

Module:Format ISBN

From Wikipedia, the free encyclopedia
This is an old revision of this page, as edited by Trappist the monk (talk | contribs) at 01:16, 23 April 2023. The present address (URL) is a permanent link to this revision, which may differ significantly from the current revision.

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

local data = mw.loadData ('Module:Format ISBN/data')
local index_t = data.index_t;
local hyphen_pos_t = data.hyphen_pos_t;
local idx_count = data.count;


--[[--------------------------< B I N A R Y _ S E A R C H >----------------------------------------------------

do a binary search for the hyphen positioning data for <target_isbn> in <hyphen_pos_t> using its index sequence
<index_t>.

accepts one input <target_isbn> (a string) which it converts to a number

returns index into <hyphen_pos_t> as a number when proper formatting is found; nil else

]]

local function binary_search (target_isbn)
	target_isbn = tonumber (target_isbn);										-- convert to number

	if (index_t[1] >= target_isbn) or (index_t[idx_count] < target_isbn) then	-- invalid; out of range; 9780000000000 to whatever the last value is
		return;																	-- TODO: return something meaningful?
	end
	
	local idx_bot = 1;															-- initialize to index 1 (first element in <index_t>)
	local idx_top = idx_count;													-- initialize to index of last element in <index_t>
	
	while idx_bot ~= idx_top do
		local idx_mid = math.ceil ((idx_bot + idx_top) / 2);					-- get the mid-point in the index sequence
		if index_t[idx_mid] >= target_isbn then									-- when mid-point index value is greater than or equal to the target isbn (it should be)
			if index_t[idx_mid-1] < target_isbn then							-- and when the preceding <index_t> value is less than the target isbn (it should be)
				return index_t[idx_mid];										-- we found the correct mapping for this <target> isbn; return index into <hyphen_pos_t>
			end
			idx_top = idx_mid - 1;												-- adjust <idx_top>
		else
			idx_bot = idx_mid													-- adjust <idx_bot>
		end
	end
	mw.logObject ('didn\'t find formatting for isbn: ' .. target_isbn);			-- just in case for the nonce
end


--[[--------------------------< C O N V E R T _ T O _ I S B N 1 3 >--------------------------------------------

convert 10-digit isbn to 13-digit isbn;  adds 978 GS1 prefix and recalculates the check digit

]]

local function convert_to_isbn13 (isbn10)
	local isbn12 = '978'.. isbn10:sub(1, 9);									-- concatenate '978' with first 9 digits of <isbn10> (drop the check digit)
	local check = 0;															-- initialize the check digit calculation
	for i=1, 12 do																-- for the first 12 digits ('978' and 9 others)
		check = check + tonumber (isbn12:sub (i, i)) * (3 - (i % 2) * 2);		-- accumulate check digit
	end;
	return isbn12 .. ((10 - (check % 10)) %10);									-- append check digit and done
end


--[[--------------------------< F O R M A T _ I S B N >--------------------------------------------------------

takes five inputs:
	<isbn_str> – isbn as a string
	<link> – boolean: when true, link formatted isbn to Special:BookSources; not linked else
	<show_err_msg>: boolean: when true, shows error message returned from check_isbn(); no message else
	<separator>: boolean: when true, use space character as separator; hyphen else
	<template_name>: supplied by the template for use in error messaging

]]

local function format_isbn (isbn_str, link, show_err_msg, separator, ret_10, template_name)
	if (not isbn_str) or ('' == isbn_str) then
		return '';																-- empty or nil input? empty output
	end

	link = link and link or false;												-- <link> is whatever <link> is or it is false

	isbn_str = tostring (isbn_str);												-- some other module may have fed us an unquoted isbn (as a number)
	local err_msg = require ("Module:Check isxn").check_isbn ({args={isbn_str, template_name=template_name}});	-- does this isbn 'look' like a valid isbn?  does not check ranging
	if '' ~= err_msg then 
		if show_err_msg then
			return isbn_str .. ' ' .. err_msg;									-- when check_isbn() returns an error message and when messages are enabled, return <isbn_str> with the message
		else
			return isbn_str;													-- there was an error but not showing error message, return the isbn as is
		end
	end

	isbn_str = isbn_str:gsub ('[^%dX]', '');									-- strip spaces and hyphens from the isbn

	local isbn10_check_digit;
	if #isbn_str == 10 then
		isbn10_check_digit = isbn_str:sub (-1);									-- extract the check digit for later
		isbn_str = convert_to_isbn13 (isbn_str);								-- convert isbn10 to isbn13
	end		

	local index = binary_search (isbn_str);										-- look for the formatting that applies to this isbn
	if index then																-- if found
		local format_t = hyphen_pos_t[index];									-- get the formatting sequence
		local result_t = {isbn_str:sub (1, 3)};									-- init <result_t> with prefix; the first three digits ('978' or '979')
		local digit_ptr = 4;													-- initialize to point at first digit following the prefix
		
		for _, n in ipairs (format_t) do										-- loop through the formatting sequence
			table.insert (result_t, isbn_str:sub (digit_ptr, digit_ptr+n-1));	-- add the digits from <digit_ptr> to <digit_ptr+n-1> to <result_t> sequence
			digit_ptr = digit_ptr + n;											-- advance the digit pointer
		end
		table.insert (result_t, isbn_str:sub (13));								-- and add the check digit to <result_t>

		local formatted_isbn_str = table.concat (result_t, separator and ' ' or '-');	-- assemble isbn with space separators or hyphens (default)
		if isbn10_check_digit and ret_10 then
			formatted_isbn_str = formatted_isbn_str:gsub ('^978%-', ''):gsub ('%d$', isbn10_check_digit);
		end
		if link then
			return '[[Special:BookSources/' ..isbn_str .. '|' .. formatted_isbn_str ..']]';
		else 
			return formatted_isbn_str;
		end
	end

	return isbn_str;															-- should never actually be reached; but, if we do, return the original text
end


--[[--------------------------< F O R M A T _ P L A I N >------------------------------------------------------

plain text output:	
	no linking to Special:BookSources
	no error message output – on error return input; for use in cs1|2 template |isbn= params, no point in causing confusion due to multiple error messages

	|separator=space – render formatted ISBN with spaces instead of hyphens
	
{{#invoke:format isbn|format_plain|template=Format ISBN}}

]]

local function format_plain (frame)
	local args_t = get_args(frame)
	local isbn_str = args_t[1]
	local separator = 'space' == args_t.separator;
	return format_isbn (isbn_str, nil, nil, separator, nil, args_t.template_name);	-- no linking, no error messaging
end


--[[--------------------------< F O R M A T _ P L A I N 1 0 >--------------------------------------------------

plain text output:	
	no linking to Special:BookSources
	no error message output – on error return input; for use in cs1|2 template |isbn= params, no point in causing confusion due to multiple error messages
	preserves isbn10 when input is isbn10 – does not convert isbn13 to isbn10; isbn13 input -> formatted isbn13 output

	|separator=space – render formatted ISBN with spaces instead of hyphens
	
{{#invoke:format isbn|format_plain10|template_name=Format ISBN10}}

]]

local function format_plain10 (frame)
	local args_t = get_args(frame)
	local isbn_str = args_t[1]
	local separator = 'space' == args_t.separator;
	local ret_10 = true
	return format_isbn (isbn_str, nil, nil, separator, true, args_t.template_name);	-- no linking, no error messaging; return formatted isbn10
end


--[[--------------------------< F O R M A T _ L I N K >--------------------------------------------------------

linked text output:	
	links to Special:BookSources
	
	|suppress-errors=yes – suppress error messages
	|separator=space – render formatted ISBN with spaces instead of hyphens
	
{{#invoke:format isbn|format_linked|template=Format ISBN link}}

]]

local function format_linked (frame)
	local args_t = get_args(frame)
	local isbn_str = args_t[1];
	local show_err_msg = 'yes' ~= args_t['suppress-errors'];					-- always show errors unless |suppress-errors=yes
	local separator = 'space' == args_t.separator;								-- boolean: when true use space separator; hyphen else
	return format_isbn (isbn_str, true, show_err_msg, separator, args_t.template_name)		-- linked output, show error messages unless suppressed
end


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

return {
	format_plain = format_plain,
	format_plain10 = format_plain10,
	format_linked = format_linked,
	}