OpenSolaris_b135/cmd/msgfmt/xgettext.c

Compare this file to the similar file:
Show the results in this format:

/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License, Version 1.0 only
 * (the "License").  You may not use this file except in compliance
 * with the License.
 *
 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
 * or http://www.opensolaris.org/os/licensing.
 * See the License for the specific language governing permissions
 * and limitations under the License.
 *
 * When distributing Covered Code, include this CDDL HEADER in each
 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
 * If applicable, add the following below this CDDL HEADER, with the
 * fields enclosed by brackets "[]" replaced with your own identifying
 * information: Portions Copyright [yyyy] [name of copyright owner]
 *
 * CDDL HEADER END
 */
/*
 * Copyright 1991, 1999, 2001-2002 Sun Microsystems, Inc.
 * All rights reserved.
 * Use is subject to license terms.
 */
#pragma ident	"%Z%%M%	%I%	%E% SMI"

#include	<ctype.h>
#include	<stdio.h>
#include	<stdlib.h>
#include	<string.h>

#define	TRUE	1
#define	FALSE	0
#define	MAX_PATH_LEN	1024
#define	MAX_DOMAIN_LEN	1024
#define	MAX_STRING_LEN  2048

#define	USAGE	"Usage:	xgettext [-a [-x exclude-file]] [-jns]\
[-c comment-tag]\n	[-d default-domain] [-m prefix] \
[-M suffix] [-p pathname] files ...\n\
	xgettext -h\n"

#define	DEFAULT_DOMAIN	"messages"

extern char	yytext[];
extern int	yylex(void);

/*
 * Contains a list of strings to be used to store ANSI-C style string.
 * Each quoted string is stored in one node.
 */
struct strlist_st {
	char			*str;
	struct strlist_st	*next;
};

/*
 * istextdomain	: Boolean telling if this node contains textdomain call.
 * isduplicate 	: Boolean telling if this node duplicate of any other msgid.
 * msgid	: contains msgid or textdomain if istextdomain is true.
 * msgstr	: contains msgstr.
 * comment	: comment extracted in case of -c option.
 * fname	: tells which file contains msgid.
 * linenum	: line number in the file.
 * next   	: Next node.
 */
struct element_st {
	char			istextdomain;
	char			isduplicate;
	struct strlist_st	*msgid;
	struct strlist_st	*msgstr;
	struct strlist_st	*comment;
	char			*fname;
	int			linenum;
	struct element_st	*next;
};

/*
 * dname	   : domain name. NULL if default domain.
 * gettext_head    : Head of linked list containing [d]gettext().
 * gettext_tail    : Tail of linked list containing [d]gettext().
 * textdomain_head : Head of linked list containing textdomain().
 * textdomain_tail : Tail of linked list containing textdomain().
 * next		   : Next node.
 *
 * Each domain contains two linked list.
 *	(gettext_head,  textdomain_head)
 * If -s option is used, then textdomain_head contains all
 * textdomain() calls and no textdomain() calls are stored in gettext_head.
 * If -s option is not used, textdomain_head is empty list and
 * gettext_head contains all gettext() dgettext(), and textdomain() calls.
 */
struct domain_st {
	char			*dname;
	struct element_st	*gettext_head;
	struct element_st	*gettext_tail;
	struct element_st	*textdomain_head;
	struct element_st	*textdomain_tail;
	struct domain_st	*next;
};

/*
 * There are two domain linked lists.
 * def_dom contains default domain linked list and
 * dom_head contains all other deomain linked lists to be created by
 * dgettext() calls.
 */
static struct domain_st	*def_dom = NULL;
static struct domain_st	*dom_head = NULL;
static struct domain_st	*dom_tail = NULL;

/*
 * This linked list contains a list of strings to be excluded when
 * -x option is used.
 */
static struct exclude_st {
	struct strlist_st	*exstr;
	struct exclude_st	*next;
} *excl_head;

/*
 * All option flags and values for each option if any.
 */
static int	aflg = FALSE;
static int	cflg = FALSE;
static char	*comment_tag = NULL;
static char	*default_domain = NULL;
static int	hflg = FALSE;
static int	jflg = FALSE;
static int	mflg = FALSE;
static int	Mflg = FALSE;
static char	*suffix = NULL;
static char	*prefix = NULL;
static int	nflg = FALSE;
static int	pflg = FALSE;
static char	*pathname = NULL;
static int	sflg = FALSE;
static int	tflg = FALSE;	/* Undocumented option to extract dcgettext */
static int	xflg = FALSE;
static char	*exclude_file = NULL;

/*
 * Each variable shows the current state of parsing input file.
 *
 * in_comment    : Means inside comment block (C or C++).
 * in_cplus_comment    : Means inside C++ comment block.
 * in_gettext    : Means inside gettext call.
 * in_dgettext   : Means inside dgettext call.
 * in_dcgettext  : Means inside dcgettext call.
 * in_textdomain : Means inside textdomain call.
 * in_str	 : Means currently processing ANSI style string.
 * in_quote	 : Means currently processing double quoted string.
 * in_skippable_string	: Means currently processing double quoted string,
 *                        that occurs outside a call to gettext, dgettext,
 *                        dcgettext, textdomain, with -a not specified.
 * is_last_comment_line : Means the current line is the last line
 *			  of the comment block. This is necessary because
 *			  in_comment becomes FALSE when '* /' is encountered.
 * is_first_comma_found : This is used only for dcgettext because dcgettext()
 *			  requires 2 commas. So need to do different action
 *			  depending on which commas encountered.
 * num_nested_open_paren : This keeps track of the number of open parens to
 *			   handle dcgettext ((const char *)0,"msg",LC_TIME);
 */
static int	in_comment		= FALSE;
static int	in_cplus_comment	= FALSE;
static int	in_gettext		= FALSE;
static int	in_dgettext		= FALSE;
static int	in_dcgettext		= FALSE;
static int	in_textdomain		= FALSE;
static int	in_str			= FALSE;
static int	in_quote		= FALSE;
static int	is_last_comment_line	= FALSE;
static int	is_first_comma_found	= FALSE;
static int	in_skippable_string	= FALSE;
static int	num_nested_open_paren	= 0;

/*
 * This variable contains the first line of gettext(), dgettext(), or
 * textdomain() calls.
 * This is necessary for multiple lines of a single call to store
 * the starting line.
 */
static int	linenum_saved = 0;

int	stdin_only = FALSE;	/* Read input from stdin */

/*
 * curr_file    : Contains current file name processed.
 * curr_domain  : Contains the current domain for each dgettext().
 *		  This is NULL for gettext().
 * curr_line    : Contains the current line processed.
 * qstring_buf  : Contains the double quoted string processed.
 * curr_linenum : Line number being processed in the current input file.
 * warn_linenum : Line number of current warning message.
 */
char	curr_file[MAX_PATH_LEN];
static char	curr_domain[MAX_DOMAIN_LEN];
static char	curr_line[MAX_STRING_LEN];
static char	qstring_buf[MAX_STRING_LEN];
int	curr_linenum = 1;
int	warn_linenum = 0;

/*
 * strhead  : This list contains ANSI style string.
 *		Each node contains double quoted string.
 * strtail  : This is the tail of strhead.
 * commhead : This list contains comments string.
 *		Each node contains one line of comment.
 * commtail : This is the tail of commhead.
 */
static struct strlist_st	*strhead = NULL;
static struct strlist_st	*strtail = NULL;
static struct strlist_st	*commhead = NULL;
static struct strlist_st	*commtail = NULL;

/*
 * gargc : Same as argc. Used to pass argc to lex routine.
 * gargv : Same as argv. Used to pass argc to lex routine.
 */
int	gargc;
char	**gargv;

