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 ®Exp, 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('<', "<") 0663 .replace('>', ">"); 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('<', "<") 0688 .replace('>', ">"); 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 ®Exp, 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 ®Exp, 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 ®Exp, 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 }