Saltar para o conteúdo

Módulo:Format ISBN

Origem: Wikipédia, a enciclopédia livre.
Documentação do módulo[ver] [editar] [histórico] [purgar]

Este módulo implementa {{Formata ISBN}}

require ('strict');

local data = mw.loadData ('Módulo:Format ISBN/data');							-- buscar dados de posicionamento do separador
	local hyphen_pos_t = data.hyphen_pos_t;										-- a tabela k/v de dados de posicionamento de hífen
	local index_t = data.index_t;												-- uma sequência de índice na tabela de dados de posicionamento de hífen; usado por binary_search()
	local idx_count = data.count;												-- de count = #index_t; em ~/data; usado por binary_search()


--[[--------------------------< B I N A R Y _ S E A R C H >----------------------------------------------------

Faz uma pesquisa binária para os dados de posicionamento do hífen para <target_isbn> em <hyphen_pos_t> usando
sua sequência de índice <índice_t>.

Aceita uma entrada <target_isbn> (uma string) que converte em um número.

Retorna o índice em <hyphen_pos_t> como um número quando a formatação adequada é encontrada; nada mais

]]

local function binary_search (target_isbn)
	target_isbn = tonumber (target_isbn);										-- converte para número porque os valores de index_t[x] são números

	if (index_t[1] >= target_isbn) or (index_t[idx_count] < target_isbn) then	-- inválido; fora do intervalo; 9780000000000 para qual é que seja o último valor
		return;																	
	end
	
	local idx_bot = 1;															-- inicializa no índice 1 (primeiro elemento em <index_t>)
	local idx_top = idx_count;													-- inicializa para o índice do último elemento em <index_t>
	
	while idx_bot ~= idx_top do
		local idx_mid = math.ceil ((idx_bot + idx_top) / 2);					-- obtem o ponto médio na sequência do índice
		if index_t[idx_mid] >= target_isbn then									-- quando o valor do índice do ponto médio é maior ou igual ao isbn alvo
			if index_t[idx_mid-1] < target_isbn then							-- e quando o valor <index_t> anterior for menor que o isbn alvo
				return index_t[idx_mid];										-- encontramos o mapeamento correto para <target> isbn; retornar índice em <hyphen_pos_t>
			end
			idx_top = idx_mid - 1;												-- ajusta <idx_top>
		else
			idx_bot = idx_mid;													-- ajusta <idx_bot>
		end
	end
	mw.logObject ('didn\'t find formatting for isbn: ' .. target_isbn);			-- apenas para se acontecer
end


--[[--------------------------< C O N V E R T _ T O _ I S B N 1 0 >--------------------------------------------

Converter isbn de 13 dígitos em isbn de 10 dígitos; remove o prefixo 978 GS1 e recalcula o dígito de
verificação.

Recebe uma única entrada; o isbn de 13 dígitos como uma string sem separadores.

Assume que o prefixo GS1 é 978; não há mapeamento entre isbn10 e isbn13 com prefixo 979. funções de chamada são
necessárias para garantir que <isbn13> seja uma sequência formada corretamente de 13 dígitos (sem separadores)
que comece com 978.

]]

local function convert_to_isbn10 (isbn13)
	local isbn9 = isbn13:sub (4, 12);											-- obtem os 9 dígitos de <isbn13> que seguem o prefixo '978' GS1 (elimina o dígito de verificação)

	local check = 0;															-- inicializa o cálculo do dígito de verificação
	local i = 1;																-- índice
	for j=10, 2, -1 do															-- <j> é a ponderação de cada um dos 9 dígitos; contagem regressiva, da esquerda para a direita
		check = check + tonumber (isbn9:sub (i, i)) * j;						-- acumula a soma dos produtos ponderados dos dígitos
		i = i + 1;																-- próximo índice
	end

	check = check % 11;															-- o resto dos produtos ponderados dos dígitos dividido por 11

	if 0 == check then
		return isbn9 .. '0';													-- caso especial
	else
		check = 11 - check;														-- calcula o dígito de verificação
		return isbn9 ..  ((10 == check) and 'X' or check);						-- quando <check> for dez, use 'X'; <check> nos outros casos
	end
end


--[[--------------------------< C O N V E R T _ T O _ I S B N 1 3 >--------------------------------------------

Converte isbn de 10 dígitos em isbn de 13 dígitos; adiciona o prefixo 978 GS1 e recalcula o dígito de
verificação

Recebe uma única entrada; o isbn de 10 dígitos como uma string (sem separadores)

]]