static void add_line_to_comment(void);
static void add_qstring_to_str(void);
static void add_str_to_element_list(int, char *);
static void copy_strlist_to_str(char *, struct strlist_st *);
static void end_ansi_string(void);
static void free_strlist(struct strlist_st *);
void handle_newline(void);
static void initialize_globals(void);
static void output_comment(FILE *, struct strlist_st *);
static void output_msgid(FILE *, struct strlist_st *, int);
static void output_textdomain(FILE *, struct element_st *);
static void print_help(void);
static void read_exclude_file(void);
static void trim_line(char *);
static void write_all_files(void);
static void write_one_file(struct domain_st *);

static void lstrcat(char *, const char *);

/*
 * Utility functions to malloc a node and initialize fields.
 */
static struct domain_st  *new_domain(void);
static struct strlist_st *new_strlist(void);
static struct element_st *new_element(void);
static struct exclude_st *new_exclude(void);

/*
 * Main program of xgettext.
 */
int
main(int argc, char **argv)
{
	int		opterr = FALSE;
	int		c;

	initialize_globals();

	while ((c = getopt(argc, argv, "jhax:nsc:d:m:M:p:t")) != EOF) {
		switch (c) {
		case 'a':
			aflg = TRUE;
			break;
		case 'c':
			cflg = TRUE;
			comment_tag = optarg;
			break;
		case 'd':
			default_domain = optarg;
			break;
		case 'h':
			hflg = TRUE;
			break;
		case 'j':
			jflg = TRUE;
			break;
		case 'M':
			Mflg = TRUE;
			suffix = optarg;
			break;
		case 'm':
			mflg = TRUE;
			prefix = optarg;
			break;
		case 'n':
			nflg = TRUE;
			break;
		case 'p':
			pflg = TRUE;
			pathname = optarg;
			break;
		case 's':
			sflg = TRUE;
			break;
		case 't':
			tflg = TRUE;
			break;
		case 'x':
			xflg = TRUE;
			exclude_file = optarg;
			break;
		case '?':
			opterr = TRUE;
			break;
		}
	}

	/* if -h is used, ignore all other options. */
	if (hflg == TRUE) {
		(void) fprintf(stderr, USAGE);
		print_help();
		exit(0);
	}

	/* -x can be used only with -a */
	if ((xflg == TRUE) && (aflg == FALSE))
		opterr = TRUE;

	/* -j cannot be used with -a */
	if ((jflg == TRUE) && (aflg == TRUE)) {
		(void) fprintf(stderr,
		"-a and -j options cannot be used together.\n");
		opterr = TRUE;
	}

	/* -j cannot be used with -s */
	if ((jflg == TRUE) && (sflg == TRUE)) {
		(void) fprintf(stderr,
		"-j and -s options cannot be used together.\n");
		opterr = TRUE;
	}

	if (opterr == TRUE) {
		(void) fprintf(stderr, USAGE);
		exit(2);
	}

	/* error, if no files are specified. */
	if (optind == argc) {
		(void) fprintf(stderr, USAGE);
		exit(2);
	}

	if (xflg == TRUE) {
		read_exclude_file();
	}

	/* If files are -, then read from stdin */
	if (argv[optind][0] == '-') {
		stdin_only = TRUE;
		optind++;
	} else {
		stdin_only = FALSE;
	}

	/* Store argc and argv to pass to yylex() */
	gargc = argc;
	gargv = argv;

#ifdef DEBUG
	(void) printf("optind=%d\n", optind);
	{
	int i = optind;
	for (; i < argc; i++) {
		(void) printf("   %d, <%s>\n", i, argv[i]);
	}
	}
#endif

	if (stdin_only == FALSE) {
		if (freopen(argv[optind], "r", stdin) == NULL) {
			(void) fprintf(stderr,
			"ERROR, can't open input file: %s\n", argv[optind]);
			exit(2);
		}
		(void) strcpy(curr_file, gargv[optind]);
		optind++;
	}

	/*
	 * Process input.
	 */
	(void) yylex();

#ifdef DEBUG
	printf("\n======= default_domain ========\n");
	print_one_domain(def_dom);
	printf("======= domain list ========\n");
	print_all_domain(dom_head);
#endif

	/*
	 * Write out all .po files.
	 */
	write_all_files();

	return (0);
} /* main */

/*
 * Prints help information for each option.
 */
static void
print_help(void)
{
	(void) fprintf(stderr, "\n");
	(void) fprintf(stderr,
		"-a\t\t\tfind ALL strings\n");
	(void) fprintf(stderr,
		"-c <comment-tag>\tget comments containing <flag>\n");
	(void) fprintf(stderr,
	"-d <default-domain>\tuse <default-domain> for default domain\n");
	(void) fprintf(stderr,
		"-h\t\t\tHelp\n");
	(void) fprintf(stderr,
		"-j\t\t\tupdate existing file with the current result\n");
	(void) fprintf(stderr,
		"-M <suffix>\t\tfill in msgstr with msgid<suffix>\n");
	(void) fprintf(stderr,
		"-m <prefix>\t\tfill in msgstr with <prefix>msgid\n");
	(void) fprintf(stderr,
		"-n\t\t\tline# file name and line number info in output\n");
	(void) fprintf(stderr,
		"-p <pathname>\t\tuse <pathname> for output file directory\n");
	(void) fprintf(stderr,
		"-s\t\t\tgenerate sorted output files\n");
	(void) fprintf(stderr,
"-x <exclude-file>\texclude strings in file <exclude-file> from output\n");
	(void) fprintf(stderr,
		"-\t\t\tread stdin, use as a filter (input only)\n");
} /* print_help */

/*
 * Extract file name and line number information from macro line
 * and set the global variable accordingly.
 * The valid line format is
 *   1) # nnn
 *    or
 *   2) # nnn "xxxxx"
 *   where nnn is line number and xxxxx is file name.
 */
static void
extract_filename_linenumber(char *mline)
{
	int	num;
	char	*p, *q, *r;

	/*
	 * mline can contain multi newline.
	 * line number should be increased by the number of newlines.
	 */
	p = mline;
	while ((p = strchr(p, '\n')) != NULL) {
		p++;
		curr_linenum++;
	}
	p = strchr(mline, ' ');
	if (p == NULL)
		return;
	q = strchr(++p, ' ');
	if (q == NULL) {
		/* case 1 */
		if ((num = atoi(p)) > 0) {
			curr_linenum = num;
			return;
		}
	} else {
		/* case 2 */
		*q++ = 0;
		if (*q == '"') {
			q++;
			r = strchr(q, '"');
			if (r == NULL) {
				return;
			}
			*r = 0;
			if ((num = atoi(p)) > 0) {
				curr_linenum = num;
				(void) strcpy(curr_file, q);
			}
		}
	}
} /* extract_filename_linenumber */

/*
 * Handler for MACRO line which starts with #.
 */
void
handle_macro_line(void)
{
#ifdef DEBUG
	(void) printf("Macro line=<%s>\n", yytext);
#endif
	if (cflg == TRUE)
		lstrcat(curr_line, yytext);

	if (in_quote == TRUE) {
		lstrcat(qstring_buf, yytext);
	} else if (in_comment == FALSE) {
		extract_filename_linenumber(yytext);
	}

	curr_linenum--;
	handle_newline();
} /* handle_macro_line */

/*
 * Handler for C++ comments which starts with //.
 */
void
handle_cplus_comment_line(void)
{
	if (cflg == TRUE)
		lstrcat(curr_line, yytext);

	if (in_quote == TRUE) {
		lstrcat(qstring_buf, yytext);
	} else if ((in_comment == FALSE) &&
		    (in_skippable_string == FALSE)) {

		/*
		 * If already in c comments, don't do anything.
		 * Set both flags to TRUE here.
		 * Both flags will be set to FALSE when newline
		 * encounters.
		 */
		in_cplus_comment = TRUE;
		in_comment = TRUE;
	}
} /* handle_cplus_comment_line */

