d81a2cb259b612b596ba8414cfba5eb6d323c54e
[smdp.git] / src / parser.c
1 /*
2  * Functions necessary to parse a file and transform its content into
3  * a deck of slides containing lines. All based on markdown formating
4  * rules.
5  * Copyright (C) 2015 Michael Goehler
6  *
7  * This file is part of mdp.
8  *
9  * This program is free software: you can redistribute it and/or modify
10  * it under the terms of the GNU General Public License as published by
11  * the Free Software Foundation, either version 3 of the License, or
12  * (at your option) any later version.
13  *
14  * This program is distributed in the hope that it will be useful,
15  * but WITHOUT ANY WARRANTY; without even the implied warranty of
16  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17  * GNU General Public License for more details.
18  *
19  * You should have received a copy of the GNU General Public License
20  * along with this program. If not, see <http://www.gnu.org/licenses/>.
21  *
22  */
23
24 #include <ctype.h>
25 #include <errno.h>
26 #include <stdio.h>
27 #include <stdlib.h>
28 #include <wchar.h>
29 #include <wctype.h>
30 #include <string.h>
31
32 #include "parser.h"
33
34 deck_t *markdown_load(FILE *input) {
35
36     wchar_t c = L'\0';    // char
37     int i = 0;    // increment
38     int hc = 0;   // header count
39     int lc = 0;   // line count
40     int sc = 1;   // slide count
41     int bits = 0; // markdown bits
42     int prev = 0; // markdown bits of previous line
43
44     deck_t *deck = new_deck();
45     slide_t *slide = deck->slide;
46     line_t *line = NULL;
47     line_t *tmp = NULL;
48     cstring_t *text = cstring_init();
49
50     // initialize bits as empty line
51     SET_BIT(bits, IS_EMPTY);
52
53     while ((c = fgetwc(input)) != WEOF) {
54         if (ferror(input)) {
55             fprintf(stderr, "markdown_load() failed to read input: %s\n", strerror(errno));
56             exit(EXIT_FAILURE);
57         }
58
59         if(c == L'\n') {
60
61             // markdown analyse
62             prev = bits;
63             bits = markdown_analyse(text, prev);
64
65             // if first line in file is markdown hr
66             if(!line && CHECK_BIT(bits, IS_HR)) {
67
68                 // clear text
69                 (text->reset)(text);
70
71             } else if(line && CHECK_BIT(bits, IS_STOP)) {
72
73                 // set stop bit on last line
74                 SET_BIT(line->bits, IS_STOP);
75
76                 // clear text
77                 (text->reset)(text);
78
79             // if text is markdown hr
80             } else if(CHECK_BIT(bits, IS_HR) &&
81                       CHECK_BIT(line->bits, IS_EMPTY)) {
82
83                 slide->lines = lc;
84
85                 // clear text
86                 (text->reset)(text);
87
88                 // create next slide
89                 slide = next_slide(slide);
90                 sc++;
91
92             } else if(CHECK_BIT(bits, IS_TILDE_CODE) && CHECK_BIT(bits, IS_EMPTY)) {
93                 // remove tilde code markers
94                 (text->reset)(text);
95             } else {
96
97                 // if slide ! has line
98                 if(!slide->line || !line) {
99
100                     // create new line
101                     line = new_line();
102                     slide->line = line;
103                     lc = 1;
104
105                 } else {
106
107                     // create next line
108                     line = next_line(line);
109                     lc++;
110
111                 }
112
113                 // add text to line
114                 line->text = text;
115
116                 // add bits to line
117                 line->bits = bits;
118
119                 // calc offset
120                 line->offset = next_nonblank(text, 0);
121
122                 // adjust line length dynamicaly - excluding markup
123                 if(line->text->value)
124                     adjust_line_length(line);
125
126                 // new text
127                 text = cstring_init();
128             }
129
130         } else if(c == L'\t') {
131
132             // expand tab to spaces
133             for (i = 0;  i < EXPAND_TABS;  i++) {
134                 (text->expand)(text, L' ');
135             }
136
137         } else if(c == L'\\') {
138
139             // add char to line
140             (text->expand)(text, c);
141
142             // if !IS_CODE add next char to line
143             // and do not increase line count
144             if(next_nonblank(text, 0) < CODE_INDENT) {
145
146                 c = fgetwc(input);
147                 (text->expand)(text, c);
148             }
149
150         } else if(iswprint(c) || iswspace(c)) {
151
152             // add char to line
153             (text->expand)(text, c);
154         }
155     }
156     (text->delete)(text);
157
158     slide->lines = lc;
159     deck->slides = sc;
160
161     // detect header
162     line = deck->slide->line;
163     if(line && line->text->size > 0 && line->text->value[0] == L'%') {
164
165         // assign header to deck
166         deck->header = line;
167
168         // find first non-header line
169         while(line && line->text->size > 0 && line->text->value[0] == L'%') {
170             hc++;
171             line = line->next;
172         }
173
174         // only split header if any non-header line is found
175         if(line) {
176
177             // split linked list
178             line->prev->next = NULL;
179             line->prev = NULL;
180
181             // remove header lines from slide
182             deck->slide->line = line;
183
184             // adjust counts
185             deck->headers += hc;
186             deck->slide->lines -= hc;
187         } else {
188
189             // remove header from deck
190             deck->header = NULL;
191         }
192     }
193
194     slide = deck->slide;
195     while(slide) {
196         line = slide->line;
197
198         // ignore mdpress format attributes
199         if(line &&
200            slide->lines > 1 &&
201            !CHECK_BIT(line->bits, IS_EMPTY) &&
202            line->text->value[line->offset] == L'=' &&
203            line->text->value[line->offset + 1] == L' ') {
204
205             // remove line from linked list
206             slide->line = line->next;
207             line->next->prev = NULL;
208
209             // maintain loop condition
210             tmp = line;
211             line = line->next;
212
213             // adjust line count
214             slide->lines -= 1;
215
216             // delete line
217             (tmp->text->delete)(tmp->text);
218             free(tmp);
219         }
220
221         while(line) {
222             // combine underlined H1/H2 in single line
223             if((CHECK_BIT(line->bits, IS_H1) ||
224                 CHECK_BIT(line->bits, IS_H2)) &&
225                CHECK_BIT(line->bits, IS_EMPTY) &&
226                line->prev &&
227                !CHECK_BIT(line->prev->bits, IS_EMPTY)) {
228
229
230                 // remove line from linked list
231                 line->prev->next = line->next;
232                 if(line->next)
233                     line->next->prev = line->prev;
234
235                 // set bits on previous line
236                 if(CHECK_BIT(line->bits, IS_H1)) {
237                     SET_BIT(line->prev->bits, IS_H1);
238                 } else {
239                     SET_BIT(line->prev->bits, IS_H2);
240                 }
241
242                 // adjust line count
243                 slide->lines -= 1;
244
245                 // maintain loop condition
246                 tmp = line;
247                 line = line->prev;
248
249                 // delete line
250                 (tmp->text->delete)(tmp->text);
251                 free(tmp);
252
253             // pass enclosing flag IS_UNORDERED_LIST_3
254             // to nested levels for unordered lists
255             } else if(CHECK_BIT(line->bits, IS_UNORDERED_LIST_3)) {
256                 tmp = line->next;
257                 line_t *list_last_level_3 = line;
258
259                 while(tmp &&
260                       CHECK_BIT(tmp->bits, IS_UNORDERED_LIST_3)) {
261                     if(CHECK_BIT(tmp->bits, IS_UNORDERED_LIST_3)) {
262                         list_last_level_3 = tmp;
263                     }
264                     tmp = tmp->next;
265                 }
266
267                 for(tmp = line; tmp != list_last_level_3; tmp = tmp->next) {
268                     SET_BIT(tmp->bits, IS_UNORDERED_LIST_3);
269                 }
270
271             // pass enclosing flag IS_UNORDERED_LIST_2
272             // to nested levels for unordered lists
273             } else if(CHECK_BIT(line->bits, IS_UNORDERED_LIST_2)) {
274                 tmp = line->next;
275                 line_t *list_last_level_2 = line;
276
277                 while(tmp &&
278                       (CHECK_BIT(tmp->bits, IS_UNORDERED_LIST_2) ||
279                        CHECK_BIT(tmp->bits, IS_UNORDERED_LIST_3))) {
280                     if(CHECK_BIT(tmp->bits, IS_UNORDERED_LIST_2)) {
281                         list_last_level_2 = tmp;
282                     }
283                     tmp = tmp->next;
284                 }
285
286                 for(tmp = line; tmp != list_last_level_2; tmp = tmp->next) {
287                     SET_BIT(tmp->bits, IS_UNORDERED_LIST_2);
288                 }
289
290             // pass enclosing flag IS_UNORDERED_LIST_1
291             // to nested levels for unordered lists
292             } else if(CHECK_BIT(line->bits, IS_UNORDERED_LIST_1)) {
293                 tmp = line->next;
294                 line_t *list_last_level_1 = line;
295
296                 while(tmp &&
297                       (CHECK_BIT(tmp->bits, IS_UNORDERED_LIST_1) ||
298                        CHECK_BIT(tmp->bits, IS_UNORDERED_LIST_2) ||
299                        CHECK_BIT(tmp->bits, IS_UNORDERED_LIST_3))) {
300                     if(CHECK_BIT(tmp->bits, IS_UNORDERED_LIST_1)) {
301                         list_last_level_1 = tmp;
302                     }
303                     tmp = tmp->next;
304                 }
305
306                 for(tmp = line; tmp != list_last_level_1; tmp = tmp->next) {
307                     SET_BIT(tmp->bits, IS_UNORDERED_LIST_1);
308                 }
309             }
310
311             line = line->next;
312         }
313         slide = slide->next;
314     }
315
316     return deck;
317 }
318
319 int markdown_analyse(cstring_t *text, int prev) {
320
321     // static variables can not be redeclaired, but changed outside of a declaration
322     // the program remembers their value on every function calls
323     static int unordered_list_level = 0;
324     static int unordered_list_level_offset[] = {-1, -1, -1, -1};
325     static int num_tilde_characters = 0;
326
327     int i = 0;      // increment
328     int bits = 0;   // markdown bits
329     int offset = 0; // text offset
330     int eol    = 0; // end of line
331
332     int equals = 0, hashes = 0,
333         stars  = 0, minus  = 0,
334         spaces = 0, other  = 0; // special character counts
335
336     const int unordered_list_offset = unordered_list_level_offset[unordered_list_level];
337
338     // return IS_EMPTY on null pointers
339     if(!text || !text->value) {
340         SET_BIT(bits, IS_EMPTY);
341         return bits;
342     }
343
344     // count leading spaces
345     offset = next_nonblank(text, 0);
346
347     // IS_TILDE_CODE
348     if (wcsncmp(text->value, L"~~~", 3) == 0) {
349             int tildes_in_line = next_nontilde(text, 0);
350             if (tildes_in_line >= num_tilde_characters) {
351                     if (num_tilde_characters > 0) {
352                             num_tilde_characters = 0;
353                     } else {
354                             num_tilde_characters = tildes_in_line;
355                     }
356                     SET_BIT(bits, IS_EMPTY);
357                     SET_BIT(bits, IS_TILDE_CODE);
358                     return bits;
359             }
360     }
361
362     if (num_tilde_characters > 0) {
363             SET_BIT(bits, IS_CODE);
364             SET_BIT(bits, IS_TILDE_CODE);
365             return bits;
366     }
367
368     // IS_STOP
369     if((offset < CODE_INDENT || !CHECK_BIT(prev, IS_CODE)) &&
370        (!wcsncmp(&text->value[offset], L"<br>", 4) ||
371         !wcsncmp(&text->value[offset], L"<BR>", 4) ||
372         !wcsncmp(&text->value[offset], L"^", 1))) {
373         SET_BIT(bits, IS_STOP);
374         return bits;
375     }
376
377     // strip trailing spaces
378     for(eol = text->size; eol > offset && iswspace(text->value[eol - 1]); eol--);
379
380     // IS_UNORDERED_LIST_#
381     if(text->size >= offset + 2 &&
382        (text->value[offset] == L'*' || text->value[offset] == L'-') &&
383        iswspace(text->value[offset + 1])) {
384
385         // if different from last lines offset
386         if(offset != unordered_list_offset) {
387
388             // test if offset matches a lower indent level
389             for(i = unordered_list_level; i >= 0; i--) {
390                 if(unordered_list_level_offset[i] == offset) {
391                     unordered_list_level = i;
392                     break;
393                 }
394             }
395             // if offset doesn't match any previously stored indent level
396             if(i != unordered_list_level) {
397                 unordered_list_level = MIN(unordered_list_level + 1, UNORDERED_LIST_MAX_LEVEL);
398                 // memorize the offset as next bigger indent level
399                 unordered_list_level_offset[unordered_list_level] = offset;
400             }
401         }
402
403         // if no previous indent level matches, this must be the first line of the list
404         if(unordered_list_level == 0) {
405             unordered_list_level = 1;
406             unordered_list_level_offset[1] = offset;
407         }
408
409         switch(unordered_list_level) {
410             case 1: SET_BIT(bits, IS_UNORDERED_LIST_1); break;
411             case 2: SET_BIT(bits, IS_UNORDERED_LIST_2); break;
412             case 3: SET_BIT(bits, IS_UNORDERED_LIST_3); break;
413             default: break;
414         }
415     }
416
417     if(!CHECK_BIT(bits, IS_UNORDERED_LIST_1) &&
418        !CHECK_BIT(bits, IS_UNORDERED_LIST_2) &&
419        !CHECK_BIT(bits, IS_UNORDERED_LIST_3)) {
420
421         // continue list if indent level is still the same as in previous line
422         if ((CHECK_BIT(prev, IS_UNORDERED_LIST_1) ||
423              CHECK_BIT(prev, IS_UNORDERED_LIST_2) ||
424              CHECK_BIT(prev, IS_UNORDERED_LIST_3)) &&
425             offset >= unordered_list_offset) {
426
427             switch(unordered_list_level) {
428                 case 1: SET_BIT(bits, IS_UNORDERED_LIST_1); break;
429                 case 2: SET_BIT(bits, IS_UNORDERED_LIST_2); break;
430                 case 3: SET_BIT(bits, IS_UNORDERED_LIST_3); break;
431                 default: break;
432             }
433
434             // this line extends the previous list item
435             SET_BIT(bits, IS_UNORDERED_LIST_EXT);
436
437         // or reset indent level
438         } else {
439             unordered_list_level = 0;
440         }
441     }
442
443     if(!CHECK_BIT(bits, IS_UNORDERED_LIST_1) &&
444        !CHECK_BIT(bits, IS_UNORDERED_LIST_2) &&
445        !CHECK_BIT(bits, IS_UNORDERED_LIST_3)) {
446
447         // IS_CODE
448         if(offset >= CODE_INDENT &&
449            (CHECK_BIT(prev, IS_EMPTY) ||
450             CHECK_BIT(prev, IS_CODE)  ||
451             CHECK_BIT(prev, IS_STOP))) {
452             SET_BIT(bits, IS_CODE);
453
454         } else {
455
456             // IS_QUOTE
457             if(text->value[offset] == L'>') {
458                 SET_BIT(bits, IS_QUOTE);
459             }
460
461             // IS_CENTER
462             if(text->size >= offset + 3 &&
463                text->value[offset] == L'-' &&
464                text->value[offset + 1] == L'>' &&
465                iswspace(text->value[offset + 2])) {
466                 SET_BIT(bits, IS_CENTER);
467
468                 // remove start tag
469                 (text->strip)(text, offset, 3);
470                 eol -= 3;
471
472                 if(text->size >= offset + 3 &&
473                    text->value[eol - 1] == L'-' &&
474                    text->value[eol - 2] == L'<' &&
475                    iswspace(text->value[eol - 3])) {
476
477                     // remove end tags
478                     (text->strip)(text, eol - 3, 3);
479
480                     // adjust end of line
481                     for(eol = text->size; eol > offset && iswspace(text->value[eol - 1]); eol--);
482
483                 }
484             }
485
486             for(i = offset; i < eol; i++) {
487
488                 if(iswspace(text->value[i])) {
489                     spaces++;
490
491                 } else {
492                     switch(text->value[i]) {
493                         case L'=': equals++;  break;
494                         case L'#': hashes++;  break;
495                         case L'*': stars++;   break;
496                         case L'-': minus++;   break;
497                         case L'\\': other++; i++; break;
498                         default:  other++;   break;
499                     }
500                 }
501             }
502
503             // IS_H1
504             if(equals > 0 &&
505                hashes + stars + minus + spaces + other == 0) {
506                 SET_BIT(bits, IS_H1);
507             }
508             if(text->value[offset] == L'#' &&
509                iswspace(text->value[offset+1])) {
510                 SET_BIT(bits, IS_H1);
511                 SET_BIT(bits, IS_H1_ATX);
512             }
513
514             // IS_H2
515             if(minus > 0 &&
516                equals + hashes + stars + spaces + other == 0) {
517                 SET_BIT(bits, IS_H2);
518             }
519             if(text->value[offset] == L'#' &&
520                text->value[offset+1] == L'#' &&
521                iswspace(text->value[offset+2])) {
522                 SET_BIT(bits, IS_H2);
523                 SET_BIT(bits, IS_H2_ATX);
524             }
525
526             // IS_HR
527             if((minus >= 3 && equals + hashes + stars + other == 0) ||
528                (stars >= 3 && equals + hashes + minus + other == 0)) {
529
530                 SET_BIT(bits, IS_HR);
531             }
532
533             // IS_EMPTY
534             if(other == 0) {
535                 SET_BIT(bits, IS_EMPTY);
536             }
537         }
538     }
539
540     return bits;
541 }
542
543 void markdown_debug(deck_t *deck, int debug) {
544
545     int sc = 0; // slide count
546     int lc = 0; // line count
547
548     int offset;
549     line_t *header;
550
551     if(debug == 1) {
552         fwprintf(stderr, L"headers: %i\nslides: %i\n", deck->headers, deck->slides);
553
554     } else if(debug > 1) {
555
556         // print header to STDERR
557         if(deck->header) {
558             header = deck->header;
559             while(header &&
560                 header->length > 0 &&
561                 header->text->value[0] == L'%') {
562
563                 // skip descriptor word (e.g. %title:)
564                 offset = next_blank(header->text, 0) + 1;
565
566                 fwprintf(stderr, L"header: %S\n", &header->text->value[offset]);
567                 header = header->next;
568             }
569         }
570     }
571
572     slide_t *slide = deck->slide;
573     line_t *line;
574
575     // print slide/line count to STDERR
576     while(slide) {
577         sc++;
578
579         if(debug == 1) {
580             fwprintf(stderr, L"  slide %i: %i lines\n", sc, slide->lines);
581
582         } else if(debug > 1) {
583
584             // also print bits and line length
585             fwprintf(stderr, L"  slide %i:\n", sc);
586             line = slide->line;
587             lc = 0;
588             while(line) {
589                 lc++;
590                 fwprintf(stderr, L"    line %i: bits = %i, length = %i\n", lc, line->bits, line->length);
591                 line = line->next;
592             }
593         }
594
595         slide = slide->next;
596     }
597 }
598
599 void adjust_line_length(line_t *line) {
600     int l = 0;
601     const static wchar_t *special = L"\\*_`"; // list of interpreted chars
602     const wchar_t *c = &line->text->value[line->offset];
603     cstack_t *stack = cstack_init();
604
605     // for each char in line
606     for(; *c; c++) {
607         // if char is in special char list
608         if(wcschr(special, *c)) {
609
610             // closing special char (or second backslash)
611             if((stack->top)(stack, *c)) {
612                 if(*c == L'\\') l++;
613                 (stack->pop)(stack);
614
615             // treat special as regular char
616             } else if((stack->top)(stack, L'\\')) {
617                 l++;
618                 (stack->pop)(stack);
619
620             // opening special char
621             } else {
622                 (stack->push)(stack, *c);
623             }
624
625         } else {
626             // remove backslash from stack
627             if((stack->top)(stack, L'\\'))
628                 (stack->pop)(stack);
629             l++;
630         }
631     }
632
633     if(CHECK_BIT(line->bits, IS_H1_ATX))
634         l -= 2;
635     if(CHECK_BIT(line->bits, IS_H2_ATX))
636         l -= 3;
637
638     line->length = l;
639
640     (stack->delete)(stack);
641 }
642
643 int next_nonblank(cstring_t *text, int i) {
644     while ((i < text->size) && iswspace((text->value)[i]))
645         i++;
646
647     return i;
648 }
649
650
651 int prev_blank(cstring_t *text, int i) {
652     while ((i > 0) && !iswspace((text->value)[i]))
653         i--;
654
655     return i;
656 }
657
658 int next_blank(cstring_t *text, int i) {
659     while ((i < text->size) && !iswspace((text->value)[i]))
660         i++;
661
662     return i;
663 }
664
665 int next_word(cstring_t *text, int i) {
666     return next_nonblank(text, next_blank(text, i));
667 }
668
669 int next_nontilde(cstring_t *text, int i) {
670     while ((i < text->size) && text->value[i] == L'~')
671         i++;
672
673     return i;
674 }
675