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.

require('Module:No globals')

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

-- Format is: {["prefix"] = {{digits, {from, to}, ...}, ...} ...} local _pattern_tables = {} pcall(function	_pattern_tables = mw.loadData('Module:Format ISBN/data') end)

local function _format(isbn, length, digits) return string.sub(isbn, 1, length) .. "-" .. string.sub(isbn, length + 1, length + 8 - digits) .. "-" .. string.sub(isbn, length + 8 - 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

local function _is_within_range(publisher, range) local low, high = range[1], range[2] return publisher >= low and publisher <= high end

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

local function _find_table(ean, isbn) for length = 1, string.len(isbn) do local prefix = ean .. "-" .. string.sub(isbn, 1, length) local table_ = _pattern_tables[prefix] if table_ then return table_, length end end return nil, 0 end

local function _format_group(ean, isbn) local table_, length = _find_table(ean, isbn) if table_ then return _format_from_table(isbn, length, 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