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) => &gt;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 &gt;
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