Sourcemeta Core 0.0.0
Loading...
Searching...
No Matches
html_writer.h
1#ifndef SOURCEMETA_CORE_HTML_WRITER_H_
2#define SOURCEMETA_CORE_HTML_WRITER_H_
3
4#ifndef SOURCEMETA_CORE_HTML_EXPORT
5#include <sourcemeta/core/html_export.h>
6#endif
7
8#include <sourcemeta/core/html_buffer.h>
9#include <sourcemeta/core/html_escape.h>
10#include <sourcemeta/core/preprocessor.h>
11
12#include <array> // std::array
13#include <cassert> // assert
14#include <cstddef> // std::size_t
15#include <cstdint> // std::uint8_t
16#include <string> // std::string
17#include <string_view> // std::string_view
18#include <vector> // std::vector
19
20namespace sourcemeta::core {
21
37class SOURCEMETA_CORE_HTML_EXPORT HTMLWriter {
38public:
40 SOURCEMETA_FORCEINLINE auto reserve(std::size_t bytes) -> void {
41 this->buffer_.reserve(bytes);
42 }
43
46 SOURCEMETA_FORCEINLINE auto close() -> HTMLWriter & {
47 this->tag_open_ = false;
48 assert(!this->tag_stack_.empty());
49 if (this->tag_stack_.empty()) [[unlikely]] {
50 return *this;
51 }
52 const auto &closing{this->tag_stack_.back()};
53 // Copying every byte of the fixed storage is cheaper than copying a
54 // variable number of them, and the bytes past the tag are then dropped
55 this->buffer_.reserve_additional(closing.bytes.size());
56 this->buffer_.append_unchecked(
57 std::string_view{closing.bytes.data(), closing.bytes.size()});
58 this->buffer_.remove_suffix(closing.bytes.size() - closing.size);
59 this->tag_stack_.pop_back();
60 return *this;
61 }
62
65 SOURCEMETA_FORCEINLINE auto attribute(std::string_view name,
66 std::string_view value)
67 -> HTMLWriter & {
68 assert(this->tag_open_);
69 this->reopen_tag();
70 // The space before the name, the equals sign and the quotation mark after
71 // it, along with the end of the tag, which takes four bytes for a void
72 // element
73 this->buffer_.reserve_additional(name.size() + 7);
74 this->buffer_.append_unchecked(" ");
75 this->buffer_.append_unchecked(name);
76 this->buffer_.append_unchecked("=\"");
77 html_escape_append(this->buffer_, value);
78 this->buffer_.reserve_additional(4);
79 if (this->tag_open_is_void_) {
80 this->buffer_.append_unchecked("\" />");
81 } else {
82 this->buffer_.append_unchecked("\">");
83 }
84 return *this;
85 }
86
99 SOURCEMETA_FORCEINLINE auto attribute(std::string_view name) -> HTMLWriter & {
100 assert(this->tag_open_);
101 this->reopen_tag();
102 this->buffer_.reserve_additional(name.size() + 1);
103 this->buffer_.append_unchecked(" ");
104 this->buffer_.append_unchecked(name);
105 this->end_open_tag();
106 return *this;
107 }
108
124 SOURCEMETA_FORCEINLINE auto ensure_line_feed() -> HTMLWriter & {
125 this->tag_open_ = false;
126 if (this->buffer_.size() > 0 && this->buffer_.back() != '\n') {
127 this->buffer_.append('\n');
128 }
129
130 return *this;
131 }
132
146 [[nodiscard]] SOURCEMETA_FORCEINLINE auto take() -> std::string {
147 this->tag_open_ = false;
148 this->tag_stack_.clear();
149 return this->buffer_.take();
150 }
151
163 SOURCEMETA_FORCEINLINE auto clear() -> void {
164 this->tag_open_ = false;
165 this->tag_stack_.clear();
166 this->buffer_.clear();
167 }
168
175 SOURCEMETA_FORCEINLINE auto text(std::string_view content) -> HTMLWriter & {
176 this->tag_open_ = false;
177 html_escape_append(this->buffer_, content);
178 return *this;
179 }
180
183 SOURCEMETA_FORCEINLINE auto raw(std::string_view content) -> HTMLWriter & {
184 this->tag_open_ = false;
185 this->buffer_.append(content);
186 return *this;
187 }
188
190 [[nodiscard]] SOURCEMETA_FORCEINLINE auto str() -> const std::string & {
191 return this->buffer_.str();
192 }
193
195 auto write(std::ostream &stream) -> void;
196
197// Exporting symbols that depends on the standard C++ library is considered
198// safe.
199// https://learn.microsoft.com/en-us/cpp/error-messages/compiler-warnings/compiler-warning-level-2-c4275?view=msvc-170&redirectedfrom=MSDN
200#if defined(_MSC_VER)
201#pragma warning(push)
202#pragma warning(disable : 4251)
203#endif
204
205#ifndef DOXYGEN
206// Macro to generate container element methods.
207// Container elements write <tag> on open and </tag> on close().
208// Overloads:
209// .tag() open with no attributes
210// .tag(text) open, write escaped text, close (shorthand)
211// NOLINTBEGIN(bugprone-macro-parentheses)
212#define HTML_WRITER_CONTAINER(name) \
213 SOURCEMETA_FORCEINLINE auto name() -> HTMLWriter & { \
214 this->open_tag("<" #name ">", ClosingTag{"</" #name ">"}); \
215 return *this; \
216 } \
217 SOURCEMETA_FORCEINLINE auto name(std::string_view text_content) \
218 -> HTMLWriter & { \
219 this->open_tag("<" #name ">", ClosingTag{"</" #name ">"}); \
220 this->text(text_content); \
221 this->close(); \
222 return *this; \
223 }
224// NOLINTEND(bugprone-macro-parentheses)
225
226// Same as above but with a different C++ method name than the HTML tag
227// NOLINTBEGIN(bugprone-macro-parentheses)
228#define HTML_WRITER_CONTAINER_NAMED(name, tag) \
229 SOURCEMETA_FORCEINLINE auto name() -> HTMLWriter & { \
230 this->open_tag("<" #tag ">", ClosingTag{"</" #tag ">"}); \
231 return *this; \
232 } \
233 SOURCEMETA_FORCEINLINE auto name(std::string_view text_content) \
234 -> HTMLWriter & { \
235 this->open_tag("<" #tag ">", ClosingTag{"</" #tag ">"}); \
236 this->text(text_content); \
237 this->close(); \
238 return *this; \
239 }
240// NOLINTEND(bugprone-macro-parentheses)
241
242// Macro to generate void element methods.
243// Void elements are self-closing: <tag /> or <tag attr="val" />
244// NOLINTBEGIN(bugprone-macro-parentheses)
245#define HTML_WRITER_VOID(name) \
246 SOURCEMETA_FORCEINLINE auto name() -> HTMLWriter & { \
247 this->void_tag("<" #name " />"); \
248 return *this; \
249 }
250// NOLINTEND(bugprone-macro-parentheses)
251#endif
252
253 // =========================================================================
254 // Document Structure Elements
255 // =========================================================================
256
258 HTML_WRITER_CONTAINER(html)
260 HTML_WRITER_VOID(base)
262 HTML_WRITER_CONTAINER(head)
264 HTML_WRITER_VOID(link)
266 HTML_WRITER_VOID(meta)
268 HTML_WRITER_CONTAINER(style)
270 HTML_WRITER_CONTAINER(title)
272 HTML_WRITER_CONTAINER(body)
273
274 // =========================================================================
275 // Content Sectioning Elements
276 // =========================================================================
277
278
279 HTML_WRITER_CONTAINER(address)
281 HTML_WRITER_CONTAINER(article)
283 HTML_WRITER_CONTAINER(aside)
285 HTML_WRITER_CONTAINER(footer)
287 HTML_WRITER_CONTAINER(header)
289 HTML_WRITER_CONTAINER(h1)
291 HTML_WRITER_CONTAINER(h2)
293 HTML_WRITER_CONTAINER(h3)
295 HTML_WRITER_CONTAINER(h4)
297 HTML_WRITER_CONTAINER(h5)
299 HTML_WRITER_CONTAINER(h6)
301 HTML_WRITER_CONTAINER(hgroup)
303 HTML_WRITER_CONTAINER(main)
305 HTML_WRITER_CONTAINER(nav)
307 HTML_WRITER_CONTAINER(section)
309 HTML_WRITER_CONTAINER(search)
310
311 // =========================================================================
312 // Text Content Elements
313 // =========================================================================
314
315
316 HTML_WRITER_CONTAINER(blockquote)
318 HTML_WRITER_CONTAINER(dd)
320 HTML_WRITER_CONTAINER(div)
322 HTML_WRITER_CONTAINER(dl)
324 HTML_WRITER_CONTAINER(dt)
326 HTML_WRITER_CONTAINER(figcaption)
328 HTML_WRITER_CONTAINER(figure)
330 HTML_WRITER_VOID(hr)
332 HTML_WRITER_CONTAINER(li)
334 HTML_WRITER_CONTAINER(menu)
336 HTML_WRITER_CONTAINER(ol)
338 HTML_WRITER_CONTAINER(p)
340 HTML_WRITER_CONTAINER(pre)
342 HTML_WRITER_CONTAINER(ul)
343
344 // =========================================================================
345 // Inline Text Semantics Elements
346 // =========================================================================
347
348
349 HTML_WRITER_CONTAINER(a)
351 HTML_WRITER_CONTAINER(abbr)
353 HTML_WRITER_CONTAINER(b)
355 HTML_WRITER_CONTAINER(bdi)
357 HTML_WRITER_CONTAINER(bdo)
359 HTML_WRITER_VOID(br)
361 HTML_WRITER_CONTAINER(cite)
363 HTML_WRITER_CONTAINER(code)
365 HTML_WRITER_CONTAINER(data)
367 HTML_WRITER_CONTAINER(dfn)
369 HTML_WRITER_CONTAINER(em)
371 HTML_WRITER_CONTAINER(i)
373 HTML_WRITER_CONTAINER(kbd)
375 HTML_WRITER_CONTAINER(mark)
377 HTML_WRITER_CONTAINER(q)
379 HTML_WRITER_CONTAINER(rp)
381 HTML_WRITER_CONTAINER(rt)
383 HTML_WRITER_CONTAINER(ruby)
385 HTML_WRITER_CONTAINER(s)
387 HTML_WRITER_CONTAINER(samp)
389 HTML_WRITER_CONTAINER(small)
391 HTML_WRITER_CONTAINER(span)
393 HTML_WRITER_CONTAINER(strong)
395 HTML_WRITER_CONTAINER(sub)
397 HTML_WRITER_CONTAINER(sup)
399 HTML_WRITER_CONTAINER(time)
401 HTML_WRITER_CONTAINER(u)
403 HTML_WRITER_CONTAINER(var)
405 HTML_WRITER_VOID(wbr)
406
407 // =========================================================================
408 // Image and Multimedia Elements
409 // =========================================================================
410
411
412 HTML_WRITER_VOID(area)
414 HTML_WRITER_CONTAINER(audio)
416 HTML_WRITER_VOID(img)
418 HTML_WRITER_CONTAINER(map)
420 HTML_WRITER_VOID(track)
422 HTML_WRITER_CONTAINER(video)
423
424 // =========================================================================
425 // Embedded Content Elements
426 // =========================================================================
427
428
429 HTML_WRITER_VOID(embed)
431 HTML_WRITER_CONTAINER(iframe)
433 HTML_WRITER_CONTAINER(object)
435 HTML_WRITER_CONTAINER(picture)
437 HTML_WRITER_CONTAINER(portal)
439 HTML_WRITER_VOID(source)
440
441 // =========================================================================
442 // Scripting Elements
443 // =========================================================================
444
445
446 HTML_WRITER_CONTAINER(canvas)
448 HTML_WRITER_CONTAINER(noscript)
450 HTML_WRITER_CONTAINER(script)
451
452 // =========================================================================
453 // Demarcating Edits Elements
454 // =========================================================================
455
456
457 HTML_WRITER_CONTAINER(del)
459 HTML_WRITER_CONTAINER(ins)
460
461 // =========================================================================
462 // Table Content Elements
463 // =========================================================================
464
465
466 HTML_WRITER_CONTAINER(caption)
468 HTML_WRITER_VOID(col)
470 HTML_WRITER_CONTAINER(colgroup)
472 HTML_WRITER_CONTAINER(table)
474 HTML_WRITER_CONTAINER(tbody)
476 HTML_WRITER_CONTAINER(td)
478 HTML_WRITER_CONTAINER(tfoot)
480 HTML_WRITER_CONTAINER(th)
482 HTML_WRITER_CONTAINER(thead)
484 HTML_WRITER_CONTAINER(tr)
485
486 // =========================================================================
487 // Forms Elements
488 // =========================================================================
489
490
491 HTML_WRITER_CONTAINER(button)
493 HTML_WRITER_CONTAINER(datalist)
495 HTML_WRITER_CONTAINER(fieldset)
497 HTML_WRITER_CONTAINER(form)
499 HTML_WRITER_VOID(input)
501 HTML_WRITER_CONTAINER(label)
503 HTML_WRITER_CONTAINER(legend)
505 HTML_WRITER_CONTAINER(meter)
507 HTML_WRITER_CONTAINER(optgroup)
509 HTML_WRITER_CONTAINER(option)
511 HTML_WRITER_CONTAINER(output)
513 HTML_WRITER_CONTAINER(progress)
515 HTML_WRITER_CONTAINER(select)
517 HTML_WRITER_CONTAINER(textarea)
518
519 // =========================================================================
520 // Interactive Elements
521 // =========================================================================
522
523
524 HTML_WRITER_CONTAINER(details)
526 HTML_WRITER_CONTAINER(dialog)
528 HTML_WRITER_CONTAINER(summary)
529
530 // =========================================================================
531 // Web Components Elements
532 // =========================================================================
533
534
535 HTML_WRITER_CONTAINER(slot)
537 // The trailing underscore keeps this element name from colliding with the
538 // template keyword
539 // NOLINTNEXTLINE(readability-identifier-naming)
540 HTML_WRITER_CONTAINER_NAMED(template_, template)
541
542#ifndef DOXYGEN
543#undef HTML_WRITER_CONTAINER
544#undef HTML_WRITER_CONTAINER_NAMED
545#undef HTML_WRITER_VOID
546#endif
547
548private:
549 // A closing tag kept in a fixed number of bytes, as copying a constant
550 // number of bytes is cheaper than copying a variable one
551 struct ClosingTag {
552 constexpr explicit ClosingTag(const std::string_view tag) noexcept
553 : size{static_cast<std::uint8_t>(tag.size())} {
554 for (std::size_t index = 0; index < tag.size(); index += 1) {
555 this->bytes[index] = tag[index];
556 }
557 }
558
559 std::array<char, 16> bytes{};
560 std::uint8_t size;
561 };
562
563 SOURCEMETA_FORCEINLINE auto open_tag(std::string_view opening,
564 const ClosingTag &closing) -> void {
565 this->buffer_.append(opening);
566 this->tag_stack_.push_back(closing);
567 this->tag_open_ = true;
568 this->tag_open_is_void_ = false;
569 }
570
571 SOURCEMETA_FORCEINLINE auto void_tag(std::string_view opening) -> void {
572 this->buffer_.append(opening);
573 this->tag_open_ = true;
574 this->tag_open_is_void_ = true;
575 }
576
577 // Remove the characters that end the tag opened last, so that attributes
578 // can go before them
579 SOURCEMETA_FORCEINLINE auto reopen_tag() -> void {
580 this->buffer_.remove_suffix(this->tag_open_is_void_ ? 3 : 1);
581 }
582
583 SOURCEMETA_FORCEINLINE auto end_open_tag() -> void {
584 if (this->tag_open_is_void_) {
585 this->buffer_.append(" />");
586 } else {
587 this->buffer_.append('>');
588 }
589 }
590
591 HTMLBuffer buffer_;
592 std::vector<ClosingTag> tag_stack_;
593 bool tag_open_{false};
594 bool tag_open_is_void_{false};
595#if defined(_MSC_VER)
596#pragma warning(pop)
597#endif
598};
599
600} // namespace sourcemeta::core
601
602#endif
SOURCEMETA_FORCEINLINE auto str() -> const std::string &
Get the rendered HTML string.
Definition html_writer.h:190
SOURCEMETA_FORCEINLINE auto close() -> HTMLWriter &
Definition html_writer.h:46
SOURCEMETA_FORCEINLINE auto attribute(std::string_view name) -> HTMLWriter &
Definition html_writer.h:99
auto write(std::ostream &stream) -> void
Write the rendered HTML to an output stream.
SOURCEMETA_FORCEINLINE auto reserve(std::size_t bytes) -> void
Pre-allocate the output buffer.
Definition html_writer.h:40
SOURCEMETA_FORCEINLINE auto clear() -> void
Definition html_writer.h:163
SOURCEMETA_FORCEINLINE auto attribute(std::string_view name, std::string_view value) -> HTMLWriter &
Definition html_writer.h:65
SOURCEMETA_FORCEINLINE auto text(std::string_view content) -> HTMLWriter &
Definition html_writer.h:175
SOURCEMETA_FORCEINLINE auto take() -> std::string
Definition html_writer.h:146
SOURCEMETA_FORCEINLINE auto ensure_line_feed() -> HTMLWriter &
Definition html_writer.h:124
SOURCEMETA_FORCEINLINE auto raw(std::string_view content) -> HTMLWriter &
Definition html_writer.h:183
Definition html_writer.h:37
auto html_escape_append(Output &output, const std::string_view input) -> void
Definition html_escape.h:57
SOURCEMETA_CORE_UNICODE_EXPORT auto script(const char32_t codepoint) noexcept -> UnicodeScript