Replaced deprecated gtk_menu_popup() calls with modern constructs in gtk3.22-client
[freeciv.git] / utility / inputfile.c
blobce9edbfee11254d48307a60044552df17b574513
1 /**********************************************************************
2 Freeciv - Copyright (C) 1996 - A Kjeldberg, L Gregersen, P Unold
3 This program is free software; you can redistribute it and/or modify
4 it under the terms of the GNU General Public License as published by
5 the Free Software Foundation; either version 2, or (at your option)
6 any later version.
8 This program is distributed in the hope that it will be useful,
9 but WITHOUT ANY WARRANTY; without even the implied warranty of
10 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 GNU General Public License for more details.
12 ***********************************************************************/
14 /**********************************************************************
15 A low-level object for reading a registry-format file.
16 original author: David Pfitzner <dwp@mso.anu.edu.au>
18 This module implements an object which is useful for reading/parsing
19 a file in the registry format of registry.c. It takes care of the
20 low-level file-reading details, and provides functions to return
21 specific "tokens" from the file. Probably this should really use
22 higher-level tools... (flex/lex bison/yacc?)
24 When the user tries to read a token, we return a (const char*)
25 pointing to some data if the token was found, or NULL otherwise.
26 The data pointed to should not be modified. The retuned pointer
27 is valid _only_ until another inputfile is performed. (So should
28 be used immediately, or fc_strdup-ed etc.)
30 The tokens recognised are as follows:
31 (Single quotes are delimiters used here, but are not part of the
32 actual tokens/strings.)
33 Most tokens can be preceeded by optional whitespace; exceptions
34 are section_name and entry_name.
36 section_name: '[foo]'
37 returned token: 'foo'
39 entry_name: 'foo =' (optional whitespace allowed before '=')
40 returned token: 'foo'
42 end_of_line: newline, or optional '#' or ';' (comment characters)
43 followed by any other chars, then newline.
44 returned token: should not be used except to check non-NULL.
46 table_start: '{'
47 returned token: should not be used except to check non-NULL.
49 table_end: '}'
50 returned token: should not be used except to check non-NULL.
52 comma: literal ','
53 returned token: should not be used except to check non-NULL.
55 value: a signed integer, or a double-quoted string, or a
56 gettext-marked double quoted string. Strings _may_ contain
57 raw embedded newlines, and escaped doublequotes, or \.
58 eg: '123', '-999', '"foo"', '_("foo")'
59 returned token: string containing number, for numeric, or string
60 starting at first doublequote for strings, but ommiting
61 trailing double-quote. Note this does _not_ translate
62 escaped doublequotes etc back to normal.
64 ***********************************************************************/
66 #ifdef HAVE_CONFIG_H
67 #include <fc_config.h>
68 #endif
70 #include <stdarg.h>
71 #include <stdio.h>
72 #include <string.h>
74 /* utility */
75 #include "astring.h"
76 #include "fcintl.h"
77 #include "ioz.h"
78 #include "log.h"
79 #include "mem.h"
80 #include "shared.h" /* TRUE, FALSE */
81 #include "support.h"
83 #include "inputfile.h"
85 #define INF_DEBUG_FOUND FALSE
86 #define INF_DEBUG_NOT_FOUND FALSE
88 #define INF_MAGIC (0xabdc0132) /* arbitrary */
90 struct inputfile {
91 unsigned int magic; /* memory check */
92 char *filename; /* filename as passed to fopen */
93 fz_FILE *fp; /* read from this */
94 bool at_eof; /* flag for end-of-file */
95 struct astring cur_line; /* data from current line */
96 int cur_line_pos; /* position in current line */
97 int line_num; /* line number from file in cur_line */
98 struct astring token; /* data returned to user */
99 struct astring partial; /* used in accumulating multi-line strings;
100 used only in get_token_value, but put
101 here so it gets freed when file closed */
102 datafilename_fn_t datafn; /* function like datafilename(); use a
103 function pointer just to keep this
104 inputfile module "generic" */
105 bool in_string; /* set when reading multi-line strings,
106 to know not to handle *include at start
107 of line as include mechanism */
108 int string_start_line; /* when in_string is true, this is the
109 start line of current string */
110 struct inputfile *included_from; /* NULL for toplevel file, otherwise
111 points back to files which this one
112 has been included from */
115 /* A function to get a specific token type: */
116 typedef const char *(*get_token_fn_t)(struct inputfile *inf);
118 static const char *get_token_section_name(struct inputfile *inf);
119 static const char *get_token_entry_name(struct inputfile *inf);
120 static const char *get_token_eol(struct inputfile *inf);
121 static const char *get_token_table_start(struct inputfile *inf);
122 static const char *get_token_table_end(struct inputfile *inf);
123 static const char *get_token_comma(struct inputfile *inf);
124 static const char *get_token_value(struct inputfile *inf);
126 static struct {
127 const char *name;
128 get_token_fn_t func;
130 tok_tab[INF_TOK_LAST] =
132 { "section_name", get_token_section_name },
133 { "entry_name", get_token_entry_name },
134 { "end_of_line", get_token_eol },
135 { "table_start", get_token_table_start },
136 { "table_end", get_token_table_end },
137 { "comma", get_token_comma },
138 { "value", get_token_value },
141 static bool read_a_line(struct inputfile *inf);
143 #define inf_log(inf, level, message, ...) \
144 if (log_do_output_for_level(level)) { \
145 do_log(__FILE__, __FUNCTION__, __FC_LINE__, FALSE, level, "%s", \
146 inf_log_str(inf, message, ## __VA_ARGS__)); \
148 #define inf_warn(inf, message) \
149 inf_log(inf, LOG_NORMAL, "%s", message);
151 /**********************************************************************
152 Return true if c is a 'comment' character: '#' or ';'
153 ***********************************************************************/
154 static bool is_comment(int c)
156 return (c == '#' || c == ';');
159 /**********************************************************************
160 Set values to zeros; should have free'd/closed everything before
161 this if appropriate.
162 ***********************************************************************/
163 static void init_zeros(struct inputfile *inf)
165 fc_assert_ret(NULL != inf);
166 inf->magic = INF_MAGIC;
167 inf->filename = NULL;
168 inf->fp = NULL;
169 inf->datafn = NULL;
170 inf->included_from = NULL;
171 inf->line_num = inf->cur_line_pos = 0;
172 inf->at_eof = inf->in_string = FALSE;
173 inf->string_start_line = 0;
174 astr_init(&inf->cur_line);
175 astr_init(&inf->token);
176 astr_init(&inf->partial);
179 /**********************************************************************
180 Check sensible values for an opened inputfile.
181 ***********************************************************************/
182 static bool inf_sanity_check(struct inputfile *inf)
184 fc_assert_ret_val(NULL != inf, FALSE);
185 fc_assert_ret_val(INF_MAGIC == inf->magic, FALSE);
186 fc_assert_ret_val(NULL != inf->fp, FALSE);
187 fc_assert_ret_val(0 <= inf->line_num, FALSE);
188 fc_assert_ret_val(0 <= inf->cur_line_pos, FALSE);
189 fc_assert_ret_val(FALSE == inf->at_eof
190 || TRUE == inf->at_eof, FALSE);
191 fc_assert_ret_val(FALSE == inf->in_string
192 || TRUE == inf->in_string, FALSE);
193 #ifdef DEBUG
194 fc_assert_ret_val(0 <= inf->string_start_line, FALSE);
195 if (inf->included_from && !inf_sanity_check(inf->included_from)) {
196 return FALSE;
198 #endif /* DEBUG */
199 return TRUE;
202 /**************************************************************************
203 Return the filename the inputfile was loaded as, or "(anonymous)"
204 if this inputfile was loaded from a stream rather than from a file.
205 **************************************************************************/
206 static const char *inf_filename(struct inputfile *inf)
208 if (inf->filename) {
209 return inf->filename;
210 } else {
211 return "(anonymous)";
215 /**********************************************************************
216 Open the file, and return an allocated, initialized structure.
217 Returns NULL if the file could not be opened.
218 ***********************************************************************/
219 struct inputfile *inf_from_file(const char *filename,
220 datafilename_fn_t datafn)
222 struct inputfile *inf;
223 fz_FILE *fp;
225 fc_assert_ret_val(NULL != filename, NULL);
226 fc_assert_ret_val(0 < strlen(filename), NULL);
227 fp = fz_from_file(filename, "r", -1, 0);
228 if (!fp) {
229 return NULL;
231 log_debug("inputfile: opened \"%s\" ok", filename);
232 inf = inf_from_stream(fp, datafn);
233 inf->filename = fc_strdup(filename);
234 return inf;
237 /**********************************************************************
238 Open the stream, and return an allocated, initialized structure.
239 Returns NULL if the file could not be opened.
240 ***********************************************************************/
241 struct inputfile *inf_from_stream(fz_FILE *stream, datafilename_fn_t datafn)
243 struct inputfile *inf;
245 fc_assert_ret_val(NULL != stream, NULL);
246 inf = fc_malloc(sizeof(*inf));
247 init_zeros(inf);
249 inf->filename = NULL;
250 inf->fp = stream;
251 inf->datafn = datafn;
253 log_debug("inputfile: opened \"%s\" ok", inf_filename(inf));
254 return inf;
258 /**********************************************************************
259 Close the file and free associated memory, but don't recurse
260 included_from files, and don't free the actual memory where
261 the inf record is stored (ie, the memory where the users pointer
262 points to). This is used when closing an included file.
263 ***********************************************************************/
264 static void inf_close_partial(struct inputfile *inf)
266 fc_assert_ret(inf_sanity_check(inf));
268 log_debug("inputfile: sub-closing \"%s\"", inf_filename(inf));
270 if (fz_ferror(inf->fp) != 0) {
271 log_error("Error before closing %s: %s", inf_filename(inf),
272 fz_strerror(inf->fp));
273 fz_fclose(inf->fp);
274 inf->fp = NULL;
276 else if (fz_fclose(inf->fp) != 0) {
277 log_error("Error closing %s", inf_filename(inf));
279 if (inf->filename) {
280 free(inf->filename);
282 inf->filename = NULL;
283 astr_free(&inf->cur_line);
284 astr_free(&inf->token);
285 astr_free(&inf->partial);
287 /* assign zeros for safety if accidently re-use etc: */
288 init_zeros(inf);
289 inf->magic = ~INF_MAGIC;
291 log_debug("inputfile: sub-closed ok");
294 /**********************************************************************
295 Close the file and free associated memory, included any partially
296 recursed included files, and the memory allocated for 'inf' itself.
297 Should only be used on an actually open inputfile.
298 After this, the pointer should not be used.
299 ***********************************************************************/
300 void inf_close(struct inputfile *inf)
302 fc_assert_ret(inf_sanity_check(inf));
304 log_debug("inputfile: closing \"%s\"", inf_filename(inf));
305 if (inf->included_from) {
306 inf_close(inf->included_from);
308 inf_close_partial(inf);
309 free(inf);
310 log_debug("inputfile: closed ok");
313 /**********************************************************************
314 Return TRUE if have data for current line.
315 ***********************************************************************/
316 static bool have_line(struct inputfile *inf)
318 fc_assert_ret_val(inf_sanity_check(inf), FALSE);
319 return !astr_empty(&inf->cur_line);
322 /**********************************************************************
323 Return TRUE if current pos is at end of current line.
324 ***********************************************************************/
325 static bool at_eol(struct inputfile *inf)
327 fc_assert_ret_val(inf_sanity_check(inf), TRUE);
328 fc_assert_ret_val(inf->cur_line_pos <= astr_len(&inf->cur_line), TRUE);
329 return (inf->cur_line_pos >= astr_len(&inf->cur_line));
332 /**********************************************************************
333 Return TRUE if current pos is at end of file.
334 ***********************************************************************/
335 bool inf_at_eof(struct inputfile *inf)
337 fc_assert_ret_val(inf_sanity_check(inf), TRUE);
338 return inf->at_eof;
341 /**********************************************************************
342 Check for an include command, which is an isolated line with:
343 *include "filename"
344 If a file is included via this mechanism, returns 1, and sets up
345 data appropriately: (*inf) will now correspond to the new file,
346 which is opened but no data read, and inf->included_from is set
347 to newly malloced memory which corresponds to the old file.
348 ***********************************************************************/
349 static bool check_include(struct inputfile *inf)
351 const char *include_prefix = "*include";
352 static size_t len = 0;
353 size_t bare_name_len;
354 char *bare_name;
355 const char *c, *bare_name_start, *full_name;
356 struct inputfile *new_inf, temp;
358 if (len==0) {
359 len = strlen(include_prefix);
361 fc_assert_ret_val(inf_sanity_check(inf), FALSE);
362 if (inf->in_string || astr_len(&inf->cur_line) <= len
363 || inf->cur_line_pos > 0) {
364 return FALSE;
366 if (strncmp(astr_str(&inf->cur_line), include_prefix, len) != 0) {
367 return FALSE;
369 /* from here, the include-line must be well formed */
370 /* keep inf->cur_line_pos accurate just so error messages are useful */
372 /* skip any whitespace: */
373 inf->cur_line_pos = len;
374 c = astr_str(&inf->cur_line) + len;
375 while (*c != '\0' && fc_isspace(*c)){
376 c++;
379 if (*c != '\"') {
380 inf_log(inf, LOG_ERROR,
381 "Did not find opening doublequote for '*include' line");
382 return FALSE;
384 c++;
385 inf->cur_line_pos = c - astr_str(&inf->cur_line);
387 bare_name_start = c;
388 while (*c != '\0' && *c != '\"') c++;
389 if (*c != '\"') {
390 inf_log(inf, LOG_ERROR,
391 "Did not find closing doublequote for '*include' line");
392 return FALSE;
394 c++;
395 bare_name_len = c - bare_name_start;
396 bare_name = fc_malloc(bare_name_len);
397 strncpy(bare_name, bare_name_start, bare_name_len - 1);
398 bare_name[bare_name_len - 1] = '\0';
399 inf->cur_line_pos = c - astr_str(&inf->cur_line);
401 /* check rest of line is well-formed: */
402 while (*c != '\0' && fc_isspace(*c) && !is_comment(*c)) {
403 c++;
405 if (!(*c == '\0' || is_comment(*c))) {
406 inf_log(inf, LOG_ERROR, "Junk after filename for '*include' line");
407 return FALSE;
409 inf->cur_line_pos = astr_len(&inf->cur_line) - 1;
411 full_name = inf->datafn(bare_name);
412 if (!full_name) {
413 log_error("Could not find included file \"%s\"", bare_name);
414 free(bare_name);
415 return FALSE;
417 free(bare_name);
419 /* avoid recursion: (first filename may not have the same path,
420 * but will at least stop infinite recursion) */
422 struct inputfile *inc = inf;
423 do {
424 if (inc->filename && strcmp(full_name, inc->filename) == 0) {
425 log_error("Recursion trap on '*include' for \"%s\"", full_name);
426 return FALSE;
428 } while ((inc = inc->included_from));
431 new_inf = inf_from_file(full_name, inf->datafn);
433 /* Swap things around so that memory pointed to by inf (user pointer,
434 and pointer in calling functions) contains the new inputfile,
435 and newly allocated memory for new_inf contains the old inputfile.
436 This is pretty scary, lets hope it works...
438 temp = *new_inf;
439 *new_inf = *inf;
440 *inf = temp;
441 inf->included_from = new_inf;
442 return TRUE;
445 /**********************************************************************
446 Read a new line into cur_line.
447 Increments line_num and cur_line_pos.
448 Returns 0 if didn't read or other problem: treat as EOF.
449 Strips newline from input.
450 ***********************************************************************/
451 static bool read_a_line(struct inputfile *inf)
453 struct astring *line;
454 char *ret;
455 int pos;
457 fc_assert_ret_val(inf_sanity_check(inf), FALSE);
459 if (inf->at_eof) {
460 return FALSE;
463 /* abbreviation: */
464 line = &inf->cur_line;
466 /* minimum initial line length: */
467 astr_reserve(line, 80);
468 astr_clear(line);
469 pos = 0;
471 /* Read until we get a full line:
472 * At start of this loop, pos is index to trailing null
473 * (or first position) in line.
475 for(;;) {
476 ret = fz_fgets((char *) astr_str(line) + pos,
477 astr_capacity(line) - pos, inf->fp);
479 if (!ret) {
480 /* fgets failed */
481 if (pos > 0) {
482 inf_log(inf, LOG_ERROR, _("End-of-file not in line of its own"));
484 inf->at_eof = TRUE;
485 if (inf->in_string) {
486 /* Note: Don't allow multi-line strings to cross "include"
487 * boundaries */
488 inf_log(inf, LOG_ERROR, "Multi-line string went to end-of-file");
489 return FALSE;
491 break;
494 /* Cope with \n\r line endings if not caught by library:
495 * strip off any leading \r */
496 if (0 == pos && 0 < astr_len(line) && astr_str(line)[0] == '\r') {
497 memmove((char *)astr_str(line), astr_str(line)+1, astr_len(line));
500 pos = astr_len(line);
502 if (0 < pos && astr_str(line)[pos - 1] == '\n') {
503 int end;
504 /* Cope with \r\n line endings if not caught by library:
505 * strip off any trailing \r */
506 if (1 < pos && astr_str(line)[pos - 2] == '\r') {
507 end = pos - 2;
508 } else {
509 end = pos - 1;
511 *((char *) astr_str(line) + end) = '\0';
512 break;
514 astr_reserve(line, pos * 2);
517 if (!inf->at_eof) {
518 inf->line_num++;
519 inf->cur_line_pos = 0;
521 if (check_include(inf)) {
522 return read_a_line(inf);
524 return TRUE;
525 } else {
526 astr_clear(line);
527 if (inf->included_from) {
528 /* Pop the include, and get next line from file above instead. */
529 struct inputfile *inc = inf->included_from;
530 inf_close_partial(inf);
531 *inf = *inc; /* so the user pointer in still valid
532 * (and inf pointers in calling functions) */
533 free(inc);
534 return read_a_line(inf);
536 return FALSE;
540 /**********************************************************************
541 Return a detailed log message, including information on current line
542 number etc. Message can be NULL: then just logs information on where
543 we are in the file.
544 ***********************************************************************/
545 char *inf_log_str(struct inputfile *inf, const char *message, ...)
547 va_list args;
548 static char str[512];
550 fc_assert_ret_val(inf_sanity_check(inf), NULL);
552 if (message) {
553 va_start(args, message);
554 fc_vsnprintf(str, sizeof(str), message, args);
555 va_end(args);
556 sz_strlcat(str, "\n");
557 } else {
558 str[0] = '\0';
561 cat_snprintf(str, sizeof(str), " file \"%s\", line %d, pos %d%s",
562 inf_filename(inf), inf->line_num, inf->cur_line_pos,
563 (inf->at_eof ? ", EOF" : ""));
565 if (!astr_empty(&inf->cur_line)) {
566 cat_snprintf(str, sizeof(str), "\n looking at: '%s'",
567 astr_str(&inf->cur_line) + inf->cur_line_pos);
569 if (inf->in_string) {
570 cat_snprintf(str, sizeof(str),
571 "\n processing string starting at line %d",
572 inf->string_start_line);
574 while ((inf = inf->included_from)) { /* local pointer assignment */
575 cat_snprintf(str, sizeof(str), "\n included from file \"%s\", line %d",
576 inf_filename(inf), inf->line_num);
579 return str;
582 /**********************************************************************
583 Returns token of given type from given inputfile.
584 ***********************************************************************/
585 const char *inf_token(struct inputfile *inf, enum inf_token_type type)
587 const char *c;
588 const char *name;
589 get_token_fn_t func;
591 fc_assert_ret_val(inf_sanity_check(inf), NULL);
592 fc_assert_ret_val(INF_TOK_FIRST <= type && INF_TOK_LAST > type, NULL);
594 name = tok_tab[type].name ? tok_tab[type].name : "(unnamed)";
595 func = tok_tab[type].func;
597 if (!func) {
598 log_error("token type %d (%s) not supported yet", type, name);
599 c = NULL;
600 } else {
601 while (!have_line(inf) && read_a_line(inf)) {
602 /* Nothing. */
604 if (!have_line(inf)) {
605 c = NULL;
606 } else {
607 c = func(inf);
610 if (c && INF_DEBUG_FOUND) {
611 log_debug("inputfile: found %s '%s'", name, astr_str(&inf->token));
613 return c;
616 /**********************************************************************
617 Read as many tokens of specified type as possible, discarding
618 the results; returns number of such tokens read and discarded.
619 ***********************************************************************/
620 int inf_discard_tokens(struct inputfile *inf, enum inf_token_type type)
622 int count = 0;
624 while(inf_token(inf, type))
625 count++;
626 return count;
629 /**********************************************************************
630 Returns section name in current position of inputfile. Returns NULL
631 if there is no section name on that position. Sets inputfile position
632 after section name.
633 ***********************************************************************/
634 static const char *get_token_section_name(struct inputfile *inf)
636 const char *c, *start;
638 fc_assert_ret_val(have_line(inf), NULL);
640 c = astr_str(&inf->cur_line) + inf->cur_line_pos;
641 if (*c++ != '[') {
642 return NULL;
644 start = c;
645 while (*c != '\0' && *c != ']') {
646 c++;
648 if (*c != ']') {
649 return NULL;
651 *((char *) c) = '\0'; /* Tricky. */
652 astr_set(&inf->token, "%s", start);
653 *((char *) c) = ']'; /* Revert. */
654 inf->cur_line_pos = c + 1 - astr_str(&inf->cur_line);
655 return astr_str(&inf->token);
658 /**********************************************************************
659 Returns next entry name from inputfile. Skips white spaces and
660 comments. Sets inputfile position after entry name.
661 ***********************************************************************/
662 static const char *get_token_entry_name(struct inputfile *inf)
664 const char *c, *start, *end;
665 char trailing;
667 fc_assert_ret_val(have_line(inf), NULL);
669 c = astr_str(&inf->cur_line) + inf->cur_line_pos;
670 while (*c != '\0' && fc_isspace(*c)) {
671 c++;
673 if (*c == '\0') {
674 return NULL;
676 start = c;
677 while (*c != '\0' && !fc_isspace(*c) && *c != '=' && !is_comment(*c)) {
678 c++;
680 if (!(*c != '\0' && (fc_isspace(*c) || *c == '='))) {
681 return NULL;
683 end = c;
684 while (*c != '\0' && *c != '=' && !is_comment(*c)) {
685 c++;
687 if (*c != '=') {
688 return NULL;
690 trailing = *end;
691 *((char *) end) = '\0'; /* Tricky. */
692 astr_set(&inf->token, "%s", start);
693 *((char *) end) = trailing; /* Revert. */
694 inf->cur_line_pos = c + 1 - astr_str(&inf->cur_line);
695 return astr_str(&inf->token);
698 /**********************************************************************
699 If inputfile is at end-of-line, frees current line, and returns " ".
700 If there is still something on that line, returns NULL.
701 ***********************************************************************/
702 static const char *get_token_eol(struct inputfile *inf)
704 const char *c;
706 fc_assert_ret_val(have_line(inf), NULL);
708 if (!at_eol(inf)) {
709 c = astr_str(&inf->cur_line) + inf->cur_line_pos;
710 while (*c != '\0' && fc_isspace(*c)) {
711 c++;
713 if (*c != '\0' && !is_comment(*c)) {
714 return NULL;
718 /* finished with this line: say that we don't have it any more: */
719 astr_clear(&inf->cur_line);
720 inf->cur_line_pos = 0;
722 astr_set(&inf->token, " ");
723 return astr_str(&inf->token);
726 /**********************************************************************
727 Get a flag token of a single character, with optional
728 preceeding whitespace.
729 ***********************************************************************/
730 static const char *get_token_white_char(struct inputfile *inf,
731 char target)
733 const char *c;
735 fc_assert_ret_val(have_line(inf), NULL);
737 c = astr_str(&inf->cur_line) + inf->cur_line_pos;
738 while (*c != '\0' && fc_isspace(*c)) {
739 c++;
741 if (*c != target) {
742 return NULL;
744 inf->cur_line_pos = c + 1 - astr_str(&inf->cur_line);
745 astr_set(&inf->token, "%c", target);
746 return astr_str(&inf->token);
749 /**********************************************************************
750 Get flag token for table start, or NULL if that is not next token.
751 ***********************************************************************/
752 static const char *get_token_table_start(struct inputfile *inf)
754 return get_token_white_char(inf, '{');
757 /**********************************************************************
758 Get flag token for table end, or NULL if that is not next token.
759 ***********************************************************************/
760 static const char *get_token_table_end(struct inputfile *inf)
762 return get_token_white_char(inf, '}');
765 /**********************************************************************
766 Get flag token comma, or NULL if that is not next token.
767 ***********************************************************************/
768 static const char *get_token_comma(struct inputfile *inf)
770 return get_token_white_char(inf, ',');
773 /**********************************************************************
774 This one is more complicated; note that it may read in multiple lines.
775 ***********************************************************************/
776 static const char *get_token_value(struct inputfile *inf)
778 struct astring *partial;
779 const char *c, *start;
780 char trailing;
781 bool has_i18n_marking = FALSE;
782 char border_character = '\"';
784 fc_assert_ret_val(have_line(inf), NULL);
786 c = astr_str(&inf->cur_line) + inf->cur_line_pos;
787 while (*c != '\0' && fc_isspace(*c)) {
788 c++;
790 if (*c == '\0') {
791 return NULL;
794 if (*c == '-' || *c == '+' || fc_isdigit(*c)) {
795 /* a number: */
796 start = c++;
797 while (*c != '\0' && fc_isdigit(*c)) {
798 c++;
800 if (*c == '.') {
801 /* Float maybe */
802 c++;
803 while (*c != '\0' && fc_isdigit(*c)) {
804 c++;
807 /* check that the trailing stuff is ok: */
808 if (!(*c == '\0' || *c == ',' || fc_isspace(*c) || is_comment(*c))) {
809 return NULL;
811 /* If its a comma, we don't want to obliterate it permanently,
812 * so remember it: */
813 trailing = *c;
814 *((char *) c) = '\0'; /* Tricky. */
816 inf->cur_line_pos = c - astr_str(&inf->cur_line);
817 astr_set(&inf->token, "%s", start);
819 *((char *) c) = trailing; /* Revert. */
820 return astr_str(&inf->token);
823 /* allow gettext marker: */
824 if (*c == '_' && *(c + 1) == '(') {
825 has_i18n_marking = TRUE;
826 c += 2;
827 while (*c != '\0' && fc_isspace(*c)) {
828 c++;
830 if (*c == '\0') {
831 return NULL;
835 border_character = *c;
837 if (border_character == '*') {
838 const char *rfname;
839 fz_FILE *fp;
840 bool eof;
841 int pos;
843 c++;
845 start = c;
846 while (*c != '*') {
847 if (*c == '\0' || *c == '\n') {
848 return NULL;
850 c++;
852 c++;
853 /* check that the trailing stuff is ok: */
854 if (!(*c == '\0' || *c == ',' || fc_isspace(*c) || is_comment(*c))) {
855 return NULL;
857 /* We don't want to obliterate ending '*' permanently,
858 * so remember it: */
859 trailing = *(c - 1);
860 *((char *) (c - 1)) = '\0'; /* Tricky. */
862 rfname = fileinfoname(get_data_dirs(), start);
863 if (rfname == NULL) {
864 inf_log(inf, LOG_ERROR,
865 _("Cannot find stringfile \"%s\"."), start);
866 *((char *) c) = trailing; /* Revert. */
867 return NULL;
869 *((char *) c) = trailing; /* Revert. */
870 fp = fz_from_file(rfname, "r", -1, 0);
871 if (!fp) {
872 inf_log(inf, LOG_ERROR,
873 _("Cannot open stringfile \"%s\"."), rfname);
874 return NULL;
876 log_debug("Stringfile \"%s\" opened ok", start);
877 *((char *) (c - 1)) = trailing; /* Revert. */
878 astr_set(&inf->token, "*"); /* Mark as a string read from a file */
880 eof = FALSE;
881 pos = 1; /* Past 'filestring' marker */
882 while (!eof) {
883 char *ret;
885 ret = fz_fgets((char *) astr_str(&inf->token) + pos,
886 astr_capacity(&inf->token) - pos, fp);
887 if (ret == NULL) {
888 eof = TRUE;
889 } else {
890 pos = astr_len(&inf->token);
891 astr_reserve(&inf->token, pos + 200);
895 fz_fclose(fp);
897 inf->cur_line_pos = c + 1 - astr_str(&inf->cur_line);
899 return astr_str(&inf->token);
900 } else if (border_character != '\"'
901 && border_character != '\''
902 && border_character != '$') {
903 /* A one-word string: maybe FALSE or TRUE. */
904 start = c;
905 while (fc_isalnum(*c)) {
906 c++;
908 /* check that the trailing stuff is ok: */
909 if (!(*c == '\0' || *c == ',' || fc_isspace(*c) || is_comment(*c))) {
910 return NULL;
912 /* If its a comma, we don't want to obliterate it permanently,
913 * so remember it: */
914 trailing = *c;
915 *((char *) c) = '\0'; /* Tricky. */
917 inf->cur_line_pos = c - astr_str(&inf->cur_line);
918 astr_set(&inf->token, "%s", start);
920 *((char *) c) = trailing; /* Revert. */
921 return astr_str(&inf->token);
924 /* From here, we know we have a string, we just have to find the
925 trailing (un-escaped) double-quote. We read in extra lines if
926 necessary to find it. If we _don't_ find the end-of-string
927 (that is, we come to end-of-file), we return NULL, but we
928 leave the file in at_eof, and don't try to back-up to the
929 current point. (That would be more difficult, and probably
930 not necessary: at that point we probably have a malformed
931 string/file.)
933 As we read extra lines, the string value from previous
934 lines is placed in partial.
937 /* prepare for possibly multi-line string: */
938 inf->string_start_line = inf->line_num;
939 inf->in_string = TRUE;
941 partial = &inf->partial; /* abbreviation */
942 astr_clear(partial);
944 start = c++; /* start includes the initial \", to
945 * distinguish from a number */
946 for (;;) {
947 while (*c != '\0' && *c != border_character) {
948 /* skip over escaped chars, including backslash-doublequote,
949 * and backslash-backslash: */
950 if (*c == '\\' && *(c + 1) != '\0') {
951 c++;
953 c++;
956 if (*c == border_character) {
957 /* Found end of string */
958 break;
961 astr_add(partial, "%s\n", start);
963 if (!read_a_line(inf)) {
964 /* shouldn't happen */
965 inf_log(inf, LOG_ERROR,
966 "Bad return for multi-line string from read_a_line");
967 return NULL;
969 c = start = astr_str(&inf->cur_line);
972 /* found end of string */
973 trailing = *c;
974 *((char *) c) = '\0'; /* Tricky. */
976 inf->cur_line_pos = c + 1 - astr_str(&inf->cur_line);
977 astr_set(&inf->token, "%s%s", astr_str(partial), start);
979 *((char *) c) = trailing; /* Revert. */
981 /* check gettext tag at end: */
982 if (has_i18n_marking) {
983 if (*++c == ')') {
984 inf->cur_line_pos++;
985 } else {
986 inf_warn(inf, "Missing end of i18n string marking");
989 inf->in_string = FALSE;
990 return astr_str(&inf->token);