colors.cpp 5.22 KB
Newer Older
louiz’'s avatar
louiz’ committed
1
#include <bridge/colors.hpp>
2 3
#include <xmpp/xmpp_stanza.hpp>

louiz’'s avatar
louiz’ committed
4 5 6
#include <algorithm>
#include <iostream>

7 8
#include <string.h>

louiz’'s avatar
louiz’ committed
9 10
using namespace std::string_literals;

11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34
static const char IRC_NUM_COLORS = 16;

static const char* irc_colors_to_css[IRC_NUM_COLORS] = {
  "white",
  "black",
  "blue",
  "green",
  "indianred",
  "red",
  "magenta",
  "brown",
  "yellow",
  "lightgreen",
  "cyan",
  "lightcyan",
  "lightblue",
  "lightmagenta",
  "gray",
  "white",
};

#define XHTML_NS "http://www.w3.org/1999/xhtml"

struct styles_t
louiz’'s avatar
louiz’ committed
35
{
36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73
  bool strong;
  bool underline;
  bool italic;
  int fg;
  int bg;
};

/** We keep the currently-applied CSS styles in a structure. Each time a tag
 * is found, update this style list, then close the current span XML element
 * (if it is open), then reopen it with all the new styles in it.  This is
 * done this way because IRC formatting does not map well with XML
 * (hierarchical tags), it’s a lot easier and cleaner to remove all styles
 * and reapply them for each tag, instead of trying to keep a consistent
 * hierarchy of span, strong, em etc tags.  The generated XML is one-level
 * deep only.
*/
Xmpp::body irc_format_to_xhtmlim(const std::string& s)
{
  if (s.find_first_of(irc_format_char) == std::string::npos)
    // there is no special formatting at all
    return std::make_tuple(s, nullptr);

  std::string cleaned;

  styles_t styles = {false, false, false, -1, -1};

  std::unique_ptr<XmlNode> result = std::make_unique<XmlNode>("body");
  (*result)["xmlns"] = XHTML_NS;

  XmlNode* current_node = result.get();
  std::string::size_type pos_start = 0;
  std::string::size_type pos_end;

  while ((pos_end = s.find_first_of(irc_format_char, pos_start)) != std::string::npos)
    {
      const std::string txt = s.substr(pos_start, pos_end-pos_start);
      cleaned += txt;
      if (current_node->has_children())
74
        current_node->get_last_child()->add_to_tail(txt);
75
      else
76
        current_node->add_to_inner(txt);
77 78 79

      if (s[pos_end] == IRC_FORMAT_BOLD_CHAR)
        styles.strong = !styles.strong;
Link Mauve's avatar
Link Mauve committed
80 81 82 83 84 85
      else if (s[pos_end] == IRC_FORMAT_NEWLINE_CHAR)
        {
          XmlNode* br_node = new XmlNode("br");
          current_node->add_child(br_node);
          cleaned += '\n';
        }
86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139
      else if (s[pos_end] == IRC_FORMAT_UNDERLINE_CHAR)
        styles.underline = !styles.underline;
      else if (s[pos_end] == IRC_FORMAT_ITALIC_CHAR)
        styles.italic = !styles.italic;
      else if (s[pos_end] == IRC_FORMAT_RESET_CHAR)
        styles = {false, false, false, -1, -1};
      else if (s[pos_end] == IRC_FORMAT_REVERSE_CHAR)
        { }                      // TODO
      else if (s[pos_end] == IRC_FORMAT_REVERSE2_CHAR)
        { }                      // TODO
      else if (s[pos_end] == IRC_FORMAT_FIXED_CHAR)
        { }                      // TODO
      else if (s[pos_end] == IRC_FORMAT_COLOR_CHAR)
        {
          size_t pos = pos_end + 1;
          styles.fg = -1;
          styles.bg = -1;
          // get the first number following the format char
          if (pos < s.size() && s[pos] >= '0' && s[pos] <= '9')
            {                   // first digit
              styles.fg = s[pos++] - '0';
              if (pos < s.size() && s[pos] >= '0' && s[pos] <= '9')
                // second digit
                styles.fg = styles.fg * 10 + s[pos++] - '0';
            }
          if (pos < s.size() && s[pos] == ',')
            {                   // get bg color after the comma
              pos++;
              if (pos < s.size() && s[pos] >= '0' && s[pos] <= '9')
                {               // first digit
                  styles.bg = s[pos++] - '0';
                  if (pos < s.size() && s[pos] >= '0' && s[pos] <= '9')
                    // second digit
                    styles.bg = styles.bg * 10 + s[pos++] - '0';
                }
            }
          pos_end = pos - 1;
        }

      // close opened span, if any
      if (current_node != result.get())
        {
          result->add_child(current_node);
          current_node = result.get();
        }
      // Take all currently-applied style and create a new span with it
      std::string styles_str;
      if (styles.strong)
        styles_str += "font-weight:bold;";
      if (styles.underline)
        styles_str += "text-decoration:underline;";
      if (styles.italic)
        styles_str += "font-style:italic;";
      if (styles.fg != -1)
louiz’'s avatar
louiz’ committed
140
        styles_str += "color:"s +
141 142
          irc_colors_to_css[styles.fg % IRC_NUM_COLORS] + ";";
      if (styles.bg != -1)
louiz’'s avatar
louiz’ committed
143
        styles_str += "background-color:"s +
144 145 146 147 148 149 150 151 152 153 154 155 156 157 158
          irc_colors_to_css[styles.bg % IRC_NUM_COLORS] + ";";
      if (!styles_str.empty())
        {
          current_node = new XmlNode("span");
          (*current_node)["style"] = styles_str;
        }

      pos_start = pos_end + 1;
    }

  // If some text remains, without any format char, just append that text at
  // the end of the current node
  const std::string txt = s.substr(pos_start, pos_end-pos_start);
  cleaned += txt;
  if (current_node->has_children())
159
    current_node->get_last_child()->add_to_tail(txt);
160
  else
161
    current_node->add_to_inner(txt);
162 163

  if (current_node != result.get())
louiz’'s avatar
louiz’ committed
164
    result->add_child(current_node);
165 166 167

  Xmpp::body body_res = std::make_tuple(cleaned, std::move(result));
  return body_res;
louiz’'s avatar
louiz’ committed
168
}