rmultimarkdown 4.7.1.1 → 6.2.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/LICENSE +2 -2
- data/README.md +7 -9
- data/Rakefile +33 -18
- data/ext/Makefile +261 -0
- data/ext/extconf.rb +23 -3
- data/ext/mmd/aho-corasick.c +596 -0
- data/ext/mmd/aho-corasick.h +120 -0
- data/ext/mmd/beamer.c +344 -0
- data/ext/mmd/beamer.h +72 -0
- data/ext/mmd/char.c +156 -0
- data/ext/mmd/char.h +111 -0
- data/ext/mmd/char_lookup.c +212 -0
- data/ext/mmd/critic_markup.c +330 -0
- data/ext/mmd/critic_markup.h +94 -0
- data/ext/mmd/d_string.c +402 -0
- data/ext/mmd/epub.c +563 -0
- data/ext/mmd/epub.h +69 -0
- data/ext/mmd/fodt.c +2288 -0
- data/ext/mmd/fodt.h +81 -0
- data/ext/mmd/html.c +2460 -0
- data/ext/mmd/html.h +81 -0
- data/ext/mmd/i18n.h +170 -0
- data/ext/mmd/include/d_string.h +182 -0
- data/ext/mmd/include/libMultiMarkdown.h +548 -0
- data/ext/mmd/include/token.h +233 -0
- data/ext/mmd/latex.c +2435 -0
- data/ext/mmd/latex.h +83 -0
- data/ext/mmd/lexer.c +3001 -0
- data/ext/mmd/lexer.h +75 -0
- data/ext/mmd/memoir.c +138 -0
- data/ext/mmd/memoir.h +67 -0
- data/ext/mmd/miniz.c +7557 -0
- data/ext/mmd/miniz.h +1328 -0
- data/ext/mmd/mmd.c +2798 -0
- data/ext/mmd/mmd.h +120 -0
- data/ext/mmd/object_pool.c +141 -0
- data/ext/mmd/object_pool.h +101 -0
- data/ext/mmd/opendocument-content.c +2071 -0
- data/ext/mmd/opendocument-content.h +135 -0
- data/ext/mmd/opendocument.c +981 -0
- data/ext/mmd/opendocument.h +118 -0
- data/ext/mmd/parser.c +1760 -0
- data/ext/mmd/parser.h +39 -0
- data/{MultiMarkdown-4 → ext/mmd}/rng.c +90 -49
- data/ext/mmd/scanners.c +77512 -0
- data/ext/mmd/scanners.h +101 -0
- data/ext/mmd/stack.c +142 -0
- data/ext/mmd/stack.h +113 -0
- data/ext/mmd/textbundle.c +455 -0
- data/ext/mmd/textbundle.h +115 -0
- data/ext/mmd/token.c +773 -0
- data/ext/mmd/token_pairs.c +263 -0
- data/ext/mmd/token_pairs.h +123 -0
- data/ext/mmd/transclude.c +549 -0
- data/ext/mmd/transclude.h +87 -0
- data/ext/mmd/uthash.h +1074 -0
- data/ext/mmd/uuid.c +154 -0
- data/ext/mmd/uuid.h +77 -0
- data/ext/mmd/version.h +111 -0
- data/ext/mmd/writer.c +2652 -0
- data/ext/mmd/writer.h +260 -0
- data/ext/mmd/zip.c +210 -0
- data/ext/mmd/zip.h +120 -0
- data/ext/{multi_markdown.c → ruby_multi_markdown.c} +87 -18
- data/lib/multi_markdown.bundle +0 -0
- data/lib/multi_markdown.rb +5 -8
- data/lib/multi_markdown/version.rb +1 -1
- data/rmultimarkdown.gemspec +2 -2
- data/test/{extensions_test.rb.rb → extensions_test.rb} +10 -54
- data/test/multi_markdown_test.rb +13 -0
- metadata +67 -47
- data/MultiMarkdown-4/GLibFacade.c +0 -310
- data/MultiMarkdown-4/GLibFacade.h +0 -100
- data/MultiMarkdown-4/beamer.c +0 -182
- data/MultiMarkdown-4/beamer.h +0 -11
- data/MultiMarkdown-4/critic.c +0 -111
- data/MultiMarkdown-4/critic.h +0 -15
- data/MultiMarkdown-4/glib.h +0 -11
- data/MultiMarkdown-4/html.c +0 -1117
- data/MultiMarkdown-4/html.h +0 -14
- data/MultiMarkdown-4/latex.c +0 -1217
- data/MultiMarkdown-4/latex.h +0 -16
- data/MultiMarkdown-4/libMultiMarkdown.h +0 -177
- data/MultiMarkdown-4/lyx.c +0 -2265
- data/MultiMarkdown-4/lyx.h +0 -37
- data/MultiMarkdown-4/lyxbeamer.c +0 -265
- data/MultiMarkdown-4/lyxbeamer.h +0 -11
- data/MultiMarkdown-4/memoir.c +0 -80
- data/MultiMarkdown-4/memoir.h +0 -10
- data/MultiMarkdown-4/multimarkdown.c +0 -518
- data/MultiMarkdown-4/odf.c +0 -1222
- data/MultiMarkdown-4/odf.h +0 -18
- data/MultiMarkdown-4/opml.c +0 -189
- data/MultiMarkdown-4/opml.h +0 -15
- data/MultiMarkdown-4/parse_utilities.c +0 -884
- data/MultiMarkdown-4/parser.c +0 -16656
- data/MultiMarkdown-4/parser.h +0 -188
- data/MultiMarkdown-4/rtf.c +0 -665
- data/MultiMarkdown-4/rtf.h +0 -17
- data/MultiMarkdown-4/strtok.c +0 -56
- data/MultiMarkdown-4/strtok.h +0 -9
- data/MultiMarkdown-4/text.c +0 -53
- data/MultiMarkdown-4/text.h +0 -11
- data/MultiMarkdown-4/toc.c +0 -142
- data/MultiMarkdown-4/toc.h +0 -15
- data/MultiMarkdown-4/transclude.c +0 -307
- data/MultiMarkdown-4/transclude.h +0 -28
- data/MultiMarkdown-4/writer.c +0 -731
- data/MultiMarkdown-4/writer.h +0 -38
data/ext/mmd/mmd.c
ADDED
|
@@ -0,0 +1,2798 @@
|
|
|
1
|
+
/**
|
|
2
|
+
|
|
3
|
+
MultiMarkdown 6 -- Lightweight markup processor to produce HTML, LaTeX, and more.
|
|
4
|
+
|
|
5
|
+
@file mmd.c
|
|
6
|
+
|
|
7
|
+
@brief Create MMD parsing engine
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
@author Fletcher T. Penney
|
|
11
|
+
@bug
|
|
12
|
+
|
|
13
|
+
**/
|
|
14
|
+
|
|
15
|
+
/*
|
|
16
|
+
|
|
17
|
+
Copyright © 2016 - 2017 Fletcher T. Penney.
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
The `MultiMarkdown 6` project is released under the MIT License..
|
|
21
|
+
|
|
22
|
+
GLibFacade.c and GLibFacade.h are from the MultiMarkdown v4 project:
|
|
23
|
+
|
|
24
|
+
https://github.com/fletcher/MultiMarkdown-4/
|
|
25
|
+
|
|
26
|
+
MMD 4 is released under both the MIT License and GPL.
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
CuTest is released under the zlib/libpng license. See CuTest.c for the text
|
|
30
|
+
of the license.
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
## The MIT License ##
|
|
34
|
+
|
|
35
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
36
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
37
|
+
in the Software without restriction, including without limitation the rights
|
|
38
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
39
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
40
|
+
furnished to do so, subject to the following conditions:
|
|
41
|
+
|
|
42
|
+
The above copyright notice and this permission notice shall be included in
|
|
43
|
+
all copies or substantial portions of the Software.
|
|
44
|
+
|
|
45
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
46
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
47
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
48
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
49
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
50
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
51
|
+
THE SOFTWARE.
|
|
52
|
+
|
|
53
|
+
*/
|
|
54
|
+
|
|
55
|
+
#include <stdio.h>
|
|
56
|
+
#include <stdlib.h>
|
|
57
|
+
#include <string.h>
|
|
58
|
+
|
|
59
|
+
#include "char.h"
|
|
60
|
+
#include "d_string.h"
|
|
61
|
+
#include "epub.h"
|
|
62
|
+
#include "i18n.h"
|
|
63
|
+
#include "lexer.h"
|
|
64
|
+
#include "libMultiMarkdown.h"
|
|
65
|
+
#include "mmd.h"
|
|
66
|
+
#include "object_pool.h"
|
|
67
|
+
#include "opendocument.h"
|
|
68
|
+
#include "parser.h"
|
|
69
|
+
#include "scanners.h"
|
|
70
|
+
#include "stack.h"
|
|
71
|
+
#include "textbundle.h"
|
|
72
|
+
#include "token.h"
|
|
73
|
+
#include "token_pairs.h"
|
|
74
|
+
#include "writer.h"
|
|
75
|
+
#include "version.h"
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
// Basic parser function declarations
|
|
79
|
+
void * ParseAlloc();
|
|
80
|
+
void Parse();
|
|
81
|
+
void ParseFree();
|
|
82
|
+
void ParseTrace();
|
|
83
|
+
|
|
84
|
+
void mmd_pair_tokens_in_block(token * block, token_pair_engine * e, stack * s);
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
/// strdup() not available on all platforms
|
|
88
|
+
static char * my_strdup(const char * source) {
|
|
89
|
+
if (source == NULL) {
|
|
90
|
+
return NULL;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
char * result = malloc(strlen(source) + 1);
|
|
94
|
+
|
|
95
|
+
if (result) {
|
|
96
|
+
strcpy(result, source);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
return result;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
/// Build MMD Engine
|
|
104
|
+
mmd_engine * mmd_engine_create(DString * d, unsigned long extensions) {
|
|
105
|
+
mmd_engine * e = malloc(sizeof(mmd_engine));
|
|
106
|
+
|
|
107
|
+
if (e) {
|
|
108
|
+
e->dstr = d;
|
|
109
|
+
|
|
110
|
+
e->root = NULL;
|
|
111
|
+
|
|
112
|
+
e->extensions = extensions;
|
|
113
|
+
|
|
114
|
+
e->recurse_depth = 0;
|
|
115
|
+
|
|
116
|
+
e->allow_meta = (extensions & EXT_COMPATIBILITY) ? false : true;
|
|
117
|
+
|
|
118
|
+
e->language = LC_EN;
|
|
119
|
+
e->quotes_lang = ENGLISH;
|
|
120
|
+
|
|
121
|
+
e->abbreviation_stack = stack_new(0);
|
|
122
|
+
e->citation_stack = stack_new(0);
|
|
123
|
+
e->definition_stack = stack_new(0);
|
|
124
|
+
e->footnote_stack = stack_new(0);
|
|
125
|
+
e->glossary_stack = stack_new(0);
|
|
126
|
+
e->header_stack = stack_new(0);
|
|
127
|
+
e->link_stack = stack_new(0);
|
|
128
|
+
e->metadata_stack = stack_new(0);
|
|
129
|
+
e->table_stack = stack_new(0);
|
|
130
|
+
e->asset_hash = NULL;
|
|
131
|
+
|
|
132
|
+
e->pairings1 = token_pair_engine_new();
|
|
133
|
+
e->pairings2 = token_pair_engine_new();
|
|
134
|
+
e->pairings3 = token_pair_engine_new();
|
|
135
|
+
e->pairings4 = token_pair_engine_new();
|
|
136
|
+
|
|
137
|
+
// CriticMarkup
|
|
138
|
+
if (extensions & EXT_CRITIC) {
|
|
139
|
+
token_pair_engine_add_pairing(e->pairings1, CRITIC_ADD_OPEN, CRITIC_ADD_CLOSE, PAIR_CRITIC_ADD, PAIRING_ALLOW_EMPTY | PAIRING_PRUNE_MATCH);
|
|
140
|
+
token_pair_engine_add_pairing(e->pairings1, CRITIC_DEL_OPEN, CRITIC_DEL_CLOSE, PAIR_CRITIC_DEL, PAIRING_ALLOW_EMPTY | PAIRING_PRUNE_MATCH);
|
|
141
|
+
token_pair_engine_add_pairing(e->pairings1, CRITIC_COM_OPEN, CRITIC_COM_CLOSE, PAIR_CRITIC_COM, PAIRING_ALLOW_EMPTY | PAIRING_PRUNE_MATCH);
|
|
142
|
+
token_pair_engine_add_pairing(e->pairings1, CRITIC_SUB_OPEN, CRITIC_SUB_DIV_A, PAIR_CRITIC_SUB_DEL, PAIRING_ALLOW_EMPTY | PAIRING_PRUNE_MATCH);
|
|
143
|
+
token_pair_engine_add_pairing(e->pairings1, CRITIC_SUB_DIV_B, CRITIC_SUB_CLOSE, PAIR_CRITIC_SUB_ADD, PAIRING_ALLOW_EMPTY | PAIRING_PRUNE_MATCH);
|
|
144
|
+
token_pair_engine_add_pairing(e->pairings1, CRITIC_HI_OPEN, CRITIC_HI_CLOSE, PAIR_CRITIC_HI, PAIRING_ALLOW_EMPTY | PAIRING_PRUNE_MATCH);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// HTML Comments
|
|
148
|
+
token_pair_engine_add_pairing(e->pairings2, HTML_COMMENT_START, HTML_COMMENT_STOP, PAIR_HTML_COMMENT, PAIRING_ALLOW_EMPTY | PAIRING_PRUNE_MATCH);
|
|
149
|
+
|
|
150
|
+
// Brackets, Parentheses, Angles
|
|
151
|
+
token_pair_engine_add_pairing(e->pairings3, BRACKET_LEFT, BRACKET_RIGHT, PAIR_BRACKET, PAIRING_ALLOW_EMPTY | PAIRING_PRUNE_MATCH);
|
|
152
|
+
|
|
153
|
+
if (extensions & EXT_NOTES) {
|
|
154
|
+
token_pair_engine_add_pairing(e->pairings3, BRACKET_CITATION_LEFT, BRACKET_RIGHT, PAIR_BRACKET_CITATION, PAIRING_ALLOW_EMPTY | PAIRING_PRUNE_MATCH);
|
|
155
|
+
token_pair_engine_add_pairing(e->pairings3, BRACKET_FOOTNOTE_LEFT, BRACKET_RIGHT, PAIR_BRACKET_FOOTNOTE, PAIRING_ALLOW_EMPTY | PAIRING_PRUNE_MATCH);
|
|
156
|
+
token_pair_engine_add_pairing(e->pairings3, BRACKET_GLOSSARY_LEFT, BRACKET_RIGHT, PAIR_BRACKET_GLOSSARY, PAIRING_ALLOW_EMPTY | PAIRING_PRUNE_MATCH);
|
|
157
|
+
token_pair_engine_add_pairing(e->pairings3, BRACKET_ABBREVIATION_LEFT, BRACKET_RIGHT, PAIR_BRACKET_ABBREVIATION, PAIRING_ALLOW_EMPTY | PAIRING_PRUNE_MATCH);
|
|
158
|
+
} else {
|
|
159
|
+
token_pair_engine_add_pairing(e->pairings3, BRACKET_CITATION_LEFT, BRACKET_RIGHT, PAIR_BRACKET, PAIRING_ALLOW_EMPTY | PAIRING_PRUNE_MATCH);
|
|
160
|
+
token_pair_engine_add_pairing(e->pairings3, BRACKET_FOOTNOTE_LEFT, BRACKET_RIGHT, PAIR_BRACKET, PAIRING_ALLOW_EMPTY | PAIRING_PRUNE_MATCH);
|
|
161
|
+
token_pair_engine_add_pairing(e->pairings3, BRACKET_GLOSSARY_LEFT, BRACKET_RIGHT, PAIR_BRACKET, PAIRING_ALLOW_EMPTY | PAIRING_PRUNE_MATCH);
|
|
162
|
+
token_pair_engine_add_pairing(e->pairings3, BRACKET_ABBREVIATION_LEFT, BRACKET_RIGHT, PAIR_BRACKET, PAIRING_ALLOW_EMPTY | PAIRING_PRUNE_MATCH);
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
token_pair_engine_add_pairing(e->pairings3, BRACKET_VARIABLE_LEFT, BRACKET_RIGHT, PAIR_BRACKET_VARIABLE, PAIRING_ALLOW_EMPTY | PAIRING_PRUNE_MATCH);
|
|
166
|
+
|
|
167
|
+
token_pair_engine_add_pairing(e->pairings3, BRACKET_IMAGE_LEFT, BRACKET_RIGHT, PAIR_BRACKET_IMAGE, PAIRING_ALLOW_EMPTY | PAIRING_PRUNE_MATCH);
|
|
168
|
+
token_pair_engine_add_pairing(e->pairings3, PAREN_LEFT, PAREN_RIGHT, PAIR_PAREN, PAIRING_ALLOW_EMPTY | PAIRING_PRUNE_MATCH);
|
|
169
|
+
token_pair_engine_add_pairing(e->pairings3, ANGLE_LEFT, ANGLE_RIGHT, PAIR_ANGLE, PAIRING_ALLOW_EMPTY | PAIRING_PRUNE_MATCH);
|
|
170
|
+
token_pair_engine_add_pairing(e->pairings3, BRACE_DOUBLE_LEFT, BRACE_DOUBLE_RIGHT, PAIR_BRACES, PAIRING_ALLOW_EMPTY | PAIRING_PRUNE_MATCH);
|
|
171
|
+
|
|
172
|
+
// Strong/Emph
|
|
173
|
+
token_pair_engine_add_pairing(e->pairings4, STAR, STAR, PAIR_STAR, 0);
|
|
174
|
+
token_pair_engine_add_pairing(e->pairings4, UL, UL, PAIR_UL, 0);
|
|
175
|
+
|
|
176
|
+
// Quotes and Backticks
|
|
177
|
+
token_pair_engine_add_pairing(e->pairings3, BACKTICK, BACKTICK, PAIR_BACKTICK, PAIRING_PRUNE_MATCH | PAIRING_MATCH_LENGTH);
|
|
178
|
+
|
|
179
|
+
token_pair_engine_add_pairing(e->pairings4, BACKTICK, QUOTE_RIGHT_ALT, PAIR_QUOTE_ALT, PAIRING_ALLOW_EMPTY | PAIRING_MATCH_LENGTH);
|
|
180
|
+
token_pair_engine_add_pairing(e->pairings4, QUOTE_SINGLE, QUOTE_SINGLE, PAIR_QUOTE_SINGLE, PAIRING_ALLOW_EMPTY | PAIRING_PRUNE_MATCH);
|
|
181
|
+
token_pair_engine_add_pairing(e->pairings4, QUOTE_DOUBLE, QUOTE_DOUBLE, PAIR_QUOTE_DOUBLE, PAIRING_ALLOW_EMPTY | PAIRING_PRUNE_MATCH);
|
|
182
|
+
|
|
183
|
+
// Math
|
|
184
|
+
if (!(extensions & EXT_COMPATIBILITY)) {
|
|
185
|
+
token_pair_engine_add_pairing(e->pairings3, MATH_PAREN_OPEN, MATH_PAREN_CLOSE, PAIR_MATH, PAIRING_ALLOW_EMPTY | PAIRING_PRUNE_MATCH);
|
|
186
|
+
token_pair_engine_add_pairing(e->pairings3, MATH_BRACKET_OPEN, MATH_BRACKET_CLOSE, PAIR_MATH, PAIRING_ALLOW_EMPTY | PAIRING_PRUNE_MATCH);
|
|
187
|
+
token_pair_engine_add_pairing(e->pairings3, MATH_DOLLAR_SINGLE, MATH_DOLLAR_SINGLE, PAIR_MATH, PAIRING_ALLOW_EMPTY | PAIRING_PRUNE_MATCH);
|
|
188
|
+
token_pair_engine_add_pairing(e->pairings3, MATH_DOLLAR_DOUBLE, MATH_DOLLAR_DOUBLE, PAIR_MATH, PAIRING_ALLOW_EMPTY | PAIRING_PRUNE_MATCH);
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
// Superscript/Subscript
|
|
192
|
+
if (!(extensions & EXT_COMPATIBILITY)) {
|
|
193
|
+
token_pair_engine_add_pairing(e->pairings4, SUPERSCRIPT, SUPERSCRIPT, PAIR_SUPERSCRIPT, PAIRING_PRUNE_MATCH);
|
|
194
|
+
token_pair_engine_add_pairing(e->pairings4, SUBSCRIPT, SUBSCRIPT, PAIR_SUBSCRIPT, PAIRING_PRUNE_MATCH);
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
// Text Braces -- for raw text syntax
|
|
198
|
+
if (!(extensions & EXT_COMPATIBILITY)) {
|
|
199
|
+
token_pair_engine_add_pairing(e->pairings4, TEXT_BRACE_LEFT, TEXT_BRACE_RIGHT, PAIR_BRACE, PAIRING_PRUNE_MATCH);
|
|
200
|
+
token_pair_engine_add_pairing(e->pairings4, RAW_FILTER_LEFT, TEXT_BRACE_RIGHT, PAIR_RAW_FILTER, PAIRING_PRUNE_MATCH);
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
return e;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
/// Create MMD Engine using an existing DString (A new copy is *not* made)
|
|
208
|
+
mmd_engine * mmd_engine_create_with_dstring(DString * d, unsigned long extensions) {
|
|
209
|
+
return mmd_engine_create(d, extensions);
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
|
|
213
|
+
/// Create MMD Engine using a C string (A private copy of the string will be
|
|
214
|
+
/// made. The one passed here can be freed by the calling function)
|
|
215
|
+
mmd_engine * mmd_engine_create_with_string(const char * str, unsigned long extensions) {
|
|
216
|
+
DString * d = d_string_new(str);
|
|
217
|
+
|
|
218
|
+
return mmd_engine_create(d, extensions);
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
|
|
222
|
+
/// Set language and smart quotes language
|
|
223
|
+
void mmd_engine_set_language(mmd_engine * e, short language) {
|
|
224
|
+
if (!e) {
|
|
225
|
+
return;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
e->language = language;
|
|
229
|
+
|
|
230
|
+
switch (language) {
|
|
231
|
+
case LC_DE:
|
|
232
|
+
e->quotes_lang = GERMAN;
|
|
233
|
+
break;
|
|
234
|
+
|
|
235
|
+
case LC_EN:
|
|
236
|
+
e->quotes_lang = ENGLISH;
|
|
237
|
+
break;
|
|
238
|
+
|
|
239
|
+
case LC_ES:
|
|
240
|
+
e->quotes_lang = ENGLISH;
|
|
241
|
+
break;
|
|
242
|
+
|
|
243
|
+
case LC_FR:
|
|
244
|
+
e->quotes_lang = FRENCH;
|
|
245
|
+
break;
|
|
246
|
+
|
|
247
|
+
case LC_NL:
|
|
248
|
+
e->quotes_lang = DUTCH;
|
|
249
|
+
break;
|
|
250
|
+
|
|
251
|
+
case LC_SV:
|
|
252
|
+
e->quotes_lang = SWEDISH;
|
|
253
|
+
break;
|
|
254
|
+
|
|
255
|
+
default:
|
|
256
|
+
e->quotes_lang = ENGLISH;
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
|
|
261
|
+
void mmd_engine_reset(mmd_engine * e) {
|
|
262
|
+
if (e->root) {
|
|
263
|
+
token_tree_free(e->root);
|
|
264
|
+
e->root = NULL;
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
// Abbreviations need to be freed
|
|
268
|
+
while (e->abbreviation_stack->size) {
|
|
269
|
+
footnote_free(stack_pop(e->abbreviation_stack));
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
// Citations need to be freed
|
|
273
|
+
while (e->citation_stack->size) {
|
|
274
|
+
footnote_free(stack_pop(e->citation_stack));
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
// Footnotes need to be freed
|
|
278
|
+
while (e->footnote_stack->size) {
|
|
279
|
+
footnote_free(stack_pop(e->footnote_stack));
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
// Glossaries need to be freed
|
|
283
|
+
while (e->glossary_stack->size) {
|
|
284
|
+
footnote_free(stack_pop(e->glossary_stack));
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
// Links need to be freed
|
|
288
|
+
while (e->link_stack->size) {
|
|
289
|
+
link_free(stack_pop(e->link_stack));
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
// Metadata needs to be freed
|
|
293
|
+
while (e->metadata_stack->size) {
|
|
294
|
+
meta_free(stack_pop(e->metadata_stack));
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
// Free asset hash
|
|
298
|
+
asset * a, * a_tmp;
|
|
299
|
+
HASH_ITER(hh, e->asset_hash, a, a_tmp) {
|
|
300
|
+
HASH_DEL(e->asset_hash, a); // Remove item from hash
|
|
301
|
+
asset_free(a); // Free the asset
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
// Reset other stacks
|
|
305
|
+
e->definition_stack->size = 0;
|
|
306
|
+
e->header_stack->size = 0;
|
|
307
|
+
e->table_stack->size = 0;
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
|
|
311
|
+
/// Free an existing MMD Engine
|
|
312
|
+
void mmd_engine_free(mmd_engine * e, bool freeDString) {
|
|
313
|
+
if (e == NULL) {
|
|
314
|
+
return;
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
mmd_engine_reset(e);
|
|
318
|
+
|
|
319
|
+
if (freeDString) {
|
|
320
|
+
d_string_free(e->dstr, true);
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
token_pair_engine_free(e->pairings1);
|
|
324
|
+
token_pair_engine_free(e->pairings2);
|
|
325
|
+
token_pair_engine_free(e->pairings3);
|
|
326
|
+
token_pair_engine_free(e->pairings4);
|
|
327
|
+
|
|
328
|
+
// Pointers to blocks that are freed elsewhere
|
|
329
|
+
stack_free(e->definition_stack);
|
|
330
|
+
stack_free(e->header_stack);
|
|
331
|
+
stack_free(e->table_stack);
|
|
332
|
+
|
|
333
|
+
// Takedown
|
|
334
|
+
stack_free(e->abbreviation_stack);
|
|
335
|
+
stack_free(e->citation_stack);
|
|
336
|
+
stack_free(e->footnote_stack);
|
|
337
|
+
stack_free(e->glossary_stack);
|
|
338
|
+
stack_free(e->link_stack);
|
|
339
|
+
stack_free(e->metadata_stack);
|
|
340
|
+
|
|
341
|
+
free(e);
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
|
|
345
|
+
bool line_is_empty(token * t) {
|
|
346
|
+
while (t) {
|
|
347
|
+
switch (t->type) {
|
|
348
|
+
case NON_INDENT_SPACE:
|
|
349
|
+
case INDENT_TAB:
|
|
350
|
+
case INDENT_SPACE:
|
|
351
|
+
t = t->next;
|
|
352
|
+
break;
|
|
353
|
+
|
|
354
|
+
case TEXT_LINEBREAK:
|
|
355
|
+
case TEXT_NL:
|
|
356
|
+
return true;
|
|
357
|
+
|
|
358
|
+
default:
|
|
359
|
+
return false;
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
return true;
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
|
|
367
|
+
/// Determine what sort of line this is
|
|
368
|
+
void mmd_assign_line_type(mmd_engine * e, token * line) {
|
|
369
|
+
if (!line) {
|
|
370
|
+
return;
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
if (!line->child) {
|
|
374
|
+
line->type = LINE_EMPTY;
|
|
375
|
+
return;
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
const char * source = e->dstr->str;
|
|
379
|
+
|
|
380
|
+
token * t = NULL;
|
|
381
|
+
token * first_child = line->child;
|
|
382
|
+
|
|
383
|
+
short temp_short;
|
|
384
|
+
size_t scan_len;
|
|
385
|
+
|
|
386
|
+
// Skip non-indenting space
|
|
387
|
+
if (first_child->type == NON_INDENT_SPACE) {
|
|
388
|
+
//token_remove_first_child(line);
|
|
389
|
+
first_child = first_child->next;
|
|
390
|
+
} else if (first_child->type == TEXT_PLAIN && first_child->len == 1) {
|
|
391
|
+
if (source[first_child->start] == ' ') {
|
|
392
|
+
//token_remove_first_child(line);
|
|
393
|
+
first_child = first_child->next;
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
if (first_child == NULL) {
|
|
398
|
+
line->type = LINE_EMPTY;
|
|
399
|
+
return;
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
switch (first_child->type) {
|
|
403
|
+
case INDENT_TAB:
|
|
404
|
+
if (line_is_empty(first_child)) {
|
|
405
|
+
line->type = LINE_EMPTY;
|
|
406
|
+
e->allow_meta = false;
|
|
407
|
+
} else {
|
|
408
|
+
line->type = LINE_INDENTED_TAB;
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
break;
|
|
412
|
+
|
|
413
|
+
case INDENT_SPACE:
|
|
414
|
+
if (line_is_empty(first_child)) {
|
|
415
|
+
line->type = LINE_EMPTY;
|
|
416
|
+
e->allow_meta = false;
|
|
417
|
+
} else {
|
|
418
|
+
line->type = LINE_INDENTED_SPACE;
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
break;
|
|
422
|
+
|
|
423
|
+
case ANGLE_LEFT:
|
|
424
|
+
if (scan_html_block(&source[line->start])) {
|
|
425
|
+
line->type = LINE_HTML;
|
|
426
|
+
} else {
|
|
427
|
+
line->type = LINE_PLAIN;
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
break;
|
|
431
|
+
|
|
432
|
+
case ANGLE_RIGHT:
|
|
433
|
+
line->type = LINE_BLOCKQUOTE;
|
|
434
|
+
first_child->type = MARKER_BLOCKQUOTE;
|
|
435
|
+
break;
|
|
436
|
+
|
|
437
|
+
case BACKTICK:
|
|
438
|
+
if (e->extensions & EXT_COMPATIBILITY) {
|
|
439
|
+
line->type = LINE_PLAIN;
|
|
440
|
+
break;
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
scan_len = scan_fence_end(&source[first_child->start]);
|
|
444
|
+
|
|
445
|
+
if (scan_len) {
|
|
446
|
+
switch (first_child->len) {
|
|
447
|
+
case 3:
|
|
448
|
+
line->type = LINE_FENCE_BACKTICK_3;
|
|
449
|
+
break;
|
|
450
|
+
|
|
451
|
+
case 4:
|
|
452
|
+
line->type = LINE_FENCE_BACKTICK_4;
|
|
453
|
+
break;
|
|
454
|
+
|
|
455
|
+
default:
|
|
456
|
+
line->type = LINE_FENCE_BACKTICK_5;
|
|
457
|
+
break;
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
break;
|
|
461
|
+
} else {
|
|
462
|
+
scan_len = scan_fence_start(&source[first_child->start]);
|
|
463
|
+
|
|
464
|
+
if (scan_len) {
|
|
465
|
+
switch (first_child->len) {
|
|
466
|
+
case 3:
|
|
467
|
+
line->type = LINE_FENCE_BACKTICK_START_3;
|
|
468
|
+
break;
|
|
469
|
+
|
|
470
|
+
case 4:
|
|
471
|
+
line->type = LINE_FENCE_BACKTICK_START_4;
|
|
472
|
+
break;
|
|
473
|
+
|
|
474
|
+
default:
|
|
475
|
+
line->type = LINE_FENCE_BACKTICK_START_5;
|
|
476
|
+
break;
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
break;
|
|
480
|
+
}
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
line->type = LINE_PLAIN;
|
|
484
|
+
break;
|
|
485
|
+
|
|
486
|
+
case COLON:
|
|
487
|
+
line->type = LINE_PLAIN;
|
|
488
|
+
|
|
489
|
+
if (e->extensions & EXT_COMPATIBILITY) {
|
|
490
|
+
break;
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
if (scan_definition(&source[first_child->start])) {
|
|
494
|
+
line->type = LINE_DEFINITION;
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
break;
|
|
498
|
+
|
|
499
|
+
case HASH1:
|
|
500
|
+
case HASH2:
|
|
501
|
+
case HASH3:
|
|
502
|
+
case HASH4:
|
|
503
|
+
case HASH5:
|
|
504
|
+
case HASH6:
|
|
505
|
+
if (scan_atx(&source[first_child->start])) {
|
|
506
|
+
line->type = (first_child->type - HASH1) + LINE_ATX_1;
|
|
507
|
+
first_child->type = (line->type - LINE_ATX_1) + MARKER_H1;
|
|
508
|
+
|
|
509
|
+
// Strip trailing whitespace from '#' sequence
|
|
510
|
+
first_child->len = first_child->type - MARKER_H1 + 1;
|
|
511
|
+
|
|
512
|
+
// Strip trailing '#' sequence if present
|
|
513
|
+
if (line->child->tail->type == TEXT_NL) {
|
|
514
|
+
if ((line->child->tail->prev->type >= HASH1) &&
|
|
515
|
+
(line->child->tail->prev->type <= HASH6)) {
|
|
516
|
+
line->child->tail->prev->type -= HASH1;
|
|
517
|
+
line->child->tail->prev->type += MARKER_H1;
|
|
518
|
+
}
|
|
519
|
+
} else {
|
|
520
|
+
if ((line->child->tail->type >= HASH1) &&
|
|
521
|
+
(line->child->tail->type <= HASH6)) {
|
|
522
|
+
line->child->tail->type -= TEXT_EMPTY;
|
|
523
|
+
line->child->tail->type += MARKER_H1;
|
|
524
|
+
}
|
|
525
|
+
}
|
|
526
|
+
} else {
|
|
527
|
+
line->type = LINE_PLAIN;
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
break;
|
|
531
|
+
|
|
532
|
+
case HTML_COMMENT_START:
|
|
533
|
+
if (!first_child->next || !first_child->next->next) {
|
|
534
|
+
line->type = LINE_START_COMMENT;
|
|
535
|
+
} else {
|
|
536
|
+
line->type = LINE_PLAIN;
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
break;
|
|
540
|
+
|
|
541
|
+
case HTML_COMMENT_STOP:
|
|
542
|
+
if (!first_child->next || !first_child->next->next) {
|
|
543
|
+
line->type = LINE_STOP_COMMENT;
|
|
544
|
+
} else {
|
|
545
|
+
line->type = LINE_PLAIN;
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
break;
|
|
549
|
+
|
|
550
|
+
case TEXT_NUMBER_POSS_LIST:
|
|
551
|
+
if (first_child->next) {
|
|
552
|
+
switch (source[first_child->next->start]) {
|
|
553
|
+
case ' ':
|
|
554
|
+
case '\t':
|
|
555
|
+
line->type = LINE_LIST_ENUMERATED;
|
|
556
|
+
first_child->type = MARKER_LIST_ENUMERATOR;
|
|
557
|
+
|
|
558
|
+
switch (first_child->next->type) {
|
|
559
|
+
case TEXT_PLAIN:
|
|
560
|
+
|
|
561
|
+
// Strip whitespace between bullet and text
|
|
562
|
+
while (char_is_whitespace(source[first_child->next->start])) {
|
|
563
|
+
first_child->next->start++;
|
|
564
|
+
first_child->next->len--;
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
break;
|
|
568
|
+
|
|
569
|
+
case INDENT_SPACE:
|
|
570
|
+
case INDENT_TAB:
|
|
571
|
+
case NON_INDENT_SPACE:
|
|
572
|
+
t = first_child;
|
|
573
|
+
|
|
574
|
+
while (t->next && ((t->next->type == INDENT_SPACE) ||
|
|
575
|
+
(t->next->type == INDENT_TAB) ||
|
|
576
|
+
(t->next->type == NON_INDENT_SPACE))) {
|
|
577
|
+
tokens_prune(t->next, t->next);
|
|
578
|
+
}
|
|
579
|
+
|
|
580
|
+
break;
|
|
581
|
+
}
|
|
582
|
+
|
|
583
|
+
break;
|
|
584
|
+
|
|
585
|
+
default:
|
|
586
|
+
line->type = LINE_PLAIN;
|
|
587
|
+
first_child->type = TEXT_PLAIN;
|
|
588
|
+
break;
|
|
589
|
+
}
|
|
590
|
+
} else {
|
|
591
|
+
line->type = LINE_LIST_ENUMERATED;
|
|
592
|
+
first_child->type = MARKER_LIST_ENUMERATOR;
|
|
593
|
+
}
|
|
594
|
+
|
|
595
|
+
break;
|
|
596
|
+
|
|
597
|
+
case EQUAL:
|
|
598
|
+
|
|
599
|
+
// Could this be a setext heading marker?
|
|
600
|
+
if (scan_setext(&source[first_child->start])) {
|
|
601
|
+
line->type = LINE_SETEXT_1;
|
|
602
|
+
} else {
|
|
603
|
+
line->type = LINE_PLAIN;
|
|
604
|
+
}
|
|
605
|
+
|
|
606
|
+
break;
|
|
607
|
+
|
|
608
|
+
case DASH_N:
|
|
609
|
+
case DASH_M:
|
|
610
|
+
if (scan_setext(&source[first_child->start])) {
|
|
611
|
+
line->type = LINE_SETEXT_2;
|
|
612
|
+
break;
|
|
613
|
+
}
|
|
614
|
+
|
|
615
|
+
case STAR:
|
|
616
|
+
case UL:
|
|
617
|
+
// Could this be a horizontal rule?
|
|
618
|
+
t = first_child->next;
|
|
619
|
+
temp_short = first_child->len;
|
|
620
|
+
|
|
621
|
+
while (t) {
|
|
622
|
+
switch (t->type) {
|
|
623
|
+
case DASH_N:
|
|
624
|
+
case DASH_M:
|
|
625
|
+
if (t->type == first_child->type) {
|
|
626
|
+
t = t->next;
|
|
627
|
+
|
|
628
|
+
if (t) {
|
|
629
|
+
temp_short += t->len;
|
|
630
|
+
}
|
|
631
|
+
} else {
|
|
632
|
+
temp_short = 0;
|
|
633
|
+
t = NULL;
|
|
634
|
+
}
|
|
635
|
+
|
|
636
|
+
break;
|
|
637
|
+
|
|
638
|
+
case STAR:
|
|
639
|
+
case UL:
|
|
640
|
+
if (t->type == first_child->type) {
|
|
641
|
+
t = t->next;
|
|
642
|
+
temp_short++;
|
|
643
|
+
} else {
|
|
644
|
+
temp_short = 0;
|
|
645
|
+
t = NULL;
|
|
646
|
+
}
|
|
647
|
+
|
|
648
|
+
break;
|
|
649
|
+
|
|
650
|
+
case NON_INDENT_SPACE:
|
|
651
|
+
case INDENT_TAB:
|
|
652
|
+
case INDENT_SPACE:
|
|
653
|
+
t = t->next;
|
|
654
|
+
break;
|
|
655
|
+
|
|
656
|
+
case TEXT_PLAIN:
|
|
657
|
+
if ((t->len == 1) && (source[t->start] == ' ')) {
|
|
658
|
+
t = t->next;
|
|
659
|
+
break;
|
|
660
|
+
}
|
|
661
|
+
|
|
662
|
+
temp_short = 0;
|
|
663
|
+
t = NULL;
|
|
664
|
+
break;
|
|
665
|
+
|
|
666
|
+
case TEXT_NL:
|
|
667
|
+
case TEXT_LINEBREAK:
|
|
668
|
+
t = NULL;
|
|
669
|
+
break;
|
|
670
|
+
|
|
671
|
+
default:
|
|
672
|
+
temp_short = 0;
|
|
673
|
+
t = NULL;
|
|
674
|
+
break;
|
|
675
|
+
}
|
|
676
|
+
}
|
|
677
|
+
|
|
678
|
+
if (temp_short > 2) {
|
|
679
|
+
// This is a horizontal rule, not a list item
|
|
680
|
+
line->type = LINE_HR;
|
|
681
|
+
break;
|
|
682
|
+
}
|
|
683
|
+
|
|
684
|
+
if (first_child->type == UL) {
|
|
685
|
+
// Revert to plain for this type
|
|
686
|
+
line->type = LINE_PLAIN;
|
|
687
|
+
break;
|
|
688
|
+
}
|
|
689
|
+
|
|
690
|
+
// If longer than 1 character, then it can't be a list marker, so it's a
|
|
691
|
+
// plain line
|
|
692
|
+
if (first_child->len > 1) {
|
|
693
|
+
line->type = LINE_PLAIN;
|
|
694
|
+
break;
|
|
695
|
+
}
|
|
696
|
+
|
|
697
|
+
case PLUS:
|
|
698
|
+
if (!first_child->next) {
|
|
699
|
+
// TODO: Should this be an empty list item instead??
|
|
700
|
+
line->type = LINE_PLAIN;
|
|
701
|
+
} else {
|
|
702
|
+
switch (source[first_child->next->start]) {
|
|
703
|
+
case ' ':
|
|
704
|
+
case '\t':
|
|
705
|
+
line->type = LINE_LIST_BULLETED;
|
|
706
|
+
first_child->type = MARKER_LIST_BULLET;
|
|
707
|
+
|
|
708
|
+
switch (first_child->next->type) {
|
|
709
|
+
case TEXT_PLAIN:
|
|
710
|
+
|
|
711
|
+
// Strip whitespace between bullet and text
|
|
712
|
+
while (char_is_whitespace(source[first_child->next->start])) {
|
|
713
|
+
first_child->next->start++;
|
|
714
|
+
first_child->next->len--;
|
|
715
|
+
}
|
|
716
|
+
|
|
717
|
+
break;
|
|
718
|
+
|
|
719
|
+
case INDENT_SPACE:
|
|
720
|
+
case INDENT_TAB:
|
|
721
|
+
case NON_INDENT_SPACE:
|
|
722
|
+
t = first_child;
|
|
723
|
+
|
|
724
|
+
while (t->next && ((t->next->type == INDENT_SPACE) ||
|
|
725
|
+
(t->next->type == INDENT_TAB) ||
|
|
726
|
+
(t->next->type == NON_INDENT_SPACE))) {
|
|
727
|
+
tokens_prune(t->next, t->next);
|
|
728
|
+
}
|
|
729
|
+
|
|
730
|
+
break;
|
|
731
|
+
}
|
|
732
|
+
|
|
733
|
+
break;
|
|
734
|
+
|
|
735
|
+
default:
|
|
736
|
+
line->type = LINE_PLAIN;
|
|
737
|
+
break;
|
|
738
|
+
}
|
|
739
|
+
}
|
|
740
|
+
|
|
741
|
+
break;
|
|
742
|
+
|
|
743
|
+
case TEXT_LINEBREAK:
|
|
744
|
+
case TEXT_NL:
|
|
745
|
+
e->allow_meta = false;
|
|
746
|
+
line->type = LINE_EMPTY;
|
|
747
|
+
break;
|
|
748
|
+
|
|
749
|
+
case TOC:
|
|
750
|
+
line->type = (e->extensions & EXT_COMPATIBILITY) ? LINE_PLAIN : LINE_TOC;
|
|
751
|
+
break;
|
|
752
|
+
|
|
753
|
+
case BRACKET_LEFT:
|
|
754
|
+
if (e->extensions & EXT_COMPATIBILITY) {
|
|
755
|
+
scan_len = scan_ref_link_no_attributes(&source[line->start]);
|
|
756
|
+
line->type = (scan_len) ? LINE_DEF_LINK : LINE_PLAIN;
|
|
757
|
+
} else {
|
|
758
|
+
scan_len = scan_ref_link(&source[line->start]);
|
|
759
|
+
line->type = (scan_len) ? LINE_DEF_LINK : LINE_PLAIN;
|
|
760
|
+
}
|
|
761
|
+
|
|
762
|
+
break;
|
|
763
|
+
|
|
764
|
+
case BRACKET_ABBREVIATION_LEFT:
|
|
765
|
+
if (e->extensions & EXT_NOTES) {
|
|
766
|
+
scan_len = scan_ref_abbreviation(&source[line->start]);
|
|
767
|
+
line->type = (scan_len) ? LINE_DEF_ABBREVIATION : LINE_PLAIN;
|
|
768
|
+
} else {
|
|
769
|
+
scan_len = scan_ref_link_no_attributes(&source[line->start]);
|
|
770
|
+
line->type = (scan_len) ? LINE_DEF_LINK : LINE_PLAIN;
|
|
771
|
+
}
|
|
772
|
+
|
|
773
|
+
break;
|
|
774
|
+
|
|
775
|
+
case BRACKET_CITATION_LEFT:
|
|
776
|
+
if (e->extensions & EXT_NOTES) {
|
|
777
|
+
scan_len = scan_ref_citation(&source[line->start]);
|
|
778
|
+
line->type = (scan_len) ? LINE_DEF_CITATION : LINE_PLAIN;
|
|
779
|
+
} else {
|
|
780
|
+
scan_len = scan_ref_link_no_attributes(&source[line->start]);
|
|
781
|
+
line->type = (scan_len) ? LINE_DEF_LINK : LINE_PLAIN;
|
|
782
|
+
}
|
|
783
|
+
|
|
784
|
+
break;
|
|
785
|
+
|
|
786
|
+
case BRACKET_FOOTNOTE_LEFT:
|
|
787
|
+
if (e->extensions & EXT_NOTES) {
|
|
788
|
+
scan_len = scan_ref_foot(&source[line->start]);
|
|
789
|
+
line->type = (scan_len) ? LINE_DEF_FOOTNOTE : LINE_PLAIN;
|
|
790
|
+
} else {
|
|
791
|
+
scan_len = scan_ref_link_no_attributes(&source[line->start]);
|
|
792
|
+
line->type = (scan_len) ? LINE_DEF_LINK : LINE_PLAIN;
|
|
793
|
+
}
|
|
794
|
+
|
|
795
|
+
break;
|
|
796
|
+
|
|
797
|
+
case BRACKET_GLOSSARY_LEFT:
|
|
798
|
+
if (e->extensions & EXT_NOTES) {
|
|
799
|
+
scan_len = scan_ref_glossary(&source[line->start]);
|
|
800
|
+
line->type = (scan_len) ? LINE_DEF_GLOSSARY : LINE_PLAIN;
|
|
801
|
+
} else {
|
|
802
|
+
scan_len = scan_ref_link_no_attributes(&source[line->start]);
|
|
803
|
+
line->type = (scan_len) ? LINE_DEF_LINK : LINE_PLAIN;
|
|
804
|
+
}
|
|
805
|
+
|
|
806
|
+
break;
|
|
807
|
+
|
|
808
|
+
case PIPE:
|
|
809
|
+
|
|
810
|
+
// If PIPE is first, save checking later and assign LINE_TABLE now
|
|
811
|
+
if (!(e->extensions & EXT_COMPATIBILITY)) {
|
|
812
|
+
scan_len = scan_table_separator(&source[line->start]);
|
|
813
|
+
line->type = (scan_len) ? LINE_TABLE_SEPARATOR : LINE_TABLE;
|
|
814
|
+
|
|
815
|
+
break;
|
|
816
|
+
}
|
|
817
|
+
|
|
818
|
+
case TEXT_PLAIN:
|
|
819
|
+
if (e->allow_meta && !(e->extensions & EXT_COMPATIBILITY)) {
|
|
820
|
+
scan_len = scan_url(&source[line->start]);
|
|
821
|
+
|
|
822
|
+
if (scan_len == 0) {
|
|
823
|
+
scan_len = scan_meta_line(&source[line->start]);
|
|
824
|
+
line->type = (scan_len) ? LINE_META : LINE_PLAIN;
|
|
825
|
+
break;
|
|
826
|
+
}
|
|
827
|
+
}
|
|
828
|
+
|
|
829
|
+
default:
|
|
830
|
+
line->type = LINE_PLAIN;
|
|
831
|
+
break;
|
|
832
|
+
}
|
|
833
|
+
|
|
834
|
+
if ((line->type == LINE_PLAIN) &&
|
|
835
|
+
!(e->extensions & EXT_COMPATIBILITY)) {
|
|
836
|
+
// Check if this is a potential table line
|
|
837
|
+
token * walker = first_child;
|
|
838
|
+
|
|
839
|
+
while (walker != NULL) {
|
|
840
|
+
if (walker->type == PIPE) {
|
|
841
|
+
scan_len = scan_table_separator(&source[line->start]);
|
|
842
|
+
line->type = (scan_len) ? LINE_TABLE_SEPARATOR : LINE_TABLE;
|
|
843
|
+
|
|
844
|
+
return;
|
|
845
|
+
}
|
|
846
|
+
|
|
847
|
+
walker = walker->next;
|
|
848
|
+
}
|
|
849
|
+
}
|
|
850
|
+
}
|
|
851
|
+
|
|
852
|
+
|
|
853
|
+
/// Strip leading indenting space from line (if present)
|
|
854
|
+
void deindent_line(token * line) {
|
|
855
|
+
if (!line || !line->child) {
|
|
856
|
+
return;
|
|
857
|
+
}
|
|
858
|
+
|
|
859
|
+
token * t;
|
|
860
|
+
|
|
861
|
+
switch (line->child->type) {
|
|
862
|
+
case INDENT_TAB:
|
|
863
|
+
case INDENT_SPACE:
|
|
864
|
+
t = line->child;
|
|
865
|
+
line->child = t->next;
|
|
866
|
+
t->next = NULL;
|
|
867
|
+
|
|
868
|
+
if (line->child) {
|
|
869
|
+
line->child->prev = NULL;
|
|
870
|
+
line->child->tail = t->tail;
|
|
871
|
+
}
|
|
872
|
+
|
|
873
|
+
token_free(t);
|
|
874
|
+
break;
|
|
875
|
+
}
|
|
876
|
+
}
|
|
877
|
+
|
|
878
|
+
|
|
879
|
+
/// Strip leading indenting space from block
|
|
880
|
+
/// (for recursively parsing nested lists)
|
|
881
|
+
void deindent_block(mmd_engine * e, token * block) {
|
|
882
|
+
if (!block || !block->child) {
|
|
883
|
+
return;
|
|
884
|
+
}
|
|
885
|
+
|
|
886
|
+
token * t = block->child;
|
|
887
|
+
|
|
888
|
+
while (t != NULL) {
|
|
889
|
+
deindent_line(t);
|
|
890
|
+
mmd_assign_line_type(e, t);
|
|
891
|
+
|
|
892
|
+
t = t->next;
|
|
893
|
+
}
|
|
894
|
+
}
|
|
895
|
+
|
|
896
|
+
|
|
897
|
+
/// Strip leading blockquote marker from line
|
|
898
|
+
void strip_quote_markers_from_line(token * line, const char * source) {
|
|
899
|
+
if (!line || !line->child) {
|
|
900
|
+
return;
|
|
901
|
+
}
|
|
902
|
+
|
|
903
|
+
token * t;
|
|
904
|
+
|
|
905
|
+
switch (line->child->type) {
|
|
906
|
+
case MARKER_BLOCKQUOTE:
|
|
907
|
+
case NON_INDENT_SPACE:
|
|
908
|
+
t = line->child;
|
|
909
|
+
line->child = t->next;
|
|
910
|
+
t->next = NULL;
|
|
911
|
+
|
|
912
|
+
if (line->child) {
|
|
913
|
+
line->child->prev = NULL;
|
|
914
|
+
line->child->tail = t->tail;
|
|
915
|
+
}
|
|
916
|
+
|
|
917
|
+
token_free(t);
|
|
918
|
+
break;
|
|
919
|
+
}
|
|
920
|
+
|
|
921
|
+
if (line->child && (line->child->type == TEXT_PLAIN)) {
|
|
922
|
+
// Strip leading whitespace from first text token
|
|
923
|
+
t = line->child;
|
|
924
|
+
|
|
925
|
+
while (t->len && char_is_whitespace(source[t->start])) {
|
|
926
|
+
t->start++;
|
|
927
|
+
t->len--;
|
|
928
|
+
}
|
|
929
|
+
|
|
930
|
+
if (t->len == 0) {
|
|
931
|
+
line->child = t->next;
|
|
932
|
+
t->next = NULL;
|
|
933
|
+
|
|
934
|
+
if (line->child) {
|
|
935
|
+
line->child->prev = NULL;
|
|
936
|
+
line->child->tail = t->tail;
|
|
937
|
+
}
|
|
938
|
+
|
|
939
|
+
token_free(t);
|
|
940
|
+
}
|
|
941
|
+
}
|
|
942
|
+
}
|
|
943
|
+
|
|
944
|
+
|
|
945
|
+
/// Strip leading blockquote markers and non-indent space
|
|
946
|
+
/// (for recursively parsing blockquotes)
|
|
947
|
+
void strip_quote_markers_from_block(mmd_engine * e, token * block) {
|
|
948
|
+
if (!block || !block->child) {
|
|
949
|
+
return;
|
|
950
|
+
}
|
|
951
|
+
|
|
952
|
+
token * t = block->child;
|
|
953
|
+
|
|
954
|
+
while (t != NULL) {
|
|
955
|
+
strip_quote_markers_from_line(t, e->dstr->str);
|
|
956
|
+
mmd_assign_line_type(e, t);
|
|
957
|
+
|
|
958
|
+
t = t->next;
|
|
959
|
+
}
|
|
960
|
+
}
|
|
961
|
+
|
|
962
|
+
|
|
963
|
+
/// Create a token chain from source string
|
|
964
|
+
/// stop_on_empty_line allows us to stop parsing part of the way through
|
|
965
|
+
token * mmd_tokenize_string(mmd_engine * e, size_t start, size_t len, bool stop_on_empty_line) {
|
|
966
|
+
// Reset metadata flag
|
|
967
|
+
e->allow_meta = (e->extensions & EXT_COMPATIBILITY) ? false : true;
|
|
968
|
+
|
|
969
|
+
|
|
970
|
+
// Create a scanner (for re2c)
|
|
971
|
+
Scanner s;
|
|
972
|
+
s.start = &e->dstr->str[start];
|
|
973
|
+
s.cur = s.start;
|
|
974
|
+
|
|
975
|
+
// Strip trailing whitespace
|
|
976
|
+
// while (len && char_is_whitespace_or_line_ending(str[len - 1]))
|
|
977
|
+
// len--;
|
|
978
|
+
|
|
979
|
+
// Where do we stop parsing?
|
|
980
|
+
const char * stop = &e->dstr->str[start] + len;
|
|
981
|
+
|
|
982
|
+
int type; // TOKEN type
|
|
983
|
+
token * t; // Create tokens for incorporation
|
|
984
|
+
|
|
985
|
+
token * root = token_new(0, start, 0); // Store the final parse tree here
|
|
986
|
+
token * line = token_new(0, start, 0); // Store current line here
|
|
987
|
+
|
|
988
|
+
const char * last_stop = &e->dstr->str[start]; // Remember where last token ended
|
|
989
|
+
|
|
990
|
+
do {
|
|
991
|
+
// Scan for next token (type of 0 means there is nothing left);
|
|
992
|
+
type = scan(&s, stop);
|
|
993
|
+
|
|
994
|
+
//if (type && s.start != last_stop) {
|
|
995
|
+
if (s.start != last_stop) {
|
|
996
|
+
// We skipped characters between tokens
|
|
997
|
+
|
|
998
|
+
if (type) {
|
|
999
|
+
// Create a default token type for the skipped characters
|
|
1000
|
+
t = token_new(TEXT_PLAIN, (size_t)(last_stop - e->dstr->str), (size_t)(s.start - last_stop));
|
|
1001
|
+
|
|
1002
|
+
token_append_child(line, t);
|
|
1003
|
+
} else {
|
|
1004
|
+
if (stop > last_stop) {
|
|
1005
|
+
// Source text ends without newline
|
|
1006
|
+
t = token_new(TEXT_PLAIN, (size_t)(last_stop - e->dstr->str), (size_t)(stop - last_stop));
|
|
1007
|
+
|
|
1008
|
+
token_append_child(line, t);
|
|
1009
|
+
}
|
|
1010
|
+
}
|
|
1011
|
+
} else if (type == 0 && stop > last_stop) {
|
|
1012
|
+
// Source text ends without newline
|
|
1013
|
+
t = token_new(TEXT_PLAIN, (size_t)(last_stop - e->dstr->str), (size_t)(stop - last_stop));
|
|
1014
|
+
token_append_child(line, t);
|
|
1015
|
+
}
|
|
1016
|
+
|
|
1017
|
+
|
|
1018
|
+
switch (type) {
|
|
1019
|
+
case 0:
|
|
1020
|
+
// 0 means we finished with input
|
|
1021
|
+
// Add current line to root
|
|
1022
|
+
|
|
1023
|
+
// What sort of line is this?
|
|
1024
|
+
mmd_assign_line_type(e, line);
|
|
1025
|
+
|
|
1026
|
+
token_append_child(root, line);
|
|
1027
|
+
break;
|
|
1028
|
+
|
|
1029
|
+
case TEXT_NL_SP:
|
|
1030
|
+
case TEXT_LINEBREAK_SP:
|
|
1031
|
+
case TEXT_LINEBREAK:
|
|
1032
|
+
case TEXT_NL:
|
|
1033
|
+
|
|
1034
|
+
// We hit the end of a line
|
|
1035
|
+
switch (type) {
|
|
1036
|
+
case TEXT_NL_SP:
|
|
1037
|
+
t = token_new(TEXT_NL, (size_t)(s.start - e->dstr->str), (size_t)(s.cur - s.start) - 1);
|
|
1038
|
+
break;
|
|
1039
|
+
|
|
1040
|
+
case TEXT_LINEBREAK_SP:
|
|
1041
|
+
t = token_new(TEXT_LINEBREAK, (size_t)(s.start - e->dstr->str), (size_t)(s.cur - s.start) - 1);
|
|
1042
|
+
break;
|
|
1043
|
+
|
|
1044
|
+
default:
|
|
1045
|
+
t = token_new(type, (size_t)(s.start - e->dstr->str), (size_t)(s.cur - s.start));
|
|
1046
|
+
break;
|
|
1047
|
+
}
|
|
1048
|
+
|
|
1049
|
+
token_append_child(line, t);
|
|
1050
|
+
|
|
1051
|
+
// What sort of line is this?
|
|
1052
|
+
mmd_assign_line_type(e, line);
|
|
1053
|
+
|
|
1054
|
+
token_append_child(root, line);
|
|
1055
|
+
|
|
1056
|
+
// If this is first line, do we have proper metadata?
|
|
1057
|
+
if (e->allow_meta && root->child == line) {
|
|
1058
|
+
if (line->type == LINE_SETEXT_2) {
|
|
1059
|
+
line->type = LINE_YAML;
|
|
1060
|
+
} else if (line->type != LINE_META) {
|
|
1061
|
+
e->allow_meta = false;
|
|
1062
|
+
}
|
|
1063
|
+
}
|
|
1064
|
+
|
|
1065
|
+
if (stop_on_empty_line) {
|
|
1066
|
+
if (line->type == LINE_EMPTY) {
|
|
1067
|
+
return root;
|
|
1068
|
+
}
|
|
1069
|
+
}
|
|
1070
|
+
|
|
1071
|
+
switch (type) {
|
|
1072
|
+
case TEXT_NL_SP:
|
|
1073
|
+
line = token_new(0, s.cur - e->dstr->str - 1, 0);
|
|
1074
|
+
t = token_new(NON_INDENT_SPACE, (size_t)(s.cur - e->dstr->str - 1), 1);
|
|
1075
|
+
token_append_child(line, t);
|
|
1076
|
+
break;
|
|
1077
|
+
|
|
1078
|
+
case TEXT_LINEBREAK_SP:
|
|
1079
|
+
line = token_new(0, s.cur - e->dstr->str - 1, 0);
|
|
1080
|
+
t = token_new(NON_INDENT_SPACE, (size_t)(s.cur - e->dstr->str - 1), 1);
|
|
1081
|
+
token_append_child(line, t);
|
|
1082
|
+
break;
|
|
1083
|
+
|
|
1084
|
+
default:
|
|
1085
|
+
line = token_new(0, s.cur - e->dstr->str, 0);
|
|
1086
|
+
break;
|
|
1087
|
+
}
|
|
1088
|
+
|
|
1089
|
+
break;
|
|
1090
|
+
|
|
1091
|
+
default:
|
|
1092
|
+
t = token_new(type, (size_t)(s.start - e->dstr->str), (size_t)(s.cur - s.start));
|
|
1093
|
+
token_append_child(line, t);
|
|
1094
|
+
break;
|
|
1095
|
+
}
|
|
1096
|
+
|
|
1097
|
+
// Remember where token ends to detect skipped characters
|
|
1098
|
+
last_stop = s.cur;
|
|
1099
|
+
} while (type != 0);
|
|
1100
|
+
|
|
1101
|
+
|
|
1102
|
+
return root;
|
|
1103
|
+
}
|
|
1104
|
+
|
|
1105
|
+
|
|
1106
|
+
/// Parse token tree
|
|
1107
|
+
void mmd_parse_token_chain(mmd_engine * e, token * chain) {
|
|
1108
|
+
|
|
1109
|
+
if (e->recurse_depth == kMaxParseRecursiveDepth) {
|
|
1110
|
+
return;
|
|
1111
|
+
}
|
|
1112
|
+
|
|
1113
|
+
e->recurse_depth++;
|
|
1114
|
+
|
|
1115
|
+
void* pParser = ParseAlloc (malloc); // Create a parser (for lemon)
|
|
1116
|
+
token * walker = chain->child; // Walk the existing tree
|
|
1117
|
+
token * remainder; // Hold unparsed tail of chain
|
|
1118
|
+
|
|
1119
|
+
#ifndef NDEBUG
|
|
1120
|
+
ParseTrace(stderr, "parser >>");
|
|
1121
|
+
#endif
|
|
1122
|
+
|
|
1123
|
+
// Remove existing token tree
|
|
1124
|
+
e->root = NULL;
|
|
1125
|
+
|
|
1126
|
+
while (walker != NULL) {
|
|
1127
|
+
remainder = walker->next;
|
|
1128
|
+
|
|
1129
|
+
// Snip token from remainder
|
|
1130
|
+
walker->next = NULL;
|
|
1131
|
+
walker->tail = walker;
|
|
1132
|
+
|
|
1133
|
+
if (remainder) {
|
|
1134
|
+
remainder->prev = NULL;
|
|
1135
|
+
}
|
|
1136
|
+
|
|
1137
|
+
#ifndef NDEBUG
|
|
1138
|
+
fprintf(stderr, "\nNew line\n");
|
|
1139
|
+
#endif
|
|
1140
|
+
|
|
1141
|
+
Parse(pParser, walker->type, walker, e);
|
|
1142
|
+
|
|
1143
|
+
walker = remainder;
|
|
1144
|
+
}
|
|
1145
|
+
|
|
1146
|
+
// Signal finish to parser
|
|
1147
|
+
#ifndef NDEBUG
|
|
1148
|
+
fprintf(stderr, "\nFinish parse\n");
|
|
1149
|
+
#endif
|
|
1150
|
+
Parse(pParser, 0, NULL, e);
|
|
1151
|
+
|
|
1152
|
+
// Disconnect of (now empty) root
|
|
1153
|
+
chain->child = NULL;
|
|
1154
|
+
token_append_child(chain, e->root);
|
|
1155
|
+
e->root = NULL;
|
|
1156
|
+
|
|
1157
|
+
ParseFree(pParser, free);
|
|
1158
|
+
|
|
1159
|
+
e->recurse_depth--;
|
|
1160
|
+
}
|
|
1161
|
+
|
|
1162
|
+
|
|
1163
|
+
void mmd_pair_tokens_in_chain(token * head, token_pair_engine * e, stack * s) {
|
|
1164
|
+
|
|
1165
|
+
while (head != NULL) {
|
|
1166
|
+
mmd_pair_tokens_in_block(head, e, s);
|
|
1167
|
+
|
|
1168
|
+
head = head->next;
|
|
1169
|
+
}
|
|
1170
|
+
}
|
|
1171
|
+
|
|
1172
|
+
|
|
1173
|
+
/// Match token pairs inside block
|
|
1174
|
+
void mmd_pair_tokens_in_block(token * block, token_pair_engine * e, stack * s) {
|
|
1175
|
+
if (block == NULL || e == NULL) {
|
|
1176
|
+
return;
|
|
1177
|
+
}
|
|
1178
|
+
|
|
1179
|
+
switch (block->type) {
|
|
1180
|
+
case BLOCK_BLOCKQUOTE:
|
|
1181
|
+
case BLOCK_DEFLIST:
|
|
1182
|
+
case BLOCK_DEFINITION:
|
|
1183
|
+
case BLOCK_DEF_ABBREVIATION:
|
|
1184
|
+
case BLOCK_DEF_CITATION:
|
|
1185
|
+
case BLOCK_DEF_FOOTNOTE:
|
|
1186
|
+
case BLOCK_DEF_GLOSSARY:
|
|
1187
|
+
case BLOCK_DEF_LINK:
|
|
1188
|
+
case BLOCK_H1:
|
|
1189
|
+
case BLOCK_H2:
|
|
1190
|
+
case BLOCK_H3:
|
|
1191
|
+
case BLOCK_H4:
|
|
1192
|
+
case BLOCK_H5:
|
|
1193
|
+
case BLOCK_H6:
|
|
1194
|
+
case BLOCK_PARA:
|
|
1195
|
+
case BLOCK_SETEXT_1:
|
|
1196
|
+
case BLOCK_SETEXT_2:
|
|
1197
|
+
case BLOCK_TERM:
|
|
1198
|
+
token_pairs_match_pairs_inside_token(block, e, s, 0);
|
|
1199
|
+
break;
|
|
1200
|
+
|
|
1201
|
+
case DOC_START_TOKEN:
|
|
1202
|
+
case BLOCK_LIST_BULLETED:
|
|
1203
|
+
case BLOCK_LIST_BULLETED_LOOSE:
|
|
1204
|
+
case BLOCK_LIST_ENUMERATED:
|
|
1205
|
+
case BLOCK_LIST_ENUMERATED_LOOSE:
|
|
1206
|
+
mmd_pair_tokens_in_chain(block->child, e, s);
|
|
1207
|
+
break;
|
|
1208
|
+
|
|
1209
|
+
case BLOCK_LIST_ITEM:
|
|
1210
|
+
case BLOCK_LIST_ITEM_TIGHT:
|
|
1211
|
+
token_pairs_match_pairs_inside_token(block, e, s, 0);
|
|
1212
|
+
mmd_pair_tokens_in_chain(block->child, e, s);
|
|
1213
|
+
break;
|
|
1214
|
+
|
|
1215
|
+
case LINE_TABLE:
|
|
1216
|
+
case BLOCK_TABLE:
|
|
1217
|
+
// TODO: Need to parse into cells first
|
|
1218
|
+
token_pairs_match_pairs_inside_token(block, e, s, 0);
|
|
1219
|
+
mmd_pair_tokens_in_chain(block->child, e, s);
|
|
1220
|
+
break;
|
|
1221
|
+
|
|
1222
|
+
case BLOCK_EMPTY:
|
|
1223
|
+
case BLOCK_CODE_INDENTED:
|
|
1224
|
+
case BLOCK_CODE_FENCED:
|
|
1225
|
+
default:
|
|
1226
|
+
// Nothing to do here
|
|
1227
|
+
return;
|
|
1228
|
+
}
|
|
1229
|
+
}
|
|
1230
|
+
|
|
1231
|
+
|
|
1232
|
+
/// Ambidextrous tokens can open OR close a pair. This routine gives the opportunity
|
|
1233
|
+
/// to change this behavior on case-by-case basis. For example, in `foo **bar** foo`, the
|
|
1234
|
+
/// first set of asterisks can open, but not close a pair. The second set can close, but not
|
|
1235
|
+
/// open a pair. This allows for complex behavior without having to bog down the tokenizer
|
|
1236
|
+
/// with figuring out which type of asterisk we have. Default behavior is that open and close
|
|
1237
|
+
/// are enabled, so we just have to figure out when to turn it off.
|
|
1238
|
+
void mmd_assign_ambidextrous_tokens_in_block(mmd_engine * e, token * block, size_t start_offset) {
|
|
1239
|
+
if (block == NULL || block->child == NULL) {
|
|
1240
|
+
return;
|
|
1241
|
+
}
|
|
1242
|
+
|
|
1243
|
+
size_t offset; // Temp variable for use below
|
|
1244
|
+
size_t lead_count, lag_count, pre_count, post_count;
|
|
1245
|
+
|
|
1246
|
+
token * t = block->child;
|
|
1247
|
+
|
|
1248
|
+
char * str = e->dstr->str;
|
|
1249
|
+
|
|
1250
|
+
while (t != NULL) {
|
|
1251
|
+
switch (t->type) {
|
|
1252
|
+
case BLOCK_META:
|
|
1253
|
+
|
|
1254
|
+
// Do we treat this like metadata?
|
|
1255
|
+
if (!(e->extensions & EXT_COMPATIBILITY) &&
|
|
1256
|
+
!(e->extensions & EXT_NO_METADATA)) {
|
|
1257
|
+
break;
|
|
1258
|
+
}
|
|
1259
|
+
|
|
1260
|
+
// This is not metadata
|
|
1261
|
+
t->type = BLOCK_PARA;
|
|
1262
|
+
|
|
1263
|
+
case DOC_START_TOKEN:
|
|
1264
|
+
case BLOCK_BLOCKQUOTE:
|
|
1265
|
+
case BLOCK_DEF_ABBREVIATION:
|
|
1266
|
+
case BLOCK_DEFLIST:
|
|
1267
|
+
case BLOCK_DEFINITION:
|
|
1268
|
+
case BLOCK_H1:
|
|
1269
|
+
case BLOCK_H2:
|
|
1270
|
+
case BLOCK_H3:
|
|
1271
|
+
case BLOCK_H4:
|
|
1272
|
+
case BLOCK_H5:
|
|
1273
|
+
case BLOCK_H6:
|
|
1274
|
+
case BLOCK_LIST_BULLETED:
|
|
1275
|
+
case BLOCK_LIST_BULLETED_LOOSE:
|
|
1276
|
+
case BLOCK_LIST_ENUMERATED:
|
|
1277
|
+
case BLOCK_LIST_ENUMERATED_LOOSE:
|
|
1278
|
+
case BLOCK_LIST_ITEM:
|
|
1279
|
+
case BLOCK_LIST_ITEM_TIGHT:
|
|
1280
|
+
case BLOCK_PARA:
|
|
1281
|
+
case BLOCK_SETEXT_1:
|
|
1282
|
+
case BLOCK_SETEXT_2:
|
|
1283
|
+
case BLOCK_TABLE:
|
|
1284
|
+
case BLOCK_TERM:
|
|
1285
|
+
case LINE_LIST_BULLETED:
|
|
1286
|
+
case LINE_LIST_ENUMERATED:
|
|
1287
|
+
// Assign child tokens of blocks
|
|
1288
|
+
mmd_assign_ambidextrous_tokens_in_block(e, t, start_offset);
|
|
1289
|
+
break;
|
|
1290
|
+
|
|
1291
|
+
case CRITIC_SUB_DIV:
|
|
1292
|
+
// Divide this into two tokens
|
|
1293
|
+
t->child = token_new(CRITIC_SUB_DIV_B, t->start + 1, 1);
|
|
1294
|
+
t->child->next = t->next;
|
|
1295
|
+
t->next = t->child;
|
|
1296
|
+
t->child = NULL;
|
|
1297
|
+
t->len = 1;
|
|
1298
|
+
t->type = CRITIC_SUB_DIV_A;
|
|
1299
|
+
break;
|
|
1300
|
+
|
|
1301
|
+
case STAR:
|
|
1302
|
+
// Look left and skip over neighboring '*' characters
|
|
1303
|
+
offset = t->start;
|
|
1304
|
+
|
|
1305
|
+
while ((offset != 0) && ((str[offset] == '*') || (str[offset] == '_'))) {
|
|
1306
|
+
offset--;
|
|
1307
|
+
}
|
|
1308
|
+
|
|
1309
|
+
// We can only close if there is something to left besides whitespace
|
|
1310
|
+
if ((offset == 0) || (char_is_whitespace_or_line_ending(str[offset]))) {
|
|
1311
|
+
// Whitespace or punctuation to left, so can't close
|
|
1312
|
+
t->can_close = 0;
|
|
1313
|
+
}
|
|
1314
|
+
|
|
1315
|
+
// Look right and skip over neighboring '*' characters
|
|
1316
|
+
offset = t->start + 1;
|
|
1317
|
+
|
|
1318
|
+
while ((str[offset] == '*') || (str[offset] == '_')) {
|
|
1319
|
+
offset++;
|
|
1320
|
+
}
|
|
1321
|
+
|
|
1322
|
+
// We can only open if there is something to right besides whitespace/punctuation
|
|
1323
|
+
if (char_is_whitespace_or_line_ending(str[offset])) {
|
|
1324
|
+
// Whitespace to right, so can't open
|
|
1325
|
+
t->can_open = 0;
|
|
1326
|
+
}
|
|
1327
|
+
|
|
1328
|
+
// If we're in the middle of a word, then we need to be more precise
|
|
1329
|
+
if (t->can_open && t->can_close) {
|
|
1330
|
+
lead_count = 0; //!< '*' in run before current
|
|
1331
|
+
lag_count = 0; //!< '*' in run after current
|
|
1332
|
+
pre_count = 0; //!< '*' before word
|
|
1333
|
+
post_count = 0; //!< '*' after word
|
|
1334
|
+
|
|
1335
|
+
offset = t->start - 1;
|
|
1336
|
+
|
|
1337
|
+
// How many '*' in this run before current token?
|
|
1338
|
+
while (offset && (str[offset] == '*')) {
|
|
1339
|
+
lead_count++;
|
|
1340
|
+
offset--;
|
|
1341
|
+
}
|
|
1342
|
+
|
|
1343
|
+
// Skip over letters/numbers
|
|
1344
|
+
// TODO: Need to fix this to actually get run at beginning of word, not in middle,
|
|
1345
|
+
// e.g. **foo*bar*foo*bar**
|
|
1346
|
+
while (offset && (!char_is_whitespace_or_line_ending_or_punctuation(str[offset]))) {
|
|
1347
|
+
offset--;
|
|
1348
|
+
}
|
|
1349
|
+
|
|
1350
|
+
// Are there '*' at the beginning of this word?
|
|
1351
|
+
while ((offset != -1) && (str[offset] == '*')) {
|
|
1352
|
+
pre_count++;
|
|
1353
|
+
offset--;
|
|
1354
|
+
}
|
|
1355
|
+
|
|
1356
|
+
offset = t->start + 1;
|
|
1357
|
+
|
|
1358
|
+
// How many '*' in this run after current token?
|
|
1359
|
+
while (str[offset] == '*') {
|
|
1360
|
+
lag_count++;
|
|
1361
|
+
offset++;
|
|
1362
|
+
}
|
|
1363
|
+
|
|
1364
|
+
// Skip over letters/numbers
|
|
1365
|
+
// TODO: Same as above
|
|
1366
|
+
while (!char_is_whitespace_or_line_ending_or_punctuation(str[offset])) {
|
|
1367
|
+
offset++;
|
|
1368
|
+
}
|
|
1369
|
+
|
|
1370
|
+
// Are there '*' at the end of this word?
|
|
1371
|
+
while (offset && (str[offset] == '*')) {
|
|
1372
|
+
post_count++;
|
|
1373
|
+
offset++;
|
|
1374
|
+
}
|
|
1375
|
+
|
|
1376
|
+
// Are there '*' before/after word?
|
|
1377
|
+
if (pre_count + post_count > 0) {
|
|
1378
|
+
if (pre_count + post_count == lead_count + lag_count + 1) {
|
|
1379
|
+
// Same number outside as in the current run
|
|
1380
|
+
// **foo****bar**
|
|
1381
|
+
if (pre_count == post_count) {
|
|
1382
|
+
// **foo****bar**
|
|
1383
|
+
// We want to wrap the word, since
|
|
1384
|
+
// <strong>foo</strong><strong>bar</strong> doesn't make sense
|
|
1385
|
+
t->can_open = 0;
|
|
1386
|
+
t->can_close = 0;
|
|
1387
|
+
} else if (pre_count == 0) {
|
|
1388
|
+
// foo**bar**
|
|
1389
|
+
// Open only so we don't close outside the word
|
|
1390
|
+
t->can_close = 0;
|
|
1391
|
+
} else if (post_count == 0) {
|
|
1392
|
+
// **foo**bar
|
|
1393
|
+
// Close only so we don't close outside the word
|
|
1394
|
+
t->can_open = 0;
|
|
1395
|
+
}
|
|
1396
|
+
} else if (pre_count == lead_count + lag_count + 1 + post_count) {
|
|
1397
|
+
// ***foo**bar*
|
|
1398
|
+
// We want to close what's open
|
|
1399
|
+
t->can_open = 0;
|
|
1400
|
+
} else if (post_count == pre_count + lead_count + lag_count + 1) {
|
|
1401
|
+
// *foo**bar***
|
|
1402
|
+
// We want to open a set to close at the end
|
|
1403
|
+
t->can_close = 0;
|
|
1404
|
+
} else {
|
|
1405
|
+
if (pre_count != lead_count + lag_count + 1) {
|
|
1406
|
+
// **foo**bar -> close, otherwise don't
|
|
1407
|
+
t->can_close = 0;
|
|
1408
|
+
}
|
|
1409
|
+
|
|
1410
|
+
if (post_count != lead_count + lag_count + 1) {
|
|
1411
|
+
// foo**bar** -> open, otherwise don't
|
|
1412
|
+
t->can_open = 0;
|
|
1413
|
+
}
|
|
1414
|
+
}
|
|
1415
|
+
}
|
|
1416
|
+
}
|
|
1417
|
+
|
|
1418
|
+
break;
|
|
1419
|
+
|
|
1420
|
+
case UL:
|
|
1421
|
+
// Look left and skip over neighboring '_' characters
|
|
1422
|
+
offset = t->start;
|
|
1423
|
+
|
|
1424
|
+
while ((offset != 0) && ((str[offset] == '_') || (str[offset] == '*'))) {
|
|
1425
|
+
offset--;
|
|
1426
|
+
}
|
|
1427
|
+
|
|
1428
|
+
if ((offset == 0) || (char_is_whitespace_or_line_ending(str[offset]))) {
|
|
1429
|
+
// Whitespace to left, so can't close
|
|
1430
|
+
t->can_close = 0;
|
|
1431
|
+
}
|
|
1432
|
+
|
|
1433
|
+
// We don't allow intraword underscores (e.g. `foo_bar_foo`)
|
|
1434
|
+
if ((offset > 0) && (char_is_alphanumeric(str[offset]))) {
|
|
1435
|
+
// Letters to left, so can't open
|
|
1436
|
+
t->can_open = 0;
|
|
1437
|
+
}
|
|
1438
|
+
|
|
1439
|
+
// Look right and skip over neighboring '_' characters
|
|
1440
|
+
offset = t->start + 1;
|
|
1441
|
+
|
|
1442
|
+
while ((str[offset] == '*') || (str[offset] == '_')) {
|
|
1443
|
+
offset++;
|
|
1444
|
+
}
|
|
1445
|
+
|
|
1446
|
+
if (char_is_whitespace_or_line_ending(str[offset])) {
|
|
1447
|
+
// Whitespace to right, so can't open
|
|
1448
|
+
t->can_open = 0;
|
|
1449
|
+
}
|
|
1450
|
+
|
|
1451
|
+
if (char_is_alphanumeric(str[offset])) {
|
|
1452
|
+
// Letters to right, so can't close
|
|
1453
|
+
t->can_close = 0;
|
|
1454
|
+
}
|
|
1455
|
+
|
|
1456
|
+
break;
|
|
1457
|
+
|
|
1458
|
+
case BACKTICK:
|
|
1459
|
+
// Backticks are used for code spans, but also for ``foo'' double quote syntax.
|
|
1460
|
+
// We care only about the quote syntax.
|
|
1461
|
+
offset = t->start;
|
|
1462
|
+
|
|
1463
|
+
// TODO: This does potentially prevent ``foo `` from closing due to space before closer?
|
|
1464
|
+
// Bug or feature??
|
|
1465
|
+
if (t->len != 2) {
|
|
1466
|
+
break;
|
|
1467
|
+
}
|
|
1468
|
+
|
|
1469
|
+
if ((offset == 0) || (str[offset] != '`' && char_is_whitespace_or_line_ending_or_punctuation(str[offset - 1]))) {
|
|
1470
|
+
// Whitespace or punctuation to left, so can't close
|
|
1471
|
+
t->can_close = 0;
|
|
1472
|
+
}
|
|
1473
|
+
|
|
1474
|
+
break;
|
|
1475
|
+
|
|
1476
|
+
case QUOTE_SINGLE:
|
|
1477
|
+
// Some of these are actually APOSTROPHE's and should not be paired
|
|
1478
|
+
offset = t->start;
|
|
1479
|
+
|
|
1480
|
+
if (!((offset == 0) ||
|
|
1481
|
+
(char_is_whitespace_or_line_ending_or_punctuation(str[offset - 1])) ||
|
|
1482
|
+
(char_is_whitespace_or_line_ending_or_punctuation(str[offset + 1])))) {
|
|
1483
|
+
t->type = APOSTROPHE;
|
|
1484
|
+
break;
|
|
1485
|
+
}
|
|
1486
|
+
|
|
1487
|
+
if (offset && (char_is_punctuation(str[offset - 1])) &&
|
|
1488
|
+
(char_is_alphanumeric(str[offset + 1]))) {
|
|
1489
|
+
// If possessive apostrophe, e.g. `x`'s
|
|
1490
|
+
if (str[offset + 1] == 's' || str[offset + 1] == 'S') {
|
|
1491
|
+
if (char_is_whitespace_or_line_ending_or_punctuation(str[offset + 2])) {
|
|
1492
|
+
t->type = APOSTROPHE;
|
|
1493
|
+
break;
|
|
1494
|
+
}
|
|
1495
|
+
}
|
|
1496
|
+
}
|
|
1497
|
+
|
|
1498
|
+
case QUOTE_DOUBLE:
|
|
1499
|
+
offset = t->start;
|
|
1500
|
+
|
|
1501
|
+
if ((offset == 0) || (char_is_whitespace_or_line_ending(str[offset - 1]))) {
|
|
1502
|
+
t->can_close = 0;
|
|
1503
|
+
}
|
|
1504
|
+
|
|
1505
|
+
if (char_is_whitespace_or_line_ending(str[offset + 1])) {
|
|
1506
|
+
t->can_open = 0;
|
|
1507
|
+
}
|
|
1508
|
+
|
|
1509
|
+
break;
|
|
1510
|
+
|
|
1511
|
+
case DASH_N:
|
|
1512
|
+
if (!(e->extensions & EXT_SMART)) {
|
|
1513
|
+
break;
|
|
1514
|
+
}
|
|
1515
|
+
|
|
1516
|
+
// We want `1-2` to trigger a DASH_N, but regular hyphen otherwise (`a-b`)
|
|
1517
|
+
// This doesn't apply to `--` or `---`
|
|
1518
|
+
offset = t->start;
|
|
1519
|
+
|
|
1520
|
+
if (t->len == 1) {
|
|
1521
|
+
// Check whether we have '1-2'
|
|
1522
|
+
if ((offset == 0) || (!char_is_digit(str[offset - 1])) ||
|
|
1523
|
+
(!char_is_digit(str[offset + 1]))) {
|
|
1524
|
+
t->type = TEXT_PLAIN;
|
|
1525
|
+
}
|
|
1526
|
+
}
|
|
1527
|
+
|
|
1528
|
+
break;
|
|
1529
|
+
|
|
1530
|
+
case MATH_DOLLAR_SINGLE:
|
|
1531
|
+
case MATH_DOLLAR_DOUBLE:
|
|
1532
|
+
if (e->extensions & EXT_COMPATIBILITY) {
|
|
1533
|
+
break;
|
|
1534
|
+
}
|
|
1535
|
+
|
|
1536
|
+
offset = t->start;
|
|
1537
|
+
|
|
1538
|
+
// Look left
|
|
1539
|
+
if ((offset == 0) || (char_is_whitespace_or_line_ending(str[offset - 1]))) {
|
|
1540
|
+
// Whitespace to left, so can't close
|
|
1541
|
+
t->can_close = 0;
|
|
1542
|
+
} else if ((offset != 0) && (!char_is_whitespace_or_line_ending_or_punctuation(str[offset - 1]))) {
|
|
1543
|
+
// No whitespace or punctuation to left, can't open
|
|
1544
|
+
t->can_open = 0;
|
|
1545
|
+
}
|
|
1546
|
+
|
|
1547
|
+
// Look right
|
|
1548
|
+
offset = t->start + t->len;
|
|
1549
|
+
|
|
1550
|
+
if (char_is_whitespace_or_line_ending(str[offset])) {
|
|
1551
|
+
// Whitespace to right, so can't open
|
|
1552
|
+
t->can_open = 0;
|
|
1553
|
+
} else if (!char_is_whitespace_or_line_ending_or_punctuation(str[offset])) {
|
|
1554
|
+
// No whitespace or punctuation to right, can't close
|
|
1555
|
+
t->can_close = 0;
|
|
1556
|
+
}
|
|
1557
|
+
|
|
1558
|
+
break;
|
|
1559
|
+
|
|
1560
|
+
case SUPERSCRIPT:
|
|
1561
|
+
case SUBSCRIPT:
|
|
1562
|
+
if (e->extensions & EXT_COMPATIBILITY) {
|
|
1563
|
+
t->type = TEXT_PLAIN;
|
|
1564
|
+
break;
|
|
1565
|
+
}
|
|
1566
|
+
|
|
1567
|
+
offset = t->start;
|
|
1568
|
+
|
|
1569
|
+
// Look left -- no whitespace to left
|
|
1570
|
+
if ((offset == 0) || (char_is_whitespace_or_line_ending_or_punctuation(str[offset - 1]))) {
|
|
1571
|
+
t->can_open = 0;
|
|
1572
|
+
}
|
|
1573
|
+
|
|
1574
|
+
if ((offset != 0) && (char_is_whitespace_or_line_ending(str[offset - 1]))) {
|
|
1575
|
+
t->can_close = 0;
|
|
1576
|
+
}
|
|
1577
|
+
|
|
1578
|
+
offset = t->start + t->len;
|
|
1579
|
+
|
|
1580
|
+
if (char_is_whitespace_or_line_ending_or_punctuation(str[offset])) {
|
|
1581
|
+
t->can_open = 0;
|
|
1582
|
+
}
|
|
1583
|
+
|
|
1584
|
+
// We need to be contiguous in order to match
|
|
1585
|
+
if (t->can_close) {
|
|
1586
|
+
offset = t->start;
|
|
1587
|
+
t->can_close = 0;
|
|
1588
|
+
|
|
1589
|
+
while ((offset > 0) && !(char_is_whitespace_or_line_ending(str[offset - 1]))) {
|
|
1590
|
+
if (str[offset - 1] == str[t->start]) {
|
|
1591
|
+
t->can_close = 1;
|
|
1592
|
+
break;
|
|
1593
|
+
}
|
|
1594
|
+
|
|
1595
|
+
offset--;
|
|
1596
|
+
}
|
|
1597
|
+
}
|
|
1598
|
+
|
|
1599
|
+
// We need to be contiguous in order to match
|
|
1600
|
+
if (t->can_open) {
|
|
1601
|
+
offset = t->start + t->len;
|
|
1602
|
+
t->can_open = 0;
|
|
1603
|
+
|
|
1604
|
+
while (!(char_is_whitespace_or_line_ending(str[offset]))) {
|
|
1605
|
+
if (str[offset] == str[t->start]) {
|
|
1606
|
+
t->can_open = 1;
|
|
1607
|
+
break;
|
|
1608
|
+
}
|
|
1609
|
+
|
|
1610
|
+
offset++;
|
|
1611
|
+
}
|
|
1612
|
+
|
|
1613
|
+
// Are we a standalone, e.g x^2
|
|
1614
|
+
if (!t->can_close && !t->can_open) {
|
|
1615
|
+
offset = t->start + t->len;
|
|
1616
|
+
|
|
1617
|
+
while (!char_is_whitespace_or_line_ending_or_punctuation(str[offset])) {
|
|
1618
|
+
offset++;
|
|
1619
|
+
}
|
|
1620
|
+
|
|
1621
|
+
t->len = offset - t->start;
|
|
1622
|
+
t->can_close = 0;
|
|
1623
|
+
|
|
1624
|
+
// Shift next token right and move those characters as child node
|
|
1625
|
+
// It's possible that one (or more?) tokens are entirely subsumed.
|
|
1626
|
+
while (t->next && t->next->start + t->next->len < offset) {
|
|
1627
|
+
tokens_prune(t->next, t->next);
|
|
1628
|
+
}
|
|
1629
|
+
|
|
1630
|
+
if ((t->next != NULL) && ((t->next->type == TEXT_PLAIN) || (t->next->type == TEXT_NUMBER_POSS_LIST))) {
|
|
1631
|
+
t->next->len = t->next->start + t->next->len - offset;
|
|
1632
|
+
t->next->start = offset;
|
|
1633
|
+
}
|
|
1634
|
+
|
|
1635
|
+
t->child = token_new(TEXT_PLAIN, t->start + 1, t->len - 1);
|
|
1636
|
+
}
|
|
1637
|
+
}
|
|
1638
|
+
|
|
1639
|
+
break;
|
|
1640
|
+
}
|
|
1641
|
+
|
|
1642
|
+
t = t->next;
|
|
1643
|
+
}
|
|
1644
|
+
|
|
1645
|
+
}
|
|
1646
|
+
|
|
1647
|
+
|
|
1648
|
+
/// Strong/emph parsing is done using single `*` and `_` characters, which are
|
|
1649
|
+
/// then combined in a separate routine here to determine when
|
|
1650
|
+
/// consecutive characters should be interpreted as STRONG instead of EMPH
|
|
1651
|
+
/// \todo: Perhaps combining this with the routine when they are paired
|
|
1652
|
+
/// would improve performance?
|
|
1653
|
+
void pair_emphasis_tokens(token * t) {
|
|
1654
|
+
token * closer;
|
|
1655
|
+
|
|
1656
|
+
while (t != NULL) {
|
|
1657
|
+
if (t->mate != NULL) {
|
|
1658
|
+
switch (t->type) {
|
|
1659
|
+
case STAR:
|
|
1660
|
+
case UL:
|
|
1661
|
+
closer = t->mate;
|
|
1662
|
+
|
|
1663
|
+
if (t->next &&
|
|
1664
|
+
(t->next->mate == closer->prev) &&
|
|
1665
|
+
(t->type == t->next->type) &&
|
|
1666
|
+
(t->next->mate != t) &&
|
|
1667
|
+
(t->start + t->len == t->next->start) &&
|
|
1668
|
+
(closer->start == closer->prev->start + closer->prev->len)) {
|
|
1669
|
+
|
|
1670
|
+
// We have a strong pair
|
|
1671
|
+
t->type = STRONG_START;
|
|
1672
|
+
t->len = 2;
|
|
1673
|
+
closer->type = STRONG_STOP;
|
|
1674
|
+
closer->len = 2;
|
|
1675
|
+
closer->start--;
|
|
1676
|
+
|
|
1677
|
+
tokens_prune(t->next, t->next);
|
|
1678
|
+
tokens_prune(closer->prev, closer->prev);
|
|
1679
|
+
|
|
1680
|
+
token_prune_graft(t, closer, PAIR_STRONG);
|
|
1681
|
+
} else {
|
|
1682
|
+
t->type = EMPH_START;
|
|
1683
|
+
closer->type = EMPH_STOP;
|
|
1684
|
+
token_prune_graft(t, closer, PAIR_EMPH);
|
|
1685
|
+
}
|
|
1686
|
+
|
|
1687
|
+
break;
|
|
1688
|
+
|
|
1689
|
+
default:
|
|
1690
|
+
break;
|
|
1691
|
+
}
|
|
1692
|
+
|
|
1693
|
+
}
|
|
1694
|
+
|
|
1695
|
+
if (t->child != NULL) {
|
|
1696
|
+
switch (t->type) {
|
|
1697
|
+
case PAIR_BACKTICK:
|
|
1698
|
+
case PAIR_MATH:
|
|
1699
|
+
break;
|
|
1700
|
+
|
|
1701
|
+
default:
|
|
1702
|
+
pair_emphasis_tokens(t->child);
|
|
1703
|
+
break;
|
|
1704
|
+
}
|
|
1705
|
+
}
|
|
1706
|
+
|
|
1707
|
+
t = t->next;
|
|
1708
|
+
}
|
|
1709
|
+
}
|
|
1710
|
+
|
|
1711
|
+
|
|
1712
|
+
void recursive_parse_list_item(mmd_engine * e, token * block) {
|
|
1713
|
+
token * marker = token_copy(block->child->child);
|
|
1714
|
+
|
|
1715
|
+
// Strip list marker from first line
|
|
1716
|
+
token_remove_first_child(block->child);
|
|
1717
|
+
|
|
1718
|
+
// Remove one indent level from all lines to allow recursive parsing
|
|
1719
|
+
deindent_block(e, block);
|
|
1720
|
+
|
|
1721
|
+
mmd_parse_token_chain(e, block);
|
|
1722
|
+
|
|
1723
|
+
// Insert marker back in place
|
|
1724
|
+
marker->next = block->child->child;
|
|
1725
|
+
|
|
1726
|
+
if (block->child->child) {
|
|
1727
|
+
block->child->child->prev = marker;
|
|
1728
|
+
}
|
|
1729
|
+
|
|
1730
|
+
block->child->child = marker;
|
|
1731
|
+
}
|
|
1732
|
+
|
|
1733
|
+
|
|
1734
|
+
void recursive_parse_indent(mmd_engine * e, token * block) {
|
|
1735
|
+
// Remove one indent level from all lines to allow recursive parsing
|
|
1736
|
+
deindent_block(e, block);
|
|
1737
|
+
|
|
1738
|
+
// First line is now plain text
|
|
1739
|
+
block->child->type = LINE_PLAIN;
|
|
1740
|
+
|
|
1741
|
+
// Strip tokens?
|
|
1742
|
+
switch (block->type) {
|
|
1743
|
+
case BLOCK_DEFINITION:
|
|
1744
|
+
// Strip leading ':' from definition
|
|
1745
|
+
token_remove_first_child(block->child);
|
|
1746
|
+
break;
|
|
1747
|
+
}
|
|
1748
|
+
|
|
1749
|
+
mmd_parse_token_chain(e, block);
|
|
1750
|
+
}
|
|
1751
|
+
|
|
1752
|
+
|
|
1753
|
+
void is_list_loose(token * list) {
|
|
1754
|
+
bool loose = false;
|
|
1755
|
+
|
|
1756
|
+
token * walker = list->child;
|
|
1757
|
+
|
|
1758
|
+
if (walker == NULL) {
|
|
1759
|
+
return;
|
|
1760
|
+
}
|
|
1761
|
+
|
|
1762
|
+
while (walker->next != NULL) {
|
|
1763
|
+
if (walker->type == BLOCK_LIST_ITEM) {
|
|
1764
|
+
if (walker->child->type == BLOCK_PARA) {
|
|
1765
|
+
loose = true;
|
|
1766
|
+
} else {
|
|
1767
|
+
walker->type = BLOCK_LIST_ITEM_TIGHT;
|
|
1768
|
+
}
|
|
1769
|
+
}
|
|
1770
|
+
|
|
1771
|
+
walker = walker->next;
|
|
1772
|
+
}
|
|
1773
|
+
|
|
1774
|
+
if (loose) {
|
|
1775
|
+
switch (list->type) {
|
|
1776
|
+
case BLOCK_LIST_BULLETED:
|
|
1777
|
+
list->type = BLOCK_LIST_BULLETED_LOOSE;
|
|
1778
|
+
break;
|
|
1779
|
+
|
|
1780
|
+
case BLOCK_LIST_ENUMERATED:
|
|
1781
|
+
list->type = BLOCK_LIST_ENUMERATED_LOOSE;
|
|
1782
|
+
break;
|
|
1783
|
+
}
|
|
1784
|
+
}
|
|
1785
|
+
}
|
|
1786
|
+
|
|
1787
|
+
|
|
1788
|
+
/// Is this actually an HTML block?
|
|
1789
|
+
void is_para_html(mmd_engine * e, token * block) {
|
|
1790
|
+
if ((block == NULL) ||
|
|
1791
|
+
(block->child == NULL) ||
|
|
1792
|
+
(block->child->type != LINE_PLAIN)) {
|
|
1793
|
+
return;
|
|
1794
|
+
}
|
|
1795
|
+
|
|
1796
|
+
token * t = block->child->child;
|
|
1797
|
+
|
|
1798
|
+
if (t->type == ANGLE_LEFT || t->type == HTML_COMMENT_START) {
|
|
1799
|
+
if (scan_html_block(&(e->dstr->str[t->start]))) {
|
|
1800
|
+
block->type = BLOCK_HTML;
|
|
1801
|
+
return;
|
|
1802
|
+
}
|
|
1803
|
+
|
|
1804
|
+
if (scan_html_line(&(e->dstr->str[t->start]))) {
|
|
1805
|
+
block->type = BLOCK_HTML;
|
|
1806
|
+
return;
|
|
1807
|
+
}
|
|
1808
|
+
}
|
|
1809
|
+
}
|
|
1810
|
+
|
|
1811
|
+
|
|
1812
|
+
void recursive_parse_blockquote(mmd_engine * e, token * block) {
|
|
1813
|
+
// Strip blockquote markers (if present)
|
|
1814
|
+
strip_quote_markers_from_block(e, block);
|
|
1815
|
+
|
|
1816
|
+
mmd_parse_token_chain(e, block);
|
|
1817
|
+
}
|
|
1818
|
+
|
|
1819
|
+
|
|
1820
|
+
void metadata_stack_describe(mmd_engine * e) {
|
|
1821
|
+
meta * m;
|
|
1822
|
+
|
|
1823
|
+
for (int i = 0; i < e->metadata_stack->size; ++i) {
|
|
1824
|
+
m = stack_peek_index(e->metadata_stack, i);
|
|
1825
|
+
fprintf(stderr, "'%s': '%s'\n", m->key, m->value);
|
|
1826
|
+
}
|
|
1827
|
+
}
|
|
1828
|
+
|
|
1829
|
+
|
|
1830
|
+
void strip_line_tokens_from_metadata(mmd_engine * e, token * metadata) {
|
|
1831
|
+
token * l = metadata->child;
|
|
1832
|
+
char * source = e->dstr->str;
|
|
1833
|
+
|
|
1834
|
+
meta * m = NULL;
|
|
1835
|
+
size_t start, len;
|
|
1836
|
+
|
|
1837
|
+
DString * d = d_string_new("");
|
|
1838
|
+
|
|
1839
|
+
while (l) {
|
|
1840
|
+
switch (l->type) {
|
|
1841
|
+
case LINE_META:
|
|
1842
|
+
meta:
|
|
1843
|
+
if (m) {
|
|
1844
|
+
meta_set_value(m, d->str);
|
|
1845
|
+
d_string_erase(d, 0, -1);
|
|
1846
|
+
}
|
|
1847
|
+
|
|
1848
|
+
len = scan_meta_key(&source[l->start]);
|
|
1849
|
+
m = meta_new(source, l->start, len);
|
|
1850
|
+
start = l->start + len + 1;
|
|
1851
|
+
len = l->start + l->len - start - 1;
|
|
1852
|
+
d_string_append_c_array(d, &source[start], len);
|
|
1853
|
+
stack_push(e->metadata_stack, m);
|
|
1854
|
+
break;
|
|
1855
|
+
|
|
1856
|
+
case LINE_INDENTED_TAB:
|
|
1857
|
+
case LINE_INDENTED_SPACE:
|
|
1858
|
+
while (l->len && char_is_whitespace(source[l->start])) {
|
|
1859
|
+
l->start++;
|
|
1860
|
+
l->len--;
|
|
1861
|
+
}
|
|
1862
|
+
|
|
1863
|
+
case LINE_PLAIN:
|
|
1864
|
+
plain:
|
|
1865
|
+
d_string_append_c(d, '\n');
|
|
1866
|
+
d_string_append_c_array(d, &source[l->start], l->len);
|
|
1867
|
+
break;
|
|
1868
|
+
|
|
1869
|
+
case LINE_SETEXT_2:
|
|
1870
|
+
case LINE_YAML:
|
|
1871
|
+
break;
|
|
1872
|
+
|
|
1873
|
+
case LINE_TABLE:
|
|
1874
|
+
if (scan_meta_line(&source[l->start])) {
|
|
1875
|
+
goto meta;
|
|
1876
|
+
} else {
|
|
1877
|
+
goto plain;
|
|
1878
|
+
}
|
|
1879
|
+
|
|
1880
|
+
default:
|
|
1881
|
+
fprintf(stderr, "ERROR!\n");
|
|
1882
|
+
token_describe(l, NULL);
|
|
1883
|
+
break;
|
|
1884
|
+
}
|
|
1885
|
+
|
|
1886
|
+
l = l->next;
|
|
1887
|
+
}
|
|
1888
|
+
|
|
1889
|
+
// Finish last line
|
|
1890
|
+
if (m) {
|
|
1891
|
+
meta_set_value(m, d->str);
|
|
1892
|
+
}
|
|
1893
|
+
|
|
1894
|
+
d_string_free(d, true);
|
|
1895
|
+
}
|
|
1896
|
+
|
|
1897
|
+
|
|
1898
|
+
void strip_line_tokens_from_deflist(mmd_engine * e, token * deflist) {
|
|
1899
|
+
token * walker = deflist->child;
|
|
1900
|
+
|
|
1901
|
+
while (walker) {
|
|
1902
|
+
switch (walker->type) {
|
|
1903
|
+
case LINE_EMPTY:
|
|
1904
|
+
walker->type = TEXT_EMPTY;
|
|
1905
|
+
break;
|
|
1906
|
+
|
|
1907
|
+
case LINE_PLAIN:
|
|
1908
|
+
walker->type = BLOCK_TERM;
|
|
1909
|
+
|
|
1910
|
+
case BLOCK_TERM:
|
|
1911
|
+
break;
|
|
1912
|
+
|
|
1913
|
+
case BLOCK_DEFINITION:
|
|
1914
|
+
strip_line_tokens_from_block(e, walker);
|
|
1915
|
+
break;
|
|
1916
|
+
}
|
|
1917
|
+
|
|
1918
|
+
walker = walker->next;
|
|
1919
|
+
}
|
|
1920
|
+
}
|
|
1921
|
+
|
|
1922
|
+
|
|
1923
|
+
void strip_line_tokens_from_table(mmd_engine * e, token * table) {
|
|
1924
|
+
token * walker = table->child;
|
|
1925
|
+
|
|
1926
|
+
while (walker) {
|
|
1927
|
+
switch (walker->type) {
|
|
1928
|
+
case BLOCK_TABLE_SECTION:
|
|
1929
|
+
strip_line_tokens_from_block(e, walker);
|
|
1930
|
+
break;
|
|
1931
|
+
|
|
1932
|
+
case BLOCK_TABLE_HEADER:
|
|
1933
|
+
strip_line_tokens_from_block(e, walker);
|
|
1934
|
+
break;
|
|
1935
|
+
|
|
1936
|
+
case LINE_EMPTY:
|
|
1937
|
+
walker->type = TEXT_EMPTY;
|
|
1938
|
+
break;
|
|
1939
|
+
}
|
|
1940
|
+
|
|
1941
|
+
walker = walker->next;
|
|
1942
|
+
}
|
|
1943
|
+
}
|
|
1944
|
+
|
|
1945
|
+
|
|
1946
|
+
void parse_table_row_into_cells(token * row) {
|
|
1947
|
+
token * first = NULL;
|
|
1948
|
+
token * last = NULL;
|
|
1949
|
+
|
|
1950
|
+
token * walker = row->child;
|
|
1951
|
+
|
|
1952
|
+
if (walker) {
|
|
1953
|
+
if (walker->type == PIPE) {
|
|
1954
|
+
walker->type = TABLE_DIVIDER;
|
|
1955
|
+
first = walker->next;
|
|
1956
|
+
} else {
|
|
1957
|
+
first = walker;
|
|
1958
|
+
last = first;
|
|
1959
|
+
}
|
|
1960
|
+
|
|
1961
|
+
walker = walker->next;
|
|
1962
|
+
}
|
|
1963
|
+
|
|
1964
|
+
while (walker) {
|
|
1965
|
+
switch (walker->type) {
|
|
1966
|
+
case PIPE:
|
|
1967
|
+
token_prune_graft(first, last, TABLE_CELL);
|
|
1968
|
+
first = NULL;
|
|
1969
|
+
last = NULL;
|
|
1970
|
+
walker->type = TABLE_DIVIDER;
|
|
1971
|
+
break;
|
|
1972
|
+
|
|
1973
|
+
case TEXT_NL:
|
|
1974
|
+
case TEXT_LINEBREAK:
|
|
1975
|
+
break;
|
|
1976
|
+
|
|
1977
|
+
default:
|
|
1978
|
+
if (!first) {
|
|
1979
|
+
first = walker;
|
|
1980
|
+
}
|
|
1981
|
+
|
|
1982
|
+
last = walker;
|
|
1983
|
+
}
|
|
1984
|
+
|
|
1985
|
+
walker = walker->next;
|
|
1986
|
+
}
|
|
1987
|
+
|
|
1988
|
+
if (first) {
|
|
1989
|
+
token_prune_graft(first, last, TABLE_CELL);
|
|
1990
|
+
}
|
|
1991
|
+
}
|
|
1992
|
+
|
|
1993
|
+
|
|
1994
|
+
void strip_line_tokens_from_block(mmd_engine * e, token * block) {
|
|
1995
|
+
if ((block == NULL) || (block->child == NULL)) {
|
|
1996
|
+
return;
|
|
1997
|
+
}
|
|
1998
|
+
|
|
1999
|
+
#ifndef NDEBUG
|
|
2000
|
+
fprintf(stderr, "Strip line tokens from %d (%lu:%lu) (child %d)\n", block->type, block->start, block->len, block->child->type);
|
|
2001
|
+
token_tree_describe(block, e->dstr->str);
|
|
2002
|
+
#endif
|
|
2003
|
+
|
|
2004
|
+
token * l = block->child;
|
|
2005
|
+
|
|
2006
|
+
// Custom actions
|
|
2007
|
+
switch (block->type) {
|
|
2008
|
+
case BLOCK_META:
|
|
2009
|
+
// Handle metadata differently
|
|
2010
|
+
return strip_line_tokens_from_metadata(e, block);
|
|
2011
|
+
|
|
2012
|
+
case BLOCK_CODE_INDENTED:
|
|
2013
|
+
|
|
2014
|
+
// Strip trailing empty lines from indented code blocks
|
|
2015
|
+
while (l->tail->type == LINE_EMPTY) {
|
|
2016
|
+
token_remove_last_child(block);
|
|
2017
|
+
}
|
|
2018
|
+
|
|
2019
|
+
break;
|
|
2020
|
+
|
|
2021
|
+
case BLOCK_DEFLIST:
|
|
2022
|
+
// Handle definition lists
|
|
2023
|
+
return strip_line_tokens_from_deflist(e, block);
|
|
2024
|
+
|
|
2025
|
+
case BLOCK_TABLE:
|
|
2026
|
+
// Handle tables
|
|
2027
|
+
return strip_line_tokens_from_table(e, block);
|
|
2028
|
+
}
|
|
2029
|
+
|
|
2030
|
+
token * children = NULL;
|
|
2031
|
+
block->child = NULL;
|
|
2032
|
+
|
|
2033
|
+
token * temp;
|
|
2034
|
+
|
|
2035
|
+
// Move contents of line directly into the parent block
|
|
2036
|
+
while (l != NULL) {
|
|
2037
|
+
// Remove leading non-indent space from line
|
|
2038
|
+
if (block->type != BLOCK_CODE_FENCED && l->child && l->child->type == NON_INDENT_SPACE) {
|
|
2039
|
+
token_remove_first_child(l);
|
|
2040
|
+
}
|
|
2041
|
+
|
|
2042
|
+
switch (l->type) {
|
|
2043
|
+
case LINE_SETEXT_1:
|
|
2044
|
+
case LINE_SETEXT_2:
|
|
2045
|
+
if ((block->type == BLOCK_SETEXT_1) ||
|
|
2046
|
+
(block->type == BLOCK_SETEXT_2)) {
|
|
2047
|
+
temp = l->next;
|
|
2048
|
+
tokens_prune(l, l);
|
|
2049
|
+
l = temp;
|
|
2050
|
+
break;
|
|
2051
|
+
}
|
|
2052
|
+
|
|
2053
|
+
case LINE_DEFINITION:
|
|
2054
|
+
if (block->type == BLOCK_DEFINITION) {
|
|
2055
|
+
// Remove leading colon
|
|
2056
|
+
token_remove_first_child(l);
|
|
2057
|
+
}
|
|
2058
|
+
|
|
2059
|
+
case LINE_ATX_1:
|
|
2060
|
+
case LINE_ATX_2:
|
|
2061
|
+
case LINE_ATX_3:
|
|
2062
|
+
case LINE_ATX_4:
|
|
2063
|
+
case LINE_ATX_5:
|
|
2064
|
+
case LINE_ATX_6:
|
|
2065
|
+
case LINE_BLOCKQUOTE:
|
|
2066
|
+
case LINE_CONTINUATION:
|
|
2067
|
+
case LINE_DEF_ABBREVIATION:
|
|
2068
|
+
case LINE_DEF_CITATION:
|
|
2069
|
+
case LINE_DEF_FOOTNOTE:
|
|
2070
|
+
case LINE_DEF_GLOSSARY:
|
|
2071
|
+
case LINE_DEF_LINK:
|
|
2072
|
+
case LINE_EMPTY:
|
|
2073
|
+
case LINE_LIST_BULLETED:
|
|
2074
|
+
case LINE_LIST_ENUMERATED:
|
|
2075
|
+
case LINE_META:
|
|
2076
|
+
case LINE_PLAIN:
|
|
2077
|
+
case LINE_START_COMMENT:
|
|
2078
|
+
case LINE_STOP_COMMENT:
|
|
2079
|
+
handle_line:
|
|
2080
|
+
|
|
2081
|
+
case LINE_INDENTED_TAB:
|
|
2082
|
+
case LINE_INDENTED_SPACE:
|
|
2083
|
+
|
|
2084
|
+
// Strip leading indent (Only the first one)
|
|
2085
|
+
if (block->type != BLOCK_CODE_FENCED && l->child && ((l->child->type == INDENT_SPACE) || (l->child->type == INDENT_TAB))) {
|
|
2086
|
+
token_remove_first_child(l);
|
|
2087
|
+
}
|
|
2088
|
+
|
|
2089
|
+
// If we're not a code block, strip additional indents
|
|
2090
|
+
if ((block->type != BLOCK_CODE_INDENTED) &&
|
|
2091
|
+
(block->type != BLOCK_CODE_FENCED)) {
|
|
2092
|
+
while (l->child && ((l->child->type == INDENT_SPACE) || (l->child->type == INDENT_TAB))) {
|
|
2093
|
+
token_remove_first_child(l);
|
|
2094
|
+
}
|
|
2095
|
+
}
|
|
2096
|
+
|
|
2097
|
+
// Add contents of line to parent block
|
|
2098
|
+
token_append_child(block, l->child);
|
|
2099
|
+
|
|
2100
|
+
// Disconnect line from it's contents
|
|
2101
|
+
l->child = NULL;
|
|
2102
|
+
|
|
2103
|
+
// Need to remember first line we strip
|
|
2104
|
+
if (children == NULL) {
|
|
2105
|
+
children = l;
|
|
2106
|
+
}
|
|
2107
|
+
|
|
2108
|
+
// Advance to next line
|
|
2109
|
+
l = l->next;
|
|
2110
|
+
break;
|
|
2111
|
+
|
|
2112
|
+
case BLOCK_DEFINITION:
|
|
2113
|
+
|
|
2114
|
+
// Sometimes these get created unintentionally inside other blocks
|
|
2115
|
+
// Process inside it, then treat it like a line to be stripped
|
|
2116
|
+
|
|
2117
|
+
// Change to plain line
|
|
2118
|
+
if (l->child) {
|
|
2119
|
+
l->child->type = LINE_PLAIN;
|
|
2120
|
+
}
|
|
2121
|
+
|
|
2122
|
+
strip_line_tokens_from_block(e, l);
|
|
2123
|
+
|
|
2124
|
+
// Move children to parent
|
|
2125
|
+
// Add ':' back
|
|
2126
|
+
if (e->dstr->str[l->child->start - 1] == ':') {
|
|
2127
|
+
temp = token_new(COLON, l->child->start - 1, 1);
|
|
2128
|
+
token_append_child(block, temp);
|
|
2129
|
+
}
|
|
2130
|
+
|
|
2131
|
+
token_append_child(block, l->child);
|
|
2132
|
+
l->child = NULL;
|
|
2133
|
+
|
|
2134
|
+
if (children == NULL) {
|
|
2135
|
+
children = l;
|
|
2136
|
+
}
|
|
2137
|
+
|
|
2138
|
+
l = l->next;
|
|
2139
|
+
break;
|
|
2140
|
+
|
|
2141
|
+
case LINE_TABLE_SEPARATOR:
|
|
2142
|
+
case LINE_TABLE:
|
|
2143
|
+
if (block->type == BLOCK_TABLE_HEADER) {
|
|
2144
|
+
l->type = (l->type == LINE_TABLE) ? TABLE_ROW : LINE_TABLE_SEPARATOR;
|
|
2145
|
+
parse_table_row_into_cells(l);
|
|
2146
|
+
} else if (block->type == BLOCK_TABLE_SECTION) {
|
|
2147
|
+
l->type = TABLE_ROW;
|
|
2148
|
+
parse_table_row_into_cells(l);
|
|
2149
|
+
} else {
|
|
2150
|
+
goto handle_line;
|
|
2151
|
+
}
|
|
2152
|
+
|
|
2153
|
+
default:
|
|
2154
|
+
// token_describe(block, e->dstr->str);
|
|
2155
|
+
// fprintf(stderr, "Unspecified line type %d inside block type %d\n", l->type, block->type);
|
|
2156
|
+
// This is a block, need to remove it from chain and
|
|
2157
|
+
// Add to parent
|
|
2158
|
+
temp = l->next;
|
|
2159
|
+
|
|
2160
|
+
token_pop_link_from_chain(l);
|
|
2161
|
+
token_append_child(block, l);
|
|
2162
|
+
|
|
2163
|
+
// Advance to next line
|
|
2164
|
+
l = temp;
|
|
2165
|
+
break;
|
|
2166
|
+
}
|
|
2167
|
+
}
|
|
2168
|
+
|
|
2169
|
+
// Free token chain of line types
|
|
2170
|
+
token_tree_free(children);
|
|
2171
|
+
}
|
|
2172
|
+
|
|
2173
|
+
|
|
2174
|
+
/// Parse part of the string into a token tree
|
|
2175
|
+
token * mmd_engine_parse_substring(mmd_engine * e, size_t byte_start, size_t byte_len) {
|
|
2176
|
+
// First, clean up any leftovers from previous parse
|
|
2177
|
+
|
|
2178
|
+
mmd_engine_reset(e);
|
|
2179
|
+
|
|
2180
|
+
// Disable metadata unless we are starting at the beginnging
|
|
2181
|
+
size_t old_ext = e->extensions;
|
|
2182
|
+
|
|
2183
|
+
if (byte_start != 0) {
|
|
2184
|
+
e->extensions |= EXT_NO_METADATA;
|
|
2185
|
+
}
|
|
2186
|
+
|
|
2187
|
+
|
|
2188
|
+
// Tokenize the string
|
|
2189
|
+
token * doc = mmd_tokenize_string(e, byte_start, byte_len, false);
|
|
2190
|
+
|
|
2191
|
+
// Parse tokens into blocks
|
|
2192
|
+
mmd_parse_token_chain(e, doc);
|
|
2193
|
+
|
|
2194
|
+
if (doc) {
|
|
2195
|
+
// Parse blocks for pairs
|
|
2196
|
+
mmd_assign_ambidextrous_tokens_in_block(e, doc, 0);
|
|
2197
|
+
|
|
2198
|
+
// Prepare stack to be used for token pairing
|
|
2199
|
+
// This avoids allocating/freeing one for each iteration.
|
|
2200
|
+
stack * pair_stack = stack_new(0);
|
|
2201
|
+
|
|
2202
|
+
|
|
2203
|
+
mmd_pair_tokens_in_block(doc, e->pairings1, pair_stack);
|
|
2204
|
+
mmd_pair_tokens_in_block(doc, e->pairings2, pair_stack);
|
|
2205
|
+
mmd_pair_tokens_in_block(doc, e->pairings3, pair_stack);
|
|
2206
|
+
mmd_pair_tokens_in_block(doc, e->pairings4, pair_stack);
|
|
2207
|
+
|
|
2208
|
+
// Free stack
|
|
2209
|
+
stack_free(pair_stack);
|
|
2210
|
+
|
|
2211
|
+
pair_emphasis_tokens(doc);
|
|
2212
|
+
|
|
2213
|
+
#ifndef NDEBUG
|
|
2214
|
+
token_tree_describe(doc, e->dstr->str);
|
|
2215
|
+
#endif
|
|
2216
|
+
}
|
|
2217
|
+
|
|
2218
|
+
// Return original extensions
|
|
2219
|
+
e->extensions = old_ext;
|
|
2220
|
+
|
|
2221
|
+
return doc;
|
|
2222
|
+
}
|
|
2223
|
+
|
|
2224
|
+
|
|
2225
|
+
/// Parse the entire string into a token tree
|
|
2226
|
+
void mmd_engine_parse_string(mmd_engine * e) {
|
|
2227
|
+
if (e) {
|
|
2228
|
+
e->root = mmd_engine_parse_substring(e, 0, e->dstr->currentStringLength);
|
|
2229
|
+
}
|
|
2230
|
+
}
|
|
2231
|
+
|
|
2232
|
+
|
|
2233
|
+
/// Does the text have metadata?
|
|
2234
|
+
bool mmd_string_has_metadata(char * source, size_t * end) {
|
|
2235
|
+
bool result;
|
|
2236
|
+
|
|
2237
|
+
mmd_engine * e = mmd_engine_create_with_string(source, 0);
|
|
2238
|
+
result = mmd_engine_has_metadata(e, end);
|
|
2239
|
+
|
|
2240
|
+
mmd_engine_free(e, true);
|
|
2241
|
+
|
|
2242
|
+
return result;
|
|
2243
|
+
}
|
|
2244
|
+
|
|
2245
|
+
|
|
2246
|
+
/// Does the text have metadata?
|
|
2247
|
+
bool mmd_d_string_has_metadata(DString * source, size_t * end) {
|
|
2248
|
+
bool result;
|
|
2249
|
+
|
|
2250
|
+
mmd_engine * e = mmd_engine_create_with_dstring(source, 0);
|
|
2251
|
+
result = mmd_engine_has_metadata(e, end);
|
|
2252
|
+
|
|
2253
|
+
mmd_engine_free(e, false);
|
|
2254
|
+
|
|
2255
|
+
return result;
|
|
2256
|
+
}
|
|
2257
|
+
|
|
2258
|
+
|
|
2259
|
+
/// Does the text have metadata?
|
|
2260
|
+
bool mmd_engine_has_metadata(mmd_engine * e, size_t * end) {
|
|
2261
|
+
bool result = false;
|
|
2262
|
+
|
|
2263
|
+
if (!e) {
|
|
2264
|
+
return false;
|
|
2265
|
+
}
|
|
2266
|
+
|
|
2267
|
+
if (!(scan_meta_line(&e->dstr->str[0]))) {
|
|
2268
|
+
// First line is not metadata, so can't have metadata
|
|
2269
|
+
// Saves the time of an unnecessary parse
|
|
2270
|
+
// TODO: Need faster confirmation of actual metadata than full tokenizing
|
|
2271
|
+
if (end) {
|
|
2272
|
+
*end = 0;
|
|
2273
|
+
}
|
|
2274
|
+
|
|
2275
|
+
return false;
|
|
2276
|
+
}
|
|
2277
|
+
|
|
2278
|
+
// Free existing parse tree
|
|
2279
|
+
if (e->root) {
|
|
2280
|
+
token_tree_free(e->root);
|
|
2281
|
+
}
|
|
2282
|
+
|
|
2283
|
+
// Tokenize the string (up until first empty line)
|
|
2284
|
+
token * doc = mmd_tokenize_string(e, 0, e->dstr->currentStringLength, true);
|
|
2285
|
+
|
|
2286
|
+
// Parse tokens into blocks
|
|
2287
|
+
mmd_parse_token_chain(e, doc);
|
|
2288
|
+
|
|
2289
|
+
if (doc) {
|
|
2290
|
+
if (doc->child && doc->child->type == BLOCK_META) {
|
|
2291
|
+
result = true;
|
|
2292
|
+
|
|
2293
|
+
if (end) {
|
|
2294
|
+
*end = doc->child->len;
|
|
2295
|
+
}
|
|
2296
|
+
}
|
|
2297
|
+
|
|
2298
|
+
token_tree_free(doc);
|
|
2299
|
+
}
|
|
2300
|
+
|
|
2301
|
+
return result;
|
|
2302
|
+
}
|
|
2303
|
+
|
|
2304
|
+
|
|
2305
|
+
/// Return metadata keys, one per line
|
|
2306
|
+
/// Returned char * must be freed
|
|
2307
|
+
char * mmd_string_metadata_keys(char * source) {
|
|
2308
|
+
char * result;
|
|
2309
|
+
|
|
2310
|
+
mmd_engine * e = mmd_engine_create_with_string(source, 0);
|
|
2311
|
+
result = mmd_engine_metadata_keys(e);
|
|
2312
|
+
|
|
2313
|
+
mmd_engine_free(e, true);
|
|
2314
|
+
|
|
2315
|
+
return result;
|
|
2316
|
+
}
|
|
2317
|
+
|
|
2318
|
+
|
|
2319
|
+
/// Return metadata keys, one per line
|
|
2320
|
+
/// Returned char * must be freed
|
|
2321
|
+
char * mmd_d_string_metadata_keys(DString * source) {
|
|
2322
|
+
char * result;
|
|
2323
|
+
|
|
2324
|
+
mmd_engine * e = mmd_engine_create_with_dstring(source, 0);
|
|
2325
|
+
result = mmd_engine_metadata_keys(e);
|
|
2326
|
+
|
|
2327
|
+
mmd_engine_free(e, false);
|
|
2328
|
+
|
|
2329
|
+
return result;
|
|
2330
|
+
}
|
|
2331
|
+
|
|
2332
|
+
|
|
2333
|
+
/// Return metadata keys, one per line
|
|
2334
|
+
/// Returned char * must be freed
|
|
2335
|
+
char * mmd_engine_metadata_keys(mmd_engine * e) {
|
|
2336
|
+
if (e->metadata_stack->size == 0) {
|
|
2337
|
+
// Ensure we have checked for metadata
|
|
2338
|
+
if (!mmd_engine_has_metadata(e, NULL)) {
|
|
2339
|
+
return NULL;
|
|
2340
|
+
}
|
|
2341
|
+
}
|
|
2342
|
+
|
|
2343
|
+
char * result = NULL;
|
|
2344
|
+
DString * output = d_string_new("");
|
|
2345
|
+
|
|
2346
|
+
meta * m;
|
|
2347
|
+
|
|
2348
|
+
for (int i = 0; i < e->metadata_stack->size; ++i) {
|
|
2349
|
+
m = stack_peek_index(e->metadata_stack, i);
|
|
2350
|
+
|
|
2351
|
+
d_string_append_printf(output, "%s\n", m->key);
|
|
2352
|
+
}
|
|
2353
|
+
|
|
2354
|
+
result = output->str;
|
|
2355
|
+
d_string_free(output, false);
|
|
2356
|
+
|
|
2357
|
+
return result;
|
|
2358
|
+
}
|
|
2359
|
+
|
|
2360
|
+
|
|
2361
|
+
/// Extract desired metadata as string value
|
|
2362
|
+
/// Returned char * must be freed
|
|
2363
|
+
char * mmd_string_metavalue_for_key(char * source, const char * key) {
|
|
2364
|
+
char * result;
|
|
2365
|
+
|
|
2366
|
+
mmd_engine * e = mmd_engine_create_with_string(source, 0);
|
|
2367
|
+
result = mmd_engine_metavalue_for_key(e, key);
|
|
2368
|
+
|
|
2369
|
+
if (result) {
|
|
2370
|
+
// We need to return a copy of the string
|
|
2371
|
+
result = my_strdup(result);
|
|
2372
|
+
}
|
|
2373
|
+
|
|
2374
|
+
mmd_engine_free(e, true);
|
|
2375
|
+
|
|
2376
|
+
return result;
|
|
2377
|
+
}
|
|
2378
|
+
|
|
2379
|
+
|
|
2380
|
+
/// Extract desired metadata as string value
|
|
2381
|
+
/// Returned char * must be freed
|
|
2382
|
+
char * mmd_d_string_metavalue_for_key(DString * source, const char * key) {
|
|
2383
|
+
char * result;
|
|
2384
|
+
|
|
2385
|
+
mmd_engine * e = mmd_engine_create_with_dstring(source, 0);
|
|
2386
|
+
result = mmd_engine_metavalue_for_key(e, key);
|
|
2387
|
+
|
|
2388
|
+
if (result) {
|
|
2389
|
+
// We need to return a copy of the string
|
|
2390
|
+
result = my_strdup(result);
|
|
2391
|
+
}
|
|
2392
|
+
|
|
2393
|
+
mmd_engine_free(e, false);
|
|
2394
|
+
|
|
2395
|
+
return result;
|
|
2396
|
+
}
|
|
2397
|
+
|
|
2398
|
+
|
|
2399
|
+
/// Grab metadata without processing entire document
|
|
2400
|
+
/// Returned char * does not need to be freed
|
|
2401
|
+
char * mmd_engine_metavalue_for_key(mmd_engine * e, const char * key) {
|
|
2402
|
+
if (e->metadata_stack->size == 0) {
|
|
2403
|
+
// Ensure we have checked for metadata
|
|
2404
|
+
if (!mmd_engine_has_metadata(e, NULL)) {
|
|
2405
|
+
return NULL;
|
|
2406
|
+
}
|
|
2407
|
+
}
|
|
2408
|
+
|
|
2409
|
+
char * result = NULL;
|
|
2410
|
+
char * clean = label_from_string(key);
|
|
2411
|
+
|
|
2412
|
+
meta * m;
|
|
2413
|
+
|
|
2414
|
+
for (int i = 0; i < e->metadata_stack->size; ++i) {
|
|
2415
|
+
m = stack_peek_index(e->metadata_stack, i);
|
|
2416
|
+
|
|
2417
|
+
if (strcmp(clean, m->key) == 0) {
|
|
2418
|
+
// We have a match
|
|
2419
|
+
free(clean);
|
|
2420
|
+
return m->value;
|
|
2421
|
+
}
|
|
2422
|
+
}
|
|
2423
|
+
|
|
2424
|
+
free(clean);
|
|
2425
|
+
return result;
|
|
2426
|
+
}
|
|
2427
|
+
|
|
2428
|
+
|
|
2429
|
+
/// Grab list of all transcluded files, but we need to know directory to search,
|
|
2430
|
+
/// as well as the path to the file
|
|
2431
|
+
/// Returned stack needs to be freed
|
|
2432
|
+
stack * mmd_string_transclusion_manifest(const char * source, const char * search_path, const char * source_path) {
|
|
2433
|
+
stack * result;
|
|
2434
|
+
|
|
2435
|
+
mmd_engine * e = mmd_engine_create_with_string(source, 0);
|
|
2436
|
+
|
|
2437
|
+
result = mmd_engine_transclusion_manifest(e, search_path, source_path);
|
|
2438
|
+
|
|
2439
|
+
mmd_engine_free(e, true);
|
|
2440
|
+
|
|
2441
|
+
return result;
|
|
2442
|
+
}
|
|
2443
|
+
|
|
2444
|
+
|
|
2445
|
+
/// Grab list of all transcluded files, but we need to know directory to search,
|
|
2446
|
+
/// as well as the path to the file
|
|
2447
|
+
/// Returned stack needs to be freed
|
|
2448
|
+
stack * mmd_d_string_transclusion_manifest(DString * source, const char * search_path, const char * source_path) {
|
|
2449
|
+
stack * result;
|
|
2450
|
+
|
|
2451
|
+
mmd_engine * e = mmd_engine_create_with_dstring(source, 0);
|
|
2452
|
+
|
|
2453
|
+
result = mmd_engine_transclusion_manifest(e, search_path, source_path);
|
|
2454
|
+
|
|
2455
|
+
mmd_engine_free(e, false);
|
|
2456
|
+
|
|
2457
|
+
return result;
|
|
2458
|
+
}
|
|
2459
|
+
|
|
2460
|
+
|
|
2461
|
+
/// Grab list of all transcluded files, but we need to know directory to search,
|
|
2462
|
+
/// as well as the path to the file
|
|
2463
|
+
/// Returned stack needs to be freed
|
|
2464
|
+
stack * mmd_engine_transclusion_manifest(mmd_engine * e, const char * search_path, const char * source_path) {
|
|
2465
|
+
// Create empty manifest stack
|
|
2466
|
+
stack * manifest = stack_new(0);
|
|
2467
|
+
|
|
2468
|
+
// Copy source text for temporary buffer
|
|
2469
|
+
DString * buffer = d_string_new(e->dstr->str);
|
|
2470
|
+
|
|
2471
|
+
mmd_transclude_source(buffer, search_path, source_path, FORMAT_HTML, NULL, manifest);
|
|
2472
|
+
|
|
2473
|
+
d_string_free(buffer, true);
|
|
2474
|
+
|
|
2475
|
+
return manifest;
|
|
2476
|
+
}
|
|
2477
|
+
|
|
2478
|
+
|
|
2479
|
+
/// Insert/replace metadata in string, returning new string
|
|
2480
|
+
char * mmd_string_update_metavalue_for_key(const char * source, const char * key, const char * value) {
|
|
2481
|
+
mmd_engine * e = mmd_engine_create_with_string(source, 0);
|
|
2482
|
+
mmd_engine_update_metavalue_for_key(e, key, value);
|
|
2483
|
+
|
|
2484
|
+
DString * d = e->dstr;
|
|
2485
|
+
|
|
2486
|
+
mmd_engine_free(e, false);
|
|
2487
|
+
|
|
2488
|
+
char * result = d->str;
|
|
2489
|
+
d_string_free(d, false);
|
|
2490
|
+
|
|
2491
|
+
return result;
|
|
2492
|
+
}
|
|
2493
|
+
|
|
2494
|
+
|
|
2495
|
+
/// Insert/replace metadata value in DString
|
|
2496
|
+
void mmd_d_string_update_metavalue_for_key(DString * source, const char * key, const char * value) {
|
|
2497
|
+
mmd_engine * e = mmd_engine_create_with_dstring(source, 0);
|
|
2498
|
+
mmd_engine_update_metavalue_for_key(e, key, value);
|
|
2499
|
+
|
|
2500
|
+
mmd_engine_free(e, false);
|
|
2501
|
+
}
|
|
2502
|
+
|
|
2503
|
+
|
|
2504
|
+
/// Insert/replace metadata value in mmd_engine
|
|
2505
|
+
void mmd_engine_update_metavalue_for_key(mmd_engine * e, const char * key, const char * value) {
|
|
2506
|
+
bool has_meta = true;
|
|
2507
|
+
size_t meta_end = 0;
|
|
2508
|
+
|
|
2509
|
+
// Check for metadata and character
|
|
2510
|
+
if (!mmd_engine_has_metadata(e, &meta_end)) {
|
|
2511
|
+
has_meta = false;
|
|
2512
|
+
}
|
|
2513
|
+
|
|
2514
|
+
// Get clean metadata key for match
|
|
2515
|
+
char * clean = label_from_string(key);
|
|
2516
|
+
|
|
2517
|
+
// Determine range to excise and replace
|
|
2518
|
+
size_t start = -1;
|
|
2519
|
+
size_t end = -1;
|
|
2520
|
+
size_t len = -1;
|
|
2521
|
+
|
|
2522
|
+
meta * m;
|
|
2523
|
+
|
|
2524
|
+
for (int i = 0; i < e->metadata_stack->size; ++i) {
|
|
2525
|
+
m = stack_peek_index(e->metadata_stack, i);
|
|
2526
|
+
|
|
2527
|
+
if (strcmp(clean, m->key) == 0) {
|
|
2528
|
+
// We have a match
|
|
2529
|
+
start = m->start;
|
|
2530
|
+
} else if (start != -1) {
|
|
2531
|
+
// We have already found a match
|
|
2532
|
+
if (end == -1) {
|
|
2533
|
+
// This is the next metadata key, so determine length
|
|
2534
|
+
end = m->start;
|
|
2535
|
+
}
|
|
2536
|
+
}
|
|
2537
|
+
}
|
|
2538
|
+
|
|
2539
|
+
DString * temp = d_string_new(key);
|
|
2540
|
+
d_string_append(temp, ":\t");
|
|
2541
|
+
d_string_append(temp, value);
|
|
2542
|
+
d_string_append_c(temp, '\n');
|
|
2543
|
+
|
|
2544
|
+
if (start != -1) {
|
|
2545
|
+
// We're replacing existing metadata
|
|
2546
|
+
|
|
2547
|
+
// Figure out where to start
|
|
2548
|
+
char * begin = &(e->dstr->str[start]);
|
|
2549
|
+
|
|
2550
|
+
while (*begin != ':') {
|
|
2551
|
+
begin++;
|
|
2552
|
+
}
|
|
2553
|
+
|
|
2554
|
+
begin++;
|
|
2555
|
+
|
|
2556
|
+
while (char_is_whitespace(*begin)) {
|
|
2557
|
+
begin++;
|
|
2558
|
+
}
|
|
2559
|
+
|
|
2560
|
+
start = begin - e->dstr->str;
|
|
2561
|
+
|
|
2562
|
+
if (end == -1) {
|
|
2563
|
+
// Replace until the end of the metadata (last key)
|
|
2564
|
+
len = meta_end - start;
|
|
2565
|
+
} else {
|
|
2566
|
+
len = end - start;
|
|
2567
|
+
}
|
|
2568
|
+
|
|
2569
|
+
d_string_erase(e->dstr, start, len);
|
|
2570
|
+
d_string_insert(e->dstr, start, "\n");
|
|
2571
|
+
d_string_insert(e->dstr, start, value);
|
|
2572
|
+
} else if (meta_end != 0) {
|
|
2573
|
+
// We're appending metadata at the end
|
|
2574
|
+
d_string_insert(e->dstr, meta_end, temp->str);
|
|
2575
|
+
} else {
|
|
2576
|
+
// There is no metadata, so prepend before document
|
|
2577
|
+
d_string_append_c(temp, '\n');
|
|
2578
|
+
d_string_prepend(e->dstr, temp->str);
|
|
2579
|
+
}
|
|
2580
|
+
|
|
2581
|
+
d_string_free(temp, true);
|
|
2582
|
+
free(clean);
|
|
2583
|
+
}
|
|
2584
|
+
|
|
2585
|
+
|
|
2586
|
+
/// Convert MMD text to specified format, with specified extensions, and language
|
|
2587
|
+
/// Returned char * must be freed
|
|
2588
|
+
char * mmd_string_convert(const char * source, unsigned long extensions, short format, short language) {
|
|
2589
|
+
char * result;
|
|
2590
|
+
|
|
2591
|
+
mmd_engine * e = mmd_engine_create_with_string(source, extensions);
|
|
2592
|
+
|
|
2593
|
+
mmd_engine_set_language(e, language);
|
|
2594
|
+
|
|
2595
|
+
result = mmd_engine_convert(e, format);
|
|
2596
|
+
|
|
2597
|
+
mmd_engine_free(e, true); // The engine has a private copy of source that must be freed
|
|
2598
|
+
|
|
2599
|
+
return result;
|
|
2600
|
+
}
|
|
2601
|
+
|
|
2602
|
+
|
|
2603
|
+
/// Convert MMD text to specified format, with specified extensions, and language
|
|
2604
|
+
/// Returned char * must be freed
|
|
2605
|
+
char * mmd_d_string_convert(DString * source, unsigned long extensions, short format, short language) {
|
|
2606
|
+
char * result;
|
|
2607
|
+
|
|
2608
|
+
mmd_engine * e = mmd_engine_create_with_dstring(source, extensions);
|
|
2609
|
+
|
|
2610
|
+
mmd_engine_set_language(e, language);
|
|
2611
|
+
|
|
2612
|
+
result = mmd_engine_convert(e, format);
|
|
2613
|
+
|
|
2614
|
+
mmd_engine_free(e, false); // The engine doesn't own the DString, so don't free it.
|
|
2615
|
+
|
|
2616
|
+
return result;
|
|
2617
|
+
}
|
|
2618
|
+
|
|
2619
|
+
|
|
2620
|
+
/// Convert MMD text to specified format, with specified extensions, and language
|
|
2621
|
+
/// Returned char * must be freed
|
|
2622
|
+
char * mmd_engine_convert(mmd_engine * e, short format) {
|
|
2623
|
+
char * result;
|
|
2624
|
+
|
|
2625
|
+
mmd_engine_parse_string(e);
|
|
2626
|
+
|
|
2627
|
+
DString * output = d_string_new("");
|
|
2628
|
+
|
|
2629
|
+
mmd_engine_export_token_tree(output, e, format);
|
|
2630
|
+
|
|
2631
|
+
// Add newline to result
|
|
2632
|
+
d_string_append_c(output, '\n');
|
|
2633
|
+
|
|
2634
|
+
result = output->str;
|
|
2635
|
+
|
|
2636
|
+
d_string_free(output, false);
|
|
2637
|
+
|
|
2638
|
+
return result;
|
|
2639
|
+
}
|
|
2640
|
+
|
|
2641
|
+
|
|
2642
|
+
/// Convert MMD text and write results to specified file -- used for "complex" output formats requiring
|
|
2643
|
+
/// multiple documents (e.g. EPUB)
|
|
2644
|
+
void mmd_string_convert_to_file(const char * source, unsigned long extensions, short format, short language, const char * directory, const char * filepath) {
|
|
2645
|
+
|
|
2646
|
+
mmd_engine * e = mmd_engine_create_with_string(source, extensions);
|
|
2647
|
+
|
|
2648
|
+
mmd_engine_set_language(e, language);
|
|
2649
|
+
|
|
2650
|
+
mmd_engine_parse_string(e);
|
|
2651
|
+
|
|
2652
|
+
mmd_engine_free(e, true); // The engine has a private copy of source, so free it.
|
|
2653
|
+
}
|
|
2654
|
+
|
|
2655
|
+
|
|
2656
|
+
/// Convert MMD text and write results to specified file -- used for "complex" output formats requiring
|
|
2657
|
+
/// multiple documents (e.g. EPUB)
|
|
2658
|
+
void mmd_d_string_convert_to_file(DString * source, unsigned long extensions, short format, short language, const char * directory, const char * filepath) {
|
|
2659
|
+
|
|
2660
|
+
mmd_engine * e = mmd_engine_create_with_dstring(source, extensions);
|
|
2661
|
+
|
|
2662
|
+
mmd_engine_set_language(e, language);
|
|
2663
|
+
|
|
2664
|
+
mmd_engine_convert_to_file(e, format, directory, filepath);
|
|
2665
|
+
|
|
2666
|
+
mmd_engine_free(e, false); // The engine doesn't own the DString, so don't free it.
|
|
2667
|
+
}
|
|
2668
|
+
|
|
2669
|
+
|
|
2670
|
+
/// Convert MMD text and write results to specified file -- used for "complex" output formats requiring
|
|
2671
|
+
/// multiple documents (e.g. EPUB)
|
|
2672
|
+
void mmd_engine_convert_to_file(mmd_engine * e, short format, const char * directory, const char * filepath) {
|
|
2673
|
+
FILE * output_stream;
|
|
2674
|
+
|
|
2675
|
+
DString * output = d_string_new("");
|
|
2676
|
+
|
|
2677
|
+
mmd_engine_parse_string(e);
|
|
2678
|
+
|
|
2679
|
+
mmd_engine_export_token_tree(output, e, format);
|
|
2680
|
+
|
|
2681
|
+
// Now we have the input source string, the output string, the (modified) parse tree, and engine stacks
|
|
2682
|
+
|
|
2683
|
+
switch (format) {
|
|
2684
|
+
case FORMAT_EPUB:
|
|
2685
|
+
epub_write_wrapper(filepath, output->str, e, directory);
|
|
2686
|
+
break;
|
|
2687
|
+
|
|
2688
|
+
case FORMAT_TEXTBUNDLE:
|
|
2689
|
+
// TODO: Need to implement this
|
|
2690
|
+
break;
|
|
2691
|
+
|
|
2692
|
+
case FORMAT_TEXTBUNDLE_COMPRESSED:
|
|
2693
|
+
textbundle_write_wrapper(filepath, output->str, e, directory);
|
|
2694
|
+
break;
|
|
2695
|
+
|
|
2696
|
+
default:
|
|
2697
|
+
|
|
2698
|
+
// Basic formats just write to file
|
|
2699
|
+
if (!(output_stream = fopen(filepath, "w"))) {
|
|
2700
|
+
// Failed to open file
|
|
2701
|
+
perror(filepath);
|
|
2702
|
+
} else {
|
|
2703
|
+
fputs(output->str, output_stream);
|
|
2704
|
+
fputc('\n', output_stream);
|
|
2705
|
+
fclose(output_stream);
|
|
2706
|
+
}
|
|
2707
|
+
|
|
2708
|
+
break;
|
|
2709
|
+
}
|
|
2710
|
+
|
|
2711
|
+
d_string_free(output, true);
|
|
2712
|
+
}
|
|
2713
|
+
|
|
2714
|
+
|
|
2715
|
+
DString * mmd_string_convert_to_data(const char * source, unsigned long extensions, short format, short language, const char * directory) {
|
|
2716
|
+
mmd_engine * e = mmd_engine_create_with_string(source, extensions);
|
|
2717
|
+
|
|
2718
|
+
mmd_engine_set_language(e, language);
|
|
2719
|
+
|
|
2720
|
+
DString * result = mmd_engine_convert_to_data(e, format, directory);
|
|
2721
|
+
|
|
2722
|
+
mmd_engine_free(e, true);
|
|
2723
|
+
|
|
2724
|
+
return result;
|
|
2725
|
+
}
|
|
2726
|
+
|
|
2727
|
+
|
|
2728
|
+
DString * mmd_d_string_convert_to_data(DString * source, unsigned long extensions, short format, short language, const char * directory) {
|
|
2729
|
+
mmd_engine * e = mmd_engine_create_with_dstring(source, extensions);
|
|
2730
|
+
|
|
2731
|
+
mmd_engine_set_language(e, language);
|
|
2732
|
+
|
|
2733
|
+
DString * result = mmd_engine_convert_to_data(e, format, directory);
|
|
2734
|
+
|
|
2735
|
+
mmd_engine_free(e, false); // The engine doesn't own the DString, so don't free it.
|
|
2736
|
+
|
|
2737
|
+
return result;
|
|
2738
|
+
}
|
|
2739
|
+
|
|
2740
|
+
|
|
2741
|
+
DString * mmd_engine_convert_to_data(mmd_engine * e, short format, const char * directory) {
|
|
2742
|
+
DString * output = d_string_new("");
|
|
2743
|
+
DString * result = NULL;
|
|
2744
|
+
|
|
2745
|
+
if (format == FORMAT_MMD) {
|
|
2746
|
+
// Simply return text (transclusion is handled externally)
|
|
2747
|
+
d_string_append_c_array(output, e->dstr->str, e->dstr->currentStringLength);
|
|
2748
|
+
|
|
2749
|
+
return output;
|
|
2750
|
+
}
|
|
2751
|
+
|
|
2752
|
+
mmd_engine_parse_string(e);
|
|
2753
|
+
|
|
2754
|
+
mmd_engine_export_token_tree(output, e, format);
|
|
2755
|
+
|
|
2756
|
+
switch (format) {
|
|
2757
|
+
case FORMAT_EPUB:
|
|
2758
|
+
result = epub_create(output->str, e, directory);
|
|
2759
|
+
|
|
2760
|
+
d_string_free(output, true);
|
|
2761
|
+
break;
|
|
2762
|
+
|
|
2763
|
+
case FORMAT_TEXTBUNDLE:
|
|
2764
|
+
case FORMAT_TEXTBUNDLE_COMPRESSED:
|
|
2765
|
+
result = textbundle_create(output->str, e, directory);
|
|
2766
|
+
|
|
2767
|
+
d_string_free(output, true);
|
|
2768
|
+
break;
|
|
2769
|
+
|
|
2770
|
+
case FORMAT_ODT:
|
|
2771
|
+
result = opendocument_text_create(output->str, e, directory);
|
|
2772
|
+
|
|
2773
|
+
d_string_free(output, true);
|
|
2774
|
+
break;
|
|
2775
|
+
|
|
2776
|
+
case FORMAT_FODT:
|
|
2777
|
+
result = opendocument_flat_text_create(output->str, e, directory);
|
|
2778
|
+
|
|
2779
|
+
d_string_free(output, true);
|
|
2780
|
+
break;
|
|
2781
|
+
|
|
2782
|
+
default:
|
|
2783
|
+
result = output;
|
|
2784
|
+
// Add newline to result
|
|
2785
|
+
d_string_append_c(result, '\n');
|
|
2786
|
+
break;
|
|
2787
|
+
}
|
|
2788
|
+
|
|
2789
|
+
return result;
|
|
2790
|
+
}
|
|
2791
|
+
|
|
2792
|
+
|
|
2793
|
+
/// Return string containing engine version.
|
|
2794
|
+
char * mmd_version(void) {
|
|
2795
|
+
char * result;
|
|
2796
|
+
result = my_strdup(MULTIMARKDOWN_VERSION);
|
|
2797
|
+
return result;
|
|
2798
|
+
}
|