1 /*
2 SPDX-FileCopyrightText: 2007-2009 Sergio Pistone <sergio_pistone@yahoo.com.ar>
3 SPDX-FileCopyrightText: 2010-2018 Mladen Milinkovic <max@smoothware.net>
4
5 SPDX-License-Identifier: GPL-2.0-or-later
6 */
7
8 #include "core/sstring.h"
9
10 #include <QList>
11 #include <QStringList>
12 #include <QRegularExpression>
13
14 #include <QDebug>
15
16 #include <QColor>
17
18 using namespace SubtitleComposer;
19
20 void *
memset_n(void * ptr,int value,size_t length,size_t size)21 memset_n(void *ptr, int value, size_t length, size_t size)
22 {
23 unsigned char *data = (unsigned char *)ptr;
24 while(length > 0) {
25 memcpy(data, &value, size);
26 data += size;
27 length--;
28 }
29 return ptr;
30 }
31
SString(const QString & string,int styleFlags,QRgb styleColor)32 SString::SString(const QString &string, int styleFlags /* = 0*/, QRgb styleColor /* = 0*/) :
33 QString(string),
34 m_styleFlags(NULL),
35 m_styleColors(NULL),
36 m_capacity(0)
37 {
38 if(QString::length()) {
39 setMinFlagsCapacity(length());
40 memset(m_styleFlags, styleFlags & AllStyles, length() * sizeof(*m_styleFlags));
41 memset_n(m_styleColors, styleColor, length(), sizeof(*m_styleColors));
42 }
43 }
44
SString(const SString & sstring)45 SString::SString(const SString &sstring) :
46 QString(sstring),
47 m_styleFlags(NULL),
48 m_styleColors(NULL),
49 m_capacity(0)
50 {
51 if(length()) {
52 setMinFlagsCapacity(length());
53 memcpy(m_styleFlags, sstring.m_styleFlags, length() * sizeof(*m_styleFlags));
54 memcpy(m_styleColors, sstring.m_styleColors, length() * sizeof(*m_styleColors));
55 }
56 }
57
58 SString &
operator =(const SString & sstring)59 SString::operator=(const SString &sstring)
60 {
61 if(this == &sstring)
62 return *this;
63
64 QString::operator=(sstring);
65 setMinFlagsCapacity(length());
66
67 if(length()) {
68 memcpy(m_styleFlags, sstring.m_styleFlags, length() * sizeof(*m_styleFlags));
69 memcpy(m_styleColors, sstring.m_styleColors, length() * sizeof(*m_styleColors));
70 }
71
72 return *this;
73 }
74
~SString()75 SString::~SString()
76 {
77 delete[] m_styleFlags;
78 delete[] m_styleColors;
79 }
80
81 void
setString(const QString & string,int styleFlags,QRgb styleColor)82 SString::setString(const QString &string, int styleFlags /* = 0*/, QRgb styleColor /* = 0*/)
83 {
84 QString::operator=(string);
85 setMinFlagsCapacity(length());
86 if(length()) {
87 memset(m_styleFlags, styleFlags & AllStyles, length() * sizeof(*m_styleFlags));
88 memset_n(m_styleColors, styleColor, length(), sizeof(*m_styleColors));
89 }
90 }
91
92 QString
richString(RichOutputMode mode) const93 SString::richString(RichOutputMode mode) const
94 {
95 if(isEmpty())
96 return *this;
97
98 QString ret;
99
100 if(mode == Compact) {
101 char prevStyleFlags = m_styleFlags[0];
102 QRgb prevStyleColor = m_styleColors[0];
103 int prevIndex = 0;
104
105 if(prevStyleFlags & Italic)
106 ret += "<i>";
107 if(prevStyleFlags & Bold)
108 ret += "<b>";
109 if(prevStyleFlags & Underline)
110 ret += "<u>";
111 if(prevStyleFlags & StrikeThrough)
112 ret += "<s>";
113 if(prevStyleFlags & Color)
114 ret += "<font color=" + QColor(prevStyleColor).name() + ">";
115
116 const int size = length();
117 QChar ch;
118 for(int index = 1; index < size; ++index) {
119 if(m_styleFlags[index] != prevStyleFlags || ((prevStyleFlags & m_styleFlags[index] & Color) && m_styleColors[index] != prevStyleColor)) {
120 QString token(QString::mid(prevIndex, index - prevIndex));
121 ret += token.replace('<', "<").replace('>', ">");
122
123 if((prevStyleFlags & StrikeThrough) && !(m_styleFlags[index] & StrikeThrough))
124 ret += "</s>";
125 if((prevStyleFlags & Underline) && !(m_styleFlags[index] & Underline))
126 ret += "</u>";
127 if((prevStyleFlags & Bold) && !(m_styleFlags[index] & Bold))
128 ret += "</b>";
129 if((prevStyleFlags & Italic) && !(m_styleFlags[index] & Italic))
130 ret += "</i>";
131 if((prevStyleFlags & Color) && (!(m_styleFlags[index] & Color) || prevStyleColor != m_styleColors[index]))
132 ret += "</font>";
133
134 while(index < size) {
135 // place opening html tags after spaces/newlines
136 ch = at(index);
137 if(ch != '\n' && ch != '\r' && ch != ' ' && ch != '\t')
138 break;
139 ret += ch;
140 index++;
141 }
142
143 if(!(prevStyleFlags & Italic) && (m_styleFlags[index] & Italic))
144 ret += "<i>";
145 if(!(prevStyleFlags & Bold) && (m_styleFlags[index] & Bold))
146 ret += "<b>";
147 if(!(prevStyleFlags & Underline) && (m_styleFlags[index] & Underline))
148 ret += "<u>";
149 if(!(prevStyleFlags & StrikeThrough) && (m_styleFlags[index] & StrikeThrough))
150 ret += "<s>";
151 if((m_styleFlags[index] & Color) && (!(prevStyleFlags & Color) || prevStyleColor != m_styleColors[index]))
152 ret += "<font color=" + QColor(m_styleColors[index]).name() + ">";
153
154 prevIndex = index;
155 prevStyleFlags = m_styleFlags[index];
156 prevStyleColor = m_styleColors[index];
157 }
158 }
159 QString token(QString::mid(prevIndex, length() - prevIndex));
160 if(token.length()) {
161 ret += token.replace('<', "<").replace('>', ">");
162
163 if(prevStyleFlags & StrikeThrough)
164 ret += "</s>";
165 if(prevStyleFlags & Underline)
166 ret += "</u>";
167 if(prevStyleFlags & Bold)
168 ret += "</b>";
169 if(prevStyleFlags & Italic)
170 ret += "</i>";
171 if(prevStyleFlags & Color)
172 ret += "</font>";
173 }
174 } else { // outputMode == Verbose
175 int currentStyleFlags = m_styleFlags[0];
176 QRgb currentColor = m_styleColors[0];
177 int prevIndex = 0;
178 for(uint index = 1, size = length(); index < size; ++index) {
179 if(currentStyleFlags != m_styleFlags[index] || ((currentStyleFlags & m_styleFlags[index] & Color) && currentColor != m_styleColors[index])) {
180 if(currentStyleFlags & StrikeThrough)
181 ret += "<s>";
182 if(currentStyleFlags & Bold)
183 ret += "<b>";
184 if(currentStyleFlags & Italic)
185 ret += "<i>";
186 if(currentStyleFlags & Underline)
187 ret += "<u>";
188 if(currentStyleFlags & Color)
189 ret += "<font color=" + QColor(currentColor).name() + ">";
190
191 ret += QString::mid(prevIndex, index - prevIndex);
192
193 if(currentStyleFlags & Color)
194 ret += "</font>";
195 if(currentStyleFlags & Underline)
196 ret += "</u>";
197 if(currentStyleFlags & Italic)
198 ret += "</i>";
199 if(currentStyleFlags & Bold)
200 ret += "</b>";
201 if(currentStyleFlags & StrikeThrough)
202 ret += "</s>";
203
204 prevIndex = index;
205
206 currentStyleFlags = m_styleFlags[index];
207 currentColor = m_styleColors[index];
208 }
209 }
210
211 if(prevIndex + 1 < length()) {
212 if(currentStyleFlags & StrikeThrough)
213 ret += "<s>";
214 if(currentStyleFlags & Bold)
215 ret += "<b>";
216 if(currentStyleFlags & Italic)
217 ret += "<i>";
218 if(currentStyleFlags & Underline)
219 ret += "<u>";
220 if(currentStyleFlags & Color)
221 ret += "<font color=" + QColor(currentColor).name() + ">";
222
223 ret += QString::mid(prevIndex);
224
225 if(currentStyleFlags & Color)
226 ret += "</font>";
227 if(currentStyleFlags & Underline)
228 ret += "</u>";
229 if(currentStyleFlags & Italic)
230 ret += "</i>";
231 if(currentStyleFlags & Bold)
232 ret += "</b>";
233 if(currentStyleFlags & StrikeThrough)
234 ret += "</s>";
235 }
236 }
237
238 return ret;
239 }
240
241 SString &
setRichString(const QString & string)242 SString::setRichString(const QString &string)
243 {
244 QRegExp tagRegExp("<(/?([bBiIuUsS]|font))[^>]*(\\s+color=\"?([\\w#]+)\"?)?[^>]*>");
245
246 clear();
247
248 int currentStyle = 0;
249 QColor currentColor;
250 int offsetPos = 0, matchedPos;
251 while((matchedPos = tagRegExp.indexIn(string, offsetPos)) != -1) {
252 QString matched(tagRegExp.cap(1).toLower());
253
254 int newStyle = currentStyle;
255 QColor newColor(currentColor);
256
257 if(matched == QLatin1String("b")) {
258 newStyle |= SString::Bold;
259 } else if(matched == QLatin1String("i")) {
260 newStyle |= SString::Italic;
261 } else if(matched == QLatin1String("u")) {
262 newStyle |= SString::Underline;
263 } else if(matched == QLatin1String("s")) {
264 newStyle |= SString::StrikeThrough;
265 } else if(matched == QLatin1String("font")) {
266 const QString &color = tagRegExp.cap(4);
267 if(!color.isEmpty()) {
268 newStyle |= SString::Color;
269 newColor.setNamedColor(color.toLower());
270 }
271 } else if(matched == QLatin1String("/b")) {
272 newStyle &= ~SString::Bold;
273 } else if(matched == QLatin1String("/i")) {
274 newStyle &= ~SString::Italic;
275 } else if(matched == QLatin1String("/u")) {
276 newStyle &= ~SString::Underline;
277 } else if(matched == QLatin1String("/s")) {
278 newStyle &= ~SString::StrikeThrough;
279 } else if(matched == QLatin1String("/font")) {
280 newStyle &= ~SString::Color;
281 newColor.setNamedColor("-invalid-");
282 }
283
284 QString token(string.mid(offsetPos, matchedPos - offsetPos));
285 append(SString(token, currentStyle, currentColor.isValid() ? currentColor.rgb() : 0));
286 currentStyle = newStyle;
287 currentColor = newColor;
288
289 offsetPos = matchedPos + tagRegExp.cap(0).length();
290 }
291
292 QString token(string.mid(offsetPos, matchedPos - offsetPos));
293 append(SString(token /*.replace("<", "<").replace(">", ">")*/, currentStyle, currentColor.isValid() ? currentColor.rgb() : 0));
294
295 return *this;
296 }
297
298 int
cummulativeStyleFlags() const299 SString::cummulativeStyleFlags() const
300 {
301 int cummulativeStyleFlags = 0;
302 for(int index = 0, size = length(); index < size; ++index) {
303 cummulativeStyleFlags |= m_styleFlags[index];
304 if(cummulativeStyleFlags == AllStyles)
305 break;
306 }
307 return cummulativeStyleFlags;
308 }
309
310 bool
hasStyleFlags(int styleFlags) const311 SString::hasStyleFlags(int styleFlags) const
312 {
313 int cummulativeStyleFlags = 0;
314 for(int index = 0, size = length(); index < size; ++index) {
315 cummulativeStyleFlags |= m_styleFlags[index];
316 if((cummulativeStyleFlags & styleFlags) == styleFlags)
317 return true;
318 }
319 return false;
320 }
321
322 SString &
setStyleFlags(int index,int len,int styleFlags)323 SString::setStyleFlags(int index, int len, int styleFlags)
324 {
325 if(index < 0 || index >= (int)length())
326 return *this;
327
328 for(int index2 = index + length(index, len); index < index2; ++index)
329 m_styleFlags[index] = styleFlags;
330
331 return *this;
332 }
333
334 SString &
setStyleFlags(int index,int len,int styleFlags,bool on)335 SString::setStyleFlags(int index, int len, int styleFlags, bool on)
336 {
337 if(index < 0 || index >= (int)length())
338 return *this;
339
340 if(on) {
341 for(int index2 = index + length(index, len); index < index2; ++index)
342 m_styleFlags[index] = m_styleFlags[index] | styleFlags;
343 } else {
344 styleFlags = ~styleFlags;
345 for(int index2 = index + length(index, len); index < index2; ++index)
346 m_styleFlags[index] = m_styleFlags[index] & styleFlags;
347 }
348
349 return *this;
350 }
351
352 SString &
setStyleColor(int index,int len,QRgb color)353 SString::setStyleColor(int index, int len, QRgb color)
354 {
355 if(index < 0 || index >= (int)length())
356 return *this;
357
358 for(int sz = index + length(index, len); index < sz; index++) {
359 m_styleColors[index] = color;
360 if(color == 0)
361 m_styleFlags[index] &= ~Color;
362 else
363 m_styleFlags[index] |= Color;
364 }
365
366 return *this;
367 }
368
369 void
clear()370 SString::clear()
371 {
372 QString::clear();
373 setMinFlagsCapacity(0);
374 }
375
376 SString &
insert(int index,QChar ch)377 SString::insert(int index, QChar ch)
378 {
379 int oldLength = length();
380
381 if(index <= oldLength && index >= 0) {
382 QString::insert(index, ch);
383
384 char *oldStyleFlags = detachFlags();
385 QRgb *oldStyleColors = detachColors();
386 setMinFlagsCapacity(length());
387
388 char fillFlags = 0;
389 int fillColor = 0;
390 if(oldLength) {
391 if(index == 0) {
392 fillFlags = oldStyleFlags[0];
393 fillColor = oldStyleColors[0];
394 } else {
395 fillFlags = oldStyleFlags[index - 1];
396 fillColor = oldStyleColors[index - 1];
397 }
398 }
399
400 memcpy(m_styleFlags, oldStyleFlags, index * sizeof(*m_styleFlags));
401 m_styleFlags[index] = fillFlags;
402 memcpy(m_styleFlags + index + 1, oldStyleFlags + index, (length() - index - 1) * sizeof(*m_styleFlags));
403
404 memcpy(m_styleColors, oldStyleColors, index * sizeof(*m_styleColors));
405 m_styleColors[index] = fillColor;
406 memcpy(m_styleColors + index + 1, oldStyleColors + index, (length() - index - 1) * sizeof(*m_styleColors));
407
408 delete[] oldStyleFlags;
409 delete[] oldStyleColors;
410 }
411
412 return *this;
413 }
414
415 SString &
insert(int index,const QString & str)416 SString::insert(int index, const QString &str)
417 {
418 int oldLength = length();
419
420 if(str.length() && index <= oldLength && index >= 0) {
421 QString::insert(index, str);
422
423 char *oldStyleFlags = detachFlags();
424 QRgb *oldStyleColors = detachColors();
425 setMinFlagsCapacity(length());
426
427 char fillFlags = 0;
428 int fillColor = 0;
429 if(oldLength) {
430 if(index == 0) {
431 fillFlags = oldStyleFlags[0];
432 fillColor = oldStyleColors[0];
433 } else {
434 fillFlags = oldStyleFlags[index - 1];
435 fillColor = oldStyleColors[index - 1];
436 }
437 }
438
439 int addedLength = str.length();
440
441 memcpy(m_styleFlags, oldStyleFlags, index * sizeof(*m_styleFlags));
442 memset(m_styleFlags + index, fillFlags, addedLength * sizeof(*m_styleFlags));
443 memcpy(m_styleFlags + index + addedLength, oldStyleFlags + index, (length() - index - addedLength) * sizeof(*m_styleFlags));
444
445 memcpy(m_styleColors, oldStyleColors, index * sizeof(*m_styleColors));
446 memset_n(m_styleColors + index, fillColor, addedLength, sizeof(*m_styleColors));
447 memcpy(m_styleColors + index + addedLength, oldStyleColors + index, (length() - index - addedLength) * sizeof(*m_styleColors));
448
449 delete[] oldStyleFlags;
450 delete[] oldStyleColors;
451 }
452
453 return *this;
454 }
455
456 SString &
insert(int index,const SString & str)457 SString::insert(int index, const SString &str)
458 {
459 int oldLength = length();
460
461 if(str.length() && index <= oldLength && index >= 0) {
462 QString::insert(index, str);
463
464 char *oldStyleFlags = detachFlags();
465 QRgb *oldStyleColors = detachColors();
466 setMinFlagsCapacity(length());
467
468 int addedLength = str.length();
469
470 memcpy(m_styleFlags, oldStyleFlags, index * sizeof(*m_styleFlags));
471 memcpy(m_styleFlags + index, str.m_styleFlags, addedLength * sizeof(*m_styleFlags));
472 memcpy(m_styleFlags + index + addedLength, oldStyleFlags + index, (length() - index - addedLength) * sizeof(*m_styleFlags));
473
474 memcpy(m_styleColors, oldStyleColors, index * sizeof(*m_styleColors));
475 memcpy(m_styleColors + index, str.m_styleColors, addedLength * sizeof(*m_styleColors));
476 memcpy(m_styleColors + index + addedLength, oldStyleColors + index, (length() - index - addedLength) * sizeof(*m_styleColors));
477
478 delete[] oldStyleFlags;
479 delete[] oldStyleColors;
480 }
481
482 return *this;
483 }
484
485 SString &
replace(int index,int len,const QString & replacement)486 SString::replace(int index, int len, const QString &replacement)
487 {
488 int oldLength = length();
489
490 if(index < 0 || index >= oldLength)
491 return *this;
492
493 len = length(index, len);
494
495 if(len == 0 && replacement.length() == 0) // nothing to do (replace nothing with nothing)
496 return *this;
497
498 QString::replace(index, len, replacement);
499
500 // simple path for when there's no need to change the styles (char substitution)
501 if(len == 1 && replacement.length() == 1)
502 return *this;
503
504 if(len == replacement.length()) {
505 // the length of the string wasn't changed
506 if(index >= oldLength) {
507 // index can't really be greater than oldLength
508 memset(m_styleFlags + index, oldLength ? m_styleFlags[oldLength - 1] : 0, len * sizeof(*m_styleFlags));
509 memset_n(m_styleColors + index, oldLength ? m_styleColors[oldLength - 1] : 0, len, sizeof(*m_styleColors));
510 } else {
511 // index < oldLength (NOTE: index is always >= 0)
512 memset(m_styleFlags + index, m_styleFlags[index], len * sizeof(*m_styleFlags));
513 memset_n(m_styleColors + index, m_styleColors[oldLength - 1], len, sizeof(*m_styleColors));
514 }
515 } else {
516 // the length of the string was changed
517 char *oldStyleFlags = detachFlags();
518 QRgb *oldStyleColors = detachColors();
519 setMinFlagsCapacity(length());
520
521 memcpy(m_styleFlags, oldStyleFlags, index * sizeof(*m_styleFlags));
522 memset(m_styleFlags + index, oldStyleFlags[index], replacement.length() * sizeof(*m_styleFlags));
523 memcpy(m_styleFlags + index + replacement.length(), oldStyleFlags + index + len, (length() - index - replacement.length()) * sizeof(*m_styleFlags));
524
525 memcpy(m_styleColors, oldStyleColors, index * sizeof(*m_styleColors));
526 memset_n(m_styleColors + index, oldStyleColors[index], replacement.length(), sizeof(*m_styleColors));
527 memcpy(m_styleColors + index + replacement.length(), oldStyleColors + index + len, (length() - index - replacement.length()) * sizeof(*m_styleColors));
528
529 delete[] oldStyleFlags;
530 delete[] oldStyleColors;
531 }
532
533 return *this;
534 }
535
536 SString &
replace(int index,int len,const SString & replacement)537 SString::replace(int index, int len, const SString &replacement)
538 {
539 int oldLength = length();
540
541 if(index < 0 || index >= oldLength)
542 return *this;
543
544 len = length(index, len);
545
546 if(len == 0 && replacement.length() == 0) // nothing to do (replace nothing with nothing)
547 return *this;
548
549 QString::replace(index, len, replacement);
550
551 // simple path for when there's no need to change the styles (char substitution)
552 // if ( len == 1 && replacement.length() == 1 )
553 // return *this;
554
555 char *oldStyleFlags = detachFlags();
556 QRgb *oldStyleColors = detachColors();
557 setMinFlagsCapacity(length());
558
559 memcpy(m_styleFlags, oldStyleFlags, index * sizeof(*m_styleFlags));
560 memcpy(m_styleFlags + index, replacement.m_styleFlags, replacement.length() * sizeof(*m_styleFlags));
561 memcpy(m_styleFlags + index + replacement.length(), oldStyleFlags + index + len, (length() - index - replacement.length()) * sizeof(*m_styleFlags));
562
563 memcpy(m_styleColors, oldStyleColors, index * sizeof(*m_styleColors));
564 memcpy(m_styleColors + index, replacement.m_styleColors, replacement.length() * sizeof(*m_styleColors));
565 memcpy(m_styleColors + index + replacement.length(), oldStyleColors + index + len, (length() - index - replacement.length()) * sizeof(*m_styleColors));
566
567 delete[] oldStyleFlags;
568 delete[] oldStyleColors;
569
570 return *this;
571 }
572
573 SString &
replace(const QString & before,const QString & after,Qt::CaseSensitivity cs)574 SString::replace(const QString &before, const QString &after, Qt::CaseSensitivity cs)
575 {
576 if(before.length() == 0 && after.length() == 0)
577 return *this;
578
579 if(before.length() == 1 && after.length() == 1) {
580 // simple path for when there's no need to change the styles flags
581 QString::replace(before, after);
582 return *this;
583 }
584
585 int oldLength = length();
586 int beforeLength = before.length();
587 int afterLength = after.length();
588
589 QList<int> changedData; // each entry contains the start index of a replaced substring
590
591 for(int offsetIndex = 0, matchedIndex; (matchedIndex = indexOf(before, offsetIndex, cs)) != -1; offsetIndex = matchedIndex + afterLength) {
592 QString::replace(matchedIndex, beforeLength, after);
593
594 changedData.append(matchedIndex);
595
596 if(!beforeLength)
597 matchedIndex++;
598 }
599
600 if(changedData.empty()) // nothing was replaced
601 return *this;
602
603 if(length()) {
604 int newOffset = 0;
605 int oldOffset = 0;
606 int unchangedLength;
607
608 char *oldStyleFlags = detachFlags();
609 QRgb *oldStyleColors = detachColors();
610 setMinFlagsCapacity(length());
611
612 for(int index = 0; index < changedData.size(); ++index) {
613 unchangedLength = changedData[index] - newOffset;
614
615 memcpy(m_styleFlags + newOffset, oldStyleFlags + oldOffset, unchangedLength * sizeof(*m_styleFlags));
616 memcpy(m_styleColors + newOffset, oldStyleColors + oldOffset, unchangedLength * sizeof(*m_styleColors));
617 newOffset += unchangedLength;
618 oldOffset += unchangedLength;
619
620 memset(m_styleFlags + newOffset, oldOffset < oldLength ? oldStyleFlags[oldOffset] : 0, afterLength * sizeof(*m_styleFlags));
621 memset_n(m_styleColors + newOffset, oldOffset < oldLength ? oldStyleColors[oldOffset] : 0, afterLength, sizeof(*m_styleColors));
622 newOffset += afterLength;
623 oldOffset += beforeLength;
624 }
625
626 memcpy(m_styleFlags + newOffset, oldStyleFlags + oldOffset, (oldLength - oldOffset) * sizeof(*m_styleFlags));
627 memcpy(m_styleColors + newOffset, oldStyleColors + oldOffset, (oldLength - oldOffset) * sizeof(*m_styleColors));
628
629 delete[] oldStyleFlags;
630 delete[] oldStyleColors;
631 } else {
632 setMinFlagsCapacity(length());
633 }
634
635 return *this;
636 }
637
638 SString &
replace(const QString & before,const SString & after,Qt::CaseSensitivity cs)639 SString::replace(const QString &before, const SString &after, Qt::CaseSensitivity cs)
640 {
641 if(before.length() == 0 && after.length() == 0)
642 return *this;
643
644 int oldLength = length();
645 int beforeLength = before.length();
646 int afterLength = after.length();
647
648 QList<int> changedData; // each entry contains the start index of a replaced substring
649
650 for(int offsetIndex = 0, matchedIndex; (matchedIndex = indexOf(before, offsetIndex, cs)) != -1; offsetIndex = matchedIndex + afterLength) {
651 QString::replace(matchedIndex, beforeLength, after);
652
653 changedData.append(matchedIndex);
654
655 if(!beforeLength)
656 matchedIndex++;
657 }
658
659 if(changedData.empty()) // nothing was replaced
660 return *this;
661
662 if(length()) {
663 int newOffset = 0;
664 int oldOffset = 0;
665 int unchangedLength;
666
667 char *oldStyleFlags = detachFlags();
668 QRgb *oldStyleColors = detachColors();
669 setMinFlagsCapacity(length());
670
671 for(int index = 0; index < changedData.size(); ++index) {
672 unchangedLength = changedData[index] - newOffset;
673
674 memcpy(m_styleFlags + newOffset, oldStyleFlags + oldOffset, unchangedLength * sizeof(*m_styleFlags));
675 memcpy(m_styleColors + newOffset, oldStyleColors + oldOffset, unchangedLength * sizeof(*m_styleColors));
676 newOffset += unchangedLength;
677 oldOffset += unchangedLength;
678
679 memcpy(m_styleFlags + newOffset, after.m_styleFlags, afterLength * sizeof(*m_styleFlags));
680 memcpy(m_styleColors + newOffset, after.m_styleColors, afterLength * sizeof(*m_styleColors));
681 newOffset += afterLength;
682 oldOffset += beforeLength;
683 }
684
685 memcpy(m_styleFlags + newOffset, oldStyleFlags + oldOffset, oldLength - oldOffset * sizeof(*m_styleFlags));
686 memcpy(m_styleColors + newOffset, oldStyleColors + oldOffset, (oldLength - oldOffset) * sizeof(*m_styleColors));
687
688 delete[] oldStyleFlags;
689 delete[] oldStyleColors;
690 } else {
691 setMinFlagsCapacity(length());
692 }
693
694 return *this;
695 }
696
697 SString &
replace(QChar before,QChar after,Qt::CaseSensitivity cs)698 SString::replace(QChar before, QChar after, Qt::CaseSensitivity cs)
699 {
700 QString::replace(before, after, cs);
701 return *this;
702 }
703
704 SString &
replace(QChar ch,const QString & after,Qt::CaseSensitivity cs)705 SString::replace(QChar ch, const QString &after, Qt::CaseSensitivity cs)
706 {
707 if(after.length() == 1) {
708 // simple path for when there's no need to change the styles flags
709 QString::replace(ch, after.at(0));
710 return *this;
711 }
712
713 int oldLength = length();
714 int afterLength = after.length();
715
716 QList<int> changedData; // each entry contains the start index of a replaced substring
717
718 for(int offsetIndex = 0, matchedIndex; (matchedIndex = indexOf(ch, offsetIndex, cs)) != -1; offsetIndex = matchedIndex + afterLength) {
719 QString::replace(matchedIndex, 1, after);
720
721 changedData.append(matchedIndex);
722 }
723
724 if(changedData.empty()) // nothing was replaced
725 return *this;
726
727 if(length()) {
728 int newOffset = 0;
729 int oldOffset = 0;
730 int unchangedLength;
731
732 char *oldStyleFlags = detachFlags();
733 QRgb *oldStyleColors = detachColors();
734 setMinFlagsCapacity(length());
735
736 for(int index = 0; index < changedData.size(); ++index) {
737 unchangedLength = changedData[index] - newOffset;
738
739 memcpy(m_styleFlags + newOffset, oldStyleFlags + oldOffset, unchangedLength * sizeof(*m_styleFlags));
740 memcpy(m_styleColors + newOffset, oldStyleColors + oldOffset, unchangedLength * sizeof(*m_styleColors));
741 newOffset += unchangedLength;
742 oldOffset += unchangedLength;
743
744 memset(m_styleFlags + newOffset, oldOffset < oldLength ? oldStyleFlags[oldOffset] : 0, afterLength * sizeof(*m_styleFlags));
745 memset_n(m_styleColors + newOffset, oldOffset < oldLength ? oldStyleColors[oldOffset] : 0, afterLength, sizeof(*m_styleColors));
746 newOffset += afterLength;
747 oldOffset += 1;
748 }
749
750 memcpy(m_styleFlags + newOffset, oldStyleFlags + oldOffset, oldLength - oldOffset * sizeof(*m_styleFlags));
751 memcpy(m_styleColors + newOffset, oldStyleColors + oldOffset, (oldLength - oldOffset) * sizeof(*m_styleColors));
752
753 delete[] oldStyleFlags;
754 delete[] oldStyleColors;
755 } else {
756 setMinFlagsCapacity(length());
757 }
758
759 return *this;
760 }
761
762 SString &
replace(QChar ch,const SString & after,Qt::CaseSensitivity cs)763 SString::replace(QChar ch, const SString &after, Qt::CaseSensitivity cs)
764 {
765 int oldLength = length();
766 int afterLength = after.length();
767
768 QList<int> changedData; // each entry contains the start index of a replaced substring
769
770 for(int offsetIndex = 0, matchedIndex; (matchedIndex = indexOf(ch, offsetIndex, cs)) != -1; offsetIndex = matchedIndex + afterLength) {
771 QString::replace(matchedIndex, 1, after);
772
773 changedData.append(matchedIndex);
774 }
775
776 if(changedData.empty()) // nothing was replaced
777 return *this;
778
779 if(length()) {
780 int newOffset = 0;
781 int oldOffset = 0;
782 int unchangedLength;
783
784 char *oldStyleFlags = detachFlags();
785 QRgb *oldStyleColors = detachColors();
786 setMinFlagsCapacity(length());
787
788 for(int index = 0; index < changedData.size(); ++index) {
789 unchangedLength = changedData[index] - newOffset;
790
791 memcpy(m_styleFlags + newOffset, oldStyleFlags + oldOffset, unchangedLength * sizeof(*m_styleFlags));
792 memcpy(m_styleColors + newOffset, oldStyleColors + oldOffset, unchangedLength * sizeof(*m_styleColors));
793 newOffset += unchangedLength;
794 oldOffset += unchangedLength;
795
796 memcpy(m_styleFlags + newOffset, after.m_styleFlags, afterLength * sizeof(*m_styleFlags));
797 memcpy(m_styleColors + newOffset, after.m_styleColors, afterLength * sizeof(*m_styleColors));
798 newOffset += afterLength;
799 oldOffset += 1;
800 }
801
802 memcpy(m_styleFlags + newOffset, oldStyleFlags + oldOffset, oldLength - oldOffset * sizeof(*m_styleFlags));
803 memcpy(m_styleColors + newOffset, oldStyleColors + oldOffset, (oldLength - oldOffset) * sizeof(*m_styleColors));
804
805 delete[] oldStyleFlags;
806 delete[] oldStyleColors;
807 } else {
808 setMinFlagsCapacity(length());
809 }
810
811 return *this;
812 }
813
814 SString &
replace(const QRegExp & rx,const QString & a)815 SString::replace(const QRegExp &rx, const QString &a)
816 {
817 QRegExp regExp(rx);
818
819 int oldLength = length();
820
821 QList<int> changedData; // each entry contains the start index of a replaced substring
822
823 QRegExp::CaretMode caretMode = QRegExp::CaretAtZero;
824 for(int offsetIndex = 0, matchedIndex; (matchedIndex = regExp.indexIn(*this, offsetIndex, caretMode)) != -1;) {
825 QString after(a);
826
827 bool escaping = false;
828 for(int afterIndex = 0, afterSize = after.length(); afterIndex < afterSize; ++afterIndex) {
829 QChar chr = after.at(afterIndex);
830 if(escaping) { // perform replace
831 escaping = false;
832 if(chr.isNumber()) {
833 int capNumber = chr.digitValue();
834 if(capNumber <= regExp.captureCount()) {
835 QString cap(regExp.cap(capNumber));
836 after.replace(afterIndex - 1, 2, cap);
837 afterIndex = afterIndex - 1 + cap.length();
838 afterSize = after.length();
839 }
840 }
841 } else if(chr == '\\')
842 escaping = !escaping;
843 }
844
845 if(regExp.matchedLength() == 0 && after.length() == 0)
846 continue;
847
848 QString::replace(matchedIndex, regExp.matchedLength(), after);
849
850 if(regExp.matchedLength() != 1 || after.length() != 1) {
851 changedData.append(matchedIndex);
852 changedData.append(regExp.matchedLength()); // before length
853 changedData.append(after.length()); // after length
854
855 if(!regExp.matchedLength())
856 matchedIndex++;
857 }
858
859 offsetIndex = matchedIndex + after.length();
860 caretMode = QRegExp::CaretWontMatch; // caret should only be matched the first time
861 }
862
863 if(changedData.empty()) // nothing was replaced
864 return *this;
865
866 if(length()) {
867 int newOffset = 0;
868 int oldOffset = 0;
869 int unchangedLength;
870 int beforeLength;
871 int afterLength;
872
873 char *oldStyleFlags = detachFlags();
874 QRgb *oldStyleColors = detachColors();
875 setMinFlagsCapacity(length());
876
877 for(int index = 0; index < changedData.size(); index += 3) {
878 unchangedLength = changedData[index] - newOffset;
879 beforeLength = changedData[index + 1];
880 afterLength = changedData[index + 2];
881
882 memcpy(m_styleFlags + newOffset, oldStyleFlags + oldOffset, unchangedLength * sizeof(*m_styleFlags));
883 memcpy(m_styleColors + newOffset, oldStyleColors + oldOffset, unchangedLength * sizeof(*m_styleColors));
884 newOffset += unchangedLength;
885 oldOffset += unchangedLength;
886
887 memset(m_styleFlags + newOffset, oldOffset < oldLength ? oldStyleFlags[oldOffset] : 0, afterLength * sizeof(*m_styleFlags));
888 memset_n(m_styleColors + newOffset, oldOffset < oldLength ? oldStyleColors[oldOffset] : 0, afterLength, sizeof(*m_styleColors));
889 newOffset += afterLength;
890 oldOffset += beforeLength;
891 }
892
893 memcpy(m_styleFlags + newOffset, oldStyleFlags + oldOffset, oldLength - oldOffset * sizeof(*m_styleFlags));
894 memcpy(m_styleColors + newOffset, oldStyleColors + oldOffset, (oldLength - oldOffset) * sizeof(*m_styleColors));
895
896 delete[] oldStyleFlags;
897 delete[] oldStyleColors;
898 } else {
899 setMinFlagsCapacity(length());
900 }
901
902 return *this;
903 }
904
905 SString &
replace(const QRegExp & rx,const SString & a)906 SString::replace(const QRegExp &rx, const SString &a)
907 {
908 QRegExp regExp(rx);
909
910 QRegExp::CaretMode caretMode = QRegExp::CaretAtZero;
911 for(int offsetIndex = 0, matchedIndex; (matchedIndex = regExp.indexIn(*this, offsetIndex, caretMode)) != -1;) {
912 SString after(a);
913
914 bool escaping = false;
915 for(int afterIndex = 0, afterSize = after.length(); afterIndex < afterSize; ++afterIndex) {
916 QChar chr = after.at(afterIndex);
917 if(escaping) { // perform replace
918 escaping = false;
919 if(chr.isNumber()) {
920 int capNumber = chr.digitValue();
921 if(capNumber <= regExp.captureCount()) {
922 QString cap(regExp.cap(capNumber));
923 after.replace(afterIndex - 1, 2, cap);
924 afterIndex = afterIndex - 1 + cap.length();
925 afterSize = after.length();
926 }
927 }
928 } else if(chr == '\\')
929 escaping = !escaping;
930 }
931
932 if(regExp.matchedLength() == 0 && after.length() == 0)
933 continue;
934
935 replace(matchedIndex, regExp.matchedLength(), after);
936
937 if(!regExp.matchedLength())
938 matchedIndex++;
939
940 offsetIndex = matchedIndex + after.length();
941 caretMode = QRegExp::CaretWontMatch; // caret should only be matched the first time
942 }
943
944 return *this;
945 }
946
947 struct SStringCapture {
948 int pos;
949 int len;
950 int no;
951 };
952
953 SString &
replace(const QRegularExpression & regExp,const QString & replacement)954 SString::replace(const QRegularExpression ®Exp, const QString &replacement)
955 {
956 return replace(regExp, SString(replacement), false);
957 }
958
959 SString &
replace(const QRegularExpression & regExp,const SString & replacement,bool styleFromReplacement)960 SString::replace(const QRegularExpression ®Exp, const SString &replacement, bool styleFromReplacement/*=true*/)
961 {
962 if(!regExp.isValid()) {
963 qWarning("SString::replace(): invalid regular expression at character %d:\n\t%s\n\t%s",
964 regExp.patternErrorOffset(),
965 regExp.pattern().toLatin1().constData(),
966 regExp.errorString().toLatin1().constData());
967 return *this;
968 }
969
970 const QString copy(*this);
971 QRegularExpressionMatchIterator iterator = regExp.globalMatch(copy);
972 if(!iterator.hasNext())
973 return *this;
974
975 int numCaptures = regExp.captureCount();
976
977 // store backreference offsets
978 QVector<SStringCapture> backReferences;
979 const int repLen = replacement.length();
980 const QChar *repChar = replacement.unicode();
981 for(int i = 0; i < repLen - 1; i++) {
982 if(repChar[i] == QLatin1Char('\\')) {
983 int no = repChar[i + 1].digitValue();
984 if(no > 0 && no <= numCaptures) {
985 SStringCapture backRef;
986 backRef.pos = i;
987 backRef.len = 2;
988
989 if(i < repLen - 2) {
990 int secondDigit = repChar[i + 2].digitValue();
991 if(secondDigit != -1 && ((no * 10) + secondDigit) <= numCaptures) {
992 no = (no * 10) + secondDigit;
993 ++backRef.len;
994 }
995 }
996
997 backRef.no = no;
998 backReferences.append(backRef);
999 }
1000 }
1001 }
1002
1003 // store data offsets
1004 int newLength = 0;
1005 int lastEnd = 0;
1006 int len;
1007 QVector<int> chunks; // set of (offset, length) values, even values reference 'copy' string, odd values reference 'replacement' string
1008 while(iterator.hasNext()) {
1009 QRegularExpressionMatch match = iterator.next();
1010
1011 // add the part from 'copy' string before the match
1012 len = match.capturedStart() - lastEnd;
1013 Q_ASSERT(len >= 0);
1014 chunks << lastEnd << len;
1015 newLength += len;
1016
1017 lastEnd = 0;
1018 for(const SStringCapture &backRef: qAsConst(backReferences)) {
1019 // part of 'replacement' before the backreference
1020 len = backRef.pos - lastEnd;
1021 Q_ASSERT(len >= 0);
1022 chunks << lastEnd << len;
1023 newLength += len;
1024
1025 // add the 'copy' string that backreference points to
1026 len = match.capturedLength(backRef.no);
1027 Q_ASSERT(len >= 0);
1028 chunks << match.capturedStart(backRef.no) << len;
1029 newLength += len;
1030
1031 lastEnd = backRef.pos + backRef.len;
1032 }
1033
1034 // add the last part of the 'replacement' string
1035 len = replacement.length() - lastEnd;
1036 Q_ASSERT(len >= 0);
1037 chunks << lastEnd << len;
1038 newLength += len;
1039
1040 lastEnd = match.capturedEnd();
1041 }
1042
1043 // add trailing part from 'copy' string after the last match
1044 len = copy.length() - lastEnd;
1045 Q_ASSERT(len >= 0);
1046 chunks << lastEnd << len;
1047 newLength += len;
1048
1049 // finally copy the data
1050 resize(newLength);
1051 char *oldStyleFlags = detachFlags();
1052 QRgb *oldStyleColors = detachColors();
1053 setMinFlagsCapacity(newLength);
1054
1055 int newOff = 0;
1056 QChar *newData = data();
1057 for(int i = 0, n = chunks.size(); i < n; i += 2) {
1058 // copy data from 'copy' string
1059 int off = chunks[i];
1060 int len = chunks[i + 1];
1061 if(len > 0) {
1062 memcpy(newData + newOff, copy.midRef(off, len).unicode(), len * sizeof(QChar));
1063 memcpy(m_styleFlags + newOff, oldStyleFlags + off, len * sizeof(*m_styleFlags));
1064 memcpy(m_styleColors + newOff, oldStyleColors + off, len * sizeof(*m_styleColors));
1065 newOff += len;
1066 }
1067
1068 i += 2;
1069 if(i < n) {
1070 // copy data from 'replacement' string
1071 int repOff = chunks[i];
1072 int repLen = chunks[i + 1];
1073 if(repLen > 0) {
1074 memcpy(newData + newOff, replacement.midRef(repOff, repLen).unicode(), repLen * sizeof(QChar));
1075 if(styleFromReplacement) {
1076 memcpy(m_styleFlags + newOff, replacement.m_styleFlags + repOff, repLen * sizeof(*m_styleFlags));
1077 memcpy(m_styleColors + newOff, replacement.m_styleColors + repOff, repLen * sizeof(*m_styleColors));
1078 } else {
1079 memset(m_styleFlags + newOff, oldStyleFlags[off + len], repLen * sizeof(*m_styleFlags));
1080 memset_n(m_styleColors + newOff, oldStyleColors[off + len], repLen, sizeof(*m_styleColors));
1081 }
1082 newOff += repLen;
1083 }
1084 }
1085 }
1086
1087 delete[] oldStyleFlags;
1088 delete[] oldStyleColors;
1089
1090 return *this;
1091 }
1092
1093 SStringList
split(const QString & sep,QString::SplitBehavior behavior,Qt::CaseSensitivity cs) const1094 SString::split(const QString &sep, QString::SplitBehavior behavior, Qt::CaseSensitivity cs) const
1095 {
1096 SStringList ret;
1097
1098 if(sep.length()) {
1099 int offsetIndex = 0;
1100
1101 for(int matchedIndex; (matchedIndex = indexOf(sep, offsetIndex, cs)) != -1; offsetIndex = matchedIndex + sep.length()) {
1102 SString token(QString::mid(offsetIndex, matchedIndex - offsetIndex));
1103 if(behavior == QString::KeepEmptyParts || token.length())
1104 ret << token;
1105 }
1106 SString token(QString::mid(offsetIndex));
1107 if(behavior == QString::KeepEmptyParts || token.length())
1108 ret << token;
1109 } else if(behavior == QString::KeepEmptyParts || length()) {
1110 ret << *this;
1111 }
1112
1113 return ret;
1114 }
1115
1116 SStringList
split(const QChar & sep,QString::SplitBehavior behavior,Qt::CaseSensitivity cs) const1117 SString::split(const QChar &sep, QString::SplitBehavior behavior, Qt::CaseSensitivity cs) const
1118 {
1119 SStringList ret;
1120
1121 int offsetIndex = 0;
1122
1123 for(int matchedIndex; (matchedIndex = indexOf(sep, offsetIndex, cs)) != -1; offsetIndex = matchedIndex + 1) {
1124 SString token(QString::mid(offsetIndex, matchedIndex - offsetIndex));
1125 if(behavior == QString::KeepEmptyParts || token.length())
1126 ret << token;
1127 }
1128 SString token(QString::mid(offsetIndex));
1129 if(behavior == QString::KeepEmptyParts || token.length())
1130 ret << token;
1131
1132 return ret;
1133 }
1134
1135 SStringList
split(const QRegExp & sep,QString::SplitBehavior behavior) const1136 SString::split(const QRegExp &sep, QString::SplitBehavior behavior) const
1137 {
1138 SStringList ret;
1139
1140 QRegExp sepAux(sep);
1141
1142 int offsetIndex = 0;
1143
1144 for(int matchedIndex; (matchedIndex = sepAux.indexIn(*this, offsetIndex)) != -1; offsetIndex = matchedIndex + sepAux.matchedLength()) {
1145 SString token(QString::mid(offsetIndex, matchedIndex - offsetIndex));
1146 if(behavior == QString::KeepEmptyParts || token.length())
1147 ret << token;
1148 }
1149 SString token(QString::mid(offsetIndex));
1150 if(behavior == QString::KeepEmptyParts || token.length())
1151 ret << token;
1152
1153 return ret;
1154 }
1155
1156 SString
left(int len) const1157 SString::left(int len) const
1158 {
1159 len = length(0, len);
1160 SString ret;
1161 ret.operator=(QString::left(len));
1162 ret.setMinFlagsCapacity(len);
1163 memcpy(ret.m_styleFlags, m_styleFlags, len * sizeof(*m_styleFlags));
1164 memcpy(ret.m_styleColors, m_styleColors, len * sizeof(*m_styleColors));
1165 return ret;
1166 }
1167
1168 SString
right(int len) const1169 SString::right(int len) const
1170 {
1171 len = length(0, len);
1172 SString ret;
1173 ret.operator=(QString::right(len));
1174 ret.setMinFlagsCapacity(len);
1175 memcpy(ret.m_styleFlags, m_styleFlags + length() - len, len * sizeof(*m_styleFlags));
1176 memcpy(ret.m_styleColors, m_styleColors + length() - len, len * sizeof(*m_styleColors));
1177 return ret;
1178 }
1179
1180 SString
mid(int index,int len) const1181 SString::mid(int index, int len) const
1182 {
1183 if(index < 0) {
1184 if(len >= 0)
1185 len += index;
1186 index = 0;
1187 }
1188
1189 if(index >= (int)length())
1190 return SString();
1191
1192 len = length(index, len);
1193 SString ret;
1194 ret.operator=(QString::mid(index, len));
1195 ret.setMinFlagsCapacity(len);
1196 memcpy(ret.m_styleFlags, m_styleFlags + index, len * sizeof(*m_styleFlags));
1197 memcpy(ret.m_styleColors, m_styleColors + index, len * sizeof(*m_styleColors));
1198 return ret;
1199 }
1200
1201 SString
toLower() const1202 SString::toLower() const
1203 {
1204 SString ret(*this);
1205 ret.operator=(QString::toLower());
1206 return ret;
1207 }
1208
1209 SString
toUpper() const1210 SString::toUpper() const
1211 {
1212 SString ret(*this);
1213 ret.operator=(QString::toUpper());
1214 return ret;
1215 }
1216
1217 SString
toTitleCase(bool lowerFirst) const1218 SString::toTitleCase(bool lowerFirst) const
1219 {
1220 const QString wordSeparators(QStringLiteral(" -_([:,;./\\\t\n\""));
1221
1222 SString ret(*this);
1223
1224 if(lowerFirst)
1225 ret.operator=(QString::toLower());
1226
1227 bool wordStart = true;
1228 for(uint idx = 0, size = length(); idx < size; ++idx) {
1229 QCharRef chr = ret[idx];
1230 if(wordStart) {
1231 if(!wordSeparators.contains(chr)) {
1232 wordStart = false;
1233 chr = chr.toUpper();
1234 }
1235 } else if(wordSeparators.contains(chr)) {
1236 wordStart = true;
1237 }
1238 }
1239
1240 return ret;
1241 }
1242
1243 SString
toSentenceCase(bool lowerFirst,bool * cont) const1244 SString::toSentenceCase(bool lowerFirst, bool *cont) const
1245 {
1246 const QString sentenceEndChars(".?!");
1247
1248 SString ret(*this);
1249
1250 if(lowerFirst)
1251 ret.operator=(QString::toLower());
1252
1253 if(isEmpty())
1254 return ret;
1255
1256 uint prevDots = 0;
1257 bool startSentence = cont ? !*cont : true;
1258
1259 for(uint index = 0, size = length(); index < size; ++index) {
1260 QCharRef chr = ret[index];
1261
1262 if(sentenceEndChars.contains(chr)) {
1263 if(chr == '.') {
1264 prevDots++;
1265 startSentence = prevDots < 3;
1266 } else {
1267 prevDots = 0;
1268 startSentence = true;
1269 }
1270 } else {
1271 if(startSentence && chr.isLetterOrNumber()) {
1272 chr = chr.toUpper();
1273 startSentence = false;
1274 }
1275
1276 if(!chr.isSpace())
1277 prevDots = 0;
1278 }
1279 }
1280
1281 if(cont)
1282 *cont = prevDots != 1 && !startSentence;
1283
1284 return ret;
1285 }
1286
1287 SString
simplified() const1288 SString::simplified() const
1289 {
1290 const QRegExp simplifySpaceRegExp("\\s{2,MAXINT}");
1291
1292 return trimmed().replace(simplifySpaceRegExp, " ");
1293 }
1294
1295 SString
trimmed() const1296 SString::trimmed() const
1297 {
1298 const QRegExp trimRegExp("(^\\s+|\\s+$)");
1299
1300 SString ret(*this);
1301 return ret.remove(trimRegExp);
1302 }
1303
1304 void
simplifyWhiteSpace(QString & text)1305 SString::simplifyWhiteSpace(QString &text)
1306 {
1307 int di = 0;
1308 bool lastWasSpace = true;
1309 bool lastWasLineFeed = true;
1310 for(int i = 0, l = text.size(); i < l; i++) {
1311 const QChar ch = text.at(i);
1312 if(lastWasSpace && (ch == QChar::Space || ch == QChar::Tabulation)) // skip consecutive spaces
1313 continue;
1314 if(lastWasLineFeed && (ch == QChar::LineFeed || ch == QChar::CarriageReturn)) // skip consecutive newlines
1315 continue;
1316 if(lastWasSpace && (ch == QChar::LineFeed || ch == QChar::CarriageReturn)) // skip space before newline
1317 di--;
1318
1319 if(ch == QChar::Tabulation) // convert tab to space
1320 text[di] = QChar::Space;
1321 else if(ch == QChar::CarriageReturn) // convert cr to lf
1322 text[di] = QChar::LineFeed;
1323 else if(di != i) // copy other chars
1324 text[di] = ch;
1325
1326 lastWasLineFeed = text[di] == QChar::LineFeed;
1327 lastWasSpace = lastWasLineFeed || text[di] == QChar::Space;
1328
1329 di++;
1330 }
1331 if(lastWasLineFeed)
1332 di--;
1333 text.truncate(di);
1334 }
1335
1336 void
simplifyWhiteSpace()1337 SString::simplifyWhiteSpace()
1338 {
1339 int di = 0;
1340 bool lastWasSpace = true;
1341 bool lastWasLineFeed = true;
1342 for(int i = 0, l = size(); i < l; i++) {
1343 const QChar ch = at(i);
1344 if(lastWasSpace && (ch == QChar::Space || ch == QChar::Tabulation)) // skip consecutive spaces
1345 continue;
1346 if(lastWasLineFeed && (ch == QChar::LineFeed || ch == QChar::CarriageReturn)) // skip consecutive newlines
1347 continue;
1348 if(lastWasSpace && (ch == QChar::LineFeed || ch == QChar::CarriageReturn)) // skip space before newline
1349 di--;
1350
1351 if(ch == QChar::Tabulation) // convert tab to space
1352 operator[](di) = QChar::Space;
1353 else if(ch == QChar::CarriageReturn) // convert cr to lf
1354 operator[](di) = QChar::LineFeed;
1355 else if(di != i) // copy other chars
1356 operator[](di) = ch;
1357
1358 if(di != i) {
1359 m_styleFlags[di] = m_styleFlags[i];
1360 m_styleColors[di] = m_styleColors[i];
1361 }
1362
1363 lastWasLineFeed = at(di) == QChar::LineFeed;
1364 lastWasSpace = lastWasLineFeed || at(di) == QChar::Space;
1365
1366 di++;
1367 }
1368 if(lastWasLineFeed)
1369 di--;
1370 truncate(di);
1371 }
1372
1373 bool
operator !=(const SString & sstring) const1374 SString::operator!=(const SString &sstring) const
1375 {
1376 if(!(static_cast<const QString &>(*this) == static_cast<const QString &>(sstring)))
1377 return true;
1378
1379 for(int i = 0, sz = length(); i < sz; i++) {
1380 if(m_styleFlags[i] != sstring.m_styleFlags[i])
1381 return true;
1382 if((m_styleFlags[i] & Color) != 0 && m_styleColors[i] != sstring.m_styleColors[i])
1383 return true;
1384 }
1385
1386 return false;
1387 }
1388
1389 char *
detachFlags()1390 SString::detachFlags()
1391 {
1392 char *ret = m_styleFlags;
1393 m_styleFlags = nullptr;
1394 m_capacity = 0;
1395 return ret;
1396 }
1397
1398 QRgb *
detachColors()1399 SString::detachColors()
1400 {
1401 QRgb *ret = m_styleColors;
1402 m_styleColors = nullptr;
1403 m_capacity = 0;
1404 return ret;
1405 }
1406
1407 void
setMinFlagsCapacity(int capacity)1408 SString::setMinFlagsCapacity(int capacity)
1409 {
1410 if(capacity > m_capacity) {
1411 m_capacity = capacity * 2;
1412 delete[] m_styleFlags;
1413 m_styleFlags = new char[m_capacity];
1414 delete[] m_styleColors;
1415 m_styleColors = new QRgb[m_capacity];
1416 } else if(capacity == 0) {
1417 m_capacity = 0;
1418 delete[] m_styleFlags;
1419 m_styleFlags = nullptr;
1420 delete[] m_styleColors;
1421 m_styleColors = nullptr;
1422 } else if(m_capacity > 100 && capacity < m_capacity / 2) {
1423 m_capacity = m_capacity / 2;
1424 delete[] m_styleFlags;
1425 m_styleFlags = new char[m_capacity];
1426 delete[] m_styleColors;
1427 m_styleColors = new QRgb[m_capacity];
1428 }
1429 }
1430
SStringList()1431 SStringList::SStringList()
1432 {}
1433
SStringList(const SString & str)1434 SStringList::SStringList(const SString &str)
1435 {
1436 append(str);
1437 }
1438
SStringList(const SStringList & list)1439 SStringList::SStringList(const SStringList &list) :
1440 QList<SString>(list)
1441 {}
1442
SStringList(const QList<SString> & list)1443 SStringList::SStringList(const QList<SString> &list) :
1444 QList<SString>(list)
1445 {}
1446
SStringList(const QStringList & list)1447 SStringList::SStringList(const QStringList &list)
1448 {
1449 for(QStringList::ConstIterator it = list.begin(), end = list.end(); it != end; ++it)
1450 append(*it);
1451 }
1452
SStringList(const QList<QString> & list)1453 SStringList::SStringList(const QList<QString> &list)
1454 {
1455 for(QList<QString>::ConstIterator it = list.begin(), end = list.end(); it != end; ++it)
1456 append(*it);
1457 }
1458
1459 SString
join(const SString & sep) const1460 SStringList::join(const SString &sep) const
1461 {
1462 SString ret;
1463
1464 bool skipSeparator = true;
1465 for(SStringList::ConstIterator it = begin(), end = this->end(); it != end; ++it) {
1466 if(skipSeparator) {
1467 ret += *it;
1468 skipSeparator = false;
1469 continue;
1470 }
1471 ret += sep;
1472 ret += *it;
1473 }
1474
1475 return ret;
1476 }
1477