Módulo:ISBN

From Wikincat
Jump to navigation Jump to search

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