Break out serializer into its own cpp file

This commit is entirely just moving code, such that not all compile
units need to pull in the full html serializer.

Tested: Unit tests pass.  Pretty good coverage.

Redfish service validator passes.

Signed-off-by: Ed Tanous <edtanous@google.com>
Change-Id: Ifaebe9534c0693dc678fd994517563b89aca0cc5
diff --git a/http/http_connection.hpp b/http/http_connection.hpp
index 7ae22e9..cb0da26 100644
--- a/http/http_connection.hpp
+++ b/http/http_connection.hpp
@@ -33,14 +33,6 @@
 namespace crow
 {
 
-inline void prettyPrintJson(crow::Response& res)
-{
-    json_html_util::dumpHtml(res.body(), res.jsonValue);
-
-    res.addHeader(boost::beast::http::field::content_type,
-                  "text/html;charset=UTF-8");
-}
-
 // NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables)
 static int connectionCount = 0;
 
@@ -355,7 +347,7 @@
 
             if (prefered == ContentType::HTML)
             {
-                prettyPrintJson(res);
+                json_html_util::prettyPrintJson(res);
             }
             else if (prefered == ContentType::CBOR)
             {
diff --git a/include/json_html_serializer.hpp b/include/json_html_serializer.hpp
index e416683..ede2c89 100644
--- a/include/json_html_serializer.hpp
+++ b/include/json_html_serializer.hpp
@@ -1,629 +1,13 @@
+#include "http_response.hpp"
+
 #include <nlohmann/json.hpp>
 
-#include <algorithm>
+#include <string>
 
 namespace json_html_util
 {
 
-static constexpr uint8_t utf8Accept = 0;
-static constexpr uint8_t utf8Reject = 1;
-
-inline uint8_t decode(uint8_t& state, uint32_t& codePoint,
-                      const uint8_t byte) noexcept
-{
-    // clang-format off
-    static const std::array<std::uint8_t, 400> utf8d =
-    {
-        {
-            0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 00..1F
-            0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 20..3F
-            0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 40..5F
-            0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 60..7F
-            1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, // 80..9F
-            7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, // A0..BF
-            8, 8, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, // C0..DF
-            0xA, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x4, 0x3, 0x3, // E0..EF
-            0xB, 0x6, 0x6, 0x6, 0x5, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, // F0..FF
-            0x0, 0x1, 0x2, 0x3, 0x5, 0x8, 0x7, 0x1, 0x1, 0x1, 0x4, 0x6, 0x1, 0x1, 0x1, 0x1, // s0..s0
-            1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 0, 1, 0, 1, 1, 1, 1, 1, 1, // s1..s2
-            1, 2, 1, 1, 1, 1, 1, 2, 1, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 1, 1, 1, 1, 1, 1, 1, 1, // s3..s4
-            1, 2, 1, 1, 1, 1, 1, 1, 1, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 3, 1, 3, 1, 1, 1, 1, 1, 1, // s5..s6
-            1, 3, 1, 1, 1, 1, 1, 3, 1, 3, 1, 1, 1, 1, 1, 1, 1, 3, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 // s7..s8
-        }
-    };
-    // clang-format on
-
-    if (state > 0x8)
-    {
-        return state;
-    }
-
-    const uint8_t type = utf8d[byte];
-
-    codePoint = (state != utf8Accept)
-                    ? (byte & 0x3fU) | (codePoint << 6)
-                    : static_cast<uint32_t>(0xff >> type) & (byte);
-
-    state = utf8d[256U + state * 16U + type];
-    return state;
-}
-
-inline void dumpEscaped(std::string& out, const std::string& str)
-{
-    std::array<char, 512> stringBuffer{{}};
-    uint32_t codePoint = 0;
-    uint8_t state = utf8Accept;
-    std::size_t bytes = 0; // number of bytes written to string_buffer
-
-    // number of bytes written at the point of the last valid byte
-    std::size_t bytesAfterLastAccept = 0;
-    std::size_t undumpedChars = 0;
-
-    for (std::size_t i = 0; i < str.size(); ++i)
-    {
-        const uint8_t byte = static_cast<uint8_t>(str[i]);
-
-        switch (decode(state, codePoint, byte))
-        {
-            case utf8Accept: // decode found a new code point
-            {
-                switch (codePoint)
-                {
-                    case 0x08: // backspace
-                    {
-                        stringBuffer[bytes++] = '\\';
-                        stringBuffer[bytes++] = 'b';
-                        break;
-                    }
-
-                    case 0x09: // horizontal tab
-                    {
-                        stringBuffer[bytes++] = '\\';
-                        stringBuffer[bytes++] = 't';
-                        break;
-                    }
-
-                    case 0x0A: // newline
-                    {
-                        stringBuffer[bytes++] = '\\';
-                        stringBuffer[bytes++] = 'n';
-                        break;
-                    }
-
-                    case 0x0C: // formfeed
-                    {
-                        stringBuffer[bytes++] = '\\';
-                        stringBuffer[bytes++] = 'f';
-                        break;
-                    }
-
-                    case 0x0D: // carriage return
-                    {
-                        stringBuffer[bytes++] = '\\';
-                        stringBuffer[bytes++] = 'r';
-                        break;
-                    }
-
-                    case 0x22: // quotation mark
-                    {
-                        stringBuffer[bytes++] = '&';
-                        stringBuffer[bytes++] = 'q';
-                        stringBuffer[bytes++] = 'u';
-                        stringBuffer[bytes++] = 'o';
-                        stringBuffer[bytes++] = 't';
-                        stringBuffer[bytes++] = ';';
-                        break;
-                    }
-
-                    case 0x27: // apostrophe
-                    {
-                        stringBuffer[bytes++] = '&';
-                        stringBuffer[bytes++] = 'a';
-                        stringBuffer[bytes++] = 'p';
-                        stringBuffer[bytes++] = 'o';
-                        stringBuffer[bytes++] = 's';
-                        stringBuffer[bytes++] = ';';
-                        break;
-                    }
-
-                    case 0x26: // ampersand
-                    {
-                        stringBuffer[bytes++] = '&';
-                        stringBuffer[bytes++] = 'a';
-                        stringBuffer[bytes++] = 'm';
-                        stringBuffer[bytes++] = 'p';
-                        stringBuffer[bytes++] = ';';
-                        break;
-                    }
-
-                    case 0x3C: // less than
-                    {
-                        stringBuffer[bytes++] = '\\';
-                        stringBuffer[bytes++] = 'l';
-                        stringBuffer[bytes++] = 't';
-                        stringBuffer[bytes++] = ';';
-                        break;
-                    }
-
-                    case 0x3E: // greater than
-                    {
-                        stringBuffer[bytes++] = '\\';
-                        stringBuffer[bytes++] = 'g';
-                        stringBuffer[bytes++] = 't';
-                        stringBuffer[bytes++] = ';';
-                        break;
-                    }
-
-                    default:
-                    {
-                        // escape control characters (0x00..0x1F)
-                        if ((codePoint <= 0x1F) or (codePoint >= 0x7F))
-                        {
-                            if (codePoint <= 0xFFFF)
-                            {
-                                // NOLINTNEXTLINE(cppcoreguidelines-pro-type-vararg)
-                                std::snprintf(&stringBuffer[bytes], 7,
-                                              "\\u%04x",
-                                              static_cast<uint16_t>(codePoint));
-                                bytes += 6;
-                            }
-                            else
-                            {
-                                // NOLINTNEXTLINE(cppcoreguidelines-pro-type-vararg)
-                                std::snprintf(
-                                    &stringBuffer[bytes], 13, "\\u%04x\\u%04x",
-                                    static_cast<uint16_t>(0xD7C0 +
-                                                          (codePoint >> 10)),
-                                    static_cast<uint16_t>(0xDC00 +
-                                                          (codePoint & 0x3FF)));
-                                bytes += 12;
-                            }
-                        }
-                        else
-                        {
-                            // copy byte to buffer (all previous bytes
-                            // been copied have in default case above)
-                            stringBuffer[bytes++] = str[i];
-                        }
-                        break;
-                    }
-                }
-
-                // write buffer and reset index; there must be 13 bytes
-                // left, as this is the maximal number of bytes to be
-                // written ("\uxxxx\uxxxx\0") for one code point
-                if (stringBuffer.size() - bytes < 13)
-                {
-                    out.append(stringBuffer.data(), bytes);
-                    bytes = 0;
-                }
-
-                // remember the byte position of this accept
-                bytesAfterLastAccept = bytes;
-                undumpedChars = 0;
-                break;
-            }
-
-            case utf8Reject: // decode found invalid UTF-8 byte
-            {
-                // in case we saw this character the first time, we
-                // would like to read it again, because the byte
-                // may be OK for itself, but just not OK for the
-                // previous sequence
-                if (undumpedChars > 0)
-                {
-                    --i;
-                }
-
-                // reset length buffer to the last accepted index;
-                // thus removing/ignoring the invalid characters
-                bytes = bytesAfterLastAccept;
-
-                stringBuffer[bytes++] = '\\';
-                stringBuffer[bytes++] = 'u';
-                stringBuffer[bytes++] = 'f';
-                stringBuffer[bytes++] = 'f';
-                stringBuffer[bytes++] = 'f';
-                stringBuffer[bytes++] = 'd';
-
-                bytesAfterLastAccept = bytes;
-
-                undumpedChars = 0;
-
-                // continue processing the string
-                state = utf8Accept;
-                break;
-            }
-
-            default: // decode found yet incomplete multi-byte code point
-            {
-                ++undumpedChars;
-                break;
-            }
-        }
-    }
-
-    // we finished processing the string
-    if (state == utf8Accept)
-    {
-        // write buffer
-        if (bytes > 0)
-        {
-            out.append(stringBuffer.data(), bytes);
-        }
-    }
-    else
-    {
-        // write all accepted bytes
-        out.append(stringBuffer.data(), bytesAfterLastAccept);
-        out += "\\ufffd";
-    }
-}
-
-inline unsigned int countDigits(uint64_t number) noexcept
-{
-    unsigned int nDigits = 1;
-    for (;;)
-    {
-        if (number < 10)
-        {
-            return nDigits;
-        }
-        if (number < 100)
-        {
-            return nDigits + 1;
-        }
-        if (number < 1000)
-        {
-            return nDigits + 2;
-        }
-        if (number < 10000)
-        {
-            return nDigits + 3;
-        }
-        number = number / 10000U;
-        nDigits += 4;
-    }
-}
-
-template <typename NumberType,
-          std::enable_if_t<std::is_same<NumberType, uint64_t>::value or
-                               std::is_same<NumberType, int64_t>::value,
-                           int> = 0>
-void dumpInteger(std::string& out, NumberType number)
-{
-    std::array<char, 64> numberbuffer{{}};
-
-    static constexpr std::array<std::array<char, 2>, 100> digitsTo99{{
-        {'0', '0'}, {'0', '1'}, {'0', '2'}, {'0', '3'}, {'0', '4'}, {'0', '5'},
-        {'0', '6'}, {'0', '7'}, {'0', '8'}, {'0', '9'}, {'1', '0'}, {'1', '1'},
-        {'1', '2'}, {'1', '3'}, {'1', '4'}, {'1', '5'}, {'1', '6'}, {'1', '7'},
-        {'1', '8'}, {'1', '9'}, {'2', '0'}, {'2', '1'}, {'2', '2'}, {'2', '3'},
-        {'2', '4'}, {'2', '5'}, {'2', '6'}, {'2', '7'}, {'2', '8'}, {'2', '9'},
-        {'3', '0'}, {'3', '1'}, {'3', '2'}, {'3', '3'}, {'3', '4'}, {'3', '5'},
-        {'3', '6'}, {'3', '7'}, {'3', '8'}, {'3', '9'}, {'4', '0'}, {'4', '1'},
-        {'4', '2'}, {'4', '3'}, {'4', '4'}, {'4', '5'}, {'4', '6'}, {'4', '7'},
-        {'4', '8'}, {'4', '9'}, {'5', '0'}, {'5', '1'}, {'5', '2'}, {'5', '3'},
-        {'5', '4'}, {'5', '5'}, {'5', '6'}, {'5', '7'}, {'5', '8'}, {'5', '9'},
-        {'6', '0'}, {'6', '1'}, {'6', '2'}, {'6', '3'}, {'6', '4'}, {'6', '5'},
-        {'6', '6'}, {'6', '7'}, {'6', '8'}, {'6', '9'}, {'7', '0'}, {'7', '1'},
-        {'7', '2'}, {'7', '3'}, {'7', '4'}, {'7', '5'}, {'7', '6'}, {'7', '7'},
-        {'7', '8'}, {'7', '9'}, {'8', '0'}, {'8', '1'}, {'8', '2'}, {'8', '3'},
-        {'8', '4'}, {'8', '5'}, {'8', '6'}, {'8', '7'}, {'8', '8'}, {'8', '9'},
-        {'9', '0'}, {'9', '1'}, {'9', '2'}, {'9', '3'}, {'9', '4'}, {'9', '5'},
-        {'9', '6'}, {'9', '7'}, {'9', '8'}, {'9', '9'},
-    }};
-
-    // special case for "0"
-    if (number == 0)
-    {
-        out += '0';
-        return;
-    }
-
-    // use a pointer to fill the buffer
-    auto* bufferPtr = numberbuffer.begin();
-
-    const bool isNegative = std::is_same<NumberType, int64_t>::value &&
-                            !(number >= 0); // see issue #755
-    uint64_t absValue = 0;
-
-    unsigned int nChars = 0;
-
-    if (isNegative)
-    {
-        *bufferPtr = '-';
-        absValue = static_cast<uint64_t>(0 - number);
-
-        // account one more byte for the minus sign
-        nChars = 1 + countDigits(absValue);
-    }
-    else
-    {
-        absValue = static_cast<uint64_t>(number);
-        nChars = countDigits(absValue);
-    }
-
-    // spare 1 byte for '\0'
-    if (nChars >= numberbuffer.size() - 1)
-    {
-        return;
-    }
-
-    // jump to the end to generate the string from backward
-    // so we later avoid reversing the result
-    std::advance(bufferPtr, nChars - 1);
-
-    // Fast int2ascii implementation inspired by "Fastware" talk by Andrei
-    // Alexandrescu See: https://www.youtube.com/watch?v=o4-CwDo2zpg
-    while (absValue >= 100)
-    {
-        const auto digitsIndex = static_cast<unsigned>((absValue % 100));
-        absValue /= 100;
-        *bufferPtr = digitsTo99[digitsIndex][1];
-        bufferPtr = std::prev(bufferPtr);
-        *bufferPtr = digitsTo99[digitsIndex][0];
-        bufferPtr = std::prev(bufferPtr);
-    }
-
-    if (absValue >= 10)
-    {
-        const auto digitsIndex = static_cast<unsigned>(absValue);
-        *bufferPtr = digitsTo99[digitsIndex][1];
-        bufferPtr = std::prev(bufferPtr);
-        *bufferPtr = digitsTo99[digitsIndex][0];
-        // assignment never used: bufferPtr = std::prev(bufferPtr);
-    }
-    else
-    {
-        *bufferPtr = static_cast<char>('0' + absValue);
-        // assignment never used: bufferPtr = std::prev(bufferPtr);
-    }
-
-    out.append(numberbuffer.data(), nChars);
-}
-
-inline void dumpfloat(std::string& out, double number,
-                      std::true_type /*isIeeeSingleOrDouble*/)
-{
-    std::array<char, 64> numberbuffer{{}};
-
-    ::nlohmann::detail::to_chars(numberbuffer.begin(), numberbuffer.end(),
-                                 number);
-
-    out += numberbuffer.data();
-}
-
-inline void dumpfloat(std::string& out, double number,
-                      std::false_type /*isIeeeSingleOrDouble*/)
-{
-    std::array<char, 64> numberbuffer{{}};
-    // get number of digits for a float -> text -> float round-trip
-    static constexpr auto d = std::numeric_limits<double>::max_digits10;
-
-    // the actual conversion
-    // NOLINTNEXTLINE(cppcoreguidelines-pro-type-vararg)
-    std::ptrdiff_t len = std::snprintf(numberbuffer.data(), numberbuffer.size(),
-                                       "%.*g", d, number);
-
-    // negative value indicates an error
-    if (len <= 0)
-    {
-        return;
-    }
-
-    // check if buffer was large enough
-    if (numberbuffer.size() < static_cast<std::size_t>(len))
-    {
-        return;
-    }
-
-    auto* end = numberbuffer.begin();
-    std::advance(end, len);
-    end = std::remove(numberbuffer.begin(), end, ',');
-    std::fill(end, numberbuffer.end(), '\0');
-
-    if (std::distance(numberbuffer.begin(), end) > len)
-    {
-        return;
-    }
-    len = std::distance(numberbuffer.begin(), end);
-
-    out.append(numberbuffer.data(), static_cast<std::size_t>(len));
-
-    // determine if need to append ".0"
-    auto* newEnd = numberbuffer.begin();
-    std::advance(newEnd, len + 1);
-
-    const bool valueIsIntLike =
-        std::none_of(numberbuffer.begin(), newEnd,
-                     [](char c) { return (c == '.' or c == 'e'); });
-
-    if (valueIsIntLike)
-    {
-        out += ".0";
-    }
-}
-
-inline void dumpfloat(std::string& out, double number)
-{
-    // NaN / inf
-    if (!std::isfinite(number))
-    {
-        out += "null";
-        return;
-    }
-
-    // If float is an IEEE-754 single or double precision number,
-    // use the Grisu2 algorithm to produce short numbers which are
-    // guaranteed to round-trip, using strtof and strtod, resp.
-    //
-    // NB: The test below works if <long double> == <double>.
-    static constexpr bool isIeeeSingleOrDouble =
-        (std::numeric_limits<double>::is_iec559 and
-         std::numeric_limits<double>::digits == 24 and
-         std::numeric_limits<double>::max_exponent == 128) or
-        (std::numeric_limits<double>::is_iec559 and
-         std::numeric_limits<double>::digits == 53 and
-         std::numeric_limits<double>::max_exponent == 1024);
-
-    dumpfloat(out, number,
-              std::integral_constant<bool, isIeeeSingleOrDouble>());
-}
-
-inline void dump(std::string& out, const nlohmann::json& val)
-{
-    switch (val.type())
-    {
-        case nlohmann::json::value_t::object:
-        {
-            if (val.empty())
-            {
-                out += "{}";
-                return;
-            }
-
-            out += "{";
-
-            out += "<div class=tab>";
-            for (auto i = val.begin(); i != val.end();)
-            {
-                out += "&quot";
-                dumpEscaped(out, i.key());
-                out += "&quot: ";
-
-                bool inATag = false;
-                if (i.key() == "@odata.id" || i.key() == "@odata.context" ||
-                    i.key() == "Members@odata.nextLink" || i.key() == "Uri")
-                {
-                    inATag = true;
-                    out += "<a href=\"";
-                    dumpEscaped(out, i.value());
-                    out += "\">";
-                }
-                dump(out, i.value());
-                if (inATag)
-                {
-                    out += "</a>";
-                }
-                i++;
-                if (i != val.end())
-                {
-                    out += ",";
-                }
-                out += "<br>";
-            }
-            out += "</div>";
-            out += '}';
-
-            return;
-        }
-
-        case nlohmann::json::value_t::array:
-        {
-            if (val.empty())
-            {
-                out += "[]";
-                return;
-            }
-
-            out += "[";
-
-            out += "<div class=tab>";
-
-            // first n-1 elements
-            for (auto i = val.cbegin(); i != val.cend() - 1; ++i)
-            {
-                dump(out, *i);
-                out += ",<br>";
-            }
-
-            // last element
-            dump(out, val.back());
-
-            out += "</div>";
-            out += ']';
-
-            return;
-        }
-
-        case nlohmann::json::value_t::string:
-        {
-            out += '\"';
-            const std::string* ptr = val.get_ptr<const std::string*>();
-            dumpEscaped(out, *ptr);
-            out += '\"';
-            return;
-        }
-
-        case nlohmann::json::value_t::boolean:
-        {
-            if (*(val.get_ptr<const bool*>()))
-            {
-                out += "true";
-            }
-            else
-            {
-                out += "false";
-            }
-            return;
-        }
-
-        case nlohmann::json::value_t::number_integer:
-        {
-            dumpInteger(out, *(val.get_ptr<const int64_t*>()));
-            return;
-        }
-
-        case nlohmann::json::value_t::number_unsigned:
-        {
-            dumpInteger(out, *(val.get_ptr<const uint64_t*>()));
-            return;
-        }
-
-        case nlohmann::json::value_t::number_float:
-        {
-            dumpfloat(out, *(val.get_ptr<const double*>()));
-            return;
-        }
-
-        case nlohmann::json::value_t::discarded:
-        {
-            out += "<discarded>";
-            return;
-        }
-
-        case nlohmann::json::value_t::null:
-        {
-            out += "null";
-            return;
-        }
-        case nlohmann::json::value_t::binary:
-        {
-            // Do nothing;  Should never happen.
-            return;
-        }
-    }
-}
-
-inline void dumpHtml(std::string& out, const nlohmann::json& json)
-{
-    out += "<html>\n"
-           "<head>\n"
-           "<title>Redfish API</title>\n"
-           "<link href=\"/redfish.css\" rel=\"stylesheet\">\n"
-           "</head>\n"
-           "<body>\n"
-           "<div class=\"container\">\n"
-           "<img src=\"/DMTF_Redfish_logo_2017.svg\" alt=\"redfish\" "
-           "height=\"406px\" "
-           "width=\"576px\">\n"
-           "<div class=\"content\">\n";
-    dump(out, json);
-    out += "</div>\n"
-           "</div>\n"
-           "</body>\n"
-           "</html>\n";
-}
+void dumpHtml(std::string& out, const nlohmann::json& json);
+void prettyPrintJson(crow::Response& res);
 
 } // namespace json_html_util
diff --git a/meson.build b/meson.build
index 6cdde78..cdfc3dd 100644
--- a/meson.build
+++ b/meson.build
@@ -338,6 +338,7 @@
   'src/boost_beast.cpp',
   'src/boost_url.cpp',
   'src/dbus_singleton.cpp',
+  'src/json_html_serializer.cpp',
 )
 
 bmcweblib = static_library(
diff --git a/src/json_html_serializer.cpp b/src/json_html_serializer.cpp
new file mode 100644
index 0000000..405061b
--- /dev/null
+++ b/src/json_html_serializer.cpp
@@ -0,0 +1,571 @@
+#include "json_html_serializer.hpp"
+
+#include "http_response.hpp"
+
+#include <nlohmann/json.hpp>
+
+#include <algorithm>
+#include <array>
+#include <limits>
+
+namespace json_html_util
+{
+
+static constexpr uint8_t utf8Accept = 0;
+static constexpr uint8_t utf8Reject = 1;
+
+static uint8_t decode(uint8_t& state, uint32_t& codePoint,
+                      const uint8_t byte) noexcept
+{
+    // clang-format off
+    static const std::array<std::uint8_t, 400> utf8d =
+    {
+        {
+            0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 00..1F
+            0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 20..3F
+            0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 40..5F
+            0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 60..7F
+            1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, // 80..9F
+            7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, // A0..BF
+            8, 8, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, // C0..DF
+            0xA, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x4, 0x3, 0x3, // E0..EF
+            0xB, 0x6, 0x6, 0x6, 0x5, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, // F0..FF
+            0x0, 0x1, 0x2, 0x3, 0x5, 0x8, 0x7, 0x1, 0x1, 0x1, 0x4, 0x6, 0x1, 0x1, 0x1, 0x1, // s0..s0
+            1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 0, 1, 0, 1, 1, 1, 1, 1, 1, // s1..s2
+            1, 2, 1, 1, 1, 1, 1, 2, 1, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 1, 1, 1, 1, 1, 1, 1, 1, // s3..s4
+            1, 2, 1, 1, 1, 1, 1, 1, 1, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 3, 1, 3, 1, 1, 1, 1, 1, 1, // s5..s6
+            1, 3, 1, 1, 1, 1, 1, 3, 1, 3, 1, 1, 1, 1, 1, 1, 1, 3, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 // s7..s8
+        }
+    };
+    // clang-format on
+
+    if (state > 0x8)
+    {
+        return state;
+    }
+
+    const uint8_t type = utf8d[byte];
+
+    codePoint = (state != utf8Accept)
+                    ? (byte & 0x3fU) | (codePoint << 6)
+                    : static_cast<uint32_t>(0xff >> type) & (byte);
+
+    state = utf8d[256U + state * 16U + type];
+    return state;
+}
+
+static void dumpEscaped(std::string& out, const std::string& str)
+{
+    std::array<char, 512> stringBuffer{{}};
+    uint32_t codePoint = 0;
+    uint8_t state = utf8Accept;
+    std::size_t bytes = 0; // number of bytes written to string_buffer
+
+    // number of bytes written at the point of the last valid byte
+    std::size_t bytesAfterLastAccept = 0;
+    std::size_t undumpedChars = 0;
+
+    for (std::size_t i = 0; i < str.size(); ++i)
+    {
+        const uint8_t byte = static_cast<uint8_t>(str[i]);
+
+        switch (decode(state, codePoint, byte))
+        {
+            case utf8Accept: // decode found a new code point
+            {
+                switch (codePoint)
+                {
+                    case 0x08: // backspace
+                    {
+                        stringBuffer[bytes++] = '\\';
+                        stringBuffer[bytes++] = 'b';
+                        break;
+                    }
+
+                    case 0x09: // horizontal tab
+                    {
+                        stringBuffer[bytes++] = '\\';
+                        stringBuffer[bytes++] = 't';
+                        break;
+                    }
+
+                    case 0x0A: // newline
+                    {
+                        stringBuffer[bytes++] = '\\';
+                        stringBuffer[bytes++] = 'n';
+                        break;
+                    }
+
+                    case 0x0C: // formfeed
+                    {
+                        stringBuffer[bytes++] = '\\';
+                        stringBuffer[bytes++] = 'f';
+                        break;
+                    }
+
+                    case 0x0D: // carriage return
+                    {
+                        stringBuffer[bytes++] = '\\';
+                        stringBuffer[bytes++] = 'r';
+                        break;
+                    }
+
+                    case 0x22: // quotation mark
+                    {
+                        stringBuffer[bytes++] = '&';
+                        stringBuffer[bytes++] = 'q';
+                        stringBuffer[bytes++] = 'u';
+                        stringBuffer[bytes++] = 'o';
+                        stringBuffer[bytes++] = 't';
+                        stringBuffer[bytes++] = ';';
+                        break;
+                    }
+
+                    case 0x27: // apostrophe
+                    {
+                        stringBuffer[bytes++] = '&';
+                        stringBuffer[bytes++] = 'a';
+                        stringBuffer[bytes++] = 'p';
+                        stringBuffer[bytes++] = 'o';
+                        stringBuffer[bytes++] = 's';
+                        stringBuffer[bytes++] = ';';
+                        break;
+                    }
+
+                    case 0x26: // ampersand
+                    {
+                        stringBuffer[bytes++] = '&';
+                        stringBuffer[bytes++] = 'a';
+                        stringBuffer[bytes++] = 'm';
+                        stringBuffer[bytes++] = 'p';
+                        stringBuffer[bytes++] = ';';
+                        break;
+                    }
+
+                    case 0x3C: // less than
+                    {
+                        stringBuffer[bytes++] = '\\';
+                        stringBuffer[bytes++] = 'l';
+                        stringBuffer[bytes++] = 't';
+                        stringBuffer[bytes++] = ';';
+                        break;
+                    }
+
+                    case 0x3E: // greater than
+                    {
+                        stringBuffer[bytes++] = '\\';
+                        stringBuffer[bytes++] = 'g';
+                        stringBuffer[bytes++] = 't';
+                        stringBuffer[bytes++] = ';';
+                        break;
+                    }
+
+                    default:
+                    {
+                        // escape control characters (0x00..0x1F)
+                        if ((codePoint <= 0x1F) or (codePoint >= 0x7F))
+                        {
+                            if (codePoint <= 0xFFFF)
+                            {
+                                // NOLINTNEXTLINE(cppcoreguidelines-pro-type-vararg)
+                                std::snprintf(&stringBuffer[bytes], 7,
+                                              "\\u%04x",
+                                              static_cast<uint16_t>(codePoint));
+                                bytes += 6;
+                            }
+                            else
+                            {
+                                // NOLINTNEXTLINE(cppcoreguidelines-pro-type-vararg)
+                                std::snprintf(
+                                    &stringBuffer[bytes], 13, "\\u%04x\\u%04x",
+                                    static_cast<uint16_t>(0xD7C0 +
+                                                          (codePoint >> 10)),
+                                    static_cast<uint16_t>(0xDC00 +
+                                                          (codePoint & 0x3FF)));
+                                bytes += 12;
+                            }
+                        }
+                        else
+                        {
+                            // copy byte to buffer (all previous bytes
+                            // been copied have in default case above)
+                            stringBuffer[bytes++] = str[i];
+                        }
+                        break;
+                    }
+                }
+
+                // write buffer and reset index; there must be 13 bytes
+                // left, as this is the maximal number of bytes to be
+                // written ("\uxxxx\uxxxx\0") for one code point
+                if (stringBuffer.size() - bytes < 13)
+                {
+                    out.append(stringBuffer.data(), bytes);
+                    bytes = 0;
+                }
+
+                // remember the byte position of this accept
+                bytesAfterLastAccept = bytes;
+                undumpedChars = 0;
+                break;
+            }
+
+            case utf8Reject: // decode found invalid UTF-8 byte
+            {
+                // in case we saw this character the first time, we
+                // would like to read it again, because the byte
+                // may be OK for itself, but just not OK for the
+                // previous sequence
+                if (undumpedChars > 0)
+                {
+                    --i;
+                }
+
+                // reset length buffer to the last accepted index;
+                // thus removing/ignoring the invalid characters
+                bytes = bytesAfterLastAccept;
+
+                stringBuffer[bytes++] = '\\';
+                stringBuffer[bytes++] = 'u';
+                stringBuffer[bytes++] = 'f';
+                stringBuffer[bytes++] = 'f';
+                stringBuffer[bytes++] = 'f';
+                stringBuffer[bytes++] = 'd';
+
+                bytesAfterLastAccept = bytes;
+
+                undumpedChars = 0;
+
+                // continue processing the string
+                state = utf8Accept;
+                break;
+            }
+
+            default: // decode found yet incomplete multi-byte code point
+            {
+                ++undumpedChars;
+                break;
+            }
+        }
+    }
+
+    // we finished processing the string
+    if (state == utf8Accept)
+    {
+        // write buffer
+        if (bytes > 0)
+        {
+            out.append(stringBuffer.data(), bytes);
+        }
+    }
+    else
+    {
+        // write all accepted bytes
+        out.append(stringBuffer.data(), bytesAfterLastAccept);
+        out += "\\ufffd";
+    }
+}
+
+static unsigned int countDigits(uint64_t number) noexcept
+{
+    unsigned int nDigits = 1;
+    for (;;)
+    {
+        if (number < 10)
+        {
+            return nDigits;
+        }
+        if (number < 100)
+        {
+            return nDigits + 1;
+        }
+        if (number < 1000)
+        {
+            return nDigits + 2;
+        }
+        if (number < 10000)
+        {
+            return nDigits + 3;
+        }
+        number = number / 10000U;
+        nDigits += 4;
+    }
+}
+
+template <typename NumberType,
+          std::enable_if_t<std::is_same<NumberType, uint64_t>::value or
+                               std::is_same<NumberType, int64_t>::value,
+                           int> = 0>
+void dumpInteger(std::string& out, NumberType number)
+{
+    std::array<char, 64> numberbuffer{{}};
+
+    static constexpr std::array<std::array<char, 2>, 100> digitsTo99{{
+        {'0', '0'}, {'0', '1'}, {'0', '2'}, {'0', '3'}, {'0', '4'}, {'0', '5'},
+        {'0', '6'}, {'0', '7'}, {'0', '8'}, {'0', '9'}, {'1', '0'}, {'1', '1'},
+        {'1', '2'}, {'1', '3'}, {'1', '4'}, {'1', '5'}, {'1', '6'}, {'1', '7'},
+        {'1', '8'}, {'1', '9'}, {'2', '0'}, {'2', '1'}, {'2', '2'}, {'2', '3'},
+        {'2', '4'}, {'2', '5'}, {'2', '6'}, {'2', '7'}, {'2', '8'}, {'2', '9'},
+        {'3', '0'}, {'3', '1'}, {'3', '2'}, {'3', '3'}, {'3', '4'}, {'3', '5'},
+        {'3', '6'}, {'3', '7'}, {'3', '8'}, {'3', '9'}, {'4', '0'}, {'4', '1'},
+        {'4', '2'}, {'4', '3'}, {'4', '4'}, {'4', '5'}, {'4', '6'}, {'4', '7'},
+        {'4', '8'}, {'4', '9'}, {'5', '0'}, {'5', '1'}, {'5', '2'}, {'5', '3'},
+        {'5', '4'}, {'5', '5'}, {'5', '6'}, {'5', '7'}, {'5', '8'}, {'5', '9'},
+        {'6', '0'}, {'6', '1'}, {'6', '2'}, {'6', '3'}, {'6', '4'}, {'6', '5'},
+        {'6', '6'}, {'6', '7'}, {'6', '8'}, {'6', '9'}, {'7', '0'}, {'7', '1'},
+        {'7', '2'}, {'7', '3'}, {'7', '4'}, {'7', '5'}, {'7', '6'}, {'7', '7'},
+        {'7', '8'}, {'7', '9'}, {'8', '0'}, {'8', '1'}, {'8', '2'}, {'8', '3'},
+        {'8', '4'}, {'8', '5'}, {'8', '6'}, {'8', '7'}, {'8', '8'}, {'8', '9'},
+        {'9', '0'}, {'9', '1'}, {'9', '2'}, {'9', '3'}, {'9', '4'}, {'9', '5'},
+        {'9', '6'}, {'9', '7'}, {'9', '8'}, {'9', '9'},
+    }};
+
+    // special case for "0"
+    if (number == 0)
+    {
+        out += '0';
+        return;
+    }
+
+    // use a pointer to fill the buffer
+    auto* bufferPtr = numberbuffer.begin();
+
+    const bool isNegative = std::is_same<NumberType, int64_t>::value &&
+                            !(number >= 0); // see issue #755
+    uint64_t absValue = 0;
+
+    unsigned int nChars = 0;
+
+    if (isNegative)
+    {
+        *bufferPtr = '-';
+        absValue = static_cast<uint64_t>(0 - number);
+
+        // account one more byte for the minus sign
+        nChars = 1 + countDigits(absValue);
+    }
+    else
+    {
+        absValue = static_cast<uint64_t>(number);
+        nChars = countDigits(absValue);
+    }
+
+    // spare 1 byte for '\0'
+    if (nChars >= numberbuffer.size() - 1)
+    {
+        return;
+    }
+
+    // jump to the end to generate the string from backward
+    // so we later avoid reversing the result
+    std::advance(bufferPtr, nChars - 1);
+
+    // Fast int2ascii implementation inspired by "Fastware" talk by Andrei
+    // Alexandrescu See: https://www.youtube.com/watch?v=o4-CwDo2zpg
+    while (absValue >= 100)
+    {
+        const auto digitsIndex = static_cast<unsigned>((absValue % 100));
+        absValue /= 100;
+        *bufferPtr = digitsTo99[digitsIndex][1];
+        bufferPtr = std::prev(bufferPtr);
+        *bufferPtr = digitsTo99[digitsIndex][0];
+        bufferPtr = std::prev(bufferPtr);
+    }
+
+    if (absValue >= 10)
+    {
+        const auto digitsIndex = static_cast<unsigned>(absValue);
+        *bufferPtr = digitsTo99[digitsIndex][1];
+        bufferPtr = std::prev(bufferPtr);
+        *bufferPtr = digitsTo99[digitsIndex][0];
+        // assignment never used: bufferPtr = std::prev(bufferPtr);
+    }
+    else
+    {
+        *bufferPtr = static_cast<char>('0' + absValue);
+        // assignment never used: bufferPtr = std::prev(bufferPtr);
+    }
+
+    out.append(numberbuffer.data(), nChars);
+}
+
+static void dumpfloat(std::string& out, double number)
+{
+    // NaN / inf
+    if (!std::isfinite(number))
+    {
+        out += "null";
+        return;
+    }
+    std::array<char, 64> numberbuffer{{}};
+
+    ::nlohmann::detail::to_chars(numberbuffer.begin(), numberbuffer.end(),
+                                 number);
+
+    out += numberbuffer.data();
+}
+
+static void dump(std::string& out, const nlohmann::json& val)
+{
+    switch (val.type())
+    {
+        case nlohmann::json::value_t::object:
+        {
+            if (val.empty())
+            {
+                out += "{}";
+                return;
+            }
+
+            out += "{";
+
+            out += "<div class=tab>";
+            for (auto i = val.begin(); i != val.end();)
+            {
+                out += "&quot";
+                dumpEscaped(out, i.key());
+                out += "&quot: ";
+
+                bool inATag = false;
+                if (i.key() == "@odata.id" || i.key() == "@odata.context" ||
+                    i.key() == "Members@odata.nextLink" || i.key() == "Uri")
+                {
+                    inATag = true;
+                    out += "<a href=\"";
+                    dumpEscaped(out, i.value());
+                    out += "\">";
+                }
+                dump(out, i.value());
+                if (inATag)
+                {
+                    out += "</a>";
+                }
+                i++;
+                if (i != val.end())
+                {
+                    out += ",";
+                }
+                out += "<br>";
+            }
+            out += "</div>";
+            out += '}';
+
+            return;
+        }
+
+        case nlohmann::json::value_t::array:
+        {
+            if (val.empty())
+            {
+                out += "[]";
+                return;
+            }
+
+            out += "[";
+
+            out += "<div class=tab>";
+
+            // first n-1 elements
+            for (auto i = val.cbegin(); i != val.cend() - 1; ++i)
+            {
+                dump(out, *i);
+                out += ",<br>";
+            }
+
+            // last element
+            dump(out, val.back());
+
+            out += "</div>";
+            out += ']';
+
+            return;
+        }
+
+        case nlohmann::json::value_t::string:
+        {
+            out += '\"';
+            const std::string* ptr = val.get_ptr<const std::string*>();
+            dumpEscaped(out, *ptr);
+            out += '\"';
+            return;
+        }
+
+        case nlohmann::json::value_t::boolean:
+        {
+            if (*(val.get_ptr<const bool*>()))
+            {
+                out += "true";
+            }
+            else
+            {
+                out += "false";
+            }
+            return;
+        }
+
+        case nlohmann::json::value_t::number_integer:
+        {
+            dumpInteger(out, *(val.get_ptr<const int64_t*>()));
+            return;
+        }
+
+        case nlohmann::json::value_t::number_unsigned:
+        {
+            dumpInteger(out, *(val.get_ptr<const uint64_t*>()));
+            return;
+        }
+
+        case nlohmann::json::value_t::number_float:
+        {
+            dumpfloat(out, *(val.get_ptr<const double*>()));
+            return;
+        }
+
+        case nlohmann::json::value_t::discarded:
+        {
+            out += "<discarded>";
+            return;
+        }
+
+        case nlohmann::json::value_t::null:
+        {
+            out += "null";
+            return;
+        }
+        case nlohmann::json::value_t::binary:
+        {
+            // Do nothing;  Should never happen.
+            return;
+        }
+    }
+}
+
+void dumpHtml(std::string& out, const nlohmann::json& json)
+{
+    out += "<html>\n"
+           "<head>\n"
+           "<title>Redfish API</title>\n"
+           "<link href=\"/redfish.css\" rel=\"stylesheet\">\n"
+           "</head>\n"
+           "<body>\n"
+           "<div class=\"container\">\n"
+           "<img src=\"/DMTF_Redfish_logo_2017.svg\" alt=\"redfish\" "
+           "height=\"406px\" "
+           "width=\"576px\">\n"
+           "<div class=\"content\">\n";
+    dump(out, json);
+    out += "</div>\n"
+           "</div>\n"
+           "</body>\n"
+           "</html>\n";
+}
+
+void prettyPrintJson(crow::Response& res)
+{
+    json_html_util::dumpHtml(res.body(), res.jsonValue);
+
+    res.addHeader(boost::beast::http::field::content_type,
+                  "text/html;charset=UTF-8");
+}
+
+} // namespace json_html_util