lmi-commits
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

[lmi-commits] [lmi] master 5e2fc70 007/156: Make HTML generation utiliti


From: Greg Chicares
Subject: [lmi-commits] [lmi] master 5e2fc70 007/156: Make HTML generation utilities more type-safe
Date: Tue, 30 Jan 2018 17:21:50 -0500 (EST)

branch: master
commit 5e2fc7034076dfc92fafca45e0706bce4e928328
Author: Vadim Zeitlin <address@hidden>
Commit: Vadim Zeitlin <address@hidden>

    Make HTML generation utilities more type-safe
    
    Add element and void_element classes as well as predefined constants for
    tags and attributes.
---
 Makefile.am                |   2 +
 group_quote_pdf_gen_wx.cpp | 182 +++++++++++++++----------
 html.cpp                   | 125 +++++++++++++++++
 html.hpp                   | 326 +++++++++++++++++++++++++++++++++++++++++++++
 html_text.hpp              | 199 ---------------------------
 objects.make               |   1 +
 6 files changed, 569 insertions(+), 266 deletions(-)

diff --git a/Makefile.am b/Makefile.am
index a7dca3f..9164a0c 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -317,6 +317,7 @@ liblmi_common_sources = \
     global_settings.cpp \
     group_values.cpp \
     group_quote_pdf_gen.cpp \
+    html.cpp \
     illustrator.cpp \
     input.cpp \
     input_harmonization.cpp \
@@ -1151,6 +1152,7 @@ noinst_HEADERS = \
     group_quote_pdf_gen.hpp \
     group_values.hpp \
     handle_exceptions.hpp \
+    html.hpp \
     icon_monger.hpp \
     ieee754.hpp \
     ihs_irc7702.hpp \
diff --git a/group_quote_pdf_gen_wx.cpp b/group_quote_pdf_gen_wx.cpp
index cc056c3..66ba8f1 100644
--- a/group_quote_pdf_gen_wx.cpp
+++ b/group_quote_pdf_gen_wx.cpp
@@ -28,7 +28,7 @@
 #include "calendar_date.hpp"            // jdn_t()
 #include "data_directory.hpp"           // AddDataDir()
 #include "force_linking.hpp"
-#include "html_text.hpp"
+#include "html.hpp"
 #include "ledger.hpp"
 #include "ledger_invariant.hpp"
 #include "ledger_text_formats.hpp"      // ledger_format()
