extend sample.md so it looks more like a documentation
[smdp.git] / 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 "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 text is markdown hr
57             if(CHECK_BIT(bits, IS_HR) &&
58                CHECK_BIT(line->bits, IS_EMPTY)) {
59
60                 slide->lines = lc;
61
62                 // clear text
63                 (text->reset)(text);
64                 l = 0;
65
66                 // create next slide
67                 slide = next_slide(slide);
68                 sc++;
69
70             } else {
71
72                 // if slide ! has line
73                 if(!slide->line) {
74
75                     // create new line
76                     line = new_line();
77                     slide->line = line;
78                     lc = 1;
79
80                 } else {
81
82                     // create next line
83                     line = next_line(line);
84                     lc++;
85
86                 }
87
88                 // add text to line
89                 line->text = text;
90
91                 // add bits to line
92                 line->bits = bits;
93
94                 // add length to line
95                 line->length = l;
96
97                 // calc offset
98                 line->offset = next_nonblank(text, 0);
99
100                 // new text
101                 text = cstring_init();
102                 l = 0;
103             }
104
105         } else if(c == '\t') {
106
107             // expand tab to spaces
108             for (i = 0;  i < EXPAND_TABS;  i++) {
109                 (text->expand)(text, ' ');
110                 l++;
111             }
112
113         } else if(c == '\\') {
114
115             // add char to line
116             (text->expand)(text, c);
117             l++;
118
119             // if !IS_CODE add next char to line
120             // and do not increase line count
121             if(next_nonblank(text, 0) < CODE_INDENT) {
122
123                 c = fgetc(input);
124                 (text->expand)(text, c);
125
126                 if(is_utf8(c)) {
127
128                     // if utf-8 char > 1 byte add remaing to line
129                     for(i = 0; i < length_utf8(c) - 1; i++) {
130                         c = fgetc(input);
131                         (text->expand)(text, c);
132                     }
133                 }
134
135             }
136
137         } else if(isprint(c) || isspace((unsigned char) c)) {
138
139             // add char to line
140             (text->expand)(text, c);
141             l++;
142
143         } else if(is_utf8(c)) {
144
145             // add char to line
146             (text->expand)(text, c);
147
148             // if utf-8 char > 1 byte add remaing to line
149             for(i = 0; i < length_utf8(c) - 1; i++) {
150                 c = fgetc(input);
151                 (text->expand)(text, c);
152             }
153
154             l++;
155         }
156     }
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->text[0] == '%') {
164
165         // assign header to deck
166         deck->header = line;
167
168         // find first non-header line
169         while(line->text->size > 0 && line->text->text[0] == '%') {
170             hc++;
171             line = line->next;
172         }
173
174         // split linked list
175         line->prev->next = (void*)0;
176         line->prev = (void*)0;
177
178         // remove header lines from slide
179         deck->slide->line = line;
180
181         // adjust counts
182         deck->headers += hc;
183         deck->slide->lines -= hc;
184     }
185
186     // combine underlined H1/H2 in single line
187     slide = deck->slide;
188     while(slide) {
189         line = slide->line;
190         while(line) {
191             if((CHECK_BIT(line->bits, IS_H1) ||
192                 CHECK_BIT(line->bits, IS_H2)) &&
193                CHECK_BIT(line->bits, IS_EMPTY) &&
194                line->prev &&
195                !CHECK_BIT(line->prev->bits, IS_EMPTY)) {
196
197                 // remove line from linked list
198                 line->prev->next = line->next;
199                 if(line->next)
200                     line->next->prev = line->prev;
201
202                 // set bits on previous line
203                 if(CHECK_BIT(line->bits, IS_H1)) {
204                     SET_BIT(line->prev->bits, IS_H1);
205                 } else {
206                     SET_BIT(line->prev->bits, IS_H2);
207                 }
208
209                 // adjust line count
210                 slide->lines -= 1;
211
212                 // maintain loop condition
213                 tmp = line;
214                 line = line->prev;
215
216                 // delete line
217                 (tmp->text->delete)(tmp->text);
218                 free(tmp);
219             }
220             line = line->next;
221         }
222         slide = slide->next;
223     }
224
225     return deck;
226 }
227
228 int markdown_analyse(cstring_t *text) {
229
230     int i = 0;      // increment
231     int bits = 0;   // markdown bits
232     int offset = 0; // text offset
233     int eol    = 0; // end of line
234
235     int equals = 0, hashes = 0,
236         stars  = 0, minus  = 0,
237         spaces = 0, other  = 0; // special character counts
238
239     // count leading spaces
240     offset = next_nonblank(text, 0);
241
242     // strip trailing spaces
243     for(eol = text->size; eol > offset && isspace((unsigned char) text->text[eol - 1]); eol--);
244
245     // IS_CODE
246     if(offset >= CODE_INDENT) {
247         SET_BIT(bits, IS_CODE);
248     }
249
250     for(i = offset; i < eol; i++) {
251
252         if(text->text[i] == ' ') {
253             spaces++;
254
255         } else if(CHECK_BIT(bits, IS_CODE)) {
256             other++;
257
258         } else {
259             switch(text->text[i]) {
260                 case '=': equals++;  break;
261                 case '#': hashes++;  break;
262                 case '*': stars++;   break;
263                 case '-': minus++;   break;
264                 case '\\': other++; i++; break;
265                 default:  other++;   break;
266             }
267         }
268     }
269
270     // IS_H1
271     if((equals > 0 &&
272         hashes + stars + minus + spaces + other == 0) ||
273        (text &&
274         text->text &&
275         text->text[offset] == '#' &&
276         text->text[offset+1] != '#')) {
277
278         SET_BIT(bits, IS_H1);
279     }
280
281     // IS_H2
282     if((minus > 0 &&
283         equals + hashes + stars + spaces + other == 0) ||
284        (text &&
285         text->text &&
286         text->text[offset] == '#' &&
287         text->text[offset+1] == '#')) {
288
289         SET_BIT(bits, IS_H2);
290     }
291
292     // IS_QUOTE
293     if(text &&
294        text->text &&
295        text->text[offset] == '>') {
296
297         SET_BIT(bits, IS_QUOTE);
298     }
299
300     // IS_HR
301     if((minus >= 3 && equals + hashes + stars + other == 0) ||
302        (stars >= 3 && equals + hashes + minus + other == 0)) {
303
304         SET_BIT(bits, IS_HR);
305     }
306
307     // IS_EMPTY
308     if(other == 0) {
309         SET_BIT(bits, IS_EMPTY);
310     }
311
312     return bits;
313 }
314
315 void markdown_debug(deck_t *deck, int debug) {
316
317     int sc = 0; // slide count
318     int lc = 0; // line count
319
320     int offset;
321     line_t *header;
322
323     if(debug == 1) {
324         fprintf(stderr, "headers: %i\nslides: %i\n", deck->headers, deck->slides);
325
326     } else if(debug > 1) {
327
328         // print header to STDERR
329         if(deck->header) {
330             header = deck->header;
331             while(header &&
332                 header->length > 0 &&
333                 header->text->text[0] == '%') {
334
335                 // skip descriptor word (e.g. %title:)
336                 offset = next_blank(header->text, 0) + 1;
337
338                 fprintf(stderr, "header: %s\n", &header->text->text[offset]);
339                 header = header->next;
340             }
341         }
342     }
343
344     slide_t *slide = deck->slide;
345     line_t *line;
346
347     // print slide/line count to STDERR
348     while(slide) {
349         sc++;
350
351         if(debug == 1) {
352             fprintf(stderr, "  slide %i: %i lines\n", sc, slide->lines);
353
354         } else if(debug > 1) {
355
356             // also print bits and line length
357             fprintf(stderr, "  slide %i:\n", sc);
358             line = slide->line;
359             lc = 0;
360             while(line) {
361                 lc++;
362                 fprintf(stderr, "    line %i: bits = %i, length = %i\n", lc, line->bits, line->length);
363                 line = line->next;
364             }
365         }
366
367         slide = slide->next;
368     }
369 }
370
371 int is_utf8(char ch) {
372     return (ch & 0x80);
373 }
374
375 int length_utf8(char ch) {
376
377     int i = 0; // increment
378
379     while(ch & 0x80) {
380         i++;
381         ch <<= 1;
382     }
383
384     return i;
385 }
386
387 int next_nonblank(cstring_t *text, int i) {
388     while ((i < text->size) && isspace((unsigned char) (text->text)[i]))
389         ++i;
390
391     return i;
392 }
393
394 int next_blank(cstring_t *text, int i) {
395     while ((i < text->size) && !isspace((unsigned char) (text->text)[i]))
396         ++i;
397
398     return i;
399 }
400
401 int next_word(cstring_t *text, int i) {
402     return next_nonblank(text, next_blank(text, i));
403 }
404