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

xlnt-community / xlnt / 0c9fe53d-9c91-4214-bafb-b83fcb6e8e38

26 Jan 2026 10:24PM UTC coverage: 83.928% (+1.1%) from 82.793%
0c9fe53d-9c91-4214-bafb-b83fcb6e8e38

Pull #87

circleci

doomlaur
Add documentation on string encodings
Pull Request #87: Improve documentation when exceptions are thrown (fixes #81)

15263 of 19954 branches covered (76.49%)

722 of 901 new or added lines in 37 files covered. (80.13%)

15 existing lines in 5 files now uncovered.

12491 of 14883 relevant lines covered (83.93%)

12216.5 hits per line

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

92.49
./source/cell/cell.cpp
1
// Copyright (c) 2014-2022 Thomas Fussell
2
// Copyright (c) 2010-2015 openpyxl
3
// Copyright (c) 2024-2025 xlnt-community
4
//
5
// Permission is hereby granted, free of charge, to any person obtaining a copy
6
// of this software and associated documentation files (the "Software"), to deal
7
// in the Software without restriction, including without limitation the rights
8
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
// copies of the Software, and to permit persons to whom the Software is
10
// furnished to do so, subject to the following conditions:
11
//
12
// The above copyright notice and this permission notice shall be included in
13
// all copies or substantial portions of the Software.
14
//
15
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
// THE SOFTWARE
22
//
23
// @license: http://www.opensource.org/licenses/mit-license.php
24
// @author: see AUTHORS file
25

26
#include <algorithm>
27
#include <cassert>
28
#include <cmath>
29

30
#include <xlnt/cell/cell.hpp>
31
#include <xlnt/cell/cell_reference.hpp>
32
#include <xlnt/cell/comment.hpp>
33
#include <xlnt/cell/hyperlink.hpp>
34
#include <xlnt/cell/rich_text.hpp>
35
#include <xlnt/packaging/manifest.hpp>
36
#include <xlnt/packaging/relationship.hpp>
37
#include <xlnt/styles/alignment.hpp>
38
#include <xlnt/styles/border.hpp>
39
#include <xlnt/styles/color.hpp>
40
#include <xlnt/styles/fill.hpp>
41
#include <xlnt/styles/font.hpp>
42
#include <xlnt/styles/format.hpp>
43
#include <xlnt/styles/number_format.hpp>
44
#include <xlnt/styles/protection.hpp>
45
#include <xlnt/styles/style.hpp>
46
#include <xlnt/utils/date.hpp>
47
#include <xlnt/utils/datetime.hpp>
48
#include <xlnt/utils/exceptions.hpp>
49
#include <xlnt/utils/time.hpp>
50
#include <xlnt/utils/timedelta.hpp>
51
#include <xlnt/workbook/workbook.hpp>
52
#include <xlnt/worksheet/column_properties.hpp>
53
#include <xlnt/worksheet/phonetic_pr.hpp>
54
#include <xlnt/worksheet/row_properties.hpp>
55
#include <xlnt/worksheet/worksheet.hpp>
56
#include <detail/implementations/cell_impl.hpp>
57
#include <detail/implementations/format_impl.hpp>
58
#include <detail/implementations/hyperlink_impl.hpp>
59
#include <detail/implementations/stylesheet.hpp>
60
#include <detail/implementations/worksheet_impl.hpp>
61
#include <detail/serialization/serialisation_helpers.hpp>
62

