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('<', "&lt;").replace('>', "&gt;");
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('<', "&lt;").replace('>', "&gt;");
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("&lt;", "<").replace("&gt;", ">")*/, 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 &regExp, 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 &regExp, 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