/* * This module provides a utility for processing a template file, * generating an output stream of the template contents with conditional * and/or iterative substitution from fields of a file. * * The template file marks the fields to be process by preceding the field * with a '%[' and closing with a ']'. * * int process_template_file ( fname, out, outctx, callback, cbctx ); * int process_template_string ( template, out, outctx, callback, cbctx ); * * Example: %[$readfile sys$disk:[dat]test ] * %[# comment ] * %[$LB] %[$RB] ! inserts %[ and ], respectively. * %[$if tag ] * %[$elif tag ] * %[$else ] * %[tag] * %[$escape ] ! redirects output to callback * * Revised: 14-MAY-1996 ! fixed bug in emit_bytes flush logic. */ #include #include #include #include #include #include "subaccess.h" #include "template.h" /* verify prototypes */ static int def_subfield_count = 0; /* default symbol list */ static struct subfield_context def_subfield; #define SRC_REC_SIZE 8192 #define SRC_REC_MAXFLD 64 struct field_context { int line_num; int escape_mode; int if_state; /* 0-false, 1-true */ int out_len; template_callback out, escape; void *out_arg, *escape_arg; struct subfield_context subfield; char outbuf[512]; /* scratch space */ }; static int parse_template ( char *template, int length, struct field_context *ctx ); /* forward reference */ static void init_context ( struct field_context *src_ctx, struct field_context *dst_ctx ) { dst_ctx->line_num = 0; dst_ctx->escape_mode = 0; dst_ctx->if_state = 0; dst_ctx->out_len = 0; dst_ctx->out = src_ctx->out; dst_ctx->out_arg = src_ctx->out_arg; dst_ctx->escape = src_ctx->escape; dst_ctx->escape_arg = src_ctx->escape_arg; dst_ctx->subfield.item_count = 0; /* don't inherits fields */ } static int emit_bytes ( char *buf, int buflen, struct field_context *ctx ) { int status, j; status = 1; #ifdef DEBUG printf("Emit %d bytes to %x, escape-mode: %d, current len: %d\n", buflen, ctx, ctx->escape_mode, ctx->out_len); if ( buflen > 0 ) printf(" buf: '%s'\n", buf ); #endif if ( buflen < 0 ) { if ( buf ) buflen = strlen ( buf ); else { /* flush remaining bytes */ if ( ctx->out_len <= 0 ) return 1; if ( ctx->escape_mode ) { status = (*ctx->escape)(ctx->escape_arg, ctx->outbuf, ctx->out_len ); } else { status = (*ctx->out)(ctx->out_arg, ctx->outbuf, ctx->out_len ); } ctx->out_len = 0; return status; } } while ( (status&1) == 1 ) { if ( buflen + ctx->out_len >= sizeof(ctx->outbuf) ) { /* copy and flush */ j = sizeof(ctx->outbuf) - ctx->out_len; memmove (&ctx->outbuf[ctx->out_len], buf, (size_t) j); if ( ctx->escape_mode ) { status = (*ctx->escape)(ctx->escape_arg, ctx->outbuf, sizeof(ctx->outbuf) ); } else { status = (*ctx->out)(ctx->out_arg, ctx->outbuf, sizeof(ctx->outbuf) ); } ctx->out_len = 0; buf = &buf[j]; buflen = buflen - j; } else { /* * All of bulen fit in outbuf, copy and exit. */ memmove (&ctx->outbuf[ctx->out_len], buf, (size_t) buflen); ctx->out_len += buflen; break; } } return status; } /*****************************************************************************/ /* Main routine for interpret the contents of a template field. */ static int process_field ( char *field, int fldlen, struct field_context *ctx ) { int i, j, k, key_num, status; unsigned int reclen; struct field_context subctx; char fname[256], *rec, *kn_str; /* * Parse the field. */ if ( *field != '$' && *field != '#' ) { /* * Assume field is name of subfield in current context to * sustitute. Lookup value and output. */ char *value; value = subfield_value ( &ctx->subfield, field ); if ( value ) { status = emit_bytes ( value, -1, ctx ); return status; } else { ctx->escape_mode = 0; emit_bytes ( "Unknwon field: ", -1, ctx ); emit_bytes ( field, fldlen, ctx ); emit_bytes ( "\n", 1, ctx ); return 0; } } else if ( *field == '#' ) { /* Comment, ignore */ } else if ( strncmp ( field, "$readfile ", 10 ) == 0 ) { /* * Open file. Parse out filename. */ for ( i = 10; isspace(field[i]); i++ ); for ( j = i+1; !isspace(field[j]); j++ ) if ( !field[j] ) { /* Missing 3rd field */ emit_bytes ( "Missing parameter on $readfile\n", -1, ctx ); return 0; } if ( (j - i) > 255 ) { /* Filename too long */ return 0; } strncpy ( fname, &field[i], j-i ); fname[j-i] = '\0'; key_num = 0; kn_str = strchr ( fname, '#' ); /* select alt. key # */ if ( kn_str ) { *kn_str++ = '\0'; sscanf ( kn_str, "%d", &key_num ); } /* * Open file. */ init_context ( ctx, &subctx ); status = subfield_open ( fname, "r", SRC_REC_SIZE, &subctx.subfield ); if ( (status&1) == 0 ) { emit_bytes ( "Error openning file '", -1, ctx ); emit_bytes ( fname, -1, ctx ); emit_bytes ( "'\n", 2, ctx ); return 0; } /* * If key_num is not primary, read first record by that key to * setup subsequent sequetial reads. */ if ( key_num > 0 ) { status = subfield_read ( &subctx.subfield, " ", key_num, 1); } /* * Make context for outputting. */ emit_bytes ( (char *) 0, -1, ctx ); /* * Read records of file. */ while ( (status=subfield_read(&subctx.subfield, "", key_num, 0))&1 ) { /* * Recursively process remainder. */ #ifdef DEBUG for(i=0;iescape_mode; emit_bytes ( (char *) 0, -1, ctx ); ctx->escape_mode = 1; parse_template ( &field[8], fldlen - 8, ctx ); emit_bytes ( (char *) 0, -1, ctx ); ctx->escape_mode = save_escape; } else if ( (strncmp ( field, "$if ", 4 ) == 0) || (strncmp ( field, "$elif ", 6 ) == 0) ) { /* * Conditional processing, else and elseif */ char save_delim, *value; for ( i = 3; !isspace(field[i]); i++ ); if ( (i == 5) && ctx->if_state ) return 1; /* skip */ for ( ; isspace(field[i]); i++ ); /* skip white */ if ( !field[i] ) { emit_bytes("Syntax error on $if\n",-1,ctx); return 0; } for ( j = i+1; !isspace(field[j]); j++ ); /* find end */ save_delim = field[j]; field[j] = '\0'; value = subfield_value ( &ctx->subfield, &field[i] ); field[j] = save_delim; if ( save_delim ) j++; if ( !value ) { emit_bytes ( "Unknown field in $if: ", -1, ctx ); emit_bytes ( &field[i], j-i, ctx ); emit_bytes ( "\n", 1, ctx ); } else if ( *value ) { ctx->if_state = 1; status = parse_template ( &field[j], fldlen - j, ctx ); } else { ctx->if_state = 0; } } else if ( strncmp ( field, "$else ", 6 ) == 0 ) { /* * Process else, only process if if-state false. */ if ( ctx->if_state ) return 1; /* skip */ status = parse_template ( &field[6], fldlen-6, ctx ); } else { emit_bytes ( "Unknown directive: ", -1, ctx ); emit_bytes ( field, fldlen, ctx ); return 0; } return 1; } /****************************************************************************/ /* * Scan string for %[field] constructs. */ static int parse_template ( char *template, int length, struct field_context *ctx ) { int i, state, field_start, bdepth; for ( state = i = 0; i < length; i++) switch ( state ) { case 0: /* Check for start */ if ( template[i] == '%' ) { if ( i+1 < length ) if ( template[i+1] == '[' ) { i++; /* skip the '%' */ state = 1; break; } } /* Add character to output */ if ( ctx->out_len < sizeof(ctx->outbuf) ) { ctx->outbuf[ctx->out_len++] = template[i]; } else { emit_bytes ( &template[i], 1, ctx ); } break; case 1: /* * Save position and scan for closing bracket. */ field_start = i; bdepth = 0; state = 2; case 2: if ( template[i] == ']' ) { /* * Check for embedded brackets. */ if ( --bdepth >= 0 ) break; /* * field closed, process it */ template[i] = '\0'; if ( !process_field ( &template[field_start], i - field_start, ctx ) ) length = 0; template[i] = ']'; state = 0; /* reset */ } else if ( template[i] == '[' ) bdepth++; break; } return 1; } /****************************************************************************/ /* Make global list of default template fields. */ int set_default_template_fields ( int count, char **name, char **value ) { int i; /* * Free current array and allocate new. */ if ( def_subfield_count > 0 ) free ( def_subfield.fld ); def_subfield.item_count = def_subfield_count = 0; if ( count > 0 ) { def_subfield.fld = (struct subfield_item *) malloc ( sizeof(struct subfield_item) * count ); if ( !def_subfield.fld ) return 0; def_subfield_count = def_subfield.item_count = count; } /* * Initialize list. Note we do not allocate separate storage for * the strings. */ for ( i = 0; i < count; i++ ) { def_subfield.fld[i].size = 0; def_subfield.fld[i].name = name[i]; def_subfield.fld[i].value = value[i]; } return 1; } /****************************************************************************/ /* Top-level routine for processing a template file. */ int process_template_file ( char *fname, template_callback out, void *out_arg, template_callback escape, void *escape_arg ) { FILE *tfile; char *template, *old_template; int length, status, i, t_used, t_alloc; /* * Read template file into memory. */ t_alloc = 8192; template = malloc ( t_alloc ); if ( ! template ) return SS$_INSFMEM; tfile = fopen ( fname, "r" ); if ( !tfile ) return 20; for ( t_used = 0; t_used >= 0; t_used += length ) { if ( t_alloc <= t_used ) { /* Expand template region */ old_template = template; t_alloc += 8192; template = realloc ( old_template, 8192 ); free ( old_template ); } length = fread ( &template[t_used], sizeof(char), t_alloc-t_used, tfile ); if ( length <= 0 ) break; } fclose ( tfile ); /* * Parse the template. */ status = process_template ( template, t_used, out, out_arg, escape, escape_arg ); free ( template ); return status; } int process_template ( char *template, int t_size, template_callback out, void *out_arg, template_callback escape, void *escape_arg ) { int length, status; struct field_context field_ctx; /* * initialize context for parse operation. */ field_ctx.out_len = 0; field_ctx.out = out; field_ctx.escape_mode = 0; field_ctx.out_arg = out_arg; field_ctx.escape = escape; field_ctx.escape_arg = escape_arg; if ( def_subfield_count > 0 ) field_ctx.subfield = def_subfield; else field_ctx.subfield.item_count = 0; /* * Parse the template. */ status = parse_template ( template, t_size, &field_ctx ); if ( (status&1) == 1 ) status = emit_bytes ( (char *) 0, -1, &field_ctx ); return status; }