63
namespace {
64

65
std::pair<bool, double> cast_numeric(const std::string &s)
11✔
66
{
67
    size_t len_convert = 0;
11✔
68
    double result = xlnt::detail::deserialise(s, &len_convert);
11✔
69
    return (len_convert != s.size())
11✔
70
        ? std::make_pair(false, 0.0)
11✔
71
        : std::make_pair(true, result);
11✔
72
}
73

74
std::pair<bool, double> cast_percentage(const std::string &s)
14✔
75
{
76
    if (s.back() == '%')
14✔
77
    {
78
        auto number = cast_numeric(s.substr(0, s.size() - 1));
1✔
79

80
        if (number.first)
1!
81
        {
82
            return {true, number.second / 100};
1✔
83
        }
84
    }
85

86
    return {false, 0.0};
13✔
87
}
88

89
std::pair<bool, xlnt::time> cast_time(const std::string &s)
13✔
90
{
91
    xlnt::time result;
13✔
92

93
    std::vector<std::string> time_components;
13✔
94
    std::size_t prev = 0;
13✔
95
    auto colon_index = s.find(':');
13✔
96

97
    while (colon_index != std::string::npos)
18✔
98
    {
99
        time_components.push_back(s.substr(prev, colon_index - prev));
5✔
100
        prev = colon_index + 1;
5✔
101
        colon_index = s.find(':', colon_index + 1);
5✔
102
    }
103

104
    time_components.push_back(s.substr(prev, colon_index - prev));
13✔
105

106
    if (time_components.size() < 2 || time_components.size() > 3)
13!
107
    {
108
        return {false, result};
9✔
109
    }
110

111
    std::vector<double> numeric_components;
4✔
112

113
    for (const auto & component : time_components)
12✔
114
    {
115
        if (component.empty() || (component.substr(0, component.find('.')).size() > 2))
9!
116
        {
117
            return {false, result};
1✔
118
        }
119

120
        for (auto d : component)
34✔
121
        {
122
            if (!(d >= '0' && d <= '9') && d != '.')
26!
123
            {
124
                return {false, result};
×
125
            }
126
        }
127
        auto numeric = xlnt::detail::deserialise(component);
8✔
128

129
        numeric_components.push_back(numeric);
8✔
130
    }
131

132
    result.hour = static_cast<int>(numeric_components[0]);
3✔
133
    result.minute = static_cast<int>(numeric_components[1]);
3✔
134

135
    if (std::fabs(static_cast<double>(result.minute) - numeric_components[1]) > std::numeric_limits<double>::epsilon())
3✔
136
    {
137
        result.minute = result.hour;
1✔
138
        result.hour = 0;
1✔
139
        result.second = static_cast<int>(numeric_components[1]);
1✔
140
        result.microsecond = static_cast<int>((numeric_components[1] - result.second) * 1E6);
1✔
141
    }
142
    else if (numeric_components.size() > 2)
2✔
143
    {
144
        result.second = static_cast<int>(numeric_components[2]);
1✔
145
        result.microsecond = static_cast<int>((numeric_components[2] - result.second) * 1E6);
1✔
146
    }
147

148
    return {true, result};
3✔
149
}
13✔
150

151
} // namespace
152

