File indexing completed on 2024-05-12 15:36:55
0001 /* 0002 SPDX-FileCopyrightText: 2002-2008 The Kopete developers <kopete-devel@kde.org> 0003 SPDX-FileCopyrightText: 2008 Carlo Segato <brandon.ml@gmail.com> 0004 SPDX-FileCopyrightText: 2002-2003 Stefan Gehn <metz@gehn.net> 0005 SPDX-FileCopyrightText: 2005 Engin AYDOGAN <engin@bzzzt.biz> 0006 0007 SPDX-License-Identifier: LGPL-2.1-or-later 0008 */ 0009 0010 #include "kemoticonstheme.h" 0011 #include "kemoticons.h" 0012 0013 #include "kemoticons_core_debug.h" 0014 0015 class Q_DECL_HIDDEN KEmoticonsTheme::KEmoticonsThemeData : public QSharedData 0016 { 0017 public: 0018 KEmoticonsThemeData(); 0019 ~KEmoticonsThemeData(); 0020 KEmoticonsProvider *provider = nullptr; 0021 }; 0022 0023 KEmoticonsTheme::KEmoticonsThemeData::KEmoticonsThemeData() 0024 { 0025 } 0026 0027 KEmoticonsTheme::KEmoticonsThemeData::~KEmoticonsThemeData() 0028 { 0029 // delete provider; 0030 } 0031 0032 KEmoticonsTheme::KEmoticonsTheme() 0033 { 0034 d = new KEmoticonsThemeData; 0035 } 0036 0037 KEmoticonsTheme::KEmoticonsTheme(const KEmoticonsTheme &ket) 0038 { 0039 d = ket.d; 0040 } 0041 0042 KEmoticonsTheme::KEmoticonsTheme(KEmoticonsProvider *p) 0043 { 0044 d = new KEmoticonsThemeData; 0045 d->provider = p; 0046 } 0047 0048 KEmoticonsTheme::~KEmoticonsTheme() 0049 { 0050 } 0051 0052 #if KEMOTICONS_BUILD_DEPRECATED_SINCE(5, 0) 0053 bool KEmoticonsTheme::loadTheme(const QString &path) 0054 { 0055 if (!d->provider) { 0056 return false; 0057 } 0058 0059 return d->provider->loadTheme(path); 0060 } 0061 #endif 0062 0063 #if KEMOTICONS_BUILD_DEPRECATED_SINCE(5, 0) 0064 bool KEmoticonsTheme::removeEmoticon(const QString &emo) 0065 { 0066 if (!d->provider) { 0067 return false; 0068 } 0069 0070 return d->provider->removeEmoticon(emo); 0071 } 0072 #endif 0073 0074 #if KEMOTICONS_BUILD_DEPRECATED_SINCE(5, 0) 0075 bool KEmoticonsTheme::addEmoticon(const QString &emo, const QString &text, KEmoticonsProvider::AddEmoticonOption option) 0076 { 0077 if (!d->provider) { 0078 return false; 0079 } 0080 0081 return d->provider->addEmoticon(emo, text, option); 0082 } 0083 #endif 0084 0085 #if KEMOTICONS_BUILD_DEPRECATED_SINCE(5, 0) 0086 void KEmoticonsTheme::save() 0087 { 0088 if (!d->provider) { 0089 return; 0090 } 0091 0092 d->provider->saveTheme(); 0093 } 0094 #endif 0095 0096 QString KEmoticonsTheme::themeName() const 0097 { 0098 if (!d->provider) { 0099 return QString(); 0100 } 0101 0102 return d->provider->themeName(); 0103 } 0104 0105 void KEmoticonsTheme::setThemeName(const QString &name) 0106 { 0107 if (!d->provider) { 0108 return; 0109 } 0110 0111 d->provider->setThemeName(name); 0112 } 0113 0114 QString KEmoticonsTheme::themePath() const 0115 { 0116 if (!d->provider) { 0117 return QString(); 0118 } 0119 0120 return d->provider->themePath(); 0121 } 0122 0123 QString KEmoticonsTheme::fileName() const 0124 { 0125 if (!d->provider) { 0126 return QString(); 0127 } 0128 0129 return d->provider->fileName(); 0130 } 0131 0132 QHash<QString, QStringList> KEmoticonsTheme::emoticonsMap() const 0133 { 0134 if (!d->provider) { 0135 return QHash<QString, QStringList>(); 0136 } 0137 0138 return d->provider->emoticonsMap(); 0139 } 0140 0141 #if KEMOTICONS_BUILD_DEPRECATED_SINCE(5, 0) 0142 void KEmoticonsTheme::createNew() 0143 { 0144 if (!d->provider) { 0145 return; 0146 } 0147 0148 d->provider->newTheme(); 0149 } 0150 #endif 0151 0152 QString KEmoticonsTheme::parseEmoticons(const QString &text, ParseMode mode, const QStringList &exclude) const 0153 { 0154 const QList<Token> tokens = tokenize(text, mode | SkipHTML); 0155 if (tokens.isEmpty() && !text.isEmpty()) { 0156 return text; 0157 } 0158 0159 QString result; 0160 0161 for (const Token &token : tokens) { 0162 switch (token.type) { 0163 case Text: 0164 result += token.text; 0165 break; 0166 case Image: 0167 if (!exclude.contains(token.text)) { 0168 result += token.picHTMLCode; 0169 } else { 0170 result += token.text; 0171 } 0172 break; 0173 default: 0174 qCWarning(KEMOTICONS_CORE) << "Unknown token type. Something's broken."; 0175 break; 0176 } 0177 } 0178 return result; 0179 } 0180 0181 bool EmoticonCompareEscaped(const KEmoticonsProvider::Emoticon &s1, const KEmoticonsProvider::Emoticon &s2) 0182 { 0183 return s1.matchTextEscaped.length() > s2.matchTextEscaped.length(); 0184 } 0185 bool EmoticonCompare(const KEmoticonsProvider::Emoticon &s1, const KEmoticonsProvider::Emoticon &s2) 0186 { 0187 return s1.matchText.length() > s2.matchText.length(); 0188 } 0189 0190 QList<KEmoticonsTheme::Token> KEmoticonsTheme::tokenize(const QString &message, ParseMode mode) const 0191 { 0192 if (!d->provider) { 0193 return QList<KEmoticonsTheme::Token>(); 0194 } 0195 0196 if (!(mode & (StrictParse | RelaxedParse))) { 0197 //if none of theses two mode are selected, use the mode from the config 0198 mode |= KEmoticons::parseMode(); 0199 } 0200 0201 QList<Token> result; 0202 0203 /* previous char, in the firs iteration assume that it is space since we want 0204 * to let emoticons at the beginning, the very first previous QChar must be a space. */ 0205 QChar p = QLatin1Char(' '); 0206 QChar c; /* current char */ 0207 QChar n; 0208 0209 /* This is the EmoticonNode container, it will represent each matched emoticon */ 0210 typedef QPair<KEmoticonsProvider::Emoticon, int> EmoticonNode; 0211 QList<EmoticonNode> foundEmoticons; 0212 /* First-pass, store the matched emoticon locations in foundEmoticons */ 0213 QList<KEmoticonsProvider::Emoticon> emoticonList; 0214 QList<KEmoticonsProvider::Emoticon>::const_iterator it; 0215 int pos; 0216 0217 bool inHTMLTag = false; 0218 bool inHTMLLink = false; 0219 bool inHTMLEntity = false; 0220 QString needle; // search for this 0221 0222 for (pos = 0; pos < message.length(); ++pos) { 0223 c = message[pos]; 0224 0225 if (mode & SkipHTML) { // Shall we skip HTML ? 0226 if (!inHTMLTag) { // Are we already in an HTML tag ? 0227 if (c == QLatin1Char('<')) { // If not check if are going into one 0228 inHTMLTag = true; // If we are, change the state to inHTML 0229 p = c; 0230 continue; 0231 } 0232 } else { // We are already in a HTML tag 0233 if (c == QLatin1Char('>')) { // Check if it ends 0234 inHTMLTag = false; // If so, change the state 0235 0236 if (p == QLatin1Char('a')) { 0237 inHTMLLink = false; 0238 } 0239 } else if (c == QLatin1Char('a') && p == QLatin1Char('<')) { // check if we just entered an anchor tag 0240 inHTMLLink = true; // don't put smileys in urls 0241 } 0242 p = c; 0243 continue; 0244 } 0245 0246 if (!inHTMLEntity) { // are we 0247 if (c == QLatin1Char('&')) { 0248 inHTMLEntity = true; 0249 } 0250 } 0251 } 0252 0253 if (inHTMLLink) { // i can't think of any situation where a link address might need emoticons 0254 p = c; 0255 continue; 0256 } 0257 0258 if ((mode & StrictParse) && !p.isSpace() && p != QLatin1Char('>')) { // '>' may mark the end of an html tag 0259 p = c; 0260 continue; 0261 } /* strict requires space before the emoticon */ 0262 0263 if (d->provider->emoticonsIndex().contains(c)) { 0264 emoticonList = d->provider->emoticonsIndex().value(c); 0265 if (mode & SkipHTML) { 0266 std::sort(emoticonList.begin(), emoticonList.end(), EmoticonCompareEscaped); 0267 } else { 0268 std::sort(emoticonList.begin(), emoticonList.end(), EmoticonCompare); 0269 } 0270 bool found = false; 0271 QList<KEmoticonsProvider::Emoticon>::const_iterator end = emoticonList.constEnd(); 0272 for (it = emoticonList.constBegin(); it != end; ++it) { 0273 // If this is an HTML, then search for the HTML form of the emoticon. 0274 // For instance <o) => >o) 0275 needle = (mode & SkipHTML) ? (*it).matchTextEscaped : (*it).matchText; 0276 if (pos == message.indexOf(needle, pos)) { 0277 if (mode & StrictParse) { 0278 /* check if the character after this match is space or end of string*/ 0279 if (message.length() > pos + needle.length()) { 0280 n = message[pos + needle.length()]; 0281 //<br/> marks the end of a line 0282 if (n != QLatin1Char('<') && !n.isSpace() && !n.isNull() && n != QLatin1Char('&')) { 0283 break; 0284 } 0285 } 0286 } 0287 /* Perfect match */ 0288 foundEmoticons.append(EmoticonNode((*it), pos)); 0289 found = true; 0290 /* Skip the matched emoticon's matchText */ 0291 pos += needle.length() - 1; 0292 break; 0293 } 0294 0295 if (found) { 0296 break; 0297 } 0298 } 0299 0300 if (!found) { 0301 if (inHTMLEntity) { 0302 // If we are in an HTML entity such as > 0303 const int htmlEnd = message.indexOf(QLatin1Char(';'), pos); 0304 // Search for where it ends 0305 if (htmlEnd == -1) { 0306 // Apparently this HTML entity isn't ended, something is wrong, try skip the '&' 0307 // and continue 0308 // qCDebug(KEMOTICONS_CORE) << "Broken HTML entity, trying to recover."; 0309 inHTMLEntity = false; 0310 pos++; 0311 } else { 0312 pos = htmlEnd; 0313 inHTMLEntity = false; 0314 } 0315 } 0316 } 0317 } /* else no emoticons begin with this character, so don't do anything */ 0318 p = c; 0319 } 0320 0321 /* if no emoticons found just return the text */ 0322 if (foundEmoticons.isEmpty()) { 0323 result.append(Token(Text, message)); 0324 return result; 0325 } 0326 0327 /* Second-pass, generate tokens based on the matches */ 0328 0329 pos = 0; 0330 int length; 0331 0332 for (int i = 0, total = foundEmoticons.size(); i < total; ++i) { 0333 EmoticonNode itFound = foundEmoticons.at(i); 0334 needle = (mode & SkipHTML) ? itFound.first.matchTextEscaped : itFound.first.matchText; 0335 0336 if ((length = (itFound.second - pos))) { 0337 result.append(Token(Text, message.mid(pos, length))); 0338 result.append(Token(Image, itFound.first.matchTextEscaped, itFound.first.picPath, itFound.first.picHTMLCode)); 0339 pos += length + needle.length(); 0340 } else { 0341 result.append(Token(Image, itFound.first.matchTextEscaped, itFound.first.picPath, itFound.first.picHTMLCode)); 0342 pos += needle.length(); 0343 } 0344 } 0345 0346 if (message.length() - pos) { // if there is remaining regular text 0347 result.append(Token(Text, message.mid(pos))); 0348 } 0349 0350 return result; 0351 } 0352 0353 bool KEmoticonsTheme::isNull() const 0354 { 0355 return d->provider ? false : true; 0356 } 0357 0358 KEmoticonsTheme &KEmoticonsTheme::operator=(const KEmoticonsTheme &ket) 0359 { 0360 if (d == ket.d) { 0361 return *this; 0362 } 0363 0364 d = ket.d; 0365 return *this; 0366 } 0367