summaryrefslogtreecommitdiff
path: root/src/bridge/colors.cpp
blob: bdc34bfd2a2c5149ff82e4206b3cbe34db488dc4 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
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
74
75
76
77
78
79
80
81
82
83
84
85
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
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
#include <bridge/colors.hpp>
#include <xmpp/xmpp_stanza.hpp>

#include <algorithm>
#include <iostream>

#include <string.h>

using namespace std::string_literals;

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
{
  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())
        current_node->get_last_child()->add_to_tail(txt);
      else
        current_node->add_to_inner(txt);

      if (s[pos_end] == IRC_FORMAT_BOLD_CHAR)
        styles.strong = !styles.strong;
      else if (s[pos_end] == IRC_FORMAT_NEWLINE_CHAR)
        {
          XmlNode* br_node = new XmlNode("br");
          current_node->add_child(br_node);
          cleaned += '\n';
        }
      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)
        styles_str += "color:"s +
          irc_colors_to_css[styles.fg % IRC_NUM_COLORS] + ";";
      if (styles.bg != -1)
        styles_str += "background-color:"s +
          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())
    current_node->get_last_child()->add_to_tail(txt);
  else
    current_node->add_to_inner(txt);

  if (current_node != result.get())
    result->add_child(current_node);

  Xmpp::body body_res = std::make_tuple(cleaned, std::move(result));
  return body_res;
}