dmake: do not set MAKEFLAGS=k
[unleashed/tickless.git] / usr / src / cmd / msgfmt / xgettext.c
blob46b52ad12407aaf1d6f8a8f46e1dc311080c86fd
1 /*
2 * CDDL HEADER START
4 * The contents of this file are subject to the terms of the
5 * Common Development and Distribution License, Version 1.0 only
6 * (the "License"). You may not use this file except in compliance
7 * with the License.
9 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
10 * or http://www.opensolaris.org/os/licensing.
11 * See the License for the specific language governing permissions
12 * and limitations under the License.
14 * When distributing Covered Code, include this CDDL HEADER in each
15 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
16 * If applicable, add the following below this CDDL HEADER, with the
17 * fields enclosed by brackets "[]" replaced with your own identifying
18 * information: Portions Copyright [yyyy] [name of copyright owner]
20 * CDDL HEADER END
23 * Copyright 1991, 1999, 2001-2002 Sun Microsystems, Inc.
24 * All rights reserved.
25 * Use is subject to license terms.
27 #include <ctype.h>
28 #include <stdio.h>
29 #include <stdlib.h>
30 #include <string.h>
32 #define TRUE 1
33 #define FALSE 0
34 #define MAX_PATH_LEN 1024
35 #define MAX_DOMAIN_LEN 1024
36 #define MAX_STRING_LEN 2048
38 #define USAGE "Usage: xgettext [-a [-x exclude-file]] [-jns]\
39 [-c comment-tag]\n [-d default-domain] [-m prefix] \
40 [-M suffix] [-p pathname] files ...\n\
41 xgettext -h\n"
43 #define DEFAULT_DOMAIN "messages"
45 extern char yytext[];
46 extern int yylex(void);
49 * Contains a list of strings to be used to store ANSI-C style string.
50 * Each quoted string is stored in one node.
52 struct strlist_st {
53 char *str;
54 struct strlist_st *next;
58 * istextdomain : Boolean telling if this node contains textdomain call.
59 * isduplicate : Boolean telling if this node duplicate of any other msgid.
60 * msgid : contains msgid or textdomain if istextdomain is true.
61 * msgstr : contains msgstr.
62 * comment : comment extracted in case of -c option.
63 * fname : tells which file contains msgid.
64 * linenum : line number in the file.
65 * next : Next node.
67 struct element_st {
68 char istextdomain;
69 char isduplicate;
70 struct strlist_st *msgid;
71 struct strlist_st *msgstr;
72 struct strlist_st *comment;
73 char *fname;
74 int linenum;
75 struct element_st *next;
79 * dname : domain name. NULL if default domain.
80 * gettext_head : Head of linked list containing [d]gettext().
81 * gettext_tail : Tail of linked list containing [d]gettext().
82 * textdomain_head : Head of linked list containing textdomain().
83 * textdomain_tail : Tail of linked list containing textdomain().
84 * next : Next node.
86 * Each domain contains two linked list.
87 * (gettext_head, textdomain_head)
88 * If -s option is used, then textdomain_head contains all
89 * textdomain() calls and no textdomain() calls are stored in gettext_head.
90 * If -s option is not used, textdomain_head is empty list and
91 * gettext_head contains all gettext() dgettext(), and textdomain() calls.
93 struct domain_st {
94 char *dname;
95 struct element_st *gettext_head;
96 struct element_st *gettext_tail;
97 struct element_st *textdomain_head;
98 struct element_st *textdomain_tail;
99 struct domain_st *next;
103 * There are two domain linked lists.
104 * def_dom contains default domain linked list and
105 * dom_head contains all other deomain linked lists to be created by
106 * dgettext() calls.
108 static struct domain_st *def_dom = NULL;
109 static struct domain_st *dom_head = NULL;
110 static struct domain_st *dom_tail = NULL;
113 * This linked list contains a list of strings to be excluded when
114 * -x option is used.
116 static struct exclude_st {
117 struct strlist_st *exstr;
118 struct exclude_st *next;
119 } *excl_head;
122 * All option flags and values for each option if any.
124 static int aflg = FALSE;
125 static int cflg = FALSE;
126 static char *comment_tag = NULL;
127 static char *default_domain = NULL;
128 static int hflg = FALSE;
129 static int jflg = FALSE;
130 static int mflg = FALSE;
131 static int Mflg = FALSE;
132 static char *suffix = NULL;
133 static char *prefix = NULL;
134 static int nflg = FALSE;
135 static int pflg = FALSE;
136 static char *pathname = NULL;
137 static int sflg = FALSE;
138 static int tflg = FALSE; /* Undocumented option to extract dcgettext */
139 static int xflg = FALSE;
140 static char *exclude_file = NULL;
143 * Each variable shows the current state of parsing input file.
145 * in_comment : Means inside comment block (C or C++).
146 * in_cplus_comment : Means inside C++ comment block.
147 * in_gettext : Means inside gettext call.
148 * in_dgettext : Means inside dgettext call.
149 * in_dcgettext : Means inside dcgettext call.
150 * in_textdomain : Means inside textdomain call.
151 * in_str : Means currently processing ANSI style string.
152 * in_quote : Means currently processing double quoted string.
153 * in_skippable_string : Means currently processing double quoted string,
154 * that occurs outside a call to gettext, dgettext,
155 * dcgettext, textdomain, with -a not specified.
156 * is_last_comment_line : Means the current line is the last line
157 * of the comment block. This is necessary because
158 * in_comment becomes FALSE when '* /' is encountered.
159 * is_first_comma_found : This is used only for dcgettext because dcgettext()
160 * requires 2 commas. So need to do different action
161 * depending on which commas encountered.
162 * num_nested_open_paren : This keeps track of the number of open parens to
163 * handle dcgettext ((const char *)0,"msg",LC_TIME);
165 static int in_comment = FALSE;
166 static int in_cplus_comment = FALSE;
167 static int in_gettext = FALSE;
168 static int in_dgettext = FALSE;
169 static int in_dcgettext = FALSE;
170 static int in_textdomain = FALSE;
171 static int in_str = FALSE;
172 static int in_quote = FALSE;
173 static int is_last_comment_line = FALSE;
174 static int is_first_comma_found = FALSE;
175 static int in_skippable_string = FALSE;
176 static int num_nested_open_paren = 0;
179 * This variable contains the first line of gettext(), dgettext(), or
180 * textdomain() calls.
181 * This is necessary for multiple lines of a single call to store
182 * the starting line.
184 static int linenum_saved = 0;
186 int stdin_only = FALSE; /* Read input from stdin */
189 * curr_file : Contains current file name processed.
190 * curr_domain : Contains the current domain for each dgettext().
191 * This is NULL for gettext().
192 * curr_line : Contains the current line processed.
193 * qstring_buf : Contains the double quoted string processed.
194 * curr_linenum : Line number being processed in the current input file.
195 * warn_linenum : Line number of current warning message.
197 char curr_file[MAX_PATH_LEN];
198 static char curr_domain[MAX_DOMAIN_LEN];
199 static char curr_line[MAX_STRING_LEN];
200 static char qstring_buf[MAX_STRING_LEN];
201 int curr_linenum = 1;
202 int warn_linenum = 0;
205 * strhead : This list contains ANSI style string.
206 * Each node contains double quoted string.
207 * strtail : This is the tail of strhead.
208 * commhead : This list contains comments string.
209 * Each node contains one line of comment.
210 * commtail : This is the tail of commhead.
212 static struct strlist_st *strhead = NULL;
213 static struct strlist_st *strtail = NULL;
214 static struct strlist_st *commhead = NULL;
215 static struct strlist_st *commtail = NULL;
218 * gargc : Same as argc. Used to pass argc to lex routine.
219 * gargv : Same as argv. Used to pass argc to lex routine.
221 int gargc;
222 char **gargv;
224 static void add_line_to_comment(void);
225 static void add_qstring_to_str(void);
226 static void add_str_to_element_list(int, char *);
227 static void copy_strlist_to_str(char *, struct strlist_st *);
228 static void end_ansi_string(void);
229 static void free_strlist(struct strlist_st *);
230 void handle_newline(void);
231 static void initialize_globals(void);
232 static void output_comment(FILE *, struct strlist_st *);
233 static void output_msgid(FILE *, struct strlist_st *, int);
234 static void output_textdomain(FILE *, struct element_st *);
235 static void print_help(void);
236 static void read_exclude_file(void);
237 static void trim_line(char *);
238 static void write_all_files(void);
239 static void write_one_file(struct domain_st *);
241 static void lstrcat(char *, const char *);
244 * Utility functions to malloc a node and initialize fields.
246 static struct domain_st *new_domain(void);
247 static struct strlist_st *new_strlist(void);
248 static struct element_st *new_element(void);
249 static struct exclude_st *new_exclude(void);
252 * Main program of xgettext.
255 main(int argc, char **argv)
257 int opterr = FALSE;
258 int c;
260 initialize_globals();
262 while ((c = getopt(argc, argv, "jhax:nsc:d:m:M:p:t")) != EOF) {
263 switch (c) {
264 case 'a':
265 aflg = TRUE;
266 break;
267 case 'c':
268 cflg = TRUE;
269 comment_tag = optarg;
270 break;
271 case 'd':
272 default_domain = optarg;
273 break;
274 case 'h':
275 hflg = TRUE;
276 break;
277 case 'j':
278 jflg = TRUE;
279 break;
280 case 'M':
281 Mflg = TRUE;
282 suffix = optarg;
283 break;
284 case 'm':
285 mflg = TRUE;
286 prefix = optarg;
287 break;
288 case 'n':
289 nflg = TRUE;
290 break;
291 case 'p':
292 pflg = TRUE;
293 pathname = optarg;
294 break;
295 case 's':
296 sflg = TRUE;
297 break;
298 case 't':
299 tflg = TRUE;
300 break;
301 case 'x':
302 xflg = TRUE;
303 exclude_file = optarg;
304 break;
305 case '?':
306 opterr = TRUE;
307 break;
311 /* if -h is used, ignore all other options. */
312 if (hflg == TRUE) {
313 (void) fprintf(stderr, USAGE);
314 print_help();
315 exit(0);
318 /* -x can be used only with -a */
319 if ((xflg == TRUE) && (aflg == FALSE))
320 opterr = TRUE;
322 /* -j cannot be used with -a */
323 if ((jflg == TRUE) && (aflg == TRUE)) {
324 (void) fprintf(stderr,
325 "-a and -j options cannot be used together.\n");
326 opterr = TRUE;
329 /* -j cannot be used with -s */
330 if ((jflg == TRUE) && (sflg == TRUE)) {
331 (void) fprintf(stderr,
332 "-j and -s options cannot be used together.\n");
333 opterr = TRUE;
336 if (opterr == TRUE) {
337 (void) fprintf(stderr, USAGE);
338 exit(2);
341 /* error, if no files are specified. */
342 if (optind == argc) {
343 (void) fprintf(stderr, USAGE);
344 exit(2);
347 if (xflg == TRUE) {
348 read_exclude_file();
351 /* If files are -, then read from stdin */
352 if (argv[optind][0] == '-') {
353 stdin_only = TRUE;
354 optind++;
355 } else {
356 stdin_only = FALSE;
359 /* Store argc and argv to pass to yylex() */
360 gargc = argc;
361 gargv = argv;
363 #ifdef DEBUG
364 (void) printf("optind=%d\n", optind);
366 int i = optind;
367 for (; i < argc; i++) {
368 (void) printf(" %d, <%s>\n", i, argv[i]);
371 #endif
373 if (stdin_only == FALSE) {
374 if (freopen(argv[optind], "r", stdin) == NULL) {
375 (void) fprintf(stderr,
376 "ERROR, can't open input file: %s\n", argv[optind]);
377 exit(2);
379 (void) strcpy(curr_file, gargv[optind]);
380 optind++;
384 * Process input.
386 (void) yylex();
388 #ifdef DEBUG
389 printf("\n======= default_domain ========\n");
390 print_one_domain(def_dom);
391 printf("======= domain list ========\n");
392 print_all_domain(dom_head);
393 #endif
396 * Write out all .po files.
398 write_all_files();
400 return (0);
401 } /* main */
404 * Prints help information for each option.
406 static void
407 print_help(void)
409 (void) fprintf(stderr, "\n");
410 (void) fprintf(stderr,
411 "-a\t\t\tfind ALL strings\n");
412 (void) fprintf(stderr,
413 "-c <comment-tag>\tget comments containing <flag>\n");
414 (void) fprintf(stderr,
415 "-d <default-domain>\tuse <default-domain> for default domain\n");
416 (void) fprintf(stderr,
417 "-h\t\t\tHelp\n");
418 (void) fprintf(stderr,
419 "-j\t\t\tupdate existing file with the current result\n");
420 (void) fprintf(stderr,
421 "-M <suffix>\t\tfill in msgstr with msgid<suffix>\n");
422 (void) fprintf(stderr,
423 "-m <prefix>\t\tfill in msgstr with <prefix>msgid\n");
424 (void) fprintf(stderr,
425 "-n\t\t\tline# file name and line number info in output\n");
426 (void) fprintf(stderr,
427 "-p <pathname>\t\tuse <pathname> for output file directory\n");
428 (void) fprintf(stderr,
429 "-s\t\t\tgenerate sorted output files\n");
430 (void) fprintf(stderr,
431 "-x <exclude-file>\texclude strings in file <exclude-file>"
432 " from output\n");
433 (void) fprintf(stderr,
434 "-\t\t\tread stdin, use as a filter (input only)\n");
435 } /* print_help */
438 * Extract file name and line number information from macro line
439 * and set the global variable accordingly.
440 * The valid line format is
441 * 1) # nnn
442 * or
443 * 2) # nnn "xxxxx"
444 * where nnn is line number and xxxxx is file name.
446 static void
447 extract_filename_linenumber(char *mline)
449 int num;
450 char *p, *q, *r;
453 * mline can contain multi newline.
454 * line number should be increased by the number of newlines.
456 p = mline;
457 while ((p = strchr(p, '\n')) != NULL) {
458 p++;
459 curr_linenum++;
461 p = strchr(mline, ' ');
462 if (p == NULL)
463 return;
464 q = strchr(++p, ' ');
465 if (q == NULL) {
466 /* case 1 */
467 if ((num = atoi(p)) > 0) {
468 curr_linenum = num;
469 return;
471 } else {
472 /* case 2 */
473 *q++ = 0;
474 if (*q == '"') {
475 q++;
476 r = strchr(q, '"');
477 if (r == NULL) {
478 return;
480 *r = 0;
481 if ((num = atoi(p)) > 0) {
482 curr_linenum = num;
483 (void) strcpy(curr_file, q);
487 } /* extract_filename_linenumber */
490 * Handler for MACRO line which starts with #.
492 void
493 handle_macro_line(void)
495 #ifdef DEBUG
496 (void) printf("Macro line=<%s>\n", yytext);
497 #endif
498 if (cflg == TRUE)
499 lstrcat(curr_line, yytext);
501 if (in_quote == TRUE) {
502 lstrcat(qstring_buf, yytext);
503 } else if (in_comment == FALSE) {
504 extract_filename_linenumber(yytext);
507 curr_linenum--;
508 handle_newline();
509 } /* handle_macro_line */
512 * Handler for C++ comments which starts with //.
514 void
515 handle_cplus_comment_line(void)
517 if (cflg == TRUE)
518 lstrcat(curr_line, yytext);
520 if (in_quote == TRUE) {
521 lstrcat(qstring_buf, yytext);
522 } else if ((in_comment == FALSE) &&
523 (in_skippable_string == FALSE)) {
526 * If already in c comments, don't do anything.
527 * Set both flags to TRUE here.
528 * Both flags will be set to FALSE when newline
529 * encounters.
531 in_cplus_comment = TRUE;
532 in_comment = TRUE;
534 } /* handle_cplus_comment_line */
537 * Handler for the comment start (slash asterisk) in input file.
539 void
540 handle_open_comment(void)
542 if (cflg == TRUE)
543 lstrcat(curr_line, yytext);
545 if (in_quote == TRUE) {
546 lstrcat(qstring_buf, yytext);
547 } else if ((in_comment == FALSE) &&
548 (in_skippable_string == FALSE)) {
550 in_comment = TRUE;
551 is_last_comment_line = FALSE;
553 * If there is any comment extracted before accidently,
554 * clean it up and start the new comment again.
556 free_strlist(commhead);
557 commhead = commtail = NULL;
562 * Handler for the comment end (asterisk slash) in input file.
564 void
565 handle_close_comment(void)
567 if (cflg == TRUE)
568 lstrcat(curr_line, yytext);
570 if (in_quote == TRUE) {
571 lstrcat(qstring_buf, yytext);
572 } else if (in_skippable_string == FALSE) {
573 in_comment = FALSE;
574 is_last_comment_line = TRUE;
579 * Handler for "gettext" in input file.
581 void
582 handle_gettext(void)
585 * If -t option is specified to extrct dcgettext,
586 * don't do anything for gettext().
588 if (tflg == TRUE) {
589 return;
592 num_nested_open_paren = 0;
594 if (cflg == TRUE)
595 lstrcat(curr_line, yytext);
597 if (in_quote == TRUE) {
598 lstrcat(qstring_buf, yytext);
599 } else if (in_comment == FALSE) {
600 in_gettext = TRUE;
601 linenum_saved = curr_linenum;
603 * gettext will be put into default domain .po file
604 * curr_domain does not change for gettext.
606 curr_domain[0] = '\0';
608 } /* handle_gettext */
611 * Handler for "dgettext" in input file.
613 void
614 handle_dgettext(void)
617 * If -t option is specified to extrct dcgettext,
618 * don't do anything for dgettext().
620 if (tflg == TRUE) {
621 return;
624 num_nested_open_paren = 0;
626 if (cflg == TRUE)
627 lstrcat(curr_line, yytext);
629 if (in_quote == TRUE) {
630 lstrcat(qstring_buf, yytext);
631 } else if (in_comment == FALSE) {
632 in_dgettext = TRUE;
633 linenum_saved = curr_linenum;
635 * dgettext will be put into domain file specified.
636 * curr_domain will follow.
638 curr_domain[0] = '\0';
640 } /* handle_dgettext */
643 * Handler for "dcgettext" in input file.
645 void
646 handle_dcgettext(void)
649 * dcgettext will be extracted only when -t flag is specified.
651 if (tflg == FALSE) {
652 return;
655 num_nested_open_paren = 0;
657 is_first_comma_found = FALSE;
659 if (cflg == TRUE)
660 lstrcat(curr_line, yytext);
662 if (in_quote == TRUE) {
663 lstrcat(qstring_buf, yytext);
664 } else if (in_comment == FALSE) {
665 in_dcgettext = TRUE;
666 linenum_saved = curr_linenum;
668 * dcgettext will be put into domain file specified.
669 * curr_domain will follow.
671 curr_domain[0] = '\0';
673 } /* handle_dcgettext */
676 * Handler for "textdomain" in input file.
678 void
679 handle_textdomain(void)
681 if (cflg == TRUE)
682 lstrcat(curr_line, yytext);
684 if (in_quote == TRUE) {
685 lstrcat(qstring_buf, yytext);
686 } else if (in_comment == FALSE) {
687 in_textdomain = TRUE;
688 linenum_saved = curr_linenum;
689 curr_domain[0] = '\0';
691 } /* handle_textdomain */
694 * Handler for '(' in input file.
696 void
697 handle_open_paren(void)
699 if (cflg == TRUE)
700 lstrcat(curr_line, yytext);
702 if (in_quote == TRUE) {
703 lstrcat(qstring_buf, yytext);
704 } else if (in_comment == FALSE) {
705 if ((in_gettext == TRUE) ||
706 (in_dgettext == TRUE) ||
707 (in_dcgettext == TRUE) ||
708 (in_textdomain == TRUE)) {
709 in_str = TRUE;
710 num_nested_open_paren++;
713 } /* handle_open_paren */
716 * Handler for ')' in input file.
718 void
719 handle_close_paren(void)
721 if (cflg == TRUE)
722 lstrcat(curr_line, yytext);
724 if (in_quote == TRUE) {
725 lstrcat(qstring_buf, yytext);
726 } else if (in_comment == FALSE) {
727 if ((in_gettext == TRUE) ||
728 (in_dgettext == TRUE) ||
729 (in_dcgettext == TRUE) ||
730 (in_textdomain == TRUE)) {
732 * If this is not the matching close paren with
733 * the first open paren, no action is necessary.
735 if (--num_nested_open_paren > 0)
736 return;
737 add_str_to_element_list(in_textdomain, curr_domain);
738 in_str = FALSE;
739 in_gettext = FALSE;
740 in_dgettext = FALSE;
741 in_dcgettext = FALSE;
742 in_textdomain = FALSE;
743 } else if (aflg == TRUE) {
744 end_ansi_string();
747 } /* handle_close_paren */
750 * Handler for '\\n' in input file.
752 * This is a '\' followed by new line.
753 * This can be treated like a new line except when this is a continuation
754 * of a ANSI-C string.
755 * If this is a part of ANSI string, treat the current line as a double
756 * quoted string and the next line is the start of the double quoted
757 * string.
759 void
760 handle_esc_newline(void)
762 if (cflg == TRUE)
763 lstrcat(curr_line, "\\");
765 curr_linenum++;
767 if (in_quote == TRUE) {
768 add_qstring_to_str();
769 } else if ((in_comment == TRUE) ||
770 (is_last_comment_line == TRUE)) {
771 if (in_cplus_comment == FALSE) {
772 add_line_to_comment();
776 curr_line[0] = '\0';
777 } /* handle_esc_newline */
780 * Handler for '"' in input file.
782 void
783 handle_quote(void)
785 if (cflg == TRUE)
786 lstrcat(curr_line, yytext);
788 if (in_comment == TRUE) {
789 /*EMPTY*/
790 } else if ((in_gettext == TRUE) ||
791 (in_dgettext == TRUE) ||
792 (in_dcgettext == TRUE) ||
793 (in_textdomain == TRUE)) {
794 if (in_str == TRUE) {
795 if (in_quote == FALSE) {
796 in_quote = TRUE;
797 } else {
798 add_qstring_to_str();
799 in_quote = FALSE;
802 } else if (aflg == TRUE) {
804 * The quote is found outside of gettext, dgetext, and
805 * textdomain. Everytime a quoted string is found,
806 * add it to the string list.
807 * in_str stays TRUE until ANSI string ends.
809 if (in_str == TRUE) {
810 if (in_quote == TRUE) {
811 in_quote = FALSE;
812 add_qstring_to_str();
813 } else {
814 in_quote = TRUE;
816 } else {
817 in_str = TRUE;
818 in_quote = TRUE;
819 linenum_saved = curr_linenum;
821 } else {
822 in_skippable_string = (in_skippable_string == TRUE) ?
823 FALSE : TRUE;
825 } /* handle_quote */
828 * Handler for ' ' or TAB in input file.
830 void
831 handle_spaces(void)
833 if (cflg == TRUE)
834 lstrcat(curr_line, yytext);
836 if (in_quote == TRUE) {
837 lstrcat(qstring_buf, yytext);
839 } /* handle_spaces */
842 * Flattens a linked list containing ANSI string to the one string.
844 static void
845 copy_strlist_to_str(char *str, struct strlist_st *strlist)
847 struct strlist_st *p;
849 str[0] = '\0';
851 if (strlist != NULL) {
852 p = strlist;
853 while (p != NULL) {
854 if (p->str != NULL) {
855 lstrcat(str, p->str);
857 p = p->next;
860 } /* copy_strlist_to_str */
863 * Handler for ',' in input file.
865 void
866 handle_comma(void)
868 if (cflg == TRUE)
869 lstrcat(curr_line, yytext);
871 if (in_quote == TRUE) {
872 lstrcat(qstring_buf, yytext);
873 } else if (in_comment == FALSE) {
874 if (in_str == TRUE) {
875 if (in_dgettext == TRUE) {
876 copy_strlist_to_str(curr_domain, strhead);
877 free_strlist(strhead);
878 strhead = strtail = NULL;
879 } else if (in_dcgettext == TRUE) {
881 * Ignore the second comma.
883 if (is_first_comma_found == FALSE) {
884 copy_strlist_to_str(curr_domain,
885 strhead);
886 free_strlist(strhead);
887 strhead = strtail = NULL;
888 is_first_comma_found = TRUE;
890 } else if (aflg == TRUE) {
891 end_ansi_string();
895 } /* handle_comma */
898 * Handler for any other character that does not have special handler.
900 void
901 handle_character(void)
903 if (cflg == TRUE)
904 lstrcat(curr_line, yytext);
906 if (in_quote == TRUE) {
907 lstrcat(qstring_buf, yytext);
908 } else if (in_comment == FALSE) {
909 if (in_str == TRUE) {
910 if (aflg == TRUE) {
911 end_ansi_string();
915 } /* handle_character */
918 * Handler for new line in input file.
920 void
921 handle_newline(void)
923 curr_linenum++;
926 * in_quote is always FALSE here for ANSI-C code.
928 if ((in_comment == TRUE) ||
929 (is_last_comment_line == TRUE)) {
930 if (in_cplus_comment == TRUE) {
931 in_cplus_comment = FALSE;
932 in_comment = FALSE;
933 } else {
934 add_line_to_comment();
938 curr_line[0] = '\0';
940 * C++ comment always ends with new line.
942 } /* handle_newline */
945 * Process ANSI string.
947 static void
948 end_ansi_string(void)
950 if ((aflg == TRUE) &&
951 (in_str == TRUE) &&
952 (in_gettext == FALSE) &&
953 (in_dgettext == FALSE) &&
954 (in_dcgettext == FALSE) &&
955 (in_textdomain == FALSE)) {
956 add_str_to_element_list(FALSE, curr_domain);
957 in_str = FALSE;
959 } /* end_ansi_string */
962 * Initialize global variables if necessary.
964 static void
965 initialize_globals(void)
967 default_domain = strdup(DEFAULT_DOMAIN);
968 curr_domain[0] = '\0';
969 curr_file[0] = '\0';
970 qstring_buf[0] = '\0';
971 } /* initialize_globals() */
974 * Extract only string part when read a exclude file by removing
975 * keywords (e.g. msgid, msgstr, # ) and heading and trailing blanks and
976 * double quotes.
978 static void
979 trim_line(char *line)
981 int i, p, len;
982 int first = 0;
983 int last = 0;
984 char c;
986 len = strlen(line);
989 * Find the position of the last non-whitespace character.
991 i = len - 1;
992 /*CONSTCOND*/
993 while (1) {
994 c = line[i--];
995 if ((c != ' ') && (c != '\n') && (c != '\t')) {
996 last = ++i;
997 break;
1002 * Find the position of the first non-whitespace character
1003 * by skipping "msgid" initially.
1005 if (strncmp("msgid ", line, 6) == 0) {
1006 i = 5;
1007 } else if (strncmp("msgstr ", line, 7) == 0) {
1008 i = 6;
1009 } else if (strncmp("# ", line, 2) == 0) {
1010 i = 2;
1011 } else {
1012 i = 0;
1015 /*CONSTCOND*/
1016 while (1) {
1017 c = line[i++];
1018 if ((c != ' ') && (c != '\n') && (c != '\t')) {
1019 first = --i;
1020 break;
1025 * For Backward compatibility, we consider both double quoted
1026 * string and non-quoted string.
1027 * The double quote is removed before being stored if exists.
1029 if (line[first] == '"') {
1030 first++;
1032 if (line[last] == '"') {
1033 last--;
1037 * Now copy the valid part of the string.
1039 p = first;
1040 for (i = 0; i <= (last-first); i++) {
1041 line[i] = line[p++];
1043 line [i] = '\0';
1044 } /* trim_line */
1047 * Read exclude file and stores it in the global linked list.
1049 static void
1050 read_exclude_file(void)
1052 FILE *fp;
1053 struct exclude_st *tmp_excl;
1054 struct strlist_st *tail;
1055 int ignore_line;
1056 char line [MAX_STRING_LEN];
1058 if ((fp = fopen(exclude_file, "r")) == NULL) {
1059 (void) fprintf(stderr, "ERROR, can't open exclude file: %s\n",
1060 exclude_file);
1061 exit(2);
1064 ignore_line = TRUE;
1065 while (fgets(line, MAX_STRING_LEN, fp) != NULL) {
1067 * Line starting with # is a comment line and ignored.
1068 * Blank line is ignored, too.
1070 if ((line[0] == '\n') || (line[0] == '#')) {
1071 continue;
1072 } else if (strncmp(line, "msgstr", 6) == 0) {
1073 ignore_line = TRUE;
1074 } else if (strncmp(line, "domain", 6) == 0) {
1075 ignore_line = TRUE;
1076 } else if (strncmp(line, "msgid", 5) == 0) {
1077 ignore_line = FALSE;
1078 tmp_excl = new_exclude();
1079 tmp_excl->exstr = new_strlist();
1080 trim_line(line);
1081 tmp_excl->exstr->str = strdup(line);
1082 tail = tmp_excl->exstr;
1084 * Prepend new exclude string node to the list.
1086 tmp_excl->next = excl_head;
1087 excl_head = tmp_excl;
1088 } else {
1090 * If more than one line of string forms msgid,
1091 * append it to the string linked list.
1093 if (ignore_line == FALSE) {
1094 trim_line(line);
1095 tail->next = new_strlist();
1096 tail->next->str = strdup(line);
1097 tail = tail->next;
1100 } /* while */
1102 #ifdef DEBUG
1103 tmp_excl = excl_head;
1104 while (tmp_excl != NULL) {
1105 printf("============================\n");
1106 tail = tmp_excl->exstr;
1107 while (tail != NULL) {
1108 printf("%s###\n", tail->str);
1109 tail = tail->next;
1111 tmp_excl = tmp_excl->next;
1113 #endif
1114 } /* read_exclude_file */
1117 * Get next character from the string list containing ANSI style string.
1118 * This function returns three valus. (p, *m, *c).
1119 * p is returned by return value and, *m and *c are returned by changing
1120 * values in the location pointed.
1122 * p : points node in the linked list for ANSI string.
1123 * Each node contains double quoted string.
1124 * m : The location of the next characters in the double quoted string
1125 * as integer index in the string.
1126 * When it gets to end of quoted string, the next node will be
1127 * read and m starts as zero for every new node.
1128 * c : Stores the value of the characterto be returned.
1130 static struct strlist_st *
1131 get_next_ch(struct strlist_st *p, int *m, char *c)
1133 char ch, oct, hex;
1134 int value, i;
1137 * From the string list, find non-null string first.
1140 /*CONSTCOND*/
1141 while (1) {
1142 if (p == NULL) {
1143 break;
1144 } else if (p->str == NULL) {
1145 p = p->next;
1146 } else if (p->str[*m] == '\0') {
1147 p = p->next;
1148 *m = 0;
1149 } else {
1150 break;
1155 * No more character is available.
1157 if (p == NULL) {
1158 *c = 0;
1159 return (NULL);
1163 * Check if the character back slash.
1164 * If yes, ANSI defined escape sequence rule is used.
1166 if (p->str[*m] != '\\') {
1167 *c = p->str[*m];
1168 *m = *m + 1;
1169 return (p);
1170 } else {
1172 * Get next character after '\'.
1174 *m = *m + 1;
1175 ch = p->str[*m];
1176 switch (ch) {
1177 case 'a':
1178 *c = '\a';
1179 break;
1180 case 'b':
1181 *c = '\b';
1182 break;
1183 case 'f':
1184 *c = '\f';
1185 break;
1186 case 'n':
1187 *c = '\n';
1188 break;
1189 case 'r':
1190 *c = '\r';
1191 break;
1192 case 't':
1193 *c = '\t';
1194 break;
1195 case 'v':
1196 *c = '\v';
1197 break;
1198 case '0':
1199 case '1':
1200 case '2':
1201 case '3':
1202 case '4':
1203 case '5':
1204 case '6':
1205 case '7':
1207 * Get maximum of three octal digits.
1209 value = ch;
1210 for (i = 0; i < 2; i++) {
1211 *m = *m + 1;
1212 oct = p->str[*m];
1213 if ((oct >= '0') && (oct <= '7')) {
1214 value = value * 8 + (oct - '0');
1215 } else {
1216 *m = *m - 1;
1217 break;
1220 *c = value;
1221 #ifdef DEBUG
1222 /* (void) fprintf(stderr, "octal=%d\n", value); */
1223 #endif
1224 break;
1225 case 'x':
1226 value = 0;
1228 * Remove all heading zeros first and
1229 * get one or two valuid hexadecimal charaters.
1231 *m = *m + 1;
1232 while (p->str[*m] == '0') {
1233 *m = *m + 1;
1235 value = 0;
1236 for (i = 0; i < 2; i++) {
1237 hex = p->str[*m];
1238 *m = *m + 1;
1239 if (isdigit(hex)) {
1240 value = value * 16 + (hex - '0');
1241 } else if (isxdigit(hex)) {
1242 hex = tolower(hex);
1243 value = value * 16 + (hex - 'a' + 10);
1244 } else {
1245 *m = *m - 1;
1246 break;
1249 *c = value;
1250 #ifdef DEBUG
1251 (void) fprintf(stderr, "hex=%d\n", value);
1252 #endif
1253 *m = *m - 1;
1254 break;
1255 default :
1257 * Undefined by ANSI.
1258 * Just ignore "\".
1260 *c = p->str[*m];
1261 break;
1264 * Advance pointer to point the next character to be parsed.
1266 *m = *m + 1;
1267 return (p);
1269 } /* get_next_ch */
1272 * Compares two msgids.
1273 * Comparison is done by values, not by characters represented.
1274 * For example, '\t', '\011' and '0x9' are identical values.
1275 * Return values are same as in strcmp.
1276 * 1 if msgid1 > msgid2
1277 * 0 if msgid1 = msgid2
1278 * -1 if msgid1 < msgid2
1280 static int
1281 msgidcmp(struct strlist_st *id1, struct strlist_st *id2)
1283 char c1, c2;
1284 int m1, m2;
1286 m1 = 0;
1287 m2 = 0;
1289 /*CONSTCOND*/
1290 while (1) {
1291 id1 = get_next_ch(id1, &m1, &c1);
1292 id2 = get_next_ch(id2, &m2, &c2);
1294 if ((c1 == 0) && (c2 == 0)) {
1295 return (0);
1298 if (c1 > c2) {
1299 return (1);
1300 } else if (c1 < c2) {
1301 return (-1);
1304 /*NOTREACHED*/
1305 } /* msgidcmp */
1308 * Check if a ANSI string (which is a linked list itself) is a duplicate
1309 * of any string in the list of ANSI string.
1311 static int
1312 isduplicate(struct element_st *list, struct strlist_st *str)
1314 struct element_st *p;
1316 if (list == NULL) {
1317 return (FALSE);
1320 p = list;
1321 while (p != NULL) {
1322 if (p->msgid != NULL) {
1323 if (msgidcmp(p->msgid, str) == 0) {
1324 return (TRUE);
1327 p = p->next;
1330 return (FALSE);
1331 } /* isduplicate */
1334 * Extract a comment line and add to the linked list containing
1335 * comment block.
1336 * Each comment line is stored in the node.
1338 static void
1339 add_line_to_comment(void)
1341 struct strlist_st *tmp_str;
1343 tmp_str = new_strlist();
1344 tmp_str->str = strdup(curr_line);
1345 tmp_str->next = NULL;
1347 if (commhead == NULL) {
1348 /* Empty comment list */
1349 commhead = tmp_str;
1350 commtail = tmp_str;
1351 } else {
1352 /* append it to the list */
1353 commtail->next = tmp_str;
1354 commtail = commtail->next;
1357 is_last_comment_line = FALSE;
1358 } /* add_line_to_comment */
1361 * Add a double quoted string to the linked list containing ANSI string.
1363 static void
1364 add_qstring_to_str(void)
1366 struct strlist_st *tmp_str;
1368 tmp_str = new_strlist();
1369 tmp_str->str = strdup(qstring_buf);
1370 tmp_str->next = NULL;
1372 if (strhead == NULL) {
1373 /* Null ANSI string */
1374 strhead = tmp_str;
1375 strtail = tmp_str;
1376 } else {
1377 /* Append it to the ANSI string linked list */
1378 strtail->next = tmp_str;
1379 strtail = strtail->next;
1382 qstring_buf[0] = '\0';
1383 } /* add_qstring_to_str */
1386 * Finds the head of domain nodes given domain name.
1388 static struct domain_st *
1389 find_domain_node(char *dname)
1391 struct domain_st *tmp_dom, *p;
1394 * If -a option is specified everything will be written to the
1395 * default domain file.
1397 if (aflg == TRUE) {
1398 if (def_dom == NULL) {
1399 def_dom = new_domain();
1401 return (def_dom);
1404 if ((dname == NULL) ||
1405 (dname[0] == '\0') ||
1406 (strcmp(dname, default_domain) == 0)) {
1407 if (def_dom == NULL) {
1408 def_dom = new_domain();
1410 if (strcmp(dname, default_domain) == 0) {
1411 (void) fprintf(stderr,
1412 "%s \"%s\" is used in dgettext of file:%s"
1413 " line:%d.\n",
1414 "Warning: default domain name",
1415 default_domain, curr_file, curr_linenum);
1417 return (def_dom);
1418 } else {
1419 p = dom_head;
1420 while (p != NULL) {
1421 if (strcmp(p->dname, dname) == 0) {
1422 return (p);
1424 p = p->next;
1427 tmp_dom = new_domain();
1428 tmp_dom->dname = strdup(dname);
1430 if (dom_head == NULL) {
1431 dom_head = tmp_dom;
1432 dom_tail = tmp_dom;
1433 } else {
1434 dom_tail->next = tmp_dom;
1435 dom_tail = dom_tail->next;
1437 return (tmp_dom);
1439 } /* find_domain_node */
1442 * Frees the ANSI string linked list.
1444 static void
1445 free_strlist(struct strlist_st *ptr)
1447 struct strlist_st *p;
1449 p = ptr;
1450 ptr = NULL;
1451 while (p != NULL) {
1452 ptr = p->next;
1453 free(p->str);
1454 free(p);
1455 p = ptr;
1457 } /* free_strlist */
1460 * Finds if a ANSI string is contained in the exclude file.
1462 static int
1463 isexcluded(struct strlist_st *strlist)
1465 struct exclude_st *p;
1467 p = excl_head;
1468 while (p != NULL) {
1469 if (msgidcmp(p->exstr, strlist) == 0) {
1470 return (TRUE);
1472 p = p->next;
1474 return (FALSE);
1475 } /* isexcluded */
1478 * Finds if a comment block is to be extracted.
1480 * When -c option is specified, find out if comment block contains
1481 * comment-tag as a token separated by blanks. If it does, this
1482 * comment block is associated with the next msgid encountered.
1483 * Comment block is a linked list where each node contains one line
1484 * of comments.
1486 static int
1487 isextracted(struct strlist_st *strlist)
1489 struct strlist_st *p;
1490 char *first, *pc;
1493 p = strlist;
1494 while (p != NULL) {
1495 first = strdup(p->str);
1496 while ((first != NULL) && (first[0] != '\0')) {
1497 pc = first;
1499 /*CONSTCOND*/
1500 while (1) {
1501 if (*pc == '\0') {
1502 break;
1503 } else if ((*pc == ' ') || (*pc == '\t')) {
1504 *pc++ = '\0';
1505 break;
1507 pc++;
1509 if (strcmp(first, comment_tag) == 0) {
1510 return (TRUE);
1512 first = pc;
1514 p = p->next;
1515 } /* while */
1518 * Not found.
1520 return (FALSE);
1521 } /* isextracted */
1524 * Adds ANSI string to the domain element list.
1526 static void
1527 add_str_to_element_list(int istextdomain, char *domain_list)
1529 struct element_st *tmp_elem;
1530 struct element_st *p, *q;
1531 struct domain_st *tmp_dom;
1532 int result;
1535 * This can happen if something like gettext(USAGE) is used
1536 * and it is impossible to get msgid for this gettext.
1537 * Since -x option should be used in this kind of cases,
1538 * it is OK not to catch msgid.
1540 if (strhead == NULL) {
1541 return;
1545 * The global variable curr_domain contains either NULL
1546 * for default_domain or domain name for dgettext().
1548 tmp_dom = find_domain_node(domain_list);
1551 * If this msgid is in the exclude file,
1552 * then free the linked list and return.
1554 if ((istextdomain == FALSE) &&
1555 (isexcluded(strhead) == TRUE)) {
1556 free_strlist(strhead);
1557 strhead = strtail = NULL;
1558 return;
1561 tmp_elem = new_element();
1562 tmp_elem->msgid = strhead;
1563 tmp_elem->istextdomain = istextdomain;
1565 * If -c option is specified and TAG matches,
1566 * then associate the comment to the next [d]gettext() calls
1567 * encountered in the source code.
1568 * textdomain() calls will not have any effect.
1570 if (istextdomain == FALSE) {
1571 if ((cflg == TRUE) && (commhead != NULL)) {
1572 if (isextracted(commhead) == TRUE) {
1573 tmp_elem->comment = commhead;
1574 } else {
1575 free_strlist(commhead);
1577 commhead = commtail = NULL;
1581 tmp_elem->linenum = linenum_saved;
1582 tmp_elem->fname = strdup(curr_file);
1585 if (sflg == TRUE) {
1587 * If this is textdomain() call and -s option is specified,
1588 * append this node to the textdomain linked list.
1590 if (istextdomain == TRUE) {
1591 if (tmp_dom->textdomain_head == NULL) {
1592 tmp_dom->textdomain_head = tmp_elem;
1593 tmp_dom->textdomain_tail = tmp_elem;
1594 } else {
1595 tmp_dom->textdomain_tail->next = tmp_elem;
1596 tmp_dom->textdomain_tail = tmp_elem;
1598 strhead = strtail = NULL;
1599 return;
1603 * Insert the node to the properly sorted position.
1605 q = NULL;
1606 p = tmp_dom->gettext_head;
1607 while (p != NULL) {
1608 result = msgidcmp(strhead, p->msgid);
1609 if (result == 0) {
1611 * Duplicate id. Do not store.
1613 free_strlist(strhead);
1614 strhead = strtail = NULL;
1615 return;
1616 } else if (result > 0) {
1617 /* move to the next node */
1618 q = p;
1619 p = p->next;
1620 } else {
1621 tmp_elem->next = p;
1622 if (q != NULL) {
1623 q->next = tmp_elem;
1624 } else {
1625 tmp_dom->gettext_head = tmp_elem;
1627 strhead = strtail = NULL;
1628 return;
1630 } /* while */
1633 * New msgid is the largest or empty list.
1635 if (q != NULL) {
1636 /* largest case */
1637 q->next = tmp_elem;
1638 } else {
1639 /* empty list */
1640 tmp_dom->gettext_head = tmp_elem;
1642 } else {
1644 * Check if this msgid is already in the same domain.
1646 if (tmp_dom != NULL) {
1647 if (isduplicate(tmp_dom->gettext_head,
1648 tmp_elem->msgid) == TRUE) {
1649 tmp_elem->isduplicate = TRUE;
1653 * If -s option is not specified, then everything
1654 * is stored in gettext linked list.
1656 if (tmp_dom->gettext_head == NULL) {
1657 tmp_dom->gettext_head = tmp_elem;
1658 tmp_dom->gettext_tail = tmp_elem;
1659 } else {
1660 tmp_dom->gettext_tail->next = tmp_elem;
1661 tmp_dom->gettext_tail = tmp_elem;
1665 strhead = strtail = NULL;
1666 } /* add_str_to_element_list */
1669 * Write all domain linked list to the files.
1671 static void
1672 write_all_files(void)
1674 struct domain_st *tmp;
1677 * Write out default domain file.
1679 write_one_file(def_dom);
1682 * If dgettext() exists and -a option is not used,
1683 * then there are non-empty linked list.
1685 tmp = dom_head;
1686 while (tmp != NULL) {
1687 write_one_file(tmp);
1688 tmp = tmp->next;
1690 } /* write_all_files */
1693 * add an element_st list to the linked list.
1695 static void
1696 add_node_to_polist(struct element_st **pohead,
1697 struct element_st **potail, struct element_st *elem)
1699 if (elem == NULL) {
1700 return;
1703 if (*pohead == NULL) {
1704 *pohead = *potail = elem;
1705 } else {
1706 (*potail)->next = elem;
1707 *potail = (*potail)->next;
1709 } /* add_node_to_polist */
1711 #define INIT_STATE 0
1712 #define IN_MSGID 1
1713 #define IN_MSGSTR 2
1714 #define IN_COMMENT 3
1716 * Reads existing po file into the linked list and returns the head
1717 * of the linked list.
1719 static struct element_st *
1720 read_po(char *fname)
1722 struct element_st *tmp_elem = NULL;
1723 struct element_st *ehead = NULL, *etail = NULL;
1724 struct strlist_st *comment_tail = NULL;
1725 struct strlist_st *msgid_tail = NULL;
1726 struct strlist_st *msgstr_tail = NULL;
1727 int state = INIT_STATE;
1728 char line [MAX_STRING_LEN];
1729 FILE *fp;
1731 if ((fp = fopen(fname, "r")) == NULL) {
1732 return (NULL);
1735 while (fgets(line, MAX_STRING_LEN, fp) != NULL) {
1737 * Line starting with # is a comment line and ignored.
1738 * Blank line is ignored, too.
1740 if (line[0] == '\n') {
1741 continue;
1742 } else if (line[0] == '#') {
1744 * If tmp_elem is not NULL, there is msgid pair
1745 * stored. Therefore, add it.
1747 if ((tmp_elem != NULL) && (state == IN_MSGSTR)) {
1748 add_node_to_polist(&ehead, &etail, tmp_elem);
1751 if ((state == INIT_STATE) || (state == IN_MSGSTR)) {
1752 state = IN_COMMENT;
1753 tmp_elem = new_element();
1754 tmp_elem->comment = comment_tail =
1755 new_strlist();
1757 * remove new line and skip "# "
1758 * in the beginning of the existing
1759 * comment line.
1761 line[strlen(line)-1] = 0;
1762 comment_tail->str = strdup(line+2);
1763 } else if (state == IN_COMMENT) {
1764 comment_tail->next = new_strlist();
1765 comment_tail = comment_tail->next;
1767 * remove new line and skip "# "
1768 * in the beginning of the existing
1769 * comment line.
1771 line[strlen(line)-1] = 0;
1772 comment_tail->str = strdup(line+2);
1775 } else if (strncmp(line, "domain", 6) == 0) {
1776 /* ignore domain line */
1777 continue;
1778 } else if (strncmp(line, "msgid", 5) == 0) {
1779 if (state == IN_MSGSTR) {
1780 add_node_to_polist(&ehead, &etail, tmp_elem);
1781 tmp_elem = new_element();
1782 } else if (state == INIT_STATE) {
1783 tmp_elem = new_element();
1786 state = IN_MSGID;
1787 trim_line(line);
1788 tmp_elem->msgid = msgid_tail = new_strlist();
1789 msgid_tail->str = strdup(line);
1791 } else if (strncmp(line, "msgstr", 6) == 0) {
1792 state = IN_MSGSTR;
1793 trim_line(line);
1794 tmp_elem->msgstr = msgstr_tail = new_strlist();
1795 msgstr_tail->str = strdup(line);
1796 } else {
1798 * If more than one line of string forms msgid,
1799 * append it to the string linked list.
1801 if (state == IN_MSGID) {
1802 trim_line(line);
1803 msgid_tail->next = new_strlist();
1804 msgid_tail = msgid_tail->next;
1805 msgid_tail->str = strdup(line);
1806 } else if (state == IN_MSGSTR) {
1807 trim_line(line);
1808 msgstr_tail->next = new_strlist();
1809 msgstr_tail = msgstr_tail->next;
1810 msgstr_tail->str = strdup(line);
1813 } /* while */
1816 * To insert the last msgid pair.
1818 if (tmp_elem != NULL) {
1819 add_node_to_polist(&ehead, &etail, tmp_elem);
1822 #ifdef DEBUG
1824 struct domain_st *tmp_domain = new_domain();
1825 char tmpstr[256];
1827 sprintf(tmpstr, "existing_po file : <%s>", fname);
1828 tmp_domain->dname = strdup(tmpstr);
1829 tmp_domain->gettext_head = ehead;
1830 printf("======= existing po file <%s> ========\n", fname);
1831 print_one_domain(tmp_domain);
1833 #endif /* DEBUG */
1835 (void) fclose(fp);
1836 return (ehead);
1837 } /* read_po */
1840 * This function will append the second list to the first list.
1841 * If the msgid in the second list contains msgid in the first list,
1842 * it will be marked as duplicate.
1844 static struct element_st *
1845 append_list(struct element_st *l1, struct element_st *l2)
1847 struct element_st *p = NULL, *q = NULL, *l1_tail = NULL;
1849 if (l1 == NULL)
1850 return (l2);
1851 if (l2 == NULL)
1852 return (l1);
1855 * in this while loop, just mark isduplicate field of node in the
1856 * l2 list if the same msgid exists in l1 list.
1858 p = l2;
1859 while (p != NULL) {
1860 q = l1;
1861 while (q != NULL) {
1862 if (msgidcmp(p->msgid, q->msgid) == 0) {
1863 p->isduplicate = TRUE;
1864 break;
1866 q = q->next;
1868 p = p->next;
1871 /* Now connect two linked lists. */
1872 l1_tail = l1;
1873 while (l1_tail->next != NULL) {
1874 if (l1->next == NULL)
1875 break;
1876 l1_tail = l1_tail-> next;
1878 l1_tail->next = l2;
1880 return (l1);
1881 } /* append_list */
1884 * Writes one domain list to the file.
1886 static void
1887 write_one_file(struct domain_st *head)
1889 FILE *fp;
1890 char fname [MAX_PATH_LEN];
1891 char dname [MAX_DOMAIN_LEN];
1892 struct element_st *p;
1893 struct element_st *existing_po_list;
1896 * If head is NULL, then it still has to create .po file
1897 * so that it will guarantee that the previous .po file was
1898 * alwasys deleted.
1899 * This is why checking NULL pointer has been moved to after
1900 * creating .po file.
1904 * If domain name is NULL, it is the default domain list.
1905 * The domain name is either "messages" or specified by option -d.
1906 * The default domain name is contained in default_domain variable.
1908 dname[0] = '\0';
1909 if ((head != NULL) &&
1910 (head->dname != NULL)) {
1911 (void) strcpy(dname, head->dname);
1912 } else {
1913 (void) strcpy(dname, default_domain);
1917 * path is the current directory if not specified by option -p.
1919 fname[0] = 0;
1920 if (pflg == TRUE) {
1921 (void) strcat(fname, pathname);
1922 (void) strcat(fname, "/");
1924 (void) strcat(fname, dname);
1925 (void) strcat(fname, ".po");
1928 * If -j flag is specified, read exsiting .po file and
1929 * append the current list to the end of the list read from
1930 * the existing .po file.
1932 if (jflg == TRUE) {
1934 * If head is NULL, we don't have to change existing file.
1935 * Therefore, just return it.
1937 if (head == NULL) {
1938 return;
1940 existing_po_list = read_po(fname);
1941 head->gettext_head = append_list(existing_po_list,
1942 head->gettext_head);
1943 #ifdef DEBUG
1944 if (head->dname != NULL) {
1945 printf("===after merge (-j option): <%s>===\n",
1946 head->dname);
1947 } else {
1948 printf("===after merge (-j option): <NULL>===\n");
1950 print_one_domain(head);
1951 #endif
1953 } /* if jflg */
1955 if ((fp = fopen(fname, "w")) == NULL) {
1956 (void) fprintf(stderr,
1957 "ERROR, can't open output file: %s\n", fname);
1958 exit(2);
1961 (void) fprintf(fp, "domain \"%s\"\n", dname);
1963 /* See comments above in the beginning of this function */
1964 if (head == NULL)
1965 return;
1968 * There are separate storage for textdomain() calls if
1969 * -s option is used (textdomain_head linked list).
1970 * Otherwise, textdomain() is mixed with gettext(0 and dgettext().
1971 * If mixed, the boolean varaible istextdomain is used to see
1972 * if the current node contains textdomain() or [d]gettext().
1974 if (sflg == TRUE) {
1975 p = head->textdomain_head;
1976 while (p != NULL) {
1978 * textdomain output line already contains
1979 * FIle name and line number information.
1980 * Therefore, does not have to check for nflg.
1982 output_textdomain(fp, p);
1983 p = p->next;
1987 p = head->gettext_head;
1988 while (p != NULL) {
1991 * Comment is printed only if -c is used and
1992 * associated with gettext or dgettext.
1993 * textdomain is not associated with comments.
1994 * Changes:
1995 * comments should be extracted in case of -j option
1996 * because there are read from exising file.
1998 if (((cflg == TRUE) || (jflg == TRUE)) &&
1999 (p->istextdomain != TRUE)) {
2000 output_comment(fp, p->comment);
2004 * If -n is used, then file number and line number
2005 * information is printed.
2006 * In case of textdomain(), this information is redundant
2007 * and is not printed.
2008 * If linenum is 0, it means this information has been
2009 * read from existing po file and it already contains
2010 * file and line number info as a comment line. So, it
2011 * should not printed in such case.
2013 if ((nflg == TRUE) && (p->istextdomain == FALSE) &&
2014 (p->linenum > 0)) {
2015 (void) fprintf(fp, "# File:%s, line:%d\n",
2016 p->fname, p->linenum);
2020 * Depending on the type of node, output textdomain comment
2021 * or msgid.
2023 if ((sflg == FALSE) &&
2024 (p->istextdomain == TRUE)) {
2025 output_textdomain(fp, p);
2026 } else {
2027 output_msgid(fp, p->msgid, p->isduplicate);
2029 p = p->next;
2031 } /* while */
2033 (void) fclose(fp);
2034 } /* write_one_file */
2037 * Prints out textdomain call as a comment line with file name and
2038 * the line number information.
2040 static void
2041 output_textdomain(FILE *fp, struct element_st *p)
2044 if (p == NULL)
2045 return;
2048 * Write textdomain() line as a comment.
2050 (void) fprintf(fp, "# File:%s, line:%d, textdomain(\"%s\");\n",
2051 p->fname, p->linenum, p->msgid->str);
2052 } /* output_textdomain */
2055 * Prints out comments from linked list.
2057 static void
2058 output_comment(FILE *fp, struct strlist_st *p)
2060 if (p == NULL)
2061 return;
2064 * Write comment section.
2066 while (p != NULL) {
2067 (void) fprintf(fp, "# %s\n", p->str);
2068 p = p->next;
2070 } /* output_comment */
2073 * Prints out msgid along with msgstr.
2075 static void
2076 output_msgid(FILE *fp, struct strlist_st *p, int duplicate)
2078 struct strlist_st *q;
2080 if (p == NULL)
2081 return;
2084 * Write msgid section.
2085 * If duplciate flag is ON, prepend "# " in front of every line
2086 * so that they are considered as comment lines in .po file.
2088 if (duplicate == TRUE) {
2089 (void) fprintf(fp, "# ");
2091 (void) fprintf(fp, "msgid \"%s\"\n", p->str);
2092 q = p->next;
2093 while (q != NULL) {
2094 if (duplicate == TRUE) {
2095 (void) fprintf(fp, "# ");
2097 (void) fprintf(fp, " \"%s\"\n", q->str);
2098 q = q->next;
2102 * Write msgstr section.
2103 * if -M option is specified, append <suffix> to msgid.
2104 * if -m option is specified, prepend <prefix> to msgid.
2106 if (duplicate == TRUE) {
2107 (void) fprintf(fp, "# ");
2109 if ((mflg == TRUE) || (Mflg == TRUE)) {
2110 if (mflg == TRUE) {
2112 * If single line msgid, add suffix to the same line
2114 if ((Mflg == TRUE) && (p->next == NULL)) {
2115 /* -M and -m and single line case */
2116 (void) fprintf(fp,
2117 "msgstr \"%s%s%s\"\n",
2118 prefix, p->str, suffix);
2119 } else {
2120 /* -M and -m and multi line case */
2121 (void) fprintf(fp,
2122 "msgstr \"%s%s\"\n",
2123 prefix, p->str);
2125 } else {
2126 if ((Mflg == TRUE) && (p->next == NULL)) {
2127 /* -M only with single line case */
2128 (void) fprintf(fp, "msgstr \"%s%s\"\n",
2129 p->str, suffix);
2130 } else {
2131 /* -M only with multi line case */
2132 (void) fprintf(fp, "msgstr \"%s\"\n", p->str);
2135 q = p->next;
2136 while (q != NULL) {
2137 if (duplicate == TRUE) {
2138 (void) fprintf(fp, "# ");
2140 (void) fprintf(fp, " \"%s\"\n", q->str);
2141 q = q->next;
2144 * If multi line msgid, add suffix after the last line.
2146 if ((Mflg == TRUE) && (p->next != NULL) &&
2147 (suffix[0] != '\0')) {
2148 (void) fprintf(fp, " \"%s\"\n", suffix);
2150 } else {
2151 (void) fprintf(fp, "msgstr\n");
2153 } /* output_msgid */
2156 * Malloc a new element node and initialize fields.
2158 static struct element_st *
2159 new_element(void)
2161 struct element_st *tmp;
2163 tmp = (struct element_st *)malloc(sizeof (struct element_st));
2164 tmp->istextdomain = FALSE;
2165 tmp->isduplicate = FALSE;
2166 tmp->msgid = NULL;
2167 tmp->msgstr = NULL;
2168 tmp->comment = NULL;
2169 tmp->fname = NULL;
2170 tmp->linenum = 0;
2171 tmp->next = NULL;
2173 return (tmp);
2174 } /* new_element */
2177 * Malloc a new domain node and initialize fields.
2179 static struct domain_st *
2180 new_domain(void)
2182 struct domain_st *tmp;
2184 tmp = (struct domain_st *)malloc(sizeof (struct domain_st));
2185 tmp->dname = NULL;
2186 tmp->gettext_head = NULL;
2187 tmp->gettext_tail = NULL;
2188 tmp->textdomain_head = NULL;
2189 tmp->textdomain_tail = NULL;
2190 tmp->next = NULL;
2192 return (tmp);
2193 } /* new_domain */
2196 * Malloc a new string list node and initialize fields.
2198 static struct strlist_st *
2199 new_strlist(void)
2201 struct strlist_st *tmp;
2203 tmp = (struct strlist_st *)malloc(sizeof (struct strlist_st));
2204 tmp->str = NULL;
2205 tmp->next = NULL;
2207 return (tmp);
2208 } /* new_strlist */
2211 * Malloc a new exclude string list node and initialize fields.
2213 static struct exclude_st *
2214 new_exclude(void)
2216 struct exclude_st *tmp;
2218 tmp = (struct exclude_st *)malloc(sizeof (struct exclude_st));
2219 tmp->exstr = NULL;
2220 tmp->next = NULL;
2222 return (tmp);
2223 } /* new_exclude */
2226 * Local version of strcat to keep within maximum string size.
2228 static void
2229 lstrcat(char *s1, const char *s2)
2231 char *es1 = &s1[MAX_STRING_LEN];
2232 char *ss1 = s1;
2234 while (*s1++)
2236 --s1;
2237 while (*s1++ = *s2++)
2238 if (s1 >= es1) {
2239 s1[-1] = '\0';
2240 if ((in_comment == TRUE || in_quote == TRUE) &&
2241 (warn_linenum != curr_linenum)) {
2242 if (stdin_only == FALSE) {
2243 (void) fprintf(stderr,
2244 "WARNING: file %s line %d exceeds "\
2245 "%d characters: \"%15.15s\"\n",
2246 curr_file, curr_linenum,
2247 MAX_STRING_LEN, ss1);
2248 } else {
2249 (void) fprintf(stderr,
2250 "WARNING: line %d exceeds "\
2251 "%d characters: \"%15.15s\"\n",
2252 curr_linenum, MAX_STRING_LEN, ss1);
2254 warn_linenum = curr_linenum;
2256 break;
2258 } /* lstrcat */
2260 #ifdef DEBUG
2262 * Debug print routine. Compiled only with DEBUG on.
2264 void
2265 print_element_list(struct element_st *q)
2267 struct strlist_st *r;
2269 while (q != NULL) {
2270 printf(" istextdomain = %d\n", q->istextdomain);
2271 printf(" isduplicate = %d\n", q->isduplicate);
2272 if ((q->msgid != NULL) && (q->msgid->str != NULL)) {
2273 printf(" msgid = <%s>\n", q->msgid->str);
2274 r = q->msgid->next;
2275 while (r != NULL) {
2276 printf(" <%s>\n", r->str);
2277 r = r->next;
2279 } else {
2280 printf(" msgid = <NULL>\n");
2282 if ((q->msgstr != NULL) && (q->msgstr->str != NULL)) {
2283 printf(" msgstr= <%s>\n", q->msgstr->str);
2284 r = q->msgstr->next;
2285 while (r != NULL) {
2286 printf(" <%s>\n", r->str);
2287 r = r->next;
2289 } else {
2290 printf(" msgstr= <NULL>\n");
2293 if (q->comment == NULL) {
2294 printf(" comment = <NULL>\n");
2295 } else {
2296 printf(" comment = <%s>\n", q->comment->str);
2297 r = q->comment->next;
2298 while (r != NULL) {
2299 printf(" <%s>\n", r->str);
2300 r = r->next;
2304 if (q->fname == NULL) {
2305 printf(" fname = <NULL>\n");
2306 } else {
2307 printf(" fname = <%s>\n", q->fname);
2309 printf(" linenum = %d\n", q->linenum);
2310 printf("\n");
2311 q = q->next;
2316 * Debug print routine. Compiled only with DEBUG on.
2318 void
2319 print_one_domain(struct domain_st *p)
2321 struct element_st *q;
2323 if (p == NULL) {
2324 printf("domain pointer = <NULL>\n");
2325 return;
2326 } else if (p->dname == NULL) {
2327 printf("domain_name = <%s>\n", "<NULL>");
2328 } else {
2329 printf("domain_name = <%s>\n", p->dname);
2331 q = p->gettext_head;
2332 print_element_list(q);
2334 q = p->textdomain_head;
2335 print_element_list(q);
2336 } /* print_one_domain */
2338 void
2339 print_all_domain(struct domain_st *dom_list)
2341 struct domain_st *p;
2342 struct element_st *q;
2344 p = dom_list;
2345 while (p != NULL) {
2346 print_one_domain(p);
2347 p = p->next;
2348 } /* while */
2349 } /* print_all_domain */
2350 #endif