@@ -60,25 +60,28 @@ LMI_FORCE_LINKING_IN_SITU(group_quote_pdf_generator_wx)
 namespace
 {
 
-/// Transform 'html' -> '<br><br>html', but return empty string unchanged.
+/// Transform 's' -> '<br><br>s', but return empty string unchanged.
 
-html::text brbr(std::string const& html)
+html::text brbr(std::string const& s)
 {
-    return html::text::escape_for_html_elem(html)
-            .wrap_if_not_empty<html::tag::br>()
-            .wrap_if_not_empty<html::tag::br>()
-            ;
+    using namespace html;
+
+    return s.empty()
+        ? text()
+        : tag::br + tag::br + text::from(s)
+        ;
 }
 
-/// Transform 'html' -> '<br><br><b>html</b>', but return empty string 
unchanged.
+/// Transform 's' -> '<br><br><b>s</b>', but return empty string unchanged.
 
-html::text brbrb(std::string const& html)
+html::text brbrb(std::string const& s)
 {
-    return html::text::escape_for_html_elem(html)
-                .wrap_if_not_empty<html::tag::b>()
-                .wrap_if_not_empty<html::tag::br>()
-                .wrap_if_not_empty<html::tag::br>()
-                ;
+    using namespace html;
+
+    return s.empty()
+        ? text()
+        : tag::br + tag::br + tag::b(text::from(s))
+        ;
 }
 
 /// Generate HTML representation of a field name and value in an HTML table.
@@ -86,18 +89,27 @@ html::text brbrb(std::string const& html)
 /// The HTML fragment generated by this function contains two <td> tags with
 /// the given contents.
 
-wxString name_value_as_html_table_data
+html::text name_value_as_html_table_data
     (std::string const& name
     ,std::string const& value
     )
 {
-    return wxString::Format
-        ("<td nowrap align=\"right\"><b>%s%s&nbsp;&nbsp;</b></td>"
-         "<td>%s&nbsp;&nbsp;&nbsp;&nbsp;</td>"
-        ,html::text::escape_for_html_elem(name).as_string()
-        ,(value.empty() ? "" : ":")
-        ,html::text::escape_for_html_elem(value).as_string()
-        );
+    using namespace html;
+
+    auto const nbsp2 = text::nbsp() + text::nbsp();
+
+    return
+        tag::td[attr::nowrap][attr::align("right")]
+            (tag::b
+                (text::from(name))
+                (text::from(value.empty() ? "" : ":"))
+                (nbsp2)
+            )
+        +
+        tag::td
+            (text::from(value))
+            (nbsp2 + nbsp2)
+        ;
 }
 
 /// Simple description of a custom field, consisting of a non-empty name and a
@@ -319,7 +331,7 @@ class group_quote_pdf_generator_wx
         std::string premium_mode_;
         std::string contract_state_;
         std::string effective_date_;
-        html::text   footer_html_;
+        html::text  footer_html_;
 
         // Dynamically-determined fields.
         std::string elected_riders_;
@@ -873,7 +885,7 @@ void group_quote_pdf_generator_wx::output_image_header
     wxDCFontChanger set_bigger_font(pdf_dc, pdf_dc.GetFont().Scaled(1.5));
     wxDCTextColourChanger set_white_text(pdf_dc, *wxWHITE);
 
-    // Don't use escape_for_html_elem() here: instead, call
+    // Don't use html::text::from() here: instead, call
     // wxString::FromUTF8() directly, e.g., to preserve literal '&'.
     wxString const image_text
         (wxString::FromUTF8(report_data_.short_product_.c_str())
@@ -895,45 +907,49 @@ void group_quote_pdf_generator_wx::output_document_header
     ,int* pos_y
     )
 {
-    wxString const title_html = wxString::Format
-        ("<table width=\"100%%\">"
-         "<tr>"
-         "<td align=\"center\"><i><font size=\"+1\">%s</font></i></td>"
-         "</tr>"
-         "<tr>"
-         "<td align=\"center\"><i>Prepared Date: %s</i></td>"
-         "</tr>"
-         "<tr>"
-         "<td align=\"center\"><i>Prepared By: %s</i></td>"
-         "</tr>"
-         "</table>"
-        ,html::text::escape_for_html_elem(report_data_.company_).as_string()
-        ,wxDateTime::Today().FormatDate()
-        
,html::text::escape_for_html_elem(report_data_.prepared_by_).as_string()
-        );
+    using namespace html;
+
+    auto title_html =
+        tag::table[attr::width("100%")]
+            (tag::tr
+                (tag::td[attr::align("center")]
+                    (tag::i
+                        (tag::font[attr::size("+1")]
+                            (text::from(report_data_.company_)
+                            )
+                        )
+                    )
+                )
+            )
+            (tag::tr
+                (tag::td[attr::align("center")]
+                    (tag::i
+                        (text::from
+                            ("Prepared Date: "
+                            +wxDateTime::Today().FormatDate().ToStdString()
+                            )
+                        )
+                    )
+                )
+            )
+            (tag::tr
+                (tag::td[attr::align("center")]
+                    (tag::i
+                        (text::from("Prepared By: " + 
report_data_.prepared_by_)
+                        )
+                    )
+                )
+            );
 
     pdf_writer.output_html
         (pdf_writer.get_horz_margin()
         ,*pos_y
         ,pdf_writer.get_page_width() / 2
-        ,title_html
+        ,title_html.as_html()
         );
 
-    // Build the summary table with all the mandatory fields.
-    wxString summary_html =
-         "<table width=\"100%\" cellspacing=\"0\" cellpadding=\"0\">"
-         // This extra top empty row works around a bug in wxHTML
-         // table positioning code: it uses the provided ordinate
-         // coordinate as a base line of the first table line and
-         // not as its top, as it ought to, so without this line
-         // the rectangle drawn below wouldn't contain the header.
-         "<tr>"
-         "<td align=\"center\" colspan=\"4\">&nbsp;</td>"
-         "</tr>"
-         "<tr>"
-         "<td align=\"center\" colspan=\"4\"><font size=\"+1\">Plan Details 
Summary</font></td>"
-         "</tr>"
-         ;
+    // Build the summary table with all the mandatory fields, starting by
+    // building the (partly) dynamic fields rows part.
 
     // Add fixed fields first, then any additional ones,
     // in left-to-right then top-to-bottom order.
@@ -955,24 +971,56 @@ void group_quote_pdf_generator_wx::output_document_header
     std::vector<extra_summary_field> const& f = report_data_.extra_fields_;
     fields.insert(fields.end(), f.begin(), f.end());
 
-    bool parity = true;
-    for(auto const& i : fields)
+    text fields_html;
+    for(std::size_t i = 0; i < fields.size(); i += 2)
         {
-        summary_html += parity ? "<tr>" : "";
-        summary_html += name_value_as_html_table_data(i.name, i.value);
-        summary_html += parity ? "" : "</tr>";
-        parity = !parity;
+        auto row_html = name_value_as_html_table_data
+            (fields[i].name, fields[i].value
+            )
+            ;
+
+        if(i + 1 < fields.size())
+            {
+            row_html += name_value_as_html_table_data
+                    (fields[i + 1].name, fields[i + 1].value
+                    )
+                    ;
+            }
+
+        fields_html += tag::tr(row_html);
         }
-    summary_html += parity ? "" : "</tr>";
 
     // Finally close the summary table.
-    summary_html += "</table>";
+    auto const summary_html =
+        tag::table[attr::width("100%")]
+                  [attr::cellspacing("0")]
+                  [attr::cellpadding("0")]
+            // This extra top empty row works around a bug in wxHTML
+            // table positioning code: it uses the provided ordinate
+            // coordinate as a base line of the first table line and
+            // not as its top, as it ought to, so without this line
+            // the rectangle drawn below wouldn't contain the header.
+            (tag::tr
+                (tag::td[attr::align("center")][attr::colspan("4")]
+                    (text::nbsp())
+                )
+            )
+            (tag::tr
+                (tag::td[attr::align("center")][attr::colspan("4")]
+                    (tag::font[attr::size("+1")]
+                        (text::from("Plan Details Summary"))
+                    )
+                )
+            )
+            (fields_html
+            )
+            ;
 
     int const summary_height = pdf_writer.output_html
         (pdf_writer.get_horz_margin() + pdf_writer.get_page_width() / 2
         ,*pos_y
         ,pdf_writer.get_page_width() / 2
-        ,summary_html
+        ,summary_html.as_html()
         );
 
     // wxHTML tables don't support "frame" attribute, so draw the border around
@@ -1147,13 +1195,13 @@ void group_quote_pdf_generator_wx::output_footer
         *pos_y += vert_skip;
         }
 
-    auto footer_html = report_data_.footer_html_.wrap<html::tag::p>();
+    auto footer_html = html::tag::p(report_data_.footer_html_);
 
     *pos_y += pdf_writer.output_html
         (pdf_writer.get_horz_margin()
         ,*pos_y
         ,pdf_writer.get_page_width()
-        ,std::move(footer_html).as_string()
+        ,footer_html.as_html()
         ,output_mode
         );
 }