/*
 * Handler for the comment start (slash asterisk) in input file.
 */
void
handle_open_comment(void)
{
	if (cflg == TRUE)
		lstrcat(curr_line, yytext);

	if (in_quote == TRUE) {
		lstrcat(qstring_buf, yytext);
	} else if ((in_comment == FALSE) &&
		    (in_skippable_string == FALSE)) {

		in_comment = TRUE;
		is_last_comment_line = FALSE;
		/*
		 * If there is any comment extracted before accidently,
		 * clean it up and start the new comment again.
		 */
		free_strlist(commhead);
		commhead = commtail = NULL;
	}
}

/*
 * Handler for the comment end (asterisk slash) in input file.
 */
void
handle_close_comment(void)
{
	if (cflg == TRUE)
		lstrcat(curr_line, yytext);

	if (in_quote == TRUE) {
		lstrcat(qstring_buf, yytext);
	} else if (in_skippable_string == FALSE) {
		in_comment = FALSE;
		is_last_comment_line = TRUE;
	}
}

/*
 * Handler for "gettext" in input file.
 */
void
handle_gettext(void)
{
	/*
	 * If -t option is specified to extrct dcgettext,
	 * don't do anything for gettext().
	 */
	if (tflg == TRUE) {
		return;
	}

	num_nested_open_paren = 0;

	if (cflg == TRUE)
		lstrcat(curr_line, yytext);

	if (in_quote == TRUE) {
		lstrcat(qstring_buf, yytext);
	} else if (in_comment == FALSE) {
		in_gettext = TRUE;
		linenum_saved = curr_linenum;
		/*
		 * gettext will be put into default domain .po file
		 * curr_domain does not change for gettext.
		 */
		curr_domain[0] = NULL;
	}
} /* handle_gettext */

/*
 * Handler for "dgettext" in input file.
 */
void
handle_dgettext(void)
{
	/*
	 * If -t option is specified to extrct dcgettext,
	 * don't do anything for dgettext().
	 */
	if (tflg == TRUE) {
		return;
	}

	num_nested_open_paren = 0;

	if (cflg == TRUE)
		lstrcat(curr_line, yytext);

	if (in_quote == TRUE) {
		lstrcat(qstring_buf, yytext);
	} else if (in_comment == FALSE) {
		in_dgettext = TRUE;
		linenum_saved = curr_linenum;
		/*
		 * dgettext will be put into domain file specified.
		 * curr_domain will follow.
		 */
		curr_domain[0] = NULL;
	}
} /* handle_dgettext */

/*
 * Handler for "dcgettext" in input file.
 */
void
handle_dcgettext(void)
{
	/*
	 * dcgettext will be extracted only when -t flag is specified.
	 */
	if (tflg == FALSE) {
		return;
	}

	num_nested_open_paren = 0;

	is_first_comma_found = FALSE;

	if (cflg == TRUE)
		lstrcat(curr_line, yytext);

	if (in_quote == TRUE) {
		lstrcat(qstring_buf, yytext);
	} else if (in_comment == FALSE) {
		in_dcgettext = TRUE;
		linenum_saved = curr_linenum;
		/*
		 * dcgettext will be put into domain file specified.
		 * curr_domain will follow.
		 */
		curr_domain[0] = NULL;
	}
} /* handle_dcgettext */

/*
 * Handler for "textdomain" in input file.
 */
void
handle_textdomain(void)
{
	if (cflg == TRUE)
		lstrcat(curr_line, yytext);

	if (in_quote == TRUE) {
		lstrcat(qstring_buf, yytext);
	} else if (in_comment == FALSE) {
		in_textdomain = TRUE;
		linenum_saved = curr_linenum;
		curr_domain[0] = NULL;
	}
} /* handle_textdomain */

/*
 * Handler for '(' in input file.
 */
void
handle_open_paren(void)
{
	if (cflg == TRUE)
		lstrcat(curr_line, yytext);

	if (in_quote == TRUE) {
		lstrcat(qstring_buf, yytext);
	} else if (in_comment == FALSE) {
		if ((in_gettext == TRUE) ||
		    (in_dgettext == TRUE) ||
		    (in_dcgettext == TRUE) ||
		    (in_textdomain == TRUE)) {
			in_str = TRUE;
			num_nested_open_paren++;
		}
	}
} /* handle_open_paren */

/*
 * Handler for ')' in input file.
 */
void
handle_close_paren(void)
{
	if (cflg == TRUE)
		lstrcat(curr_line, yytext);

	if (in_quote == TRUE) {
		lstrcat(qstring_buf, yytext);
	} else if (in_comment == FALSE) {
		if ((in_gettext == TRUE) ||
		    (in_dgettext == TRUE) ||
		    (in_dcgettext == TRUE) ||
		    (in_textdomain == TRUE)) {
			/*
			 * If this is not the matching close paren with
			 * the first open paren, no action is necessary.
			 */
			if (--num_nested_open_paren > 0)
				return;
			add_str_to_element_list(in_textdomain, curr_domain);
			in_str = FALSE;
			in_gettext = FALSE;
			in_dgettext = FALSE;
			in_dcgettext = FALSE;
			in_textdomain = FALSE;
		} else if (aflg == TRUE) {
			end_ansi_string();
		}
	}
} /* handle_close_paren */

/*
 * Handler for '\\n' in input file.
 *
 * This is a '\' followed by new line.
 * This can be treated like a new line except when this is a continuation
 * of a ANSI-C string.
 * If this is a part of ANSI string, treat the current line as a double
 * quoted string and the next line is the start of the double quoted
 * string.
 */
void
handle_esc_newline(void)
{
	if (cflg == TRUE)
		lstrcat(curr_line, "\\");

	curr_linenum++;

	if (in_quote == TRUE) {
		add_qstring_to_str();
	} else if ((in_comment == TRUE) ||
	    (is_last_comment_line == TRUE)) {
		if (in_cplus_comment == FALSE) {
			add_line_to_comment();
		}
	}

	curr_line[0] = NULL;
} /* handle_esc_newline */

/*
 * Handler for '"' in input file.
 */
void
handle_quote(void)
{
	if (cflg == TRUE)
		lstrcat(curr_line, yytext);

	if (in_comment == TRUE) {
		/*EMPTY*/
	} else if ((in_gettext == TRUE) ||
			(in_dgettext == TRUE) ||
			(in_dcgettext == TRUE) ||
			(in_textdomain == TRUE)) {
		if (in_str == TRUE) {
			if (in_quote == FALSE) {
				in_quote = TRUE;
			} else {
				add_qstring_to_str();
				in_quote = FALSE;
			}
		}
	} else if (aflg == TRUE) {
		/*
		 * The quote is found outside of gettext, dgetext, and
		 * textdomain. Everytime a quoted string is found,
		 * add it to the string list.
		 * in_str stays TRUE until ANSI string ends.
		 */
		if (in_str == TRUE) {
			if (in_quote == TRUE) {
				in_quote = FALSE;
				add_qstring_to_str();
			} else {
				in_quote = TRUE;
			}
		} else {
			in_str = TRUE;
			in_quote = TRUE;
			linenum_saved = curr_linenum;
		}
	} else {
		in_skippable_string = (in_skippable_string == TRUE) ?
					FALSE : TRUE;
	}
} /* handle_quote */

/*
 * Handler for ' ' or TAB in input file.
 */
void
handle_spaces(void)
{
	if (cflg == TRUE)
		lstrcat(curr_line, yytext);

	if (in_quote == TRUE) {
		lstrcat(qstring_buf, yytext);
	}
} /* handle_spaces */

