Module:Format ISBN

-- Module to test for and format ISBN numbers. -- Example usage: --  --   -- Console example: --  mw.log(p.format({isbn="1843560283"})) --  mw.log(p.is_isbn({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') local arguments = require('Module:Arguments')

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

local function _format(isbn, length, digits) return isbn:sub(1, length) .. "-" .. isbn:sub(length + 1, length + 8 - digits) .. "-" .. isbn:sub(length + 8 - digits + 1, 9) .. "-" .. isbn:sub(10, 10) end

local function _partially_format(isbn) return isbn:sub(1, 9) .. "-" .. isbn:sub(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 publisher = tonumber(isbn:sub(length + 1, length + 8 - digits)) 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 _format_group(ean, isbn) if not _pattern_tables then _load_pattern_tables end for length = 1, isbn:len do local table_ = _pattern_tables[ean .. "-" .. isbn:sub(1, length)] if table_ then return _format_from_table(isbn, length, table_) end end return _partially_format(isbn) end

local function _has_checkdigit_10(isbn) local sum, total = 0, 0 for c in isbn:gmatch(".") 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 isbn:gmatch(".") 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" .. isbn:sub(1, 9) local i, sum = 0, 0 for c in converted:gmatch(".") 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 _convert_13_to_10(isbn) local converted = isbn:sub(4, 12) local sum, mult = 0, 10 for c in converted:gmatch(".") do		sum = sum + mult * tonumber(c) mult = mult - 1 end local check = (11 - (sum % 11)) % 11 return converted .. (check == 10 and "X" or tostring(check)) end

local function _is_isbn_10(isbn) return isbn:len == 10 and isbn:match("^%d+X?$") and _has_checkdigit_10(isbn) end

local function _is_isbn_13(isbn) return isbn:len == 13 and isbn:match("^97[89]%d+$") and _has_checkdigit_13(isbn) end

local function _format_isbn(isbn, use_conversion, use_down_conversion) local function __format_isbn_10(isbn) return _format_group("978", isbn) end

local function __format_isbn_13(isbn) return isbn:sub(1, 3) .. "-" .. _format_group(isbn:sub(1, 3), isbn:sub(4, 13)) end

if _is_isbn_10(isbn) then if use_conversion then return __format_isbn_13(_convert_10_to_13(isbn)) else return __format_isbn_10(isbn) end elseif _is_isbn_13(isbn) then if use_down_conversion then if isbn:sub(1, 3) == "978" then return __format_isbn_10(_convert_13_to_10(isbn)) else return "error" end else return __format_isbn_13(isbn) end end

return "error" end

function p._is_isbn(args) local isbn = (args["isbn"] or args[1] or ""):gsub("[- ]", "")

if (_is_isbn_10(isbn) or _is_isbn_13(isbn)) then return args["valid"] or args[2] or "true" else return args["invalid"] or args[3] or "" end end

function p._format(args) local isbn = (args["isbn"] or args[1] or ""):gsub("[- ]", "") local use_conversion = yesno(args["use_conversion"] or args["convert"] or args[4] or "false") local use_down_conversion = yesno(args["use_down_conversion"] or args["convert_down"] or args[5] or "false") local formatted = _format_isbn(isbn, use_conversion, use_down_conversion)

local error_ = args["error"] or args[6] or nil if formatted == "error" and error_ then return error_ end

if formatted ~= "error" then local use_link = yesno(args["use_link"] or args["link"] or args[7] or "false") if use_link then formatted = "" .. formatted .. "" end end

local use_spaces = yesno(args["use_spaces"] or args["spaces"] or args[3] or "false") if use_spaces then formatted = formatted:gsub("[-]", " ") end

local prefix = args["prefix"] or args[2] or "ISBN" if prefix:len > 0 then formatted = prefix .. " " .. formatted end

return formatted end

function p.is_isbn(frame) local args = arguments.getArgs(frame,		{trim = false, removeBlanks = false, frameOnly = true}) return p._is_isbn(args) end

function p.format(frame) local args = arguments.getArgs(frame,		{trim = false, removeBlanks = false, wrappers = "Template:Format ISBN"}) return p._format(args) end

return p