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.
Files changed (110) hide show
  1. checksums.yaml +4 -4
  2. data/LICENSE +2 -2
  3. data/README.md +7 -9
  4. data/Rakefile +33 -18
  5. data/ext/Makefile +261 -0
  6. data/ext/extconf.rb +23 -3
  7. data/ext/mmd/aho-corasick.c +596 -0
  8. data/ext/mmd/aho-corasick.h +120 -0
  9. data/ext/mmd/beamer.c +344 -0
  10. data/ext/mmd/beamer.h +72 -0
  11. data/ext/mmd/char.c +156 -0
  12. data/ext/mmd/char.h +111 -0
  13. data/ext/mmd/char_lookup.c +212 -0
  14. data/ext/mmd/critic_markup.c +330 -0
  15. data/ext/mmd/critic_markup.h +94 -0
  16. data/ext/mmd/d_string.c +402 -0
  17. data/ext/mmd/epub.c +563 -0
  18. data/ext/mmd/epub.h +69 -0
  19. data/ext/mmd/fodt.c +2288 -0
  20. data/ext/mmd/fodt.h +81 -0
  21. data/ext/mmd/html.c +2460 -0
  22. data/ext/mmd/html.h +81 -0
  23. data/ext/mmd/i18n.h +170 -0
  24. data/ext/mmd/include/d_string.h +182 -0
  25. data/ext/mmd/include/libMultiMarkdown.h +548 -0
  26. data/ext/mmd/include/token.h +233 -0
  27. data/ext/mmd/latex.c +2435 -0
  28. data/ext/mmd/latex.h +83 -0
  29. data/ext/mmd/lexer.c +3001 -0
  30. data/ext/mmd/lexer.h +75 -0
  31. data/ext/mmd/memoir.c +138 -0
  32. data/ext/mmd/memoir.h +67 -0
  33. data/ext/mmd/miniz.c +7557 -0
  34. data/ext/mmd/miniz.h +1328 -0
  35. data/ext/mmd/mmd.c +2798 -0
  36. data/ext/mmd/mmd.h +120 -0
  37. data/ext/mmd/object_pool.c +141 -0
  38. data/ext/mmd/object_pool.h +101 -0
  39. data/ext/mmd/opendocument-content.c +2071 -0
  40. data/ext/mmd/opendocument-content.h +135 -0
  41. data/ext/mmd/opendocument.c +981 -0
  42. data/ext/mmd/opendocument.h +118 -0
  43. data/ext/mmd/parser.c +1760 -0
  44. data/ext/mmd/parser.h +39 -0
  45. data/{MultiMarkdown-4 → ext/mmd}/rng.c +90 -49
  46. data/ext/mmd/scanners.c +77512 -0
  47. data/ext/mmd/scanners.h +101 -0
  48. data/ext/mmd/stack.c +142 -0
  49. data/ext/mmd/stack.h +113 -0
  50. data/ext/mmd/textbundle.c +455 -0
  51. data/ext/mmd/textbundle.h +115 -0
  52. data/ext/mmd/token.c +773 -0
  53. data/ext/mmd/token_pairs.c +263 -0
  54. data/ext/mmd/token_pairs.h +123 -0
  55. data/ext/mmd/transclude.c +549 -0
  56. data/ext/mmd/transclude.h +87 -0
  57. data/ext/mmd/uthash.h +1074 -0
  58. data/ext/mmd/uuid.c +154 -0
  59. data/ext/mmd/uuid.h +77 -0
  60. data/ext/mmd/version.h +111 -0
  61. data/ext/mmd/writer.c +2652 -0
  62. data/ext/mmd/writer.h +260 -0
  63. data/ext/mmd/zip.c +210 -0
  64. data/ext/mmd/zip.h +120 -0
  65. data/ext/{multi_markdown.c → ruby_multi_markdown.c} +87 -18
  66. data/lib/multi_markdown.bundle +0 -0
  67. data/lib/multi_markdown.rb +5 -8
  68. data/lib/multi_markdown/version.rb +1 -1
  69. data/rmultimarkdown.gemspec +2 -2
  70. data/test/{extensions_test.rb.rb → extensions_test.rb} +10 -54
  71. data/test/multi_markdown_test.rb +13 -0
  72. metadata +67 -47
  73. data/MultiMarkdown-4/GLibFacade.c +0 -310
  74. data/MultiMarkdown-4/GLibFacade.h +0 -100
  75. data/MultiMarkdown-4/beamer.c +0 -182
  76. data/MultiMarkdown-4/beamer.h +0 -11
  77. data/MultiMarkdown-4/critic.c +0 -111
  78. data/MultiMarkdown-4/critic.h +0 -15
  79. data/MultiMarkdown-4/glib.h +0 -11
  80. data/MultiMarkdown-4/html.c +0 -1117
  81. data/MultiMarkdown-4/html.h +0 -14
  82. data/MultiMarkdown-4/latex.c +0 -1217
  83. data/MultiMarkdown-4/latex.h +0 -16
  84. data/MultiMarkdown-4/libMultiMarkdown.h +0 -177
  85. data/MultiMarkdown-4/lyx.c +0 -2265
  86. data/MultiMarkdown-4/lyx.h +0 -37
  87. data/MultiMarkdown-4/lyxbeamer.c +0 -265
  88. data/MultiMarkdown-4/lyxbeamer.h +0 -11
  89. data/MultiMarkdown-4/memoir.c +0 -80
  90. data/MultiMarkdown-4/memoir.h +0 -10
  91. data/MultiMarkdown-4/multimarkdown.c +0 -518
  92. data/MultiMarkdown-4/odf.c +0 -1222
  93. data/MultiMarkdown-4/odf.h +0 -18
  94. data/MultiMarkdown-4/opml.c +0 -189
  95. data/MultiMarkdown-4/opml.h +0 -15
  96. data/MultiMarkdown-4/parse_utilities.c +0 -884
  97. data/MultiMarkdown-4/parser.c +0 -16656
  98. data/MultiMarkdown-4/parser.h +0 -188
  99. data/MultiMarkdown-4/rtf.c +0 -665
  100. data/MultiMarkdown-4/rtf.h +0 -17
  101. data/MultiMarkdown-4/strtok.c +0 -56
  102. data/MultiMarkdown-4/strtok.h +0 -9
  103. data/MultiMarkdown-4/text.c +0 -53
  104. data/MultiMarkdown-4/text.h +0 -11
  105. data/MultiMarkdown-4/toc.c +0 -142
  106. data/MultiMarkdown-4/toc.h +0 -15
  107. data/MultiMarkdown-4/transclude.c +0 -307
  108. data/MultiMarkdown-4/transclude.h +0 -28
  109. data/MultiMarkdown-4/writer.c +0 -731
  110. data/MultiMarkdown-4/writer.h +0 -38