/*
 * Flattens a linked list containing ANSI string to the one string.
 */
static void
copy_strlist_to_str(char *str, struct strlist_st *strlist)
{
	struct strlist_st	*p;

	str[0] = NULL;

	if (strlist != NULL) {
		p = strlist;
		while (p != NULL) {
			if (p->str != NULL) {
				lstrcat(str, p->str);
			}
			p = p->next;
		}
	}
} /* copy_strlist_to_str */

/*
 * Handler for ',' in input file.
 */
void
handle_comma(void)
{
	if (cflg == TRUE)
		lstrcat(curr_line, yytext);

	if (in_quote == TRUE) {
		lstrcat(qstring_buf, yytext);
	} else if (in_comment == FALSE) {
		if (in_str == TRUE) {
			if (in_dgettext == TRUE) {
				copy_strlist_to_str(curr_domain, strhead);
				free_strlist(strhead);
				strhead = strtail = NULL;
			} else if (in_dcgettext == TRUE) {
				/*
				 * Ignore the second comma.
				 */
				if (is_first_comma_found == FALSE) {
					copy_strlist_to_str(curr_domain,
								strhead);
					free_strlist(strhead);
					strhead = strtail = NULL;
					is_first_comma_found = TRUE;
				}
			} else if (aflg == TRUE) {
				end_ansi_string();
			}
		}
	}
} /* handle_comma */

/*
 * Handler for any other character that does not have special handler.
 */
void
handle_character(void)
{
	if (cflg == TRUE)
		lstrcat(curr_line, yytext);

	if (in_quote == TRUE) {
		lstrcat(qstring_buf, yytext);
	} else if (in_comment == FALSE) {
		if (in_str == TRUE) {
			if (aflg == TRUE) {
				end_ansi_string();
			}
		}
	}
} /* handle_character */

/*
 * Handler for new line in input file.
 */
void
handle_newline(void)
{
	curr_linenum++;

	/*
	 * in_quote is always FALSE here for ANSI-C code.
	 */
	if ((in_comment == TRUE) ||
	    (is_last_comment_line == TRUE)) {
		if (in_cplus_comment == TRUE) {
			in_cplus_comment = FALSE;
			in_comment = FALSE;
		} else {
			add_line_to_comment();
		}
	}

	curr_line[0] = NULL;
	/*
	 * C++ comment always ends with new line.
	 */
} /* handle_newline */

/*
 * Process ANSI string.
 */
static void
end_ansi_string(void)
{
	if ((aflg == TRUE) &&
	    (in_str == TRUE) &&
	    (in_gettext == FALSE) &&
	    (in_dgettext == FALSE) &&
	    (in_dcgettext == FALSE) &&
	    (in_textdomain == FALSE)) {
		add_str_to_element_list(FALSE, curr_domain);
		in_str = FALSE;
	}
} /* end_ansi_string */

/*
 * Initialize global variables if necessary.
 */
static void
initialize_globals(void)
{
	default_domain = strdup(DEFAULT_DOMAIN);
	curr_domain[0] = NULL;
	curr_file[0] = NULL;
	qstring_buf[0] = NULL;
} /* initialize_globals() */

/*
 * Extract only string part when read a exclude file by removing
 * keywords (e.g. msgid, msgstr, # ) and heading and trailing blanks and
 * double quotes.
 */
static void
trim_line(char *line)
{
	int	i, p, len;
	int	first = 0;
	int	last = 0;
	char	c;

	len = strlen(line);

	/*
	 * Find the position of the last non-whitespace character.
	 */
	i = len - 1;
	/*CONSTCOND*/
	while (1) {
		c = line[i--];
		if ((c != ' ') && (c != '\n') && (c != '\t')) {
			last = ++i;
			break;
		}
	}

	/*
	 * Find the position of the first non-whitespace character
	 * by skipping "msgid" initially.
	 */
	if (strncmp("msgid ", line, 6) == 0) {
		i = 5;
	} else if (strncmp("msgstr ", line, 7) == 0) {
		i = 6;
	} else if (strncmp("# ", line, 2) == 0) {
		i = 2;
	} else {
		i = 0;
	}

	/*CONSTCOND*/
	while (1) {
		c = line[i++];
		if ((c != ' ') && (c != '\n') && (c != '\t')) {
			first = --i;
			break;
		}
	}

	/*
	 * For Backward compatibility, we consider both double quoted
	 * string and non-quoted string.
	 * The double quote is removed before being stored if exists.
	 */
	if (line[first] == '"') {
		first++;
	}
	if (line[last] == '"') {
		last--;
	}

	/*
	 * Now copy the valid part of the string.
	 */
	p = first;
	for (i = 0; i <= (last-first); i++) {
		line[i] = line[p++];
	}
	line [i] = NULL;
} /* trim_line */

/*
 * Read exclude file and stores it in the global linked list.
 */
static void
read_exclude_file(void)
{
	FILE	*fp;
	struct exclude_st	*tmp_excl;
	struct strlist_st	*tail;
	int			ignore_line;
	char			line [MAX_STRING_LEN];

	if ((fp = fopen(exclude_file, "r")) == NULL) {
		(void) fprintf(stderr, "ERROR, can't open exclude file: %s\n",
				exclude_file);
		exit(2);
	}

	ignore_line = TRUE;
	while (fgets(line, MAX_STRING_LEN, fp) != NULL) {
		/*
		 * Line starting with # is a comment line and ignored.
		 * Blank line is ignored, too.
		 */
		if ((line[0] == '\n') || (line[0] == '#')) {
			continue;
		} else if (strncmp(line, "msgstr", 6) == 0) {
			ignore_line = TRUE;
		} else if (strncmp(line, "domain", 6) == 0) {
			ignore_line = TRUE;
		} else if (strncmp(line, "msgid", 5) == 0) {
			ignore_line = FALSE;
			tmp_excl = new_exclude();
			tmp_excl->exstr = new_strlist();
			trim_line(line);
			tmp_excl->exstr->str = strdup(line);
			tail = tmp_excl->exstr;
			/*
			 * Prepend new exclude string node to the list.
			 */
			tmp_excl->next = excl_head;
			excl_head = tmp_excl;
		} else {
			/*
			 * If more than one line of string forms msgid,
			 * append it to the string linked list.
			 */
			if (ignore_line == FALSE) {
				trim_line(line);
				tail->next = new_strlist();
				tail->next->str = strdup(line);
				tail = tail->next;
			}
		}
	} /* while */

#ifdef DEBUG
	tmp_excl = excl_head;
	while (tmp_excl != NULL) {
		printf("============================\n");
		tail = tmp_excl->exstr;
		while (tail != NULL) {
			printf("%s###\n", tail->str);
			tail = tail->next;
		}
		tmp_excl = tmp_excl->next;
	}
#endif
} /* read_exclude_file */

/*
 * Get next character from the string list containing ANSI style string.
 * This function returns three valus. (p, *m, *c).
 * p is returned by return value and, *m and *c are returned by changing
 * values in the location pointed.
 *
 *  p : points node in the linked list for ANSI string.
 *	Each node contains double quoted string.
 *  m : The location of the next characters in the double quoted string
 *	as integer index in the string.
 *	When it gets to end of quoted string, the next node will be
 *	read and m starts as zero for every new node.
 *  c : Stores the value of the characterto be returned.
 */
