Coverage Report

Created: 2026-11-12 06:39

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/crow/include/crow/mustache.h
Line
Count
Source
1
/**
2
 * \file crow/mustache.h
3
 * \brief This file includes the definition of the crow::mustache
4
 * namespace and its members.
5
 */
6
7
#pragma once
8
#include <string>
9
#include <vector>
10
#include <fstream>
11
#include <iterator>
12
#include <functional>
13
#include "crow/json.h"
14
#include "crow/logging.h"
15
#include "crow/returnable.h"
16
#include "crow/utility.h"
17
18
namespace crow // NOTE: Already documented in "crow/app.h"
19
{
20
    /**
21
     * \namespace crow::mustache
22
     * \brief In this namespace is defined most of the functions and
23
     * classes related to template rendering.
24
     *
25
     * If you are here you might want to read these functions and
26
     * classes:
27
     *
28
     * - \ref template_t
29
     * - \ref load_text
30
     * - \ref load_text_unsafe
31
     * - \ref load
32
     * - \ref load_unsafe
33
     *
34
     * As name suggest, crow uses [mustache](https://en.wikipedia.org/wiki/Mustache_(template_system))
35
     * as main template rendering system.
36
     *
37
     * You may be interested in taking a look at the [Templating guide
38
     * page](https://crowcpp.org/master/guides/templating/).
39
     */
40
    namespace mustache
41
    {
42
        using context = json::wvalue;
43
44
        template_t load(const std::string& filename);
45
46
        /**
47
         * \class invalid_template_exception
48
         * \brief Represents compilation error of an template. Throwed
49
         * specially at mustache compile time.
50
         */
51
        class invalid_template_exception : public std::exception
52
        {
53
        public:
54
            invalid_template_exception(const std::string& msg_):
55
817
              msg("crow::mustache error: " + msg_)
56
817
            {}
57
            virtual const char* what() const throw() override
58
0
            {
59
0
                return msg.c_str();
60
0
            }
61
            std::string msg;
62
        };
63
64
        /**
65
         * \struct rendered_template
66
         * \brief Returned object after call the
67
         * \ref template_t::render() method. Its intended to be
68
         * returned during a **rule declaration**.
69
         *
70
         * \see \ref CROW_ROUTE
71
         * \see \ref CROW_BP_ROUTE
72
         */
73
        struct rendered_template : returnable
74
        {
75
            rendered_template():
76
0
              returnable("text/html") {}
77
78
            rendered_template(std::string& body):
79
0
              returnable("text/html"), body_(std::move(body)) {}
80
81
            std::string body_;
82
83
            std::string dump() const override
84
0
            {
85
0
                return body_;
86
0
            }
87
        };
88
89
        /**
90
         * \enum ActionType
91
         * \brief Used in \ref Action to represent different parsing
92
         * behaviors.
93
         *
94
         * \see \ref Action
95
         */
96
        enum class ActionType
97
        {
98
            Ignore,
99
            Tag,
100
            UnescapeTag,
101
            OpenBlock,
102
            CloseBlock,
103
            ElseBlock,
104
            Partial,
105
        };
106
107
        /**
108
         * \struct Action
109
         * \brief Used during mustache template compilation to
110
         * represent parsing actions.
111
         *
112
         * \see \ref compile
113
         * \see \ref template_t
114
         */
115
        struct Action
116
        {
117
            bool has_end_match;
118
            char tag_char;
119
            int start;
120
            int end;
121
            int pos;
122
            ActionType t;
123
124
            Action(char tag_char_, ActionType t_, size_t start_, size_t end_, size_t pos_ = 0):
125
9.85M
              has_end_match(false), tag_char(tag_char_), start(static_cast<int>(start_)), end(static_cast<int>(end_)), pos(static_cast<int>(pos_)), t(t_)
126
9.85M
            {
127
9.85M
            }
128
129
8.07M
            bool missing_end_pair() const {
130
8.07M
                switch (t)
131
8.07M
                {
132
3.92M
                    case ActionType::Ignore:
133
4.14M
                    case ActionType::Tag:
134
4.16M
                    case ActionType::UnescapeTag:
135
4.23M
                    case ActionType::CloseBlock:
136
7.99M
                    case ActionType::Partial:
137
7.99M
                        return false;
138
139
                    // requires a match
140
10.0k
                    case ActionType::OpenBlock:
141
75.0k
                    case ActionType::ElseBlock:
142
75.0k
                        return !has_end_match;
143
144
0
                    default:
145
0
                        throw std::logic_error("invalid type");
146
8.07M
                }
147
8.07M
            }
148
        };
149
150
        /**
151
         * \class template_t
152
         * \brief Compiled mustache template object.
153
         *
154
         * \warning Use \ref compile instead.
155
         */
156
        class template_t
157
        {
158
        public:
159
            template_t(std::string body):
160
2.66M
              body_(std::move(body))
161
2.66M
            {
162
                // {{ {{# {{/ {{^ {{! {{> {{=
163
2.66M
                parse();
164
2.66M
            }
165
166
        private:
167
            std::string tag_name(const Action& action) const
168
2.92M
            {
169
2.92M
                return body_.substr(action.start, action.end - action.start);
170
2.92M
            }
171
            auto find_context(const std::string& name, const std::vector<const context*>& stack, bool shouldUseOnlyFirstStackValue = false) const -> std::pair<bool, const context&>
172
0
            {
173
0
                if (name == ".")
174
0
                {
175
0
                    return {true, *stack.back()};
176
0
                }
177
0
                static json::wvalue empty_str;
178
0
                empty_str = "";
179
0
180
0
                size_t dotPosition = name.find(".");
181
0
                if (dotPosition == std::string::npos)
182
0
                {
183
0
                    for (auto it = stack.rbegin(); it != stack.rend(); ++it)
184
0
                    {
185
0
                        if ((*it)->t() == json::type::Object)
186
0
                        {
187
0
                            if ((*it)->count(name))
188
0
                                return {true, (**it)[name]};
189
0
                        }
190
0
                    }
191
0
                }
192
0
                else
193
0
                {
194
0
                    std::vector<size_t> dotPositions;
195
0
                    dotPositions.push_back(std::string::npos);
196
0
                    while (dotPosition != std::string::npos)
197
0
                    {
198
0
                        dotPositions.push_back(dotPosition);
199
0
                        dotPosition = name.find(".", dotPosition + 1);
200
0
                    }
201
0
                    dotPositions.push_back(name.size());
202
0
                    std::vector<std::string> names;
203
0
                    names.reserve(dotPositions.size() - 1);
204
0
                    for (size_t i = 1; i < dotPositions.size(); i++)
205
0
                        names.emplace_back(name.substr(dotPositions[i - 1] + 1, dotPositions[i] - dotPositions[i - 1] - 1));
206
0
207
0
                    for (auto it = stack.rbegin(); it != stack.rend(); ++it)
208
0
                    {
209
0
                        const context* view = *it;
210
0
                        bool found = true;
211
0
                        for (auto jt = names.begin(); jt != names.end(); ++jt)
212
0
                        {
213
0
                            if (view->t() == json::type::Object &&
214
0
                                view->count(*jt))
215
0
                            {
216
0
                                view = &(*view)[*jt];
217
0
                            }
218
0
                            else
219
0
                            {
220
0
                                if (shouldUseOnlyFirstStackValue)
221
0
                                {
222
0
                                    return {false, empty_str};
223
0
                                }
224
0
                                found = false;
225
0
                                break;
226
0
                            }
227
0
                        }
228
0
                        if (found)
229
0
                            return {true, *view};
230
0
                    }
231
0
                }
232
0
233
0
                return {false, empty_str};
234
0
            }
235
236
            void escape(const std::string& in, std::string& out) const
237
0
            {
238
0
                out.reserve(out.size() + in.size());
239
0
                for (auto it = in.begin(); it != in.end(); ++it)
240
0
                {
241
0
                    switch (*it)
242
0
                    {
243
0
                        case '&': out += "&amp;"; break;
244
0
                        case '<': out += "&lt;"; break;
245
0
                        case '>': out += "&gt;"; break;
246
0
                        case '"': out += "&quot;"; break;
247
0
                        case '\'': out += "&#39;"; break;
248
0
                        case '/': out += "&#x2F;"; break;
249
0
                        case '`': out += "&#x60;"; break;
250
0
                        case '=': out += "&#x3D;"; break;
251
0
                        default: out += *it; break;
252
0
                    }
253
0
                }
254
0
            }
255
256
            bool isTagInsideObjectBlock(const int& current, const std::vector<const context*>& stack) const
257
0
            {
258
0
                int openedBlock = 0;
259
0
                for (int i = current; i > 0; --i)
260
0
                {
261
0
                    auto& action = actions_[i - 1];
262
0
263
0
                    if (action.t == ActionType::OpenBlock)
264
0
                    {
265
0
                        if (openedBlock == 0 && (*stack.rbegin())->t() == json::type::Object)
266
0
                        {
267
0
                            return true;
268
0
                        }
269
0
                        --openedBlock;
270
0
                    }
271
0
                    else if (action.t == ActionType::CloseBlock)
272
0
                    {
273
0
                        ++openedBlock;
274
0
                    }
275
0
                }
276
0
277
0
                return false;
278
0
            }
279
280
            void render_internal(int actionBegin, int actionEnd, std::vector<const context*>& stack, std::string& out, int indent) const
281
2.66M
            {
282
2.66M
                int current = actionBegin;
283
284
2.66M
                if (indent)
285
4.09k
                    out.insert(out.size(), indent, ' ');
286
287
6.85M
                while (current < actionEnd)
288
4.18M
                {
289
4.18M
                    auto& fragment = fragments_[current];
290
4.18M
                    auto& action = actions_[current];
291
4.18M
                    render_fragment(fragment, indent, out);
292
4.18M
                    switch (action.t)
293
4.18M
                    {
294
1.22M
                        case ActionType::Ignore:
295
                            // do nothing
296
1.22M
                            break;
297
2.66M
                        case ActionType::Partial:
298
2.66M
                        {
299
2.66M
                            std::string partial_name = tag_name(action);
300
2.66M
                            auto partial_templ = load(partial_name);
301
2.66M
                            int partial_indent = action.pos;
302
2.66M
                            partial_templ.render_internal(0, partial_templ.fragments_.size() - 1, stack, out, partial_indent ? indent + partial_indent : 0);
303
2.66M
                        }
304
2.66M
                        break;
305
12.5k
                        case ActionType::UnescapeTag:
306
226k
                        case ActionType::Tag:
307
226k
                        {
308
226k
                            bool shouldUseOnlyFirstStackValue = false;
309
226k
                            if (isTagInsideObjectBlock(current, stack))
310
75.4k
                            {
311
75.4k
                                shouldUseOnlyFirstStackValue = true;
312
75.4k
                            }
313
226k
                            auto optional_ctx = find_context(tag_name(action), stack, shouldUseOnlyFirstStackValue);
314
226k
                            auto& ctx = optional_ctx.second;
315
226k
                            switch (ctx.t())
316
226k
                            {
317
0
                                case json::type::False:
318
0
                                case json::type::True:
319
0
                                case json::type::Number:
320
0
                                    out += ctx.dump();
321
0
                                    break;
322
226k
                                case json::type::String:
323
226k
                                    if (action.t == ActionType::Tag)
324
213k
                                        escape(ctx.s, out);
325
12.5k
                                    else
326
12.5k
                                        out += ctx.s;
327
226k
                                    break;
328
0
                                case json::type::Function:
329
0
                                {
330
0
                                    std::string execute_result = ctx.execute();
331
0
                                    while (execute_result.find("{{") != std::string::npos)
332
0
                                    {
333
0
                                        template_t result_plug(execute_result);
334
0
                                        execute_result = result_plug.render_string(*(stack[0]));
335
0
                                    }
336
337
0
                                    if (action.t == ActionType::Tag)
338
0
                                        escape(execute_result, out);
339
0
                                    else
340
0
                                        out += execute_result;
341
0
                                }
342
0
                                break;
343
60
                                default:
344
60
                                    throw std::runtime_error("not implemented tag type" + utility::lexical_cast<std::string>(static_cast<int>(ctx.t())));
345
226k
                            }
346
226k
                        }
347
226k
                        break;
348
226k
                        case ActionType::ElseBlock:
349
33.6k
                        {
350
33.6k
                            static context nullContext;
351
33.6k
                            auto optional_ctx = find_context(tag_name(action), stack);
352
33.6k
                            if (!optional_ctx.first)
353
30.8k
                            {
354
30.8k
                                stack.emplace_back(&nullContext);
355
30.8k
                                break;
356
30.8k
                            }
357
358
2.72k
                            auto& ctx = optional_ctx.second;
359
2.72k
                            switch (ctx.t())
360
2.72k
                            {
361
0
                                case json::type::List:
362
0
                                    if (ctx.l && !ctx.l->empty())
363
0
                                        current = action.pos;
364
0
                                    else
365
0
                                        stack.emplace_back(&nullContext);
366
0
                                    break;
367
0
                                case json::type::False:
368
1.98k
                                case json::type::Null:
369
1.98k
                                    stack.emplace_back(&nullContext);
370
1.98k
                                    break;
371
740
                                default:
372
740
                                    current = action.pos;
373
740
                                    break;
374
2.72k
                            }
375
2.72k
                            break;
376
2.72k
                        }
377
7.80k
                        case ActionType::OpenBlock:
378
7.80k
                        {
379
7.80k
                            auto optional_ctx = find_context(tag_name(action), stack);
380
7.80k
                            if (!optional_ctx.first)
381
2.67k
                            {
382
2.67k
                                current = action.pos;
383
2.67k
                                break;
384
2.67k
                            }
385
386
5.12k
                            auto& ctx = optional_ctx.second;
387
5.12k
                            switch (ctx.t())
388
5.12k
                            {
389
0
                                case json::type::List:
390
0
                                    if (ctx.l)
391
0
                                        for (auto it = ctx.l->begin(); it != ctx.l->end(); ++it)
392
0
                                        {
393
0
                                            stack.push_back(&*it);
394
0
                                            render_internal(current + 1, action.pos, stack, out, indent);
395
0
                                            stack.pop_back();
396
0
                                        }
397
0
                                    current = action.pos;
398
0
                                    break;
399
0
                                case json::type::Number:
400
3.12k
                                case json::type::String:
401
4.07k
                                case json::type::Object:
402
4.07k
                                case json::type::True:
403
4.07k
                                    stack.push_back(&ctx);
404
4.07k
                                    break;
405
0
                                case json::type::False:
406
1.04k
                                case json::type::Null:
407
1.04k
                                    current = action.pos;
408
1.04k
                                    break;
409
0
                                default:
410
0
                                    throw std::runtime_error("{{#: not implemented context type: " + utility::lexical_cast<std::string>(static_cast<int>(ctx.t())));
411
0
                                    break;
412
5.12k
                            }
413
5.12k
                            break;
414
5.12k
                        }
415
33.6k
                        case ActionType::CloseBlock:
416
33.6k
                            stack.pop_back();
417
33.6k
                            break;
418
0
                        default:
419
0
                            throw std::runtime_error("not implemented " + utility::lexical_cast<std::string>(static_cast<int>(action.t)));
420
4.18M
                    }
421
4.18M
                    current++;
422
4.18M
                }
423
2.66M
                auto& fragment = fragments_[actionEnd];
424
2.66M
                render_fragment(fragment, indent, out);
425
2.66M
            }
426
            void render_fragment(const std::pair<int, int> fragment, int indent, std::string& out) const
427
6.85M
            {
428
6.85M
                if (indent)
429
4.09k
                {
430
4.09k
                    for (int i = fragment.first; i < fragment.second; i++)
431
0
                    {
432
0
                        out += body_[i];
433
0
                        if (body_[i] == '\n' && i + 1 != static_cast<int>(body_.size()))
434
0
                            out.insert(out.size(), indent, ' ');
435
0
                    }
436
4.09k
                }
437
6.84M
                else
438
6.84M
                    out.insert(out.size(), body_, fragment.first, fragment.second - fragment.first);
439
6.85M
            }
440
441
        public:
442
            /// Output a returnable template from this mustache template
443
            rendered_template render() const
444
0
            {
445
0
                context empty_ctx;
446
0
                std::vector<const context*> stack;
447
0
                stack.emplace_back(&empty_ctx);
448
0
449
0
                std::string ret;
450
0
                render_internal(0, fragments_.size() - 1, stack, ret, 0);
451
0
                return rendered_template(ret);
452
0
            }
453
454
            /// Apply the values from the context provided and output a returnable template from this mustache template
455
            rendered_template render(const context& ctx) const
456
0
            {
457
0
                std::vector<const context*> stack;
458
0
                stack.emplace_back(&ctx);
459
0
460
0
                std::string ret;
461
0
                render_internal(0, fragments_.size() - 1, stack, ret, 0);
462
0
                return rendered_template(ret);
463
0
            }
464
465
            /// Apply the values from the context provided and output a returnable template from this mustache template
466
            rendered_template render(const context&& ctx) const
467
0
            {
468
0
                return render(ctx);
469
0
            }
470
471
            /// Output a returnable template from this mustache template
472
            std::string render_string() const
473
0
            {
474
0
                context empty_ctx;
475
0
                std::vector<const context*> stack;
476
0
                stack.emplace_back(&empty_ctx);
477
0
478
0
                std::string ret;
479
0
                render_internal(0, fragments_.size() - 1, stack, ret, 0);
480
0
                return ret;
481
0
            }
482
483
            /// Apply the values from the context provided and output a returnable template from this mustache template
484
            std::string render_string(const context& ctx) const
485
4.28k
            {
486
4.28k
                std::vector<const context*> stack;
487
4.28k
                stack.emplace_back(&ctx);
488
489
4.28k
                std::string ret;
490
4.28k
                render_internal(0, fragments_.size() - 1, stack, ret, 0);
491
4.28k
                return ret;
492
4.28k
            }
493
494
        private:
495
            void parse()
496
2.66M
            {
497
2.66M
                std::string tag_open = "{{";
498
2.66M
                std::string tag_close = "}}";
499
500
2.66M
                std::vector<int> blockPositions;
501
502
2.66M
                size_t current = 0;
503
9.85M
                while (1)
504
9.85M
                {
505
9.85M
                    size_t idx = body_.find(tag_open, current);
506
9.85M
                    if (idx == body_.npos)
507
2.66M
                    {
508
2.66M
                        fragments_.emplace_back(static_cast<int>(current), static_cast<int>(body_.size()));
509
2.66M
                        actions_.emplace_back('!', ActionType::Ignore, 0, 0);
510
2.66M
                        break;
511
2.66M
                    }
512
7.19M
                    fragments_.emplace_back(static_cast<int>(current), static_cast<int>(idx));
513
514
7.19M
                    idx += tag_open.size();
515
7.19M
                    size_t endIdx = body_.find(tag_close, idx);
516
7.19M
                    if (endIdx == idx)
517
12
                    {
518
12
                        throw invalid_template_exception("empty tag is not allowed");
519
12
                    }
520
7.19M
                    if (endIdx == body_.npos)
521
179
                    {
522
                        // error, no matching tag
523
179
                        throw invalid_template_exception("not matched opening tag");
524
179
                    }
525
7.19M
                    current = endIdx + tag_close.size();
526
7.19M
                    char tag_char = body_[idx];
527
7.19M
                    switch (tag_char)
528
7.19M
                    {
529
687k
                        case '#':
530
687k
                            idx++;
531
688k
                            while (body_[idx] == ' ')
532
461
                                idx++;
533
688k
                            while (body_[endIdx - 1] == ' ')
534
653
                                endIdx--;
535
687k
                            blockPositions.emplace_back(static_cast<int>(actions_.size()));
536
687k
                            actions_.emplace_back(tag_char, ActionType::OpenBlock, idx, endIdx);
537
687k
                            break;
538
160k
                        case '/':
539
160k
                            idx++;
540
161k
                            while (body_[idx] == ' ')
541
706
                                idx++;
542
161k
                            while (body_[endIdx - 1] == ' ')
543
697
                                endIdx--;
544
160k
                            {
545
160k
                                if (blockPositions.empty())
546
27
                                {
547
27
                                    throw invalid_template_exception(
548
27
                                             std::string("unexpected closing tag: ")
549
27
                                             + body_.substr(idx, endIdx - idx)
550
27
                                             );
551
27
                                }
552
160k
                                auto& matched = actions_[blockPositions.back()];
553
160k
                                if (body_.compare(idx, endIdx - idx,
554
160k
                                                  body_, matched.start, matched.end - matched.start) != 0)
555
198
                                {
556
198
                                     throw invalid_template_exception(
557
198
                                             std::string("not matched {{")
558
198
                                             + matched.tag_char
559
198
                                             + "{{/ pair: "
560
198
                                             + body_.substr(matched.start, matched.end - matched.start) + ", "
561
198
                                             + body_.substr(idx, endIdx - idx)
562
198
                                             );
563
198
                                }
564
160k
                                matched.pos = static_cast<int>(actions_.size());
565
160k
                                matched.has_end_match = true;
566
160k
                            }
567
0
                            actions_.emplace_back(tag_char, ActionType::CloseBlock, idx, endIdx, blockPositions.back());
568
160k
                            blockPositions.pop_back();
569
160k
                            break;
570
163k
                        case '^':
571
163k
                            idx++;
572
164k
                            while (body_[idx] == ' ')
573
722
                                idx++;
574
164k
                            while (body_[endIdx - 1] == ' ')
575
818
                                endIdx--;
576
163k
                            blockPositions.emplace_back(static_cast<int>(actions_.size()));
577
163k
                            actions_.emplace_back(tag_char, ActionType::ElseBlock, idx, endIdx);
578
163k
                            break;
579
1.52M
                        case '!':
580
                            // do nothing action
581
1.52M
                            actions_.emplace_back(tag_char, ActionType::Ignore, idx + 1, endIdx);
582
1.52M
                            break;
583
4.35M
                        case '>': // partial
584
4.35M
                            idx++;
585
4.52M
                            while (body_[idx] == ' ')
586
174k
                                idx++;
587
4.44M
                            while (body_[endIdx - 1] == ' ')
588
95.6k
                                endIdx--;
589
4.35M
                            actions_.emplace_back(tag_char, ActionType::Partial, idx, endIdx);
590
4.35M
                            break;
591
6.88k
                        case '{':
592
6.88k
                            if (tag_open != "{{" || tag_close != "}}")
593
77
                                throw invalid_template_exception("cannot use triple mustache when delimiter changed");
594
595
6.80k
                            idx++;
596
6.80k
                            if (body_[endIdx + 2] != '}')
597
18
                            {
598
18
                                throw invalid_template_exception("{{{: }}} not matched");
599
18
                            }
600
8.41k
                            while (body_[idx] == ' ')
601
1.61k
                                idx++;
602
11.3k
                            while (body_[endIdx - 1] == ' ')
603
4.57k
                                endIdx--;
604
6.79k
                            actions_.emplace_back(tag_char, ActionType::UnescapeTag, idx, endIdx);
605
6.79k
                            current++;
606
6.79k
                            break;
607
27.2k
                        case '&':
608
27.2k
                            idx++;
609
33.4k
                            while (body_[idx] == ' ')
610
6.20k
                                idx++;
611
33.2k
                            while (body_[endIdx - 1] == ' ')
612
5.96k
                                endIdx--;
613
27.2k
                            actions_.emplace_back(tag_char, ActionType::UnescapeTag, idx, endIdx);
614
27.2k
                            break;
615
22.6k
                        case '=':
616
                            // tag itself is no-op
617
22.6k
                            idx++;
618
22.6k
                            actions_.emplace_back(tag_char, ActionType::Ignore, idx, endIdx);
619
22.6k
                            endIdx--;
620
22.6k
                            if (body_[endIdx] != '=')
621
31
                                throw invalid_template_exception("{{=: not matching = tag: " + body_.substr(idx, endIdx - idx));
622
22.5k
                            endIdx--;
623
1.07M
                            while (body_[idx] == ' ')
624
1.05M
                                idx++;
625
1.07M
                            while (body_[endIdx] == ' ')
626
1.05M
                                endIdx--;
627
22.5k
                            endIdx++;
628
22.5k
                            {
629
22.5k
                                bool succeeded = false;
630
10.8M
                                for (size_t i = idx; i < endIdx; i++)
631
10.8M
                                {
632
10.8M
                                    if (body_[i] == ' ')
633
22.5k
                                    {
634
22.5k
                                        tag_open = body_.substr(idx, i - idx);
635
46.0k
                                        while (body_[i] == ' ')
636
23.5k
                                            i++;
637
22.5k
                                        tag_close = body_.substr(i, endIdx - i);
638
22.5k
                                        if (tag_open.empty())
639
0
                                            throw invalid_template_exception("{{=: empty open tag");
640
22.5k
                                        if (tag_close.empty())
641
0
                                            throw invalid_template_exception("{{=: empty close tag");
642
643
22.5k
                                        if (tag_close.find(" ") != tag_close.npos)
644
75
                                            throw invalid_template_exception("{{=: invalid open/close tag: " + tag_open + " " + tag_close);
645
22.4k
                                        succeeded = true;
646
22.4k
                                        break;
647
22.5k
                                    }
648
10.8M
                                }
649
22.5k
                                if (!succeeded)
650
75
                                    throw invalid_template_exception("{{=: cannot find space between new open/close tags");
651
22.5k
                            }
652
22.4k
                            break;
653
251k
                        default:
654
                            // normal tag case;
655
437k
                            while (body_[idx] == ' ')
656
185k
                                idx++;
657
444k
                            while (body_[endIdx - 1] == ' ')
658
193k
                                endIdx--;
659
251k
                            actions_.emplace_back(tag_char, ActionType::Tag, idx, endIdx);
660
251k
                            break;
661
7.19M
                    }
662
7.19M
                }
663
664
                // ensure no unmatched tags
665
10.7M
                for (int i = 0; i < static_cast<int>(actions_.size()); i++)
666
8.07M
                {
667
8.07M
                    if (actions_[i].missing_end_pair())
668
125
                    {
669
125
                        throw invalid_template_exception(
670
125
                                std::string("open tag has no matching end tag {{")
671
125
                                + actions_[i].tag_char
672
125
                                + " {{/ pair: "
673
125
                                + body_.substr(actions_[i].start, actions_[i].end - actions_[i].start)
674
125
                                );
675
125
                    }
676
8.07M
                }
677
678
                // removing standalones
679
7.95M
                for (int i = static_cast<int>(actions_.size()) - 2; i >= 0; i--)
680
5.29M
                {
681
5.29M
                    if (actions_[i].t == ActionType::Tag || actions_[i].t == ActionType::UnescapeTag)
682
238k
                        continue;
683
5.05M
                    auto& fragment_before = fragments_[i];
684
5.05M
                    auto& fragment_after = fragments_[i + 1];
685
5.05M
                    bool is_last_action = i == static_cast<int>(actions_.size()) - 2;
686
5.05M
                    bool all_space_before = true;
687
5.05M
                    int j, k;
688
6.97M
                    for (j = fragment_before.second - 1; j >= fragment_before.first; j--)
689
2.24M
                    {
690
2.24M
                        if (body_[j] != ' ')
691
320k
                        {
692
320k
                            all_space_before = false;
693
320k
                            break;
694
320k
                        }
695
2.24M
                    }
696
5.05M
                    if (all_space_before && i > 0)
697
4.73M
                        continue;
698
323k
                    if (!all_space_before && body_[j] != '\n')
699
191k
                        continue;
700
132k
                    bool all_space_after = true;
701
143k
                    for (k = fragment_after.first; k < static_cast<int>(body_.size()) && k < fragment_after.second; k++)
702
139k
                    {
703
139k
                        if (body_[k] != ' ')
704
128k
                        {
705
128k
                            all_space_after = false;
706
128k
                            break;
707
128k
                        }
708
139k
                    }
709
132k
                    if (all_space_after && !is_last_action)
710
3.16k
                        continue;
711
129k
                    if (!all_space_after &&
712
128k
                        !(
713
128k
                          body_[k] == '\n' ||
714
118k
                          (body_[k] == '\r' &&
715
99.7k
                           k + 1 < static_cast<int>(body_.size()) &&
716
99.7k
                           body_[k + 1] == '\n')))
717
34.0k
                        continue;
718
94.9k
                    if (actions_[i].t == ActionType::Partial)
719
93.1k
                    {
720
93.1k
                        actions_[i].pos = fragment_before.second - j - 1;
721
93.1k
                    }
722
94.9k
                    fragment_before.second = j + 1;
723
94.9k
                    if (!all_space_after)
724
94.1k
                    {
725
94.1k
                        if (body_[k] == '\n')
726
9.48k
                            k++;
727
84.7k
                        else
728
84.7k
                            k += 2;
729
94.1k
                        fragment_after.first = k;
730
94.1k
                    }
731
94.9k
                }
732
2.66M
            }
733
734
            std::vector<std::pair<int, int>> fragments_;
735
            std::vector<Action> actions_;
736
            std::string body_;
737
        };
738
739
        /// \brief The function that compiles a source into a mustache
740
        /// template.
741
        inline template_t compile(const std::string& body)
742
2.66M
        {
743
2.66M
            return template_t(body);
744
2.66M
        }
745
746
        namespace detail
747
        {
748
            inline std::string& get_template_base_directory_ref()
749
2.66M
            {
750
2.66M
                static std::string template_base_directory = "templates";
751
2.66M
                return template_base_directory;
752
2.66M
            }
753
754
            /// A base directory not related to any blueprint
755
            inline std::string& get_global_template_base_directory_ref()
756
0
            {
757
0
                static std::string template_base_directory = "templates";
758
0
                return template_base_directory;
759
0
            }
760
        } // namespace detail
761
762
        /// \brief The default way that \ref load, \ref load_unsafe,
763
        /// \ref load_text and \ref load_text_unsafe use to read the
764
        /// contents of a file.
765
        inline std::string default_loader(const std::string& filename)
766
2.66M
        {
767
2.66M
            std::string path = detail::get_template_base_directory_ref();
768
2.66M
            std::ifstream inf(utility::join_path(path, filename));
769
2.66M
            if (!inf)
770
2.66M
            {
771
2.66M
                CROW_LOG_WARNING << "Template \"" << filename << "\" not found.";
772
2.66M
                return {};
773
2.66M
            }
774
0
            return {std::istreambuf_iterator<char>(inf), std::istreambuf_iterator<char>()};
775
2.66M
        }
776
777
        namespace detail
778
        {
779
            inline std::function<std::string(std::string)>& get_loader_ref()
780
2.66M
            {
781
2.66M
                static std::function<std::string(std::string)> loader = default_loader;
782
2.66M
                return loader;
783
2.66M
            }
784
        } // namespace detail
785
786
        /// \brief Defines the templates directory path at **route
787
        /// level**. By default is `templates/`.
788
        inline void set_base(const std::string& path)
789
0
        {
790
0
            auto& base = detail::get_template_base_directory_ref();
791
0
            base = path;
792
0
            if (base.back() != '\\' &&
793
0
                base.back() != '/')
794
0
            {
795
0
                base += '/';
796
0
            }
797
0
        }
798
799
        /// \brief Defines the templates directory path at **global
800
        /// level**. By default is `templates/`.
801
        inline void set_global_base(const std::string& path)
802
0
        {
803
0
            auto& base = detail::get_global_template_base_directory_ref();
804
0
            base = path;
805
0
            if (base.back() != '\\' &&
806
0
                base.back() != '/')
807
0
            {
808
0
                base += '/';
809
0
            }
810
0
        }
811
812
        /// \brief Change the way that \ref load, \ref load_unsafe,
813
        /// \ref load_text and \ref load_text_unsafe reads a file.
814
        ///
815
        /// By default, the previously mentioned functions load files
816
        /// using \ref default_loader, that only reads a file and
817
        /// returns a std::string.
818
        inline void set_loader(std::function<std::string(std::string)> loader)
819
0
        {
820
0
            detail::get_loader_ref() = std::move(loader);
821
0
        }
822
823
        /// \brief Open, read and sanitize a file but returns a
824
        /// std::string without a previous rendering process.
825
        ///
826
        /// Except for the **sanitize process** this function does the
827
        /// almost the same thing that \ref load_text_unsafe.
828
        inline std::string load_text(const std::string& filename)
829
0
        {
830
0
            std::string filename_sanitized(filename);
831
0
            utility::sanitize_filename(filename_sanitized);
832
0
            return detail::get_loader_ref()(filename_sanitized);
833
0
        }
834
835
        /// \brief Open and read a file but returns a std::string
836
        /// without a previous rendering process.
837
        ///
838
        /// This function is more like a helper to reduce code like
839
        /// this...
840
        ///
841
        /// ```cpp
842
        /// std::ifstream file("home.html");
843
        /// return std::string({std::istreambuf_iterator<char>(file), std::istreambuf_iterator<char>()});
844
        /// ```
845
        ///
846
        /// ... Into this...
847
        ///
848
        /// ```cpp
849
        /// return load("home.html");
850
        /// ```
851
        ///
852
        /// \warning Usually \ref load_text is more recommended to use
853
        /// instead because it may prevent some [XSS Attacks](https://en.wikipedia.org/wiki/Cross-site_scripting).
854
        /// **Never blindly trust your users!**
855
        inline std::string load_text_unsafe(const std::string& filename)
856
0
        {
857
0
            return detail::get_loader_ref()(filename);
858
0
        }
859
860
        /// \brief Open, read and renders a file using a mustache
861
        /// compiler. It also sanitize the input before compilation.
862
        inline template_t load(const std::string& filename)
863
2.66M
        {
864
2.66M
            std::string filename_sanitized(filename);
865
2.66M
            utility::sanitize_filename(filename_sanitized);
866
2.66M
            return compile(detail::get_loader_ref()(filename_sanitized));
867
2.66M
        }
868
869
        /// \brief Open, read and renders a file using a mustache
870
        /// compiler. But it **do not** sanitize the input before
871
        /// compilation.
872
        ///
873
        /// \warning Usually \ref load is more recommended to use
874
        /// instead because it may prevent some [XSS Attacks](https://en.wikipedia.org/wiki/Cross-site_scripting).
875
        /// **Never blindly trust your users!**
876
        inline template_t load_unsafe(const std::string& filename)
877
0
        {
878
0
            return compile(detail::get_loader_ref()(filename));
879
0
        }
880
    } // namespace mustache
881
} // namespace crow