rmultimarkdown 4.7.1.1 → 6.2.2.1

Sign up to get free protection for your applications and to get access to all the features.
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
+