Module:Format ISBN
| This module depends on the following other modules: |
This module implements {{Format ISBN}}
require ('strict');
local data = mw.loadData ('Module:Format ISBN/data'); -- fetch separator positioning data
local hyphen_pos_t = data.hyphen_pos_t; -- the hyphen positioning data k/v table
local index_t = data.index_t; -- an index sequence into the hyphen positioning data table; used by binary_search()
local idx_count = data.count; -- from count = #index_t; in ~/data; used by binary_search()
--[[--------------------------< B I N A R Y _ S E A R C H >----------------------------------------------------
do a binary search for the hyphen positioning data for <target_isbn> in <hyphen_pos_t> using its index sequence
<index_t>.
accepts one input <target_isbn> (a string) which it converts to a number
returns index into <hyphen_pos_t> as a number when proper formatting is found; nil else
]]
local function binary_search (target_isbn)
target_isbn = tonumber (target_isbn); -- convert to number because index_t[x] values are numbers
if (index_t[1] >= target_isbn) or (index_t[idx_count] < target_isbn) then -- invalid; out of range; 9780000000000 to whatever the last value is
return; -- TODO: return something meaningful?
end
local idx_bot = 1; -- initialize to index 1 (first element in <index_t>)
local idx_top = idx_count; -- initialize to index of last element in <index_t>
while idx_bot ~= idx_top do
local idx_mid = math.ceil ((idx_bot + idx_top) / 2); -- get the mid-point in the index sequence
if index_t[idx_mid] >= target_isbn then -- when mid-point index value is greater than or equal to the target isbn
if index_t[idx_mid-1] < target_isbn then -- and when the preceding <index_t> value is less than the target isbn
return index_t[idx_mid]; -- we found the correct mapping for <target> isbn; return index into <hyphen_pos_t>
end
idx_top = idx_mid - 1; -- adjust <idx_top>
else
idx_bot = idx_mid; -- adjust <idx_bot>
end
end
mw.logObject ('didn\'t find formatting for isbn: ' .. target_isbn); -- just in case for the nonce
end
--[[--------------------------< C O N V E R T _ T O _ I S B N 1 0 >--------------------------------------------
convert 13-digit isbn to 10-digit isbn; removes 978 GS1 prefix and recalculates the check digit
takes a single input; the 13-digit isbn as a string without separators
assumes that the GS1 prefix is 978; there is no mapping between isbn10 and 979-prefixed isbn13. calling functions
are required to ensure that <isbn13> is a properly formed string of 13 digits (no separators) that begins with 978.
]]
local function convert_to_isbn10 (isbn13)
local isbn9 = isbn13:sub (4, 12); -- get the 9 digits of <isbn13> that follow the '978' GS1 prefix (drop the check digit)
local check = 0; -- initialize the check digit calculation
local i = 1; -- index
for j=10, 2, -1 do -- <j> is weighting for each of the 9 digits; counting down, left to right
check = check + tonumber (isbn9:sub (i, i)) * j; -- accumulate the sum the weighted-digit-products
i = i + 1; -- bump the index
end
check = check % 11; -- remainder of the weighted-digit-products divided by 11
if 0 == check then
return isbn9 .. '0'; -- special case
else
check = 11 - check; -- calculate the check digit
return isbn9 .. ((10 == check) and 'X' or check); -- when <check> is ten, use 'X'; <check> else
end
end
--[[--------------------------< C O N V E R T _ T O _ I S B N 1 3 >--------------------------------------------
convert 10-digit isbn to 13-digit isbn; adds 978 GS1 prefix and recalculates the check digit
takes a single input; the 10-digit isbn as a string (no separators)
]]
local function convert_to_isbn13 (isbn10)
local isbn12 = '978'.. isbn10:sub(1, 9); -- concatenate '978' with first 9 digits of <isbn10> (drop the check digit)
local check = 0; -- initialize the check digit calculation
for i=1, 12 do -- for the first 12 digits ('978' and 9 others)
check = check + tonumber (isbn12:sub (i, i)) * (3 - (i % 2) * 2); -- accumulate checksum
end
return isbn12 .. ((10 - (check % 10)) %10); -- extract check digit from checksum; append and done
end
--[[--------------------------< _ F O R M A T _ I S B N >------------------------------------------------------
Module entry point when require()'d into another module
takes five inputs:
<isbn_str> – isbn as a string
<show_err_msg>: boolean: when true, shows error message returned from check_isbn(); no message else
<separator>: boolean: when true, use space character as separator; hyphen else
<template_name>: supplied by the template for use in error messaging
<output_format>: a value of 10 or 13 dictates the format of the output; other values ignored
returns formatted sbn, isbn10, or isbn13 (whichever was the input or per |out=) on success; initial <isbn_str> else
]]
local function _format_isbn (isbn_str, show_err_msg, separator, output_format, template_name)
if (not isbn_str) or ('' == isbn_str) then
return ''; -- empty or nil input? empty output
end
local isbn_str_raw = isbn_str; -- this will be the return value if unable to format
isbn_str = isbn_str:gsub ('[^%dX]', ''); -- strip all formatting (spaces and hyphens) from the isbn/sbn
local flags = {}; -- a convenient place for flag stuff
if '13' == output_format then -- set a flag for output format; ignored when <isbn_str> is an sbn
flags.out13 = true;
elseif '10' == output_format then
flags.out10 = true;
end
if 9 == #isbn_str then -- looks like an sbn?
isbn_str = '0' .. isbn_str; -- convert to isbn10
flags.sbn = true; -- set a flag
end
local err_msg = require ("Module:Check isxn").check_isbn ({args={isbn_str, template_name=template_name}}); -- does <isbn_str> 'look' like a valid isbn? does not check ranging
if '' ~= err_msg then -- when there is an error message
if show_err_msg then -- and we are showing error messages
return isbn_str_raw, err_msg; -- return our input and the message
else
return isbn_str_raw; -- not showing error messages; return our input without the message
end
end
if 13 == #isbn_str and flags.out10 and isbn_str:match ('^978') then -- if isbn13 but we want an isbn10 output; only for GS1 prefix of 978
flags.isbn10_check_digit = (convert_to_isbn10 (isbn_str)):sub (-1); -- calculate and extract the isbn10 check digit for later
end
if 10 == #isbn_str then -- if isbn10 or sbn
flags.isbn10_check_digit = isbn_str:sub (-1); -- extract the check digit for later
isbn_str = convert_to_isbn13 (isbn_str); -- convert isbn10 to isbn13 for formatting
end
local index = binary_search (isbn_str); -- look for the formatting that applies to <isbn_str>
if index then -- if found
local format_t = hyphen_pos_t[index]; -- get the formatting sequence
local result_t = {isbn_str:sub (1, 3)}; -- init <result_t> with prefix; the GS1 prefix element ('978' or '979')
local digit_ptr = 4; -- initialize to point at registration group element
for _, n in ipairs (format_t) do -- loop through the formatting sequence to build a sequence of isbn13 elements
table.insert (result_t, isbn_str:sub (digit_ptr, digit_ptr+n-1)); -- add the digits from <isbn_str>[<digit_ptr>] to <isbn_str>[<digit_ptr+n-1>] to <result_t> sequence
digit_ptr = digit_ptr + n; -- advance the digit pointer
end
table.insert (result_t, isbn_str:sub (13)); -- and add the check digit element to <result_t>
isbn_str = table.concat (result_t, separator and ' ' or '-'); -- assemble formatted <isbn_str> with space or hyphen (default) separators
if flags.isbn10_check_digit then -- if we saved the check digit from an sbn or isbn10
if flags.sbn then -- when input is an sbn
isbn_str = isbn_str:gsub ('^978%-0%-', ''):gsub ('%d$', flags.isbn10_check_digit); -- remove GS1 prefix element and registration group element; restore check digit
else -- when input is an isbn10
if not flags.out13 then
isbn_str = isbn_str:gsub ('^978%-', ''):gsub ('%d$', flags.isbn10_check_digit); -- remove GS1 prefix element; restore check digit
end
end
end
return isbn_str; -- return formatted <isbn_str>
end
return isbn_str_raw; -- should never actually be reached; but, if we do, return original input string
end
--[[--------------------------< F O R M A T _ P L A I N >------------------------------------------------------
plain text output:
no linking to Special:BookSources
no error message output – on error, return input; for use in cs1|2 template |isbn= params, no point in causing confusion due to multiple error messages
|separator=space – render formatted ISBN with spaces instead of hyphens
|out= – takes either of 10 or 13 to specify the output format if different from the default
{{#invoke:format isbn|format_plain}}
]]
local function format_plain (frame)
local args_t = require ('Module:Arguments').getArgs (frame); -- get template and invoke parameters
local isbn_str = args_t[1];
local separator = 'space' == args_t.separator; -- boolean: when true use space separator; hyphen else
local output_format = args_t.out; -- 10 or 13 to convert input format to the other for output
return _format_isbn (isbn_str, nil, separator, output_format); -- no error messaging
end
--[[--------------------------< F O R M A T _ L I N K >--------------------------------------------------------
linked text output:
links to Special:BookSources
|suppress-errors=yes – suppress error messages
|separator=space – render formatted ISBN with spaces instead of hyphens
|out= – takes either of 10 or 13 to specify the output format if different from the default
{{#invoke:format isbn|format_linked|template=Format ISBN link}}
]]
local function format_linked (frame)
local args_t = require ('Module:Arguments').getArgs (frame); -- get template and invoke parameters
local isbn_str = args_t[1];
local show_err_msg = 'yes' ~= args_t['suppress-errors']; -- always show errors unless |suppress-errors=yes
local separator = 'space' == args_t.separator; -- boolean: when true use space separator; hyphen else
local output_format = args_t.out; -- 10 or 13 to convert input format to the other for output
local formatted_isbn_str, err_msg = _format_isbn (isbn_str, show_err_msg, separator, output_format, args_t.template_name); -- show error messages unless suppressed
if err_msg then
return formatted_isbn_str .. ' ' .. err_msg; -- return unformatted, unlinked isbn and error message
else
return '[[Special:BookSources/' ..isbn_str .. '|' .. formatted_isbn_str ..']]'; -- return formatted and linked isbn
end
end
--[[--------------------------< E X P O R T S >----------------------------------------------------------------
]]
return {
format_plain = format_plain, -- template entry points
format_linked = format_linked,
_format_isbn = _format_isbn, -- entry point when this module require()'d into another module
}
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.
- 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:
- 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.
- 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.
- 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.
- Responsible use. Any risk arising from the use of information from this website is entirely the responsibility of the user.