1 /* $Id: iptcutil.c,v 1.11 2015-06-21 01:09:09 bfriesen Exp $ */
2 
3 #include "tif_config.h"
4 
5 #include <stdio.h>
6 #include <stdlib.h>
7 #include <string.h>
8 #include <ctype.h>
9 
10 #ifdef HAVE_STRINGS_H
11 # include <strings.h>
12 #endif
13 
14 #ifdef HAVE_IO_H
15 # include <io.h>
16 #endif
17 
18 #ifdef HAVE_FCNTL_H
19 # include <fcntl.h>
20 #endif
21 
22 #ifdef WIN32
23 #define STRNICMP strnicmp
24 #else
25 #define STRNICMP strncasecmp
26 #endif
27 
28 typedef struct _tag_spec
29 {
30   short
31     id;
32 
33   char
34     *name;
35 } tag_spec;
36 
37 static tag_spec tags[] = {
38   { 5,"Image Name" },
39   { 7,"Edit Status" },
40   { 10,"Priority" },
41   { 15,"Category" },
42   { 20,"Supplemental Category" },
43   { 22,"Fixture Identifier" },
44   { 25,"Keyword" },
45   { 30,"Release Date" },
46   { 35,"Release Time" },
47   { 40,"Special Instructions" },
48   { 45,"Reference Service" },
49   { 47,"Reference Date" },
50   { 50,"Reference Number" },
51   { 55,"Created Date" },
52   { 60,"Created Time" },
53   { 65,"Originating Program" },
54   { 70,"Program Version" },
55   { 75,"Object Cycle" },
56   { 80,"Byline" },
57   { 85,"Byline Title" },
58   { 90,"City" },
59   { 95,"Province State" },
60   { 100,"Country Code" },
61   { 101,"Country" },
62   { 103,"Original Transmission Reference" },
63   { 105,"Headline" },
64   { 110,"Credit" },
65   { 115,"Source" },
66   { 116,"Copyright String" },
67   { 120,"Caption" },
68   { 121,"Local Caption" },
69   { 122,"Caption Writer" },
70   { 200,"Custom Field 1" },
71   { 201,"Custom Field 2" },
72   { 202,"Custom Field 3" },
73   { 203,"Custom Field 4" },
74   { 204,"Custom Field 5" },
75   { 205,"Custom Field 6" },
76   { 206,"Custom Field 7" },
77   { 207,"Custom Field 8" },
78   { 208,"Custom Field 9" },
79   { 209,"Custom Field 10" },
80   { 210,"Custom Field 11" },
81   { 211,"Custom Field 12" },
82   { 212,"Custom Field 13" },
83   { 213,"Custom Field 14" },
84   { 214,"Custom Field 15" },
85   { 215,"Custom Field 16" },
86   { 216,"Custom Field 17" },
87   { 217,"Custom Field 18" },
88   { 218,"Custom Field 19" },
89   { 219,"Custom Field 20" }
90 };
91 
92 /*
93  * We format the output using HTML conventions
94  * to preserve control characters and such.
95  */
formatString(FILE * ofile,const char * s,int len)96 void formatString(FILE *ofile, const char *s, int len)
97 {
98   putc('"', ofile);
99   for (; len > 0; --len, ++s) {
100     int c = *s;
101     switch (c) {
102     case '&':
103       fputs("&amp;", ofile);
104       break;
105 #ifdef HANDLE_GT_LT
106     case '<':
107       fputs("&lt;", ofile);
108       break;
109     case '>':
110       fputs("&gt;", ofile);
111       break;
112 #endif
113     case '"':
114       fputs("&quot;", ofile);
115       break;
116     default:
117       if (iscntrl(c))
118         fprintf(ofile, "&#%d;", c);
119       else
120         putc(*s, ofile);
121       break;
122     }
123   }
124   fputs("\"\n", ofile);
125 }
126 
127 typedef struct _html_code
128 {
129   short
130     len;
131   const char
132    *code,
133     val;
134 } html_code;
135 
136 static html_code html_codes[] = {
137 #ifdef HANDLE_GT_LT
138     { 4,"&lt;",'<' },
139     { 4,"&gt;",'>' },
140 #endif
141     { 5,"&amp;",'&' },
142     { 6,"&quot;",'"' }
143 };
144 
145 /*
146  * This routine converts HTML escape sequence
147  * back to the original ASCII representation.
148  * - returns the number of characters dropped.
149  */
convertHTMLcodes(char * s,int len)150 int convertHTMLcodes(char *s, int len)
151 {
152   if (len <=0 || s==(char*)NULL || *s=='\0')
153     return 0;
154 
155   if (s[1] == '#')
156     {
157       int val, o;
158 
159       if (sscanf(s,"&#%d;",&val) == 1)
160         {
161           o = 3;
162           while (s[o] != ';')
163             {
164               o++;
165               if (o > 5)
166                 break;
167             }
168           if (o < 5)
169             strcpy(s+1, s+1+o);
170           *s = val;
171           return o;
172         }
173     }
174   else
175     {
176       int
177         i,
178         codes = sizeof(html_codes) / sizeof(html_code);
179 
180       for (i=0; i < codes; i++)
181         {
182           if (html_codes[i].len <= len)
183             if (STRNICMP(s, html_codes[i].code, html_codes[i].len) == 0)
184               {
185                 strcpy(s+1, s+html_codes[i].len);
186                 *s = html_codes[i].val;
187                 return html_codes[i].len-1;
188               }
189         }
190     }
191 
192   return 0;
193 }
194 
formatIPTC(FILE * ifile,FILE * ofile)195 int formatIPTC(FILE *ifile, FILE *ofile)
196 {
197   unsigned int
198     foundiptc,
199     tagsfound;
200 
201   char
202     *readable,
203     *str;
204 
205   long
206     tagindx,
207     taglen;
208 
209   int
210     i,
211     tagcount = sizeof(tags) / sizeof(tag_spec);
212 
213   int
214     c,
215     dataset,
216     recnum;
217 
218   foundiptc = 0; /* found the IPTC-Header */
219   tagsfound = 0; /* number of tags found */
220 
221   c = getc(ifile);
222   while (c != EOF)
223     {
224       if (c == 0x1c)
225         foundiptc = 1;
226       else
227         {
228           if (foundiptc)
229             {
230               return -1;
231             }
232           else
233             {
234               c = getc(ifile);
235               continue;
236             }
237         }
238 
239       /* we found the 0x1c tag and now grab the dataset and record number tags */
240       dataset = getc(ifile);
241       if ((char) dataset == EOF)
242         return -1;
243       recnum = getc(ifile);
244       if ((char) recnum == EOF)
245         return -1;
246       /* try to match this record to one of the ones in our named table */
247       for (i=0; i< tagcount; i++)
248         {
249           if (tags[i].id == recnum)
250             break;
251         }
252       if (i < tagcount)
253         readable = tags[i].name;
254       else
255         readable = "";
256 
257       /* then we decode the length of the block that follows - long or short fmt */
258       c = getc(ifile);
259       if (c == EOF)
260         return 0;
261       if (c & (unsigned char) 0x80)
262         {
263           unsigned char
264             buffer[4];
265 
266           for (i=0; i<4; i++)
267             {
268               c = getc(ifile);
269               if (c == EOF)
270                 return -1;
271               buffer[i] = c;
272             }
273           taglen = (((long) buffer[ 0 ]) << 24) |
274             (((long) buffer[ 1 ]) << 16) |
275             (((long) buffer[ 2 ]) <<  8) |
276             (((long) buffer[ 3 ]));
277         }
278       else
279         {
280           int
281             x = c;
282 
283           taglen = x << 8;
284           x = getc(ifile);
285           if (x == EOF)
286             return -1;
287           taglen |= (long) x;
288         }
289       /* Place limits on tag length */
290       if ((taglen <= 0) || (taglen > 1048576))
291         {
292           printf("Inappropriate IPTC tag length %ld\n",taglen);
293           return -1;
294         }
295       /* make a buffer to hold the tag data and snag it from the input stream */
296       str = (char *) malloc((unsigned int) (taglen+1));
297       if (str == (char *) NULL)
298         {
299           printf("Memory allocation failed");
300           return 0;
301         }
302       for (tagindx=0; tagindx<taglen; tagindx++)
303         {
304           c = getc(ifile);
305           if (c == EOF)
306             {
307               free(str);
308               return -1;
309             }
310           str[tagindx] = c;
311         }
312       str[ taglen ] = 0;
313 
314       /* now finish up by formatting this binary data into ASCII equivalent */
315       if (strlen(readable) > 0)
316         fprintf(ofile, "%d#%d#%s=",(unsigned int)dataset, (unsigned int) recnum, readable);
317       else
318         fprintf(ofile, "%d#%d=",(unsigned int)dataset, (unsigned int) recnum);
319       formatString( ofile, str, taglen );
320       free(str);
321 
322       tagsfound++;
323 
324       c = getc(ifile);
325     }
326   return tagsfound;
327 }
328 
329 int tokenizer(unsigned inflag,char *token,int tokmax,char *line,
330 char *white,char *brkchar,char *quote,char eschar,char *brkused,
331 int *next,char *quoted);
332 
super_fgets(char * b,int * blen,FILE * file)333 char *super_fgets(char *b, int *blen, FILE *file)
334 {
335   int
336     c,
337     len;
338 
339   char
340     *q;
341 
342   len=*blen;
343   for (q=b; ; q++)
344     {
345       c=fgetc(file);
346       if (c == EOF || c == '\n')
347         break;
348       if (((long)q - (long)b + 1 ) >= (long) len)
349         {
350           long
351             tlen;
352 
353           tlen=(long)q-(long)b;
354           len<<=1;
355           b=(char *) realloc((char *) b,(len+2));
356           if ((char *) b == (char *) NULL)
357             break;
358           q=b+tlen;
359         }
360       *q=(unsigned char) c;
361     }
362   *blen=0;
363   if ((unsigned char *)b != (unsigned char *) NULL)
364     {
365       int
366         tlen;
367 
368       tlen=(long)q - (long)b;
369       if (tlen == 0)
370         return (char *) NULL;
371       b[tlen] = '\0';
372       *blen=++tlen;
373     }
374   return b;
375 }
376 
377 #define BUFFER_SZ 4096
378 
main(int argc,char * argv[])379 int main(int argc, char *argv[])
380 {
381   /* unsigned int */
382   /*   length; */
383 
384   /*unsigned char
385    *buffer;*/
386 
387   int
388     i,
389     mode; /* iptc binary, or iptc text */
390 
391   FILE
392     *ifile = stdin,
393     *ofile = stdout;
394 
395   char
396     c,
397     *usage = "usage: iptcutil -t | -b [-i file] [-o file] <input >output";
398 
399   if( argc < 2 )
400     {
401       puts(usage);
402       return 1;
403     }
404 
405   mode = 0;
406   /* length = -1; */
407   /* buffer = (unsigned char *)NULL; */
408 
409   for (i=1; i<argc; i++)
410     {
411       c = argv[i][0];
412       if (c == '-' || c == '/')
413         {
414           c = argv[i][1];
415           switch( c )
416             {
417             case 't':
418               mode = 1;
419 #ifdef WIN32
420               /* Set "stdout" to binary mode: */
421               _setmode( _fileno( ofile ), _O_BINARY );
422 #endif
423               break;
424             case 'b':
425               mode = 0;
426 #ifdef WIN32
427               /* Set "stdin" to binary mode: */
428               _setmode( _fileno( ifile ), _O_BINARY );
429 #endif
430               break;
431             case 'i':
432               if (mode == 0)
433                 ifile = fopen(argv[++i], "rb");
434               else
435                 ifile = fopen(argv[++i], "rt");
436               if (ifile == (FILE *)NULL)
437                 {
438                   printf("Unable to open: %s\n", argv[i]);
439                   return 1;
440                 }
441               break;
442             case 'o':
443               if (mode == 0)
444                 ofile = fopen(argv[++i], "wt");
445               else
446                 ofile = fopen(argv[++i], "wb");
447               if (ofile == (FILE *)NULL)
448                 {
449                   printf("Unable to open: %s\n", argv[i]);
450                   return 1;
451                 }
452               break;
453             default:
454               printf("Unknown option: %s\n", argv[i]);
455               return 1;
456             }
457         }
458       else
459         {
460           puts(usage);
461           return 1;
462         }
463     }
464 
465   if (mode == 0) /* handle binary iptc info */
466     formatIPTC(ifile, ofile);
467 
468   if (mode == 1) /* handle text form of iptc info */
469     {
470       char
471         brkused,
472         quoted,
473         *line,
474         *token,
475         *newstr;
476 
477       int
478         state,
479         next;
480 
481       unsigned char
482         recnum = 0,
483         dataset = 0;
484 
485       int
486         inputlen = BUFFER_SZ;
487 
488       line = (char *) malloc(inputlen);
489       token = (char *)NULL;
490       while((line = super_fgets(line,&inputlen,ifile))!=NULL)
491         {
492           state=0;
493           next=0;
494 
495           token = (char *) malloc(inputlen);
496           newstr = (char *) malloc(inputlen);
497           while(tokenizer(0, token, inputlen, line, "", "=", "\"", 0,
498                           &brkused,&next,&quoted)==0)
499             {
500               if (state == 0)
501                 {
502                   int
503                     state,
504                     next;
505 
506                   char
507                     brkused,
508                     quoted;
509 
510                   state=0;
511                   next=0;
512                   while(tokenizer(0, newstr, inputlen, token, "", "#", "", 0,
513                                   &brkused, &next, &quoted)==0)
514                     {
515                       if (state == 0)
516                         dataset = (unsigned char) atoi(newstr);
517                       else
518                         if (state == 1)
519                           recnum = (unsigned char) atoi(newstr);
520                       state++;
521                     }
522                 }
523               else
524                 if (state == 1)
525                   {
526                     int
527                       next;
528 
529                     unsigned long
530                       len;
531 
532                     char
533                       brkused,
534                       quoted;
535 
536                     next=0;
537                     len = strlen(token);
538                     while(tokenizer(0, newstr, inputlen, token, "", "&", "", 0,
539                                     &brkused, &next, &quoted)==0)
540                       {
541                         if (brkused && next > 0)
542                           {
543                             char
544                               *s = &token[next-1];
545 
546                             len -= convertHTMLcodes(s, strlen(s));
547                           }
548                       }
549 
550                     fputc(0x1c, ofile);
551                     fputc(dataset, ofile);
552                     fputc(recnum, ofile);
553                     if (len < 0x10000)
554                       {
555                         fputc((len >> 8) & 255, ofile);
556                         fputc(len & 255, ofile);
557                       }
558                     else
559                       {
560                         fputc(((len >> 24) & 255) | 0x80, ofile);
561                         fputc((len >> 16) & 255, ofile);
562                         fputc((len >> 8) & 255, ofile);
563                         fputc(len & 255, ofile);
564                       }
565                     next=0;
566                     while (len--)
567                       fputc(token[next++], ofile);
568                   }
569               state++;
570             }
571           free(token);
572           token = (char *)NULL;
573           free(newstr);
574           newstr = (char *)NULL;
575         }
576       free(line);
577 
578       fclose( ifile );
579       fclose( ofile );
580     }
581 
582   return 0;
583 }
584 
585 /*
586 	This routine is a generalized, finite state token parser. It allows
587     you extract tokens one at a time from a string of characters.  The
588     characters used for white space, for break characters, and for quotes
589     can be specified. Also, characters in the string can be preceded by
590     a specifiable escape character which removes any special meaning the
591     character may have.
592 
593 	There are a lot of formal parameters in this subroutine call, but
594 	once you get familiar with them, this routine is fairly easy to use.
595 	"#define" macros can be used to generate simpler looking calls for
596 	commonly used applications of this routine.
597 
598 	First, some terminology:
599 
600 	token:		used here, a single unit of information in
601 				the form of a group of characters.
602 
603 	white space:	space that gets ignored (except within quotes
604 				or when escaped), like blanks and tabs.  in
605 				addition, white space terminates a non-quoted
606 				token.
607 
608 	break character: a character that separates non-quoted tokens.
609 				commas are a common break character.  the
610 				usage of break characters to signal the end
611 				of a token is the same as that of white space,
612 				except multiple break characters with nothing
613 				or only white space between generate a null
614 				token for each two break characters together.
615 
616 				for example, if blank is set to be the white
617 				space and comma is set to be the break
618 				character, the line ...
619 
620 				A, B, C ,  , DEF
621 
622 				... consists of 5 tokens:
623 
624 				1)	"A"
625 				2)	"B"
626 				3)	"C"
627 				4)	""      (the null string)
628 				5)	"DEF"
629 
630 	quote character: 	a character that, when surrounding a group
631 				of other characters, causes the group of
632 				characters to be treated as a single token,
633 				no matter how many white spaces or break
634 				characters exist in the group.	also, a
635 				token always terminates after the closing
636 				quote.	for example, if ' is the quote
637 				character, blank is white space, and comma
638 				is the break character, the following
639 				string ...
640 
641 				A, ' B, CD'EF GHI
642 
643 				... consists of 4 tokens:
644 
645 				1)	"A"
646 				2)	" B, CD" (note the blanks & comma)
647 				3)	"EF"
648 				4)	"GHI"
649 
650 				the quote characters themselves do
651 				not appear in the resultant tokens.  the
652 				double quotes are delimiters i use here for
653 				documentation purposes only.
654 
655 	escape character:	a character which itself is ignored but
656 				which causes the next character to be
657 				used as is.  ^ and \ are often used as
658 				escape characters.  an escape in the last
659 				position of the string gets treated as a
660 				"normal" (i.e., non-quote, non-white,
661 				non-break, and non-escape) character.
662 				for example, assume white space, break
663 				character, and quote are the same as in the
664 				above examples, and further, assume that
665 				^ is the escape character.  then, in the
666 				string ...
667 
668 				ABC, ' DEF ^' GH' I ^ J K^ L ^
669 
670 				... there are 7 tokens:
671 
672 				1)	"ABC"
673 				2)	" DEF ' GH"
674 				3)	"I"
675 				4)	" "     (a lone blank)
676 				5)	"J"
677 				6)	"K L"
678 				7)	"^"     (passed as is at end of line)
679 
680 
681 	OK, now that you have this background, here's how to call "tokenizer":
682 
683 	result=tokenizer(flag,token,maxtok,string,white,break,quote,escape,
684 		      brkused,next,quoted)
685 
686 	result: 	0 if we haven't reached EOS (end of string), and
687 			1 if we have (this is an "int").
688 
689 	flag:		right now, only the low order 3 bits are used.
690 			1 => convert non-quoted tokens to upper case
691 			2 => convert non-quoted tokens to lower case
692 			0 => do not convert non-quoted tokens
693 			(this is a "char").
694 
695 	token:		a character string containing the returned next token
696 			(this is a "char[]").
697 
698 	maxtok: 	the maximum size of "token".  characters beyond
699 			"maxtok" are truncated (this is an "int").
700 
701 	string: 	the string to be parsed (this is a "char[]").
702 
703 	white:		a string of the valid white spaces.  example:
704 
705 			char whitesp[]={" \t"};
706 
707 			blank and tab will be valid white space (this is
708 			a "char[]").
709 
710 	break:		a string of the valid break characters.  example:
711 
712 			char breakch[]={";,"};
713 
714 			semicolon and comma will be valid break characters
715 			(this is a "char[]").
716 
717 			IMPORTANT:  do not use the name "break" as a C
718 			variable, as this is a reserved word in C.
719 
720 	quote:		a string of the valid quote characters.  an example
721 			would be
722 
723 			char whitesp[]={"'\"");
724 
725 			(this causes single and double quotes to be valid)
726 			note that a token starting with one of these characters
727 			needs the same quote character to terminate it.
728 
729 			for example,
730 
731 			"ABC '
732 
733 			is unterminated, but
734 
735 			"DEF" and 'GHI'
736 
737 			are properly terminated.  note that different quote
738 			characters can appear on the same line; only for
739 			a given token do the quote characters have to be
740 			the same (this is a "char[]").
741 
742 	escape: 	the escape character (NOT a string ... only one
743 			allowed).  use zero if none is desired (this is
744 			a "char").
745 
746 	brkused:	the break character used to terminate the current
747 			token.	if the token was quoted, this will be the
748 			quote used.  if the token is the last one on the
749 			line, this will be zero (this is a pointer to a
750 			"char").
751 
752 	next:		this variable points to the first character of the
753 			next token.  it gets reset by "tokenizer" as it steps
754 			through the string.  set it to 0 upon initialization,
755 			and leave it alone after that.	you can change it
756 			if you want to jump around in the string or re-parse
757 			from the beginning, but be careful (this is a
758 			pointer to an "int").
759 
760 	quoted: 	set to 1 (true) if the token was quoted and 0 (false)
761 			if not.  you may need this information (for example:
762 			in C, a string with quotes around it is a character
763 			string, while one without is an identifier).
764 
765 			(this is a pointer to a "char").
766 */
767 
768 /* states */
769 
770 #define IN_WHITE 0
771 #define IN_TOKEN 1
772 #define IN_QUOTE 2
773 #define IN_OZONE 3
774 
775 int _p_state;	   /* current state	 */
776 unsigned _p_flag;  /* option flag	 */
777 char _p_curquote;  /* current quote char */
778 int _p_tokpos;	   /* current token pos  */
779 
780 /* routine to find character in string ... used only by "tokenizer" */
781 
sindex(char ch,char * string)782 int sindex(char ch,char *string)
783 {
784   char *cp;
785   for(cp=string;*cp;++cp)
786     if(ch==*cp)
787       return (int)(cp-string);	/* return postion of character */
788   return -1;			/* eol ... no match found */
789 }
790 
791 /* routine to store a character in a string ... used only by "tokenizer" */
792 
chstore(char * string,int max,char ch)793 void chstore(char *string,int max,char ch)
794 {
795   char c;
796   if(_p_tokpos>=0&&_p_tokpos<max-1)
797     {
798       if(_p_state==IN_QUOTE)
799         c=ch;
800       else
801         switch(_p_flag&3)
802           {
803           case 1: 	    /* convert to upper */
804             c=toupper((int) ch);
805             break;
806 
807           case 2: 	    /* convert to lower */
808             c=tolower((int) ch);
809             break;
810 
811           default:	    /* use as is */
812             c=ch;
813             break;
814           }
815       string[_p_tokpos++]=c;
816     }
817   return;
818 }
819 
tokenizer(unsigned inflag,char * token,int tokmax,char * line,char * white,char * brkchar,char * quote,char eschar,char * brkused,int * next,char * quoted)820 int tokenizer(unsigned inflag,char *token,int tokmax,char *line,
821               char *white,char *brkchar,char *quote,char eschar,char *brkused,
822               int *next,char *quoted)
823 {
824   int qp;
825   char c,nc;
826 
827   *brkused=0;		/* initialize to null */
828   *quoted=0;		/* assume not quoted  */
829 
830   if(!line[*next])	/* if we're at end of line, indicate such */
831     return 1;
832 
833   _p_state=IN_WHITE;   /* initialize state */
834   _p_curquote=0;	   /* initialize previous quote char */
835   _p_flag=inflag;	   /* set option flag */
836 
837   for(_p_tokpos=0;(c=line[*next]);++(*next))	/* main loop */
838     {
839       if((qp=sindex(c,brkchar))>=0)  /* break */
840         {
841           switch(_p_state)
842             {
843 	    case IN_WHITE:		/* these are the same here ...	*/
844 	    case IN_TOKEN:		/* ... just get out		*/
845 	    case IN_OZONE:		/* ditto			*/
846 	      ++(*next);
847 	      *brkused=brkchar[qp];
848 	      goto byebye;
849 
850 	    case IN_QUOTE:		 /* just keep going */
851 	      chstore(token,tokmax,c);
852 	      break;
853             }
854         }
855       else if((qp=sindex(c,quote))>=0)  /* quote */
856         {
857           switch(_p_state)
858             {
859 	    case IN_WHITE:	 /* these are identical, */
860 	      _p_state=IN_QUOTE; /* change states   */
861 	      _p_curquote=quote[qp]; /* save quote char */
862 	      *quoted=1;	/* set to true as long as something is in quotes */
863 	      break;
864 
865 	    case IN_QUOTE:
866 	      if(quote[qp]==_p_curquote) /* same as the beginning quote? */
867                 {
868                   _p_state=IN_OZONE;
869                   _p_curquote=0;
870                 }
871 	      else
872 	        chstore(token,tokmax,c); /* treat as regular char */
873 	      break;
874 
875 	    case IN_TOKEN:
876 	    case IN_OZONE:
877 	      *brkused=c; /* uses quote as break char */
878 	      goto byebye;
879             }
880         }
881       else if((qp=sindex(c,white))>=0) /* white */
882         {
883           switch(_p_state)
884             {
885 	    case IN_WHITE:
886 	    case IN_OZONE:
887 	      break;		/* keep going */
888 
889 	    case IN_TOKEN:
890 	      _p_state=IN_OZONE;
891 	      break;
892 
893 	    case IN_QUOTE:
894 	      chstore(token,tokmax,c); /* it's valid here */
895 	      break;
896             }
897         }
898       else if(c==eschar)  /* escape */
899         {
900           nc=line[(*next)+1];
901           if(nc==0) 		/* end of line */
902             {
903               *brkused=0;
904               chstore(token,tokmax,c);
905               ++(*next);
906               goto byebye;
907             }
908           switch(_p_state)
909             {
910 	    case IN_WHITE:
911 	      --(*next);
912 	      _p_state=IN_TOKEN;
913 	      break;
914 
915 	    case IN_TOKEN:
916 	    case IN_QUOTE:
917 	      ++(*next);
918 	      chstore(token,tokmax,nc);
919 	      break;
920 
921 	    case IN_OZONE:
922 	      goto byebye;
923             }
924         }
925       else	/* anything else is just a real character */
926         {
927           switch(_p_state)
928             {
929 	    case IN_WHITE:
930 	      _p_state=IN_TOKEN; /* switch states */
931 
932 	    case IN_TOKEN:		 /* these 2 are     */
933 	    case IN_QUOTE:		 /*  identical here */
934 	      chstore(token,tokmax,c);
935 	      break;
936 
937 	    case IN_OZONE:
938 	      goto byebye;
939             }
940         }
941     }		/* end of main loop */
942 
943  byebye:
944   token[_p_tokpos]=0;	/* make sure token ends with EOS */
945 
946   return 0;
947 }
948 /*
949  * Local Variables:
950  * mode: c
951  * c-basic-offset: 2
952  * fill-column: 78
953  * End:
954  */
955