#include "common.hh" #include "htmlutil.hh" #include "tag.hh" #include #include namespace { class TextRenderable : public virtual Renderable { public: explicit TextRenderable(std::string content) : content_(std::move(content)) {} void render(std::string* out) const override { html::escape(content_, out); } private: std::string content_; }; class ScriptRenderable : public virtual Renderable { public: explicit ScriptRenderable(std::string content) : content_(std::move(content)) {} void render(std::string* out) const override { out->append(content_); } private: std::string content_; }; class TagImpl : public Tag { public: TagImpl(std::string name, std::string content) : name_(std::move(name)) { add(std::move(content)); assert(html::escape(name_) == name_); } std::string_view name() const override { return name_; } bool empty() const override { return child_.empty(); } bool has_attr(std::string const& name) const override { return attr_.count(name); } Tag* clear_content() override { child_.clear(); return this; } Tag* attr(std::string name, std::string value) override { assert(html::escape(name) == name); attr_[name] = std::move(value); return this; } Tag* add(std::string content) override { if (!content.empty()) { if (name_ == "script") { child_.push_back( std::make_unique(std::move(content))); } else { child_.push_back(std::make_unique(std::move(content))); } } return this; } Tag* add(std::unique_ptr tag) override { auto* ret = tag.get(); child_.push_back(std::move(tag)); return ret; } void render(std::string* out) const override { size_t need = 1 + name_.size(); for (auto const& pair : attr_) need += 1 + pair.first.size() + 2 + pair.second.size() + 1; if (empty()) ++need; out->reserve(need); out->push_back('<'); out->append(name_); for (auto const& pair : attr_) { out->push_back(' '); out->append(pair.first); out->append("=\""); html::escape(pair.second, out, html::EscapeTarget::ATTRIBUTE); out->push_back('"'); } if (empty()) { if (name_ == "script") { // Some browsers don't allow , must be written as // out->append(">"); } else { // There are tags, like where the / is optional but why botter. out->append("/>"); } return; } out->push_back('>'); for (auto& child : child_) { child->render(out); } out->reserve(name_.size() + 3); out->append(""); out->append(name_); out->push_back('>'); } private: std::string name_; std::map attr_; std::vector> child_; }; } // namespace Tag* Tag::add_tag(std::string name, std::string content) { return add(create(std::move(name), std::move(content))); } std::unique_ptr Tag::create(std::string name, std::string content) { return std::make_unique(std::move(name), std::move(content)); }