diff --git a/html.cpp b/html.cpp
new file mode 100644
index 0000000..e0f0738
--- /dev/null
+++ b/html.cpp
@@ -0,0 +1,125 @@
+// Utilities for representing and generating HTML.
+//
+// Copyright (C) 2017 Gregory W. Chicares.
+//
+// This program is free software; you can redistribute it and/or modify
+// it under the terms of the GNU General Public License version 2 as
+// published by the Free Software Foundation.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program; if not, write to the Free Software Foundation,
+// Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
+//
+// http://savannah.nongnu.org/projects/lmi
+// email: <address@hidden>
+// snail: Chicares, 186 Belle Woods Drive, Glastonbury CT 06033, USA
+
+#include "pchfile.hpp"
+
+#include "html.hpp"
+
+namespace html
+{
+
+namespace attr
+{
+
+extern attribute const align        ("align");
+extern attribute const cellpadding  ("cellpadding");
+extern attribute const cellspacing  ("cellspacing");
+extern attribute const colspan      ("colspan");
+extern attribute const nowrap       ("nowrap");
+extern attribute const size         ("size");
+extern attribute const width        ("width");
+
+} // namespace attr
+
+namespace tag
+{
+
+extern element      const b         ("b");
+extern void_element const br        ("br");
+extern element      const font      ("font");
+extern element      const i         ("i");
+extern element      const p         ("p");
+extern element      const table     ("table");
+extern element      const td        ("td");
+extern element      const tr        ("tr");
+
+} // namespace tag
+
+std::string attribute::as_string() const
+{
+    std::string s(name_);
+    if(!value_.empty())
+        {
+        s += "=";
+        // TODO: Escape quotes.
+        s += value_;
+        }
+    return s;
+}
+
+namespace detail
+{
+
+std::string any_element::get_start() const
+{
+    std::string s("<");
+    // Extra +1 for the space before attributes, even if it's not needed.
+    s.reserve(1 + std::strlen(name_) + 1 + attributes_.length() + 1);
+    s += name_;
+    if(!attributes_.empty())
+        {
+        s += " ";
+        s += attributes_;
+        }
+    s += ">";
+    return s;
+}
+
+void any_element::update_attributes(attribute const& attr)
+{
+    if(attributes_.empty())
+        {
+        attributes_ = attr.as_string();
+        }
+    else
+        {
+        attributes_ += " ";
+        attributes_ += attr.as_string();
+        }
+}
+
+} // namespace detail
+
+void element::update_contents(std::string&& contents)
+{
+    if(contents_.empty())
+        {
+        contents_ = std::move(contents);
+        }
+    else
+        {
+        contents_ += contents;
+        }
+}
+
+element::operator text() const
+{
+    std::string s(get_start());
+    s.reserve(s.length() + contents_.length() + 2 + std::strlen(name_) + 1);
+    s += contents_;
+    s += "</";
+    s += name_;
+    s += ">";
+
+    return text::from_html(std::move(s));
+}
+
+} // namespace html
diff --git a/html.hpp b/html.hpp
new file mode 100644
index 0000000..ecad033
--- /dev/null
+++ b/html.hpp
@@ -0,0 +1,326 @@
+// Utilities for representing and generating HTML.
+//
+// Copyright (C) 2017 Gregory W. Chicares.
+//
+// This program is free software; you can redistribute it and/or modify
+// it under the terms of the GNU General Public License version 2 as
+// published by the Free Software Foundation.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program; if not, write to the Free Software Foundation,
+// Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
+//
+// http://savannah.nongnu.org/projects/lmi
+// email: <address@hidden>
+// snail: Chicares, 186 Belle Woods Drive, Glastonbury CT 06033, USA
+
+#ifndef html_hpp
+#define html_hpp
+
+#include "config.hpp"
+
+#include <string>
+#include <utility>                      // std::move
+
+/// Namespace for helpers used for HTML generation.
+///
+/// Main idea is to avoid generating HTML using raw strings, which is error
+/// prone and difficult to read and maintain. One source of errors is
+/// forgetting to escape special characters, such as "<" or "&", and html::text
+/// class helps with this by providing from() method doing it automatically.
+///
+/// Another one is forgetting to close a tag (or closing a wrong one) and while
+/// html::text is too low level to help with this, html::element can be used
+/// for structured HTML generation, which guarantees that the result is
+/// well-formed. By using predefined constants in html::tag and html::attr
+/// namespaces, typos in the element names can also be automatically avoided.
+namespace html
+{
+
+/// Represents a piece of text containing HTML.
+///
+/// This is a separate type for type safety, e.g. to avoid passing raw,
+/// unescaped, strings to a function expecting HTML (or, less catastrophically,
+/// but still wrongly, passing already escaped HTML to a function doing
+/// escaping internally).
+///
+/// As it still needs to be converted to a string sooner or later to be really
+/// used, it does provide a conversion -- but it can be used only once.
+class text
+{
+  public:
+    // This type has value semantics.
+    text() = default;
+    text(text const&) = default;
+    text(text&&) = default;
+    text& operator=(text const&) = default;
+    text& operator=(text&&) = default;
+
+    /// Escape special XML characters in the given string, ensuring that it
+    /// appears correctly inside HTML element contents. Notice that we don't
+    /// need to escape quotes here as we never use the result of this function
+    /// inside an HTML attribute, only inside HTML elements.
+    static text from(std::string const& s)
+    {
+        std::string z;
+        z.reserve(s.length());
+        for(auto const& c : s)
+            {
+            switch(c)
+                {
+                case '<': z += "&lt;" ; break;
+                case '>': z += "&gt;" ; break;
+                case '&': z += "&amp;"; break;
+                default : z += c      ;
+                }
+            }
+
+        return text{std::move(z)};
+    }
+
+    /// Use the given string with HTML inside it directly. No escaping is done
+    /// by this ctor.
+    static text from_html(std::string s)
+    {
+        return text{std::move(s)};
+    }
+
+    /// Just a symbolic name for a non breaking space HTML entiry.
+    static text nbsp()
+    {
+        return text::from_html("&nbsp;");
+    }
+
+    /// Append another text fragment to this one.
+    ///
+    /// This method allows chained invocation for appending more than one
+    /// fragment at once.
+    text& operator+=(text const& t)
+    {
+        m_html += t.m_html;
+
+        return *this;
+    }
+
+    std::string const& as_html() const&
+    {
+        return m_html;
+    }
+
+    std::string&& as_html() &&
+    {
+        return std::move(m_html);
+    }
+
+  private:
+    // This move ctor is private and does not perform any escaping.
+    explicit text(std::string&& html)
+        :m_html{html}
+    {
+    }
+
+    std::string m_html;
+};
+
+/// Represents a single attribute of an HTML element.
+class attribute
+{
+  public:
+    explicit attribute(char const* name)
+        :name_{name}
+    {
+    }
+
+    attribute operator()(std::string value) const
+    {
+        return attribute(name_, std::move(value));
+    }
+
+    std::string as_string() const;
+
+  private:
+    attribute(char const* name, std::string&& value)
+        :name_{name}
+        ,value_{std::move(value)}
+    {
+    }
+
+    char const* const name_;
+    std::string const value_;
+};
+
+namespace detail
+{
+
+class any_element
+{
+  public:
+    /// Ctor should only be used with literal strings as argument.
+    explicit any_element(char const* name)
+        :name_(name)
+    {
+    }
+
+  protected:
+    // Return the opening tag of the element, with attributes, if any.
+    std::string get_start() const;
+
+    // Add the given attribute to our attributes string.
+    void update_attributes(attribute const& attr);
+
+    char const* const name_;
+
+  private:
+    std::string       attributes_;
+};
+
+} // namespace detail
+
+/// Represents a normal HTML element which can have content inside it.
+///
+/// This class uses the so called fluent API model in which calls to its
+/// different methods return the object itself and so can be chained together.
+/// For example (assuming an implicit "using namespace html"):
+///
+///     auto para_with_link =
+///         tag::p[attr::align("center")]
+///             (text("Link to "))
+///             (tag::a[attr::href("http://lmi.nongnu.org/";)]
+///                 (text::from("lmi project page"))
+///             )
+///         ;
+
+class element : private detail::any_element
+{
+  public:
+    /// Ctor should only be used with literal strings as argument.
+    explicit element(char const* name)
+        :detail::any_element(name)
+    {
+    }
+
+    element(element const&) = default;
+    element(element&&) = default;
+
+    /// Add an attribute.
+    element operator[](attribute const& attr) const&
+    {
+        element e{*this};
+        e.update_attributes(attr);
+        return e;
+    }
+
+    element&& operator[](attribute const& attr) &&
+    {
+        update_attributes(attr);
+        return std::move(*this);
+    }
+
+    /// Add inner contents.
+    element operator()(text contents) const&
+    {
+        element e{*this};
+        e.update_contents(std::move(contents).as_html());
+        return e;
+    }
+
+    element&& operator()(text contents) &&
+    {
+        update_contents(std::move(contents).as_html());
+        return std::move(*this);
+    }
+
+    /// Convert to HTML text with this element and its contents.
+    ///
+    /// This implicit conversion operator is not really dangerous as it is
+    /// normal to represent an HTML element as HTML text and it's very
+    /// convenient to have it as it allows to accept either another element or
+    /// text in our own operator() and also use operator+() defined below to
+    /// concatenate HTML elements without having to convert them to text
+    /// beforehand.
+    operator text() const;
+
+  private:
+    void update_contents(std::string&& contents);
+
+    std::string contents_;
+};
+
+/// Represents a void HTML element which can't have anything inside it.
+class void_element : private detail::any_element
+{
+  public:
+    explicit void_element(char const* name)
+        :detail::any_element(name)
+    {
+    }
+
+    void_element(void_element const&) = default;
+    void_element(void_element&&) = default;
+
+    void_element operator[](attribute const& attr) const&
+    {
+        void_element e{*this};
+        e.update_attributes(std::move(attr));
+        return e;
+    }
+
+    void_element&& operator[](attribute const& attr) &&
+    {
+        update_attributes(std::move(attr));
+        return std::move(*this);
+    }
+
+    operator text() const
+    {
+        return text::from_html(get_start());
+    }
+};
+
+/// Namespace for HTML attributes.
+
+namespace attr
+{
+
+extern attribute const align;
+extern attribute const cellpadding;
+extern attribute const cellspacing;
+extern attribute const colspan;
+extern attribute const nowrap;
+extern attribute const size;
+extern attribute const width;
+
+} // namespace attr
+
+/// Namespace for HTML tags.
+
+namespace tag
+{
+
+extern element      const b;
+extern void_element const br;
+extern element      const font;
+extern element      const i;
+extern element      const p;
+extern element      const table;
+extern element      const td;
+extern element      const tr;
+
+} // namespace tag
+
+inline
+text operator+(text t1, text const& t2)
+{
+    auto t{std::move(t1)};
+    t += t2;
+    return t;
+}
+
+} // namespace html
+
+#endif // html_hpp
diff --git a/html_text.hpp b/html_text.hpp
deleted file mode 100644
index 3f6c3c4..0000000
--- a/html_text.hpp
+++ /dev/null
@@ -1,199 +0,0 @@
-// Text representing HTML contents.
-//
-// Copyright (C) 2017 Gregory W. Chicares.
-//
-// This program is free software; you can redistribute it and/or modify
-// it under the terms of the GNU General Public License version 2 as
-// published by the Free Software Foundation.
-//
-// This program is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-// GNU General Public License for more details.
-//
-// You should have received a copy of the GNU General Public License
-// along with this program; if not, write to the Free Software Foundation,
-// Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
-//
-// http://savannah.nongnu.org/projects/lmi
-// email: <address@hidden>
-// snail: Chicares, 186 Belle Woods Drive, Glastonbury CT 06033, USA
-
-#ifndef html_text_hpp
-#define html_text_hpp
-
-#include "config.hpp"
-
-#include <string>
-#include <utility>                      // std::move
-
-/// Namespace for helpers used for HTML generation.
-namespace html
-{
-
-/// Namespace for the support HTML tags.
-///
-/// Tags are only used as template arguments, so they don't need to be defined,
-/// just declared -- and tag::info below specialized for them.
-
-namespace tag
-{
-
-struct b;
-struct br;
-struct p;
-
-template<typename T>
-struct info;
-
-template<>
-struct info<b>
-{
-    static char const* get_name() { return "b"; }
-    static bool has_end() { return true; }
-};
-
-template<>
-struct info<br>
-{
-    static char const* get_name() { return "br"; }
-    static bool has_end() { return false; }
-};
-
-template<>
-struct info<p>
-{
-    static char const* get_name() { return "p"; }
-    static bool has_end() { return true; }
-};
-
-} // namespace tag
-
-/// Represents a piece of text containing HTML.
-///
-/// This is a separate type for type safety, e.g. to avoid passing raw,
-/// unescaped, strings to a function expecting HTML (or, less catastrophically,
-/// but still wrongly, passing already escaped HTML to a function doing
-/// escaping internally).
-///
-/// As it still needs to be converted to a string sooner or later to be really
-/// used, it does provide a conversion -- but it can be used only once.
-class text
-{
-public:
-    // This type has value semantics.
-    text() = default;
-    text(text const&) = default;
-    text(text &&) = default;
-    text& operator=(text const&) = default;
-    text& operator=(text&&) = default;
-
-    /// Escape special XML characters in the given string, ensuring that it
-    /// appears correctly inside HTML element contents. Notice that we don't
-    /// need to escape quotes here as we never use the result of this function
-    /// inside an HTML attribute, only inside HTML elements.
-    static text escape_for_html_elem(std::string const& s)
-    {
-        std::string z;
-        z.reserve(s.length());
-        for(auto const& c : s)
-            {
-            switch(c)
-                {
-                case '<': z += "&lt;" ; break;
-                case '>': z += "&gt;" ; break;
-                case '&': z += "&amp;"; break;
-                default : z += c      ;
-                }
-            }
-
-        return text{std::move(z)};
-    }
-
-    /// Wrap contents of this HTML snippet into the given tag (or just prepend
-    /// the tag if it doesn't have the matching end tag).
-    template <typename T>
-    text wrap() const
-    {
-        return do_wrap(tag::info<T>::get_name(), tag::info<T>::has_end());
-    }
-
-    /// Wrap contents of this HTML snippet into the given tag (or just prepend
-    /// the tag if it doesn't have the matching end tag), but only if it is not
-    /// empty -- otherwise just return empty text.
-    ///
-    /// For the tags without matching closing tags, such as e.g. "<br>",
-    /// wrapping the text means just prepending the tag to it. This is still
-    /// done only if the text is not empty.
-    template <typename T>
-    text wrap_if_not_empty() const
-    {
-        return m_html.empty() ? text{} : wrap<T>();
-    }
-
-    /// Append another HTML fragment to this one.
-    ///
-    /// This method allows chained invocation for appending more than one
-    /// fragment at once.
-    text& operator+=(text t)
-    {
-        m_html += std::move(t).m_html;
-
-        return *this;
-    }
-
-    /// Return the string with UTF-8 encoded HTML text of the given object
-    /// consuming it in the process.
-    ///
-    /// Notice that this method can only be called on rvalue references, and so
-    /// it can't be used more than once.
-    std::string&& as_string() &&
-    {
-        return std::move(m_html);
-    }
-
-private:
-    // This move ctor is private and does not perform any escaping.
-    text(std::string&& html)
-        :m_html{html}
-    {
-    }
-
-    // Type-independent part implementation of public wrap(): having it as a
-    // separate function avoids template bloat.
-    text do_wrap(char const* outer_tag, bool has_end) const
-    {
-        std::string z;
-        z.reserve(m_html.length() + 10);
-
-        z = '<';
-        z += outer_tag;
-        z += '>';
-
-        z += m_html;
-
-        if(has_end)
-            {
-            z += "</";
-            z += outer_tag;
-            z += '>';
-            }
-
-        return text{std::move(z)};
-    }
-
-
-    std::string m_html;
-};
-
-inline
-text operator+(text t1, text t2)
-{
-    auto t{t1};
-    t += t2;
-    return t;
-}
-
-} // namespace html
-
-#endif // html_text_hpp
diff --git a/objects.make b/objects.make
index 70c2649..37dbef0 100644
--- a/objects.make
+++ b/objects.make
@@ -205,6 +205,7 @@ common_common_objects := \
   global_settings.o \
   group_quote_pdf_gen.o \
   group_values.o \
+  html.o \
   illustrator.o \
   input.o \
   input_harmonization.o \



reply via email to

[Prev in Thread] Current Thread [Next in Thread]