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