• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

gansm / finalcut / #767

09 Feb 2026 11:49PM UTC coverage: 69.07%. Remained the same
#767

push

travis-ci

gansm
More iterators and further code improvements

126 of 129 new or added lines in 7 files covered. (97.67%)

161 existing lines in 3 files now uncovered.

37625 of 54474 relevant lines covered (69.07%)

250.26 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

96.7
/final/util/fstring.cpp
1
/***********************************************************************
2
* fstring.cpp - Unicode string class with UTF-8 support                *
3
*                                                                      *
4
* This file is part of the FINAL CUT widget toolkit                    *
5
*                                                                      *
6
* Copyright 2012-2026 Markus Gans                                      *
7
*                                                                      *
8
* FINAL CUT is free software; you can redistribute it and/or modify    *
9
* it under the terms of the GNU Lesser General Public License as       *
10
* published by the Free Software Foundation; either version 3 of       *
11
* the License, or (at your option) any later version.                  *
12
*                                                                      *
13
* FINAL CUT is distributed in the hope that it will be useful, but     *
14
* WITHOUT ANY WARRANTY; without even the implied warranty of           *
15
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *
16
* GNU Lesser General Public License for more details.                  *
17
*                                                                      *
18
* You should have received a copy of the GNU Lesser General Public     *
19
* License along with this program.  If not, see                        *
20
* <http://www.gnu.org/licenses/>.                                      *
21
***********************************************************************/
22

23
#include <algorithm>
24
#include <string>
25
#include <utility>
26
#include <vector>
27

28
#include "final/fapplication.h"
29
#include "final/util/flog.h"
30
#include "final/util/fstring.h"
31

