ctxt: introduce an escape character to prevent unwanted markups
[ctxt.git] / fmt.c
blobadbbe69ecbfc069f3a68aee5bb4a501ef9db25ca
1 #include <ctype.h>
2 #include <stdio.h>
3 #include <stdlib.h>
4 #include <string.h>
5 #include "ctxt.h"
7 #define MAXLINE 1024
8 #define LENGTH(vars) (sizeof(vars) / sizeof(vars[0]))
10 static char *markers[] = {"**", "$$", "%%", "[]", "||", "//", "''", "``"};
11 struct block {
12 char *beg;
13 char *end;
14 int txt; /* process inline markups in the block */
15 } blocks[] = {
16 {".#1", ".#2", 1}, {".!1", ".!2"},
17 {".TS", ".TE", 1}, {".DS", ".DE", 1}, {".G1", ".G2"},
18 {".EQ", ".EN"}, {".PS", ".PE"}, {".[", ".]"}, {".de", ".."},
21 struct fmt {
22 struct doc *doc;
23 struct txt *txt;
24 struct fmt_ops *ops;
25 int level;
26 int esc;
29 static struct fmt *fmt_alloc(struct doc *doc, struct txt *txt,
30 struct fmt_ops *ops, int esc)
32 struct fmt *fmt = xmalloc(sizeof(struct fmt));
33 fmt->doc = doc;
34 fmt->txt = txt;
35 fmt->ops = ops;
36 fmt->level = 0;
37 fmt->esc = esc;
38 return fmt;
41 static void fmt_free(struct fmt *fmt)
43 free(fmt);
46 static char *fmt_line(struct fmt *fmt, int line)
48 char *s = txt_line(fmt->txt, line);
49 int i;
50 for (i = 0; i < fmt->level && s && *s; i++)
51 s++;
52 return s;
55 static char *marker_char(char c, char p, char n)
57 int i;
58 if (p == '\\' || (p && isalnum(p)) || isspace(n))
59 return NULL;
60 for (i = 0; i < LENGTH(markers); i++)
61 if (markers[i][0] == c)
62 return markers[i];
63 return NULL;
66 static char *fillbuf(char *beg, char *end)
68 static char buf[MAXLINE];
69 memcpy(buf, beg, end - beg);
70 buf[end - beg] = '\0';
71 return buf;
74 static void put_text(struct fmt *fmt, char *s)
76 char *_s = s;
77 char *o = s;
78 while (*s) {
79 char *r, *m;
80 if (s[1] && s[0] == fmt->esc) {
81 fmt->ops->put(fmt->doc, fillbuf(o, s));
82 fmt->ops->put(fmt->doc, fillbuf(s + 1, s + 2));
83 o = s + 2;
84 s = s + 2;
85 continue;
87 m = marker_char(*s, s == _s ? 0 : s[-1], s[1]);
88 r = m ? strchr(s + 1, m[1]) : NULL;
89 if (m && r) {
90 fmt->ops->put(fmt->doc, fillbuf(o, s));
91 fmt->ops->put_txt(fmt->doc, fillbuf(s + 1, r), m);
92 o = r + 1;
93 s = r + 1;
94 continue;
96 s++;
98 fmt->ops->put(fmt->doc, o);
101 static void raw_line(struct fmt *fmt, char *s)
103 fmt->ops->put(fmt->doc, s);
106 static void put_line(struct fmt *fmt, int n)
108 put_text(fmt, fmt_line(fmt, n));
109 put_text(fmt, "\n");
112 static void put_lines(struct fmt *fmt, int beg, int end)
114 int i;
115 for (i = beg; i < end; i++)
116 put_line(fmt, i);
119 static void raw_lines(struct fmt *fmt, int beg, int end)
121 int i;
122 for (i = beg; i < end; i++) {
123 raw_line(fmt, fmt_line(fmt, i));
124 raw_line(fmt, "\n");
128 static int indents(char *s)
130 char *r = s;
131 while (isspace(*r))
132 r++;
133 return r - s;
136 static int islist(char *first, char *line)
138 char *signs = "*-+";
139 if (!line) {
140 if (!first || strlen(first) < 2)
141 return 0;
142 if (first[1] != ' ' || !strchr(signs, first[0]))
143 return 0;
144 return 1;
146 if (strlen(line) < 2)
147 return 0;
148 return line[0] == first[0] && line[1] == first[1];
151 static void fmt_handle(struct fmt *fmt, int beg, int end, int level);
153 static int fmt_head(struct fmt *fmt, int beg, int end)
155 char *line = fmt_line(fmt, beg);
156 char *next = fmt_line(fmt, beg + 1);
157 char c;
158 char *s = next;
159 char *signs = "=-~";
160 if (!next || !*line || beg == end || fmt->level)
161 return 0;
162 c = *next;
163 if (!c || !strchr(signs, *next))
164 return 0;
165 while (*++s)
166 if (*s != c)
167 return 0;
168 fmt->ops->head_beg(fmt->doc, strchr(signs, *next) - signs);
169 put_text(fmt, line);
170 fmt->ops->head_end(fmt->doc, strchr(signs, *next) - signs);
171 return 2;
174 static int cmd_line(struct fmt *fmt, int i)
176 return !fmt->level && *fmt_line(fmt, i) == '.';
179 static int par_end(struct fmt *fmt, int beg, int end)
181 int i = beg;
182 char *line;
183 while (i < end && (line = fmt_line(fmt, i))) {
184 if (!*line || indents(line) || islist(line, NULL) || cmd_line(fmt, i))
185 break;
186 i++;
188 return i;
191 static int fmt_par(struct fmt *fmt, int beg, int end)
193 int i;
194 if (indents(fmt_line(fmt, beg)) || cmd_line(fmt, beg))
195 return 0;
196 fmt->ops->par_beg(fmt->doc);
197 i = par_end(fmt, beg, end);
198 put_lines(fmt, beg, i);
199 fmt->ops->par_end(fmt->doc);
200 return i - beg;
203 static int fmt_rawline(struct fmt *fmt, int beg, int end)
205 if (*fmt_line(fmt, beg) != '.')
206 return 0;
207 put_lines(fmt, beg, beg + 1);
208 return 1;
211 static int min(int a, int b)
213 return a < b ? a : b;
216 static int fmt_deindent(struct fmt *fmt, int n, int indent)
218 char *line;
219 int result = n;
220 while ((line = fmt_line(fmt, n))) {
221 if (*line && indents(line) < indent)
222 break;
223 n++;
224 if (*line)
225 result = n;
227 return result;
230 static int fmt_pre(struct fmt *fmt, int beg, int end)
232 int level = indents(fmt_line(fmt, beg));
233 int next = min(end, fmt_deindent(fmt, beg + 1, level));
234 fmt->level += level;
235 fmt->ops->block_beg(fmt->doc, NULL);
236 raw_lines(fmt, beg, next);
237 fmt->ops->block_end(fmt->doc, NULL);
238 fmt->level -= level;
239 return next - beg;
242 static int fmt_block(struct fmt *fmt, int beg, int end)
244 struct block *blk = NULL;
245 int next;
246 char *line;
247 int depth = 1;
248 int i;
249 line = fmt_line(fmt, beg);
250 if (indents(line))
251 return fmt_pre(fmt, beg, end);
252 for (i = 0; i < LENGTH(blocks); i++)
253 if (!strncmp(blocks[i].beg, line, strlen(blocks[i].beg)))
254 blk = &blocks[i];
255 if (!blk)
256 return 0;
257 next = beg + 1;
258 while (next < end && depth > 0) {
259 line = fmt_line(fmt, next++);
260 if (!strncmp(blk->beg, line, strlen(blk->beg)))
261 depth++;
262 if (!strncmp(blk->end, line, strlen(blk->end)))
263 depth--;
265 fmt->ops->block_beg(fmt->doc, fmt_line(fmt, beg));
266 if (beg + 1 < next) {
267 if (blk->txt)
268 put_lines(fmt, beg + 1, next - 1);
269 else
270 raw_lines(fmt, beg + 1, next - 1);
272 fmt->ops->block_end(fmt->doc, fmt_line(fmt, next - 1));
273 return next - beg;
276 static char *listhead(char *s, char *head)
278 if (s[0] != '+')
279 return NULL;
280 s += 2;
281 while (*s && *s != ':')
282 *head++ = *s++;
283 if (*s == ':')
284 s++;
285 while (isspace(*s))
286 s++;
287 *head = '\0';
288 return s;
291 static int fmt_list(struct fmt *fmt, int beg, int end)
293 int i = beg;
294 char *first = fmt_line(fmt, i);
295 char *line = first;
296 if (!islist(first, NULL))
297 return 0;
298 fmt->ops->list_beg(fmt->doc, line[0]);
299 while ((line = fmt_line(fmt, i)) && islist(first, line)) {
300 int next = min(end, fmt_deindent(fmt, i + 1, 2));
301 int head = i;
302 char lhead[MAXLINE];
303 if (line[0] == '+') {
304 line = listhead(line, lhead);
305 fmt->ops->item_beg(fmt->doc, lhead);
306 put_text(fmt, line);
307 put_text(fmt, "\n");
308 head++;
309 } else {
310 fmt->ops->item_beg(fmt->doc, NULL);
313 fmt->level += 2;
314 i = par_end(fmt, i, next);
315 put_lines(fmt, head, i);
316 fmt->level -= 2;
318 fmt_handle(fmt, i, next, 2);
319 i = next;
320 fmt->ops->item_end(fmt->doc);
321 if (i == end)
322 break;
324 fmt->ops->list_end(fmt->doc);
325 return i - beg;
328 /* .T1/.T2 tables */
329 static int fmt_table(struct fmt *fmt, int beg, int end)
331 char *hdr = fmt_line(fmt, beg);
332 int i;
333 int row = 0, col = 0;
334 if (hdr[0] != '.' || hdr[1] != 'T' || hdr[2] != '1')
335 return 0;
336 fmt->ops->table_beg(fmt->doc, 0);
337 for (i = beg + 1; i < end; i++) {
338 char *cur = fmt_line(fmt, i);
339 if (cur[0] == '.' && cur[1] == 'T' && cur[2] == '2')
340 break;
341 if (cur[0] == '%') {
342 raw_line(fmt, cur + 2);
343 raw_line(fmt, "\n");
344 continue;
346 if (strchr("_=\n", cur[0])) {
347 if (col)
348 fmt->ops->entry_end(fmt->doc);
349 if (row)
350 fmt->ops->row_end(fmt->doc);
351 if (cur[0] != '\n') {
352 raw_line(fmt, cur);
353 raw_line(fmt, "\n");
355 col = 0;
356 continue;
358 if (strchr("#*-+", cur[0])) {
359 if (!col) {
360 fmt->ops->row_beg(fmt->doc);
361 row++;
362 } else {
363 fmt->ops->entry_end(fmt->doc);
365 fmt->ops->entry_beg(fmt->doc);
366 col++;
368 fmt->level += 2;
369 put_line(fmt, i);
370 fmt->level -= 2;
372 if (col)
373 fmt->ops->entry_end(fmt->doc);
374 if (row)
375 fmt->ops->row_end(fmt->doc);
376 fmt->ops->table_end(fmt->doc);
377 return i - beg + 1;
380 /* simple tables */
382 static int table_columns(char *line)
384 int n;
385 for (n = 0; *line; n++) {
386 while (*line == '\t')
387 line++;
388 while (*line && *line != '\t')
389 line++;
391 return n;
394 static void table_row(struct fmt *fmt, char *s)
396 if (*s == '=' || *s == '-')
397 return;
398 fmt->ops->row_beg(fmt->doc);
399 while (*s) {
400 char *r = s;
401 while (*s && *s != '\t')
402 s++;
403 fmt->ops->entry_beg(fmt->doc);
404 put_text(fmt, fillbuf(r, s));
405 fmt->ops->entry_end(fmt->doc);
406 while (*s == '\t')
407 s++;
409 fmt->ops->row_end(fmt->doc);
412 static int fmt_tableascii(struct fmt *fmt, int beg, int end)
414 int i;
415 int n;
416 if (*fmt_line(fmt, beg) != '=')
417 return 0;
418 n = table_columns(fmt_line(fmt, beg + 1));
419 fmt->ops->table_beg(fmt->doc, n);
420 for (i = beg + 1; i < end; i++) {
421 if (!fmt->level && !*fmt_line(fmt, i))
422 break;
423 table_row(fmt, fmt_line(fmt, i));
425 fmt->ops->table_end(fmt->doc);
426 return i - beg;
429 /* parsing text */
431 static int (*parts[])(struct fmt *fmt, int beg, int end) = {
432 fmt_head, fmt_list, fmt_table, fmt_tableascii,
433 fmt_block, fmt_rawline
436 static void fmt_handle(struct fmt *fmt, int beg, int end, int level)
438 int line = beg;
439 int i;
440 fmt->level += level;
441 while (line < end) {
442 int c = 0;
443 if (!*fmt_line(fmt, line)) {
444 put_line(fmt, line++);
445 continue;
447 for (i = 0; i < LENGTH(parts); i++)
448 if ((c = parts[i](fmt, line, end)))
449 break;
450 /* start a paragraph only after a blank line */
451 if (!c && (line == beg || !*fmt_line(fmt, line - 1)))
452 c = fmt_par(fmt, line, end);
453 line += c;
454 if (!c)
455 put_line(fmt, line++);
457 fmt->level -= level;
460 void format(struct doc *doc, struct txt *txt, struct fmt_ops *ops, int esc)
462 struct fmt *fmt = fmt_alloc(doc, txt, ops, esc);
463 fmt->ops->doc_beg(doc);
464 fmt_handle(fmt, 0, txt->n, 0);
465 fmt->ops->doc_end(doc);
466 fmt_free(fmt);