Module:Sandbox/Polygnotus2

-- Module:ISBNValidator
-- A module to validate ISBN-10 and ISBN-13 strings with or without dashes
local p = {}

-- Main validation function
function p.isValid(frame)
    local isbn = frame.args[1] or ""
    if frame:getParent() then
        isbn = frame.args[1] or frame:getParent().args[1] or ""
    end
    
    -- Clean the input and determine ISBN type
    local cleanISBN, isbnType = p._cleanISBN(isbn)
    
    -- Validate according to type
    local result = false
    if isbnType == 10 then
        result = p._isValidISBN10(cleanISBN)
    elseif isbnType == 13 then
        result = p._isValidISBN13(cleanISBN)
    end
    
    return result and "valid" or "invalid"
end

-- Function to get the ISBN type (10 or 13)
function p.getType(frame)
    local isbn = frame.args[1] or ""
    if frame:getParent() then
        isbn = frame.args[1] or frame:getParent().args[1] or ""
    end
    
    local _, isbnType = p._cleanISBN(isbn)
    
    if isbnType == 10 then
        return "ISBN-10"
    elseif isbnType == 13 then
        return "ISBN-13"
    else
        return "Unknown or invalid ISBN format"
    end
end

-- Internal function to clean ISBN and determine type
function p._cleanISBN(isbn)
    -- Trim whitespace
    isbn = isbn:match("^%s*(.-)%s*$")
    
    -- Remove common prefixes
    isbn = isbn:gsub("^ISBN[%s:-]*", "")
    isbn = isbn:gsub("^ISBN%s*[%s:-]*", "")
    
    -- Remove all non-alphanumeric characters
    local cleanISBN = isbn:gsub("[^%dXx]", "")
    
    -- Determine type based on length
    local isbnType = nil
    if #cleanISBN == 10 then
        isbnType = 10
    elseif #cleanISBN == 13 then
        isbnType = 13
    end
    
    return cleanISBN:upper(), isbnType
end

-- Validate ISBN-10
function p._isValidISBN10(isbn)
    if #isbn ~= 10 then
        return false
    end
    
    -- Check that first 9 characters are digits
    if not isbn:sub(1, 9):match("^%d+$") then
        return false
    end
    
    -- Check that last character is digit or 'X'
    local lastChar = isbn:sub(10, 10)
    if not (lastChar:match("%d") or lastChar == "X") then
        return false
    end
    
    -- Calculate checksum
    local sum = 0
    for i = 1, 9 do
        sum = sum + tonumber(isbn:sub(i, i)) * (11 - i)
    end
    
    -- Add the check digit
    if isbn:sub(10, 10) == "X" then
        sum = sum + 10
    else
        sum = sum + tonumber(isbn:sub(10, 10))
    end
    
    -- Valid if sum is divisible by 11
    return sum % 11 == 0
end

-- Validate ISBN-13
function p._isValidISBN13(isbn)
    if #isbn ~= 13 then
        return false
    end
    
    -- Check all characters are digits
    if not isbn:match("^%d+$") then
        return false
    end
    
    -- Check that it starts with 978 or 979 (current ISBN-13 prefixes)
    if not (isbn:sub(1, 3) == "978" or isbn:sub(1, 3) == "979") then
        return false
    end
    
    -- Calculate checksum (alternating 1 and 3 weights)
    local sum = 0
    for i = 1, 13 do
        local weight = (i % 2 == 1) and 1 or 3
        sum = sum + tonumber(isbn:sub(i, i)) * weight
    end
    
    -- Valid if sum is divisible by 10
    return sum % 10 == 0
end

-- Normalize ISBN to standard format with dashes
function p.normalize(frame)
    local isbn = frame.args[1] or ""
    if frame:getParent() then
        isbn = frame.args[1] or frame:getParent().args[1] or ""
    end
    
    local cleanISBN, isbnType = p._cleanISBN(isbn)
    
    -- Check if valid before normalizing
    local isValid = false
    if isbnType == 10 then
        isValid = p._isValidISBN10(cleanISBN)
    elseif isbnType == 13 then
        isValid = p._isValidISBN13(cleanISBN)
    end
    
    if not isValid then
        return "Invalid ISBN: " .. isbn
    end
    
    -- Normalize format based on type
    if isbnType == 10 then
        -- ISBN-10 typical format: group-publisher-title-check
        -- This is simplified; actual grouping depends on ISBN ranges
        -- For demonstration, use a simple 1-2-6-1 pattern
        return cleanISBN:sub(1, 1) .. "-" .. 
               cleanISBN:sub(2, 3) .. "-" .. 
               cleanISBN:sub(4, 9) .. "-" .. 
               cleanISBN:sub(10, 10)
    else -- ISBN-13
        -- ISBN-13 typical format: prefix-group-publisher-title-check
        -- This is simplified; actual grouping depends on ISBN ranges
        -- For demonstration, use a simple 3-1-2-6-1 pattern
        return cleanISBN:sub(1, 3) .. "-" .. 
               cleanISBN:sub(4, 4) .. "-" .. 
               cleanISBN:sub(5, 6) .. "-" .. 
               cleanISBN:sub(7, 12) .. "-" .. 
               cleanISBN:sub(13, 13)
    end
