2 * Functions necessary to parse a file and transform its content into
3 * a deck of slides containing lines. All based on markdown formating
5 * Copyright (C) 2014 Michael Goehler
7 * This file is part of mdp.
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.
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.
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/>.
32 deck_t *markdown_load(FILE *input) {
35 int i = 0; // increment
36 int l = 0; // line length
37 int hc = 0; // header count
38 int lc = 0; // line count
39 int sc = 0; // slide count
40 int bits = 0; // markdown bits
42 deck_t *deck = new_deck();
43 slide_t *slide = new_slide();
46 cstring_t *text = cstring_init();
48 // assign first slide to deck
52 while ((c = fgetc(input)) != EOF) {
54 fprintf(stderr, "markdown_load() failed to read input: %s\n", strerror(errno));
61 bits = markdown_analyse(text);
63 // if first line in file is markdown hr
64 if(!line && CHECK_BIT(bits, IS_HR)) {
69 // if text is markdown hr
70 } else if(CHECK_BIT(bits, IS_HR) &&
71 CHECK_BIT(line->bits, IS_EMPTY)) {
80 slide = next_slide(slide);
85 // if slide ! has line
96 line = next_line(line);
107 // add length to line
111 line->offset = next_nonblank(text, 0);
114 text = cstring_init();
118 } else if(c == '\t') {
120 // expand tab to spaces
121 for (i = 0; i < EXPAND_TABS; i++) {
122 (text->expand)(text, ' ');
126 } else if(c == '\\') {
129 (text->expand)(text, c);
132 // if !IS_CODE add next char to line
133 // and do not increase line count
134 if(next_nonblank(text, 0) < CODE_INDENT) {
137 (text->expand)(text, c);
141 // if utf-8 char > 1 byte add remaing to line
142 for(i = 0; i < length_utf8(c) - 1; i++) {
144 (text->expand)(text, c);
150 } else if(isprint(c) || isspace((unsigned char) c)) {
153 (text->expand)(text, c);
156 } else if(is_utf8(c)) {
159 (text->expand)(text, c);
161 // if utf-8 char > 1 byte add remaing to line
162 for(i = 0; i < length_utf8(c) - 1; i++) {
164 (text->expand)(text, c);
175 line = deck->slide->line;
176 if(line && line->text->size > 0 && line->text->text[0] == '%') {
178 // assign header to deck
181 // find first non-header line
182 while(line->text->size > 0 && line->text->text[0] == '%') {
188 line->prev->next = (void*)0;
189 line->prev = (void*)0;
191 // remove header lines from slide
192 deck->slide->line = line;
196 deck->slide->lines -= hc;
203 if((CHECK_BIT(line->bits, IS_H1) ||
204 CHECK_BIT(line->bits, IS_H2)) &&
205 CHECK_BIT(line->bits, IS_EMPTY) &&
207 !CHECK_BIT(line->prev->bits, IS_EMPTY)) {
208 // combine underlined H1/H2 in single line
210 // remove line from linked list
211 line->prev->next = line->next;
213 line->next->prev = line->prev;
215 // set bits on previous line
216 if(CHECK_BIT(line->bits, IS_H1)) {
217 SET_BIT(line->prev->bits, IS_H1);
219 SET_BIT(line->prev->bits, IS_H2);
225 // maintain loop condition
230 (tmp->text->delete)(tmp->text);
232 } else if(CHECK_BIT(line->bits, IS_UNORDERED_LIST_3)) {
234 line_t *list_last_level_3 = line;
237 CHECK_BIT(tmp->bits, IS_UNORDERED_LIST_3)) {
238 if(CHECK_BIT(tmp->bits, IS_UNORDERED_LIST_3)) {
239 list_last_level_3 = tmp;
244 for(tmp = line; tmp != list_last_level_3; tmp = tmp->next) {
245 SET_BIT(tmp->bits, IS_UNORDERED_LIST_3);
247 } else if(CHECK_BIT(line->bits, IS_UNORDERED_LIST_2)) {
249 line_t *list_last_level_2 = line;
252 (CHECK_BIT(tmp->bits, IS_UNORDERED_LIST_2) ||
253 CHECK_BIT(tmp->bits, IS_UNORDERED_LIST_3))) {
254 if(CHECK_BIT(tmp->bits, IS_UNORDERED_LIST_2)) {
255 list_last_level_2 = tmp;
260 for(tmp = line; tmp != list_last_level_2; tmp = tmp->next) {
261 SET_BIT(tmp->bits, IS_UNORDERED_LIST_2);
263 } else if(CHECK_BIT(line->bits, IS_UNORDERED_LIST_1)) {
265 line_t *list_last_level_1 = line;
268 (CHECK_BIT(tmp->bits, IS_UNORDERED_LIST_1) ||
269 CHECK_BIT(tmp->bits, IS_UNORDERED_LIST_2) ||
270 CHECK_BIT(tmp->bits, IS_UNORDERED_LIST_3))) {
271 if(CHECK_BIT(tmp->bits, IS_UNORDERED_LIST_1)) {
272 list_last_level_1 = tmp;
277 for(tmp = line; tmp != list_last_level_1; tmp = tmp->next) {
278 SET_BIT(tmp->bits, IS_UNORDERED_LIST_1);
290 int markdown_analyse(cstring_t *text) {
292 static int unordered_list_level = 0;
293 static int unordered_list_level_offset[] = {-1, -1, -1, -1};
295 int i = 0; // increment
296 int bits = 0; // markdown bits
297 int offset = 0; // text offset
298 int eol = 0; // end of line
300 int equals = 0, hashes = 0,
301 stars = 0, minus = 0,
302 spaces = 0, other = 0; // special character counts
304 const int unordered_list_offset = unordered_list_level_offset[unordered_list_level];
306 // count leading spaces
307 offset = next_nonblank(text, 0);
309 // strip trailing spaces
310 for(eol = text->size; eol > offset && isspace((unsigned char) text->text[eol - 1]); eol--);
312 // IS_UNORDERED_LIST_#
313 if(text->size >= offset + 2 &&
314 (text->text[offset] == '*' || text->text[offset] == '-') &&
315 text->text[offset + 1] == ' ') {
317 for(i = offset; i<eol; i++) {
318 if(text->text[i] != '*' &&
319 text->text[i] != '-' &&
320 text->text[i] != ' ') {
321 if(offset > unordered_list_offset + CODE_INDENT) {
322 SET_BIT(bits, IS_CODE);
323 } else if(offset != unordered_list_offset) {
324 for(i = unordered_list_level; i >= 0; i--) {
325 if(unordered_list_level_offset[i] == offset) {
326 unordered_list_level = i;
330 if(i != unordered_list_level) {
331 unordered_list_level = MIN(unordered_list_level + 1, UNORDERED_LIST_MAX_LEVEL);
332 unordered_list_level_offset[unordered_list_level] = offset;
336 if(unordered_list_level == 0) {
337 unordered_list_level = 1;
338 unordered_list_level_offset[1] = offset;
341 switch(unordered_list_level) {
342 case 1: SET_BIT(bits, IS_UNORDERED_LIST_1); break;
343 case 2: SET_BIT(bits, IS_UNORDERED_LIST_2); break;
344 case 3: SET_BIT(bits, IS_UNORDERED_LIST_3); break;
353 if(!CHECK_BIT(bits, IS_UNORDERED_LIST_1) &&
354 !CHECK_BIT(bits, IS_UNORDERED_LIST_2) &&
355 !CHECK_BIT(bits, IS_UNORDERED_LIST_3)) {
357 unordered_list_level = 0;
360 if(offset >= CODE_INDENT) {
361 SET_BIT(bits, IS_CODE);
365 for(i = offset; i < eol; i++) {
367 if(text->text[i] == ' ') {
370 } else if(CHECK_BIT(bits, IS_CODE)) {
374 switch(text->text[i]) {
375 case '=': equals++; break;
376 case '#': hashes++; break;
377 case '*': stars++; break;
378 case '-': minus++; break;
379 case '\\': other++; i++; break;
380 default: other++; break;
387 hashes + stars + minus + spaces + other == 0) ||
390 text->text[offset] == '#' &&
391 text->text[offset+1] != '#')) {
393 SET_BIT(bits, IS_H1);
398 equals + hashes + stars + spaces + other == 0) ||
401 text->text[offset] == '#' &&
402 text->text[offset+1] == '#')) {
404 SET_BIT(bits, IS_H2);
410 text->text[offset] == '>') {
412 SET_BIT(bits, IS_QUOTE);
416 if((minus >= 3 && equals + hashes + stars + other == 0) ||
417 (stars >= 3 && equals + hashes + minus + other == 0)) {
419 SET_BIT(bits, IS_HR);
424 SET_BIT(bits, IS_EMPTY);
432 void markdown_debug(deck_t *deck, int debug) {
434 int sc = 0; // slide count
435 int lc = 0; // line count
441 fprintf(stderr, "headers: %i\nslides: %i\n", deck->headers, deck->slides);
443 } else if(debug > 1) {
445 // print header to STDERR
447 header = deck->header;
449 header->length > 0 &&
450 header->text->text[0] == '%') {
452 // skip descriptor word (e.g. %title:)
453 offset = next_blank(header->text, 0) + 1;
455 fprintf(stderr, "header: %s\n", &header->text->text[offset]);
456 header = header->next;
461 slide_t *slide = deck->slide;
464 // print slide/line count to STDERR
469 fprintf(stderr, " slide %i: %i lines\n", sc, slide->lines);
471 } else if(debug > 1) {
473 // also print bits and line length
474 fprintf(stderr, " slide %i:\n", sc);
479 fprintf(stderr, " line %i: bits = %i, length = %i\n", lc, line->bits, line->length);
488 int is_utf8(char ch) {
492 int length_utf8(char ch) {
494 int i = 0; // increment
504 int next_nonblank(cstring_t *text, int i) {
505 while ((i < text->size) && isspace((unsigned char) (text->text)[i]))
511 int prev_blank(cstring_t *text, int i) {
512 while ((i > 0) && !isspace((unsigned char) (text->text)[i]))
518 int next_blank(cstring_t *text, int i) {
519 while ((i < text->size) && !isspace((unsigned char) (text->text)[i]))
525 int next_word(cstring_t *text, int i) {
526 return next_nonblank(text, next_blank(text, i));