153
namespace xlnt {
154

155
const std::unordered_map<std::string, int> &cell::error_codes()
1✔
156
{
157
    static const auto codes = std::unordered_map<std::string, int>{
158
        {"#NULL!", 0},
×
159
        {"#DIV/0!", 1},
×
160
        {"#VALUE!", 2},
×
161
        {"#REF!", 3},
×
162
        {"#NAME?", 4},
×
163
        {"#NUM!", 5},
×
164
        {"#N/A!", 6}};
10!
165

166
    return codes;
1✔
167
}
1!
168

169
std::string cell::check_string(const std::string &to_check)
651✔
170
{
171
    // so we can modify it
172
    std::string s = to_check;
651✔
173

174
    if (s.size() == 0)
651✔
175
    {
176
        return s;
2✔
177
    }
178
    else if (s.size() > 32767)
649✔
179
    {
180
        s = s.substr(0, 32767); // max string length in Excel
1✔
181
    }
182

183
    for (char c : s)
68,729✔
184
    {
185
        if (c >= 0 && (c <= 8 || c == 11 || c == 12 || (c >= 14 && c <= 31)))
68,109!
186
        {
187
            throw illegal_character(c);
29✔
188
        }
189
    }
190

191
    return s;
620✔
192
}
29✔
193

194
cell::cell(detail::cell_impl *d)
3,376✔
195
    : d_(d)
3,376✔
196
{
197
}
3,376✔
198

199
bool cell::garbage_collectible() const
996✔
200
{
201
    return d_->is_garbage_collectible();
996✔
202
}
203

204
void cell::value(std::nullptr_t)
1✔
205
{
206
    clear_value();
1✔
207
}
1✔
208

209
void cell::value(bool boolean_value)
10✔
210
{
211
    d_->type_ = type::boolean;
10✔
212
    d_->value_numeric_ = boolean_value ? 1.0 : 0.0;
10✔
213
}
10✔
214

215
void cell::value(int int_value)
14✔
216
{
217
    d_->value_numeric_ = static_cast<double>(int_value);
14✔
218
    d_->type_ = type::number;
14✔
219
}
14✔
220

221
void cell::value(unsigned int int_value)
2✔
222
{
223
    d_->value_numeric_ = static_cast<double>(int_value);
2✔
224
    d_->type_ = type::number;
2✔
225
}
2✔
226

227
void cell::value(long long int int_value)
2✔
228
{
229
    d_->value_numeric_ = static_cast<double>(int_value);
2✔
230
    d_->type_ = type::number;
2✔
231
}
2✔
232

233
void cell::value(unsigned long long int int_value)
2✔
234
{
235
    d_->value_numeric_ = static_cast<double>(int_value);
2✔
236
    d_->type_ = type::number;
2✔
237
}
2✔
238

239
void cell::value(float float_value)
2✔
240
{
241
    d_->value_numeric_ = static_cast<double>(float_value);
2✔
242
    d_->type_ = type::number;
2✔
243
}
2✔
244

245
void cell::value(double float_value)
6✔
246
{
247
    d_->value_numeric_ = static_cast<double>(float_value);
6✔
248
    d_->type_ = type::number;
6✔
249
}
6✔
250

251
void cell::value(const std::string &s)
340✔
252
{
253
    value(rich_text(check_string(s)));
340✔
254
}
311✔
255

256
void cell::value(const rich_text &text)
311✔
257
{
258
    check_string(text.plain_text());
311✔
259

260
    value_no_check(text);
311✔
261
}
311✔
262

263
void cell::value(const char *c)
138✔
264
{
265
    value(std::string(c));
138✔
266
}
138✔
267

268
void cell::value(const cell c)
7✔
269
{
270
    if (c.worksheet().workbook() != worksheet().workbook())
7✔
271
    {
272
        copy_from_other_workbook(c);
5✔
273
        return;
5✔
274
    }
275

276
    // Same workbook: shallow copy (existing behavior)
277
    d_->type_ = c.d_->type_;
2✔
278
    d_->value_numeric_ = c.d_->value_numeric_;
2✔
279
    d_->value_text_ = c.d_->value_text_;
2✔
280
    d_->hyperlink_ = c.d_->hyperlink_;
2✔
281
    d_->formula_ = c.d_->formula_;
2✔
282
    d_->format_ = c.d_->format_;
2✔
283
}
284

285
void cell::value_no_check(const rich_text &text)
314✔
286
{
287
    d_->type_ = type::shared_string;
314✔
288
    d_->value_numeric_ = static_cast<double>(workbook().add_shared_string(text));
314✔
289
}
314✔
290

291
void cell::copy_from_other_workbook(const cell &source)
5✔
292
{
293
    d_->type_ = source.d_->type_;
5✔
294

295
    // Handle shared_string: remap to destination workbook
296
    if (source.data_type() == type::shared_string)
5✔
297
    {
298
        value_no_check(source.value<rich_text>());
3✔
299
    }
300
    else
301
    {
302
        d_->value_numeric_ = source.d_->value_numeric_;
2✔
303
    }
304

305
    d_->value_text_ = source.d_->value_text_;
5✔
306
    d_->formula_ = source.d_->formula_;
5✔
307

308
    // Copy external hyperlinks; internal hyperlinks (cell/range references)
309
    // are not yet implemented as they would need worksheet title remapping.
310
    // TODO: implement internal hyperlink remapping.
311
    if (source.has_hyperlink())
5✔
312
    {
313
        auto copy_hyperlink = source.hyperlink();
1✔
314

315
        if (copy_hyperlink.external())
1!
316
        {
317
            hyperlink(copy_hyperlink.url(), copy_hyperlink.display());
1✔
318
        }
319
    }
320

321
    if (source.has_format())
5✔
322
    {
323
        format(source.format());
1✔
324
    }
325
    else
326
    {
327
        d_->format_.clear();
4✔
328
    }
329
}
5✔
330

331
void cell::value(const date &d)
3✔
332
{
333
    d_->type_ = type::number;
3✔
334
    d_->value_numeric_ = d.to_number(base_date());
3✔
335
    number_format(number_format::date_yyyymmdd2());
3✔
336
}
3✔
337

338
void cell::value(const datetime &d)
5✔
339
{
340
    d_->type_ = type::number;
5✔
341
    d_->value_numeric_ = d.to_number(base_date());
5✔
342
    number_format(number_format::date_datetime());
5✔
343
}
5✔
344

345
void cell::value(const time &t)
2✔
346
{
347
    d_->type_ = type::number;
2✔
348
    d_->value_numeric_ = t.to_number();
2✔
349
    number_format(number_format::date_time6());
2✔
350
}
2✔
351

352
void cell::value(const timedelta &t)
2✔
353
{
354
    d_->type_ = type::number;
2✔
355
    d_->value_numeric_ = t.to_number();
2✔
356
    number_format(xlnt::number_format("[hh]:mm:ss"));
2✔
357
}
2✔
358

359
row_t cell::row() const
7✔
360
{
361
    return d_->row_;
7✔
362
}
363

364
column_t cell::column() const
39✔
365
{
366
    return d_->column_;
39✔
367
}
368

369
column_t::index_t cell::column_index() const
1✔
370
{
371
    return d_->column_.index;
1✔
372
}
373

374
void cell::merged(bool merged)
774✔
375
{
376
    d_->is_merged_ = merged;
774✔
377
}
774✔
378

379
bool cell::is_merged() const
×
380
{
381
    return d_->is_merged_;
×
382
}
383

384
bool cell::phonetics_visible() const
1,003✔
385
{
386
    return d_->phonetics_visible_;
1,003✔
387
}
388

389
void cell::show_phonetics(bool phonetics)
2✔
390
{
391
    d_->phonetics_visible_ = phonetics;
2✔
392
}
2✔
393

394
bool cell::is_date() const
7✔
395
{
396
    return data_type() == type::number
7✔
397
        && has_format()
4!
398
        && number_format().is_date_format();
11!
399
}
400

401
cell_reference cell::reference() const
1,176✔
402
{
403
    return {d_->column_, d_->row_};
1,176✔
404
}
405

406
bool cell::compare(const cell &other, bool compare_by_reference) const
17✔
407
{
408
    if (compare_by_reference)
17!
409
    {
410
        return d_ == other.d_;
17✔
411
    }
412
    else
413
    {
414
        return *d_ == *other.d_;
×
415
    }
416
}
417

418
bool cell::operator==(const cell &comparand) const
17✔
419
{
420
    return compare(comparand, true);
17✔
421
}
422

423
bool cell::operator!=(const cell &comparand) const
2✔
424
{
425
    return !(*this == comparand);
2✔
426
}
427

428
cell &cell::operator=(const cell &rhs) = default;
1✔
429

430
hyperlink cell::hyperlink() const
113✔
431
{
432
    if (!d_->hyperlink_.is_set())
113✔
433
    {
434
        throw invalid_attribute("cell \"" + reference().to_string() + "\" has no hyperlink");
1✔
435
    }
436
    return xlnt::hyperlink(&d_->hyperlink_.get());
112✔
437
}
438

439
void cell::hyperlink(const std::string &url, const std::string &display)
43✔
440
{
441
    if (url.empty())
43✔
442
    {
443
        throw invalid_parameter("the hyperlink URL for cell \"" + reference().to_string() + "\" is empty");
1✔
444
    }
445

446
    auto ws = worksheet();
42✔
447
    auto &manifest = ws.workbook().manifest();
42✔
448

449
    d_->hyperlink_ = detail::hyperlink_impl();
42✔
450

451
    // check for existing relationships
452
    auto relationships = manifest.relationships(ws.path(), relationship_type::hyperlink);
42✔
453
    auto relation = std::find_if(relationships.cbegin(), relationships.cend(),
42✔
454
        [&url](const xlnt::relationship &rel) { return rel.target().path().string() == url; });
63✔
455
    if (relation != relationships.end())
42✔
456
    {
457
        d_->hyperlink_.get().relationship = *relation;
32✔
458
    }
459
    else
460
    { // register a new relationship
461
        auto rel_id = manifest.register_relationship(
462
            uri(ws.path().string()),
20✔
463
            relationship_type::hyperlink,
464
            uri(url),
20✔
465
            target_mode::external);
10✔
466
        // TODO: make manifest::register_relationship return the created relationship instead of rel id
467
        d_->hyperlink_.get().relationship = manifest.relationship(ws.path(), rel_id);
10✔
468
    }
10✔
469
    // if a value is already present, the display string is ignored
470
    if (has_value())
42✔
471
    {
472
        d_->hyperlink_.get().display.set(to_string());
35✔
473
    }
474
    else
475
    {
476
        d_->hyperlink_.get().display.set(display.empty() ? url : display);
7✔
477
        value(hyperlink().display());
7✔
478
    }
479
}
42✔
480

481
void cell::hyperlink(xlnt::cell target, const std::string &display)
4✔
482
{
483
    // TODO: should this computed value be a method on a cell?
484
    const auto cell_address = target.worksheet().title() + "!" + target.reference().to_string();
4✔
485

486
    d_->hyperlink_ = detail::hyperlink_impl();
4✔
487
    d_->hyperlink_.get().relationship = xlnt::relationship("", relationship_type::hyperlink,
8✔
488
        uri(""), uri(cell_address), target_mode::internal);
28✔
489
    // if a value is already present, the display string is ignored
490
    if (has_value())
4✔
491
    {
492
        d_->hyperlink_.get().display.set(to_string());
1✔
493
    }
494
    else
495
    {
496
        d_->hyperlink_.get().display.set(display.empty() ? cell_address : display);
3✔
497
        value(hyperlink().display());
3✔
498
    }
499
}
4✔
500

501
void cell::hyperlink(xlnt::range target, const std::string &display)
3✔
502
{
503
    // TODO: should this computed value be a method on a cell?
504
    const auto range_address = target.target_worksheet().title() + "!" + target.reference().to_string();
3✔
505

506
    d_->hyperlink_ = detail::hyperlink_impl();
3✔
507
    d_->hyperlink_.get().relationship = xlnt::relationship("", relationship_type::hyperlink,
6✔
508
        uri(""), uri(range_address), target_mode::internal);
21✔
509

510
    // if a value is already present, the display string is ignored
511
    if (has_value())
3✔
512
    {
513
        d_->hyperlink_.get().display.set(to_string());
1✔
514
    }
515
    else
516
    {
517
        d_->hyperlink_.get().display.set(display.empty() ? range_address : display);
2✔
518
        value(hyperlink().display());
2✔
519
    }
520
}
3✔
521

522
void cell::formula(const std::string &formula)
21✔
523
{
524
    if (formula.empty())
21✔
525
    {
526
        return clear_formula();
1✔
527
    }
528

529
    if (formula[0] == '=')
20✔
530
    {
531
        d_->formula_ = formula.substr(1);
8✔
532
    }
533
    else
534
    {
535
        d_->formula_ = formula;
12✔
536
    }
537

538
    worksheet().register_calc_chain_in_manifest();
20✔
539
}
540

541
bool cell::has_formula() const
2,248✔
542
{
543
    return d_->formula_.is_set();
2,248✔
544
}
545

546
std::string cell::formula() const
22✔
547
{
548
    if (!d_->formula_.is_set())
22✔
549
    {
550
        throw invalid_attribute("cell \"" + reference().to_string() + "\" has no formula");
3✔
551
    }
552
    return d_->formula_.get();
19✔
553
}
554

555
void cell::clear_formula()
795✔
556
{
557
    if (has_formula())
795✔
558
    {
559
        d_->formula_.clear();
18✔
560
        worksheet().garbage_collect_formulae();
18✔
561
    }
562
}
795✔
563

564
std::string cell::error() const
21✔
565
{
566
    if (d_->type_ != type::error)
21✔
567
    {
568
        throw xlnt::invalid_attribute("called error() when cell type is not error, but " + std::to_string(static_cast<int>(d_->type_)));
7✔
569
    }
570
    return value<std::string>();
14✔
571
}
572

573
void cell::error(const std::string &error)
17✔
574
{
575
    if (error.length() == 0 || error[0] != '#')
17✔
576
    {
577
        throw invalid_data_type(error);
2✔
578
    }
579

580
    d_->value_text_.plain_text(error, false);
15✔
581
    d_->type_ = type::error;
15✔
582
}
15✔
583

584
cell cell::offset(int column, int row)
1✔
585
{
586
    return worksheet().cell(reference().make_offset(column, row));
1✔
587
}
588

589
worksheet cell::worksheet()
620✔
590
{
591
    return xlnt::worksheet(d_->parent_);
620✔
592
}
593

594
const worksheet cell::worksheet() const
280✔
595
{
596
    return xlnt::worksheet(d_->parent_);
280✔
597
}
598

599
workbook cell::workbook()
494✔
600
{
601
    return worksheet().workbook();
494✔
602
}
603

604
const workbook cell::workbook() const
223✔
605
{
606
    return worksheet().workbook();
223✔
607
}
608

609
std::pair<int, int> cell::anchor() const
35✔
610
{
611
    double left = 0;
35✔
612

613
    for (column_t column_index = 1; column_index <= d_->column_ - 1; column_index++)
35!
614
    {
615
        left += worksheet().column_width(column_index);
×
616
    }
617

618
    double top = 0;
35✔
619

620
    for (row_t row_index = 1; row_index <= d_->row_ - 1; row_index++)
51✔
621
    {
622
        top += worksheet().row_height(row_index);
16✔
623
    }
624

625
    return {static_cast<int>(left), static_cast<int>(top)};
35✔
626
}
627

628
cell::type cell::data_type() const
3,076✔
629
{
630
    return d_->type_;
3,076✔
631
}
632

633
void cell::data_type(type t)
159✔
634
{
635
    d_->type_ = t;
159✔
636
}
159✔
637

638
number_format cell::computed_number_format() const
51✔
639
{
640
    return xlnt::number_format();
51✔
641
}
642

643
font cell::computed_font() const
×
644
{
645
    return xlnt::font();
×
646
}
647

648
fill cell::computed_fill() const
×
649
{
650
    return xlnt::fill();
×
651
}
652

653
border cell::computed_border() const
×
654
{
655
    return xlnt::border();
×
656
}
657

658
alignment cell::computed_alignment() const
×
659
{
660
    return xlnt::alignment();
×
661
}
662

663
protection cell::computed_protection() const
×
664
{
665
    return xlnt::protection();
×
666
}
667

668
void cell::clear_value()
767✔
669
{
670
    d_->value_numeric_ = 0;
767✔
671
    d_->value_text_.clear();
767✔
672
    d_->type_ = cell::type::empty;
767✔
673
    clear_formula();
767✔
674
}
767✔
675

676
template <>
677
bool cell::value() const
7✔
678
{
679
    return d_->value_numeric_ != 0.0;
7✔
680
}
681

682
template <>
683
int cell::value() const
24✔
684
{
685
    return static_cast<int>(d_->value_numeric_);
24✔
686
}
687

688
template <>
689
long long int cell::value() const
1✔
690
{
691
    return static_cast<long long int>(d_->value_numeric_);
1✔
692
}
693

694
template <>
695
unsigned int cell::value() const
1✔
696
{
697
    return static_cast<unsigned int>(d_->value_numeric_);
1✔
698
}
699

700
template <>
701
unsigned long long cell::value() const
1✔
702
{
703
    return static_cast<unsigned long long>(d_->value_numeric_);
1✔
704
}
705

706
template <>
707
float cell::value() const
1✔
708
{
709
    return static_cast<float>(d_->value_numeric_);
1✔
710
}
711

712
template <>
713
double cell::value() const
67✔
714
{
715
    return static_cast<double>(d_->value_numeric_);
67✔
716
}
717

718
template <>
719
time cell::value() const
3✔
720
{
721
    return time::from_number(d_->value_numeric_);
3✔
722
}
723

724
template <>
725
datetime cell::value() const
×
726
{
727
    return datetime::from_number(d_->value_numeric_, base_date());
×
728
}
729

730
template <>
731
date cell::value() const
×
732
{
733
    return date::from_number(static_cast<int>(d_->value_numeric_), base_date());
×
734
}
735

736
template <>
737
timedelta cell::value() const
×
738
{
739
    return timedelta::from_number(d_->value_numeric_);
×
740
}
741

742
void cell::alignment(const class alignment &alignment_)
1✔
743
{
744
    auto new_format = has_format() ? modifiable_format() : workbook().create_format();
1!
745
    format(new_format.alignment(alignment_, optional<bool>(true)));
1✔
746
}
1✔
747

748
void cell::border(const class border &border_)
1✔
749
{
750
    auto new_format = has_format() ? modifiable_format() : workbook().create_format();
1!
751
    format(new_format.border(border_, optional<bool>(true)));
1✔
752
}
1✔
753

754
void cell::fill(const class fill &fill_)
13✔
755
{
756
    auto new_format = has_format() ? modifiable_format() : workbook().create_format();
13!
757
    format(new_format.fill(fill_, optional<bool>(true)));
13✔
758
}
13✔
759

760
void cell::font(const class font &font_)
26✔
761
{
762
    auto new_format = has_format() ? modifiable_format() : workbook().create_format();
26!
763
    format(new_format.font(font_, optional<bool>(true)));
26✔
764
}
26✔
765

766
void cell::number_format(const class number_format &number_format_)
21✔
767
{
768
    auto new_format = has_format() ? modifiable_format() : workbook().create_format();
21!
769
    format(new_format.number_format(number_format_, optional<bool>(true)));
21✔
770
}
21✔
771

772
void cell::protection(const class protection &protection_)
1✔
773
{
774
    auto new_format = has_format() ? modifiable_format() : workbook().create_format();
1!
775
    format(new_format.protection(protection_, optional<bool>(true)));
1✔
776
}
1✔
777

778
template <>
779
std::string cell::value() const
233✔
780
{
781
    return value<rich_text>().plain_text();
233✔
782
}
783

784
template <>
785
rich_text cell::value() const
239✔
786
{
787
    if (data_type() == cell::type::shared_string)
239✔
788
    {
789
        return workbook().shared_strings(static_cast<std::size_t>(d_->value_numeric_));
210✔
790
    }
791

792
    return d_->value_text_;
29✔
793
}
794

795
bool cell::has_value() const
123✔
796
{
797
    return d_->type_ != cell::type::empty;
123✔
798
}
799

800
std::string cell::to_string() const
51✔
801
{
802
    auto nf = computed_number_format();
51✔
803

804
    switch (data_type())
51!
805
    {
806
    case cell::type::empty:
2✔
807
        return "";
4✔
808
    case cell::type::date:
5✔
809
    case cell::type::number:
810
        return nf.format(value<double>(), base_date());
5✔
811
    case cell::type::inline_string:
40✔
812
    case cell::type::shared_string:
813
    case cell::type::formula_string:
814
    case cell::type::error:
815
        return nf.format(value<std::string>());
40✔
816
    case cell::type::boolean:
4✔
817
        return value<double>() == 0.0 ? "FALSE" : "TRUE";
8✔
818
    }
819

820
    return "";
×
821
}
51✔
822

823
bool cell::has_format() const
1,170✔
824
{
825
    return d_->format_.is_set();
1,170✔
826
}
827

828
void cell::format(const class format new_format)
115✔
829
{
830
    // Check if format belongs to a different workbook (dangling pointer risk)
831
    if (!workbook().owns_format(new_format))
115✔
832
    {
833
        copy_format_from_other_workbook(new_format);
2✔
834
        return;
2✔
835
    }
836

837
    // Same workbook: direct assignment (original behavior)
838
    d_->format_ = new_format.d_;
113✔
839
}
840

841
void cell::copy_format_from_other_workbook(const class format &source_format)
2✔
842
{
843
    auto cloned_format = workbook().clone_format_from(source_format);
2✔
844

845
    // Use the cloned format
846
    d_->format_ = cloned_format.d_;
2✔
847
}
2✔
848

849
calendar cell::base_date() const
13✔
850
{
851
    return workbook().base_date();
13✔
852
}
853

854
bool operator==(std::nullptr_t, const cell &cell)
4✔
855
{
856
    return cell.data_type() == cell::type::empty;
4✔
857
}
858

859
bool operator==(const cell &cell, std::nullptr_t)
2✔
860
{
861
    return nullptr == cell;
2✔
862
}
863

864
bool operator!=(std::nullptr_t, const cell &cell)
×
865
{
866
    return !(nullptr == cell);
×
867
}
868

869
bool operator!=(const cell &cell, std::nullptr_t)
×
870
{
871
    return nullptr != cell;
×
872
}
873

874
std::ostream &operator<<(std::ostream &stream, const xlnt::cell &cell)
6✔
875
{
876
    return stream << cell.to_string();
6✔
877
}
878

879
void cell::value(const std::string &value_string, bool infer_type)
23✔
880
{
881
    value(value_string);
23✔
882

883
    if (!infer_type || value_string.empty())
23!
884
    {
885
        return;
9✔
886
    }
887

888
    if (value_string.front() == '=' && value_string.size() > 1)
23✔
889
    {
890
        formula(value_string);
2✔
891
        return;
2✔
892
    }
893

894
    if (value_string.front() == '#' && value_string.size() > 1)
21!
895
    {
896
        error(value_string);
7✔
897
        return;
7✔
898
    }
899

900
    auto percentage = cast_percentage(value_string);
14✔
901

902
    if (percentage.first)
14✔
903
    {
904
        d_->value_numeric_ = percentage.second;
1✔
905
        d_->type_ = cell::type::number;
1✔
906
        number_format(xlnt::number_format::percentage());
1✔
907
    }
908
    else
909
    {
910
        auto time = cast_time(value_string);
13✔
911

912
        if (time.first)
13✔
913
        {
914
            d_->type_ = cell::type::number;
3✔
915
            number_format(number_format::date_time6());
3✔
916
            d_->value_numeric_ = time.second.to_number();
3✔
917
        }
918
        else
919
        {
920
            auto numeric = cast_numeric(value_string);
10✔
921

922
            if (numeric.first)
10✔
923
            {
924
                d_->value_numeric_ = numeric.second;
8✔
925
                d_->type_ = cell::type::number;
8✔
926
            }
927
        }
928
    }
929
}
930

931
void cell::clear_format()
3✔
932
{
933
    if (d_->format_.is_set())
3✔
934
        d_->format_.clear();
2✔
935
}
3✔
936

937
void cell::clear_style()
3✔
938
{
939
    if (has_format())
3!
940
    {
941
        modifiable_format().clear_style();
3✔
942
    }
943
}
3✔
944

945
void cell::style(const class style &new_style)
10✔
946
{
947
    auto new_format = has_format() ? format() : workbook().create_format();
10!
948

949
    new_format.alignment(new_style.alignment());
10✔
950
    new_format.border(new_style.border());
10✔
951
    new_format.fill(new_style.fill());
10✔
952
    new_format.font(new_style.font());
10✔
953
    new_format.number_format(new_style.number_format());
10✔
954
    new_format.protection(new_style.protection());
10✔
955

956
    format(new_format.style(new_style));
10✔
957
}
10✔
958

959
void cell::style(const std::string &style_name)
2✔
960
{
961
    style(workbook().style(style_name));
3✔
962
}
1✔
963

964
style cell::style()
9✔
965
{
966
    if (!has_format() || !format().has_style())
9!
967
    {
968
        throw invalid_attribute("cell " + reference().to_string() + " does not have a style");
2✔
969
    }
970

971
    auto f = format();
7✔
972

973
    return f.style();
14✔
974
}
7✔
975

976
const style cell::style() const
6✔
977
{
978
    if (!has_format() || !format().has_style())
6!
979
    {
980
        throw invalid_attribute("cell " + reference().to_string() + " does not have a style");
2✔
981
    }
982

983
    return format().style();
4✔
984
}
985

986
bool cell::has_style() const
6✔
987
{
988
    return has_format() && format().has_style();
6!
989
}
990

991
format cell::modifiable_format()
12✔
992
{
993
    if (!d_->format_.is_set())
12!
994
    {
NEW
995
        throw invalid_attribute("cell " + reference().to_string() + " does not have a format");
×
996
    }
997

998
    return xlnt::format(d_->format_);
12✔
999
}
1000

1001
const format cell::format() const
428✔
1002
{
1003
    if (!d_->format_.is_set())
428✔
1004
    {
1005
        throw invalid_attribute("cell " + reference().to_string() + " does not have a format");
2✔
1006
    }
1007

1008
    return xlnt::format(d_->format_);
426✔
1009
}
1010

1011
alignment cell::alignment() const
3✔
1012
{
1013
    if (has_format())
3✔
1014
    {
1015
        return format().alignment();
2✔
1016
    }
1017

1018
    return {};
1✔
1019
}
1020

1021
border cell::border() const
3✔
1022
{
1023
    if (has_format())
3✔
1024
    {
1025
        return format().border();
2✔
1026
    }
1027

1028
    return {};
1✔
1029
}
1030

1031
fill cell::fill() const
5✔
1032
{
1033
    if (has_format())
5✔
1034
    {
1035
        return format().fill();
4✔
1036
    }
1037

1038
    return {};
1✔
1039
}
1040

1041
font cell::font() const
14✔
1042
{
1043
    if (has_format())
14✔
1044
    {
1045
        return format().font();
13✔
1046
    }
1047

1048
    return {};
1✔
1049
}
1050

1051
number_format cell::number_format() const
20✔
1052
{
1053
    if (has_format())
20✔
1054
    {
1055
        return format().number_format();
19✔
1056
    }
1057

1058
    return {};
1✔
1059
}
1060

1061
protection cell::protection() const
3✔
1062
{
1063
    if (has_format())
3✔
1064
    {
1065
        return format().protection();
2✔
1066
    }
1067

1068
    return {};
1✔
1069
}
1070

1071
bool cell::has_hyperlink() const
1,013✔
1072
{
1073
    return d_->hyperlink_.is_set();
1,013✔
1074
}
1075

1076
// comment
1077

1078
bool cell::has_comment() const
1,089✔
1079
{
1080
    return d_->comment_.is_set();
1,089✔
1081
}
1082

1083
void cell::clear_comment()
2✔
1084
{
1085
    if (has_comment())
2✔
1086
    {
1087
        d_->parent_->comments_.erase(reference().to_string());
1✔
1088
        d_->comment_.clear();
1✔
1089
    }
1090
}
2✔
1091

1092
class comment cell::comment() const
56✔
1093
{
1094
    if (!has_comment())
56✔
1095
    {
1096
        throw xlnt::invalid_attribute("cell " + reference().to_string() + " has no comment");
2✔
1097
    }
1098

1099
    return *d_->comment_.get();
54✔
1100
}
1101

1102
void cell::comment(const std::string &text, const std::string &author)
×
1103
{
1104
    comment(xlnt::comment(text, author));
×
1105
}
×
1106

1107
void cell::comment(const std::string &text, const class font &comment_font, const std::string &author)
4✔
1108
{
1109
    comment(xlnt::comment(xlnt::rich_text(text, comment_font), author));
4✔
1110
}
4✔
1111

1112
void cell::comment(const class comment &new_comment)
34✔
1113
{
1114
    if (has_comment())
34!
1115
    {
1116
        *d_->comment_.get() = new_comment;
×
1117
    }
1118
    else
1119
    {
1120
        d_->parent_->comments_[reference().to_string()] = new_comment;
34✔
1121
        d_->comment_.set(&d_->parent_->comments_[reference().to_string()]);
34✔
1122
    }
1123

1124
    // offset comment 5 pixels down and 5 pixels right of the top right corner of the cell
1125
    auto cell_position = anchor();
34✔
1126
    cell_position.first += static_cast<int>(width()) + 5;
34✔
1127
    cell_position.second += 5;
34✔
1128

1129
    d_->comment_.get()->position(cell_position.first, cell_position.second);
34✔
1130

1131
    worksheet().register_comments_in_manifest();
34✔
1132
}
34✔
1133

1134
double cell::width() const
34✔
1135
{
1136
    return worksheet().column_width(column());
34✔
1137
}
1138

1139
double cell::height() const
×
1140
{
1141
    return worksheet().row_height(row());
×
1142
}
1143

1144
} // namespace xlnt
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