631 lines
16 KiB
C
631 lines
16 KiB
C
/* -*- C -*-
|
|
* Copyright (c) 2003-2006 Motoyuki Kasahara
|
|
*
|
|
* Redistribution and use in source and binary forms, with or without
|
|
* modification, are permitted provided that the following conditions
|
|
* are met:
|
|
* 1. Redistributions of source code must retain the above copyright
|
|
* notice, this list of conditions and the following disclaimer.
|
|
* 2. Redistributions in binary form must reproduce the above copyright
|
|
* notice, this list of conditions and the following disclaimer in the
|
|
* documentation and/or other materials provided with the distribution.
|
|
* 3. Neither the name of the project nor the names of its contributors
|
|
* may be used to endorse or promote products derived from this software
|
|
* without specific prior written permission.
|
|
*
|
|
* THIS SOFTWARE IS PROVIDED BY THE PROJECT AND CONTRIBUTORS ``AS IS'' AND
|
|
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
|
* ARE DISCLAIMED. IN NO EVENT SHALL THE PROJECT OR CONTRIBUTORS BE LIABLE
|
|
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
|
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
|
|
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
|
|
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
|
|
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
|
|
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
|
|
* SUCH DAMAGE.
|
|
*/
|
|
|
|
#ifdef HAVE_CONFIG_H
|
|
#include "config.h"
|
|
#endif
|
|
|
|
#include <stdio.h>
|
|
#include <sys/types.h>
|
|
#include <stdlib.h>
|
|
|
|
#ifdef ENABLE_NLS
|
|
#ifdef HAVE_LOCALE_H
|
|
#include <locale.h>
|
|
#endif
|
|
#include <libintl.h>
|
|
#endif
|
|
|
|
/*
|
|
* The maximum length of path name.
|
|
*/
|
|
#ifndef PATH_MAX
|
|
#ifdef MAXPATHLEN
|
|
#define PATH_MAX MAXPATHLEN
|
|
#else /* not MAXPATHLEN */
|
|
#define PATH_MAX 1024
|
|
#endif /* not MAXPATHLEN */
|
|
#endif /* not PATH_MAX */
|
|
|
|
#include "eb/eb.h"
|
|
#include "eb/error.h"
|
|
#include "eb/text.h"
|
|
#include "eb/appendix.h"
|
|
|
|
#include "getopt.h"
|
|
#include "ebutils.h"
|
|
|
|
/*
|
|
* Tricks for gettext.
|
|
*/
|
|
#ifdef ENABLE_NLS
|
|
#define _(string) gettext(string)
|
|
#ifdef gettext_noop
|
|
#define N_(string) gettext_noop(string)
|
|
#else
|
|
#define N_(string) (string)
|
|
#endif
|
|
#else
|
|
#define _(string) (string)
|
|
#define N_(string) (string)
|
|
#endif
|
|
|
|
/*
|
|
* Character type tests and conversions.
|
|
*/
|
|
#define ASCII_ISDIGIT(c) ('0' <= (c) && (c) <= '9')
|
|
#define ASCII_ISUPPER(c) ('A' <= (c) && (c) <= 'Z')
|
|
#define ASCII_ISLOWER(c) ('a' <= (c) && (c) <= 'z')
|
|
#define ASCII_ISALPHA(c) \
|
|
(ASCII_ISUPPER(c) || ASCII_ISLOWER(c))
|
|
#define ASCII_ISALNUM(c) \
|
|
(ASCII_ISUPPER(c) || ASCII_ISLOWER(c) || ASCII_ISDIGIT(c))
|
|
#define ASCII_ISXDIGIT(c) \
|
|
(ASCII_ISDIGIT(c) || ('A' <= (c) && (c) <= 'F') || ('a' <= (c) && (c) <= 'f'))
|
|
#define ASCII_TOUPPER(c) (('a' <= (c) && (c) <= 'z') ? (c) - 0x20 : (c))
|
|
#define ASCII_TOLOWER(c) (('A' <= (c) && (c) <= 'Z') ? (c) + 0x20 : (c))
|
|
|
|
/*
|
|
* Default maximum length of text.
|
|
*/
|
|
#define DEFAULT_MAX_TEXT_LENGTH EB_SIZE_PAGE
|
|
|
|
/*
|
|
* Default book directory.
|
|
*/
|
|
#define DEFAULT_BOOK_DIRECTORY "."
|
|
|
|
/*
|
|
* Dummy stop code.
|
|
*
|
|
* Application cannot order EB Library not to use stop code.
|
|
* EB Library guesses stop code automatically when appendix is not
|
|
* given by application.
|
|
*
|
|
* Instead, we use dummy stop code. The code is never used in text
|
|
* of CD-ROM book.
|
|
*/
|
|
#define DUMMY_STOP_CODE0 0x1f00
|
|
#define DUMMY_STOP_CODE1 0x0000
|
|
|
|
/*
|
|
* Command line options.
|
|
*/
|
|
static const char *short_options = "c:hl:np:v";
|
|
static struct option long_options[] = {
|
|
{"code", required_argument, NULL, 'c'},
|
|
{"help", no_argument, NULL, 'h'},
|
|
{"text-length", required_argument, NULL, 'l'},
|
|
{"no-candidates", no_argument, NULL, 'n'},
|
|
{"text-position", required_argument, NULL, 'p'},
|
|
{"version", no_argument, NULL, 'v'},
|
|
{NULL, 0, NULL, 0}
|
|
};
|
|
|
|
/*
|
|
* Program name and version.
|
|
*/
|
|
static const char *program_name = "ebstopcode";
|
|
static const char *program_version = VERSION;
|
|
static const char *invoked_name;
|
|
|
|
/*
|
|
* Unexported functions.
|
|
*/
|
|
static int parse_stop_code_argument(const char *argument,
|
|
unsigned int *stop_code0, unsigned int *stop_code1);
|
|
static int parse_text_length_argument(const char *argument,
|
|
ssize_t *text_length);
|
|
static int parse_text_position_argument(const char *argument,
|
|
EB_Position *text_position);
|
|
static void output_help(void);
|
|
static int scan_subbook_text(const char *book_directory,
|
|
const char *subbook_name, EB_Position *text_position,
|
|
ssize_t max_text_length, int show_stop_code_flag, unsigned int stop_code0,
|
|
unsigned int stop_code1);
|
|
static EB_Error_Code hook_stop_code(EB_Book *book, EB_Appendix *appendix,
|
|
void *container, EB_Hook_Code code, int argc, const unsigned int *argv);
|
|
|
|
|
|
int
|
|
main(int argc, char *argv[])
|
|
{
|
|
const char *book_directory;
|
|
ssize_t max_text_length;
|
|
const char *subbook_name;
|
|
int show_stop_code_flag;
|
|
EB_Position text_position;
|
|
unsigned int stop_code0, stop_code1;
|
|
int ch;
|
|
|
|
invoked_name = argv[0];
|
|
max_text_length = DEFAULT_MAX_TEXT_LENGTH;
|
|
show_stop_code_flag = 1;
|
|
stop_code0 = DUMMY_STOP_CODE0;
|
|
stop_code1 = DUMMY_STOP_CODE1;
|
|
text_position.page = 0;
|
|
text_position.offset = 0;
|
|
|
|
/*
|
|
* Initialize locale data.
|
|
*/
|
|
#ifdef ENABLE_NLS
|
|
#ifdef HAVE_SETLOCALE
|
|
setlocale(LC_ALL, "");
|
|
#endif
|
|
bindtextdomain(TEXT_DOMAIN_NAME, LOCALEDIR);
|
|
textdomain(TEXT_DOMAIN_NAME);
|
|
#endif
|
|
|
|
/*
|
|
* Parse command line options.
|
|
*/
|
|
for (;;) {
|
|
ch = getopt_long(argc, argv, short_options, long_options, NULL);
|
|
if (ch == -1)
|
|
break;
|
|
|
|
switch (ch) {
|
|
case 'c':
|
|
/*
|
|
* Option `-c'. Specify stop code manually.
|
|
*/
|
|
if (parse_stop_code_argument(optarg, &stop_code0, &stop_code1) < 0)
|
|
goto die;
|
|
max_text_length = 0;
|
|
break;
|
|
|
|
case 'h':
|
|
/*
|
|
* Option `-h'. Display help message, then exit.
|
|
*/
|
|
output_help();
|
|
exit(0);
|
|
|
|
case 'l':
|
|
/*
|
|
* Option `-l'. Specify maximum length of text.
|
|
*/
|
|
if (parse_text_length_argument(optarg, &max_text_length) < 0)
|
|
goto die;
|
|
stop_code0 = DUMMY_STOP_CODE0;
|
|
stop_code1 = DUMMY_STOP_CODE1;
|
|
break;
|
|
|
|
case 'n':
|
|
/*
|
|
* Option `-n'. Do not output stop code candidates.
|
|
*/
|
|
show_stop_code_flag = 0;
|
|
break;
|
|
|
|
case 'p':
|
|
/*
|
|
* Option `-p'. Specify text position.
|
|
*/
|
|
if (parse_text_position_argument(optarg, &text_position) < 0)
|
|
goto die;
|
|
break;
|
|
|
|
case 'v':
|
|
/*
|
|
* Option `-v'. Display version number, then exit.
|
|
*/
|
|
output_version(program_name, program_version);
|
|
exit(0);
|
|
|
|
default:
|
|
output_try_help(invoked_name);
|
|
goto die;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Check the number of rest arguments.
|
|
*/
|
|
if (argc - optind < 2) {
|
|
fprintf(stderr, _("%s: too few argument\n"), invoked_name);
|
|
output_try_help(invoked_name);
|
|
goto die;
|
|
}
|
|
if (2 < argc - optind) {
|
|
fprintf(stderr, _("%s: too many arguments\n"), invoked_name);
|
|
output_try_help(invoked_name);
|
|
goto die;
|
|
}
|
|
if (argc - optind == 2) {
|
|
book_directory = argv[optind];
|
|
subbook_name = argv[optind + 1];
|
|
} else {
|
|
book_directory = DEFAULT_BOOK_DIRECTORY;
|
|
subbook_name = argv[optind];
|
|
}
|
|
|
|
/*
|
|
* Scan stop code in text.
|
|
*/
|
|
if (scan_subbook_text(book_directory, subbook_name, &text_position,
|
|
max_text_length, show_stop_code_flag, stop_code0, stop_code1) < 0)
|
|
goto die;
|
|
|
|
return 0;
|
|
|
|
die:
|
|
fflush(stdout);
|
|
exit(1);
|
|
}
|
|
|
|
|
|
/*
|
|
* Parse stop code given as argument of the `-c' option.
|
|
*/
|
|
static int
|
|
parse_stop_code_argument(const char *argument, unsigned int *stop_code0,
|
|
unsigned int *stop_code1)
|
|
{
|
|
const char *p = argument;
|
|
const char *foundp;
|
|
|
|
/*
|
|
* Parse stop_code0.
|
|
*/
|
|
while (*p == ' ' || *p == '\t')
|
|
p++;
|
|
if (*p == '0' && (*(p + 1) == 'x' || *(p + 1) == 'X'))
|
|
p += 2;
|
|
|
|
foundp = p;
|
|
while (ASCII_ISXDIGIT(*p))
|
|
p++;
|
|
|
|
if (p == foundp || (*p != ' ' && *p != '\t'))
|
|
goto failed;
|
|
*stop_code0 = strtol(foundp, NULL, 16);
|
|
if (*stop_code0 != 0x1f09 && *stop_code0 != 0x1f41)
|
|
goto failed;
|
|
|
|
/*
|
|
* Parse stop_code1.
|
|
*/
|
|
while (*p == ' ' || *p == '\t')
|
|
p++;
|
|
if (*p == '0' && (*(p + 1) == 'x' || *(p + 1) == 'X'))
|
|
p += 2;
|
|
|
|
foundp = p;
|
|
while (ASCII_ISXDIGIT(*p))
|
|
p++;
|
|
|
|
if (p == foundp || (*p != ' ' && *p != '\t' && *p != '\0'))
|
|
goto failed;
|
|
*stop_code1 = strtol(foundp, NULL, 16);
|
|
if (0xffff < *stop_code1)
|
|
goto failed;
|
|
|
|
while (*p == ' ' || *p == '\t')
|
|
p++;
|
|
if (*p != '\0')
|
|
goto failed;
|
|
|
|
return 0;
|
|
|
|
/*
|
|
* An error occurs...
|
|
*/
|
|
failed:
|
|
fprintf(stderr, _("%s: invalid stop code `%s'\n"), invoked_name,
|
|
argument);
|
|
return -1;
|
|
}
|
|
|
|
|
|
/*
|
|
* Parse stop code given as argument of the `-t' option.
|
|
*/
|
|
static int
|
|
parse_text_length_argument(const char *argument, ssize_t *text_length)
|
|
{
|
|
const char *p = argument;
|
|
const char *foundp;
|
|
|
|
while (*p == ' ' || *p == '\t')
|
|
p++;
|
|
|
|
foundp = p;
|
|
while (ASCII_ISDIGIT(*p))
|
|
p++;
|
|
if (p == foundp)
|
|
goto failed;
|
|
while (*p == ' ' || *p == '\t')
|
|
p++;
|
|
if (*p != '\0')
|
|
goto failed;
|
|
|
|
*text_length = strtol(foundp, NULL, 10);
|
|
return 0;
|
|
|
|
/*
|
|
* An error occurs...
|
|
*/
|
|
failed:
|
|
fprintf(stderr, _("%s: invalid text length `%s'\n"), invoked_name,
|
|
argument);
|
|
return -1;
|
|
}
|
|
|
|
|
|
/*
|
|
* Parse text position given as argument of the `-p' option.
|
|
*/
|
|
static int
|
|
parse_text_position_argument(const char *argument, EB_Position *text_position)
|
|
{
|
|
const char *p = argument;
|
|
const char *end_p;
|
|
int page;
|
|
int offset;
|
|
|
|
/*
|
|
* Parse page.
|
|
*/
|
|
while (*p == ' ' || *p == '\t')
|
|
p++;
|
|
|
|
if (*p == '0' && (*(p + 1) == 'x' || *(p + 1) == 'X'))
|
|
p += 2;
|
|
page = strtol(p, (char **)&end_p, 16);
|
|
if (end_p == p || *end_p != ':')
|
|
goto failed;
|
|
if (page <= 0)
|
|
goto failed;
|
|
|
|
/*
|
|
* Parse offset.
|
|
*/
|
|
p = end_p + 1;
|
|
|
|
if (*p == '0' && (*(p + 1) == 'x' || *(p + 1) == 'X'))
|
|
p += 2;
|
|
offset = strtol(p, (char **)&end_p, 16);
|
|
if (end_p == p)
|
|
goto failed;
|
|
while (*end_p == ' ' || *end_p == '\t')
|
|
end_p++;
|
|
if (*end_p != '\0')
|
|
goto failed;
|
|
if (offset < 0 || EB_SIZE_PAGE <= offset)
|
|
goto failed;
|
|
|
|
text_position->page = page;
|
|
text_position->offset = offset;
|
|
|
|
return 0;
|
|
|
|
/*
|
|
* An error occurs...
|
|
*/
|
|
failed:
|
|
fprintf(stderr, _("%s: invalid text position `%s'\n"), invoked_name,
|
|
argument);
|
|
return -1;
|
|
}
|
|
|
|
|
|
/*
|
|
* Output help message to standard out, then exit.
|
|
*/
|
|
static void
|
|
output_help(void)
|
|
{
|
|
printf(_("Usage: %s [option...] [book-directory] subbook\n"),
|
|
program_name);
|
|
printf(_("Options:\n"));
|
|
printf(_(" -c CODE, --code CODE\n"));
|
|
printf(_(" set stop code manually\n"));
|
|
printf(_(" -h --help display this help, then exit\n"));
|
|
printf(_(" -l LENGTH, --text-length LENGTH\n"));
|
|
printf(_(" maximum length of output text\n"));
|
|
printf(_(" (default: %d)\n"),
|
|
DEFAULT_MAX_TEXT_LENGTH);
|
|
printf(_(" -n --no-candidates suppress stop code candidates\n"));
|
|
printf(_(" -p PAGE:OFFSET, --text-position PAGE:OFFSET\n"));
|
|
printf(_(" start position of text\n"));
|
|
printf(_(" -v --version display version number, then exit\n"));
|
|
printf(_("\nArgument:\n"));
|
|
printf(_(" book-directory top directory of a CD-ROM book\n"));
|
|
printf(_(" (default: %s)\n"),
|
|
DEFAULT_BOOK_DIRECTORY);
|
|
printf(_("\nReport bugs to %s.\n"), MAILING_ADDRESS);
|
|
fflush(stdout);
|
|
}
|
|
|
|
|
|
/*
|
|
* Scan stop code in text.
|
|
*/
|
|
static int
|
|
scan_subbook_text(const char *book_directory, const char *subbook_name,
|
|
EB_Position *text_position, ssize_t max_text_length,
|
|
int show_stop_code_flag, unsigned int stop_code0, unsigned int stop_code1)
|
|
{
|
|
EB_Error_Code error_code;
|
|
EB_Book book;
|
|
EB_Hookset hookset;
|
|
EB_Hook hook;
|
|
EB_Appendix appendix;
|
|
EB_Appendix_Subbook appendix_subbook;
|
|
EB_Subbook_Code subbook_code;
|
|
char text[EB_SIZE_PAGE];
|
|
ssize_t text_length;
|
|
|
|
/*
|
|
* Initialize EB Library, `book', `appendix' and `hookset'.
|
|
*/
|
|
eb_initialize_library();
|
|
eb_initialize_book(&book);
|
|
eb_initialize_appendix(&appendix);
|
|
eb_initialize_hookset(&hookset);
|
|
|
|
/*
|
|
* Bind `book'.
|
|
*/
|
|
error_code = eb_bind(&book, book_directory);
|
|
if (error_code != EB_SUCCESS) {
|
|
fprintf(stderr, _("%s: failed to bind the book, %s: %s\n"),
|
|
program_name, eb_error_message(error_code), book_directory);
|
|
goto die;
|
|
}
|
|
|
|
/*
|
|
* Get a subbook code from the subbook name.
|
|
*/
|
|
error_code = find_subbook(&book, subbook_name, &subbook_code);
|
|
if (error_code != EB_SUCCESS) {
|
|
fprintf(stderr, "%s: %s: %s\n",
|
|
program_name, eb_error_message(error_code), subbook_name);
|
|
goto die;
|
|
}
|
|
|
|
/*
|
|
* Set current subbook.
|
|
*/
|
|
if (eb_set_subbook(&book, subbook_code) < 0) {
|
|
fprintf(stderr, _("%s: failed to set the current subbook, %s\n"),
|
|
program_name, eb_error_message(error_code));
|
|
goto die;
|
|
}
|
|
|
|
/*
|
|
* Set stop-code manually.
|
|
* (we hack `appendix' directly.)
|
|
*/
|
|
appendix.code = subbook_code;
|
|
appendix.subbook_current = &appendix_subbook;
|
|
appendix_subbook.code = subbook_code;
|
|
appendix_subbook.stop_code0 = stop_code0;
|
|
appendix_subbook.stop_code1 = stop_code1;
|
|
|
|
/*
|
|
* Set text hooks (for 0x1f41 and 0x1f09).
|
|
*/
|
|
if (show_stop_code_flag) {
|
|
hook.code = EB_HOOK_BEGIN_KEYWORD;
|
|
hook.function = hook_stop_code;
|
|
eb_set_hook(&hookset, &hook);
|
|
|
|
hook.code = EB_HOOK_SET_INDENT;
|
|
hook.function = hook_stop_code;
|
|
eb_set_hook(&hookset, &hook);
|
|
}
|
|
|
|
/*
|
|
* Get a position where the text data starts, if text_position
|
|
* is {page=0, offset=0}.
|
|
*/
|
|
if (text_position->page == 0 && text_position->offset == 0) {
|
|
error_code = eb_text(&book, text_position);
|
|
if (error_code != EB_SUCCESS) {
|
|
fprintf(stderr, _("%s: failed to get text information, %s\n"),
|
|
program_name, eb_error_message(error_code));
|
|
goto die;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Read text.
|
|
*/
|
|
error_code = eb_seek_text(&book, text_position);
|
|
if (error_code != EB_SUCCESS) {
|
|
fprintf(stderr, "%s: %s\n",
|
|
program_name, eb_error_message(error_code));
|
|
goto die;
|
|
}
|
|
|
|
text_length = 0;
|
|
while (max_text_length == 0 || text_length < max_text_length) {
|
|
ssize_t result_length;
|
|
size_t read_length;
|
|
|
|
if (max_text_length == 0
|
|
|| sizeof(text) - 1 <= max_text_length - text_length) {
|
|
read_length = sizeof(text) - 1;
|
|
} else {
|
|
read_length = max_text_length - text_length;
|
|
}
|
|
error_code = eb_read_text(&book, &appendix, &hookset, NULL,
|
|
read_length, text, &result_length);
|
|
if (error_code != EB_SUCCESS) {
|
|
fprintf(stderr, _("%s: failed to read text, %s\n"),
|
|
program_name, eb_error_message(error_code));
|
|
goto die;
|
|
}
|
|
if (result_length <= 0)
|
|
break;
|
|
fputs_eucjp_to_locale(text, stdout);
|
|
text_length += result_length;
|
|
}
|
|
|
|
/*
|
|
* Finalize `hookset', `appendix', `book' and EB Library.
|
|
*/
|
|
eb_finalize_hookset(&hookset);
|
|
eb_finalize_appendix(&appendix);
|
|
eb_finalize_book(&book);
|
|
eb_finalize_library();
|
|
return 0;
|
|
|
|
/*
|
|
* An error occurs...
|
|
*/
|
|
die:
|
|
eb_finalize_hookset(&hookset);
|
|
eb_finalize_appendix(&appendix);
|
|
eb_finalize_book(&book);
|
|
eb_finalize_library();
|
|
return -1;
|
|
}
|
|
|
|
|
|
/*
|
|
* Hook function for EB_HOOK_BEGIN_KEYWORD and EB_HOK_SET_INDENT.
|
|
*/
|
|
static EB_Error_Code
|
|
hook_stop_code(EB_Book *book, EB_Appendix *appendix, void *container,
|
|
EB_Hook_Code code, int argc, const unsigned int *argv)
|
|
{
|
|
char string[EB_SIZE_PAGE];
|
|
|
|
if (0 < book->text_context.printable_count) {
|
|
sprintf(string, "\n=== stop-code?: 0x%04x 0x%04x ===\n",
|
|
argv[0], argv[1]);
|
|
eb_write_text_string(book, string);
|
|
}
|
|
|
|
return EB_SUCCESS;
|
|
}
|