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