File indexing completed on 2024-05-26 04:59:26

0001 /*
0002     SPDX-FileCopyrightText: 2007-2009 Sergio Pistone <sergio_pistone@yahoo.com.ar>
0003     SPDX-FileCopyrightText: 2010-2022 Mladen Milinkovic <max@smoothware.net>
0004 
0005     SPDX-License-Identifier: GPL-2.0-or-later
0006 */
0007 
0008 #include "core/richstring.h"
0009 
0010 #include "helpers/common.h"
0011 
0012 #include <QColor>
0013 #include <QDebug>
0014 #include <QList>
0015 #include <QRegularExpression>
0016 #include <QStringList>
0017 #include <QVector>
0018 
0019 #include <type_traits>
0020 
0021 #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
0022 // QStringView use is unoptimized in Qt5, and some methods are missing pre 5.15
0023 #include <QStringRef>
0024 #define QStringView(x) QStringRef(&(x))
0025 #define QStringView_ QStringRef
0026 #define capturedView capturedRef
0027 #else
0028 #include <QStringView>
0029 #define QStringView_ QStringView
0030 #endif
0031 
0032 
0033 using namespace SubtitleComposer;
0034 
0035 namespace SubtitleComposer {
0036 class RichStyle {
0037 public:
0038     RichStyle() {}
0039     RichStyle(quint8 flags, QRgb color, quint64 klass, qint32 voice)
0040         : m_flags(flags), m_color(color), m_class(klass), m_voice(voice) {}
0041 
0042     inline RichString::StyleFlags flags() const { return m_flags; }
0043     inline RichString::StyleFlags & flags() { return m_flags; }
0044     inline RichString::StyleFlags flags(RichString::StyleFlag mask) const { return m_flags & mask; }
0045 
0046     inline QRgb color() const { return flags(RichString::Color) ? m_color : s_null.m_color; }
0047     inline QRgb & color() { return m_color; }
0048 
0049     inline quint64 klass() const { return m_class; }
0050     inline quint64 & klass() { return m_class; }
0051 
0052     inline qint32 voice() const { return m_voice; }
0053     inline qint32 & voice() { return m_voice; }
0054 
0055     static const RichStyle s_null;
0056 
0057     inline bool operator==(const RichStyle &other) const {
0058         return flags() == other.flags()
0059             && (!flags(RichString::Color) || color() == other.color())
0060             && (voice() == other.voice())
0061             && (klass() == other.klass());
0062     }
0063     inline bool operator!=(const RichStyle &other) { return !operator==(other); }
0064 
0065 private:
0066     RichString::StyleFlags m_flags;
0067     QRgb m_color;
0068     quint64 m_class;
0069     qint32 m_voice;
0070 };
0071 
0072 const RichStyle RichStyle::s_null(0, 0, 0, -1);
0073 
0074 class RichStringStyle {
0075     friend QDataStream & ::operator<<(QDataStream &stream, const SubtitleComposer::RichString &string);
0076     friend QDataStream & ::operator>>(QDataStream &stream, SubtitleComposer::RichString &string);
0077 
0078 public:
0079     RichStringStyle(int len);
0080     RichStringStyle(int len, quint8 styleFlags, QRgb styleColor, const QString &klass, const QString &voice);
0081     RichStringStyle(int len, quint8 styleFlags, QRgb styleColor, const QSet<QString> &classList, const QString &voice);
0082     RichStringStyle(const RichStringStyle &other);
0083     ~RichStringStyle();
0084 
0085     void clear();
0086 
0087     RichStringStyle & operator=(const RichStringStyle &other);
0088 
0089     qint32 voiceIndex(const QString &name);
0090     qint32 classIndex(const QString &name);
0091 
0092     inline QString voiceName(int index) const { return m_voiceList.at(index); }
0093     inline int voiceCount() const { return m_voiceList.size(); }
0094     inline QString className(int index) const { return m_classList.at(index); }
0095     inline int classCount() const { return m_classList.size(); }
0096 
0097     inline RichStyle * operator[](int index) { Q_ASSERT(index >= 0 && index < m_length); return &m_style[index]; }
0098     inline const RichStyle & at(int index) const { return index >= 0 && index < m_length ? m_style[index] : RichStyle::s_null; }
0099 
0100     /**
0101      * @brief Insert invalid style for len characters at index
0102      *        Use fill() or copy() to update invalid characters.
0103      * @param index
0104      * @param len
0105      */
0106     inline void insert(int index, int len) { replace(index, 0, len); }
0107     /**
0108      * @brief Remove style for len characters at index and insert invalid style for newLen characters
0109      *        Use fill() or copy() to update invalid characters.
0110      * @param index
0111      * @param len
0112      */
0113     void replace(int index, int len, int newLen);
0114 
0115     inline void fill(int index, int len, const RichStyle &style);
0116     void copy(int index, int len, const RichStringStyle &src, int srcOffset=0);
0117 
0118     void swap(RichStringStyle &other, bool swapLists);
0119 
0120     void removeUnused();
0121 
0122     inline void richText(QString &out, int prevIndex, int curIndex, bool opening);
0123 
0124 private:
0125     void detach();
0126     void updateCapacity();
0127 
0128 private:
0129     QVector<QString> m_classList;
0130     QVector<QString> m_voiceList;
0131     RichStyle *m_style;
0132     int m_length;
0133     int m_capacity;
0134 };
0135 
0136 struct ReplaceHelper {
0137     struct BackRef {
0138         int start;
0139         int end;
0140         int no;
0141     };
0142     struct MatchRef {
0143         qsizetype offset;
0144         qsizetype length;
0145         enum { NONE, SUBJECT, REPLACEMENT } ref;
0146     };
0147     using MatchRefList = QList<MatchRef>;
0148 
0149     template<class T>
0150     static MatchRefList match(RichString &str, const QString &before, const T &after, Qt::CaseSensitivity cs);
0151 
0152     template<class T>
0153     static MatchRefList match(RichString &str, const QRegularExpression &regExp, const T &replacement);
0154 
0155     template<class T>
0156     static void replace(const MatchRefList &matchList, RichString &str, const T &replacement);
0157 };
0158 }
0159 
0160 void
0161 RichStringStyle::replace(int index, int lenRemove, int lenAdd)
0162 {
0163     Q_ASSERT(index + lenRemove <= m_length);
0164     const int tailLength = m_length - index - lenRemove;
0165 
0166     RichStyle *oldStyle = m_style;
0167 
0168     detach();
0169     m_length += lenAdd - lenRemove;
0170     updateCapacity();
0171 
0172     if(index) {
0173         // restore data before inserted part
0174         memcpy(m_style, oldStyle, index * sizeof(*m_style));
0175     }
0176     if(tailLength) {
0177         // restore data after inserted part
0178         const int tailStart = index + lenAdd;
0179         const int tailStartOld = index + lenRemove;
0180         memcpy(m_style + tailStart, oldStyle + tailStartOld, tailLength * sizeof(*m_style));
0181     }
0182 
0183     delete[] oldStyle;
0184 }
0185 
0186 inline void
0187 RichStringStyle::fill(int index, int len, const RichStyle &style)
0188 {
0189     Q_ASSERT(index + len <= m_length);
0190     if(len) {
0191         RichStyle *s = m_style + index;
0192         const RichStyle *e = s + len;
0193         while(s != e)
0194             *s++ = style;
0195     }
0196 }
0197 
0198 void
0199 RichStringStyle::copy(int index, int len, const RichStringStyle &src, int srcOffset)
0200 {
0201     Q_ASSERT(index + len <= m_length);
0202     if(len <= 0)
0203         return;
0204 
0205     if(index == 0 && len == m_length) {
0206         // overwrite everything
0207         m_voiceList = src.m_voiceList;
0208         m_classList = src.m_classList;
0209         memcpy(m_style, src.m_style + srcOffset, len * sizeof(*m_style));
0210         return;
0211     }
0212 
0213     // merge styles
0214     const int nc = src.m_classList.size();
0215     int *classMap = new int[nc];
0216     for(int i = 0; i < nc; i++)
0217         classMap[i] = classIndex(src.m_classList[i]);
0218     const int nv = src.m_voiceList.size();
0219     int *voiceMap = new int[nv];
0220     for(int i = 0; i < nv; i++)
0221         voiceMap[i] = voiceIndex(src.m_voiceList[i]);
0222     for(int i = 0; i < len; i++) {
0223         const RichStyle &ss = src.m_style[srcOffset + i];
0224         quint64 klass = 0;
0225         for(int ci = 0; ci < nc; ci++) {
0226             if(classMap[ci] < 0)
0227                 continue;
0228             if(ss.klass() & (1ULL << ci))
0229                 klass |= 1ULL << quint32(classMap[ci]);
0230         }
0231         Q_ASSERT(ss.voice() < nv);
0232         qint32 voice = ss.voice() < 0 ? -1 : voiceMap[ss.voice()];
0233         m_style[index + i] = RichStyle(ss.flags(), ss.color(), klass, voice);
0234     }
0235     delete[] voiceMap;
0236     delete[] classMap;
0237 }
0238 
0239 void
0240 RichStringStyle::removeUnused()
0241 {
0242     const int nv = m_voiceList.size();
0243     int *voiceMap = new int[nv]();
0244     int voiceUsed = 0;
0245     for(int i = 0; i < m_length; i++) {
0246         const qint32 v = m_style[i].voice();
0247         if(v >= 0) {
0248             if(!voiceMap[v]) {
0249 #if QT_VERSION < QT_VERSION_CHECK(5, 14, 0)
0250                 QString *vlp = m_voiceList.data();
0251                 qSwap(vlp[v], vlp[voiceUsed]);
0252 #else
0253                 m_voiceList.swapItemsAt(v, voiceUsed);
0254 #endif
0255                 voiceMap[v] = ++voiceUsed;
0256             }
0257             m_style[i].voice() = voiceMap[v] - 1;
0258         }
0259     }
0260     m_voiceList.resize(voiceUsed);
0261 
0262     const int nc = m_classList.size();
0263     int *classMap = new int[nc]();
0264     int classUsed = 0;
0265     quint64 prevClass = 0;
0266     for(int i = 0; i < m_length; i++) {
0267         quint64 c = m_style[i].klass();
0268         if(!c)
0269             continue;
0270         if(i && c == prevClass) {
0271             m_style[i].klass() = m_style[i - 1].klass();
0272             continue;
0273         }
0274         prevClass = c;
0275         m_style[i].klass() = 0;
0276         for(int k = 0; c; k++, c >>= 1) {
0277             if(!(c & 1))
0278                 continue;
0279             if(!classMap[k]) {
0280 #if QT_VERSION < QT_VERSION_CHECK(5, 14, 0)
0281                 QString *clp = m_classList.data();
0282                 qSwap(clp[k], clp[classUsed]);
0283 #else
0284                 m_classList.swapItemsAt(k, classUsed);
0285 #endif
0286                 classMap[k] = ++classUsed;
0287             }
0288             m_style[i].klass() |= 1ULL << (classMap[k] - 1);
0289         }
0290     }
0291     m_classList.resize(classUsed);
0292 
0293     delete[] classMap;
0294     delete[] voiceMap;
0295 }
0296 
0297 RichStringStyle::RichStringStyle(int len)
0298     : m_style(nullptr),
0299       m_length(len),
0300       m_capacity(0)
0301 {
0302     if(m_length)
0303         updateCapacity();
0304 }
0305 
0306 RichStringStyle::RichStringStyle(int len, quint8 styleFlags, QRgb styleColor, const QString &klass, const QString &voice)
0307     : m_style(nullptr),
0308       m_length(len),
0309       m_capacity(0)
0310 {
0311     if(!klass.isEmpty())
0312         m_classList.append(klass);
0313     if(!voice.isEmpty())
0314         m_voiceList.append(voice);
0315     if(m_length) {
0316         updateCapacity();
0317         fill(0, m_length, RichStyle(quint8(styleFlags & RichString::AllStyles), styleColor, m_classList.size(), m_voiceList.size() - 1));
0318     }
0319 }
0320 
0321 RichStringStyle::RichStringStyle(int len, quint8 styleFlags, QRgb styleColor, const QSet<QString> &classList, const QString &voice)
0322     : m_style(nullptr),
0323       m_length(len),
0324       m_capacity(0)
0325 {
0326     quint64 classMap = 0;
0327     for(const QString &klass: classList) {
0328         m_classList.append(klass);
0329         classMap <<= 1;
0330         classMap |= 1;
0331     }
0332     if(!voice.isEmpty())
0333         m_voiceList.append(voice);
0334     if(m_length) {
0335         updateCapacity();
0336         fill(0, m_length, RichStyle(quint8(styleFlags & RichString::AllStyles), styleColor, classMap, m_voiceList.size() - 1));
0337     }
0338 }
0339 
0340 RichStringStyle::RichStringStyle(const RichStringStyle &other)
0341     : m_classList(other.m_classList),
0342       m_voiceList(other.m_voiceList),
0343       m_style(nullptr),
0344       m_length(other.m_length),
0345       m_capacity(0)
0346 {
0347     if(m_length) {
0348         updateCapacity();
0349         memcpy(m_style, other.m_style, m_length * sizeof(*m_style));
0350     }
0351 }
0352 
0353 RichStringStyle::~RichStringStyle()
0354 {
0355     delete[] m_style;
0356 }
0357 
0358 void
0359 RichStringStyle::clear()
0360 {
0361     m_classList.clear();
0362     m_voiceList.clear();
0363     m_length = 0;
0364     updateCapacity();
0365 }
0366 
0367 RichStringStyle &
0368 RichStringStyle::operator=(const RichStringStyle &other)
0369 {
0370     m_classList = other.m_classList;
0371     m_voiceList = other.m_voiceList;
0372     m_length = other.m_length;
0373     updateCapacity();
0374     if(m_length)
0375         memcpy(m_style, other.m_style, m_length * sizeof(*m_style));
0376     return *this;
0377 }
0378 
0379 qint32
0380 RichStringStyle::voiceIndex(const QString &name)
0381 {
0382     if(name.isEmpty())
0383         return -1;
0384     int i = m_voiceList.indexOf(name);
0385     if(i == -1) {
0386         i = m_voiceList.size();
0387         m_voiceList.push_back(name);
0388     }
0389     return i;
0390 }
0391 
0392 qint32
0393 RichStringStyle::classIndex(const QString &klass)
0394 {
0395     if(klass.isEmpty())
0396         return -1;
0397     int i = m_classList.indexOf(klass);
0398     if(i == -1) {
0399         i = m_classList.size();
0400         m_classList.push_back(klass);
0401     }
0402     return i;
0403 }
0404 
0405 void
0406 RichStringStyle::swap(RichStringStyle &other, bool swapLists)
0407 {
0408     if(swapLists) {
0409         qSwap(m_classList, other.m_classList);
0410         qSwap(m_voiceList, other.m_voiceList);
0411     }
0412     qSwap(m_style, other.m_style);
0413     qSwap(m_length, other.m_length);
0414     qSwap(m_capacity, other.m_capacity);
0415 }
0416 
0417 void
0418 RichStringStyle::detach()
0419 {
0420     m_style = nullptr;
0421     m_capacity = 0;
0422 }
0423 
0424 void
0425 RichStringStyle::updateCapacity()
0426 {
0427     if(m_length > m_capacity)
0428         m_capacity = m_length * 2;
0429     else if(m_length == 0)
0430         m_capacity = 0;
0431     else if(m_capacity > 100 && m_length < m_capacity / 2)
0432         m_capacity = m_capacity / 2;
0433     else if(m_style)
0434         return;
0435 
0436     delete[] m_style;
0437     m_style = m_capacity ? new RichStyle[m_capacity] : nullptr;
0438 }
0439 
0440 
0441 RichString::RichString(const QString &string, quint8 styleFlags, QRgb styleColor, const QSet<QString> &classList, const QString &voice)
0442     : QString(string),
0443       m_style(new RichStringStyle(string.length(), styleFlags, styleColor, classList, voice))
0444 {
0445 }
0446 
0447 RichString::RichString(const RichString &other)
0448     : QString(other),
0449       m_style(new RichStringStyle(*other.m_style))
0450 {
0451 }
0452 
0453 RichString &
0454 RichString::operator=(const QString &string)
0455 {
0456     if(this != &string) {
0457         QString::operator=(string);
0458         *m_style = RichStringStyle(string.size());
0459     }
0460     return *this;
0461 }
0462 
0463 RichString &
0464 RichString::operator=(const RichString &other)
0465 {
0466     if(this != &other) {
0467         QString::operator=(other);
0468         *m_style = RichStringStyle(*other.m_style);
0469     }
0470     return *this;
0471 }
0472 
0473 RichString::~RichString()
0474 {
0475     delete m_style;
0476 }
0477 
0478 void
0479 RichString::setString(const QString &string, quint8 styleFlags, QRgb styleColor, const QString klass, const QString voice)
0480 {
0481     QString::operator=(string);
0482     *m_style = RichStringStyle(string.length(), styleFlags, styleColor, klass, voice);
0483 }
0484 
0485 RichString::StyleFlags
0486 RichString::styleFlagsAt(int index) const
0487 {
0488     return m_style->at(index).flags();
0489 }
0490 
0491 void
0492 RichString::setStyleFlagsAt(int index, StyleFlags styleFlags) const
0493 {
0494     if(index < 0 || index >= length())
0495         return;
0496     (*m_style)[index]->flags() = styleFlags;
0497 }
0498 
0499 QRgb
0500 RichString::styleColorAt(int index) const
0501 {
0502     return m_style->at(index).color();
0503 }
0504 
0505 void
0506 RichString::setStyleColorAt(int index, QRgb rgbColor) const
0507 {
0508     if(index < 0 || index >= length())
0509         return;
0510     RichStyle *it = (*m_style)[index];
0511     if(rgbColor == 0)
0512         it->flags() &= ~RichString::Color;
0513     else
0514         it->flags() |= RichString::Color;
0515     it->color() = rgbColor;
0516 }
0517 
0518 QSet<QString>
0519 RichString::styleClassesAt(int index) const
0520 {
0521     QSet<QString> res;
0522     quint64 k = m_style->at(index).klass();
0523     for(int i = 0; k; k >>= 1, i++) {
0524         if(k & 1)
0525             res.insert(m_style->className(i));
0526     }
0527     return res;
0528 }
0529 
0530 void
0531 RichString::setStyleClassesAt(int index, const QSet<QString> &classes) const
0532 {
0533     quint64 k = 0;
0534     for(const QString &cl: classes) {
0535         qint32 i = m_style->classIndex(cl);
0536         if(i >= 0)
0537             k |= 1ULL << i;
0538     }
0539     (*m_style)[index]->klass() = k;
0540 }
0541 
0542 QString
0543 RichString::styleVoiceAt(int index) const
0544 {
0545     const qint32 i = m_style->at(index).voice();
0546     return i >= 0 ? m_style->voiceName(i) : QString();
0547 }
0548 
0549 void
0550 RichString::setStyleVoiceAt(int index, const QString &voice) const
0551 {
0552     const qint32 v = m_style->voiceIndex(voice);
0553     (*m_style)[index]->voice() = v;
0554 }
0555 
0556 QDataStream &
0557 operator<<(QDataStream &stream, const RichString &string)
0558 {
0559     stream << static_cast<const QString &>(string);
0560     stream.writeRawData(reinterpret_cast<const char *>(string.m_style->m_style), string.length() * sizeof(*string.m_style->m_style));
0561     stream << string.m_style->m_classList;
0562     stream << string.m_style->m_voiceList;
0563     return stream;
0564 }
0565 
0566 QDataStream &
0567 operator>>(QDataStream &stream, RichString &string)
0568 {
0569     stream >> static_cast<QString &>(string);
0570     string.m_style->m_length = string.length();
0571     string.m_style->updateCapacity();
0572     stream.readRawData(reinterpret_cast<char *>(string.m_style->m_style), string.length() * sizeof(*string.m_style->m_style));
0573     stream >> string.m_style->m_classList;
0574     stream >> string.m_style->m_voiceList;
0575     return stream;
0576 }
0577 
0578 inline void
0579 RichStringStyle::richText(QString &out, int prevIndex, int curIndex, bool opening)
0580 {
0581     const RichStyle &prev = at(prevIndex);
0582     const RichStyle &cur = at(curIndex);
0583     if(opening) {
0584         if(prev.voice() != cur.voice())
0585             out += "<v " + m_voiceList.at(cur.voice()) + ">";
0586         if(prev.klass() != cur.klass()) {
0587             quint64 k = ~prev.klass() & cur.klass();
0588             for(int i = 0; k; k >>= 1, i++) {
0589                 if(k & 1)
0590                     out += "<c." + m_classList.at(i) + ">";
0591             }
0592         }
0593         const quint8 sf = ~prev.flags() & cur.flags();
0594         if(sf & RichString::Italic)
0595             out += "<i>";
0596         if(sf & RichString::Bold)
0597             out += "<b>";
0598         if(sf & RichString::Underline)
0599             out += "<u>";
0600         if(sf & RichString::StrikeThrough)
0601             out += "<s>";
0602         if(sf & RichString::Color || (cur.flags(RichString::Color) && prev.color() != cur.color()))
0603             out += "<font color=" + QColor(cur.color()).name() + ">";
0604     } else {
0605         const quint8 sf = prev.flags() & ~cur.flags();
0606         if(sf & RichString::StrikeThrough)
0607             out += "</s>";
0608         if(sf & RichString::Underline)
0609             out += "</u>";
0610         if(sf & RichString::Bold)
0611             out += "</b>";
0612         if(sf & RichString::Italic)
0613             out += "</i>";
0614         if((sf & RichString::Color) || (prev.flags(RichString::Color) && prev.color() != cur.color()))
0615             out += "</font>";
0616         if(prev.klass() != cur.klass()) {
0617             quint64 k = prev.klass() & ~cur.klass();
0618             for(int i = 0; k; k >>= 1, i++) {
0619                 if(k & 1)
0620                     out += "</c>";
0621             }
0622         }
0623     }
0624 }
0625 
0626 RichString
0627 RichString::fromRichString(const QString &richstring)
0628 {
0629     RichString str;
0630     str.setRichString(richstring);
0631     return str;
0632 }
0633 
0634 QString
0635 RichString::richString() const
0636 {
0637     QString ret;
0638 
0639     if(isEmpty())
0640         return ret;
0641 
0642     const int len = length();
0643     int prev = 0;
0644 
0645     m_style->richText(ret, -1, prev, true);
0646 
0647     for(int cur = 1; cur < len; cur++) {
0648         if(m_style->at(prev) == m_style->at(cur))
0649             continue;
0650 
0651         // place closing html tags before spaces/newlines
0652         int cps = cur;
0653         while(cur > 0) {
0654             const QChar ch = at(cur - 1);
0655             if(ch != '\n' && ch != '\r' && ch != ' ' && ch != '\t')
0656                 break;
0657             cur--;
0658         }
0659 
0660         // text
0661         ret += QString::mid(prev, cur - prev)
0662                 .replace('<', "&lt;")
0663                 .replace('>', "&gt;");
0664 
0665         // closing tags
0666         m_style->richText(ret, prev, cps, false);
0667 
0668         // place opening html tags after spaces/newlines
0669         while(cps < len) {
0670             const QChar ch = at(cps);
0671             if(ch != '\n' && ch != '\r' && ch != ' ' && ch != '\t')
0672                 break;
0673             cps++;
0674         }
0675 
0676         // spaces
0677         ret += QStringView(*this).mid(cur, cps - cur);
0678         cur = cps;
0679 
0680         // opening tags
0681         m_style->richText(ret, prev, cur, true);
0682 
0683         prev = cur;
0684     }
0685     if(prev != length()) {
0686         ret += QString::mid(prev, length() - prev)
0687             .replace('<', "&lt;")
0688             .replace('>', "&gt;");
0689         m_style->richText(ret, prev, -1, false);
0690     }
0691     return ret;
0692 }
0693 
0694 RichString &
0695 RichString::setRichString(const QStringView_ &string)
0696 {
0697     staticRE$(tagRegExp, "(<"
0698             "(/?(v |c\\.?|\\w+))"
0699             "([^>]*\\bcolor=\"?([\\w#]+)\"?|[^>]+)?"
0700             "[^>]*?>|&([^;]+);|\\n)", REu | REi);
0701     staticRE$(colorRegExp, "style=\"[^\">]*\\bcolor:([\\w#]+)", REu | REi);
0702 
0703     QRegularExpressionMatchIterator it = tagRegExp.globalMatch(string);
0704 
0705     clear();
0706 
0707     QStringList colorTags;
0708     bool softBreak = false;
0709     quint8 currentStyle = 0;
0710     QColor currentColor;
0711     int offsetPos = 0, matchedPos;
0712     quint32 currentClass = 0;
0713     qint32 currentVoice = -1;
0714     QVector<int> openClass;
0715     for(;;) {
0716         quint8 newStyle = currentStyle;
0717         QColor newColor(currentColor);
0718         quint32 newClass = currentClass;
0719         qint32 newVoice = currentVoice;
0720 
0721         const bool validIter = it.hasNext();
0722         QRegularExpressionMatch m;
0723 
0724         QString mTag;
0725         QStringView ent;
0726 
0727         if(validIter) {
0728             m = it.next();
0729 
0730             matchedPos = m.capturedStart();
0731             ent = m.capturedView(6);
0732             if(ent.isNull()) {
0733                 mTag = m.captured(2).toLower();
0734                 if(mTag == QLatin1String("b") || mTag == QLatin1String("strong")) {
0735                     newStyle |= RichString::Bold;
0736                 } else if(mTag == QLatin1String("i") || mTag == QLatin1String("em")) {
0737                     newStyle |= RichString::Italic;
0738                 } else if(mTag == QLatin1String("u")) {
0739                     newStyle |= RichString::Underline;
0740                 } else if(mTag == QLatin1String("s")) {
0741                     newStyle |= RichString::StrikeThrough;
0742                 } else if(mTag == QLatin1String("font")) {
0743                     const QString &color = m.captured(5);
0744                     if(!color.isEmpty()) {
0745                         newStyle |= RichString::Color;
0746                         newColor.setNamedColor(color.toLower());
0747                         colorTags.push_back(currentColor.name());
0748                         colorTags.push_back(mTag);
0749                     }
0750                 } else if(mTag == QLatin1String("v ")) {
0751                     newVoice = m_style->voiceIndex(m.captured(4));
0752                 } else if(mTag == QLatin1String("c.") || mTag == QLatin1String("c")) {
0753                     const int i = m_style->classIndex(m.captured(4));
0754                     if(i >= 0)
0755                         newClass |= 1UL << i;
0756                     openClass.push_back(i);
0757                 } else if(mTag == QLatin1String("/b") || mTag == QLatin1String("/strong")) {
0758                     newStyle &= ~RichString::Bold;
0759                 } else if(mTag == QLatin1String("/i") || mTag == QLatin1String("/em")) {
0760                     newStyle &= ~RichString::Italic;
0761                 } else if(mTag == QLatin1String("/u")) {
0762                     newStyle &= ~RichString::Underline;
0763                 } else if(mTag == QLatin1String("/s")) {
0764                     newStyle &= ~RichString::StrikeThrough;
0765                 } else if(mTag == QLatin1String("/c")) {
0766                     const int i = openClass.back();
0767                     openClass.pop_back();
0768                     if(i >= 0)
0769                         newClass &= ~(1UL << i);
0770                 } else if(mTag == QLatin1String("/c.")) {
0771                     const int i = m_style->classIndex(m.captured(4));
0772                     if(i >= 0)
0773                         newClass &= ~(1UL << i);
0774                     for(auto it = openClass.rbegin(); it != openClass.rend(); ++it) {
0775                         if(*it == i) {
0776                             openClass.erase((++it).base());
0777                             break;
0778                         }
0779                     }
0780                 }
0781 
0782                 if(!mTag.isEmpty()) {
0783                     if(mTag.front() != QLatin1Char('/')) {
0784                         QRegularExpressionMatch mc = colorRegExp.match(m.capturedView(4));
0785                         if(mc.hasMatch()) {
0786                             newStyle |= RichString::Color;
0787                             newColor.setNamedColor(mc.captured(1).toLower());
0788                             colorTags.push_back(currentColor.name());
0789                             colorTags.push_back(mTag);
0790                         }
0791                     } else if(!colorTags.empty() && QStringView(mTag).mid(1) == colorTags.back()) {
0792                         colorTags.pop_back();
0793                         if(colorTags.size() == 1) {
0794                             newStyle &= ~RichString::Color;
0795                             newColor.setNamedColor("-invalid-");
0796                         } else {
0797                             newStyle |= RichString::Color;
0798                             newColor.setNamedColor(colorTags.back());
0799                         }
0800                         colorTags.pop_back();
0801                     }
0802                 }
0803             }
0804         } else {
0805             matchedPos = string.length();
0806         }
0807 
0808         if(const int len = matchedPos - offsetPos) {
0809             if(softBreak) {
0810                 if(!isEmpty() && QString::back() != QChar::LineFeed)
0811                     append(QChar::LineFeed);
0812                 softBreak = false;
0813             }
0814             const int index = length();
0815             QString::append(string.mid(offsetPos, len));
0816             m_style->insert(index, len);
0817             m_style->fill(index, len, RichStyle(currentStyle, currentColor.isValid() ? currentColor.rgb() : 0, currentClass, currentVoice));
0818         }
0819 
0820         currentStyle = newStyle;
0821         currentColor = newColor;
0822         currentClass = newClass;
0823         currentVoice = newVoice;
0824 
0825         if(validIter) {
0826             if(!ent.isNull()) {
0827                 if(ent == QLatin1String("nbsp")) {
0828                     append(QChar::Nbsp);
0829                 } else if(ent == QLatin1String("lt")) {
0830                     append(QLatin1Char('<'));
0831                 } else if(ent == QLatin1String("gt")) {
0832                     append(QLatin1Char('>'));
0833                 } else if(ent == QLatin1String("amp")) {
0834                     append(QLatin1Char('&'));
0835                 } else if(ent == QLatin1String("quot")) {
0836                     append(QLatin1Char('"'));
0837                 } else {
0838                     qWarning().nospace().noquote() << "Unknown entity \"&" << ent << ";\"";
0839                     append(ent.toString());
0840                 }
0841             } else {
0842                 if(m.capturedView(1).front() == QChar::LineFeed) {
0843                     softBreak = true;
0844                 } else if(mTag == QLatin1String("br")) {
0845                     append(QChar::LineFeed);
0846                     softBreak = false;
0847                 } else if(mTag == QLatin1String("p")) {
0848                     if(!isEmpty() && QString::back() != QChar::LineFeed) {
0849                         append(QChar::LineFeed);
0850                         softBreak = false;
0851                     }
0852                 } else if(mTag == QLatin1String("/p")) {
0853                     softBreak = true;
0854                 }
0855             }
0856         } else {
0857             break;
0858         }
0859 
0860         offsetPos = m.capturedEnd();
0861     }
0862 
0863     return *this;
0864 }
0865 
0866 RichString::StyleFlags
0867 RichString::cummulativeStyleFlags() const
0868 {
0869     quint8 cummulativeStyleFlags = 0;
0870     for(int i = 0, size = length(); i < size; i++) {
0871         cummulativeStyleFlags |= m_style->at(i).flags();
0872         if(cummulativeStyleFlags == AllStyles)
0873             break;
0874     }
0875     return cummulativeStyleFlags;
0876 }
0877 
0878 bool
0879 RichString::hasStyleFlags(StyleFlags styleFlags) const
0880 {
0881     StyleFlags cummulativeStyleFlags = 0;
0882     for(int i = 0, size = length(); i < size; i++) {
0883         cummulativeStyleFlags |= m_style->at(i).flags();
0884         if((cummulativeStyleFlags & styleFlags) == styleFlags)
0885             return true;
0886     }
0887     return false;
0888 }
0889 
0890 RichString &
0891 RichString::setStyleFlags(int index, int len, StyleFlags styleFlags)
0892 {
0893     if(index < 0 || index >= length())
0894         return *this;
0895 
0896     for(int end = index + length(index, len); index < end; index++)
0897         (*m_style)[index]->flags() = styleFlags;
0898 
0899     return *this;
0900 }
0901 
0902 RichString &
0903 RichString::setStyleFlags(int index, int len, StyleFlags styleFlags, bool on)
0904 {
0905     if(index < 0 || index >= length())
0906         return *this;
0907 
0908     const int end = index + length(index, len);
0909     if(on) {
0910         for(; index < end; index++)
0911             (*m_style)[index]->flags() |= styleFlags;
0912     } else {
0913         styleFlags = ~styleFlags;
0914         for(; index < end; index++)
0915             (*m_style)[index]->flags() &= styleFlags;
0916     }
0917 
0918     return *this;
0919 }
0920 
0921 QSet<QRgb>
0922 RichString::cummulativeColors() const
0923 {
0924     QSet<QRgb> res;
0925     for(int i = 0, n = length(); i < n; i++)
0926         res.insert(m_style->at(i).color());
0927     return res;
0928 }
0929 
0930 QSet<QString>
0931 RichString::cummulativeClasses() const
0932 {
0933     QSet<QString> res;
0934     for(int i = 0, n = m_style->classCount(); i < n; i++)
0935         res.insert(m_style->className(i));
0936     return res;
0937 }
0938 
0939 QSet<QString>
0940 RichString::cummulativeVoices() const
0941 {
0942     QSet<QString> res;
0943     for(int i = 0, n = m_style->voiceCount(); i < n; i++)
0944         res.insert(m_style->voiceName(i));
0945     return res;
0946 }
0947 
0948 
0949 RichString &
0950 RichString::setStyleColor(int index, int len, QRgb color)
0951 {
0952     if(index < 0 || index >= length())
0953         return *this;
0954 
0955     for(int end = index + length(index, len); index < end; index++) {
0956         (*m_style)[index]->color() = color;
0957         if(color)
0958             (*m_style)[index]->flags() |= Color;
0959         else
0960             (*m_style)[index]->flags() &= ~Color;
0961     }
0962 
0963     return *this;
0964 }
0965 
0966 void
0967 RichString::clear()
0968 {
0969     QString::clear();
0970     m_style->clear();
0971 }
0972 
0973 RichString &
0974 RichString::insert(int index, QChar ch)
0975 {
0976     if(index >= 0 && index <= length()) {
0977         const RichStyle cs = m_style->at(qMax(0, index - 1));
0978         QString::insert(index, ch);
0979         m_style->insert(index, 1);
0980         m_style->fill(index, 1, cs);
0981     }
0982 
0983     return *this;
0984 }
0985 
0986 RichString &
0987 RichString::insert(int index, const QString &str)
0988 {
0989     if(!length()) {
0990         setString(str);
0991         return *this;
0992     }
0993 
0994     if(str.length() && index >= 0 && index <= length()) {
0995         const RichStyle cs = m_style->at(qMax(0, index - 1));
0996         QString::insert(index, str);
0997         m_style->insert(index, str.length());
0998         m_style->fill(index, str.length(), cs);
0999     }
1000 
1001     return *this;
1002 }
1003 
1004 RichString &
1005 RichString::insert(int index, const RichString &str)
1006 {
1007     if(!length()) {
1008         *this = str;
1009         return *this;
1010     }
1011 
1012     if(str.length() && index >= 0 && index <= length()) {
1013         QString::insert(index, str);
1014         m_style->insert(index, str.length());
1015         m_style->copy(index, str.length(), *str.m_style);
1016     }
1017 
1018     return *this;
1019 }
1020 
1021 RichString &
1022 RichString::replace(int index, int len, const QString &replacement)
1023 {
1024     if(index < 0 || index >= length())
1025         return *this;
1026 
1027     len = length(index, len);
1028 
1029     if(!len && !replacement.length())
1030         return *this; // nothing to do
1031 
1032     const RichStyle cs = m_style->at(index);
1033 
1034     QString::replace(index, len, replacement);
1035 
1036     if(len != replacement.length())
1037         m_style->replace(index, len, replacement.length());
1038     else if(len == 1)
1039         return *this; // there's no need to change the styles (char substitution)
1040     m_style->fill(index, replacement.length(), cs);
1041     if(len)
1042         m_style->removeUnused();
1043 
1044     return *this;
1045 }
1046 
1047 RichString &
1048 RichString::replace(int index, int len, const RichString &replacement)
1049 {
1050     if(index < 0 || index >= length())
1051         return *this;
1052 
1053     len = length(index, len);
1054 
1055     if(!len && !replacement.length())
1056         return *this; // nothing to do
1057 
1058     QString::replace(index, len, replacement);
1059 
1060     if(len != replacement.length())
1061         m_style->replace(index, len, replacement.length());
1062     m_style->copy(index, replacement.length(), *replacement.m_style);
1063     if(len)
1064         m_style->removeUnused();
1065 
1066     return *this;
1067 }
1068 
1069 RichString &
1070 RichString::replace(const QString &before, const QString &after, Qt::CaseSensitivity cs)
1071 {
1072     if(before.isEmpty() && after.isEmpty())
1073         return *this;
1074 
1075     if(before.length() == 1 && after.length() == 1) {
1076         // there's no need to change the styles (char substitution)
1077         QString::replace(before, after);
1078         return *this;
1079     }
1080 
1081     const ReplaceHelper::MatchRefList matchList = ReplaceHelper::match(*this, before, after, cs);
1082     if(!matchList.empty())
1083         ReplaceHelper::replace(matchList, *this, after);
1084 
1085     return *this;
1086 }
1087 
1088 RichString &
1089 RichString::replace(const QString &before, const RichString &after, Qt::CaseSensitivity cs)
1090 {
1091     if(before.isEmpty() && after.isEmpty())
1092         return *this;
1093 
1094     const ReplaceHelper::MatchRefList matchList = ReplaceHelper::match(*this, before, after, cs);
1095     if(!matchList.empty())
1096         ReplaceHelper::replace(matchList, *this, after);
1097 
1098     return *this;
1099 }
1100 
1101 RichString &
1102 RichString::replace(QChar before, QChar after, Qt::CaseSensitivity cs)
1103 {
1104     QString::replace(before, after, cs);
1105     return *this;
1106 }
1107 
1108 RichString &
1109 RichString::replace(QChar ch, const QString &after, Qt::CaseSensitivity cs)
1110 {
1111     if(after.length() == 1) {
1112         // there's no need to change the styles (char substitution)
1113         QString::replace(ch, after.at(0));
1114         return *this;
1115     }
1116 
1117     const ReplaceHelper::MatchRefList matchList = ReplaceHelper::match(*this, ch, after, cs);
1118     if(!matchList.empty())
1119         ReplaceHelper::replace(matchList, *this, after);
1120 
1121     return *this;
1122 }
1123 
1124 RichString &
1125 RichString::replace(QChar ch, const RichString &after, Qt::CaseSensitivity cs)
1126 {
1127     const ReplaceHelper::MatchRefList matchList = ReplaceHelper::match(*this, ch, after, cs);
1128     if(!matchList.empty())
1129         ReplaceHelper::replace(matchList, *this, after);
1130     return *this;
1131 }
1132 
1133 RichString &
1134 RichString::replace(const QRegularExpression &regExp, const QString &replacement)
1135 {
1136     const ReplaceHelper::MatchRefList matchList = ReplaceHelper::match(*this, regExp, replacement);
1137     if(!matchList.empty())
1138         ReplaceHelper::replace(matchList, *this, replacement);
1139     return *this;
1140 }
1141 
1142 RichString &
1143 RichString::replace(const QRegularExpression &regExp, const RichString &replacement)
1144 {
1145     const ReplaceHelper::MatchRefList matchList = ReplaceHelper::match(*this, regExp, replacement);
1146     if(!matchList.empty())
1147         ReplaceHelper::replace(matchList, *this, replacement);
1148     return *this;
1149 }
1150 
1151 RichStringList
1152 RichString::split(const QString &sep, Qt::SplitBehaviorFlags behavior, Qt::CaseSensitivity cs) const
1153 {
1154     RichStringList ret;
1155 
1156     if(sep.length()) {
1157         int off = 0;
1158         for(;;) {
1159             const int matchedIndex = indexOf(sep, off, cs);
1160             if(matchedIndex == -1)
1161                 break;
1162             if(behavior == Qt::KeepEmptyParts || matchedIndex != off)
1163                 ret << mid(off, matchedIndex - off);
1164             off = matchedIndex + sep.length();
1165         }
1166         if(behavior == Qt::KeepEmptyParts || off < length() - 1)
1167             ret << mid(off);
1168     } else if(behavior == Qt::KeepEmptyParts || length()) {
1169         ret << *this;
1170     }
1171 
1172     return ret;
1173 }
1174 
1175 RichStringList
1176 RichString::split(const QChar &sep, Qt::SplitBehaviorFlags behavior, Qt::CaseSensitivity cs) const
1177 {
1178     RichStringList ret;
1179 
1180     int off = 0;
1181     for(;;) {
1182         const int matchedIndex = indexOf(sep, off, cs);
1183         if(matchedIndex == -1)
1184             break;
1185         if(behavior == Qt::KeepEmptyParts || matchedIndex != off)
1186             ret << mid(off, matchedIndex - off);
1187         off = matchedIndex + 1;
1188     }
1189     if(behavior == Qt::KeepEmptyParts || off < length() - 1)
1190         ret << mid(off);
1191 
1192     return ret;
1193 }
1194 
1195 RichStringList
1196 RichString::split(const QRegularExpression &sep, Qt::SplitBehaviorFlags behavior) const
1197 {
1198     RichStringList ret;
1199 
1200     int off = 0;
1201     QRegularExpressionMatchIterator iterator = sep.globalMatch(*this);
1202     while(iterator.hasNext()) {
1203         QRegularExpressionMatch match = iterator.next();
1204         const int matchedIndex = match.capturedStart();
1205         if(matchedIndex == -1)
1206             break;
1207         if(behavior == Qt::KeepEmptyParts || matchedIndex != off)
1208             ret << mid(off, matchedIndex - off);
1209         off = matchedIndex + match.capturedLength();
1210     }
1211     if(behavior == Qt::KeepEmptyParts || off < length() - 1)
1212         ret << mid(off);
1213 
1214     return ret;
1215 }
1216 
1217 RichString
1218 RichString::left(int len) const
1219 {
1220     len = length(0, len);
1221     RichString ret;
1222     ret.operator=(QString::left(len));
1223     ret.m_style->copy(0, len, *m_style, 0);
1224     return ret;
1225 }
1226 
1227 RichString
1228 RichString::right(int len) const
1229 {
1230     len = length(0, len);
1231     RichString ret;
1232     ret.operator=(QString::right(len));
1233     ret.m_style->copy(0, len, *m_style, length() - len);
1234     return ret;
1235 }
1236 
1237 RichString
1238 RichString::mid(int index, int len) const
1239 {
1240     if(index < 0) {
1241         if(len >= 0)
1242             len += index;
1243         index = 0;
1244     }
1245 
1246     if(index >= (int)length())
1247         return RichString();
1248 
1249     len = length(index, len);
1250     RichString ret;
1251     ret.operator=(QString::mid(index, len));
1252     ret.m_style->copy(0, len, *m_style, index);
1253     return ret;
1254 }
1255 
1256 RichString
1257 RichString::toLower() const
1258 {
1259     RichString ret(*this);
1260     ret.operator=(QString::toLower());
1261     return ret;
1262 }
1263 
1264 RichString
1265 RichString::toUpper() const
1266 {
1267     RichString ret(*this);
1268     ret.operator=(QString::toUpper());
1269     return ret;
1270 }
1271 
1272 RichString
1273 RichString::toTitleCase(bool lowerFirst) const
1274 {
1275     const QString wordSeparators($(" -_([:,;./\\\t\n\""));
1276 
1277     RichString ret(*this);
1278 
1279     if(lowerFirst)
1280         ret.operator=(QString::toLower());
1281 
1282     bool wordStart = true;
1283     for(QChar *chr = ret.data(); *chr != 0; chr++) {
1284         if(wordStart) {
1285             if(!wordSeparators.contains(*chr)) {
1286                 wordStart = false;
1287                 *chr = chr->toUpper();
1288             }
1289         } else if(wordSeparators.contains(*chr)) {
1290             wordStart = true;
1291         }
1292     }
1293 
1294     return ret;
1295 }
1296 
1297 RichString
1298 RichString::toSentenceCase(bool lowerFirst, bool *cont) const
1299 {
1300     const QString sentenceEndChars(".?!");
1301 
1302     RichString ret(*this);
1303 
1304     if(lowerFirst)
1305         ret.operator=(QString::toLower());
1306 
1307     if(isEmpty())
1308         return ret;
1309 
1310     uint prevDots = 0;
1311     bool startSentence = cont ? !*cont : true;
1312 
1313 
1314     for(QChar *chr = ret.data(); *chr != 0; chr++) {
1315         if(sentenceEndChars.contains(*chr)) {
1316             if(*chr == '.') {
1317                 prevDots++;
1318                 startSentence = prevDots < 3;
1319             } else {
1320                 prevDots = 0;
1321                 startSentence = true;
1322             }
1323         } else {
1324             if(startSentence && chr->isLetterOrNumber()) {
1325                 *chr = chr->toUpper();
1326                 startSentence = false;
1327             }
1328 
1329             if(!chr->isSpace())
1330                 prevDots = 0;
1331         }
1332     }
1333 
1334     if(cont)
1335         *cont = prevDots != 1 && !startSentence;
1336 
1337     return ret;
1338 }
1339 
1340 RichString
1341 RichString::simplified() const
1342 {
1343     staticRE$(simplifySpaceRegExp, "\\s{2,MAXINT}", REu);
1344 
1345     return trimmed().replace(simplifySpaceRegExp, " ");
1346 }
1347 
1348 RichString
1349 RichString::trimmed() const
1350 {
1351     staticRE$(trimRegExp, "(^\\s+|\\s+$)", REu);
1352 
1353     return RichString(*this).remove(trimRegExp);
1354 }
1355 
1356 void
1357 RichString::simplifyWhiteSpace(QString &text)
1358 {
1359     int di = 0;
1360     bool lastWasSpace = true;
1361     bool lastWasLineFeed = true;
1362     for(int i = 0, l = text.size(); i < l; i++) {
1363         const QChar ch = text.at(i);
1364         if(lastWasSpace && (ch == QChar::Space || ch == QChar::Tabulation)) // skip consecutive spaces
1365             continue;
1366         if(lastWasLineFeed && (ch == QChar::LineFeed || ch == QChar::CarriageReturn)) // skip consecutive newlines
1367             continue;
1368         if(lastWasSpace && (ch == QChar::LineFeed || ch == QChar::CarriageReturn)) // skip space before newline
1369             di--;
1370 
1371         if(ch == QChar::Tabulation) // convert tab to space
1372             text[di] = QChar::Space;
1373         else if(ch == QChar::CarriageReturn) // convert cr to lf
1374             text[di] = QChar::LineFeed;
1375         else if(di != i) // copy other chars
1376             text[di] = ch;
1377 
1378         lastWasLineFeed = text[di] == QChar::LineFeed;
1379         lastWasSpace = lastWasLineFeed || text[di] == QChar::Space;
1380 
1381         di++;
1382     }
1383     if(lastWasLineFeed)
1384         di--;
1385     text.truncate(di);
1386 }
1387 
1388 void
1389 RichString::simplifyWhiteSpace()
1390 {
1391     int di = 0;
1392     bool lastWasSpace = true;
1393     bool lastWasLineFeed = true;
1394     for(int i = 0, l = size(); i < l; i++) {
1395         const QChar ch = at(i);
1396         if(lastWasSpace && (ch == QChar::Space || ch == QChar::Tabulation)) // skip consecutive spaces
1397             continue;
1398         if(lastWasLineFeed && (ch == QChar::LineFeed || ch == QChar::CarriageReturn)) // skip consecutive newlines
1399             continue;
1400         if(lastWasSpace && (ch == QChar::LineFeed || ch == QChar::CarriageReturn)) // skip space before newline
1401             di--;
1402 
1403         if(ch == QChar::Tabulation) // convert tab to space
1404             operator[](di) = QChar::Space;
1405         else if(ch == QChar::CarriageReturn) // convert cr to lf
1406             operator[](di) = QChar::LineFeed;
1407         else if(di != i) // copy other chars
1408             operator[](di) = ch;
1409 
1410         if(di != i)
1411             m_style->copy(di, 1, *m_style, i);
1412 
1413         lastWasLineFeed = at(di) == QChar::LineFeed;
1414         lastWasSpace = lastWasLineFeed || at(di) == QChar::Space;
1415 
1416         di++;
1417     }
1418     if(lastWasLineFeed)
1419         di--;
1420     truncate(di);
1421 }
1422 
1423 bool
1424 RichString::operator!=(const RichString &richstring) const
1425 {
1426     if(!(static_cast<const QString &>(*this) == static_cast<const QString &>(richstring)))
1427         return true;
1428 
1429     for(int i = 0, sz = length(); i < sz; i++) {
1430         const RichStyle &s1 = m_style->at(i);
1431         const RichStyle &s2 = richstring.m_style->at(i);
1432         if(s1.flags() != s2.flags())
1433             return true;
1434         if(s1.flags(Color) && s1.color() != s2.color())
1435             return true;
1436     }
1437 
1438     return false;
1439 }
1440 
1441 RichStringList::RichStringList()
1442 {}
1443 
1444 RichStringList::RichStringList(const RichString &str)
1445 {
1446     append(str);
1447 }
1448 
1449 RichStringList::RichStringList(const RichStringList &list) :
1450     QList<RichString>(list)
1451 {}
1452 
1453 RichStringList::RichStringList(const QList<RichString> &list) :
1454     QList<RichString>(list)
1455 {}
1456 
1457 RichStringList::RichStringList(const QStringList &list)
1458 {
1459     for(QStringList::const_iterator it = list.cbegin(), end = list.cend(); it != end; ++it)
1460         append(RichString(*it));
1461 }
1462 
1463 #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
1464 RichStringList::RichStringList(const QList<QString> &list)
1465 {
1466     for(QList<QString>::const_iterator it = list.cbegin(), end = list.cend(); it != end; ++it)
1467         append(RichString(*it));
1468 }
1469 #endif
1470 
1471 RichString
1472 RichStringList::join(const RichString &sep) const
1473 {
1474     RichString ret;
1475 
1476     bool skipSeparator = true;
1477     for(RichStringList::ConstIterator it = begin(), end = this->end(); it != end; ++it) {
1478         if(skipSeparator) {
1479             ret += *it;
1480             skipSeparator = false;
1481             continue;
1482         }
1483         ret += sep;
1484         ret += *it;
1485     }
1486 
1487     return ret;
1488 }
1489 
1490 
1491 
1492 // SSHelper - templated replacements
1493 
1494 template<class T>
1495 ReplaceHelper::MatchRefList
1496 ReplaceHelper::match(RichString &str, const QString &before, const T &after, Qt::CaseSensitivity cs)
1497 {
1498     MatchRefList matchList;
1499     int newLength = str.length();
1500     bool matched = false;
1501     if(before.isEmpty()) {
1502         // before is empty - do what QString does
1503         for(int i = 0; i < str.length(); i++) {
1504             if(!after.isEmpty())
1505                 matchList.push_back(MatchRef{0, after.length(), MatchRef::REPLACEMENT});
1506             matchList.push_back(MatchRef{i, 1, MatchRef::SUBJECT});
1507         }
1508         if(!after.isEmpty()) {
1509             matchList.push_back(MatchRef{0, after.length(), MatchRef::REPLACEMENT});
1510             newLength += (str.length() + 1) * after.length();
1511         }
1512     } else {
1513         const int newLengthStep = after.length() - before.length();
1514         int off = 0;
1515         int len;
1516         for(;;) {
1517             const int idx = str.indexOf(before, off, cs);
1518             if(idx == -1)
1519                 break;
1520             matched = true;
1521             newLength += newLengthStep;
1522             // subject part before the match
1523             if((len = idx - off))
1524                 matchList.push_back(MatchRef{off, len, MatchRef::SUBJECT});
1525             // replacement
1526             if(!after.isEmpty())
1527                 matchList.push_back(MatchRef{0, after.length(), MatchRef::REPLACEMENT});
1528             off = idx + before.length();
1529         }
1530         // subject part after all matches
1531         if((len = str.length() - off))
1532             matchList.push_back(MatchRef{off, len, MatchRef::SUBJECT});
1533     }
1534     // old/new total lengths
1535     if(matched || !matchList.empty())
1536         matchList.push_back(MatchRef{str.length(), newLength, MatchRef::NONE});
1537     return matchList;
1538 }
1539 
1540 template<class T>
1541 ReplaceHelper::MatchRefList
1542 ReplaceHelper::match(RichString &str, const QRegularExpression &regExp, const T &replacement)
1543 {
1544     MatchRefList matchList;
1545     if(!regExp.isValid()) {
1546         qWarning()
1547             << "SSHelper::match(): invalid regular expression at character " << regExp.patternErrorOffset() << ":\n\t"
1548             << regExp.pattern() << "\n\t" << regExp.errorString();
1549         return matchList;
1550     }
1551 
1552     // prepare backreference offset list
1553     QVector<BackRef> backRefs;
1554     const int capCount = regExp.captureCount();
1555     backRefs.reserve(capCount);
1556     const QChar *repChar = replacement.unicode();
1557     const QChar *repEnd = repChar + replacement.size();
1558     while(repChar != repEnd) {
1559         if(*repChar++ != QLatin1Char('\\'))
1560             continue;
1561         int no = repChar->digitValue();
1562         repChar++;
1563         if(no >= 0 && no <= capCount) {
1564             const int start = int(repChar - replacement.unicode()) - 2;
1565             if(repChar != repEnd) {
1566                 const int secondDigit = repChar->digitValue();
1567                 const int nn = (no * 10) + secondDigit;
1568                 if(secondDigit != -1 && nn <= capCount) {
1569                     no = nn;
1570                     repChar++;
1571                 }
1572             }
1573             backRefs.push_back(BackRef{start, int(repChar - replacement.unicode()), no});
1574         }
1575     }
1576 
1577     // handle matches
1578     int matchOffset = 0;
1579     int newLength = 0;
1580     int len;
1581     QRegularExpressionMatchIterator iterator = regExp.globalMatch(str);
1582     const bool matched = iterator.hasNext();
1583     while(iterator.hasNext()) {
1584         QRegularExpressionMatch match = iterator.next();
1585 
1586         // subject part before the match
1587         if((len = match.capturedStart() - matchOffset)) {
1588             matchList.push_back(MatchRef{matchOffset, len, MatchRef::SUBJECT});
1589             newLength += len;
1590         }
1591 
1592         int replacementOffset = 0;
1593         for(const BackRef &backRef: qAsConst(backRefs)) {
1594             // replacement before backref
1595             if((len = backRef.start - replacementOffset)) {
1596                 matchList.push_back(MatchRef{replacementOffset, len, MatchRef::REPLACEMENT});
1597                 newLength += len;
1598             }
1599 
1600             // subject part that backref points to
1601             if((len = match.capturedLength(backRef.no))) {
1602                 matchList.push_back(MatchRef{match.capturedStart(backRef.no), len, MatchRef::SUBJECT});
1603                 newLength += len;
1604             }
1605 
1606             replacementOffset = backRef.end;
1607         }
1608 
1609         // remainging replacement
1610         if((len = replacement.length() - replacementOffset)) {
1611             matchList.push_back(MatchRef{replacementOffset, len, MatchRef::REPLACEMENT});
1612             newLength += len;
1613         }
1614 
1615         matchOffset = match.capturedEnd();
1616 //      if(match.capturedLength() == 0) // TODO: TEST THIS
1617 //          matchOffset++;
1618     }
1619     // subject part after all matches
1620     if((len = str.length() - matchOffset) > 0) {
1621         matchList.push_back(MatchRef{matchOffset, len, MatchRef::SUBJECT});
1622         newLength += len;
1623     }
1624     // old/new total lengths
1625     if(matched || !matchList.empty())
1626         matchList.push_back(MatchRef{str.length(), newLength, MatchRef::NONE});
1627 
1628     return matchList;
1629 }
1630 
1631 template<class T>
1632 void
1633 ReplaceHelper::replace(const MatchRefList &matchList, RichString &str, const T &replacement)
1634 {
1635     const int newLength = matchList.back().length; // last entry contains total lengths
1636     QString newString;
1637     newString.reserve(newLength);
1638     RichStringStyle newStyle(newLength);
1639     int startNew = 0;
1640     int strStyleOffset = -1;
1641     for(const MatchRef &md: matchList) {
1642         if(!md.length)
1643             continue;
1644         if(md.ref == md.SUBJECT) {
1645             newString.append(QStringView(str).mid(md.offset, md.length));
1646             newStyle.copy(startNew, md.length, *str.m_style, md.offset);
1647             startNew += md.length;
1648             strStyleOffset = md.offset + md.length;
1649         } else if(md.ref == md.REPLACEMENT) {
1650             newString.append(QStringView(replacement).mid(md.offset, md.length));
1651             if(std::is_same<decltype(replacement), const RichString &>::value)
1652                 newStyle.copy(startNew, md.length, *static_cast<const RichString &>(replacement).m_style, md.offset);
1653             else
1654                 newStyle.fill(startNew, md.length, str.m_style->at(strStyleOffset < 0 ? 0 : strStyleOffset));
1655             startNew += md.length;
1656         }
1657     }
1658     str.swap(newString);
1659     str.m_style->swap(newStyle, false);
1660 }