static struct strlist_st *
get_next_ch(struct strlist_st *p, int *m, char *c)
{
	char	ch, oct, hex;
	int	value, i;

	/*
	 * From the string list, find non-null string first.
	 */

	/*CONSTCOND*/
	while (1) {
		if (p == NULL) {
			break;
		} else if (p->str == NULL)  {
			p = p->next;
		} else if (p->str[*m] == NULL) {
			p = p->next;
			*m = 0;
		} else {
			break;
		}
	}

	/*
	 * No more character is available.
	 */
	if (p == NULL) {
		*c = 0;
		return (NULL);
	}

	/*
	 * Check if the character back slash.
	 * If yes, ANSI defined escape sequence rule is used.
	 */
	if (p->str[*m] != '\\') {
		*c = p->str[*m];
		*m = *m + 1;
		return (p);
	} else {
		/*
		 * Get next character after '\'.
		 */
		*m = *m + 1;
		ch = p->str[*m];
		switch (ch) {
		case 'a':
			*c = '\a';
			break;
		case 'b':
			*c = '\b';
			break;
		case 'f':
			*c = '\f';
			break;
		case 'n':
			*c = '\n';
			break;
		case 'r':
			*c = '\r';
			break;
		case 't':
			*c = '\t';
			break;
		case 'v':
			*c = '\v';
			break;
		case '0':
		case '1':
		case '2':
		case '3':
		case '4':
		case '5':
		case '6':
		case '7':
			/*
			 * Get maximum of three octal digits.
			 */
			value = ch;
			for (i = 0; i < 2; i++) {
				*m = *m + 1;
				oct = p->str[*m];
				if ((oct >= '0') && (oct <= '7')) {
					value = value * 8 + (oct - '0');
				} else {
					*m = *m - 1;
					break;
				}
			}
			*c = value;
#ifdef DEBUG
			/* (void) fprintf(stderr, "octal=%d\n", value); */
#endif
			break;
		case 'x':
			value = 0;
			/*
			 * Remove all heading zeros first and
			 * get one or two valuid hexadecimal charaters.
			 */
			*m = *m + 1;
			while (p->str[*m] == '0') {
				*m = *m + 1;
			}
			value = 0;
			for (i = 0; i < 2; i++) {
				hex = p->str[*m];
				*m = *m + 1;
				if (isdigit(hex)) {
					value = value * 16 + (hex - '0');
				} else if (isxdigit(hex)) {
					hex = tolower(hex);
					value = value * 16 + (hex - 'a' + 10);
				} else {
					*m = *m - 1;
					break;
				}
			}
			*c = value;
#ifdef DEBUG
			(void) fprintf(stderr, "hex=%d\n", value);
#endif
			*m = *m - 1;
			break;
		default :
			/*
			 * Undefined by ANSI.
			 * Just ignore "\".
			 */
			*c = p->str[*m];
			break;
		}
		/*
		 * Advance pointer to point the next character to be parsed.
		 */
		*m = *m + 1;
		return (p);
	}
} /* get_next_ch */

/*
 * Compares two msgids.
 * Comparison is done by values, not by characters represented.
 * For example, '\t', '\011' and '0x9' are identical values.
 * Return values are same as in strcmp.
 *   1   if  msgid1 > msgid2
 *   0   if  msgid1 = msgid2
 *  -1   if  msgid1 < msgid2
 */
static int
msgidcmp(struct strlist_st *id1, struct strlist_st *id2)
{
	char	c1, c2;
	int	m1, m2;

	m1 = 0;
	m2 = 0;

	/*CONSTCOND*/
	while (1) {
		id1 = get_next_ch(id1, &m1, &c1);
		id2 = get_next_ch(id2, &m2, &c2);

		if ((c1 == 0) && (c2 == 0)) {
			return (0);
		}

		if (c1 > c2) {
			return (1);
		} else if (c1 < c2) {
			return (-1);
		}
	}
	/*NOTREACHED*/
} /* msgidcmp */

/*
 * Check if a ANSI string (which is a linked list itself) is a duplicate
 * of any string in the list of ANSI string.
 */
static int
isduplicate(struct element_st *list, struct strlist_st *str)
{
	struct element_st	*p;

	if (list == NULL) {
		return (FALSE);
	}

	p = list;
	while (p != NULL) {
		if (p->msgid != NULL) {
			if (msgidcmp(p->msgid, str) == 0) {
				return (TRUE);
			}
		}
		p = p->next;
	}

	return (FALSE);
} /* isduplicate */

/*
 * Extract a comment line and add to the linked list containing
 * comment block.
 * Each comment line is stored in the node.
 */
static void
add_line_to_comment(void)
{
	struct strlist_st	*tmp_str;

	tmp_str = new_strlist();
	tmp_str->str = strdup(curr_line);
	tmp_str->next = NULL;

	if (commhead == NULL) {
		/* Empty comment list */
		commhead = tmp_str;
		commtail = tmp_str;
	} else {
		/* append it to the list */
		commtail->next = tmp_str;
		commtail = commtail->next;
	}

	is_last_comment_line = FALSE;
} /* add_line_to_comment */

/*
 * Add a double quoted string to the linked list containing ANSI string.
 */
static void
add_qstring_to_str(void)
{
	struct strlist_st	*tmp_str;

	tmp_str = new_strlist();
	tmp_str->str = strdup(qstring_buf);
	tmp_str->next = NULL;

	if (strhead == NULL) {
		/* Null ANSI string */
		strhead = tmp_str;
		strtail = tmp_str;
	} else {
		/* Append it to the ANSI string linked list */
		strtail->next = tmp_str;
		strtail = strtail->next;
	}

	qstring_buf[0] = NULL;
} /* add_qstring_to_str */

/*
 * Finds the head of domain nodes given domain name.
 */
static struct domain_st *
find_domain_node(char *dname)
{
	struct domain_st	*tmp_dom, *p;

	/*
	 * If -a option is specified everything will be written to the
	 * default domain file.
	 */
	if (aflg == TRUE) {
		if (def_dom == NULL) {
			def_dom = new_domain();
		}
		return (def_dom);
	}

	if ((dname == NULL) ||
	    (dname[0] == NULL) ||
	    (strcmp(dname, default_domain) == 0)) {
		if (def_dom == NULL) {
			def_dom = new_domain();
		}
		if (strcmp(dname, default_domain) == 0) {
			(void) fprintf(stderr,
		"%s \"%s\" is used in dgettext of file:%s line:%d.\n",
				"Warning: default domain name",
				default_domain, curr_file, curr_linenum);
		}
		return (def_dom);
	} else {
		p = dom_head;
		while (p != NULL) {
			if (strcmp(p->dname, dname) == 0) {
				return (p);
			}
			p = p->next;
		}

		tmp_dom = new_domain();
		tmp_dom->dname = strdup(dname);

		if (dom_head == NULL) {
			dom_head = tmp_dom;
			dom_tail = tmp_dom;
		} else {
			dom_tail->next = tmp_dom;
			dom_tail = dom_tail->next;
		}
		return (tmp_dom);
	}
} /* find_domain_node */

/*
 * Frees the ANSI string linked list.
 */
static void
free_strlist(struct strlist_st *ptr)
{
	struct strlist_st	*p;

	p = ptr;
	ptr = NULL;
	while (p != NULL) {
		ptr = p->next;
		free(p->str);
		free(p);
		p = ptr;
	}
} /* free_strlist */

/*
 * Finds if a ANSI string is contained in the exclude file.
 */
static int
isexcluded(struct strlist_st *strlist)
{
	struct exclude_st	*p;

	p = excl_head;
	while (p != NULL) {
		if (msgidcmp(p->exstr, strlist) == 0) {
			return (TRUE);
		}
		p = p->next;
	}
	return (FALSE);
} /* isexcluded */

/*
 * Finds if a comment block is to be extracted.
 *
 * When -c option is specified, find out if comment block contains
 * comment-tag as a token separated by blanks. If it does, this
 * comment block is associated with the next msgid encountered.
 * Comment block is a linked list where each node contains one line
 * of comments.
 */
