cleanup and small change of list spacing in #38 + shortened sample
[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) 2014 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 <stdio.h>
26 #include <stdlib.h>
27
28 #include "parser.h"
29
30 deck_t *markdown_load(FILE *input) {
31
32     int c = 0;    // char
33     int i = 0;    // increment
34     int l = 0;    // line length
35     int hc = 0;   // header count
36     int lc = 0;   // line count
37     int sc = 0;   // slide count
38     int bits = 0; // markdown bits
39
40     deck_t *deck = new_deck();
41     slide_t *slide = new_slide();
42     line_t *line = NULL;
43     line_t *tmp = NULL;
44     cstring_t *text = cstring_init();
45
46     // assign first slide to deck
47     deck->slide = slide;
48     sc++;
49
50     while ((c = fgetc(input)) != EOF) {
51         if(c == '\n') {
52
53             // markdown analyse
54             bits = markdown_analyse(text);
55
56             // if first line in file is markdown hr
57             if(!line && CHECK_BIT(bits, IS_HR)) {
58
59                 // clear text
60                 (text->reset)(text);
61
62             // if text is markdown hr
63             } else if(CHECK_BIT(bits, IS_HR) &&
64                       CHECK_BIT(line->bits, IS_EMPTY)) {
65
66                 slide->lines = lc;
67
68                 // clear text
69                 (text->reset)(text);
70                 l = 0;
71
72                 // create next slide
73                 slide = next_slide(slide);
74                 sc++;
75
76             } else {
77
78                 // if slide ! has line
79                 if(!slide->line) {
80
81                     // create new line
82                     line = new_line();
83                     slide->line = line;
84                     lc = 1;
85
86                 } else {
87
88                     // create next line
89                     line = next_line(line);
90                     lc++;
91
92                 }
93
94                 // add text to line
95                 line->text = text;
96
97                 // add bits to line
98                 line->bits = bits;
99
100                 // add length to line
101                 line->length = l;
102
103                 // calc offset
104                 line->offset = next_nonblank(text, 0);
105
106                 // new text
107                 text = cstring_init();
108                 l = 0;
109             }
110
111         } else if(c == '\t') {
112
113             // expand tab to spaces
114             for (i = 0;  i < EXPAND_TABS;  i++) {
115                 (text->expand)(text, ' ');
116                 l++;
117             }
118
119         } else if(c == '\\') {
120
121             // add char to line
122             (text->expand)(text, c);
123             l++;
124
125             // if !IS_CODE add next char to line
126             // and do not increase line count
127             if(next_nonblank(text, 0) < CODE_INDENT) {
128
129                 c = fgetc(input);
130                 (text->expand)(text, c);
131
132                 if(is_utf8(c)) {
133
134                     // if utf-8 char > 1 byte add remaing to line
135                     for(i = 0; i < length_utf8(c) - 1; i++) {
136                         c = fgetc(input);
137                         (text->expand)(text, c);
138                     }
139                 }
140
141             }
142
143         } else if(isprint(c) || isspace((unsigned char) c)) {
144
145             // add char to line
146             (text->expand)(text, c);
147             l++;
148
149         } else if(is_utf8(c)) {
150
151             // add char to line
152             (text->expand)(text, c);
153
154             // if utf-8 char > 1 byte add remaing to line
155             for(i = 0; i < length_utf8(c) - 1; i++) {
156                 c = fgetc(input);
157                 (text->expand)(text, c);
158             }
159
160             l++;
161         }
162     }
163
164     slide->lines = lc;
165     deck->slides = sc;
166
167     // detect header
168     line = deck->slide->line;
169     if(line && line->text->size > 0 && line->text->text[0] == '%') {
170
171         // assign header to deck
172         deck->header = line;
173
174         // find first non-header line
175         while(line->text->size > 0 && line->text->text[0] == '%') {
176             hc++;
177             line = line->next;
178         }
179
180         // split linked list
181         line->prev->next = (void*)0;
182         line->prev = (void*)0;
183
184         // remove header lines from slide
185         deck->slide->line = line;
186
187         // adjust counts
188         deck->headers += hc;
189         deck->slide->lines -= hc;
190     }
191
192     slide = deck->slide;
193     while(slide) {
194         line = slide->line;
195         while(line) {
196             if((CHECK_BIT(line->bits, IS_H1) ||
197                 CHECK_BIT(line->bits, IS_H2)) &&
198                CHECK_BIT(line->bits, IS_EMPTY) &&
199                line->prev &&
200                !CHECK_BIT(line->prev->bits, IS_EMPTY)) {
201                 // combine underlined H1/H2 in single line
202
203                 // remove line from linked list
204                 line->prev->next = line->next;
205                 if(line->next)
206                     line->next->prev = line->prev;
207
208                 // set bits on previous line
209                 if(CHECK_BIT(line->bits, IS_H1)) {
210                     SET_BIT(line->prev->bits, IS_H1);
211                 } else {
212                     SET_BIT(line->prev->bits, IS_H2);
213                 }
214
215                 // adjust line count
216                 slide->lines -= 1;
217
218                 // maintain loop condition
219                 tmp = line;
220                 line = line->prev;
221
222                 // delete line
223                 (tmp->text->delete)(tmp->text);
224                 free(tmp);
225             } else if(CHECK_BIT(line->bits, IS_UNORDERED_LIST_3)) {
226                 tmp = line->next;
227                 line_t *list_last_level_3 = line;
228
229                 while(tmp &&
230                       CHECK_BIT(tmp->bits, IS_UNORDERED_LIST_3)) {
231                     if(CHECK_BIT(tmp->bits, IS_UNORDERED_LIST_3)) {
232                         list_last_level_3 = tmp;
233                     }
234                     tmp = tmp->next;
235                 }
236
237                 for(tmp = line; tmp != list_last_level_3; tmp = tmp->next) {
238                     SET_BIT(tmp->bits, IS_UNORDERED_LIST_3);
239                 }
240             } else if(CHECK_BIT(line->bits, IS_UNORDERED_LIST_2)) {
241                 tmp = line->next;
242                 line_t *list_last_level_2 = line;
243
244                 while(tmp &&
245                       (CHECK_BIT(tmp->bits, IS_UNORDERED_LIST_2) ||
246                        CHECK_BIT(tmp->bits, IS_UNORDERED_LIST_3))) {
247                     if(CHECK_BIT(tmp->bits, IS_UNORDERED_LIST_2)) {
248                         list_last_level_2 = tmp;
249                     }
250                     tmp = tmp->next;
251                 }
252
253                 for(tmp = line; tmp != list_last_level_2; tmp = tmp->next) {
254                     SET_BIT(tmp->bits, IS_UNORDERED_LIST_2);
255                 }
256             } else if(CHECK_BIT(line->bits, IS_UNORDERED_LIST_1)) {
257                 tmp = line->next;
258                 line_t *list_last_level_1 = line;
259
260                 while(tmp &&
261                       (CHECK_BIT(tmp->bits, IS_UNORDERED_LIST_1) ||
262                        CHECK_BIT(tmp->bits, IS_UNORDERED_LIST_2) ||
263                        CHECK_BIT(tmp->bits, IS_UNORDERED_LIST_3))) {
264                     if(CHECK_BIT(tmp->bits, IS_UNORDERED_LIST_1)) {
265                         list_last_level_1 = tmp;
266                     }
267                     tmp = tmp->next;
268                 }
269                 
270                 for(tmp = line; tmp != list_last_level_1; tmp = tmp->next) {
271                     SET_BIT(tmp->bits, IS_UNORDERED_LIST_1);
272                 }
273             }
274
275             line = line->next;
276         }
277         slide = slide->next;
278     }
279
280     return deck;
281 }
282
283 int markdown_analyse(cstring_t *text) {
284
285     static int unordered_list_level = 0;
286     static int unordered_list_level_offset[] = {-1, -1, -1, -1};
287
288     int i = 0;      // increment
289     int bits = 0;   // markdown bits
290     int offset = 0; // text offset
291     int eol    = 0; // end of line
292
293     int equals = 0, hashes = 0,
294         stars  = 0, minus  = 0,
295         spaces = 0, other  = 0; // special character counts
296
297     const int unordered_list_offset = unordered_list_level_offset[unordered_list_level];
298
299     // count leading spaces
300     offset = next_nonblank(text, 0);
301
302     // strip trailing spaces
303     for(eol = text->size; eol > offset && isspace((unsigned char) text->text[eol - 1]); eol--);
304
305     // IS_UNORDERED_LIST_#
306     if(text->size >= offset + 2 &&
307        (text->text[offset] == '*' || text->text[offset] == '-') &&
308        text->text[offset + 1] == ' ') {
309
310         for(i = offset; i<eol; i++) {
311             if(text->text[i] != '*' &&
312                text->text[i] != '-' &&
313                text->text[i] != ' ') {
314                 if(offset > unordered_list_offset + CODE_INDENT) {
315                     SET_BIT(bits, IS_CODE);
316                 } else if(offset != unordered_list_offset) {
317                     for(i = unordered_list_level; i >= 0; i--) {
318                         if(unordered_list_level_offset[i] == offset) {
319                             unordered_list_level = i;
320                             break;
321                         }
322                     }
323                     if(i != unordered_list_level) {
324                         unordered_list_level = MIN(unordered_list_level + 1, UNORDERED_LIST_MAX_LEVEL);
325                         unordered_list_level_offset[unordered_list_level] = offset;
326                     }
327                 }
328
329                 if(unordered_list_level == 0) {
330                     unordered_list_level = 1;
331                     unordered_list_level_offset[1] = offset;
332                 }
333
334                 switch(unordered_list_level) {
335                     case 1: SET_BIT(bits, IS_UNORDERED_LIST_1); break;
336                     case 2: SET_BIT(bits, IS_UNORDERED_LIST_2); break;
337                     case 3: SET_BIT(bits, IS_UNORDERED_LIST_3); break;
338                     default: break;
339                 }
340                 
341                 break;
342             }
343         }
344     }
345     
346     if(!CHECK_BIT(bits, IS_UNORDERED_LIST_1) &&
347        !CHECK_BIT(bits, IS_UNORDERED_LIST_2) &&
348        !CHECK_BIT(bits, IS_UNORDERED_LIST_3)) {
349
350         unordered_list_level = 0;
351
352         // IS_CODE
353         if(offset >= CODE_INDENT) {
354             SET_BIT(bits, IS_CODE);
355
356         } else {
357
358             for(i = offset; i < eol; i++) {
359
360                 if(text->text[i] == ' ') {
361                     spaces++;
362
363                 } else if(CHECK_BIT(bits, IS_CODE)) {
364                     other++;
365
366                 } else {
367                     switch(text->text[i]) {
368                         case '=': equals++;  break;
369                         case '#': hashes++;  break;
370                         case '*': stars++;   break;
371                         case '-': minus++;   break;
372                         case '\\': other++; i++; break;
373                         default:  other++;   break;
374                     }
375                 }
376             }
377
378             // IS_H1
379             if((equals > 0 &&
380                 hashes + stars + minus + spaces + other == 0) ||
381                (text &&
382                 text->text &&
383                 text->text[offset] == '#' &&
384                 text->text[offset+1] != '#')) {
385
386                 SET_BIT(bits, IS_H1);
387             }
388
389             // IS_H2
390             if((minus > 0 &&
391                 equals + hashes + stars + spaces + other == 0) ||
392                (text &&
393                 text->text &&
394                 text->text[offset] == '#' &&
395                 text->text[offset+1] == '#')) {
396
397                 SET_BIT(bits, IS_H2);
398             }
399
400             // IS_QUOTE
401             if(text &&
402                text->text &&
403                text->text[offset] == '>') {
404
405                 SET_BIT(bits, IS_QUOTE);
406             }
407
408             // IS_HR
409             if((minus >= 3 && equals + hashes + stars + other == 0) ||
410                (stars >= 3 && equals + hashes + minus + other == 0)) {
411
412                 SET_BIT(bits, IS_HR);
413             }
414
415             // IS_EMPTY
416             if(other == 0) {
417                 SET_BIT(bits, IS_EMPTY);
418             }
419         }
420     }
421
422     return bits;
423 }
424
425 void markdown_debug(deck_t *deck, int debug) {
426
427     int sc = 0; // slide count
428     int lc = 0; // line count
429
430     int offset;
431     line_t *header;
432
433     if(debug == 1) {
434         fprintf(stderr, "headers: %i\nslides: %i\n", deck->headers, deck->slides);
435
436     } else if(debug > 1) {
437
438         // print header to STDERR
439         if(deck->header) {
440             header = deck->header;
441             while(header &&
442                 header->length > 0 &&
443                 header->text->text[0] == '%') {
444
445                 // skip descriptor word (e.g. %title:)
446                 offset = next_blank(header->text, 0) + 1;
447
448                 fprintf(stderr, "header: %s\n", &header->text->text[offset]);
449                 header = header->next;
450             }
451         }
452     }
453
454     slide_t *slide = deck->slide;
455     line_t *line;
456
457     // print slide/line count to STDERR
458     while(slide) {
459         sc++;
460
461         if(debug == 1) {
462             fprintf(stderr, "  slide %i: %i lines\n", sc, slide->lines);
463
464         } else if(debug > 1) {
465
466             // also print bits and line length
467             fprintf(stderr, "  slide %i:\n", sc);
468             line = slide->line;
469             lc = 0;
470             while(line) {
471                 lc++;
472                 fprintf(stderr, "    line %i: bits = %i, length = %i\n", lc, line->bits, line->length);
473                 line = line->next;
474             }
475         }
476
477         slide = slide->next;
478     }
479 }
480
481 int is_utf8(char ch) {
482     return (ch & 0x80);
483 }
484
485 int length_utf8(char ch) {
486
487     int i = 0; // increment
488
489     while(is_utf8(ch)) {
490         i++;
491         ch <<= 1;
492     }
493
494     return i;
495 }
496
497 int next_nonblank(cstring_t *text, int i) {
498     while ((i < text->size) && isspace((unsigned char) (text->text)[i]))
499         i++;
500
501     return i;
502 }
503
504 int next_blank(cstring_t *text, int i) {
505     while ((i < text->size) && !isspace((unsigned char) (text->text)[i]))
506         i++;
507
508     return i;
509 }
510
511 int next_word(cstring_t *text, int i) {
512     return next_nonblank(text, next_blank(text, i));
513 }
514