1 #include "tfilter.h"
2
3 #include "textout.h"
4 #include <string.h>
5
6 /*
7 * This is the general output engine for text. It allows the various commands
8 * to be stored as strings, with substitions from the arguments handled
9 * as necessary. In addition, it allows two models of font handling:
10 * grouped (e.g., {\tt ...} or <TT> ... </TT>) and ungrouped (e.g., \n.I ...).
11 * It also handles newline interpretation, ensuring that the "correct"
12 * number of newlines is generated.
13 *
14 * A command consists of a string with some special characters. These are:
15 * %% just a %
16 * %1 Insert string s1
17 * %2 Insert string s2 etc
18 * %i Insert integer i1 (but see note on definitions)
19 * %n Insert newline ONLY if not AT a newline.
20 * %N=".." Replace newlines with this string (%N revert to original behavior)
21 * Max of 32 characters.
22 * %p Insert end_par ONLY if not already present. Requires end_par to
23 * be defined.
24 * %f Insert end-font-string, if any (not an error if none defined)
25 * %e="..." Define end-font-string. Max of 32 characters
26 * %u1, %u2, etc. Just like %1, but upper case the string.
27 * %r1, %r2, etc. The values of registers defined with %a or SetOutRegister
28 * %rq1, etc. is like %r1, but the output is quoted (so that filters further
29 * downstream do not try to process the data)
30 * %a1="..." define register 1. Ditto for a2, etc.
31 * %m="..." Define mode. This is used for text systems that have special
32 * processing modes, such as verbatim in LaTeX, where character processing
33 * is changed.
34 *
35 * Others as needed
36 * (We might want general registers to be filled or emptied, or some
37 * way to indicated "paragraph only if you need to"). We need to be careful;
38 * we could re-invent PostScript/Nroff/TeX/HTML/Fortran :).
39 *
40 * Strings out written with self if possible (see OutString)
41 */
42
43 /* This the structure that holds the information about a command -
44 Not used for now */
45 typedef struct {
46 char *cmdstring;
47 } CmdInfo;
48
49 /* Names are matched as
50 cmdname-i1 (if i1 != TEXT_I_IGNORE)
51 then
52 cmdname
53 */
MatchCommand(const char * cmdname,int i1,SrEntry ** entry)54 int TextOut::MatchCommand( const char *cmdname, int i1, SrEntry **entry )
55 {
56 if (i1 != TEXT_I_IGNORE) {
57 char cmdbuf[256];
58 sprintf( cmdbuf, "%s-%d", cmdname, i1 );
59 if (!userops->Lookup( cmdbuf, entry )) return 0;
60 return userops->Lookup( cmdname, entry );
61 }
62 else {
63 return userops->Lookup( cmdname, entry );
64 }
65 }
66
HasOp(const char * cmdname)67 int TextOut::HasOp( const char *cmdname )
68 {
69 SrEntry *entry;
70
71 PutChar( 0 );
72 if (!userops && next)
73 return next->HasOp( cmdname );
74 return MatchCommand( cmdname, TEXT_I_IGNORE, &entry );
75 }
76
77 //
78 // All of the PutOp commands use PutChar( 0 ) to flush any local buffers
79 //
PutOp(const char * cmdname,char * s1,char * s2,int i1)80 int TextOut::PutOp( const char *cmdname, char *s1, char *s2, int i1 )
81 {
82 SrEntry *entry;
83
84 PutChar( 0 );
85 if (!userops && next)
86 return next->PutOp( cmdname, s1, s2, i1 );
87 if (MatchCommand( cmdname, i1, &entry )) {
88 /* Did not find, so do nothing */
89 return PutOpError( cmdname, (char *)0 );
90 }
91 else {
92 PutCommand( (char *)entry->extra_data, s1, s2, (char *)0, (char *)0,
93 i1 );
94 }
95 return 0;
96 }
97
PutOp(const char * cmdname,char * s1,char * s2,char * s3,char * s4)98 int TextOut::PutOp( const char *cmdname,
99 char *s1, char *s2, char *s3, char *s4 )
100 {
101 SrEntry *entry;
102
103 PutChar( 0 );
104 if (!userops && next)
105 return next->PutOp( cmdname, s1, s2, s3, s4 );
106 if (userops->Lookup( cmdname, &entry )) {
107 /* Did not find, so do nothing */
108 return PutOpError( cmdname, (char *)0 );
109 }
110 else {
111 PutCommand( (char *)entry->extra_data, s1, s2, s3, s4, 0 );
112 }
113 return 0;
114 }
115
PutOp(const char * cmdname,char * s1)116 int TextOut::PutOp( const char *cmdname, char *s1 )
117 {
118 SrEntry *entry;
119
120 PutChar( 0 );
121 if (!userops && next)
122 return next->PutOp( cmdname, s1 );
123 if (userops->Lookup( cmdname, &entry )) {
124 /* Did not find, so do nothing */
125 return PutOpError( cmdname, (char *)0 );
126 }
127 else {
128 PutCommand( (char *)entry->extra_data, s1, (char *)0, (char *)0,
129 (char *)0, 0 );
130 }
131 return 0;
132 }
133
PutOp(const char * cmdname)134 int TextOut::PutOp( const char *cmdname )
135 {
136 SrEntry *entry;
137
138 PutChar( 0 );
139 if (!userops && next)
140 return next->PutOp( cmdname );
141 if (userops->Lookup( cmdname, &entry )) {
142 /* Did not find, so do nothing */
143 return PutOpError( cmdname, (char *)0 );
144 }
145 else {
146 PutCommand( (char *)entry->extra_data, (char *)0, (char *)0,
147 (char *)0, (char *)0, 0 );
148 }
149 return 0;
150 }
151
PutOp(const char * cmdname,char * s1,int i1)152 int TextOut::PutOp( const char *cmdname, char *s1, int i1 )
153 {
154 SrEntry *entry;
155
156 PutChar( 0 );
157 if (!userops && next)
158 return next->PutOp( cmdname, s1, i1 );
159 if (MatchCommand( cmdname, i1, &entry )) {
160 /* Did not find, so do nothing */
161 return PutOpError( cmdname, (char *)0 );
162 }
163 else {
164 PutCommand( (char *)entry->extra_data, s1, (char *)0, (char *)0,
165 (char *)0, i1 );
166 }
167 return 0;
168 }
169
170 /* Default error behavior for PutOp command not found.
171 */
PutOpError(const char * cmdname,const char * msg)172 int TextOut::PutOpError( const char *cmdname, const char *msg )
173 {
174 if (msg) fprintf( stderr, "%s\n", msg);
175 else
176 fprintf( stderr, "Did not find definition for command %s\n", cmdname );
177 return 1;
178 }
179
SetDebug(int flag)180 int TextOut::SetDebug( int flag )
181 {
182 debug_flag = flag;
183 return 0;
184 }
185
SetOutstream(OutStream * in_outs)186 int TextOut::SetOutstream( OutStream *in_outs )
187 {
188 out = in_outs;
189 if (debug_flag) out->Debug(debug_flag);
190 if (next) next->SetOutstream( in_outs );
191 return 0;
192 }
193
Flush()194 int TextOut::Flush( )
195 {
196 return 0;
197 }
~TextOut()198 TextOut::~TextOut()
199 {
200 }
201
202 #include <ctype.h>
203
GetDQtext(char * cmdstring,char * out_str,int maxlen)204 char * TextOut::GetDQtext( char *cmdstring, char *out_str, int maxlen )
205 {
206 /* Skip the =, if any */
207 *out_str = 0;
208 if (*cmdstring == '=') cmdstring++;
209 if (*cmdstring++ != '"') return 0;
210 while (maxlen && *cmdstring != '"') {
211 *out_str++ = *cmdstring++;
212 maxlen--;
213 }
214 *out_str = 0;
215 if (*cmdstring == '"') cmdstring++;
216 return cmdstring;
217 }
218
219 /*
220 * Since we want to allow optional processing of new lines, we have a special
221 * output routine
222 */
223
OutString(const char * string)224 int TextOut::OutString( const char *string )
225 {
226 char ch;
227 int rc = 0;
228
229 if (!string) return 0;
230 if (next) {
231 if (debug_flag) printf( "TextOut::Outstring to next TextOut\n" );
232 return next->OutString( string );
233 }
234
235 if (debug_flag)
236 printf( "TextOut::Outstring to related Outstream %s\n", out->MyName() );
237 #ifdef FOO
238 if (!nl)
239 rc = out->PutToken( 0, string );
240 else {
241 while (!rc && (ch = *string++)) {
242 if (ch == '\n')
243 rc = out->PutQuoted( 0, nl );
244 else
245 rc = out->PutChar( ch );
246 }
247 }
248 #else
249 if (!nl)
250 rc = PutToken( 0, string );
251 else {
252 while (!rc && (ch = *string++)) {
253 if (ch == '\n')
254 rc = PutQuoted( 0, nl );
255 else
256 rc = PutChar( ch );
257 }
258 }
259 #endif
260 if (rc) return err->ErrMsg( rc, "Error writing character" );
261 else return 0;
262 }
263
264 /* Upper-case version of OutString */
OutStringUC(const char * string)265 int TextOut::OutStringUC( const char *string )
266 {
267 char ch;
268 int rc = 0;
269
270 if (next)
271 return next->OutStringUC( string );
272 if (!nl) {
273 while (*string) {
274 rc = out->PutChar( toupper( *string ) );
275 string++;
276 }
277 }
278 else {
279 while (!rc && (ch = *string++)) {
280 if (ch == '\n')
281 rc = out->PutQuoted( 0, nl );
282 else
283 rc = out->PutChar( toupper( ch ) );
284 }
285 }
286 if (rc) return err->ErrMsg( rc, "Error writing character" );
287 else return 0;
288 }
289
290 /*
291 * This COULD use varargs or arg matching
292 */
PutCommand(char * cmdstring,char * s1,char * s2,char * s3,char * s4,int i1)293 int TextOut::PutCommand( char *cmdstring,
294 char *s1, char *s2, char *s3, char *s4, int i1 )
295 {
296 char ch, ch2;
297 int l_was_par;
298 int l_was_nl;
299
300 if (!cmdstring) return 1;
301
302 if (debug_flag)
303 printf( "Processing command string %s\n", cmdstring );
304 /* Interpret and replace string */
305 while ((ch = *cmdstring++)) {
306 if (ch == '%') {
307 ch2 = *cmdstring++;
308 if (debug_flag) printf( "Processing command %%%c\n", ch2 );
309 l_was_par = last_was_par;
310 last_was_par = 0;
311 l_was_nl = last_was_nl;
312 last_was_nl = 0;
313 switch (ch2) {
314 int regnum, r_quoted;
315 case '%': out->PutChar( ch ); break;
316 case '1': OutString( s1 ); break;
317 case '2': OutString( s2 ); break;
318 case '3': OutString( s3 ); break;
319 case '4': OutString( s4 ); break;
320 case 'a':
321 /* Get the register number and string value */
322 ch2 = *cmdstring++;
323 regnum = ch2 - '0';
324 if (regnum < 0 || regnum >= MAX_TEXT_REGNUM) return 1;
325 cmdstring = GetDQtext( cmdstring, cmdreg[regnum],
326 MAX_TEXT_REGISTER );
327 //printf( "stng=%s\n", cmdreg[regnum] );
328 /* if cmdstring == 0, an error was found */
329 if (!cmdstring) return 1;
330
331 break;
332 case 'r':
333 /* Get the register number and output the value in that
334 register */
335 ch2 = *cmdstring++;
336 r_quoted = (ch2 == 'q');
337 if (r_quoted) ch2 = *cmdstring++;
338 //if (debug_flag & r_quoted)
339 // printf( "quoted output\n" );
340 regnum = ch2 - '0';
341 if (regnum < 0 || regnum >= MAX_TEXT_REGNUM) return 1;
342 //printf( "stng_out%d=%s\n", regnum, cmdreg[regnum] );
343 if (r_quoted)
344 out->PutQuoted( 0, cmdreg[regnum] );
345 else
346 out->PutToken( 0, cmdreg[regnum] );
347 break;
348
349 case 'e':
350 /* Get replacement string; (="...") set lfont */
351 cmdstring = GetDQtext( cmdstring, font_str, 32 );
352 /* if cmdstring == 0, an error was found */
353 if (!cmdstring) return 1;
354 lfont = font_str;
355 break;
356 case 'f': if (lfont) { out->PutToken( 0, lfont ); }
357 lfont = 0;
358 break;
359 case 'i': out->PutInt( i1 ); break;
360 case 'm':
361 /* Get mode string; (="...") set mode */
362 cmdstring = GetDQtext( cmdstring, mode_str, 32 );
363 /* if cmdstring == 0, an error was found */
364 if (!cmdstring) return 1;
365 // An empty mode_str give a null mode (simplifies binary
366 // modes)
367 if (*mode_str)
368 mode = mode_str;
369 else
370 mode = 0;
371 break;
372 case 'n': if (!l_was_nl) {
373 out->PutToken( 0, newline_onoutput );
374 UpdateNL( 1 );
375 }
376 break;
377 case 'N':
378 if (*cmdstring == '=') {
379 cmdstring = GetDQtext( cmdstring, newline_str, 32 );
380 if (!cmdstring) return 1;
381 nl = newline_str;
382 }
383 else
384 nl = 0;
385 break;
386
387 case 'p':
388 if (l_was_par == 0)
389 PutOp( "end_par" );
390 last_was_par = 1;
391 break;
392 case 'u':
393 ch2 = *cmdstring++;
394 switch (ch2) {
395 case '1': OutStringUC( s1 ); break;
396 case '2': OutStringUC( s2 ); break;
397 case '3': OutStringUC( s3 ); break;
398 case '4': OutStringUC( s4 ); break;
399 }
400 break;
401 default: out->PutChar( '%' );
402 out->PutChar( ch2 );
403 break;
404 }
405 }
406 else {
407 if (nl && ch == '\n') {
408 out->PutToken( 0, nl );
409 }
410 else {
411 out->PutChar( ch );
412 }
413 if (ch)
414 UpdateNL( ch == '\n' );
415 last_was_par = 0;
416 }
417 }
418 return 0;
419 }
420
421 /* These are the default versions. */
PutChar(const char ch)422 int TextOut::PutChar( const char ch )
423 {
424 if (debug_flag && ch) printf( "Generic TextOut::PutChar of %c\n", ch );
425 /* This really needs to remember newlines in last_was_nl */
426 if (ch)
427 UpdateNL( ch == '\n' );
428 if (next) return next->PutChar( ch );
429 else return out->PutChar( ch );
430 }
431
PutToken(int nsp,const char * token)432 int TextOut::PutToken( int nsp, const char *token )
433 {
434 /* This really needs to remember newlines in last_was_nl */
435 if (token && token[0])
436 UpdateNL( token[strlen(token)-1] == '\n' );
437 if (next) return next->PutToken( nsp, token );
438 else return out->PutToken( nsp, token );
439 }
440
PutTokenRaw(int nsp,const char * token)441 int TextOut::PutTokenRaw( int nsp, const char *token )
442 {
443 /* This really needs to remember newlines in last_was_nl */
444 return PutToken(nsp, token);
445 }
446
PutQuoted(int nsp,const char * token)447 int TextOut::PutQuoted( int nsp, const char *token )
448 {
449 if (debug_flag && token && *token)
450 printf( "Generic puttoken for %s\n", token );
451 if (token && *token) UpdateNL( 0 );
452 if (next) return next->PutQuoted( nsp, token );
453 else return out->PutQuoted( nsp, token );
454 }
455
PutNewline()456 int TextOut::PutNewline( )
457 {
458 UpdateNL( 1 );
459 if (next) return next->PutNewline();
460 else {
461 char *p = newline_onoutput;
462 int rc;
463 while (*p) if ((rc = out->PutChar( *p++ ))) break;
464 return rc;
465 }
466 }
SetNewlineString(const char * str)467 void TextOut::SetNewlineString( const char *str )
468 {
469 strncpy( newline_onoutput, str, 3 );
470 newline_onoutput[2] = 0;
471 }
472
SetMode(const char * str)473 const char *TextOut::SetMode( const char * str )
474 {
475 const char * savemode = mode;
476 mode = str;
477 // printf( "Setting mode to %s\n", str ? str : "NULL" );
478 return (const char *)savemode;
479 }
480
SetRegisterValue(int regnum,const char * val)481 int TextOut::SetRegisterValue( int regnum, const char * val )
482 {
483 //printf( "reg val %d is %s\n", regnum, val );
484 if (strlen(val) >= MAX_TEXT_REGISTER) return 1;
485 strncpy( cmdreg[regnum], val, MAX_TEXT_REGISTER-1 );
486 return 0;
487 }
488
489 //
490 // This is used to propagate newline changes down the list
491 // Do we need up as well?
492 //
UpdateNL(int in_last_was_nl)493 int TextOut::UpdateNL( int in_last_was_nl )
494 {
495 if (next) next->UpdateNL( in_last_was_nl );
496 last_was_nl = in_last_was_nl;
497 return 0;
498 }
499
500 /*
501 The format of the command file is
502 command-name command-string
503 # is used for comments, and \ is used (at the end of the line ONLY) to
504 continue to the next line.
505
506 Note that the user can define ANY commands; they are added to the table.
507
508 Note that command-names can have the form
509 foo-3
510 This will match a command name of foo and an integer value of 3. This
511 is a simple way to specify different behavior for different values
512 of an input integer (e.g., heading level or itemize depth).
513 */
514 #define MAX_NAME 128
515 #define MAX_COMMAND 1024
ReadCommands(InStream * ins)516 int TextOut::ReadCommands( InStream *ins ){
517 char ch;
518 char nametoken[MAX_NAME], *namep;
519 char *p, command[MAX_COMMAND], *pold;
520 int ln, nsp, maxlen;
521 SrEntry *entry;
522 int prepend, postpend; // indicate whether the command is added
523 // or replaces existing command.
524
525 ins->SetBreakChar( '-', BREAK_ALPHA );
526 ins->SetBreakChar( '_', BREAK_ALPHA );
527 ins->SetBreakChar( '+', BREAK_ALPHA );
528 while (!ins->GetToken( MAX_NAME, nametoken, &nsp )) {
529 /* Skip comments */
530 if (nametoken[0] == '#') {
531 ins->SkipLine();
532 continue;
533 }
534 // Check for prepend/postpend markers
535 prepend = 0;
536 postpend = 0;
537 namep = nametoken;
538 if (namep[0] == '+') {
539 prepend = 1;
540 namep++;
541 }
542 ln = strlen( nametoken );
543 if (nametoken[ln-1] == '+') {
544 postpend = 1;
545 nametoken[ln-1] = 0;
546 }
547 if (prepend && postpend) {
548 // Should issue error message
549 postpend = 0;
550 }
551
552 /* Skip the leading space (upto newline) */
553 while (!ins->GetChar( &ch ) && isspace( ch ) && ch != '\n')
554 ;
555 ins->UngetChar( ch );
556
557 // Get command string for name
558 p = command;
559 maxlen = MAX_COMMAND - 1;
560 // Allow \<space> and \<newline> as special characters.
561 while (!ins->GetChar( &ch ) && maxlen > 0) {
562 if (ch == '\n') break;
563 if (ch == '\\') {
564 ins->GetChar( &ch );
565 if (ch == '\n')
566 continue;
567 else if (ch != ' ') {
568 *p++ = '\\';
569 maxlen--;
570 }
571 }
572 *p++ = ch;
573 maxlen--;
574 }
575 if (ch != '\n' && maxlen == 0) {
576 // command too long
577 return 1;
578 }
579 *p = 0;
580 // Add to definitions.
581 // namep is nametoken, with optional + stripped off.
582 if (!userops->Insert( namep, &entry )) {
583 /* Install the definition of this command in the table */
584 /* Handle the case of prepending or postpending a definition */
585 if (entry->extra_data) {
586 pold = (char *)entry->extra_data;
587 if (prepend) {
588 p = new char[strlen(command) + strlen(pold) + 1];
589 if (!p) return 1;
590 strcpy( p, command );
591 strcat( p, pold );
592 }
593 else if (postpend) {
594 p = new char[strlen(command) + strlen(pold) + 1];
595 if (!p) return 1;
596 strcpy( p, pold );
597 strcat( p, command );
598 }
599 else {
600 p = new char[strlen(command)+1];
601 if (!p) return 1;
602 strcpy( p, command );
603 }
604 delete (char*)entry->extra_data;
605 }
606 else {
607 p = new char[strlen(command)+1];
608 if (!p) return 1;
609 strcpy( p, command );
610 }
611 entry->extra_data = (void *)p;
612 }
613 }
614 return 0;
615 }
616
Debug(int flag)617 int TextOut::Debug( int flag )
618 {
619 int old_flag = debug_flag;
620 debug_flag = flag;
621 return old_flag;
622 }
623
624 /*
625 It is sometimes useful to define an output stream that is really a textout
626 object.
627 */
TextOutStrm(OutStream * outs)628 TextOutStrm::TextOutStrm( OutStream * outs )
629 {
630 out = outs;
631 next = 0;
632
633 lfont = 0;
634 nl = 0;
635 last_was_nl = 0;
636 last_was_par = 0;
637 debug_flag = 0;
638 userops = 0;
639 strcpy( newline_onoutput, "\n" );
640 }
~TextOutStrm()641 TextOutStrm::~TextOutStrm()
642 {
643 if (out) delete out;
644 out = 0;
645 }
646