static int
isextracted(struct strlist_st *strlist)
{
	struct strlist_st	*p;
	char			*first, *pc;


	p = strlist;
	while (p != NULL) {
		first = strdup(p->str);
		while ((first != NULL) && (first[0] != NULL)) {
			pc = first;

			/*CONSTCOND*/
			while (1) {
				if (*pc == NULL) {
					break;
				} else if ((*pc == ' ') || (*pc == '\t')) {
					*pc++ = NULL;
					break;
				}
				pc++;
			}
			if (strcmp(first, comment_tag) == 0) {
				return (TRUE);
			}
			first = pc;
		}
		p = p->next;
	} /* while */

	/*
	 * Not found.
	 */
	return (FALSE);
} /* isextracted */

/*
 * Adds ANSI string to the domain element list.
 */
static void
add_str_to_element_list(int istextdomain, char *domain_list)
{
	struct element_st	*tmp_elem;
	struct element_st	*p, *q;
	struct domain_st	*tmp_dom;
	int			result;

	/*
	 * This can happen if something like gettext(USAGE) is used
	 * and it is impossible to get msgid for this gettext.
	 * Since -x option should be used in this kind of cases,
	 * it is OK not to catch msgid.
	 */
	if (strhead == NULL) {
		return;
	}

	/*
	 * The global variable curr_domain contains either NULL
	 * for default_domain or domain name for dgettext().
	 */
	tmp_dom = find_domain_node(domain_list);

	/*
	 * If this msgid is in the exclude file,
	 * then free the linked list and return.
	 */
	if ((istextdomain == FALSE) &&
	    (isexcluded(strhead) == TRUE)) {
		free_strlist(strhead);
		strhead = strtail = NULL;
		return;
	}

	tmp_elem = new_element();
	tmp_elem->msgid = strhead;
	tmp_elem->istextdomain = istextdomain;
	/*
	 * If -c option is specified and TAG matches,
	 * then associate the comment to the next [d]gettext() calls
	 * encountered in the source code.
	 * textdomain() calls will not have any effect.
	 */
	if (istextdomain == FALSE) {
		if ((cflg == TRUE) && (commhead != NULL)) {
			if (isextracted(commhead) == TRUE) {
				tmp_elem->comment = commhead;
			} else {
				free_strlist(commhead);
			}
			commhead = commtail = NULL;
		}
	}

	tmp_elem->linenum = linenum_saved;
	tmp_elem->fname = strdup(curr_file);


	if (sflg == TRUE) {
		/*
		 * If this is textdomain() call and -s option is specified,
		 * append this node to the textdomain linked list.
		 */
		if (istextdomain == TRUE) {
			if (tmp_dom->textdomain_head == NULL) {
				tmp_dom->textdomain_head = tmp_elem;
				tmp_dom->textdomain_tail = tmp_elem;
			} else {
				tmp_dom->textdomain_tail->next = tmp_elem;
				tmp_dom->textdomain_tail = tmp_elem;
			}
			strhead = strtail = NULL;
			return;
		}

		/*
		 * Insert the node to the properly sorted position.
		 */
		q = NULL;
		p = tmp_dom->gettext_head;
		while (p != NULL) {
			result = msgidcmp(strhead, p->msgid);
			if (result == 0) {
				/*
				 * Duplicate id. Do not store.
				 */
				free_strlist(strhead);
				strhead = strtail = NULL;
				return;
			} else if (result > 0) {
				/* move to the next node */
				q = p;
				p = p->next;
			} else {
				tmp_elem->next = p;
				if (q != NULL) {
					q->next = tmp_elem;
				} else {
					tmp_dom->gettext_head = tmp_elem;
				}
				strhead = strtail = NULL;
				return;
			}
		} /* while */

		/*
		 * New msgid is the largest or empty list.
		 */
		if (q != NULL) {
			/* largest case */
			q->next = tmp_elem;
		} else {
			/* empty list */
			tmp_dom->gettext_head = tmp_elem;
		}
	} else {
		/*
		 * Check if this msgid is already in the same domain.
		 */
		if (tmp_dom != NULL) {
			if (isduplicate(tmp_dom->gettext_head,
					tmp_elem->msgid) == TRUE) {
				tmp_elem->isduplicate = TRUE;
			}
		}
		/*
		 * If -s option is not specified, then everything
		 * is stored in gettext linked list.
		 */
		if (tmp_dom->gettext_head == NULL) {
			tmp_dom->gettext_head = tmp_elem;
			tmp_dom->gettext_tail = tmp_elem;
		} else {
			tmp_dom->gettext_tail->next = tmp_elem;
			tmp_dom->gettext_tail = tmp_elem;
		}
	}

	strhead = strtail = NULL;
} /* add_str_to_element_list */

/*
 * Write all domain linked list to the files.
 */
static void
write_all_files(void)
{
	struct domain_st	*tmp;

	/*
	 * Write out default domain file.
	 */
	write_one_file(def_dom);

	/*
	 * If dgettext() exists and -a option is not used,
	 * then there are non-empty linked list.
	 */
	tmp = dom_head;
	while (tmp != NULL) {
		write_one_file(tmp);
		tmp = tmp->next;
	}
} /* write_all_files */

/*
 * add an element_st list to the linked list.
 */
static void
add_node_to_polist(struct element_st **pohead,
	struct element_st **potail, struct element_st *elem)
{
	if (elem == NULL) {
		return;
	}

	if (*pohead == NULL) {
		*pohead = *potail = elem;
	} else {
		(*potail)->next = elem;
		*potail = (*potail)->next;
	}
} /* add_node_to_polist */

#define	INIT_STATE	0
#define	IN_MSGID	1
#define	IN_MSGSTR	2
#define	IN_COMMENT	3
/*
 * Reads existing po file into the linked list and returns the head
 * of the linked list.
 */
static struct element_st *
read_po(char *fname)
{
	struct element_st	*tmp_elem = NULL;
	struct element_st	*ehead = NULL, *etail = NULL;
	struct strlist_st	*comment_tail = NULL;
	struct strlist_st	*msgid_tail = NULL;
	struct strlist_st	*msgstr_tail = NULL;
	int			state = INIT_STATE;
	char			line [MAX_STRING_LEN];
	FILE			*fp;

	if ((fp = fopen(fname, "r")) == NULL) {
		return (NULL);
	}

	while (fgets(line, MAX_STRING_LEN, fp) != NULL) {
		/*
		 * Line starting with # is a comment line and ignored.
		 * Blank line is ignored, too.
		 */
		if (line[0] == '\n') {
			continue;
		} else if (line[0] == '#') {
			/*
			 * If tmp_elem is not NULL, there is msgid pair
			 * stored. Therefore, add it.
			 */
			if ((tmp_elem != NULL) && (state == IN_MSGSTR)) {
				add_node_to_polist(&ehead, &etail, tmp_elem);
			}

			if ((state == INIT_STATE) || (state == IN_MSGSTR)) {
				state = IN_COMMENT;
				tmp_elem = new_element();
				tmp_elem->comment = comment_tail =
							new_strlist();
				/*
				 * remove new line and skip "# "
				 * in the beginning of the existing
				 * comment line.
				 */
				line[strlen(line)-1] = 0;
				comment_tail->str = strdup(line+2);
			} else if (state == IN_COMMENT) {
				comment_tail->next = new_strlist();
				comment_tail = comment_tail->next;
				/*
				 * remove new line and skip "# "
				 * in the beginning of the existing
				 * comment line.
				 */
				line[strlen(line)-1] = 0;
				comment_tail->str = strdup(line+2);
			}

		} else if (strncmp(line, "domain", 6) == 0) {
			/* ignore domain line */
			continue;
		} else if (strncmp(line, "msgid", 5) == 0) {
			if (state == IN_MSGSTR) {
				add_node_to_polist(&ehead, &etail, tmp_elem);
				tmp_elem = new_element();
			} else if (state == INIT_STATE) {
				tmp_elem = new_element();
			}

			state = IN_MSGID;
			trim_line(line);
			tmp_elem->msgid = msgid_tail = new_strlist();
			msgid_tail->str = strdup(line);

		} else if (strncmp(line, "msgstr", 6) == 0) {
			state = IN_MSGSTR;
			trim_line(line);
			tmp_elem->msgstr = msgstr_tail = new_strlist();
			msgstr_tail->str = strdup(line);
		} else {
			/*
			 * If more than one line of string forms msgid,
			 * append it to the string linked list.
			 */
			if (state == IN_MSGID) {
				trim_line(line);
				msgid_tail->next = new_strlist();
				msgid_tail = msgid_tail->next;
				msgid_tail->str = strdup(line);
			} else if (state == IN_MSGSTR) {
				trim_line(line);
				msgstr_tail->next = new_strlist();
				msgstr_tail = msgstr_tail->next;
				msgstr_tail->str = strdup(line);
			}
		}
	} /* while */

	/*
	 * To insert the last msgid pair.
	 */
	if (tmp_elem != NULL) {
		add_node_to_polist(&ehead, &etail, tmp_elem);
	}

#ifdef DEBUG
	{
		struct domain_st *tmp_domain = new_domain();
		char	tmpstr[256];

		sprintf(tmpstr, "existing_po file : <%s>", fname);
		tmp_domain->dname = strdup(tmpstr);
		tmp_domain->gettext_head = ehead;
		printf("======= existing po file <%s>  ========\n", fname);
		print_one_domain(tmp_domain);
	}
#endif /* DEBUG */

	(void) fclose(fp);
	return (ehead);
} /* read_po */

