Módulo:ISBN
A documentação para este módulo pode ser criada em Módulo:ISBN/doc
-- This Lua code was originally based on the Perl modules ISBN::Business and
-- ISBN::Business::Data by brian d foy <bdfoy@cpan.org>. The Lua version is
-- a complete rewrite with a simpler structure and much less code.
--
-- This software and documentation is Copyright (c) 2007 Geoff Richards
-- <geoff@geoffrichards.co.uk>. It is free software; you can redistribute it
-- and/or modify it under the terms of the S<Lua 5.0> license. The full terms
-- are given in the file I<COPYRIGHT> supplied with the source code package,
-- and are also available here: http://www.lua.org/license.html
-- More in: http://www.geoffrichards.co.uk/lua/isbn/
--
-- Objects of this class have the following private fields in (all of
-- which are required):
-- _isbn - string, 10 or 13 chars
-- _group_code, string, varying lengths
-- _publisher_code, sting, varying lengths
-- _article_code - string, varying lengths
local M = { _NAME = "isbn" }
M.__index = M
M.VERSION = "1.2"
local isbndata = require( 'Module:Isbn._data' )
local _max_publisher_code_length, _prefix_length,
_calc_checksum, _parse_isbn,
_parse_group_code, _parse_publisher_code, _extract_article_code
function _max_publisher_code_length (self)
local isbn = self._isbn
return isbn:len()
- _prefix_length(isbn) -- prefix
- self._group_code:len() -- group
- 1 -- article
- 1 -- checksum
end
local function _new (class, input_data, correction_mode)
if not correction_mode then correction_mode = "normal" end
input_data = input_data:upper() -- we want uppercase X's
if correction_mode == "strict" then
input_data = input_data:gsub("[-%s]", "")
if input_data:find("[^0-9X]") then
return nil, "character not allowed"
end
else
input_data = input_data:gsub("[^0-9X]", "")
end
local len = input_data:len()
if correction_mode == "fix-checksum" and (len == 9 or len == 12) then
-- allow checksum to be missing if we're going to correct it anyway.
input_data = input_data .. "0"
elseif len ~= 10 and len ~= 13 then
return nil, "wrong number of digits"
end
if not input_data:find("^%d+[%dX]$") then
return nil, "digit 'X' not allowed in middle"
end
local o = { _isbn = input_data }
setmetatable(o, class)
local err = _parse_isbn(o, correction_mode)
if err then return nil, err end
return o
end
-- Allow access to old 'new' class function for backwards compatibility.
M.new = _new
function M:isbn () return self._isbn end
function M:prefix ()
local data = self._isbn
return data:len() == 13 and data:sub(1, 3) or ""
end
function _prefix_length (isbn) return isbn:len() == 13 and 3 or 0 end
function M:group_code () return self._group_code end
function M:group_name ()
return isbndata.country_name[self:group_code()]
end
function M:publisher_code () return self._publisher_code end
function M:article_code () return self._article_code end
function M:checksum () return self._isbn:sub(-1) end
function M:as_isbn10 ()
local data = self._isbn
if data:len() == 10 then return self end
if self:prefix() ~= "978" then return nil end
return _new(M, data:sub(4), "fix-checksum")
end
function M:as_isbn13 ()
local data = self._isbn
if data:len() == 13 then return self end
return _new(M, "978" .. data, "fix-checksum")
end
function _calc_checksum (data)
local sum = 0
if data:len() == 10 then
for i = 1, 9 do
sum = sum + (10 - i + 1) * data:sub(i, i)
end
local checksum = (11 - (sum % 11)) % 11
return checksum == 10 and "X" or tostring(checksum)
else
for i = 0, 10, 2 do
sum = sum + data:sub(i + 1, i + 1)
+ data:sub(i + 2, i + 2) * 3
end
-- take the next higher multiple of 10 and subtract the sum.
-- if $sum is 37, the next highest multiple of ten is 40. the
-- check digit would be 40 - 37 => 3.
local tenth = sum / 10
return tostring((10 * (tenth - tenth % 1 + 1) - sum) % 10)
end
end
function M:__tostring ()
local isbn = self._isbn
local prefix = isbn:len() == 13 and isbn:sub(1, 3) .. "-" or ""
return prefix .. self._group_code .. "-" .. self._publisher_code .. "-" ..
self._article_code .. "-" .. isbn:sub(-1)
end
function M:eq (other)
if type(other) == "string" then
local tmp = _new(M, other)
if not tmp then error("invalid ISBN value '" .. other .. "'", 2) end
other = tmp
end
local selfstr, otherstr = self._isbn, other._isbn
if selfstr:len() ~= otherstr:len() then
if selfstr:len() == 10 then
selfstr = self:as_isbn13()._isbn
else
otherstr = other:as_isbn13()._isbn
end
end
return selfstr == otherstr
end
M.__eq = M.eq
function _parse_isbn (self, correction_mode)
local isbn = self._isbn
local result
if isbn:len() == 13 and not isbn:find("^97[89]") then
return "invalid prefix"
end
result = _parse_group_code(self)
if not result then return "invalid group code" end
self._group_code = result
result = _parse_publisher_code(self)
if not result then return "invalid publisher code" end
self._publisher_code = result
_extract_article_code(self)
local expected_checksum = _calc_checksum(isbn)
if isbn:sub(-1) ~= expected_checksum then
if correction_mode == "fix-checksum" then
self._isbn = isbn:sub(1, -2) .. expected_checksum
else
return "wrong checksum digit"
end
end
end
function _parse_group_code (self)
local isbn = self._isbn
local start = _prefix_length(isbn) + 1 -- first char of group code
local count = 1
while true do
local code = isbn:sub(start, start + count - 1)
if isbndata.country_ranges[code] then return code end
count = count + 1
if count > isbndata.max_country_code_length then return end
end
end
function _parse_publisher_code (self)
-- get the longest possible publisher code
-- I'll try substrs of this to get the real one
local isbn = self._isbn
local strt = _prefix_length(isbn) + self._group_code:len() + 1
local longest = isbn:sub(strt, strt + _max_publisher_code_length(self) - 1)
local ranges = isbndata.country_ranges[self:group_code()]
for i = 1, #ranges, 2 do
local lower = ranges[i]
local upper = ranges[i + 1]
local code = longest:sub(1, lower:len())
if code >= lower and code <= upper then return code end
end
return -- failed if I got this far
end
function _extract_article_code (self)
local isbn = self._isbn
local start = _prefix_length(isbn) +
self._group_code:len() +
self._publisher_code:len()
local length = isbn:len() - start - 1
self._article_code = isbn:sub(start + 1, start + length)
end
return setmetatable(M, { _NAME = "isbn metatable", __call = _new })
-- vi:ts=4 sw=4 expandtab