end

-- Convert between ISBN-10 and ISBN-13 (when possible)
function p.convert(frame)
    local isbn = frame.args[1] or ""
    local targetType = frame.args[2] or "13" -- Default to converting to ISBN-13
    
    if frame:getParent() then
        isbn = frame.args[1] or frame:getParent().args[1] or ""
        targetType = frame.args[2] or frame:getParent().args[2] or "13"
    end
    
    local cleanISBN, isbnType = p._cleanISBN(isbn)
    
    -- Check if valid before converting
    local isValid = false
    if isbnType == 10 then
        isValid = p._isValidISBN10(cleanISBN)
    elseif isbnType == 13 then
        isValid = p._isValidISBN13(cleanISBN)
    end
    
    if not isValid then
        return "Invalid ISBN: " .. isbn
    end
    
    -- Convert based on source and target type
    if isbnType == 10 and targetType == "13" then
        return p._convertISBN10to13(cleanISBN)
    elseif isbnType == 13 and targetType == "10" then
        return p._convertISBN13to10(cleanISBN)
    else
        -- Already in target format
        return cleanISBN
    end
end

-- Convert ISBN-10 to ISBN-13
function p._convertISBN10to13(isbn10)
    -- ISBN-13 starts with 978 prefix + first 9 digits of ISBN-10
    local isbn13base = "978" .. isbn10:sub(1, 9)
    
    -- Calculate the check digit
    local sum = 0
    for i = 1, 12 do
        local weight = (i % 2 == 1) and 1 or 3
        sum = sum + tonumber(isbn13base:sub(i, i)) * weight
    end
    
    local checkDigit = (10 - (sum % 10)) % 10
    
    return isbn13base .. checkDigit
end

-- Convert ISBN-13 to ISBN-10 (only works for 978 prefix)
function p._convertISBN13to10(isbn13)
    -- Check if it has 978 prefix (only these can be converted to ISBN-10)
    if isbn13:sub(1, 3) ~= "978" then
        return "Cannot convert to ISBN-10: ISBN-13 does not have 978 prefix"
    end
    
    -- Take digits 4-12 of the ISBN-13
    local isbn10base = isbn13:sub(4, 12)
    
    -- Calculate the check digit
    local sum = 0
    for i = 1, 9 do
        sum = sum + tonumber(isbn10base:sub(i, i)) * (11 - i)
    end
    
    local remainder = sum % 11
    local checkDigit = (11 - remainder) % 11
    
    if checkDigit == 10 then
        checkDigit = "X"
    end
    
    return isbn10base .. checkDigit
end

return p

Content Disclaimer

Informasi ini disarikan dari Wikipedia dan disajikan kembali untuk tujuan edukasi. Konten tersedia di bawah lisensi CC BY-SA 3.0. Kami tidak bertanggung jawab atas ketidakakuratan data yang bersumber dari kontribusi publik tersebut.

  1. The information displayed on this website is sourced in part or in whole from Wikipedia and has been adapted for the purpose of restating it. We strive to provide accurate and relevant information, however:
  2. There is no guarantee of absolute accuracy. Wikipedia is an open, collaborative project that can be edited by anyone, so information is subject to change.
  3. It is not intended to constitute professional advice. The content displayed is for informational and educational purposes only. For important decisions (e.g., medical, legal, or financial), please consult a professional.
  4. Content copyright. Wikipedia is licensed under the Creative Commons Attribution-ShareAlike License (CC BY-SA). This means that content may be reused with appropriate attribution and shared under a similar license.
  5. Responsible use. Any risk arising from the use of information from this website is entirely the responsibility of the user.