local function convert_to_isbn13 (isbn10)
	local isbn12 = '978'.. isbn10:sub(1, 9);									-- concatena '978' com os primeiros 9 dígitos de <isbn10> (elimina o dígito de verificação)
	local check = 0;															-- inicia o cálculo do dígito de verificação
	for i=1, 12 do																-- para os primeiros 12 dígitos ('978' e 9 outros)
		check = check + tonumber (isbn12:sub (i, i)) * (3 - (i % 2) * 2);		-- acumular soma de verificação
	end
	return isbn12 .. ((10 - (check % 10)) %10);									-- extrai o dígito de verificação da soma de verificação; anexa e está pronto
end


--[[--------------------------< _ F O R M A T _ I S B N >------------------------------------------------------

Ponto de entrada do módulo quando require() é colocado em outro módulo

leva cinco entradas:
	<isbn_str> – isbn como uma string
	<show_err_msg>: boolean: quando verdadeiro, mostra mensagem de erro retornada de check_isbn(); nenhuma
	outra mensagem
	<separador>: booleano: quando verdadeiro, usa caractere de espaço como separador; outro hífen
	<template_name>: fornecido pelo modelo para uso em mensagens de erro
	<output_format>: um valor de 10 ou 13 determina o formato da saída; outros valores ignorados

Retorna sbn, isbn10 ou isbn13 formatado (qualquer que tenha sido a entrada ou por |out=) em caso de sucesso;
senão retorna <isbn_str> inicial

]]

local function _format_isbn (isbn_str, show_err_msg, separator, output_format, template_name)
	if (not isbn_str) or ('' == isbn_str) then
		return '';																-- entrada vazia ou nula retorna vazio
	end

	local isbn_str_raw = isbn_str;												-- este será o valor de retorno se não for possível formatar
	isbn_str = isbn_str:gsub ('[^%dX]', '');									-- retira toda a formatação (espaços e hífens) do isbn/sbn

	local flags = {};															-- um lugar conveniente para flags
	if '13' == output_format then												-- define uma flag para o formato de saída; ignorado quando <isbn_str> é um sbn
		flags.out13 = true;
	elseif  '10' == output_format then
		flags.out10 = true;
	end

	if 9 == #isbn_str then														-- parece uma sbn?
		isbn_str = '0' .. isbn_str;												-- converte para isbn10
		flags.sbn = true;														-- define uma flag
	end
	
	local err_msg = require ("Módulo:Check isxn").check_isbn ({args={isbn_str, template_name=template_name}});	-- <isbn_str> 'parece' um isbn válido? não verifica a faixa
	if '' ~= err_msg then														-- quando há uma mensagem de erro
		if show_err_msg then													-- e estamos mostrando mensagens de erro
			return isbn_str_raw,  err_msg;										-- retornar nossa entrada e a mensagem
		else
			return isbn_str_raw;												-- não mostra mensagens de erro; retorna nossa entrada sem a mensagem
		end
	end

	if 13 == #isbn_str and flags.out10 then										-- se é isbn13 mas queremos uma saída isbn10
		flags.isbn10_check_digit = (convert_to_isbn10 (isbn_str)):sub (-1);		-- calcula e extrai o dígito de verificação isbn10 para mais tarde
	end
	
	if 10 == #isbn_str then														-- se é isbn10 ou sbn
		flags.isbn10_check_digit = isbn_str:sub (-1);							-- extrai o dígito de verificação para mais tarde
		isbn_str = convert_to_isbn13 (isbn_str);								-- converte isbn10 em isbn13 para formatação
	end
	
	local index = binary_search (isbn_str);										-- procura a formatação que se aplica a <isbn_str>
	if index then																-- se achar
		local format_t = hyphen_pos_t[index];									-- obtem a sequência de formatação
		local result_t = {isbn_str:sub (1, 3)};									-- inicia <result_t> com prefixo; o elemento de prefixo GS1 ('978' ou '979')
		local digit_ptr = 4;													-- inicia para apontar para o elemento do grupo de registro
		
		for _, n in ipairs (format_t) do										-- fica em loop na sequência de formatação para construir uma sequência de elementos isbn13
			table.insert (result_t, isbn_str:sub (digit_ptr, digit_ptr+n-1));	-- adiciona os dígitos de <isbn_str>[<digit_ptr>] a <isbn_str>[<digit_ptr+n-1>] à sequência <result_t>
			digit_ptr = digit_ptr + n;											-- avança o ponteiro do dígito
		end
		table.insert (result_t, isbn_str:sub (13));								-- e adiciona o elemento do dígito de verificação a <result_t>

		isbn_str = table.concat (result_t, separator and ' ' or '-');			-- monta <isbn_str> formatado com separadores de espaço ou hífen (padrão)

		if flags.isbn10_check_digit then										-- se salvamos o dígito de verificação de um sbn ou isbn10
			if flags.sbn then													-- quando a entrada é um sbn
				isbn_str = isbn_str:gsub ('^978%-0%-', ''):gsub ('%d$', flags.isbn10_check_digit);	-- remove o prefixo GS1 e o grupo de registro; restaura dígito de verificação
			else																-- quando a entrada é um isbn10
				if not flags.out13 then
					isbn_str = isbn_str:gsub ('^978%-', ''):gsub ('%d$', flags.isbn10_check_digit);	-- remove o elemento de prefixo GS1; restaurar dígito de verificação
				end
			end
		end

		return isbn_str;														-- retorna formatado <isbn_str>
	end

	return isbn_str_raw;														-- nunca deveria ser realmente alcançado; mas, se acontecer, retorna a string de entrada original
