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/>.
30 deck_t *markdown_load(FILE *input) {
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
40 deck_t *deck = new_deck();
41 slide_t *slide = new_slide();
44 cstring_t *text = cstring_init();
46 // assign first slide to deck
50 while ((c = fgetc(input)) != EOF) {
54 bits = markdown_analyse(text);
56 // if first line in file is markdown hr
57 if(!line && CHECK_BIT(bits, IS_HR)) {
62 // if text is markdown hr
63 } else if(CHECK_BIT(bits, IS_HR) &&
64 CHECK_BIT(line->bits, IS_EMPTY)) {
73 slide = next_slide(slide);
78 // if slide ! has line
89 line = next_line(line);
100 // add length to line
104 line->offset = next_nonblank(text, 0);
107 text = cstring_init();
111 } else if(c == '\t') {
113 // expand tab to spaces
114 for (i = 0; i < EXPAND_TABS; i++) {
115 (text->expand)(text, ' ');
119 } else if(c == '\\') {
122 (text->expand)(text, c);
125 // if !IS_CODE add next char to line
126 // and do not increase line count
127 if(next_nonblank(text, 0) < CODE_INDENT) {
130 (text->expand)(text, c);
134 // if utf-8 char > 1 byte add remaing to line
135 for(i = 0; i < length_utf8(c) - 1; i++) {
137 (text->expand)(text, c);
143 } else if(isprint(c) || isspace((unsigned char) c)) {
146 (text->expand)(text, c);
149 } else if(is_utf8(c)) {
152 (text->expand)(text, c);
154 // if utf-8 char > 1 byte add remaing to line
155 for(i = 0; i < length_utf8(c) - 1; i++) {
157 (text->expand)(text, c);
168 line = deck->slide->line;
169 if(line && line->text->size > 0 && line->text->text[0] == '%') {
171 // assign header to deck
174 // find first non-header line
175 while(line->text->size > 0 && line->text->text[0] == '%') {
181 line->prev->next = (void*)0;
182 line->prev = (void*)0;
184 // remove header lines from slide
185 deck->slide->line = line;
189 deck->slide->lines -= hc;
196 if((CHECK_BIT(line->bits, IS_H1) ||
197 CHECK_BIT(line->bits, IS_H2)) &&
198 CHECK_BIT(line->bits, IS_EMPTY) &&
200 !CHECK_BIT(line->prev->bits, IS_EMPTY)) {
201 // combine underlined H1/H2 in single line
203 // remove line from linked list
204 line->prev->next = line->next;
206 line->next->prev = line->prev;
208 // set bits on previous line
209 if(CHECK_BIT(line->bits, IS_H1)) {
210 SET_BIT(line->prev->bits, IS_H1);
212 SET_BIT(line->prev->bits, IS_H2);
218 // maintain loop condition
223 (tmp->text->delete)(tmp->text);
225 } else if(CHECK_BIT(line->bits, IS_UNORDERED_LIST_3)) {
227 line_t *list_last_level_3 = line;
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;
237 for(tmp = line; tmp != list_last_level_3; tmp = tmp->next) {
238 SET_BIT(tmp->bits, IS_UNORDERED_LIST_3);
240 } else if(CHECK_BIT(line->bits, IS_UNORDERED_LIST_2)) {
242 line_t *list_last_level_2 = line;
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;
253 for(tmp = line; tmp != list_last_level_2; tmp = tmp->next) {
254 SET_BIT(tmp->bits, IS_UNORDERED_LIST_2);
256 } else if(CHECK_BIT(line->bits, IS_UNORDERED_LIST_1)) {
258 line_t *list_last_level_1 = line;
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;
270 for(tmp = line; tmp != list_last_level_1; tmp = tmp->next) {
271 SET_BIT(tmp->bits, IS_UNORDERED_LIST_1);
283 int markdown_analyse(cstring_t *text) {
285 static int unordered_list_level = 0;
286 static int unordered_list_level_offset[] = {-1, -1, -1, -1};
288 int i = 0; // increment
289 int bits = 0; // markdown bits
290 int offset = 0; // text offset
291 int eol = 0; // end of line
293 int equals = 0, hashes = 0,
294 stars = 0, minus = 0,
295 spaces = 0, other = 0; // special character counts
297 const int unordered_list_offset = unordered_list_level_offset[unordered_list_level];
299 // count leading spaces
300 offset = next_nonblank(text, 0);
302 // strip trailing spaces
303 for(eol = text->size; eol > offset && isspace((unsigned char) text->text[eol - 1]); eol--);
305 // IS_UNORDERED_LIST_#
306 if(text->size >= offset + 2 &&
307 (text->text[offset] == '*' || text->text[offset] == '-') &&
308 text->text[offset + 1] == ' ') {
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;
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;
329 if(unordered_list_level == 0) {
330 unordered_list_level = 1;
331 unordered_list_level_offset[1] = offset;
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;
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)) {
350 unordered_list_level = 0;
353 if(offset >= CODE_INDENT) {
354 SET_BIT(bits, IS_CODE);
358 for(i = offset; i < eol; i++) {
360 if(text->text[i] == ' ') {
363 } else if(CHECK_BIT(bits, IS_CODE)) {
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;
380 hashes + stars + minus + spaces + other == 0) ||
383 text->text[offset] == '#' &&
384 text->text[offset+1] != '#')) {
386 SET_BIT(bits, IS_H1);
391 equals + hashes + stars + spaces + other == 0) ||
394 text->text[offset] == '#' &&
395 text->text[offset+1] == '#')) {
397 SET_BIT(bits, IS_H2);
403 text->text[offset] == '>') {
405 SET_BIT(bits, IS_QUOTE);
409 if((minus >= 3 && equals + hashes + stars + other == 0) ||
410 (stars >= 3 && equals + hashes + minus + other == 0)) {
412 SET_BIT(bits, IS_HR);
417 SET_BIT(bits, IS_EMPTY);
425 void markdown_debug(deck_t *deck, int debug) {
427 int sc = 0; // slide count
428 int lc = 0; // line count
434 fprintf(stderr, "headers: %i\nslides: %i\n", deck->headers, deck->slides);
436 } else if(debug > 1) {
438 // print header to STDERR
440 header = deck->header;
442 header->length > 0 &&
443 header->text->text[0] == '%') {
445 // skip descriptor word (e.g. %title:)
446 offset = next_blank(header->text, 0) + 1;
448 fprintf(stderr, "header: %s\n", &header->text->text[offset]);
449 header = header->next;
454 slide_t *slide = deck->slide;
457 // print slide/line count to STDERR
462 fprintf(stderr, " slide %i: %i lines\n", sc, slide->lines);
464 } else if(debug > 1) {
466 // also print bits and line length
467 fprintf(stderr, " slide %i:\n", sc);
472 fprintf(stderr, " line %i: bits = %i, length = %i\n", lc, line->bits, line->length);
481 int is_utf8(char ch) {
485 int length_utf8(char ch) {
487 int i = 0; // increment
497 int next_nonblank(cstring_t *text, int i) {
498 while ((i < text->size) && isspace((unsigned char) (text->text)[i]))
504 int prev_blank(cstring_t *text, int i) {
505 while ((i > 0) && !isspace((unsigned char) (text->text)[i]))
511 int next_blank(cstring_t *text, int i) {
512 while ((i < text->size) && !isspace((unsigned char) (text->text)[i]))
518 int next_word(cstring_t *text, int i) {
519 return next_nonblank(text, next_blank(text, i));