Coverage Report

Created: 2025-07-18 06:58

/src/glaze/include/glaze/stencil/stencil.hpp
Line
Count
Source (jump to first uncovered line)
1
// Glaze Library
2
// For the license information refer to glaze.hpp
3
4
#pragma once
5
6
#include "glaze/core/read.hpp"
7
#include "glaze/core/reflect.hpp"
8
#include "glaze/core/write.hpp"
9
10
namespace glz
11
{
12
   // HTML escape function
13
   inline std::string html_escape(const std::string& input)
14
0
   {
15
0
      std::string result;
16
0
      result.reserve(static_cast<size_t>(input.size() * 1.1)); // Reserve some extra space
17
0
18
0
      for (char c : input) {
19
0
         switch (c) {
20
0
         case '<':
21
0
            result += "&lt;";
22
0
            break;
23
0
         case '>':
24
0
            result += "&gt;";
25
0
            break;
26
0
         case '&':
27
0
            result += "&amp;";
28
0
            break;
29
0
         case '"':
30
0
            result += "&quot;";
31
0
            break;
32
0
         case '\'':
33
0
            result += "&#x27;";
34
0
            break;
35
0
         default:
36
0
            result += c;
37
0
            break;
38
0
         }
39
0
      }
40
0
      return result;
41
0
   }
42
43
   template <auto Opts = opts{.format = STENCIL}, class Template, class T, resizable Buffer>
44
   [[nodiscard]] error_ctx stencil(Template&& layout, T&& value, Buffer& buffer)
45
   {
46
      context ctx{};
47
48
      if (layout.empty()) [[unlikely]] {
49
         ctx.error = error_code::no_read_input;
50
         return {ctx.error, ctx.custom_error_message, 0};
51
      }
52
53
      auto p = read_iterators<Opts, false>(layout);
54
      auto it = p.first;
55
      auto end = p.second;
56
      auto outer_start = it;
57
58
      if (not bool(ctx.error)) [[likely]] {
59
         auto skip_whitespace = [&] {
60
            while (it < end && whitespace_table[uint8_t(*it)]) {
61
               ++it;
62
            }
63
         };
64
65
         while (it < end) {
66
            if (*it == '{') {
67
               ++it;
68
               if (it != end && *it == '{') {
69
                  ++it;
70
71
                  // Check for triple braces (unescaped HTML)
72
                  bool is_triple_brace = false;
73
                  if (it != end && *it == '{') {
74
                     ++it;
75
                     is_triple_brace = true;
76
                  }
77
78
                  bool is_section = false;
79
                  bool is_inverted_section = false;
80
                  bool is_comment = false;
81
82
                  if (it != end && !is_triple_brace) {
83
                     if (*it == '!') {
84
                        ++it;
85
                        is_comment = true;
86
                     }
87
                     else if (*it == '#') {
88
                        ++it;
89
                        is_section = true;
90
                     }
91
                     else if (*it == '^') {
92
                        ++it;
93
                        is_inverted_section = true;
94
                     }
95
                  }
96
97
                  skip_whitespace();
98
99
                  auto start = it;
100
                  while (it != end && *it != '}' && *it != ' ' && *it != '\t') {
101
                     ++it;
102
                  }
103
104
                  if (it == end) {
105
                     ctx.error = error_code::unexpected_end;
106
                     return {ctx.error, ctx.custom_error_message, size_t(it - outer_start)};
107
                  }
108
109
                  const sv key{start, size_t(it - start)};
110
111
                  skip_whitespace();
112
113
                  if (is_comment) {
114
                     while (it < end && !(it + 1 < end && *it == '}' && *(it + 1) == '}')) {
115
                        ++it;
116
                     }
117
                     if (it + 1 < end) {
118
                        it += 2; // Skip '}}'
119
                     }
120
                     continue;
121
                  }
122
123
                  if (is_section || is_inverted_section) {
124
                     // Find the closing tag '{{/key}}'
125
                     std::string closing_tag = "{{/" + std::string(key) + "}}";
126
                     auto closing_pos = std::search(it, end, closing_tag.begin(), closing_tag.end());
127
128
                     if (closing_pos == end) {
129
                        ctx.error = error_code::unexpected_end;
130
                        return {ctx.error, "Closing tag not found for section", size_t(it - outer_start)};
131
                     }
132
133
                     if (it + 1 < end) {
134
                        it += 2; // Skip '}}'
135
                     }
136
137
                     // Extract inner template between current position and closing tag
138
                     std::string_view inner_template(it, closing_pos);
139
                     it = closing_pos + closing_tag.size();
140
141
                     // Retrieve the value associated with 'key'
142
                     bool condition = false;
143
                     bool is_container = false;
144
145
                     {
146
                        static constexpr auto N = reflect<T>::size;
147
                        static constexpr auto HashInfo = hash_info<T>;
148
149
                        const auto index =
150
                           decode_hash_with_size<STENCIL, T, HashInfo, HashInfo.type>::op(start, end, key.size());
151
152
                        if (index >= N) {
153
                           ctx.error = error_code::unknown_key;
154
                           return {ctx.error, ctx.custom_error_message, size_t(it - outer_start)};
155
                        }
156
                        else {
157
                           visit<N>(
158
                              [&]<size_t I>() {
159
                                 static constexpr auto TargetKey = get<I>(reflect<T>::keys);
160
                                 if (TargetKey == key) [[likely]] {
161
                                    using field_type = refl_t<T, I>;
162
163
                                    if constexpr (bool_t<field_type>) {
164
                                       // Boolean field
165
                                       if constexpr (reflectable<T>) {
166
                                          condition = bool(get_member(value, get<I>(to_tie(value))));
167
                                       }
168
                                       else if constexpr (glaze_object_t<T>) {
169
                                          condition = bool(get_member(value, get<I>(reflect<T>::values)));
170
                                       }
171
                                    }
172
                                    else if constexpr (writable_array_t<field_type>) {
173
                                       // Container field - check if empty for condition
174
                                       is_container = true;
175
176
                                       if constexpr (reflectable<T>) {
177
                                          auto& container = get_member(value, get<I>(to_tie(value)));
178
                                          condition = !empty_range(container);
179
180
                                          // Process container iteration for regular sections
181
                                          if (is_section && condition) {
182
                                             using element_type = std::decay_t<decltype(*std::begin(container))>;
183
                                             if constexpr (reflectable<element_type> || glaze_object_t<element_type>) {
184
                                                for (const auto& item : container) {
185
                                                   std::string inner_buffer;
186
                                                   auto inner_ec = stencil<Opts>(inner_template, item, inner_buffer);
187
                                                   if (inner_ec) {
188
                                                      ctx.error = inner_ec.ec;
189
                                                      return;
190
                                                   }
191
                                                   buffer.append(inner_buffer);
192
                                                }
193
                                             }
194
                                             else {
195
                                                // For primitive containers, we can't do recursive stencil
196
                                                // This would require special handling for {{.}} syntax
197
                                                ctx.error = error_code::syntax_error;
198
                                                return;
199
                                             }
200
                                          }
201
                                       }
202
                                       else if constexpr (glaze_object_t<T>) {
203
                                          auto& container = get_member(value, get<I>(reflect<T>::values));
204
                                          condition = !empty_range(container);
205
206
                                          // Process container iteration for regular sections
207
                                          if (is_section && condition) {
208
                                             using element_type = std::decay_t<decltype(*std::begin(container))>;
209
                                             if constexpr (reflectable<element_type> || glaze_object_t<element_type>) {
210
                                                for (const auto& item : container) {
211
                                                   std::string inner_buffer;
212
                                                   auto inner_ec = stencil<Opts>(inner_template, item, inner_buffer);
213
                                                   if (inner_ec) {
214
                                                      ctx.error = inner_ec.ec;
215
                                                      return;
216
                                                   }
217
                                                   buffer.append(inner_buffer);
218
                                                }
219
                                             }
220
                                             else {
221
                                                // For primitive containers, we can't do recursive stencil
222
                                                // This would require special handling for {{.}} syntax
223
                                                ctx.error = error_code::syntax_error;
224
                                                return;
225
                                             }
226
                                          }
227
                                       }
228
                                    }
229
                                    else {
230
                                       // For other types, default to false for sections
231
                                       condition = false;
232
                                    }
233
                                 }
234
                                 else {
235
                                    ctx.error = error_code::unknown_key;
236
                                 }
237
                              },
238
                              index);
239
                        }
240
                     }
241
242
                     if (bool(ctx.error)) [[unlikely]] {
243
                        return {ctx.error, ctx.custom_error_message, size_t(it - outer_start)};
244
                     }
245
246
                     // Handle inverted sections and boolean sections
247
                     if (is_inverted_section) {
248
                        // For inverted sections, show content if condition is false
249
                        if (!condition) {
250
                           std::string inner_buffer;
251
                           auto inner_ec = stencil<Opts>(inner_template, value, inner_buffer);
252
                           if (inner_ec) {
253
                              return inner_ec;
254
                           }
255
                           buffer.append(inner_buffer);
256
                        }
257
                     }
258
                     else if (is_section && !is_container) {
259
                        // For boolean sections (non-containers), show content if condition is true
260
                        if (condition) {
261
                           std::string inner_buffer;
262
                           auto inner_ec = stencil<Opts>(inner_template, value, inner_buffer);
263
                           if (inner_ec) {
264
                              return inner_ec;
265
                           }
266
                           buffer.append(inner_buffer);
267
                        }
268
                     }
269
                     // Container iteration for regular sections was already handled above
270
271
                     skip_whitespace();
272
                     continue;
273
                  }
274
275
                  // Handle regular placeholder (double braces) or unescaped (triple braces)
276
                  static constexpr auto N = reflect<T>::size;
277
                  static constexpr auto HashInfo = hash_info<T>;
278
279
                  const auto index =
280
                     decode_hash_with_size<STENCIL, T, HashInfo, HashInfo.type>::op(start, end, key.size());
281
282
                  if (index >= N) [[unlikely]] {
283
                     ctx.error = error_code::unknown_key;
284
                     return {ctx.error, ctx.custom_error_message, size_t(it - outer_start)};
285
                  }
286
                  else [[likely]] {
287
                     // For triple braces, we need to expect three closing braces
288
                     size_t expected_closing_braces = is_triple_brace ? 3 : 2;
289
290
                     // Check for correct closing braces
291
                     size_t closing_brace_count = 0;
292
                     auto temp_it = it;
293
                     while (temp_it < end && *temp_it == '}' && closing_brace_count < 3) {
294
                        ++temp_it;
295
                        ++closing_brace_count;
296
                     }
297
298
                     if (closing_brace_count < expected_closing_braces) {
299
                        ctx.error = error_code::syntax_error;
300
                        return {ctx.error, ctx.custom_error_message, size_t(it - outer_start)};
301
                     }
302
303
                     // Serialize the value
304
                     std::string temp_buffer;
305
                     static constexpr auto RawOpts =
306
                        set_json<opt_true<Opts, &opts::raw>>(); // write out string like values without quotes
307
308
                     visit<N>(
309
                        [&]<size_t I>() {
310
                           static constexpr auto TargetKey = get<I>(reflect<T>::keys);
311
                           if ((TargetKey.size() == key.size()) && comparitor<TargetKey>(start)) [[likely]] {
312
                              size_t ix = 0;
313
                              temp_buffer.resize(2 * write_padding_bytes);
314
315
                              if constexpr (reflectable<T>) {
316
                                 serialize<JSON>::template op<RawOpts>(get_member(value, get<I>(to_tie(value))), ctx,
317
                                                                       temp_buffer, ix);
318
                              }
319
                              else if constexpr (glaze_object_t<T>) {
320
                                 serialize<JSON>::template op<RawOpts>(get_member(value, get<I>(reflect<T>::values)),
321
                                                                       ctx, temp_buffer, ix);
322
                              }
323
324
                              temp_buffer.resize(ix);
325
                           }
326
                           else {
327
                              ctx.error = error_code::unknown_key;
328
                           }
329
                        },
330
                        index);
331
332
                     if (bool(ctx.error)) [[unlikely]] {
333
                        return {ctx.error, ctx.custom_error_message, size_t(it - outer_start)};
334
                     }
335
336
                     // Apply HTML escaping for double braces, leave unescaped for triple braces
337
                     if (is_triple_brace) {
338
                        buffer.append(temp_buffer);
339
                     }
340
                     else {
341
                        if constexpr (Opts.format == MUSTACHE) {
342
                           buffer.append(html_escape(temp_buffer));
343
                        }
344
                        else {
345
                           buffer.append(temp_buffer);
346
                        }
347
                     }
348
349
                     // Skip the closing braces
350
                     it += expected_closing_braces;
351
                     continue;
352
                  }
353
               }
354
               else {
355
                  buffer.append("{");
356
                  // 'it' is already incremented past the first '{'
357
               }
358
            }
359
            else {
360
               buffer.push_back(*it);
361
               ++it;
362
            }
363
         }
364
      }
365
366
      if (bool(ctx.error)) [[unlikely]] {
367
         return {ctx.error, ctx.custom_error_message, size_t(it - outer_start)};
368
      }
369
370
      return {};
371
   }
372
373
   template <auto Opts = opts{.format = STENCIL}, class Template, class T>
374
   [[nodiscard]] expected<std::string, error_ctx> stencil(Template&& layout, T&& value)
375
   {
376
      std::string buffer{};
377
      auto ec = stencil<Opts>(std::forward<Template>(layout), std::forward<T>(value), buffer);
378
      if (ec) {
379
         return unexpected<error_ctx>(ec);
380
      }
381
      return {buffer};
382
   }
383
384
   template <auto Opts = opts{.format = MUSTACHE}, class Template, class T, resizable Buffer>
385
      requires(Opts.format == MUSTACHE)
386
   [[nodiscard]] error_ctx mustache(Template&& layout, T&& value, Buffer& buffer)
387
   {
388
      return stencil<Opts>(std::forward<Template>(layout), std::forward<T>(value), buffer);
389
   }
390
391
   template <auto Opts = opts{.format = MUSTACHE}, class Template, class T>
392
      requires(Opts.format == MUSTACHE)
393
   [[nodiscard]] expected<std::string, error_ctx> mustache(Template&& layout, T&& value)
394
   {
395
      std::string buffer{};
396
      auto ec = stencil<Opts>(std::forward<Template>(layout), std::forward<T>(value), buffer);
397
      if (ec) {
398
         return unexpected<error_ctx>(ec);
399
      }
400
      return {buffer};
401
   }
402
}