end


--[[--------------------------< F O R M A T _ P L A I N >------------------------------------------------------

Saída de texto sem formatação:	
	sem ligação para Especial:Fontes_de_livros
	nenhuma saída de mensagem de erro – em caso de erro, retorna a entrada; para uso nas predefinições cs1|2 nos parâmetros |isbn=, não faz sentido causar confusão devido a múltiplas mensagens de erro

	|separator=space – renderizar ISBN formatado com espaços em vez de hífens
	|out= – leva 10 ou 13 para especificar o formato de saída se for diferente do padrão
	
{{#invoke:format isbn|format_plain}}

]]

local function format_plain (frame)
	local args_t = require ('Módulo:Arguments').getArgs (frame);				-- obtem a predefinição e invocar parâmetros
	local isbn_str = args_t[1];
	local separator = 'space' == args_t.separator;								-- booleano: quando verdadeiro usa separador de espaço; senão hífen
	local output_format = args_t.out;											-- 10 ou 13 para converter o formato de entrada para outro para saída

	return _format_isbn (isbn_str, nil, separator, output_format);				-- sem mensagens de erro
end


--[[--------------------------< F O R M A T _ L I N K >--------------------------------------------------------

Saída de texto com ligações:	
	sem ligação para Especial:Fontes_de_livros
	
	|suppress-errors=yes – suprime mensagens de erro
	|separator=space – renderiza ISBN formatado com espaços em vez de hífens
	|out= – leva 10 ou 13 para especificar o formato de saída se for diferente do padrão
	
{{#invoke:format isbn|format_linked|template=Format ISBN link}}

]]

local function format_linked (frame)
	local args_t = require ('Módulo:Arguments').getArgs (frame);				-- obtem a predefinição e invocar parâmetros
	local isbn_str = args_t[1];
	local show_err_msg = 'yes' ~= args_t['suppress-errors'];					-- sempre mostra os erros, a não ser que |suppress-errors=yes
	local separator = 'space' == args_t.separator;								-- booleano: quando verdadeiro usa separador de espaço; senão hífen
	local output_format = args_t.out;											-- 10 ou 13 para converter o formato de entrada para outro para saída

	local formatted_isbn_str, err_msg = _format_isbn (isbn_str, show_err_msg, separator, output_format, args_t.template_name); -- exibe mensagens de erro a não ser que seja suprimido
	if err_msg then
		return formatted_isbn_str .. ' ' .. err_msg;							-- retornar isbn não formatado, sem ligações e mensagem de erro
	else
		return '[[Special:BookSources/' ..isbn_str .. '|' .. formatted_isbn_str ..']]';	-- retornar isbn formatado e com ligações
	end
end


--[[--------------------------< E X P O R T S >----------------------------------------------------------------
]]

return {
	format_plain = format_plain,												-- pontos de entrada da predefinição
	format_linked = format_linked,
	
	_format_isbn = _format_isbn,												-- ponto de entrada quando este módulo requer () em outro módulo
	}