
<?xml version="1.0"?>
<feed xmlns="http://www.w3.org/2005/Atom" xml:lang="en">
	<id>https://zoophilia.wiki/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://zoophilia.wiki/index.php?action=history&amp;feed=atom&amp;title=Module%3AFormat_ISBN"/>
	<link rel="alternate" type="text/html" href="https://zoophilia.wiki/index.php?title=Module:Format_ISBN&amp;action=history"/>
	<updated>2026-05-10T21:20:34Z</updated>
	<subtitle>Revision history for this page on the wiki</subtitle>
	<generator>MediaWiki 1.45.3</generator>
	<entry>
		<id>https://zoophilia.wiki/index.php?title=Module:Format_ISBN&amp;diff=133652&amp;oldid=prev</id>
		<title>SockyPaws: Import module from English Wikipedia</title>
		<link rel="alternate" type="text/html" href="https://zoophilia.wiki/index.php?title=Module:Format_ISBN&amp;diff=133652&amp;oldid=prev"/>
		<updated>2024-11-23T12:06:32Z</updated>

		<summary type="html">&lt;p&gt;Import module from English Wikipedia&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;
-- Fetch separator positioning data.&lt;br /&gt;
local data = mw.loadData (&amp;#039;Module:Format ISBN/data&amp;#039;);&lt;br /&gt;
	-- The hyphen positioning data key/value table.&lt;br /&gt;
	local hyphen_pos_t = data.hyphen_pos_t;&lt;br /&gt;
	-- An index sequence into the hyphen positioning data table; used by&lt;br /&gt;
	-- binary_search().&lt;br /&gt;
	local index_t = data.index_t;&lt;br /&gt;
	-- From count = #index_t; in ~/data; used by binary_search().&lt;br /&gt;
	local idx_count = data.count;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
-- BINARY SEARCH&lt;br /&gt;
-- Do a binary search for the hyphen positioning data for &amp;lt;target_isbn&amp;gt; in&lt;br /&gt;
-- &amp;lt;hyphen_pos_t&amp;gt; using its index sequence &amp;lt;index_t&amp;gt;. Accepts one input&lt;br /&gt;
-- &amp;lt;target_isbn&amp;gt; (a string) which it converts to a number and returns index into&lt;br /&gt;
-- &amp;lt;hyphen_pos_t&amp;gt; as a number when proper formatting is found, else nil.&lt;br /&gt;
local function binary_search (target_isbn)&lt;br /&gt;
	-- Convert to number because index_t[x] values are numbers.&lt;br /&gt;
	target_isbn = tonumber (target_isbn);&lt;br /&gt;
&lt;br /&gt;
	-- Invalid/out of range; 9780000000000 to whatever the last value is.&lt;br /&gt;
	if (index_t[1] &amp;gt;= target_isbn) or (index_t[idx_count] &amp;lt; target_isbn) then&lt;br /&gt;
		-- TODO: Return something meaningful?&lt;br /&gt;
		return;&lt;br /&gt;
	end&lt;br /&gt;
	&lt;br /&gt;
	-- Initialize to index 1 (first element in &amp;lt;index_t&amp;gt;).&lt;br /&gt;
	local idx_bot = 1;&lt;br /&gt;
	-- Initialize to index of last element in &amp;lt;index_t&amp;gt;.&lt;br /&gt;
	local idx_top = idx_count;&lt;br /&gt;
&lt;br /&gt;
	while idx_bot ~= idx_top do&lt;br /&gt;
		-- Get the mid-point in the index sequence.&lt;br /&gt;
		local idx_mid = math.ceil ((idx_bot + idx_top) / 2);&lt;br /&gt;
		-- When mid-point index value ≥ the target ISBN…&lt;br /&gt;
		if index_t[idx_mid] &amp;gt;= target_isbn then&lt;br /&gt;
			-- …and the preceding &amp;lt;index_t&amp;gt; &amp;lt; target ISBN.&lt;br /&gt;
			if index_t[idx_mid-1] &amp;lt; target_isbn then&lt;br /&gt;
				-- Found correct mapping for &amp;lt;target&amp;gt; ISBN; return index into&lt;br /&gt;
				-- &amp;lt;hyphen_pos_t&amp;gt;.&lt;br /&gt;
				return index_t[idx_mid];&lt;br /&gt;
			end&lt;br /&gt;
			-- Adjust &amp;lt;idx_top&amp;gt;.&lt;br /&gt;
			idx_top = idx_mid - 1;&lt;br /&gt;
		else&lt;br /&gt;
			-- Adjust &amp;lt;idx_bot&amp;gt;.&lt;br /&gt;
			idx_bot = idx_mid;&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
	-- Just in case, for the nonce.&lt;br /&gt;
	mw.logObject (&amp;#039;didn\&amp;#039;t find formatting for isbn: &amp;#039; .. target_isbn);&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
-- CONVERT TO ISBN-10&lt;br /&gt;
-- Convert a 13-digit ISBN to a 10-digit ISBN; removes 978 GS1 prefix and&lt;br /&gt;
-- recalculates the check digit. Takes a single input: the 13-digit ISBN as a&lt;br /&gt;
-- string without separators. Assumes that the GS1 prefix is 978 as there is no&lt;br /&gt;
-- mapping between ISBN-10 and 979-prefixed ISBN-13s. Calling functions are&lt;br /&gt;
-- required to ensure that &amp;lt;isbn13&amp;gt; is a properly formed string of 13 digits&lt;br /&gt;
-- with no separators that begins with 978.&lt;br /&gt;
local function convert_to_isbn10 (isbn13)&lt;br /&gt;
	-- Get nine digits of &amp;lt;isbn13&amp;gt; following &amp;#039;978&amp;#039; GS1 prefix, drop check digit.&lt;br /&gt;
	local isbn9 = isbn13:sub (4, 12);&lt;br /&gt;
&lt;br /&gt;
	-- Initialize the check digit calculation.&lt;br /&gt;
	local check = 0;&lt;br /&gt;
	-- Index.&lt;br /&gt;
	local i = 1;&lt;br /&gt;
	-- &amp;lt;j&amp;gt; is weighting for each of the nine digits, from left to right.&lt;br /&gt;
	for j=10, 2, -1 do&lt;br /&gt;
		-- Accumulate the sum of the weighted-digit-products.&lt;br /&gt;
		check = check + tonumber (isbn9:sub (i, i)) * j;&lt;br /&gt;
		-- Bump the index.&lt;br /&gt;
		i = i + 1;&lt;br /&gt;
	end&lt;br /&gt;
&lt;br /&gt;
	-- Remainder of the weighted-digit-products, divided by 11.&lt;br /&gt;
	check = check % 11;&lt;br /&gt;
&lt;br /&gt;
	if 0 == check then&lt;br /&gt;
		-- Special case.&lt;br /&gt;
		return isbn9 .. &amp;#039;0&amp;#039;;&lt;br /&gt;
	else&lt;br /&gt;
		-- Calculate the check digit.&lt;br /&gt;
		check = 11 - check;&lt;br /&gt;
		-- When &amp;lt;check&amp;gt; is ten, use &amp;#039;X&amp;#039;; &amp;lt;check&amp;gt; else.&lt;br /&gt;
		return isbn9 ..  ((10 == check) and &amp;#039;X&amp;#039; or check);&lt;br /&gt;
	end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
-- CONVERT TO ISBN13&lt;br /&gt;
-- Convert the ISBN-10 to ISBN13, adding the 978 GS1 prefix, and recalculate the&lt;br /&gt;
-- check digit. Takes single input: 10-digit ISBN with no separators.&lt;br /&gt;
local function convert_to_isbn13 (isbn10)&lt;br /&gt;
	-- Concatenate 978 with first nine digits of &amp;lt;isbn10&amp;gt;, dropping check digit.&lt;br /&gt;
	local isbn12 = &amp;#039;978&amp;#039;.. isbn10:sub(1, 9);&lt;br /&gt;
	-- Initialize the check digit calculation.&lt;br /&gt;
	local check = 0;&lt;br /&gt;
	-- For the first 12 digits (&amp;#039;978&amp;#039; and nine others).&lt;br /&gt;
	for i=1, 12 do&lt;br /&gt;
		-- Accumulate checksum.&lt;br /&gt;
		check = check + tonumber (isbn12:sub (i, i)) * (3 - (i % 2) * 2);&lt;br /&gt;
	end&lt;br /&gt;
	-- Extract check digit from checksum, append and done.&lt;br /&gt;
	return isbn12 .. ((10 - (check % 10)) %10);&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
-- _FORMAT_ISBN&lt;br /&gt;
-- Module entry point when require()&amp;#039;d into another module; takes five inputs:&lt;br /&gt;
-- &amp;lt;isbn_str&amp;gt; (int): ISBN; &amp;lt;show_err_msg&amp;gt; (boolean): when true show error&lt;br /&gt;
-- message returned from check_isbn() else nil; &amp;lt;separator&amp;gt; (boolean): when true&lt;br /&gt;
-- use space character as separator, else hyphen; &amp;lt;template_name&amp;gt; (string):&lt;br /&gt;
-- supplied by template for error messaging; &amp;lt;output_format&amp;gt; (int): a value of&lt;br /&gt;
-- 10 or 13 dictates the format of the output ignoring other values. Returns&lt;br /&gt;
-- formatted SBN, ISBN-10 or ISBN-13 on success, whichever was the input or per&lt;br /&gt;
-- &amp;#039;out&amp;#039; parameter, else initial &amp;lt;isbn_str&amp;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;
		-- Empty or nil input? Empty output.&lt;br /&gt;
		return &amp;#039;&amp;#039;;&lt;br /&gt;
	end&lt;br /&gt;
&lt;br /&gt;
	-- This will be the return value, if unable to format.&lt;br /&gt;
	local isbn_str_raw = isbn_str;&lt;br /&gt;
	-- Strip all formatting (spaces and hyphens) from the ISBN/SBN.&lt;br /&gt;
	isbn_str = isbn_str:gsub (&amp;#039;[^%dX]&amp;#039;, &amp;#039;&amp;#039;);&lt;br /&gt;
&lt;br /&gt;
	-- A convenient place for flag stuff.&lt;br /&gt;
	local flags = {};&lt;br /&gt;
	-- Set a flag for output format; ignored when &amp;lt;isbn_str&amp;gt; is an SBN.&lt;br /&gt;
	if &amp;#039;13&amp;#039; == output_format then&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;
	-- Looks like an SBN?&lt;br /&gt;
	if 9 == #isbn_str then&lt;br /&gt;
		-- Convert to ISBN-10.&lt;br /&gt;
		isbn_str = &amp;#039;0&amp;#039; .. isbn_str;&lt;br /&gt;
		-- Set a flag.&lt;br /&gt;
		flags.sbn = true;&lt;br /&gt;
	end&lt;br /&gt;
	&lt;br /&gt;
	-- Does &amp;lt;isbn_str&amp;gt; &amp;#039;look&amp;#039; like a valid ISBN? Does not check ranging.&lt;br /&gt;
	local err_msg = require (&amp;quot;Module:Check isxn&amp;quot;).check_isbn ({args={isbn_str, template_name=template_name}});&lt;br /&gt;
	-- When there is an error message…&lt;br /&gt;
	if &amp;#039;&amp;#039; ~= err_msg then&lt;br /&gt;
		-- …and we are showing error messages…&lt;br /&gt;
		if show_err_msg then&lt;br /&gt;
			-- Return our input and the message.&lt;br /&gt;
			return isbn_str_raw,  err_msg;&lt;br /&gt;
		else&lt;br /&gt;
			-- Not showing error messages; return our input without the message.&lt;br /&gt;
			return isbn_str_raw;&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
&lt;br /&gt;
	-- If ISBN-13, but we want an ISBN-10 output.&lt;br /&gt;
	if 13 == #isbn_str and flags.out10 then&lt;br /&gt;
		-- Calculate and extract the ISBN-10 check digit for later.&lt;br /&gt;
		flags.isbn10_check_digit = (convert_to_isbn10 (isbn_str)):sub (-1);&lt;br /&gt;
	end&lt;br /&gt;
	&lt;br /&gt;
	-- If ISBN-10 or SBN.&lt;br /&gt;
	if 10 == #isbn_str then&lt;br /&gt;
		-- Extract the check digit for later.&lt;br /&gt;
		flags.isbn10_check_digit = isbn_str:sub (-1);&lt;br /&gt;
		-- Convert ISBN-10 to ISBN-13 for formatting.&lt;br /&gt;
		isbn_str = convert_to_isbn13 (isbn_str);&lt;br /&gt;
	end&lt;br /&gt;
	&lt;br /&gt;
	-- Look for the formatting that applies to &amp;lt;isbn_str&amp;gt;.&lt;br /&gt;
	local index = binary_search (isbn_str);&lt;br /&gt;
	-- If found…&lt;br /&gt;
	if index then&lt;br /&gt;
		-- Get the formatting sequence.&lt;br /&gt;
		local format_t = hyphen_pos_t[index];&lt;br /&gt;
		-- 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 result_t = {isbn_str:sub (1, 3)};&lt;br /&gt;
		-- Initialize to point at registration group element.&lt;br /&gt;
		local digit_ptr = 4;&lt;br /&gt;
		&lt;br /&gt;
		-- Loop through formatting sequence to build ISBN-13 elements.&lt;br /&gt;
		for _, n in ipairs (format_t) do&lt;br /&gt;
			-- Add digits from &amp;lt;isbn_str&amp;gt;[&amp;lt;digit_ptr&amp;gt;] to&lt;br /&gt;
			-- &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;
			table.insert (result_t, isbn_str:sub (digit_ptr, digit_ptr+n-1));&lt;br /&gt;
			-- Advance the digit pointer.&lt;br /&gt;
			digit_ptr = digit_ptr + n;&lt;br /&gt;
		end&lt;br /&gt;
		-- Add the check digit element to &amp;lt;result_t&amp;gt;.&lt;br /&gt;
		table.insert (result_t, isbn_str:sub (13));&lt;br /&gt;
&lt;br /&gt;
		-- Assemble formatted &amp;lt;isbn_str&amp;gt; with space/hyphen (default) separators.&lt;br /&gt;
		isbn_str = table.concat (result_t, separator and &amp;#039; &amp;#039; or &amp;#039;-&amp;#039;);&lt;br /&gt;
&lt;br /&gt;
		-- If we saved the check digit from an SBN or ISBN-10.&lt;br /&gt;
		if flags.isbn10_check_digit then&lt;br /&gt;
			-- When input is an SBN.&lt;br /&gt;
			if flags.sbn then&lt;br /&gt;
				-- Remove GS1 prefix and registration group elements, then&lt;br /&gt;
				-- restore check digit.&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);&lt;br /&gt;
			-- When input is an ISBN-10.&lt;br /&gt;
			else&lt;br /&gt;
				if not flags.out13 then&lt;br /&gt;
					-- Remove GS1 prefix element and restore check digit.&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);&lt;br /&gt;
				end&lt;br /&gt;
			end&lt;br /&gt;
		end&lt;br /&gt;
&lt;br /&gt;
		-- Return formatted &amp;lt;isbn_str&amp;gt;.&lt;br /&gt;
		return isbn_str;&lt;br /&gt;
	end&lt;br /&gt;
&lt;br /&gt;
	-- Should never be reached, but if so, return original input string.&lt;br /&gt;
	return isbn_str_raw;&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
-- FORMAT_PLAIN {{#invoke:format isbn|format_plain}}&lt;br /&gt;
-- Plain text output: No linking to Special:BookSources, and no error message&lt;br /&gt;
-- output. On error, return input; for use in CS1 &amp;amp; CS2 templates&amp;#039; &amp;#039;isbn&amp;#039;&lt;br /&gt;
-- parameter, don&amp;#039;t confuse with multiple error messages. &amp;#039;separator=space&amp;#039;&lt;br /&gt;
-- renders formatted ISBN with spaces not hyphens, out=&amp;lt;10|13&amp;gt; specifies the&lt;br /&gt;
-- output format, if different from the default.&lt;br /&gt;
local function format_plain (frame)&lt;br /&gt;
	-- Get template and invoke parameters.&lt;br /&gt;
	local args_t = require (&amp;#039;Module:Arguments&amp;#039;).getArgs (frame);&lt;br /&gt;
	local isbn_str = args_t[1];&lt;br /&gt;
	-- Boolean: When true, use space separator, else hyphen.&lt;br /&gt;
	local separator = &amp;#039;space&amp;#039; == args_t.separator;&lt;br /&gt;
	-- 10 or 13, to convert input format to the other for output.&lt;br /&gt;
	local output_format = args_t.out;&lt;br /&gt;
&lt;br /&gt;
	-- No error messaging.&lt;br /&gt;
	return _format_isbn (isbn_str, nil, separator, output_format);&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
-- FORMAT_LINK {{#invoke:format isbn|format_linked|template=Format ISBN link}}&lt;br /&gt;
-- Linked text output: links to Special:BookSources. &amp;#039;suppress-errors=yes&amp;#039;:&lt;br /&gt;
-- suppress error messages; &amp;#039;separator=space&amp;#039;: render formatted ISBN with spaces&lt;br /&gt;
-- instead of hyphens; &amp;#039;out=&amp;#039;: 10 or 13 digit output, if different from default.&lt;br /&gt;
local function format_linked (frame)&lt;br /&gt;
	-- Get template and invoke parameters.&lt;br /&gt;
	local args_t = require (&amp;#039;Module:Arguments&amp;#039;).getArgs (frame);&lt;br /&gt;
	local isbn_str = args_t[1];&lt;br /&gt;
	-- Always show errors unless &amp;#039;suppress-errors=yes&amp;#039;.&lt;br /&gt;
	local show_err_msg = &amp;#039;yes&amp;#039; ~= args_t[&amp;#039;suppress-errors&amp;#039;];&lt;br /&gt;
	-- Boolean: When true use space separator, else hyphen.&lt;br /&gt;
	local separator = &amp;#039;space&amp;#039; == args_t.separator;&lt;br /&gt;
	-- 10 or 13: Convert input format to the other for output.&lt;br /&gt;
	local output_format = args_t.out;&lt;br /&gt;
&lt;br /&gt;
	-- Show error messages unless suppressed.&lt;br /&gt;
	local formatted_isbn_str, err_msg = _format_isbn (isbn_str, show_err_msg, separator, output_format, args_t.template_name);&lt;br /&gt;
	if err_msg then&lt;br /&gt;
		-- Return unformatted, unlinked ISBN and error message.&lt;br /&gt;
		return formatted_isbn_str .. &amp;#039; &amp;#039; .. err_msg;&lt;br /&gt;
	else&lt;br /&gt;
		-- Return formatted and linked ISBN.&lt;br /&gt;
		return &amp;#039;[[Special:BookSources/&amp;#039; ..isbn_str .. &amp;#039;|&amp;#039; .. formatted_isbn_str ..&amp;#039;]]&amp;#039;;&lt;br /&gt;
	end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
-- EXPORTS&lt;br /&gt;
return {&lt;br /&gt;
	-- Template entry points.&lt;br /&gt;
	format_plain = format_plain,&lt;br /&gt;
	format_linked = format_linked,&lt;br /&gt;
	&lt;br /&gt;
	-- Entry point when this module require()&amp;#039;d into another module.&lt;br /&gt;
	_format_isbn = _format_isbn,&lt;br /&gt;
	}&lt;/div&gt;</summary>
		<author><name>SockyPaws</name></author>
	</entry>
</feed>