@@ -0,0 +1,263 @@
1
+ /**
2
+
3
+ MultiMarkdown 6 -- Lightweight markup processor to produce HTML, LaTeX, and more.
4
+
5
+ @file token_pairs.c
6
+
7
+ @brief Allow for pairing certain tokens together (e.g. '[' and ']') to create
8
+ more meaningful token trees.
9
+
10
+
11
+ @author Fletcher T. Penney
12
+ @bug
13
+
14
+ **/
15
+
16
+ /*
17
+
18
+ Copyright © 2016 - 2017 Fletcher T. Penney.
19
+
20
+
21
+ The `MultiMarkdown 6` project is released under the MIT License..
22
+
23
+ GLibFacade.c and GLibFacade.h are from the MultiMarkdown v4 project:
24
+
25
+ https://github.com/fletcher/MultiMarkdown-4/
26
+
27
+ MMD 4 is released under both the MIT License and GPL.
28
+
29
+
30
+ CuTest is released under the zlib/libpng license. See CuTest.c for the text
31
+ of the license.
32
+
33
+
34
+ ## The MIT License ##
35
+
36
+ Permission is hereby granted, free of charge, to any person obtaining a copy
37
+ of this software and associated documentation files (the "Software"), to deal
38
+ in the Software without restriction, including without limitation the rights
39
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
40
+ copies of the Software, and to permit persons to whom the Software is
41
+ furnished to do so, subject to the following conditions:
42
+
43
+ The above copyright notice and this permission notice shall be included in
44
+ all copies or substantial portions of the Software.
45
+
46
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
47
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
48
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
49
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
50
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
51
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
52
+ THE SOFTWARE.
53
+
54
+ */
55
+
56
+ #include <stdbool.h>
57
+ #include <stdio.h>
58
+ #include <stdlib.h>
59
+ #include <string.h>
60
+
61
+ #include "stack.h"
62
+ #include "token.h"
63
+ #include "token_pairs.h"
64
+
65
+
66
+ /// Create a new token pair engine
67
+ token_pair_engine * token_pair_engine_new(void) {
68
+ token_pair_engine * e = malloc(sizeof(token_pair_engine));
69
+
70
+ if (e) {
71
+ unsigned short empty[kMaxTokenTypes] = {0};
72
+ unsigned short empty2[kMaxTokenTypes][kMaxTokenTypes] = {{0}};
73
+
74
+ memcpy(e->can_open_pair, empty, sizeof(unsigned short) * kMaxTokenTypes);
75
+ memcpy(e->can_close_pair, empty, sizeof(unsigned short) * kMaxTokenTypes);
76
+
77
+ memcpy(e->pair_type, empty2, sizeof(unsigned short) * kMaxTokenTypes * kMaxTokenTypes);
78
+
79
+ memcpy(e->empty_allowed, empty, sizeof(unsigned short) * kMaxTokenTypes);
80
+ memcpy(e->match_len, empty, sizeof(unsigned short) * kMaxTokenTypes);
81
+ memcpy(e->should_prune, empty, sizeof(unsigned short) * kMaxTokenTypes);
82
+ }
83
+
84
+ return e;
85
+ }
86
+
87
+
88
+ /// Free existing token pair engine
89
+ void token_pair_engine_free(token_pair_engine * e) {
90
+ if (e == NULL) {
91
+ return;
92
+ }
93
+
94
+ free(e);
95
+ }
96
+
97
+
98
+ /// Add a new pairing configuration to a token pair engine
99
+ void token_pair_engine_add_pairing(token_pair_engine * e, unsigned short open_type, unsigned short close_type,
100
+ unsigned short pair_type, int options) {
101
+ // \todo: This needs to be more sophisticated
102
+ e->can_open_pair[open_type] = 1;
103
+ e->can_close_pair[close_type] = 1;
104
+ (e->pair_type)[open_type][close_type] = pair_type;
105
+
106
+ if (options & PAIRING_ALLOW_EMPTY) {
107
+ e->empty_allowed[pair_type] = true;
108
+ }
109
+
110
+ if (options & PAIRING_MATCH_LENGTH) {
111
+ e->match_len[pair_type] = true;
112
+ }
113
+
114
+ if (options & PAIRING_PRUNE_MATCH) {
115
+ e->should_prune[pair_type] = true;
116
+ }
117
+
118
+ }
119
+
120
+
121
+ /// Mate opener and closer together
122
+ void token_pair_mate(token * a, token * b) {
123
+ if (a == NULL | b == NULL) {
124
+ return;
125
+ }
126
+
127
+ a->mate = b;
128
+ a->unmatched = false;
129
+
130
+ b->mate = a;
131
+ b->unmatched = false;
132
+ }
133
+
134
+
135
+ /// Search a token's childen for matching pairs
136
+ void token_pairs_match_pairs_inside_token(token * parent, token_pair_engine * e, stack * s, unsigned short depth) {
137
+
138
+ // Avoid stack overflow in "pathologic" input
139
+ if (depth == kMaxPairRecursiveDepth) {
140
+ return;
141
+ }
142
+
143
+ // Walk the child chain
144
+ token * walker = parent->child;
145
+
146
+ // Counter
147
+ size_t start_counter = s->size;
148
+ size_t i; // We're sharing one stack, so any opener earlier than this belongs to a parent
149
+
150
+ token * peek;
151
+ unsigned short pair_type;
152
+
153
+ unsigned int opener_count[kMaxTokenTypes] = {0}; // Keep track of which token types are on the stack
154
+
155
+ while (walker != NULL) {
156
+
157
+ if (walker->child) {
158
+ token_pairs_match_pairs_inside_token(walker, e, s, depth + 1);
159
+ }
160
+
161
+ // Is this a closer?
162
+ if (walker->can_close && e->can_close_pair[walker->type] && walker->unmatched ) {
163
+ i = s->size;
164
+
165
+ // Do we even have a valid opener in the stack?
166
+ // It's only worth checking if the stack is beyond a certain size
167
+ if (i > start_counter + kLargeStackThreshold) {
168
+ for (int j = 0; j < kMaxTokenTypes; ++j) {
169
+ if (opener_count[j]) {
170
+ if (e->pair_type[j][walker->type]) {
171
+ goto close;
172
+ }
173
+ }
174
+ }
175
+
176
+ // No opener available for this as closer
177
+ goto open;
178
+ }
179
+
180
+ close:
181
+
182
+ // Find matching opener for this closer
183
+ while (i > start_counter) {
184
+ peek = stack_peek_index(s, i - 1);
185
+
186
+ pair_type = e->pair_type[peek->type][walker->type];
187
+
188
+ if (pair_type) {
189
+ if (!e->empty_allowed[pair_type]) {
190
+ // Make sure they aren't consecutive tokens
191
+ if ((peek->next == walker) &&
192
+ (peek->start + peek->len == walker->start)) {
193
+ // i--;
194
+ i = start_counter; // In this situation, we can't use this token as a closer
195
+ continue;
196
+ }
197
+ }
198
+
199
+ if (e->match_len[pair_type]) {
200
+ // Lengths must match
201
+ if (peek->len != walker->len) {
202
+ i--;
203
+ continue;
204
+ }
205
+ }
206
+
207
+ token_pair_mate(peek, walker);
208
+
209
+
210
+ // Clear portion of stack between opener and closer as they are now unavailable for mating
211
+ while (s->size > (i - 1)) {
212
+ peek = stack_pop(s);
213
+ opener_count[peek->type]--;
214
+ }
215
+
216
+ #ifndef NDEBUG
217
+ fprintf(stderr, "stack now sized %lu\n", s->size);
218
+ #endif
219
+ // Prune matched section
220
+
221
+ if (e->should_prune[pair_type]) {
222
+ if (peek->prev == NULL) {
223
+ walker = token_prune_graft(peek, walker, e->pair_type[peek->type][walker->type]);
224
+ parent->child = walker;
225
+ } else {
226
+ walker = token_prune_graft(peek, walker, e->pair_type[peek->type][walker->type]);
227
+ }
228
+ }
229
+
230
+ break;
231
+ }
232
+
233
+ #ifndef NDEBUG
234
+ else {
235
+ fprintf(stderr, "token type %d failed to match stack element\n", walker->type);
236
+ }
237
+
238
+ #endif
239
+ i--;
240
+ }
241
+ }
242
+
243
+ open:
244
+
245
+ // Is this an opener?
246
+ if (walker->can_open && e->can_open_pair[walker->type] && walker->unmatched) {
247
+ stack_push(s, walker);
248
+ opener_count[walker->type]++;
249
+ #ifndef NDEBUG
250
+ fprintf(stderr, "push token type %d to stack (%lu elements)\n", walker->type, s->size);
251
+ #endif
252
+ }
253
+
254
+ walker = walker->next;
255
+ }
256
+
257
+ #ifndef NDEBUG
258
+ fprintf(stderr, "token stack has %lu elements (of %lu)\n", s->size, s->capacity);
259
+ #endif
260
+
261
+ // Remove unused tokens from stack and return to parent
262
+ s->size = start_counter;
263
+ }
@@ -0,0 +1,123 @@
1
+ /**
2
+
3
+ MultiMarkdown 6 -- Lightweight markup processor to produce HTML, LaTeX, and more.
4
+
5
+ @file token_pairs.h
6
+
7
+ @brief Allow for pairing certain tokens together (e.g. '[' and ']') to create
8
+ more meaningful token trees.
9
+
10
+
11
+ @author Fletcher T. Penney
12
+ @bug
13
+
14
+ **/
15
+
16
+ /*
17
+
18
+ Copyright © 2016 - 2017 Fletcher T. Penney.
19
+
20
+
21
+ The `MultiMarkdown 6` project is released under the MIT License..
22
+
23
+ GLibFacade.c and GLibFacade.h are from the MultiMarkdown v4 project:
24
+
25
+ https://github.com/fletcher/MultiMarkdown-4/
26
+
27
+ MMD 4 is released under both the MIT License and GPL.
28
+
29
+
30
+ CuTest is released under the zlib/libpng license. See CuTest.c for the text
31
+ of the license.
32
+
33
+
34
+ ## The MIT License ##
35
+
36
+ Permission is hereby granted, free of charge, to any person obtaining a copy
37
+ of this software and associated documentation files (the "Software"), to deal
38
+ in the Software without restriction, including without limitation the rights
39
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
40
+ copies of the Software, and to permit persons to whom the Software is
41
+ furnished to do so, subject to the following conditions:
42
+
43
+ The above copyright notice and this permission notice shall be included in
44
+ all copies or substantial portions of the Software.
45
+
46
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
47
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
48
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
49
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
50
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
51
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
52
+ THE SOFTWARE.
53
+
54
+ */
55
+
56
+
57
+ #ifndef TOKEN_PAIRS_MULTIMARKDOWN_H
58
+ #define TOKEN_PAIRS_MULTIMARKDOWN_H
59
+
60
+ #include "stack.h"
61
+ #include "token.h"
62
+
63
+
64
+ #ifdef TEST
65
+ #include "CuTest.h"
66
+ #endif
67
+
68
+ #define kMaxTokenTypes 230 //!< This needs to be larger than the largest token type being used
69
+ #define kLargeStackThreshold 1000 //!< Avoid unnecessary searches of large stacks
70
+ #define kMaxPairRecursiveDepth 1000 //!< Maximum recursion depth to traverse when pairing tokens -- to prevent stack overflow with "pathologic" input
71
+
72
+
73
+ /// Store information about which tokens can be paired, and what actions to take when
74
+ /// pairing them.
75
+ struct token_pair_engine {
76
+ unsigned short can_open_pair[kMaxTokenTypes]; //!< Can token type open a pair?
77
+ unsigned short can_close_pair[kMaxTokenTypes]; //!< Can token type close a pair?
78
+
79
+ unsigned short pair_type[kMaxTokenTypes][kMaxTokenTypes]; //!< Which pair are we forming?
80
+
81
+ unsigned short empty_allowed[kMaxTokenTypes]; //!< Is this pair type allowed to be empty?
82
+ unsigned short match_len[kMaxTokenTypes]; //!< Does this pair type require matched lengths of openers/closers?
83
+ unsigned short should_prune[kMaxTokenTypes]; //!< Does this pair type need to be pruned to a child token chain?
84
+ };
85
+
86
+ typedef struct token_pair_engine token_pair_engine;
87
+
88
+
89
+ /// Flags for token pair options
90
+ enum pairings_options {
91
+ PAIRING_ALLOW_EMPTY = 1 << 0, //!< Allow consecutive tokens to match with each other
92
+ PAIRING_MATCH_LENGTH = 1 << 1, //!< Require that opening/closing tokens be same length
93
+ PAIRING_PRUNE_MATCH = 1 << 2, //!< Move the matched sub-chain into a child chain
94
+ };
95
+
96
+
97
+ /// Create a new token pair engine
98
+ token_pair_engine * token_pair_engine_new(void);
99
+
100
+ /// Free existing token pair engine
101
+ void token_pair_engine_free(
102
+ token_pair_engine * e //!< Token pair engine to be freed
103
+ );
104
+
105
+ /// Add a new pairing configuration to a token pair engine
106
+ void token_pair_engine_add_pairing(
107
+ token_pair_engine * e, //!< Token pair engine to add to
108
+ unsigned short open_type, //!< Token type for opener
109
+ unsigned short close_type, //!< Token type for closer
110
+ unsigned short pair_type, //!< Token type for pairing
111
+ int options //!< Token pair options to use
112
+ );
113
+
114
+ /// Search a token's childen for matching pairs
115
+ void token_pairs_match_pairs_inside_token(
116
+ token * parent, //!< Which tokens should we search for pairs
117
+ token_pair_engine * e, //!< Token pair engine to be used for matching
118
+ stack * s, //!< Pointer to a stack to use for pairing tokens
119
+ unsigned short depth //!< Keep track of recursion depth
120
+ );
121
+
122
+
123
+ #endif
@@ -0,0 +1,549 @@
1
+ /**
2
+
3
+ MultiMarkdown 6 -- Lightweight markup processor to produce HTML, LaTeX, and more.
4
+
5
+ @file transclude.c
6
+
7
+ @brief
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 <string.h>
57
+
58
+ #include "d_string.h"
59
+ #include "libMultiMarkdown.h"
60
+ #include "transclude.h"
61
+
62
+ #if defined(__WIN32)
63
+ #include <windows.h>
64
+ #endif
65
+
66
+ #define kBUFFERSIZE 4096 // How many bytes to read at a time
67
+
68
+
69
+ /// strndup not available on all platforms
70
+ static char * my_strndup(const char * source, size_t n) {
71
+ if (source == NULL) {
72
+ return NULL;
73
+ }
74
+
75
+ size_t len = 0;
76
+ char * result;
77
+ const char * test = source;
78
+
79
+ // strlen is too slow if strlen(source) >> n
80
+ for (len = 0; len < n; ++len) {
81
+ if (test == '\0') {
82
+ break;
83
+ }
84
+
85
+ test++;
86
+ }
87
+
88
+ result = malloc(len + 1);
89
+
90
+ if (result) {
91
+ memcpy(result, source, len);
92
+ result[len] = '\0';
93
+ }
94
+
95
+ return result;
96
+ }
97
+
98
+
99
+ /// strdup() not available on all platforms
100
+ static char * my_strdup(const char * source) {
101
+ char * result = malloc(strlen(source) + 1);
102
+
103
+ if (result) {
104
+ strcpy(result, source);
105
+ }
106
+
107
+ return result;
108
+ }
109
+
110
+
111
+ /// Windows can use either `\` or `/` as a separator -- thanks to t-beckmann on github
112
+ /// for suggesting a fix for this.
113
+ bool is_separator(char c) {
114
+ #if defined(__WIN32)
115
+ return c == '\\' || c == '/';
116
+ #else
117
+ return c == '/';
118
+ #endif
119
+ }
120
+
121
+
122
+ #ifdef TEST
123
+ void Test_is_separator(CuTest* tc) {
124
+ char * test = "a/\\";
125
+
126
+ #if defined(__WIN32)
127
+ CuAssertIntEquals(tc, false, is_separator(test[0]));
128
+ CuAssertIntEquals(tc, true, is_separator(test[1]));
129
+ CuAssertIntEquals(tc, true, is_separator(test[2]));
130
+ #else
131
+ CuAssertIntEquals(tc, false, is_separator(test[0]));
132
+ CuAssertIntEquals(tc, true, is_separator(test[1]));
133
+ CuAssertIntEquals(tc, false, is_separator(test[2]));
134
+ #endif
135
+ }
136
+ #endif
137
+
138
+
139
+ void add_trailing_sep(DString * path) {
140
+ #if defined(__WIN32)
141
+ char sep = '\\';
142
+ #else
143
+ char sep = '/';
144
+ #endif
145
+
146
+ // Ensure that folder ends in separator
147
+ if (!is_separator(path->str[path->currentStringLength - 1])) {
148
+ d_string_append_c(path, sep);
149
+ }
150
+ }
151
+
152
+ /// Combine directory and base filename to create a full path */
153
+ char * path_from_dir_base(const char * dir, const char * base) {
154
+ if (!dir && !base) {
155
+ return NULL;
156
+ }
157
+
158
+
159
+ DString * path = NULL;
160
+ char * result = NULL;
161
+
162
+ if ((base != NULL) && (is_separator(base[0]))) {
163
+ // We have an absolute path
164
+ path = d_string_new(base);
165
+ } else {
166
+ // We have a directory and relative path
167
+ path = d_string_new(dir);
168
+
169
+ // Ensure that folder ends in separator
170
+ add_trailing_sep(path);
171
+
172
+ // Append filename (if present)
173
+ if (base) {
174
+ d_string_append(path, base);
175
+ }
176
+ }
177
+
178
+ result = path->str;
179
+ d_string_free(path, false);
180
+
181
+ return result;
182
+ }
183
+
184
+
185
+ #ifdef TEST
186
+ void Test_path_from_dir_base(CuTest* tc) {
187
+ char dir[10] = "/foo";
188
+ char base[10] = "bar";
189
+
190
+ char * path = path_from_dir_base(dir, base);
191
+
192
+ #if defined(__WIN32)
193
+ CuAssertStrEquals(tc, "/foo\\bar", path);
194
+ #else
195
+ CuAssertStrEquals(tc, "/foo/bar", path);
196
+ #endif
197
+
198
+ free(path);
199
+ strcpy(base, "/bar");
200
+
201
+ path = path_from_dir_base(dir, base);
202
+
203
+ CuAssertStrEquals(tc, "/bar", path);
204
+
205
+ free(path);
206
+
207
+ path = path_from_dir_base(NULL, NULL);
208
+ CuAssertStrEquals(tc, NULL, path);
209
+ }
210
+ #endif
211
+
212
+
213
+ /// Separate filename and directory from a full path
214
+ ///
215
+ /// See http://stackoverflow.com/questions/1575278/function-to-split-a-filepath-into-path-and-file
216
+ void split_path_file(char ** dir, char ** file, const char * path) {
217
+ const char * slash = path, * next;
218
+
219
+ #if defined(__WIN32)
220
+ const char sep[] = "\\/"; // Windows allows either variant
221
+ #else
222
+ const char sep[] = "/";
223
+ #endif
224
+
225
+ while ((next = strpbrk(slash + 1, sep))) {
226
+ slash = next;
227
+ }
228
+
229
+ if (path != slash) {
230
+ slash++;
231
+ }
232
+
233
+ *dir = my_strndup(path, slash - path);
234
+ *file = my_strdup(slash);
235
+ }
236
+
237
+ #ifdef TEST
238
+ void Test_split_path_file(CuTest* tc) {
239
+ char * dir, * file;
240
+
241
+ char * path = "/foo/bar.txt";
242
+ split_path_file(&dir, &file, path);
243
+
244
+ CuAssertStrEquals(tc, "/foo/", dir);
245
+ CuAssertStrEquals(tc, "bar.txt", file);
246
+
247
+ path = "\\foo\\bar.txt";
248
+ split_path_file(&dir, &file, path);
249
+
250
+ #if defined(__WIN32)
251
+ CuAssertStrEquals(tc, "\\foo\\", dir);
252
+ CuAssertStrEquals(tc, "bar.txt", file);
253
+ #else
254
+ CuAssertStrEquals(tc, "", dir);
255
+ CuAssertStrEquals(tc, "\\foo\\bar.txt", file);
256
+ #endif
257
+ }
258
+ #endif
259
+
260
+
261
+ DString * scan_file(const char * fname) {
262
+ /* Read from a file and return a DString *
263
+ `buffer` will need to be freed elsewhere */
264
+
265
+ char chunk[kBUFFERSIZE];
266
+ size_t bytes;
267
+
268
+ FILE * file;
269
+
270
+ #if defined(__WIN32)
271
+ int wchars_num = MultiByteToWideChar(CP_UTF8, 0, fname, -1, NULL, 0);
272
+ wchar_t wstr[wchars_num];
273
+ MultiByteToWideChar(CP_UTF8, 0, fname, -1, wstr, wchars_num);
274
+
275
+ if ((file = _wfopen(wstr, L"rb")) == NULL) {
276
+ #else
277
+
278
+ if ((file = fopen(fname, "r")) == NULL ) {
279
+ #endif
280
+
281
+ return NULL;
282
+ }
283
+
284
+ DString * buffer = d_string_new("");
285
+
286
+ while ((bytes = fread(chunk, 1, kBUFFERSIZE, file)) > 0) {
287
+ d_string_append_c_array(buffer, chunk, bytes);
288
+ }
289
+
290
+ fclose(file);
291
+
292
+ return buffer;
293
+ }
294
+
295
+
296
+ /// Recursively transclude source text, given a search directory.
297
+ /// Track files to prevent infinite recursive loops
298
+ void mmd_transclude_source(DString * source, const char * search_path, const char * source_path, short format, stack * parsed, stack * manifest) {
299
+ DString * file_path;
300
+ DString * buffer;
301
+
302
+ // Ensure search_folder is tidied up
303
+ char * search_folder = path_from_dir_base(search_path, NULL);
304
+ char * source_folder;
305
+ char * source_file;
306
+
307
+ split_path_file(&source_folder, &source_file, source_path);
308
+
309
+ char * start, * stop;
310
+ char text[1100];
311
+
312
+ char * temp;
313
+
314
+ size_t offset = 0;
315
+ size_t last_match;
316
+
317
+ mmd_engine * e = mmd_engine_create_with_dstring(source, EXT_TRANSCLUDE);
318
+
319
+ if (mmd_engine_has_metadata(e, &offset)) {
320
+
321
+ temp = mmd_engine_metavalue_for_key(e, "transclude base");
322
+
323
+ if (temp) {
324
+ // The new file overrides the search path
325
+ free(search_folder);
326
+
327
+ // Calculate new search path relative to source document
328
+ search_folder = path_from_dir_base(source_folder, temp);
329
+ }
330
+ }
331
+
332
+ free(source_folder);
333
+ free(source_file);
334
+
335
+ mmd_engine_free(e, false);
336
+
337
+ if (search_folder == NULL) {
338
+ // We don't have anywhere to search, so nothing to do
339
+ goto exit;
340
+ }
341
+
342
+ // Make sure we use a parse tree for children
343
+ stack * parse_stack = parsed;
344
+
345
+ if (parsed == NULL) {
346
+ // Create temporary stack
347
+ parse_stack = stack_new(0);
348
+ }
349
+
350
+ // Remember where we currently are in the stack
351
+ size_t stack_depth = parse_stack->size;
352
+
353
+ // Iterate through source text, looking for `{{foo}}`
354
+
355
+ start = strstr(&source->str[offset], "{{");
356
+
357
+ while (start != NULL) {
358
+ stop = strstr(start, "}}");
359
+
360
+ if (stop == NULL) {
361
+ break;
362
+ }
363
+
364
+ // Remember insertion point
365
+ last_match = start - source->str;
366
+
367
+ // Ensure we have a reasonable match -- cap at 1000 characters
368
+ if (stop - start < 1000) {
369
+ // Grab text
370
+ strncpy(text, start + 2, stop - start - 2);
371
+ text[stop - start - 2] = '\0';
372
+
373
+ // Is this just {{TOC}}
374
+ if (strcmp("TOC", text) == 0) {
375
+ start = strstr(stop, "{{");
376
+ continue;
377
+ }
378
+
379
+ // Is this an absolute path or relative path?
380
+ if (is_separator(text[0])) {
381
+ // Absolute path
382
+ file_path = d_string_new(text);
383
+ } else {
384
+ // Relative path
385
+ file_path = d_string_new(search_folder);
386
+
387
+ // Ensure that search_folder ends in separator
388
+ add_trailing_sep(file_path);
389
+
390
+ d_string_append(file_path, text);
391
+ }
392
+
393
+ // Adjust file wildcard extension for output format
394
+ // e.g. `foo.*`
395
+ if ((format != FORMAT_MMD) && strncmp(&text[stop - start - 4], ".*", 2) == 0) {
396
+ // Trim '.*'
397
+ d_string_erase(file_path, file_path->currentStringLength - 2, 2);
398
+
399
+ switch (format) {
400
+ case FORMAT_HTML:
401
+ d_string_append(file_path, ".html");
402
+ break;
403
+
404
+ case FORMAT_LATEX:
405
+ case FORMAT_BEAMER:
406
+ case FORMAT_MEMOIR:
407
+ d_string_append(file_path, ".tex");
408
+ break;
409
+
410
+ case FORMAT_FODT:
411
+ d_string_append(file_path, ".fodt");
412
+ break;
413
+
414
+ default:
415
+ d_string_append(file_path, ".txt");
416
+ break;
417
+
418
+ }
419
+ }
420
+
421
+ // Prevent infinite recursive loops
422
+ for (int i = 0; i < stack_depth; ++i) {
423
+ temp = stack_peek_index(parse_stack, i);
424
+
425
+ if (strcmp(file_path->str, temp) == 0) {
426
+ // We have parsed this file already, don't recurse infinitely
427
+ last_match += 2;
428
+ goto finish_file;
429
+ }
430
+ }
431
+
432
+ // Add this file to stack
433
+ stack_push(parse_stack, file_path->str);
434
+
435
+ // Add file to the manifest?
436
+ if (manifest) {
437
+ bool add = true;
438
+
439
+ for (int i = 0; i < manifest->size; ++i) {
440
+ temp = stack_peek_index(manifest, i);
441
+
442
+ if (strcmp(file_path->str, temp) == 0) {
443
+ // Already on manifest, don't duplicate
444
+ add = false;
445
+ }
446
+ }
447
+
448
+ // Add path to manifest
449
+ if (add) {
450
+ stack_push(manifest, my_strdup(file_path->str));
451
+ }
452
+ }
453
+
454
+ // Read the file
455
+ buffer = scan_file(file_path->str);
456
+
457
+ // Substitue buffer for transclusion token
458
+ if (buffer) {
459
+ // Erase transclusion token from current source
460
+ d_string_erase(source, start - source->str, 2 + stop - start);
461
+
462
+ // Recursively check this file for transclusions
463
+ mmd_transclude_source(buffer, search_folder, file_path->str, format, parse_stack, manifest);
464
+
465
+ // Strip metadata from buffer now that we have parsed it
466
+ e = mmd_engine_create_with_dstring(buffer, EXT_TRANSCLUDE);
467
+
468
+ if (mmd_engine_has_metadata(e, &offset)) {
469
+ d_string_erase(buffer, 0, offset);
470
+ } else {
471
+ // Do we need to strip BOM?
472
+ if (strncmp(buffer->str, "\xef\xbb\xbf", 3) == 0) {
473
+ d_string_erase(buffer, 0, 3);
474
+ }
475
+ }
476
+
477
+ mmd_engine_free(e, false);
478
+
479
+ // Insert file text -- this may cause d_string to reallocate the
480
+ // character buffer, meaning start/stop are no longer valid
481
+ d_string_insert(source, start - source->str, buffer->str);
482
+
483
+ // Shift search point
484
+ last_match += buffer->currentStringLength;
485
+
486
+ d_string_free(buffer, true);
487
+ } else {
488
+ // Skip over marker
489
+ last_match += 2;
490
+ }
491
+
492
+ // Remove this file from stack
493
+ stack_pop(parse_stack);
494
+
495
+ finish_file:
496
+ d_string_free(file_path, true);
497
+
498
+ } else {
499
+ // Match was too long to be reasonable file name
500
+ // Skip over marker
501
+ last_match += 2;
502
+ }
503
+
504
+ start = strstr(source->str + last_match, "{{");
505
+ }
506
+
507
+ exit:
508
+
509
+ if (parsed == NULL) {
510
+ // Free temp stack
511
+ stack_free(parse_stack);
512
+ } else {
513
+ // Reset stack depth
514
+ parse_stack->size = stack_depth;
515
+ }
516
+
517
+ free(search_folder);
518
+ }
519
+
520
+
521
+
522
+ /// If MMD Header metadata used, insert it into appropriate place
523
+ void mmd_prepend_mmd_header(DString * source) {
524
+ size_t end;
525
+
526
+ if (mmd_d_string_has_metadata(source, &end)) {
527
+ char * meta = mmd_d_string_metavalue_for_key(source, "mmdheader");
528
+
529
+ if (meta) {
530
+ d_string_insert(source, end, "\n\n");
531
+ d_string_insert(source, end + 2, meta);
532
+ free(meta);
533
+ }
534
+ }
535
+ }
536
+
537
+
538
+ /// If MMD Footer metadata used, insert it into appropriate place
539
+ void mmd_append_mmd_footer(DString * source) {
540
+ char * meta = mmd_d_string_metavalue_for_key(source, "mmdfooter");
541
+
542
+ if (meta) {
543
+ d_string_append(source, "\n\n");
544
+ d_string_append(source, meta);
545
+
546
+ free(meta);
547
+ }
548
+ }
549
+