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
169
|
#include <bridge/colors.hpp>
#include <xmpp/xmpp_stanza.hpp>
#include <utils/make_unique.hpp>
#include <algorithm>
#include <iostream>
#include <string.h>
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->set_inner(txt);
if (s[pos_end] == IRC_FORMAT_BOLD_CHAR)
styles.strong = !styles.strong;
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())
{
current_node->close();
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 += std::string("color:") +
irc_colors_to_css[styles.fg % IRC_NUM_COLORS] + ";";
if (styles.bg != -1)
styles_str += std::string("background-color:") +
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()->set_tail(txt);
else
current_node->set_inner(txt);
if (current_node != result.get())
{
current_node->close();
result->add_child(current_node);
current_node = result.get();
}
result->close();
Xmpp::body body_res = std::make_tuple(cleaned, std::move(result));
return body_res;
}
|