/*
 * This function will append the second list to the first list.
 * If the msgid in the second list contains msgid in the first list,
 * it will be marked as duplicate.
 */
static struct element_st *
append_list(struct element_st *l1, struct element_st *l2)
{
	struct element_st	*p = NULL, *q = NULL, *l1_tail = NULL;

	if (l1 == NULL)
		return (l2);
	if (l2 == NULL)
		return (l1);

	/*
	 * in this while loop, just mark isduplicate field of node in the
	 * l2 list if the same msgid exists in l1 list.
	 */
	p = l2;
	while (p != NULL) {
		q = l1;
		while (q != NULL) {
			if (msgidcmp(p->msgid, q->msgid) == 0) {
				p->isduplicate = TRUE;
				break;
			}
			q = q->next;
		}
		p = p->next;
	}

	/* Now connect two linked lists. */
	l1_tail = l1;
	while (l1_tail->next != NULL) {
		if (l1->next == NULL)
			break;
		l1_tail = l1_tail-> next;
	}
	l1_tail->next = l2;

	return (l1);
} /* append_list */

/*
 * Writes one domain list to the file.
 */
static void
write_one_file(struct domain_st *head)
{
	FILE			*fp;
	char			fname [MAX_PATH_LEN];
	char			dname [MAX_DOMAIN_LEN];
	struct element_st	*p;
	struct element_st	*existing_po_list;

	/*
	 * If head is NULL, then it still has to create .po file
	 * so that it will guarantee that the previous .po file was
	 * alwasys deleted.
	 * This is why checking NULL pointer has been moved to after
	 * creating  .po file.
	 */

	/*
	 * If domain name is NULL, it is the default domain list.
	 * The domain name is either "messages" or specified by option -d.
	 * The default domain name is contained in default_domain variable.
	 */
	dname[0] = NULL;
	if ((head != NULL) &&
	    (head->dname != NULL)) {
		(void) strcpy(dname, head->dname);
	} else {
		(void) strcpy(dname, default_domain);
	}

	/*
	 * path is the current directory if not specified by option -p.
	 */
	fname[0] = 0;
	if (pflg == TRUE) {
		(void) strcat(fname, pathname);
		(void) strcat(fname, "/");
	}
	(void) strcat(fname, dname);
	(void) strcat(fname, ".po");

	/*
	 * If -j flag is specified, read exsiting .po file and
	 * append the current list to the end of the list read from
	 * the existing .po file.
	 */
	if (jflg == TRUE) {
		/*
		 * If head is NULL, we don't have to change existing file.
		 * Therefore, just return it.
		 */
		if (head == NULL) {
			return;
		}
		existing_po_list = read_po(fname);
		head->gettext_head = append_list(existing_po_list,
					head->gettext_head);
#ifdef DEBUG
		if (head->dname != NULL) {
			printf("===after merge (-j option): <%s>===\n",
			head->dname);
		} else {
			printf("===after merge (-j option): <NULL>===\n");
		}
		print_one_domain(head);
#endif

	} /* if jflg */

	if ((fp = fopen(fname, "w")) == NULL) {
		(void) fprintf(stderr,
			"ERROR, can't open output file: %s\n", fname);
		exit(2);
	}

	(void) fprintf(fp, "domain \"%s\"\n", dname);

	/* See comments above in the beginning of this function */
	if (head == NULL)
		return;

	/*
	 * There are separate storage for textdomain() calls if
	 * -s option is used (textdomain_head linked list).
	 * Otherwise, textdomain() is mixed with gettext(0 and dgettext().
	 * If mixed, the boolean varaible istextdomain is used to see
	 * if the current node contains textdomain() or [d]gettext().
	 */
	if (sflg == TRUE) {
		p = head->textdomain_head;
		while (p != NULL) {
			/*
			 * textdomain output line already contains
			 * FIle name and line number information.
			 * Therefore, does not have to check for nflg.
			 */
			output_textdomain(fp, p);
			p = p->next;
		}
	}

	p = head->gettext_head;
	while (p != NULL) {

		/*
		 * Comment is printed only if -c is used and
		 * associated with gettext or dgettext.
		 * textdomain is not associated with comments.
		 * Changes:
		 *    comments should be extracted in case of -j option
		 *    because there are read from exising file.
		 */
		if (((cflg == TRUE) || (jflg == TRUE)) &&
			(p->istextdomain != TRUE)) {
			output_comment(fp, p->comment);
		}

		/*
		 * If -n is used, then file number and line number
		 * information is printed.
		 * In case of textdomain(), this information is redundant
		 * and is not printed.
		 * If linenum is 0, it means this information has been
		 * read from existing po file and it already contains
		 * file and line number info as a comment line. So, it
		 * should not printed in such case.
		 */
		if ((nflg == TRUE) && (p->istextdomain == FALSE) &&
			(p->linenum > 0)) {
			(void) fprintf(fp, "# File:%s, line:%d\n",
					p->fname, p->linenum);
		}

		/*
		 * Depending on the type of node, output textdomain comment
		 * or msgid.
		 */
		if ((sflg == FALSE) &&
		    (p->istextdomain == TRUE)) {
			output_textdomain(fp, p);
		} else {
			output_msgid(fp, p->msgid, p->isduplicate);
		}
		p = p->next;

	} /* while */

	(void) fclose(fp);
} /* write_one_file */

/*
 * Prints out textdomain call as a comment line with file name and
 * the line number information.
 */
static void
output_textdomain(FILE *fp, struct element_st *p)
{

	if (p == NULL)
		return;

	/*
	 * Write textdomain() line as a comment.
	 */
	(void) fprintf(fp, "# File:%s, line:%d, textdomain(\"%s\");\n",
		p->fname, p->linenum,  p->msgid->str);
} /* output_textdomain */

/*
 * Prints out comments from linked list.
 */
static void
output_comment(FILE *fp, struct strlist_st *p)
{
	if (p == NULL)
		return;

	/*
	 * Write comment section.
	 */
	while (p != NULL) {
		(void) fprintf(fp, "# %s\n", p->str);
		p = p->next;
	}
} /* output_comment */

