Module:Format ISBN

-- Module to format ISBN numbers. -- Example usage: --  -- Console example: --  mw.log(p.format({args={isbn="1843560283"}})) -- Notes: --  Input ISBN must be valid, either 10 or 13 digits after stripping spacing. --  Formats only English language and US registration group elements (978-0, 978-1, and 979-8).

require('Module:No globals')

local p = {} local yesno = require('Module:Yesno')

local function _format(isbn, digits) return string.sub(isbn, 1, 1) .. "-" .. string.sub(isbn, 2, 9 - digits) .. "-" .. string.sub(isbn, 9 - digits + 1, 9) .. "-" .. string.sub(isbn, 10, 10) end

local function _partially_format(isbn) return string.sub(isbn, 1, 9) .. "-" .. string.sub(isbn, 10, 10) end

-- https://en.wikipedia.org/wiki/International_Standard_Book_Number#Pattern_for_English_language_ISBNs -- Table structure is: {{digits, {from, to}, {from, to} ...} ...} local _978_0 = { -- 978-0 {6, {0, 19}},	{5, {200, 227}, {229, 368}, {370, 638}, {640, 644}, {646, 647}, {649, 654},		{656, 699}},	{4, {2280, 2289}, {3690, 3699}, {6390, 6397}, {6550, 6559}, {7000, 8499}},	{3, {85000, 85999}},	{2, {900000, 949999}},	{1, {6398000, 6399999}, {6450000, 6459999}, {6480000, 6489999},		{9500000, 9999999}} } local _978_1 = { -- 978-1 {6, {1, 2}, {4, 6}},	{5, {0, 9}, {30, 34}, {100, 397}, {714, 716}},	{4, {350, 399}, {700, 999}, {3980, 5499}, {6500, 6799}, {6860, 7139},		{7170, 7319}, {7900, 7999}, {8672, 8675}, {9730, 9877}},	{3, {55000, 64999}, {68000, 68599}, {74000, 77499}, {77540, 77639},		{77650, 77699}, {77830, 78999}, {80000, 83799}, {83850, 86719},		{86760, 86979}},	{2, {869800, 915999}, {916506, 916869}, {916908, 919599}, {919655, 972999},		{987800, 991149}, {991200, 998989}},	{1, {7320000, 7399999}, {7750000, 7753999}, {7764000, 7764999},		{7770000, 7782999}, {8380000, 8384999}, {9160000, 9165059},		{9168700, 9169079}, {9196000, 9196549}, {9911500, 9911999},		{9989900, 9999999}} } local _979_8 = { -- 979-8 {5, {200, 219}},	{4, {4500, 7499}},	{3, {88500, 89999}},	{1, {9850000, 9869999}} } local _pattern_tables = { ["978-0"] = _978_0, ["978-1"] = _978_1,	["979-8"] = _979_8 }

local function _is_within_range(publisher, range) local low, high = unpack(range) return publisher >= low and publisher <= high end

local function _format_from_table(isbn, table_) for _, entry in ipairs(table_) do		local digits = entry[1] local publisher = tonumber(string.sub(isbn, 2, 9 - digits)) for i, range in ipairs(entry) do			if i > 1 and _is_within_range(publisher, range) then return _format(isbn, digits) end end end return _partially_format(isbn) end

local function _format_group(ean, isbn) local table_ = _pattern_tables[ean .. "-" .. string.sub(isbn, 1, 1)] if table_ then return _format_from_table(isbn, table_) else return _partially_format(isbn) end end

local function _has_checkdigit_10(isbn) local sum, total = 0, 0 for c in string.gmatch(isbn, ".") do		total = total + (c == "X" and 10 or tonumber(c)) sum = sum + total end return sum % 11 == 0 end

local function _has_checkdigit_13(isbn) local i, sum = 0, 0 for c in string.gmatch(isbn, ".") do		sum = sum + ((i % 2) == 0 and 1 or 3) * tonumber(c) i = i + 1 end return sum % 10 == 0 end

local function _convert_10_to_13(isbn) local converted = "978" .. string.sub(isbn, 1, 9) local i, sum = 0, 0 for c in string.gmatch(converted, ".") do		sum = sum + ((i % 2) == 0 and 1 or 3) * tonumber(c) i = i + 1 end return converted .. tostring((10 - (sum % 10)) % 10) end

local function _format_isbn(isbn, use_conversion) isbn = string.gsub(isbn, "[- ]", "") if string.len(isbn) == 10 and (string.match(isbn, "^%d+$") or string.match(isbn, "^%d+X$")) and _has_checkdigit_10(isbn) then if use_conversion then return _format_isbn(_convert_10_to_13(isbn), false) else return _format_group("978", isbn) end elseif string.len(isbn) == 13 and string.match(isbn, "^97[89]%d+$") and _has_checkdigit_13(isbn) then return string.sub(isbn, 1, 3) .. "-"			.. _format_group(string.sub(isbn, 1, 3), string.sub(isbn, 4, 13)) end return "error" end

local function __format_isbn(isbn, prefix, use_spaces, use_conversion, error_) local formatted = _format_isbn(isbn, use_conversion) if formatted == "error" and error_ then return error_ end if use_spaces then formatted = string.gsub(formatted, "[-]", " ") end if string.len(prefix) > 0 then formatted = prefix .. " " .. formatted end return formatted end

-- Table structure is: {[argument_name]={argument_position, default_value}, ...} local _arguments_table = { ["isbn"] = {1, ""}, ["prefix"] = {2, "ISBN"}, ["use_spaces"] = {3, "false"}, ["use_conversion"] = {4, "false"}, ["error"] = {5, nil} }

local function _arguments(frame) local args = frame.args local parent_args = frame.getParent and frame:getParent.args or {} return function(name) local index, default = unpack(_arguments_table[name]) return args[name] or args[index] or parent_args[name] or parent_args[index] or default end end

function p.format(frame) local _a = _arguments(frame) return __format_isbn(		_a("isbn"), _a("prefix"),		yesno(_a("use_spaces")), yesno(_a("use_conversion")), _a("error")	) end

return p