<?xml version="1.0"?>
<feed xmlns="http://www.w3.org/2005/Atom" xml:lang="en">
	<id>https://battleint.net/index.php?action=history&amp;feed=atom&amp;title=Module%3AFormat_ISBN</id>
	<title>Module:Format ISBN - Revision history</title>
	<link rel="self" type="application/atom+xml" href="https://battleint.net/index.php?action=history&amp;feed=atom&amp;title=Module%3AFormat_ISBN"/>
	<link rel="alternate" type="text/html" href="https://battleint.net/index.php?title=Module:Format_ISBN&amp;action=history"/>
	<updated>2026-11-13T19:04:39Z</updated>
	<subtitle>Revision history for this page on the wiki</subtitle>
	<generator>MediaWiki 1.44.0</generator>
	<entry>
		<id>https://battleint.net/index.php?title=Module:Format_ISBN&amp;diff=176681&amp;oldid=prev</id>
		<title>imported&gt;Deadfire: creation</title>
		<link rel="alternate" type="text/html" href="https://battleint.net/index.php?title=Module:Format_ISBN&amp;diff=176681&amp;oldid=prev"/>
		<updated>2025-01-12T00:29:10Z</updated>

		<summary type="html">&lt;p&gt;creation&lt;/p&gt;
&lt;p&gt;&lt;b&gt;New page&lt;/b&gt;&lt;/p&gt;&lt;div&gt;require (&amp;#039;strict&amp;#039;);&lt;br /&gt;
&lt;br /&gt;
local data = mw.loadData (&amp;#039;Module:Format ISBN/data&amp;#039;);							-- fetch separator positioning data&lt;br /&gt;
	local hyphen_pos_t = data.hyphen_pos_t;										-- the hyphen positioning data k/v table&lt;br /&gt;
	local index_t = data.index_t;												-- an index sequence into the hyphen positioning data table; used by binary_search()&lt;br /&gt;
	local idx_count = data.count;												-- from count = #index_t; in ~/data; used by binary_search()&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
--[[--------------------------&amp;lt; B I N A R Y _ S E A R C H &amp;gt;----------------------------------------------------&lt;br /&gt;
&lt;br /&gt;
do a binary search for the hyphen positioning data for &amp;lt;target_isbn&amp;gt; in &amp;lt;hyphen_pos_t&amp;gt; using its index sequence&lt;br /&gt;
&amp;lt;index_t&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
accepts one input &amp;lt;target_isbn&amp;gt; (a string) which it converts to a number&lt;br /&gt;
&lt;br /&gt;
returns index into &amp;lt;hyphen_pos_t&amp;gt; as a number when proper formatting is found; nil else&lt;br /&gt;
&lt;br /&gt;
]]&lt;br /&gt;
&lt;br /&gt;
local function binary_search (target_isbn)&lt;br /&gt;
	target_isbn = tonumber (target_isbn);										-- convert to number because index_t[x] values are numbers&lt;br /&gt;
&lt;br /&gt;
	if (index_t[1] &amp;gt;= target_isbn) or (index_t[idx_count] &amp;lt; target_isbn) then	-- invalid; out of range; 9780000000000 to whatever the last value is&lt;br /&gt;
		return;																	-- TODO: return something meaningful?&lt;br /&gt;
	end&lt;br /&gt;
	&lt;br /&gt;
	local idx_bot = 1;															-- initialize to index 1 (first element in &amp;lt;index_t&amp;gt;)&lt;br /&gt;
	local idx_top = idx_count;													-- initialize to index of last element in &amp;lt;index_t&amp;gt;&lt;br /&gt;
	&lt;br /&gt;
	while idx_bot ~= idx_top do&lt;br /&gt;
		local idx_mid = math.ceil ((idx_bot + idx_top) / 2);					-- get the mid-point in the index sequence&lt;br /&gt;
		if index_t[idx_mid] &amp;gt;= target_isbn then									-- when mid-point index value is greater than or equal to the target isbn&lt;br /&gt;
			if index_t[idx_mid-1] &amp;lt; target_isbn then							-- and when the preceding &amp;lt;index_t&amp;gt; value is less than the target isbn&lt;br /&gt;
				return index_t[idx_mid];										-- we found the correct mapping for &amp;lt;target&amp;gt; isbn; return index into &amp;lt;hyphen_pos_t&amp;gt;&lt;br /&gt;
			end&lt;br /&gt;
			idx_top = idx_mid - 1;												-- adjust &amp;lt;idx_top&amp;gt;&lt;br /&gt;
		else&lt;br /&gt;
			idx_bot = idx_mid;													-- adjust &amp;lt;idx_bot&amp;gt;&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
	mw.logObject (&amp;#039;didn\&amp;#039;t find formatting for isbn: &amp;#039; .. target_isbn);			-- just in case for the nonce&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
--[[--------------------------&amp;lt; C O N V E R T _ T O _ I S B N 1 0 &amp;gt;--------------------------------------------&lt;br /&gt;
&lt;br /&gt;
convert 13-digit isbn to 10-digit isbn;  removes 978 GS1 prefix and recalculates the check digit&lt;br /&gt;
&lt;br /&gt;
takes a single input; the 13-digit isbn as a string without separators&lt;br /&gt;
&lt;br /&gt;
assumes that the GS1 prefix is 978; there is no mapping between isbn10 and 979-prefixed isbn13.  calling functions&lt;br /&gt;
are required to ensure that &amp;lt;isbn13&amp;gt; is a properly formed string of 13 digits (no separators) that begins with 978.&lt;br /&gt;
&lt;br /&gt;
]]&lt;br /&gt;
&lt;br /&gt;
local function convert_to_isbn10 (isbn13)&lt;br /&gt;
	local isbn9 = isbn13:sub (4, 12);											-- get the 9 digits of &amp;lt;isbn13&amp;gt; that follow the &amp;#039;978&amp;#039; GS1 prefix (drop the check digit)&lt;br /&gt;
&lt;br /&gt;
	local check = 0;															-- initialize the check digit calculation&lt;br /&gt;
	local i = 1;																-- index&lt;br /&gt;
	for j=10, 2, -1 do															-- &amp;lt;j&amp;gt; is weighting for each of the 9 digits; counting down, left to right&lt;br /&gt;
		check = check + tonumber (isbn9:sub (i, i)) * j;						-- accumulate the sum the weighted-digit-products&lt;br /&gt;
		i = i + 1;																-- bump the index&lt;br /&gt;
	end&lt;br /&gt;
&lt;br /&gt;
	check = check % 11;															-- remainder of the weighted-digit-products divided by 11&lt;br /&gt;
&lt;br /&gt;
	if 0 == check then&lt;br /&gt;
		return isbn9 .. &amp;#039;0&amp;#039;;													-- special case&lt;br /&gt;
	else&lt;br /&gt;
		check = 11 - check;														-- calculate the check digit&lt;br /&gt;
		return isbn9 ..  ((10 == check) and &amp;#039;X&amp;#039; or check);						-- when &amp;lt;check&amp;gt; is ten, use &amp;#039;X&amp;#039;; &amp;lt;check&amp;gt; else&lt;br /&gt;
	end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
--[[--------------------------&amp;lt; C O N V E R T _ T O _ I S B N 1 3 &amp;gt;--------------------------------------------&lt;br /&gt;
&lt;br /&gt;
convert 10-digit isbn to 13-digit isbn;  adds 978 GS1 prefix and recalculates the check digit&lt;br /&gt;
&lt;br /&gt;
takes a single input; the 10-digit isbn as a string (no separators)&lt;br /&gt;
&lt;br /&gt;
]]&lt;br /&gt;
&lt;br /&gt;
local function convert_to_isbn13 (isbn10)&lt;br /&gt;
	local isbn12 = &amp;#039;978&amp;#039;.. isbn10:sub(1, 9);									-- concatenate &amp;#039;978&amp;#039; with first 9 digits of &amp;lt;isbn10&amp;gt; (drop the check digit)&lt;br /&gt;
	local check = 0;															-- initialize the check digit calculation&lt;br /&gt;
	for i=1, 12 do																-- for the first 12 digits (&amp;#039;978&amp;#039; and 9 others)&lt;br /&gt;
		check = check + tonumber (isbn12:sub (i, i)) * (3 - (i % 2) * 2);		-- accumulate checksum&lt;br /&gt;
	end&lt;br /&gt;
	return isbn12 .. ((10 - (check % 10)) %10);									-- extract check digit from checksum; append and done&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
--[[--------------------------&amp;lt; _ F O R M A T _ I S B N &amp;gt;------------------------------------------------------&lt;br /&gt;
&lt;br /&gt;
Module entry point when require()&amp;#039;d into another module&lt;br /&gt;
&lt;br /&gt;
takes five inputs:&lt;br /&gt;
	&amp;lt;isbn_str&amp;gt; – isbn as a string&lt;br /&gt;
	&amp;lt;show_err_msg&amp;gt;: boolean: when true, shows error message returned from check_isbn(); no message else&lt;br /&gt;
	&amp;lt;separator&amp;gt;: boolean: when true, use space character as separator; hyphen else&lt;br /&gt;
	&amp;lt;template_name&amp;gt;: supplied by the template for use in error messaging&lt;br /&gt;
	&amp;lt;output_format&amp;gt;: a value of 10 or 13 dictates the format of the output; other values ignored&lt;br /&gt;
&lt;br /&gt;
returns formatted sbn, isbn10, or isbn13 (whichever was the input or per |out=) on success; initial &amp;lt;isbn_str&amp;gt; else&lt;br /&gt;
&lt;br /&gt;
]]&lt;br /&gt;
&lt;br /&gt;
local function _format_isbn (isbn_str, show_err_msg, separator, output_format, template_name)&lt;br /&gt;
	if (not isbn_str) or (&amp;#039;&amp;#039; == isbn_str) then&lt;br /&gt;
		return &amp;#039;&amp;#039;;																-- empty or nil input? empty output&lt;br /&gt;
	end&lt;br /&gt;
&lt;br /&gt;
	local isbn_str_raw = isbn_str;												-- this will be the return value if unable to format&lt;br /&gt;
	isbn_str = isbn_str:gsub (&amp;#039;[^%dX]&amp;#039;, &amp;#039;&amp;#039;);									-- strip all formatting (spaces and hyphens) from the isbn/sbn&lt;br /&gt;
&lt;br /&gt;
	local flags = {};															-- a convenient place for flag stuff&lt;br /&gt;
	if &amp;#039;13&amp;#039; == output_format then												-- set a flag for output format; ignored when &amp;lt;isbn_str&amp;gt; is an sbn&lt;br /&gt;
		flags.out13 = true;&lt;br /&gt;
	elseif  &amp;#039;10&amp;#039; == output_format then&lt;br /&gt;
		flags.out10 = true;&lt;br /&gt;
	end&lt;br /&gt;
&lt;br /&gt;
	if 9 == #isbn_str then														-- looks like an sbn?&lt;br /&gt;
		isbn_str = &amp;#039;0&amp;#039; .. isbn_str;												-- convert to isbn10&lt;br /&gt;
		flags.sbn = true;														-- set a flag&lt;br /&gt;
	end&lt;br /&gt;
	&lt;br /&gt;
	local err_msg = require (&amp;quot;Module:Check isxn&amp;quot;).check_isbn ({args={isbn_str, template_name=template_name}});	-- does &amp;lt;isbn_str&amp;gt; &amp;#039;look&amp;#039; like a valid isbn?  does not check ranging&lt;br /&gt;
	if &amp;#039;&amp;#039; ~= err_msg then														-- when there is an error message&lt;br /&gt;
		if show_err_msg then													-- and we are showing error messages&lt;br /&gt;
			return isbn_str_raw,  err_msg;										-- return our input and the message&lt;br /&gt;
		else&lt;br /&gt;
			return isbn_str_raw;												-- not showing error messages; return our input without the message&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
&lt;br /&gt;
	if 13 == #isbn_str and flags.out10 then										-- if isbn13 but we want an isbn10 output&lt;br /&gt;
		flags.isbn10_check_digit = (convert_to_isbn10 (isbn_str)):sub (-1);		-- calculate and extract the isbn10 check digit for later&lt;br /&gt;
	end&lt;br /&gt;
	&lt;br /&gt;
	if 10 == #isbn_str then														-- if isbn10 or sbn&lt;br /&gt;
		flags.isbn10_check_digit = isbn_str:sub (-1);							-- extract the check digit for later&lt;br /&gt;
		isbn_str = convert_to_isbn13 (isbn_str);								-- convert isbn10 to isbn13 for formatting&lt;br /&gt;
	end&lt;br /&gt;
	&lt;br /&gt;
	local index = binary_search (isbn_str);										-- look for the formatting that applies to &amp;lt;isbn_str&amp;gt;&lt;br /&gt;
	if index then																-- if found&lt;br /&gt;
		local format_t = hyphen_pos_t[index];									-- get the formatting sequence&lt;br /&gt;
		local result_t = {isbn_str:sub (1, 3)};									-- init &amp;lt;result_t&amp;gt; with prefix; the GS1 prefix element (&amp;#039;978&amp;#039; or &amp;#039;979&amp;#039;)&lt;br /&gt;
		local digit_ptr = 4;													-- initialize to point at registration group element&lt;br /&gt;
		&lt;br /&gt;
		for _, n in ipairs (format_t) do										-- loop through the formatting sequence to build a sequence of isbn13 elements&lt;br /&gt;
			table.insert (result_t, isbn_str:sub (digit_ptr, digit_ptr+n-1));	-- add the digits from &amp;lt;isbn_str&amp;gt;[&amp;lt;digit_ptr&amp;gt;] to &amp;lt;isbn_str&amp;gt;[&amp;lt;digit_ptr+n-1&amp;gt;] to &amp;lt;result_t&amp;gt; sequence&lt;br /&gt;
			digit_ptr = digit_ptr + n;											-- advance the digit pointer&lt;br /&gt;
		end&lt;br /&gt;
		table.insert (result_t, isbn_str:sub (13));								-- and add the check digit element to &amp;lt;result_t&amp;gt;&lt;br /&gt;
&lt;br /&gt;
		isbn_str = table.concat (result_t, separator and &amp;#039; &amp;#039; or &amp;#039;-&amp;#039;);			-- assemble formatted &amp;lt;isbn_str&amp;gt; with space or hyphen (default) separators&lt;br /&gt;
&lt;br /&gt;
		if flags.isbn10_check_digit then										-- if we saved the check digit from an sbn or isbn10&lt;br /&gt;
			if flags.sbn then													-- when input is an sbn&lt;br /&gt;
				isbn_str = isbn_str:gsub (&amp;#039;^978%-0%-&amp;#039;, &amp;#039;&amp;#039;):gsub (&amp;#039;%d$&amp;#039;, flags.isbn10_check_digit);	-- remove GS1 prefix element and registration group element; restore check digit&lt;br /&gt;
			else																-- when input is an isbn10&lt;br /&gt;
				if not flags.out13 then&lt;br /&gt;
					isbn_str = isbn_str:gsub (&amp;#039;^978%-&amp;#039;, &amp;#039;&amp;#039;):gsub (&amp;#039;%d$&amp;#039;, flags.isbn10_check_digit);	-- remove GS1 prefix element; restore check digit&lt;br /&gt;
				end&lt;br /&gt;
			end&lt;br /&gt;
		end&lt;br /&gt;
&lt;br /&gt;
		return isbn_str;														-- return formatted &amp;lt;isbn_str&amp;gt;&lt;br /&gt;
	end&lt;br /&gt;
&lt;br /&gt;
	return isbn_str_raw;														-- should never actually be reached; but, if we do, return original input string&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
--[[--------------------------&amp;lt; F O R M A T _ P L A I N &amp;gt;------------------------------------------------------&lt;br /&gt;
&lt;br /&gt;
plain text output:	&lt;br /&gt;
	no linking to Special:BookSources&lt;br /&gt;
	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&lt;br /&gt;
&lt;br /&gt;
	|separator=space – render formatted ISBN with spaces instead of hyphens&lt;br /&gt;
	|out= – takes either of 10 or 13 to specify the output format if different from the default&lt;br /&gt;
	&lt;br /&gt;
{{#invoke:format isbn|format_plain}}&lt;br /&gt;
&lt;br /&gt;
]]&lt;br /&gt;
&lt;br /&gt;
local function format_plain (frame)&lt;br /&gt;
	local args_t = require (&amp;#039;Module:Arguments&amp;#039;).getArgs (frame);				-- get template and invoke parameters&lt;br /&gt;
	local isbn_str = args_t[1];&lt;br /&gt;
	local separator = &amp;#039;space&amp;#039; == args_t.separator;								-- boolean: when true use space separator; hyphen else&lt;br /&gt;
	local output_format = args_t.out;											-- 10 or 13 to convert input format to the other for output&lt;br /&gt;
&lt;br /&gt;
	return _format_isbn (isbn_str, nil, separator, output_format);				-- no error messaging&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
--[[--------------------------&amp;lt; F O R M A T _ L I N K &amp;gt;--------------------------------------------------------&lt;br /&gt;
&lt;br /&gt;
linked text output:	&lt;br /&gt;
	links to Special:BookSources&lt;br /&gt;
	&lt;br /&gt;
	|suppress-errors=yes – suppress error messages&lt;br /&gt;
	|separator=space – render formatted ISBN with spaces instead of hyphens&lt;br /&gt;
	|out= – takes either of 10 or 13 to specify the output format if different from the default&lt;br /&gt;
	&lt;br /&gt;
{{#invoke:format isbn|format_linked|template=Format ISBN link}}&lt;br /&gt;
&lt;br /&gt;
]]&lt;br /&gt;
&lt;br /&gt;
local function format_linked (frame)&lt;br /&gt;
	local args_t = require (&amp;#039;Module:Arguments&amp;#039;).getArgs (frame);				-- get template and invoke parameters&lt;br /&gt;
	local isbn_str = args_t[1];&lt;br /&gt;
	local show_err_msg = &amp;#039;yes&amp;#039; ~= args_t[&amp;#039;suppress-errors&amp;#039;];					-- always show errors unless |suppress-errors=yes&lt;br /&gt;
	local separator = &amp;#039;space&amp;#039; == args_t.separator;								-- boolean: when true use space separator; hyphen else&lt;br /&gt;
	local output_format = args_t.out;											-- 10 or 13 to convert input format to the other for output&lt;br /&gt;
&lt;br /&gt;
	local formatted_isbn_str, err_msg = _format_isbn (isbn_str, show_err_msg, separator, output_format, args_t.template_name);		-- show error messages unless suppressed&lt;br /&gt;
	if err_msg then&lt;br /&gt;
		return formatted_isbn_str .. &amp;#039; &amp;#039; .. err_msg;							-- return unformatted, unlinked isbn and error message&lt;br /&gt;
	else&lt;br /&gt;
		return &amp;#039;[[Special:BookSources/&amp;#039; ..isbn_str .. &amp;#039;|&amp;#039; .. formatted_isbn_str ..&amp;#039;]]&amp;#039;;	-- return formatted and linked isbn&lt;br /&gt;
	end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
--[[--------------------------&amp;lt; E X P O R T S &amp;gt;----------------------------------------------------------------&lt;br /&gt;
]]&lt;br /&gt;
&lt;br /&gt;
return {&lt;br /&gt;
	format_plain = format_plain,												-- template entry points&lt;br /&gt;
	format_linked = format_linked,&lt;br /&gt;
	&lt;br /&gt;
	_format_isbn = _format_isbn,												-- entry point when this module require()&amp;#039;d into another module&lt;br /&gt;
	}&lt;/div&gt;</summary>
		<author><name>imported&gt;Deadfire</name></author>
	</entry>
</feed>