blob: 02f4a46b2eb25f96fac70cf3e942c618bce14b06 [file] [log] [blame]
/*
* $Id$
*
* Copyright © 2003 Keith Packard
*
* Permission to use, copy, modify, distribute, and sell this software and its
* documentation for any purpose is hereby granted without fee, provided that
* the above copyright notice appear in all copies and that both that
* copyright notice and this permission notice appear in supporting
* documentation, and that the name of Keith Packard not be used in
* advertising or publicity pertaining to distribution of the software without
* specific, written prior permission. Keith Packard makes no
* representations about the suitability of this software for any purpose. It
* is provided "as is" without express or implied warranty.
*
* KEITH PACKARD DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE,
* INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO
* EVENT SHALL KEITH PACKARD BE LIABLE FOR ANY SPECIAL, INDIRECT OR
* CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE,
* DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
* TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
* PERFORMANCE OF THIS SOFTWARE.
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
static void *
New (int size);
static void *
Reallocate (void *p, int size);
static void
Dispose (void *p);
typedef enum { False, True } Bool;
typedef struct {
char *buf;
int size;
int len;
} String;
static String *
StringNew (void);
static void
StringAdd (String *s, char c);
static void
StringAddString (String *s, char *buf);
static String *
StringMake (char *buf);
static void
StringDel (String *s);
static void
StringPut (FILE *f, String *s);
static void
StringDispose (String *s);
typedef struct {
String *tag;
String *text;
} Replace;
static Replace *
ReplaceNew (void);
static void
ReplaceDispose (Replace *r);
static void
Bail (const char *format, int line, const char *arg);
static Replace *
ReplaceRead (FILE *f, int *linep);
typedef struct _replaceList {
struct _replaceList *next;
Replace *r;
} ReplaceList;
static ReplaceList *
ReplaceListNew (Replace *r, ReplaceList *next);
static void
ReplaceListDispose (ReplaceList *l);
typedef struct {
ReplaceList *head;
} ReplaceSet;
static ReplaceSet *
ReplaceSetNew (void);
static void
ReplaceSetDispose (ReplaceSet *s);
static void
ReplaceSetAdd (ReplaceSet *s, Replace *r);
static Replace *
ReplaceSetFind (ReplaceSet *s, char *tag);
static ReplaceSet *
ReplaceSetRead (FILE *f, int *linep);
typedef struct _skipStack {
struct _skipStack *prev;
int skipping;
} SkipStack;
static SkipStack *
SkipStackPush (SkipStack *prev, int skipping);
static SkipStack *
SkipStackPop (SkipStack *prev);
typedef struct _loopStack {
struct _loopStack *prev;
String *tag;
String *extra;
long pos;
} LoopStack;
static LoopStack *
LoopStackPush (LoopStack *prev, FILE *f, char *tag);
static LoopStack *
LoopStackLoop (ReplaceSet *rs, LoopStack *ls, FILE *f);
static void
LineSkip (FILE *f, int *linep);
static void
DoReplace (FILE *f, int *linep, ReplaceSet *s);
#define STRING_INIT 128
static void *
New (int size)
{
void *m = malloc (size);
if (!m)
abort ();
return m;
}
static void *
Reallocate (void *p, int size)
{
void *r = realloc (p, size);
if (!r)
abort ();
return r;
}
static void
Dispose (void *p)
{
free (p);
}
static String *
StringNew (void)
{
String *s;
s = New (sizeof (String));
s->buf = New (STRING_INIT);
s->size = STRING_INIT - 1;
s->buf[0] = '\0';
s->len = 0;
return s;
}
static void
StringAdd (String *s, char c)
{
if (s->len == s->size)
s->buf = Reallocate (s->buf, (s->size *= 2) + 1);
s->buf[s->len++] = c;
s->buf[s->len] = '\0';
}
static void
StringAddString (String *s, char *buf)
{
while (*buf)
StringAdd (s, *buf++);
}
static String *
StringMake (char *buf)
{
String *s = StringNew ();
StringAddString (s, buf);
return s;
}
static void
StringDel (String *s)
{
if (s->len)
s->buf[--s->len] = '\0';
}
static void
StringPut (FILE *f, String *s)
{
char *b = s->buf;
while (*b)
putc (*b++, f);
}
#define StringLast(s) ((s)->len ? (s)->buf[(s)->len - 1] : '\0')
static void
StringDispose (String *s)
{
Dispose (s->buf);
Dispose (s);
}
static Replace *
ReplaceNew (void)
{
Replace *r = New (sizeof (Replace));
r->tag = StringNew ();
r->text = StringNew ();
return r;
}
static void
ReplaceDispose (Replace *r)
{
StringDispose (r->tag);
StringDispose (r->text);
Dispose (r);
}
static void
Bail (const char *format, int line, const char *arg)
{
fprintf (stderr, "fatal: ");
fprintf (stderr, format, line, arg);
fprintf (stderr, "\n");
exit (1);
}
static int
Getc (FILE *f, int *linep)
{
int c = getc (f);
if (c == '\n')
++(*linep);
return c;
}
static void
Ungetc (int c, FILE *f, int *linep)
{
if (c == '\n')
--(*linep);
ungetc (c, f);
}
static Replace *
ReplaceRead (FILE *f, int *linep)
{
int c;
Replace *r;
while ((c = Getc (f, linep)) != '@')
{
if (c == EOF)
return 0;
}
r = ReplaceNew();
while ((c = Getc (f, linep)) != '@')
{
if (c == EOF)
{
ReplaceDispose (r);
return 0;
}
if (isspace (c))
Bail ("%d: invalid character after tag %s", *linep, r->tag->buf);
StringAdd (r->tag, c);
}
if (r->tag->buf[0] == '\0')
{
ReplaceDispose (r);
return 0;
}
while (isspace ((c = Getc (f, linep))))
;
Ungetc (c, f, linep);
while ((c = Getc (f, linep)) != '@' && c != EOF)
StringAdd (r->text, c);
if (c == '@')
Ungetc (c, f, linep);
while (isspace (StringLast (r->text)))
StringDel (r->text);
if (StringLast(r->text) == '%')
{
StringDel (r->text);
StringAdd (r->text, ' ');
}
return r;
}
static ReplaceList *
ReplaceListNew (Replace *r, ReplaceList *next)
{
ReplaceList *l = New (sizeof (ReplaceList));
l->r = r;
l->next = next;
return l;
}
static void
ReplaceListDispose (ReplaceList *l)
{
if (l)
{
ReplaceListDispose (l->next);
ReplaceDispose (l->r);
Dispose (l);
}
}
static ReplaceSet *
ReplaceSetNew (void)
{
ReplaceSet *s = New (sizeof (ReplaceSet));
s->head = 0;
return s;
}
static void
ReplaceSetDispose (ReplaceSet *s)
{
ReplaceListDispose (s->head);
Dispose (s);
}
static void
ReplaceSetAdd (ReplaceSet *s, Replace *r)
{
s->head = ReplaceListNew (r, s->head);
}
static Replace *
ReplaceSetFind (ReplaceSet *s, char *tag)
{
ReplaceList *l;
for (l = s->head; l; l = l->next)
if (!strcmp (tag, l->r->tag->buf))
return l->r;
return 0;
}
static ReplaceSet *
ReplaceSetRead (FILE *f, int *linep)
{
ReplaceSet *s = ReplaceSetNew ();
Replace *r;
while ((r = ReplaceRead (f, linep)))
{
while (ReplaceSetFind (s, r->tag->buf))
StringAdd (r->tag, '+');
ReplaceSetAdd (s, r);
}
if (!s->head)
{
ReplaceSetDispose (s);
s = 0;
}
return s;
}
static SkipStack *
SkipStackPush (SkipStack *prev, int skipping)
{
SkipStack *ss = New (sizeof (SkipStack));
ss->prev = prev;
ss->skipping = skipping;
return ss;
}
static SkipStack *
SkipStackPop (SkipStack *prev)
{
SkipStack *ss = prev->prev;
Dispose (prev);
return ss;
}
static LoopStack *
LoopStackPush (LoopStack *prev, FILE *f, char *tag)
{
LoopStack *ls = New (sizeof (LoopStack));
ls->prev = prev;
ls->tag = StringMake (tag);
ls->extra = StringNew ();
ls->pos = ftell (f);
return ls;
}
static LoopStack *
LoopStackLoop (ReplaceSet *rs, LoopStack *ls, FILE *f)
{
String *s = StringMake (ls->tag->buf);
LoopStack *ret = ls;
Bool loop;
StringAdd (ls->extra, '+');
StringAddString (s, ls->extra->buf);
loop = ReplaceSetFind (rs, s->buf) != 0;
StringDispose (s);
if (loop)
fseek (f, ls->pos, SEEK_SET);
else
{
ret = ls->prev;
StringDispose (ls->tag);
StringDispose (ls->extra);
Dispose (ls);
}
return ret;
}
static void
LineSkip (FILE *f, int *linep)
{
int c;
while ((c = Getc (f, linep)) == '\n')
;
Ungetc (c, f, linep);
}
static void
DoReplace (FILE *f, int *linep, ReplaceSet *s)
{
int c;
String *tag;
Replace *r;
SkipStack *ss = 0;
LoopStack *ls = 0;
int skipping = 0;
while ((c = Getc (f, linep)) != EOF)
{
if (c == '@')
{
tag = StringNew ();
while ((c = Getc (f, linep)) != '@')
{
if (c == EOF)
abort ();
StringAdd (tag, c);
}
if (ls)
StringAddString (tag, ls->extra->buf);
switch (tag->buf[0]) {
case '?':
ss = SkipStackPush (ss, skipping);
if (!ReplaceSetFind (s, tag->buf + 1))
skipping++;
LineSkip (f, linep);
break;
case ':':
if (!ss)
abort ();
if (ss->skipping == skipping)
++skipping;
else
--skipping;
LineSkip (f, linep);
break;
case ';':
skipping = ss->skipping;
ss = SkipStackPop (ss);
LineSkip (f, linep);
break;
case '{':
ls = LoopStackPush (ls, f, tag->buf + 1);
LineSkip (f, linep);
break;
case '}':
ls = LoopStackLoop (s, ls, f);
LineSkip (f, linep);
break;
default:
r = ReplaceSetFind (s, tag->buf);
if (r && !skipping)
StringPut (stdout, r->text);
break;
}
StringDispose (tag);
}
else if (!skipping)
putchar (c);
}
}
int
main (int argc, char **argv)
{
FILE *f;
ReplaceSet *s;
int iline, oline;
if (!argv[1])
Bail ("usage: %*s <template.sgml>", 0, argv[0]);
f = fopen (argv[1], "r");
if (!f)
{
Bail ("can't open file %s", 0, argv[1]);
exit (1);
}
iline = 1;
while ((s = ReplaceSetRead (stdin, &iline)))
{
oline = 1;
DoReplace (f, &oline, s);
ReplaceSetDispose (s);
rewind (f);
}
if (ferror (stdout))
Bail ("%s", 0, "error writing output");
exit (0);
}