32
namespace finalcut
33
{
34

35
// Herper functions
36
template <typename T>
37
constexpr auto isExactlyEqual (T rhs, T lhs) noexcept -> bool
5✔
38
{
39
#if defined(__clang__)
40
  #pragma clang diagnostic push
41
  #pragma clang diagnostic ignored "-Wfloat-equal"
42
#endif
43

44
  return rhs == lhs;
5✔
45

46
#if defined(__clang__)
47
  #pragma clang diagnostic pop
48
#endif
49
}
50

51
// static class attributes
52
wchar_t       FString::null_char{L'\0'};
53
const wchar_t FString::const_null_char{L'\0'};
54

55
//----------------------------------------------------------------------
56
// class FString
57
//----------------------------------------------------------------------
58

59
// constructors and destructor
60
//----------------------------------------------------------------------
61
FString::FString (int len)
11✔
62
{
63
  if ( len > 0 )
11✔
64
    string.assign(size_type(len), L'\0');
7✔
65
}
11✔
66

67
//----------------------------------------------------------------------
68
FString::FString (size_type len)
1✔
69
{
70
  string.assign(len, L'\0');
1✔
71
}
1✔
72

73
//----------------------------------------------------------------------
74
FString::FString (size_type len, wchar_t c)
12✔
75
{
76
  string.assign(len, c);
12✔
77
}
12✔
78

79
//----------------------------------------------------------------------
80
FString::FString (size_type len, const UniChar& c)
2✔
81
{
82
  string.assign(len, wchar_t(c));
2✔
83
}
2✔
84

85
//----------------------------------------------------------------------
86
FString::FString (const FString& s)  // copy constructor
743✔
87
  : string{s.string}
743✔
88
{ }
743✔
89

90
//----------------------------------------------------------------------
91
FString::FString (FString&& s) noexcept  // move constructor
2,675✔
92
  : string{std::move(s.string)}
2,675✔
93
{ }
2,675✔
94

95
//----------------------------------------------------------------------
96
FString::FString (const std::wstring& s)
865✔
97
{
98
  string.assign(s);
865✔
99
}
865✔
100

101
//----------------------------------------------------------------------
102
#if __cplusplus >= 201703L
103
FString::FString (const std::wstring_view& s)
104
{
105
  if ( ! s.empty() )
106
    string.assign(s);
107
}
108
#endif
109

110
//----------------------------------------------------------------------
111
FString::FString (std::wstring&& s)
2,706✔
112
  : string{std::move(s)}
2,706✔
113
{ }
2,706✔
114

115
//----------------------------------------------------------------------
116
FString::FString (const wchar_t s[])
3,448✔
117
{
118
  if ( s )
3,448✔
119
    string.assign(s);
3,443✔
120
}
3,448✔
121

122
//----------------------------------------------------------------------
123
FString::FString (const std::string& s)
58✔
124
{
125
  if ( ! s.empty() )
58✔
126
    string.assign(internal_toWideString(s.c_str()));
55✔
127
}
58✔
128

129
//----------------------------------------------------------------------
130
#if __cplusplus >= 201703L
131
FString::FString (const std::string_view& s)
132
{
133
  if ( ! s.empty() )
134
    string.assign(internal_toWideString(s.data()));
135
}
136
#endif
137

138
//----------------------------------------------------------------------
139
FString::FString (const char s[])
2,837✔
140
{
141
  if ( s )
2,837✔
142
    string.assign(internal_toWideString(s));
2,492✔
143
}
2,837✔
144

145
//----------------------------------------------------------------------
146
FString::FString (const UniChar& c)
1✔
147
{
148
  string.assign(1, static_cast<wchar_t>(c));
1✔
149
}
1✔
150

151
//----------------------------------------------------------------------
152
FString::FString (const wchar_t c)
290✔
153
{
154
  if ( c )
290✔
155
    string.assign(1, static_cast<wchar_t>(c));
287✔
156
}
290✔
157

158
//----------------------------------------------------------------------
159
FString::FString (const char c)
129✔
160
{
161
  if ( c )
129✔
162
    string.assign(1, static_cast<wchar_t>(uChar(c)));
122✔
163
}
129✔
164

165
//----------------------------------------------------------------------
166
FString::~FString() noexcept = default;  // destructor
19,367✔
167

168

169
// FString operators
170
//----------------------------------------------------------------------
171
auto FString::operator = (const FString& s) -> FString&
100✔
172
{
173
  if ( &s != this )
100✔
174
    string.assign(s.string);
100✔
175

176
  return *this;
100✔
177
}
178

179
//----------------------------------------------------------------------
180
auto FString::operator = (FString&& s) noexcept -> FString&
775✔
181
{
182
  if ( &s != this )
775✔
183
    string.assign(s.string);
775✔
184

185
  return *this;
775✔
186
}
187

188
//----------------------------------------------------------------------
189
auto FString::operator += (const FString& s) -> const FString&
18✔
190
{
191
  string.append(s.string);
18✔
192
  return *this;
18✔
193
}
194

195
//----------------------------------------------------------------------
196
auto FString::operator << (const FString& s) -> FString&
8✔
197
{
198
  string.append(s.string);
8✔
199
  return *this;
8✔
200
}
201

202
//----------------------------------------------------------------------
203
auto FString::operator << (const UniChar& c) -> FString&
1✔
204
{
205
  FString s{static_cast<wchar_t>(c)};
1✔
206
  string.append(s.string);
1✔
207
  return *this;
1✔
208
}
1✔
209

210
//----------------------------------------------------------------------
211
auto FString::operator << (const wchar_t c) -> FString&
1✔
212
{
213
  FString s{c};
1✔
214
  string.append(s.string);
1✔
215
  return *this;
1✔
216
}
1✔
217

218
//----------------------------------------------------------------------
219
auto FString::operator << (const char c) -> FString&
1✔
220
{
221
  FString s{c};
1✔
222
  string.append(s.string);
1✔
223
  return *this;
1✔
224
}
1✔
225

226
//----------------------------------------------------------------------
227
FString::operator bool () const noexcept
83✔
228
{
229
  return ! isEmpty();
83✔
230
}
231

232
//----------------------------------------------------------------------
233
auto FString::operator () () const noexcept -> const FString&
3✔
234
{
235
  return *this;
3✔
236
}
237

238

239
// public methods of FString
240
//----------------------------------------------------------------------
241
auto FString::clear() noexcept -> FString&
45✔
242
{
243
  string.clear();
45✔
244
  return *this;
45✔
245
}
246

247
//----------------------------------------------------------------------
248
auto FString::reserve (size_type new_capacity) -> FString&
1✔
249
{
250
  string.reserve(new_capacity);
1✔
251
  return *this;
1✔
252
}
253

254
//----------------------------------------------------------------------
255
auto FString::shrink_to_fit() -> FString&
1✔
256
{
257
  string.shrink_to_fit();
1✔
258
  return *this;
1✔
259
}
260

261
//----------------------------------------------------------------------
262
auto FString::resize (size_type count) -> FString&
1✔
263
{
264
  string.resize(count);
1✔
265
  return *this;
1✔
266
}
267

268
//----------------------------------------------------------------------
269
auto FString::resize (size_type count, wchar_t c) -> FString&
1✔
270
{
271
  string.resize(count, c);
1✔
272
  return *this;
1✔
273
}
274

275
//----------------------------------------------------------------------
276
auto FString::wc_str() const noexcept -> const wchar_t*
73✔
277
{
278
  // Returns a constant wide character string
279

280
  return string.c_str();
73✔
281
}
282

283
//----------------------------------------------------------------------
284
auto FString::wc_str() noexcept -> wchar_t*
63✔
285
{
286
  // Returns a wide character string
287

288
  return const_cast<wchar_t*>(string.c_str());
63✔
289
}
290

291
//----------------------------------------------------------------------
292
auto FString::c_str() const -> const char*
14✔
293
{
294
  // Returns a constant c-string
295

296
  if ( isEmpty() )
14✔
297
    return "";
3✔
298

299
  char_string = internal_toCharString(string);
11✔
300
  return char_string.c_str();
11✔
301
}
302

303
//----------------------------------------------------------------------
304
auto FString::c_str() -> char*
87✔
305
{
306
  // Returns a c-string
307

308
  if ( isEmpty() )
87✔
309
    return const_cast<char*>("");
7✔
310

311
  char_string = internal_toCharString(string);
80✔
312
  return const_cast<char*>(char_string.c_str());
80✔
313
}
314

315
//----------------------------------------------------------------------
316
auto FString::toWString() const -> std::wstring
3,221✔
317
{
318
  return string;
3,221✔
319
}
320

321
//----------------------------------------------------------------------
322
auto FString::toString() const -> std::string
13✔
323
{
324
  return internal_toCharString(string);
13✔
325
}
326

327
//----------------------------------------------------------------------
328
auto FString::toLower() const -> FString
1✔
329
{
330
  FString str;
1✔
331
  str.string.reserve(string.length());
1✔
332
  constexpr wchar_t offset = L'a' - L'A';
1✔
333

334
  for (wchar_t c : string)
4✔
335
  {
336
    if ( c < 128 )  // Fast path for ASCII
3✔
337
    {
338
      const bool is_upper = (c >= L'A') && (c <= L'Z');
3✔
339
      str.string.push_back(is_upper ? c + offset : c);
3✔
340
    }
341
    else  // Slow path for non-ASCII (uses locale)
342
    {
UNCOV
343
      str.string.push_back(wchar_t(std::towupper(std::wint_t(c))));
×
344
    }
345
  }
346

347
  return str;
1✔
UNCOV
348
}
×
349

350
//----------------------------------------------------------------------
351
auto FString::toUpper() const -> FString
1✔
352
{
353
  FString str;
1✔
354
  str.string.reserve(string.length());
1✔
355
  constexpr wchar_t offset = L'a' - L'A';
1✔
356

357
  for (wchar_t c : string)
4✔
358
  {
359
    if ( c < 128 )  // Fast path for ASCII
3✔
360
    {
361
      const bool is_lower = (c >= L'a') && (c <= L'z');
3✔
362
      str.string.push_back(is_lower ? c - offset : c);
3✔
363
    }
364
    else  // Slow path for non-ASCII (uses locale)
365
    {
UNCOV
366
      str.string.push_back(wchar_t(std::towupper(std::wint_t(c))));
×
367
    }
368
  }
369

370
  return str;
1✔
UNCOV
371
}
×
372

373
//----------------------------------------------------------------------
374
auto FString::toShort() const -> sInt16
4✔
375
{
376
  const long value = toLong();
4✔
377

378
  if ( value > SHRT_MAX )
4✔
379
    throw std::overflow_error ("overflow");
1✔
380

381
  if ( value < SHRT_MIN )
3✔
382
    throw std::underflow_error ("underflow");
1✔
383

384
  return static_cast<sInt16>(value);
2✔
385
}
386

387
//----------------------------------------------------------------------
388
auto FString::toUShort() const -> uInt16
4✔
389
{
390
  const uLong value = toULong();
4✔
391

392
  if ( value > USHRT_MAX )
3✔
393
    throw std::overflow_error ("overflow");
1✔
394

395
  return static_cast<uInt16>(value);
2✔
396
}
397

398
//----------------------------------------------------------------------
399
auto FString::toInt() const -> int
95✔
400
{
401
  const long value = toLong();
95✔
402

403
  if ( value > INT_MAX )
95✔
404
    throw std::overflow_error ("overflow");
1✔
405

406
  if ( value < INT_MIN )
94✔
407
    throw std::underflow_error ("underflow");
1✔
408

409
  return static_cast<int>(value);
93✔
410
}
411

412
//----------------------------------------------------------------------
413
auto FString::toUInt() const -> uInt
12✔
414
{
415
  const uLong value = toULong();
12✔
416

417
  if ( value > UINT_MAX )
7✔
418
    throw std::overflow_error ("overflow");
1✔
419

420
  return static_cast<uInt>(value);
6✔
421
}
422

423
//----------------------------------------------------------------------
424
auto FString::toLong() const -> long
107✔
425
{
426
  // Find actual start and end (skip whitespace)
427
  auto iter = string.begin();
107✔
428
  const auto end = string.end();
107✔
429
  internal_skipLeadingWs(iter, end);  // Skip leading whitespace
107✔
430

431
  if ( iter == end )
107✔
432
    throw std::invalid_argument("empty value");
2✔
433

434
  const Sign sign = internal_parseSign(iter);
105✔
435

436
  if ( iter == end )
105✔
UNCOV
437
    throw std::invalid_argument ("no valid number");
×
438

439
  const long value = internal_parseDigits(iter, end, sign);
105✔
440

441
  while ( iter != end )
109✔
442
  {
443
    // Skip trailing whitespace
444
    if ( ! std::iswspace(static_cast<std::wint_t>(*iter)) )
7✔
445
      throw std::invalid_argument("no valid number");  // Trailing non-whitespace
1✔
446

447
    ++iter;
6✔
448
  }
449

450
  return sign == Sign::Negative ? (~value) + 1u : value;
204✔
451
}
452

453
//----------------------------------------------------------------------
454
auto FString::toULong() const -> uLong
27✔
455
{
456
  // Find actual start and end (skip whitespace)
457
  auto iter = string.begin();
27✔
458
  const auto end = string.end();
27✔
459
  internal_skipLeadingWs(iter, end);  // Skip leading whitespace
27✔
460

461

462
  if ( iter == end )
27✔
463
    throw std::invalid_argument("empty value");
4✔
464

465
  if ( internal_parseSign(iter) == Sign::Negative )  // Handle '-' sign
23✔
466
    throw std::underflow_error ("underflow");
5✔
467

468
  if ( iter == end  )
18✔
UNCOV
469
    throw std::invalid_argument("no valid number");
×
470

471
  const uLong value = internal_parseDigits(iter, end);
18✔
472

473
  if ( iter == string.begin() + ((*string.data() == L'+') ? 1 : 0) )
15✔
474
    throw std::invalid_argument("no valid number");  // No digits parsed
2✔
475

476
  while ( iter != end )
19✔
477
  {
478
    // Skip trailing whitespace
479
    if ( ! std::iswspace(static_cast<std::wint_t>(*iter)) )
6✔
UNCOV
480
      throw std::invalid_argument("no valid number");  // Trailing non-whitespace
×
481

482
    ++iter;
6✔
483
  }
484

485
  return value;
13✔
486
}
487

488
//----------------------------------------------------------------------
489
auto FString::toFloat() const -> float
6✔
490
{
491
  const double value{toDouble()};
6✔
492

493
  if ( value > double(FLT_MAX) || value < double(-FLT_MAX) )
6✔
494
    throw std::overflow_error ("overflow");
2✔
495

496
  if ( std::fabs(value) < double(FLT_EPSILON) )  // value == 0.0f
4✔
497
    throw std::underflow_error ("underflow");
1✔
498

499
  return static_cast<float>(value);
3✔
500
}
501

502
//----------------------------------------------------------------------
503
auto FString::toDouble() const -> double
15✔
504
{
505
  if ( isEmpty() )
15✔
506
    throw std::invalid_argument ("empty value");
2✔
507

508
  errno = 0;
13✔
509
  wchar_t* p{nullptr};
13✔
510
  const double ret = std::wcstod(string.c_str(), &p);
13✔
511

512
  if ( p == string.c_str() )
13✔
513
    throw std::invalid_argument("no valid floating point value");
1✔
514

515
  if ( p != nullptr && *p != L'\0' )
12✔
UNCOV
516
    throw std::invalid_argument ("no valid floating point value");
×
517

518
  if ( errno == ERANGE )
12✔
519
  {
520
    if ( isExactlyEqual(ret, HUGE_VAL) || isExactlyEqual(ret, -HUGE_VAL) )
3✔
521
      throw std::overflow_error ("overflow");
2✔
522

523
    if ( std::fabs(ret) < DBL_EPSILON )  // ret == 0.0l
1✔
524
      throw std::underflow_error ("underflow");
1✔
525
  }
526

527
  return ret;
9✔
528
}
529

530
//----------------------------------------------------------------------
531
auto FString::ltrim() const -> FString
20✔
532
{
533
  if ( isEmpty() )  // Handle empty string
20✔
534
    return {};
3✔
535

536
  // Find last non-whitespace character
537
  const auto pos = string.find_first_not_of(L" \t\n\r\f\v");
17✔
538

539
  if ( pos == npos )  // All whitespace
17✔
540
    return {};
7✔
541

542
  return string.substr(pos);
10✔
543
}
544

545
//----------------------------------------------------------------------
546
auto FString::rtrim() const -> FString
21✔
547
{
548
  if ( isEmpty() )  // Handle empty string
21✔
549
    return {};
4✔
550

551
  // Find last non-whitespace character
552
  const auto pos = string.find_last_not_of(L" \t\n\r\f\v");
17✔
553

554
  if ( pos == npos )  // All whitespace
17✔
555
    return {};
7✔
556

557
  return string.substr(0, pos + 1);
10✔
558
}
559

560
//----------------------------------------------------------------------
561
auto FString::trim() const -> FString
13✔
562
{
563
  if ( isEmpty() )  // Handle empty string
13✔
564
    return {};
3✔
565

566
  const auto first = string.find_first_not_of(L" \t\n\r\f\v");
10✔
567

568
  if ( first == npos )  // All whitespace
10✔
UNCOV
569
    return {};
×
570

571
  const auto last = string.find_last_not_of(L" \t\n\r\f\v");
10✔
572
  return string.substr(first, last - first + 1);
10✔
573
}
574

575
//----------------------------------------------------------------------
576
auto FString::left (size_type len) const -> FString
18✔
577
{
578
  // Handle empty and too long strings
579
  if ( isEmpty() || len > getLength() )
18✔
580
    return *this;
7✔
581

582
  return string.substr(0, len);
11✔
583
}
584

585
//----------------------------------------------------------------------
586
auto FString::right (size_type len) const -> FString
43✔
587
{
588
  // Handle empty and too long strings
589
  if ( isEmpty() || len > getLength() )
43✔
590
    return *this;
7✔
591

592
  return string.substr(string.size() - len, len);
36✔
593
}
594

595
//----------------------------------------------------------------------
596
auto FString::mid (size_type pos, size_type len) const -> FString
23✔
597
{
598
  // Handle empty string
599
  if ( isEmpty() || len == 0 )
23✔
600
    return {};
7✔
601

602
  if ( pos == 0 )
16✔
603
    pos = 1;
2✔
604

605
  const size_type start_index{pos - 1};
16✔
606
  const auto length{string.length()};
16✔
607

608
  if ( start_index >= length )
16✔
609
    return {};
2✔
610

611
  FString str{};
14✔
612
  len = std::min(len, length - start_index);
14✔
613
  str.string.reserve (len);
14✔
614
  str.string.assign (string, start_index, len);
14✔
615
  return str;
14✔
616
}
14✔
617

618
//----------------------------------------------------------------------
619
auto FString::split (const FString& delimiter) const -> FStringList
35✔
620
{
621
  if ( isEmpty() )
35✔
622
    return {};
2✔
623

624
  if ( delimiter.isEmpty() )
33✔
625
    return {*this};
3✔
626

627
  FStringList string_list{};
32✔
628
  const auto delimiter_length{delimiter.getLength()};
32✔
629
  std::wstring::size_type start{0};
32✔
630
  std::wstring::size_type pos{0};
32✔
631

632
  // Calculate result size and reserve
633
  size_type count{1};
32✔
634

635
  while ( (pos = string.find(delimiter.string, pos)) != npos )
101✔
636
  {
637
    count++;
69✔
638
    pos += delimiter_length;
69✔
639
  }
640

641
  if ( count == 1 )  // 'delimiter' not found
32✔
642
  {
643
    string_list.emplace_back(std::wstring(string));
1✔
644
    return string_list;
1✔
645
  }
646

647
  string_list.reserve(count);
31✔
648

649
  // Perform split
650
  while ( (pos = string.find(delimiter.string, start)) != npos )
100✔
651
  {
652
    string_list.emplace_back(std::wstring(string, start, pos - start));
69✔
653
    start = pos + delimiter_length;
69✔
654
  }
655

656
  string_list.emplace_back(std::wstring(string, start));
31✔
657
  return string_list;
31✔
658
}
33✔
659

660
//----------------------------------------------------------------------
661
auto FString::setString (const FString& s) -> FString&
68✔
662
{
663
  string.assign(s.string);
68✔
664
  return *this;
68✔
665
}
666

667
//----------------------------------------------------------------------
668
auto FString::setNumber (sInt64 value) -> FString&
13✔
669
{
670
  const auto is_negative = isNegative(value);
13✔
671
  auto abs_value = is_negative ? ~static_cast<uInt64>(value) + 1
13✔
672
                               : static_cast<uInt64>(value);
1✔
673

674
  std::array<wchar_t, NUMBER_BUFFER_SIZE> buf{};
13✔
675
  std::size_t pos = NUMBER_BUFFER_SIZE;  // End position
13✔
676

677
  do
678
  {
679
    --pos;
109✔
680
    buf[pos] = L'0' + wchar_t(abs_value % 10);
109✔
681
    abs_value /= 10;
109✔
682
  }
683
  while ( abs_value > 0 );
109✔
684

685
  if ( is_negative )
13✔
686
  {
687
    --pos;
12✔
688
    buf[pos] = L'-';
12✔
689
  }
690

691
  string.assign(&buf[pos], NUMBER_BUFFER_SIZE - pos);
13✔
692
  return *this;
13✔
693
}
694

695
//----------------------------------------------------------------------
696
auto FString::setNumber (uInt64 value) -> FString&
21✔
697
{
698
  std::array<wchar_t, NUMBER_BUFFER_SIZE> buf{};
21✔
699
  std::size_t pos = NUMBER_BUFFER_SIZE;  // End position
21✔
700

701
  do
702
  {
703
    --pos;
154✔
704
    buf[pos] = L'0' + wchar_t(value % 10);
154✔
705
    value /= 10;
154✔
706
  }
707
  while ( value > 0 );
154✔
708

709
  string.assign(&buf[pos], NUMBER_BUFFER_SIZE - pos);
21✔
710
  return *this;
21✔
711
}
712

713
//----------------------------------------------------------------------
714
auto FString::setNumber (lDouble f_value, int precision) -> FString&
12✔
715
{
716
  // The precision can not have more than 2 digits
717
  precision = std::min(precision, 99);
12✔
718

719
  std::array<wchar_t, 8> format{};  // = "%.<precision>Lg"
12✔
720
  std::size_t pos{0};
12✔
721
  format[pos] = L'%';
12✔
722
  pos++;
12✔
723
  format[pos] = L'.';
12✔
724
  pos++;
12✔
725

726
  if ( precision >= 10 )
12✔
727
  {
728
    // The precision value is 2 digits long
729
    format[pos] = precision / 10 + L'0';
7✔
730
    pos++;
7✔
731
    format[pos] = precision % 10 + L'0';
7✔
732
    pos++;
7✔
733
  }
734
  else
735
  {
736
    // The precision value has only 1 digit
737
    format[pos] = precision + L'0';
5✔
738
    pos++;
5✔
739
  }
740

741
  format[pos] = L'L';
12✔
742
  pos++;
12✔
743
  format[pos] = L'g';
12✔
744
  pos++;
12✔
745
  format[pos]   = L'\0';
12✔
746

747
  return sprintf(format.data(), f_value);
12✔
748
}
749

750
//----------------------------------------------------------------------
751
auto FString::setFormatedNumber (sInt64 value, FString&& separator) -> FString&
5✔
752
{
753
  return internal_setFormatedNumber (value, std::move(separator));
5✔
754
}
755

756
//----------------------------------------------------------------------
757
auto FString::setFormatedNumber (uInt64 value, FString&& separator) -> FString&
6✔
758
{
759
  return internal_setFormatedNumber (value, std::move(separator));
6✔
760
}
761

762
// FString operators
763
//----------------------------------------------------------------------
764
auto FString::insert (const FString& s, int pos) -> const FString&
23✔
765
{
766
  if ( isNegative(pos) || uInt(pos) > string.length() )
23✔
767
    throw std::out_of_range("FString::insert index out of range");
4✔
768

769
  string.insert(uInt(pos), s.string, 0, s.getLength());
19✔
770
  return *this;
19✔
771
}
772

773
//----------------------------------------------------------------------
774
auto FString::insert (const FString& s, size_type pos) -> const FString&
1✔
775
{
776
  if ( pos > string.length() )
1✔
UNCOV
777
    throw std::out_of_range("");
×
778

779
  string.insert(uInt(pos), s.string, 0, s.getLength());
1✔
780
  return *this;
1✔
781
}
782

783
//----------------------------------------------------------------------
784
auto FString::replace (const FString& from, const FString& to) const -> FString
78✔
785
{
786
  // Handle empty strings
787
  if ( isEmpty() || from.isEmpty() )
78✔
788
    return *this;
7✔
789

790
  // Count occurrences to reserve exact capacity
791
  size_type count{0};
71✔
792
  std::wstring::size_type pos{0};
71✔
793

794
  while ( (pos = string.find(from.string, pos)) != npos )
136✔
795
  {
796
    count++;
65✔
797
    pos += from.getLength();
65✔
798
  }
799

800
  if ( count == 0 )  //  String 'from' not found
71✔
801
    return *this;
12✔
802

803
  // Calculate result size and reserve
804
  const size_type from_len{from.getLength()};
59✔
805
  const size_type to_len{to.getLength()};
59✔
806
  const size_type new_size{string.length() + count * (to_len - from_len)};
59✔
807

808
  FString str{};
59✔
809
  str.string.reserve(new_size);
59✔
810
  size_type last_pos{0};
59✔
811
  pos = 0;
59✔
812

813
  while ( (pos = string.find(from.string, pos)) != npos )
124✔
814
  {
815
    str.string.append (string, last_pos, pos - last_pos);  // Before
65✔
816
    str.string.append (to.string);  // The string replacement
65✔
817
    pos += from_len;
65✔
818
    last_pos = pos;
65✔
819
  }
820

821
  str.string.append (string, last_pos, npos);  // After
59✔
822
  return str;
59✔
823
}
59✔
824

825
//----------------------------------------------------------------------
826
auto FString::replaceControlCodes() const -> FString
24✔
827
{
828
  FString str{};
24✔
829
  str.string.reserve(string.length());
24✔
830

831
  for (auto c : string)
224✔
832
  {
833
    if ( c <= L'\x1f' )
200✔
834
    {
835
      str.string.push_back(c + L'\x2400');
72✔
836
    }
837
    else if ( c == L'\x7f' )
128✔
838
    {
839
      str.string.push_back(L'\x2421');
4✔
840
    }
841
    else if ( (c >= L'\x80' && c <= L'\x9f') || ! isPrintable(c) )
124✔
842
    {
843
      str.string.push_back(L' ');
36✔
844
    }
845
    else
846
      str.string.push_back(c);
88✔
847
  }
848

849
  return str;
24✔
UNCOV
850
}
×
851

852
//----------------------------------------------------------------------
853
auto FString::expandTabs (int tabstop) const -> FString
50✔
854
{
855
  if (tabstop <= 0)
50✔
856
    return *this;
2✔
857

858
  FString str{};
48✔
859
  auto tab_stop = size_type(tabstop);
48✔
860
  auto tab_count = size_type(std::count(string.begin(), string.end(), L'\t'));
48✔
861
  str.string.reserve(string.length() + (tab_count * tab_stop));
48✔
862
  size_type column{0};
48✔
863

864
  for (wchar_t c : string)
564✔
865
  {
866
    if ( c == L'\t' )
516✔
867
    {
868
      // Calculate spaces needed to reach next tab stop
869
      const size_type spaces{tab_stop - (column % tab_stop)};
76✔
870
      str.string.append(spaces, L' ');
76✔
871
      column += spaces;
76✔
872
    }
873
    else if ( c == L'\n' || c == L'\r' )
440✔
874
    {
UNCOV
875
      str.string.push_back(c);
×
UNCOV
876
      column = 0;  // Reset column on newline
×
877
    }
878
    else
879
    {
880
      str.string.push_back(c);
440✔
881
      ++column;
440✔
882
    }
883
  }
884

885
  return str;
48✔
886
}
48✔
887

888
//----------------------------------------------------------------------
889
auto FString::removeDel() const -> FString
23✔
890
{
891
  FString str{};
23✔
892
  str.string.reserve(string.length());
23✔
893
  size_type del_count{0};
23✔
894

895
  for (const auto c : string)
151✔
896
  {
897
    if ( c == L'\x7f' )
128✔
898
    {
899
      del_count++;
48✔
900
    }
901
    else if ( del_count > 0 )
80✔
902
    {
903
      del_count--;
33✔
904
    }
905
    else  // del_count == 0
906
    {
907
      str.string.push_back(c);
47✔
908
    }
909
  }
910

911
  return str;
23✔
UNCOV
912
}
×
913

914

915
//----------------------------------------------------------------------
916
auto FString::removeBackspaces() const -> FString
27✔
917
{
918
  FString str{};
27✔
919
  str.string.reserve(string.length());
27✔
920

921
  for (const auto c : string)
159✔
922
  {
923
    if ( c != L'\b' )
132✔
924
    {
925
      str.string.push_back(c);
78✔
926
    }
927
    else if ( ! str.string.empty() )
54✔
928
      str.string.pop_back();
30✔
929
  }
930

931
  return str;
27✔
UNCOV
932
}
×
933

934
//----------------------------------------------------------------------
935
auto FString::overwrite (const FString& s, int pos) -> FString&
14✔
936
{
937
  if ( pos < 0 )
14✔
938
    return overwrite (s, 0);
3✔
939

940
  return overwrite (s, size_type(pos));
11✔
941
}
942

943
//----------------------------------------------------------------------
944
auto FString::overwrite (const FString& s, size_type pos) -> FString&
16✔
945
{
946
  const auto length{string.length()};
16✔
947

948
  if ( pos > length )
16✔
949
    pos = length;
3✔
950

951
  string.replace(pos, s.getLength(), s.string);
16✔
952
  return *this;
16✔
953
}
954

955
//----------------------------------------------------------------------
956
auto FString::remove (size_type pos, size_type len) -> FString&
33✔
957
{
958
  const auto length{string.length()};
33✔
959

960
  if ( pos > length )
33✔
961
    return *this;
1✔
962

963
  if ( pos + len > length )
32✔
964
    len = length - pos;
3✔
965

966
  string.erase (pos, len);
32✔
967
  return *this;
32✔
968
}
969

970
//----------------------------------------------------------------------
971
auto FString::erase (size_type pos, size_type len) -> FString&
5✔
972
{
973
  string.erase (pos, len);
5✔
974
  return *this;
5✔
975
}
976

977
//----------------------------------------------------------------------
978
auto FString::includes (const FString& s) const noexcept -> bool
60✔
979
{
980
  if ( s.isEmpty() )
60✔
981
    return false;
8✔
982

983
  return string.find(s.string) != npos;
52✔
984
}
985

986
//----------------------------------------------------------------------
987
auto FString::contains (const FString& s) const noexcept -> bool
4✔
988
{
989
  return includes(s);
4✔
990
}
991

992
//----------------------------------------------------------------------
993
auto FString::find (const FString& s, size_type pos) const noexcept -> size_type
5✔
994
{
995
  return string.find(s.string, pos);
5✔
996
}
997

998
//----------------------------------------------------------------------
999
auto FString::rfind (const FString& s, size_type pos) const noexcept -> size_type
5✔
1000
{
1001
  return string.rfind(s.string, pos);
5✔
1002
}
1003

1004

1005
// private methods of FString
1006
//----------------------------------------------------------------------
1007
auto FString::internal_toCharString (const std::wstring& s) const -> std::string
1,050✔
1008
{
1009
  if ( s.empty() )
1,050✔
1010
    return {};
157✔
1011

1012
  auto state{std::mbstate_t()};
893✔
1013
  const wchar_t* src = s.c_str();
893✔
1014
  const auto size = std::wcsrtombs(nullptr, &src, 0, &state);
893✔
1015

1016
  if ( size == MALFORMED_STRING )
893✔
UNCOV
1017
    return {};
×
1018

1019
  std::string dest{};
893✔
1020
  dest.resize(size);
893✔
1021
  auto dest_data = const_cast<char*>(dest.data());
893✔
1022
  const auto mblength = std::wcsrtombs (dest_data, &src, size + 1, &state);
893✔
1023

1024
  if ( mblength == MALFORMED_STRING && errno != EILSEQ )
893✔
UNCOV
1025
    return {};
×
1026

1027
  return dest;
893✔
1028
}
893✔
1029

1030
//----------------------------------------------------------------------
1031
auto FString::internal_toWideString (const char src[]) const -> std::wstring
2,548✔
1032
{
1033
  if ( ! src || *src == '\0' )
2,548✔
1034
    return {};
64✔
1035

1036
  auto state{std::mbstate_t()};
2,484✔
1037
  auto size = std::mbsrtowcs(nullptr, &src, 0, &state);
2,484✔
1038

1039
  if ( size == MALFORMED_STRING )
2,484✔
UNCOV
1040
    return {};
×
1041

1042
  std::wstring dest(size, L'\0');
2,484✔
1043
  auto dest_data = const_cast<wchar_t*>(dest.data());
2,484✔
1044
  const auto wide_length = std::mbsrtowcs (dest_data, &src, size + 1, &state);
2,484✔
1045

1046
  if ( wide_length == MALFORMED_STRING )
2,484✔
UNCOV
1047
    return {};
×
1048

1049
  return dest;
2,484✔
1050
}
2,484✔
1051

1052
//----------------------------------------------------------------------
1053
inline void FString::internal_skipLeadingWs ( const_iterator& iter
134✔
1054
                                            , const_iterator end ) const noexcept
1055
{
1056
  // Skip over leading whitespace in a string
1057

1058
  while ( iter != end && std::iswspace(static_cast<std::wint_t>(*iter)) )
146✔
1059
    ++iter;
12✔
1060
}
134✔
1061

1062
//----------------------------------------------------------------------
1063
inline auto FString::internal_parseSign (const_iterator& iter) const noexcept -> Sign
128✔
1064
{
1065
  // Extract sign and increment pointer
1066

1067
  if ( *iter == L'-' )  // Handle '-' sign
128✔
1068
  {
1069
    ++iter;
14✔
1070
    return Sign::Negative;
14✔
1071
  }
1072

1073
  if ( *iter == L'+' )  // Handle '+' sign
114✔
1074
    ++iter;
2✔
1075

1076
  return Sign::Positive;
114✔
1077
}
1078

1079
//----------------------------------------------------------------------
1080
inline auto FString::internal_parseDigits ( const_iterator& iter
105✔
1081
                                          , const_iterator end
1082
                                          , Sign sign ) const -> long
1083
{
1084
  // Parse digits and detect overflow/underflow
1085

1086
  long value{0};
105✔
1087

1088
  while ( iter != end )
392✔
1089
  {
1090
    const wchar_t c{*iter};
291✔
1091

1092
    if ( c < L'0' || c > L'9' )  // Digit check
291✔
1093
      break;
1094

1095
    const auto digit{long(c - L'0')};
289✔
1096

1097
    if ( internal_isOverflowed(sign, value, digit) )
289✔
1098
    {
1099
      if ( sign == Sign::Negative )
2✔
1100
        throw std::underflow_error("underflow");
1✔
1101

1102
      throw std::overflow_error("overflow");
1✔
1103
    }
1104

1105
    value = (value * 10) + digit;
287✔
1106
    ++iter;
287✔
1107
  }
1108

1109
  return value;
103✔
1110
}
1111

1112
//----------------------------------------------------------------------
1113
inline auto FString::internal_parseDigits ( const_iterator& iter
18✔
1114
                                          , const_iterator end ) const -> uLong
1115
{
1116
  // Parse digits and detect overflow/underflow
1117

1118
  uLong value{0};
18✔
1119

1120
  while ( iter != end )
170✔
1121
  {
1122
    const wchar_t c{*iter};
158✔
1123

1124
    if ( c < L'0' || c > L'9' )  // Digit check
158✔
1125
      break;
1126

1127
    const auto digit{uLong(c - L'0')};
155✔
1128

1129
    if ( internal_isOverflowed(value, digit) )
155✔
1130
      throw std::overflow_error("overflow");
3✔
1131

1132
    value = (value * 10) + digit;
152✔
1133
    ++iter;
152✔
1134
  }
1135

1136
  return value;
15✔
1137
}
1138

1139
//----------------------------------------------------------------------
1140
inline auto FString::internal_isOverflowed ( Sign sign
289✔
1141
                                           , long value
1142
                                           , long digit ) const noexcept -> bool
1143
{
1144
  const long limit_digit = ( sign == Sign::Negative ) ? LONG_MIN_LIMIT_DIGIT
289✔
1145
                                                      : LONG_MAX_LIMIT_DIGIT;
1146
  return ( value > LONG_LIMIT )
1147
      || ( value == LONG_LIMIT && digit > limit_digit );
289✔
1148
}
1149

1150
//----------------------------------------------------------------------
1151
inline auto FString::internal_isOverflowed ( uLong value
155✔
1152
                                           , uLong digit ) const noexcept -> bool
1153
{
1154
  return ( value > ULONG_LIMIT )
1155
      || ( value == ULONG_LIMIT && digit > ULONG_LIMIT_DIGIT );
155✔
1156
}
1157

1158
//----------------------------------------------------------------------
1159
template <typename NumT>
1160
inline auto FString::internal_setFormatedNumber (NumT value, FString separator) -> FString&
11✔
1161
{
1162
  const auto is_negative = isNegative(value);
11✔
1163
  using SignedT = typename std::make_signed<NumT>::type;
1164
  auto abs_value = is_negative
11✔
1165
                 ? uInt64(-(static_cast<SignedT>(value + 1))) + 1ULL
5✔
1166
                 : uInt64(value);
2✔
1167

1168
  std::array<wchar_t, NUMBER_BUFFER_SIZE> buf{};
11✔
1169
  std::size_t pos{NUMBER_BUFFER_SIZE};  // End position
11✔
1170
  int digits{0};
11✔
1171

1172
  const wchar_t sep_char = ( separator[0] == 0 ) ? L' ' : separator[0];
11✔
1173

1174
  do
1175
  {
1176
    if ( digits > 0 && digits % 3 == 0 )  // Insert separators if digits follow
149✔
1177
    {
1178
      --pos;
44✔
1179
      buf[pos] = sep_char;
44✔
1180
    }
1181

1182
    --pos;
149✔
1183
    buf[pos] = L'0' + static_cast<wchar_t>(abs_value % 10);
149✔
1184
    abs_value /= 10;
149✔
1185
    digits++;
149✔
1186
  }
1187
  while ( abs_value > 0 );
149✔
1188

1189
  if ( is_negative )
5✔
1190
  {
1191
    --pos;
3✔
1192
    buf[pos] = L'-';
3✔
1193
  }
1194

1195
  string.assign(&buf[pos], NUMBER_BUFFER_SIZE - pos);
11✔
1196
  return *this;
11✔
1197
}
1198

1199

1200
// FString non-member operators
1201
//----------------------------------------------------------------------
1202
auto FStringCaseCompare (const FString& s1, const FString& s2) -> int
31✔
1203
{
1204
  if ( &s1 == &s2 )
31✔
1205
    return 0;
1✔
1206

1207
  auto iter1 = s1.cbegin();
30✔
1208
  auto iter2 = s2.cbegin();
30✔
1209
  const auto end1 = s1.cend();
30✔
1210
  const auto end2 = s2.cend();
30✔
1211

1212
  while ( iter1 != end1 && iter2 != end2 )
127✔
1213
  {
1214
    auto c1 = *iter1;
113✔
1215
    auto c2 = *iter2;
113✔
1216

1217
    // Convert to lowercase
1218
    if ( c1 >= L'A' && c1 <= L'Z' )
113✔
1219
      c1 += 32;
28✔
1220

1221
    if ( c2 >= L'A' && c2 <= L'Z' )
113✔
1222
      c2 += 32;
15✔
1223

1224
    if ( c1 != c2 )
113✔
1225
      return c1 - c2;
16✔
1226

1227
    ++iter1;
97✔
1228
    ++iter2;
97✔
1229
  }
1230

1231
  // Handle remaining characters
1232
  if ( iter1 != end1 )
14✔
1233
    return 1;  // s1 is longer
4✔
1234

1235
  if ( iter2 != end2 )
10✔
1236
    return -1; // s2 is longer
4✔
1237

1238
  return 0;    // Equal
6✔
1239
}
1240

1241

1242
}  // namespace finalcut
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc