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)
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.
39 entry_name: 'foo =' (optional whitespace allowed before '=')
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.
47 returned token: should not be used except to check non-NULL.
50 returned token: should not be used except to check non-NULL.
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 ***********************************************************************/
67 #include <fc_config.h>
79 #include "shared.h" /* TRUE, FALSE */
82 #include "inputfile.h"
84 #define INF_DEBUG_FOUND FALSE
85 #define INF_DEBUG_NOT_FOUND FALSE
87 #define INF_MAGIC (0xabdc0132) /* arbitrary */
90 unsigned int magic
; /* memory check */
91 char *filename
; /* filename as passed to fopen */
92 fz_FILE
*fp
; /* read from this */
93 bool at_eof
; /* flag for end-of-file */
94 struct astring cur_line
; /* data from current line */
95 int cur_line_pos
; /* position in current line */
96 int line_num
; /* line number from file in cur_line */
97 struct astring token
; /* data returned to user */
98 struct astring partial
; /* used in accumulating multi-line strings;
99 used only in get_token_value, but put
100 here so it gets freed when file closed */
101 datafilename_fn_t datafn
; /* function like datafilename(); use a
102 function pointer just to keep this
103 inputfile module "generic" */
104 bool in_string
; /* set when reading multi-line strings,
105 to know not to handle *include at start
106 of line as include mechanism */
107 int string_start_line
; /* when in_string is true, this is the
108 start line of current string */
109 struct inputfile
*included_from
; /* NULL for toplevel file, otherwise
110 points back to files which this one
111 has been included from */
114 /* A function to get a specific token type: */
115 typedef const char *(*get_token_fn_t
)(struct inputfile
*inf
);
117 static const char *get_token_section_name(struct inputfile
*inf
);
118 static const char *get_token_entry_name(struct inputfile
*inf
);
119 static const char *get_token_eol(struct inputfile
*inf
);
120 static const char *get_token_table_start(struct inputfile
*inf
);
121 static const char *get_token_table_end(struct inputfile
*inf
);
122 static const char *get_token_comma(struct inputfile
*inf
);
123 static const char *get_token_value(struct inputfile
*inf
);
129 tok_tab
[INF_TOK_LAST
] =
131 { "section_name", get_token_section_name
},
132 { "entry_name", get_token_entry_name
},
133 { "end_of_line", get_token_eol
},
134 { "table_start", get_token_table_start
},
135 { "table_end", get_token_table_end
},
136 { "comma", get_token_comma
},
137 { "value", get_token_value
},
140 static bool read_a_line(struct inputfile
*inf
);
142 #define inf_log(inf, level, message, ...) \
143 if (log_do_output_for_level(level)) { \
144 do_log(__FILE__, __FUNCTION__, __FC_LINE__, FALSE, level, "%s", \
145 inf_log_str(inf, message, ## __VA_ARGS__)); \
147 #define inf_warn(inf, message) \
148 inf_log(inf, LOG_NORMAL, "%s", message);
150 /**********************************************************************
151 Return true if c is a 'comment' character: '#' or ';'
152 ***********************************************************************/
153 static bool is_comment(int c
)
155 return (c
== '#' || c
== ';');
158 /**********************************************************************
159 Set values to zeros; should have free'd/closed everything before
161 ***********************************************************************/
162 static void init_zeros(struct inputfile
*inf
)
164 fc_assert_ret(NULL
!= inf
);
165 inf
->magic
= INF_MAGIC
;
166 inf
->filename
= NULL
;
169 inf
->included_from
= NULL
;
170 inf
->line_num
= inf
->cur_line_pos
= 0;
171 inf
->at_eof
= inf
->in_string
= FALSE
;
172 inf
->string_start_line
= 0;
173 astr_init(&inf
->cur_line
);
174 astr_init(&inf
->token
);
175 astr_init(&inf
->partial
);
178 /**********************************************************************
179 Check sensible values for an opened inputfile.
180 ***********************************************************************/
181 static bool inf_sanity_check(struct inputfile
*inf
)
183 fc_assert_ret_val(NULL
!= inf
, FALSE
);
184 fc_assert_ret_val(INF_MAGIC
== inf
->magic
, FALSE
);
185 fc_assert_ret_val(NULL
!= inf
->fp
, FALSE
);
186 fc_assert_ret_val(0 <= inf
->line_num
, FALSE
);
187 fc_assert_ret_val(0 <= inf
->cur_line_pos
, FALSE
);
188 fc_assert_ret_val(FALSE
== inf
->at_eof
189 || TRUE
== inf
->at_eof
, FALSE
);
190 fc_assert_ret_val(FALSE
== inf
->in_string
191 || TRUE
== inf
->in_string
, FALSE
);
193 fc_assert_ret_val(0 <= inf
->string_start_line
, FALSE
);
194 if (inf
->included_from
&& !inf_sanity_check(inf
->included_from
)) {
201 /**************************************************************************
202 Return the filename the inputfile was loaded as, or "(anonymous)"
203 if this inputfile was loaded from a stream rather than from a file.
204 **************************************************************************/
205 static const char *inf_filename(struct inputfile
*inf
)
208 return inf
->filename
;
210 return "(anonymous)";
214 /**********************************************************************
215 Open the file, and return an allocated, initialized structure.
216 Returns NULL if the file could not be opened.
217 ***********************************************************************/
218 struct inputfile
*inf_from_file(const char *filename
,
219 datafilename_fn_t datafn
)
221 struct inputfile
*inf
;
224 fc_assert_ret_val(NULL
!= filename
, NULL
);
225 fc_assert_ret_val(0 < strlen(filename
), NULL
);
226 fp
= fz_from_file(filename
, "r", -1, 0);
230 log_debug("inputfile: opened \"%s\" ok", filename
);
231 inf
= inf_from_stream(fp
, datafn
);
232 inf
->filename
= fc_strdup(filename
);
236 /**********************************************************************
237 Open the stream, and return an allocated, initialized structure.
238 Returns NULL if the file could not be opened.
239 ***********************************************************************/
240 struct inputfile
*inf_from_stream(fz_FILE
* stream
, datafilename_fn_t datafn
)
242 struct inputfile
*inf
;
244 fc_assert_ret_val(NULL
!= stream
, NULL
);
245 inf
= fc_malloc(sizeof(*inf
));
248 inf
->filename
= NULL
;
250 inf
->datafn
= datafn
;
252 log_debug("inputfile: opened \"%s\" ok", inf_filename(inf
));
257 /**********************************************************************
258 Close the file and free associated memory, but don't recurse
259 included_from files, and don't free the actual memory where
260 the inf record is stored (ie, the memory where the users pointer
261 points to). This is used when closing an included file.
262 ***********************************************************************/
263 static void inf_close_partial(struct inputfile
*inf
)
265 fc_assert_ret(inf_sanity_check(inf
));
267 log_debug("inputfile: sub-closing \"%s\"", inf_filename(inf
));
269 if (fz_ferror(inf
->fp
) != 0) {
270 log_error("Error before closing %s: %s", inf_filename(inf
),
271 fz_strerror(inf
->fp
));
275 else if (fz_fclose(inf
->fp
) != 0) {
276 log_error("Error closing %s", inf_filename(inf
));
281 inf
->filename
= NULL
;
282 astr_free(&inf
->cur_line
);
283 astr_free(&inf
->token
);
284 astr_free(&inf
->partial
);
286 /* assign zeros for safety if accidently re-use etc: */
288 inf
->magic
= ~INF_MAGIC
;
290 log_debug("inputfile: sub-closed ok");
293 /**********************************************************************
294 Close the file and free associated memory, included any partially
295 recursed included files, and the memory allocated for 'inf' itself.
296 Should only be used on an actually open inputfile.
297 After this, the pointer should not be used.
298 ***********************************************************************/
299 void inf_close(struct inputfile
*inf
)
301 fc_assert_ret(inf_sanity_check(inf
));
303 log_debug("inputfile: closing \"%s\"", inf_filename(inf
));
304 if (inf
->included_from
) {
305 inf_close(inf
->included_from
);
307 inf_close_partial(inf
);
309 log_debug("inputfile: closed ok");
312 /**********************************************************************
313 Return TRUE if have data for current line.
314 ***********************************************************************/
315 static bool have_line(struct inputfile
*inf
)
317 fc_assert_ret_val(inf_sanity_check(inf
), FALSE
);
318 return !astr_empty(&inf
->cur_line
);
321 /**********************************************************************
322 Return TRUE if current pos is at end of current line.
323 ***********************************************************************/
324 static bool at_eol(struct inputfile
*inf
)
326 fc_assert_ret_val(inf_sanity_check(inf
), TRUE
);
327 fc_assert_ret_val(inf
->cur_line_pos
<= astr_len(&inf
->cur_line
), TRUE
);
328 return (inf
->cur_line_pos
>= astr_len(&inf
->cur_line
));
331 /**********************************************************************
332 Return TRUE if current pos is at end of file.
333 ***********************************************************************/
334 bool inf_at_eof(struct inputfile
*inf
)
336 fc_assert_ret_val(inf_sanity_check(inf
), TRUE
);
340 /**********************************************************************
341 Check for an include command, which is an isolated line with:
343 If a file is included via this mechanism, returns 1, and sets up
344 data appropriately: (*inf) will now correspond to the new file,
345 which is opened but no data read, and inf->included_from is set
346 to newly malloced memory which corresponds to the old file.
347 ***********************************************************************/
348 static bool check_include(struct inputfile
*inf
)
350 const char *include_prefix
= "*include";
351 static size_t len
= 0;
352 size_t bare_name_len
;
354 const char *c
, *bare_name_start
, *full_name
;
355 struct inputfile
*new_inf
, temp
;
358 len
= strlen(include_prefix
);
360 fc_assert_ret_val(inf_sanity_check(inf
), FALSE
);
361 if (inf
->in_string
|| astr_len(&inf
->cur_line
) <= len
362 || inf
->cur_line_pos
> 0) {
365 if (strncmp(astr_str(&inf
->cur_line
), include_prefix
, len
) != 0) {
368 /* from here, the include-line must be well formed */
369 /* keep inf->cur_line_pos accurate just so error messages are useful */
371 /* skip any whitespace: */
372 inf
->cur_line_pos
= len
;
373 c
= astr_str(&inf
->cur_line
) + len
;
374 while (*c
!= '\0' && fc_isspace(*c
)){
379 inf_log(inf
, LOG_ERROR
,
380 "Did not find opening doublequote for '*include' line");
384 inf
->cur_line_pos
= c
- astr_str(&inf
->cur_line
);
387 while (*c
!= '\0' && *c
!= '\"') c
++;
389 inf_log(inf
, LOG_ERROR
,
390 "Did not find closing doublequote for '*include' line");
394 bare_name_len
= c
- bare_name_start
;
395 bare_name
= fc_malloc(bare_name_len
);
396 strncpy(bare_name
, bare_name_start
, bare_name_len
- 1);
397 bare_name
[bare_name_len
- 1] = '\0';
398 inf
->cur_line_pos
= c
- astr_str(&inf
->cur_line
);
400 /* check rest of line is well-formed: */
401 while (*c
!= '\0' && fc_isspace(*c
) && !is_comment(*c
)) {
404 if (!(*c
== '\0' || is_comment(*c
))) {
405 inf_log(inf
, LOG_ERROR
, "Junk after filename for '*include' line");
408 inf
->cur_line_pos
= astr_len(&inf
->cur_line
) - 1;
410 full_name
= inf
->datafn(bare_name
);
412 log_error("Could not find included file \"%s\"", bare_name
);
418 /* avoid recursion: (first filename may not have the same path,
419 * but will at least stop infinite recursion) */
421 struct inputfile
*inc
= inf
;
423 if (inc
->filename
&& strcmp(full_name
, inc
->filename
) == 0) {
424 log_error("Recursion trap on '*include' for \"%s\"", full_name
);
427 } while ((inc
= inc
->included_from
));
430 new_inf
= inf_from_file(full_name
, inf
->datafn
);
432 /* Swap things around so that memory pointed to by inf (user pointer,
433 and pointer in calling functions) contains the new inputfile,
434 and newly allocated memory for new_inf contains the old inputfile.
435 This is pretty scary, lets hope it works...
440 inf
->included_from
= new_inf
;
444 /**********************************************************************
445 Read a new line into cur_line.
446 Increments line_num and cur_line_pos.
447 Returns 0 if didn't read or other problem: treat as EOF.
448 Strips newline from input.
449 ***********************************************************************/
450 static bool read_a_line(struct inputfile
*inf
)
452 struct astring
*line
;
456 fc_assert_ret_val(inf_sanity_check(inf
), FALSE
);
463 line
= &inf
->cur_line
;
465 /* minimum initial line length: */
466 astr_reserve(line
, 80);
470 /* Read until we get a full line:
471 * At start of this loop, pos is index to trailing null
472 * (or first position) in line.
475 ret
= fz_fgets((char *) astr_str(line
) + pos
,
476 astr_capacity(line
) - pos
, inf
->fp
);
481 if (inf
->in_string
) {
482 /* Note: Don't allow multi-line strings to cross "include"
484 inf_log(inf
, LOG_ERROR
, "Multi-line string went to end-of-file");
490 /* Cope with \n\r line endings if not caught by library:
491 * strip off any leading \r */
492 if (0 == pos
&& 0 < astr_len(line
) && astr_str(line
)[0] == '\r') {
493 memmove((char *)astr_str(line
), astr_str(line
)+1, astr_len(line
));
496 pos
= astr_len(line
);
498 if (0 < pos
&& astr_str(line
)[pos
- 1] == '\n') {
500 /* Cope with \r\n line endings if not caught by library:
501 * strip off any trailing \r */
502 if (1 < pos
&& astr_str(line
)[pos
- 2] == '\r') {
507 *((char *) astr_str(line
) + end
) = '\0';
510 astr_reserve(line
, pos
* 2);
515 inf
->cur_line_pos
= 0;
517 if (check_include(inf
)) {
518 return read_a_line(inf
);
523 if (inf
->included_from
) {
524 /* Pop the include, and get next line from file above instead. */
525 struct inputfile
*inc
= inf
->included_from
;
526 inf_close_partial(inf
);
527 *inf
= *inc
; /* so the user pointer in still valid
528 * (and inf pointers in calling functions) */
530 return read_a_line(inf
);
536 /**********************************************************************
537 Return a detailed log message, including information on current line
538 number etc. Message can be NULL: then just logs information on where
540 ***********************************************************************/
541 char *inf_log_str(struct inputfile
*inf
, const char *message
, ...)
544 static char str
[512];
546 fc_assert_ret_val(inf_sanity_check(inf
), NULL
);
549 va_start(args
, message
);
550 fc_vsnprintf(str
, sizeof(str
), message
, args
);
552 sz_strlcat(str
, "\n");
557 cat_snprintf(str
, sizeof(str
), " file \"%s\", line %d, pos %d%s",
558 inf_filename(inf
), inf
->line_num
, inf
->cur_line_pos
,
559 (inf
->at_eof
? ", EOF" : ""));
561 if (!astr_empty(&inf
->cur_line
)) {
562 cat_snprintf(str
, sizeof(str
), "\n looking at: '%s'",
563 astr_str(&inf
->cur_line
) + inf
->cur_line_pos
);
565 if (inf
->in_string
) {
566 cat_snprintf(str
, sizeof(str
),
567 "\n processing string starting at line %d",
568 inf
->string_start_line
);
570 while ((inf
= inf
->included_from
)) { /* local pointer assignment */
571 cat_snprintf(str
, sizeof(str
), "\n included from file \"%s\", line %d",
572 inf_filename(inf
), inf
->line_num
);
578 /**********************************************************************
579 Returns token of given type from given inputfile.
580 ***********************************************************************/
581 const char *inf_token(struct inputfile
*inf
, enum inf_token_type type
)
587 fc_assert_ret_val(inf_sanity_check(inf
), NULL
);
588 fc_assert_ret_val(INF_TOK_FIRST
<= type
&& INF_TOK_LAST
> type
, NULL
);
590 name
= tok_tab
[type
].name
? tok_tab
[type
].name
: "(unnamed)";
591 func
= tok_tab
[type
].func
;
594 log_error("token type %d (%s) not supported yet", type
, name
);
597 while (!have_line(inf
) && read_a_line(inf
)) {
600 if (!have_line(inf
)) {
606 if (c
&& INF_DEBUG_FOUND
) {
607 log_debug("inputfile: found %s '%s'", name
, astr_str(&inf
->token
));
612 /**********************************************************************
613 Read as many tokens of specified type as possible, discarding
614 the results; returns number of such tokens read and discarded.
615 ***********************************************************************/
616 int inf_discard_tokens(struct inputfile
*inf
, enum inf_token_type type
)
620 while(inf_token(inf
, type
))
625 /**********************************************************************
626 Returns section name in current position of inputfile. Returns NULL
627 if there is no section name on that position. Sets inputfile position
629 ***********************************************************************/
630 static const char *get_token_section_name(struct inputfile
*inf
)
632 const char *c
, *start
;
634 fc_assert_ret_val(have_line(inf
), NULL
);
636 c
= astr_str(&inf
->cur_line
) + inf
->cur_line_pos
;
641 while (*c
!= '\0' && *c
!= ']') {
647 *((char *) c
) = '\0'; /* Tricky. */
648 astr_set(&inf
->token
, "%s", start
);
649 *((char *) c
) = ']'; /* Revert. */
650 inf
->cur_line_pos
= c
+ 1 - astr_str(&inf
->cur_line
);
651 return astr_str(&inf
->token
);
654 /**********************************************************************
655 Returns next entry name from inputfile. Skips white spaces and
656 comments. Sets inputfile position after entry name.
657 ***********************************************************************/
658 static const char *get_token_entry_name(struct inputfile
*inf
)
660 const char *c
, *start
, *end
;
663 fc_assert_ret_val(have_line(inf
), NULL
);
665 c
= astr_str(&inf
->cur_line
) + inf
->cur_line_pos
;
666 while (*c
!= '\0' && fc_isspace(*c
)) {
673 while (*c
!= '\0' && !fc_isspace(*c
) && *c
!= '=' && !is_comment(*c
)) {
676 if (!(*c
!= '\0' && (fc_isspace(*c
) || *c
== '='))) {
680 while (*c
!= '\0' && *c
!= '=' && !is_comment(*c
)) {
687 *((char *) end
) = '\0'; /* Tricky. */
688 astr_set(&inf
->token
, "%s", start
);
689 *((char *) end
) = trailing
; /* Revert. */
690 inf
->cur_line_pos
= c
+ 1 - astr_str(&inf
->cur_line
);
691 return astr_str(&inf
->token
);
694 /**********************************************************************
695 If inputfile is at end-of-line, frees current line, and returns " ".
696 If there is still something on that line, returns NULL.
697 ***********************************************************************/
698 static const char *get_token_eol(struct inputfile
*inf
)
702 fc_assert_ret_val(have_line(inf
), NULL
);
705 c
= astr_str(&inf
->cur_line
) + inf
->cur_line_pos
;
706 while (*c
!= '\0' && fc_isspace(*c
)) {
709 if (*c
!= '\0' && !is_comment(*c
)) {
714 /* finished with this line: say that we don't have it any more: */
715 astr_clear(&inf
->cur_line
);
716 inf
->cur_line_pos
= 0;
718 astr_set(&inf
->token
, " ");
719 return astr_str(&inf
->token
);
722 /**********************************************************************
723 Get a flag token of a single character, with optional
724 preceeding whitespace.
725 ***********************************************************************/
726 static const char *get_token_white_char(struct inputfile
*inf
,
731 fc_assert_ret_val(have_line(inf
), NULL
);
733 c
= astr_str(&inf
->cur_line
) + inf
->cur_line_pos
;
734 while (*c
!= '\0' && fc_isspace(*c
)) {
740 inf
->cur_line_pos
= c
+ 1 - astr_str(&inf
->cur_line
);
741 astr_set(&inf
->token
, "%c", target
);
742 return astr_str(&inf
->token
);
745 /**********************************************************************
746 Get flag token for table start, or NULL if that is not next token.
747 ***********************************************************************/
748 static const char *get_token_table_start(struct inputfile
*inf
)
750 return get_token_white_char(inf
, '{');
753 /**********************************************************************
754 Get flag token for table end, or NULL if that is not next token.
755 ***********************************************************************/
756 static const char *get_token_table_end(struct inputfile
*inf
)
758 return get_token_white_char(inf
, '}');
761 /**********************************************************************
762 Get flag token comma, or NULL if that is not next token.
763 ***********************************************************************/
764 static const char *get_token_comma(struct inputfile
*inf
)
766 return get_token_white_char(inf
, ',');
769 /**********************************************************************
770 This one is more complicated; note that it may read in multiple lines.
771 ***********************************************************************/
772 static const char *get_token_value(struct inputfile
*inf
)
774 struct astring
*partial
;
775 const char *c
, *start
;
777 bool has_i18n_marking
= FALSE
;
778 char border_character
= '\"';
780 fc_assert_ret_val(have_line(inf
), NULL
);
782 c
= astr_str(&inf
->cur_line
) + inf
->cur_line_pos
;
783 while (*c
!= '\0' && fc_isspace(*c
)) {
790 if (*c
== '-' || fc_isdigit(*c
)) {
793 while(*c
!= '\0' && fc_isdigit(*c
)) {
796 /* check that the trailing stuff is ok: */
797 if (!(*c
== '\0' || *c
== ',' || fc_isspace(*c
) || is_comment(*c
))) {
800 /* If its a comma, we don't want to obliterate it permanently,
801 * so rememeber it: */
803 *((char *) c
) = '\0'; /* Tricky. */
805 inf
->cur_line_pos
= c
- astr_str(&inf
->cur_line
);
806 astr_set(&inf
->token
, "%s", start
);
808 *((char *) c
) = trailing
; /* Revert. */
809 return astr_str(&inf
->token
);
812 /* allow gettext marker: */
813 if (*c
== '_' && *(c
+ 1) == '(') {
814 has_i18n_marking
= TRUE
;
816 while (*c
!= '\0' && fc_isspace(*c
)) {
824 border_character
= *c
;
826 if (border_character
!= '\"'
827 && border_character
!= '\''
828 && border_character
!= '$') {
829 /* A one-word string: maybe FALSE or TRUE. */
831 while (fc_isalnum(*c
)) {
834 /* check that the trailing stuff is ok: */
835 if (!(*c
== '\0' || *c
== ',' || fc_isspace(*c
) || is_comment(*c
))) {
838 /* If its a comma, we don't want to obliterate it permanently,
839 * so rememeber it: */
841 *((char *) c
) = '\0'; /* Tricky. */
843 inf
->cur_line_pos
= c
- astr_str(&inf
->cur_line
);
844 astr_set(&inf
->token
, "%s", start
);
846 *((char *) c
) = trailing
; /* Revert. */
847 return astr_str(&inf
->token
);
850 /* From here, we know we have a string, we just have to find the
851 trailing (un-escaped) double-quote. We read in extra lines if
852 necessary to find it. If we _don't_ find the end-of-string
853 (that is, we come to end-of-file), we return NULL, but we
854 leave the file in at_eof, and don't try to back-up to the
855 current point. (That would be more difficult, and probably
856 not necessary: at that point we probably have a malformed
859 As we read extra lines, the string value from previous
860 lines is placed in partial.
863 /* prepare for possibly multi-line string: */
864 inf
->string_start_line
= inf
->line_num
;
865 inf
->in_string
= TRUE
;
867 partial
= &inf
->partial
; /* abbreviation */
870 start
= c
++; /* start includes the initial \", to
871 * distinguish from a number */
873 while (*c
!= '\0' && *c
!= border_character
) {
874 /* skip over escaped chars, including backslash-doublequote,
875 * and backslash-backslash: */
876 if (*c
== '\\' && *(c
+ 1) != '\0') {
882 if (*c
== border_character
) {
883 /* Found end of string */
887 astr_add(partial
, "%s\n", start
);
889 if (!read_a_line(inf
)) {
890 /* shouldn't happen */
891 inf_log(inf
, LOG_ERROR
,
892 "Bad return for multi-line string from read_a_line");
895 c
= start
= astr_str(&inf
->cur_line
);
898 /* found end of string */
900 *((char *) c
) = '\0'; /* Tricky. */
902 inf
->cur_line_pos
= c
+ 1 - astr_str(&inf
->cur_line
);
903 astr_set(&inf
->token
, "%s%s", astr_str(partial
), start
);
905 *((char *) c
) = trailing
; /* Revert. */
907 /* check gettext tag at end: */
908 if (has_i18n_marking
) {
912 inf_warn(inf
, "Missing end of i18n string marking");
915 inf
->in_string
= FALSE
;
916 return astr_str(&inf
->token
);