/*
 * Prints out msgid along with msgstr.
 */
static void
output_msgid(FILE *fp, struct strlist_st *p, int duplicate)
{
	struct strlist_st	*q;

	if (p == NULL)
		return;

	/*
	 * Write msgid section.
	 * If duplciate flag is ON, prepend "# " in front of every line
	 * so that they are considered as comment lines in .po file.
	 */
	if (duplicate == TRUE) {
		(void) fprintf(fp, "# ");
	}
	(void) fprintf(fp, "msgid  \"%s\"\n", p->str);
	q = p->next;
	while (q != NULL) {
		if (duplicate == TRUE) {
			(void) fprintf(fp, "# ");
		}
		(void) fprintf(fp, "       \"%s\"\n", q->str);
		q = q->next;
	}

	/*
	 * Write msgstr section.
	 * if -M option is specified, append <suffix> to msgid.
	 * if -m option is specified, prepend <prefix> to msgid.
	 */
	if (duplicate == TRUE) {
		(void) fprintf(fp, "# ");
	}
	if ((mflg == TRUE) || (Mflg == TRUE)) {
		if (mflg == TRUE) {
			/*
			 * If single line msgid, add suffix to the same line
			 */
			if ((Mflg == TRUE) && (p->next == NULL)) {
				/* -M and -m and single line case */
				(void) fprintf(fp,
					"msgstr \"%s%s%s\"\n",
					prefix, p->str, suffix);
			} else {
				/* -M and -m and multi line case */
				(void) fprintf(fp,
					"msgstr \"%s%s\"\n",
					prefix, p->str);
			}
		} else {
			if ((Mflg == TRUE) && (p->next == NULL)) {
				/* -M only with single line case */
				(void) fprintf(fp, "msgstr \"%s%s\"\n",
						p->str, suffix);
			} else {
				/* -M only with multi line case */
				(void) fprintf(fp, "msgstr \"%s\"\n", p->str);
			}
		}
		q = p->next;
		while (q != NULL) {
			if (duplicate == TRUE) {
				(void) fprintf(fp, "# ");
			}
			(void) fprintf(fp, "       \"%s\"\n", q->str);
			q = q->next;
		}
		/*
		 * If multi line msgid, add suffix after the last line.
		 */
		if ((Mflg == TRUE) && (p->next != NULL) &&
					(suffix[0] != NULL)) {
			(void) fprintf(fp, "       \"%s\"\n", suffix);
		}
	} else {
		(void) fprintf(fp, "msgstr\n");
	}
} /* output_msgid */

/*
 * Malloc a new element node and initialize fields.
 */
static struct element_st *
new_element(void)
{
	struct element_st *tmp;

	tmp = (struct element_st *)malloc(sizeof (struct element_st));
	tmp->istextdomain = FALSE;
	tmp->isduplicate = FALSE;
	tmp->msgid = NULL;
	tmp->msgstr = NULL;
	tmp->comment = NULL;
	tmp->fname = NULL;
	tmp->linenum = 0;
	tmp->next = NULL;

	return (tmp);
} /* new_element */

/*
 * Malloc a new domain node and initialize fields.
 */
static struct domain_st *
new_domain(void)
{
	struct domain_st *tmp;

	tmp = (struct domain_st *)malloc(sizeof (struct domain_st));
	tmp->dname = NULL;
	tmp->gettext_head = NULL;
	tmp->gettext_tail = NULL;
	tmp->textdomain_head = NULL;
	tmp->textdomain_tail = NULL;
	tmp->next = NULL;

	return (tmp);
} /* new_domain */

/*
 * Malloc a new string list node and initialize fields.
 */
static struct strlist_st *
new_strlist(void)
{
	struct strlist_st *tmp;

	tmp = (struct strlist_st *)malloc(sizeof (struct strlist_st));
	tmp->str = NULL;
	tmp->next = NULL;

	return (tmp);
} /* new_strlist */

/*
 * Malloc a new exclude string list node and initialize fields.
 */
static struct exclude_st *
new_exclude(void)
{
	struct exclude_st *tmp;

	tmp = (struct exclude_st *)malloc(sizeof (struct exclude_st));
	tmp->exstr = NULL;
	tmp->next = NULL;

	return (tmp);
} /* new_exclude */

/*
 * Local version of strcat to keep within maximum string size.
 */
static void
lstrcat(char *s1, const char *s2)
{
	char	*es1 = &s1[MAX_STRING_LEN];
	char	*ss1 = s1;

	while (*s1++)
		;
	--s1;
	while (*s1++ = *s2++)
		if (s1 >= es1) {
			s1[-1] = '\0';
			if ((in_comment == TRUE || in_quote == TRUE) &&
			    (warn_linenum != curr_linenum)) {
				if (stdin_only == FALSE) {
					(void) fprintf(stderr,
					    "WARNING: file %s line %d exceeds "\
					    "%d characters:  \"%15.15s\"\n",
					    curr_file, curr_linenum,
					    MAX_STRING_LEN, ss1);
				} else {
					(void) fprintf(stderr,
					    "WARNING: line %d exceeds "\
					    "%d characters:  \"%15.15s\"\n",
					    curr_linenum, MAX_STRING_LEN, ss1);
				}
				warn_linenum = curr_linenum;
			}
			break;
		}
} /* lstrcat */

#ifdef DEBUG
/*
 * Debug print routine. Compiled only with DEBUG on.
 */
void
print_element_list(struct element_st *q)
{
	struct strlist_st	*r;

	while (q != NULL) {
		printf("   istextdomain = %d\n", q->istextdomain);
		printf("   isduplicate  = %d\n", q->isduplicate);
		if ((q->msgid != NULL) && (q->msgid->str != NULL)) {
			printf("   msgid = <%s>\n", q->msgid->str);
			r = q->msgid->next;
			while (r != NULL) {
				printf("           <%s>\n", r->str);
				r = r->next;
			}
		} else {
			printf("   msgid = <NULL>\n");
		}
		if ((q->msgstr != NULL) && (q->msgstr->str != NULL)) {
			printf("   msgstr= <%s>\n", q->msgstr->str);
			r = q->msgstr->next;
			while (r != NULL) {
				printf("           <%s>\n", r->str);
				r = r->next;
			}
		} else {
			printf("   msgstr= <NULL>\n");
		}

		if (q->comment == NULL) {
			printf("   comment = <NULL>\n");
		} else {
			printf("   comment = <%s>\n", q->comment->str);
			r = q->comment->next;
			while (r != NULL) {
				printf("             <%s>\n", r->str);
				r = r->next;
			}
		}

		if (q->fname == NULL) {
			printf("   fname = <NULL>\n");
		} else {
			printf("   fname = <%s>\n", q->fname);
		}
		printf("   linenum = %d\n", q->linenum);
		printf("\n");
		q = q->next;
	}
}

/*
 * Debug print routine. Compiled only with DEBUG on.
 */
void
print_one_domain(struct domain_st *p)
{
	struct element_st	*q;

	if (p == NULL) {
		printf("domain pointer = <NULL>\n");
		return;
	} else if (p->dname == NULL) {
		printf("domain_name = <%s>\n", "<NULL>");
	} else {
		printf("domain_name = <%s>\n", p->dname);
	}
	q = p->gettext_head;
	print_element_list(q);

	q = p->textdomain_head;
	print_element_list(q);
} /* print_one_domain */

void
print_all_domain(struct domain_st *dom_list)
{
	struct domain_st	*p;
	struct element_st	*q;

	p = dom_list;
	while (p != NULL) {
		print_one_domain(p);
		p = p->next;
	} /* while */
} /* print_all_domain */
#endif