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

xlnt-community / xlnt / 189f2a0a-770b-4c75-9a74-b09c8b8c1694

24 Jan 2026 09:36PM UTC coverage: 83.928% (+1.1%) from 82.793%
189f2a0a-770b-4c75-9a74-b09c8b8c1694

Pull #87

circleci

doomlaur
Revert "Calculation properties now have an optional ID. Returning them from a workboom no longer throws."

This reverts commit 593a9c6bd.
Pull Request #87: Improve documentation when exceptions are thrown (fixes #81)

15265 of 19956 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

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

25
#include <cassert>
26
#include <cctype>
27
#include <sstream>
28
#include <unordered_map>
29

30
#include <xlnt/cell/cell.hpp>
31
#include <xlnt/cell/comment.hpp>
32
#include <xlnt/cell/hyperlink.hpp>
33
#include <xlnt/drawing/spreadsheet_drawing.hpp>
34
#include <xlnt/packaging/manifest.hpp>
35
#include <xlnt/utils/optional.hpp>
36
#include <xlnt/utils/path.hpp>
37
#include <xlnt/workbook/workbook.hpp>
38
#include <xlnt/worksheet/selection.hpp>
39
#include <xlnt/worksheet/worksheet.hpp>
40
#include <detail/constants.hpp>
41
#include <detail/header_footer/header_footer_code.hpp>
42
#include <detail/implementations/workbook_impl.hpp>
43
#include <detail/serialization/custom_value_traits.hpp>
44
#include <detail/serialization/defined_name.hpp>
45
#include <detail/serialization/serialisation_helpers.hpp>
46
#include <detail/serialization/vector_streambuf.hpp>
47
#include <detail/serialization/xlsx_consumer.hpp>
48
#include <detail/serialization/zstream.hpp>
49
#include <detail/limits.hpp>
50
#include <detail/serialization/parsers.hpp>
51

52
namespace {
53
/// string_equal
54
/// for comparison between std::string and string literals
55
/// improves on std::string::operator==(char*) by knowing the length ahead of time
56
template <size_t N>
57
inline bool string_arr_loop_equal(const std::string &lhs, const char (&rhs)[N])
25,804✔
58
{
59
    for (size_t i = 0; i < N - 1; ++i)
90,457✔
60
    {
61
        if (lhs[i] != rhs[i])
74,348✔
62
        {
63
            return false;
9,695✔
64
        }
65
    }
66
    return true;
16,109✔
67
}
68

69
template <size_t N>
70
inline bool string_equal(const std::string &lhs, const char (&rhs)[N])
64,231✔
71
{
72
    if (lhs.size() != N - 1)
64,231✔
73
    {
74
        return false;
38,427✔
75
    }
76
    // split function to assist with inlining of the size check
77
    return string_arr_loop_equal(lhs, rhs);
25,804✔
78
}
79

80
xml::qname &qn(const std::string &namespace_, const std::string &name)
85,309✔
81
{
82
    using qname_map = std::unordered_map<std::string, xml::qname>;
83
    static auto memo = std::unordered_map<std::string, qname_map>();
85,309!
84

85
    auto &ns_memo = memo[namespace_];
85,309✔
86

87
    if (ns_memo.find(name) == ns_memo.end())
85,309✔
88
    {
89
        return ns_memo.emplace(name, xml::qname(xlnt::constants::ns(namespace_), name)).first->second;
146✔
90
    }
91

92
    return ns_memo[name];
85,163✔
93
}
94

95
/// <summary>
96
/// Returns true if bool_string represents a true xsd:boolean.
97
/// </summary>
98
bool is_true(const std::string &bool_string)
7,249✔
99
{
100
    if (bool_string == "1" || bool_string == "true")
7,249✔
101
    {
102
        return true;
3,064✔
103
    }
104

105
#ifdef THROW_ON_INVALID_XML
106
    if (bool_string == "0" || bool_string == "false")
107
    {
108
        return false;
109
    }
110

111
    throw xlnt::invalid_file("xsd:boolean should be one of: 0, 1, true, or false, found \"" + bool_string + "\"");
112
#else
113

114
    return false;
4,185✔
115
#endif
116
}
117

118
bool is_valid_reference (const std::string &reference_string)
4✔
119
{
120
    return reference_string != "#REF!";
4✔
121
}
122

123
using style_id_pair = std::pair<xlnt::detail::style_impl, std::size_t>;
124

125
/// <summary>
126
/// Try to find given xfid value in the styles vector and, if succeeded, set's the optional style.
127
/// </summary>
128
void set_style_by_xfid(const std::vector<style_id_pair> &styles,
441✔
129
    std::size_t xfid, xlnt::optional<std::string> &style)
130
{
131
    for (auto &item : styles)
3,127✔
132
    {
133
        if (item.second == xfid)
2,686✔
134
        {
135
            style = item.first.name;
417✔
136
        }
137
    }
138
}
441✔
139

140
// <sheetData> element
141
struct Sheet_Data
142
{
143
    std::vector<std::pair<xlnt::row_properties, xlnt::row_t>> parsed_rows;
144
    std::vector<xlnt::detail::Cell> parsed_cells;
145
};
146

147
xlnt::cell_type type_from_string(const std::string &str)
1,549✔
148
{
149
    if (string_equal(str, "s"))
1,549✔
150
    {
151
        return xlnt::cell::type::shared_string;
1,522✔
152
    }
153
    else if (string_equal(str, "n"))
27✔
154
    {
155
        return xlnt::cell::type::number;
2✔
156
    }
157
    else if (string_equal(str, "b"))
25!
158
    {
159
        return xlnt::cell::type::boolean;
×
160
    }
161
    else if (string_equal(str, "e"))
25!
162
    {
163
        return xlnt::cell::type::error;
×
164
    }
165
    else if (string_equal(str, "inlineStr"))
25✔
166
    {
167
        return xlnt::cell::type::inline_string;
4✔
168
    }
169
    else if (string_equal(str, "str"))
21!
170
    {
171
        return xlnt::cell::type::formula_string;
21✔
172
    }
173
    return xlnt::cell::type::shared_string;
×
174
}
175

176
xlnt::detail::Cell parse_cell(xlnt::row_t row_arg, xml::parser *parser, std::unordered_map<std::string, std::string> &array_formulae, std::unordered_map<int, std::string> &shared_formulae)
2,132✔
177
{
178
    xlnt::detail::Cell c;
2,132✔
179
    for (auto &attr : parser->attribute_map())
6,668✔
180
    {
181
        if (string_equal(attr.first.name(), "r"))
4,536✔
182
        {
183
            c.ref = xlnt::detail::Cell_Reference(row_arg, attr.second.value);
2,132✔
184
        }
185
        else if (string_equal(attr.first.name(), "t"))
2,404✔
186
        {
187
            c.type = type_from_string(attr.second.value);
1,549✔
188
        }
189
        else if (string_equal(attr.first.name(), "s"))
855✔
190
        {
191
            xlnt::detail::parse(attr.second.value, c.style_index);
851✔
192
        }
193
        else if (string_equal(attr.first.name(), "ph"))
4!
194
        {
195
            c.is_phonetic = is_true(attr.second.value);
4✔
196
        }
197
        else if (string_equal(attr.first.name(), "cm"))
×
198
        {
199
            xlnt::detail::parse(attr.second.value, c.cell_metadata_idx);
×
200
        }
201
    }
202
    int level = 1; // nesting level
2,132✔
203
        // 1 == <c>
204
        // 2 == <v>/<f>
205
        // 3 == <is><t>
206
        // exit loop at </c>
207
    while (level > 0)
9,792✔
208
    {
209
        xml::parser::event_type e = parser->next();
7,660✔
210
        switch (e)
7,660!
211
        {
212
        case xml::parser::start_element: {
1,698✔
213
            if (string_equal(parser->name(), "f") && parser->attribute_present("t"))
1,826!
214
            {
215
                // Skip shared formulas with a ref attribute because it indicates that this
216
                // is the master cell which will be handled in the xml::parser::characters case.
217
                if (parser->attribute("t") == "shared" && !parser->attribute_present("ref"))
63!
218
                {
219
                    auto shared_index = parser->attribute<int>("si");
12✔
220
                    c.formula_string = shared_formulae[shared_index];
6✔
221
                }
222
            }
223
            ++level;
1,698✔
224
            break;
1,698✔
225
        }
226
        case xml::parser::end_element: {
3,830✔
227
            --level;
3,830✔
228
            break;
3,830✔
229
        }
230
        case xml::parser::characters: {
2,132✔
231
            // only want the characters inside one of the nested tags
232
            // without this a lot of formatting whitespace can get added
233
            if (level == 2)
2,132✔
234
            {
235
                // <v> -> numeric values
236
                if (string_equal(parser->name(), "v"))
1,684✔
237
                {
238
                    c.value += std::move(parser->value());
1,626✔
239
                }
240
                // <f> formula
241
                else if (string_equal(parser->name(), "f"))
58!
242
                {
243
                    c.formula_string += std::move(parser->value());
58✔
244

245
                    if (parser->attribute_present("t"))
174✔
246
                    {
247
                        auto formula_ref = parser->attribute("ref");
18✔
248
                        auto formula_type = parser->attribute("t");
18✔
249
                        if (formula_type == "shared")
9✔
250
                        {
251
                            auto shared_index = parser->attribute<int>("si");
6✔
252
                            shared_formulae[shared_index] = c.formula_string;
3✔
253
                        }
254
                        else if (formula_type == "array")
6!
255
                        {
256
                            array_formulae[formula_ref] = c.formula_string;
6✔
257
                        }
258
                    }
9✔
259
                }
260
            }
261
            else if (level == 3)
448✔
262
            {
263
                // <is><t> -> inline string
264
                if (string_equal(parser->name(), "t"))
4!
265
                {
266
                    c.value += std::move(parser->value());
4✔
267
                }
268
            }
269
            break;
2,132✔
270
        }
271
        case xml::parser::start_namespace_decl:
×
272
        case xml::parser::end_namespace_decl:
273
        case xml::parser::start_attribute:
274
        case xml::parser::end_attribute:
275
        case xml::parser::eof:
276
        default: {
NEW
277
            throw xlnt::exception("unexpected XML parsing event " + std::to_string(e));
×
278
        }
279
        }
280
        // Prevents unhandled exceptions from being triggered.
281
        parser->attribute_map();
7,660✔
282
    }
283
    return c;
2,132✔
284
}
×
285

286
// <row> inside <sheetData> element
287
std::pair<xlnt::row_properties, int> parse_row(xml::parser *parser, std::vector<xlnt::detail::Cell> &parsed_cells, std::unordered_map<std::string, std::string> &array_formulae, std::unordered_map<int, std::string> &shared_formulae)
2,280✔
288
{
289
    std::pair<xlnt::row_properties, int> props;
2,280✔
290
    for (auto &attr : parser->attribute_map())
10,552✔
291
    {
292
        if (string_equal(attr.first.name(), "dyDescent"))
8,272✔
293
        {
294
            props.first.dy_descent = xlnt::detail::deserialise(attr.second.value);
482✔
295
        }
296
        else if (string_equal(attr.first.name(), "spans"))
7,790✔
297
        {
298
            props.first.spans = attr.second.value;
555✔
299
        }
300
        else if (string_equal(attr.first.name(), "ht"))
7,235✔
301
        {
302
            props.first.height = xlnt::detail::deserialise(attr.second.value);
1,759✔
303
        }
304
        else if (string_equal(attr.first.name(), "s"))
5,476✔
305
        {
306
            size_t style = 0;
21✔
307
            if (xlnt::detail::parse(attr.second.value, style) == std::errc())
21!
308
            {
309
                props.first.style = style;
21✔
310
            }
311
        }
312
        else if (string_equal(attr.first.name(), "hidden"))
5,455✔
313
        {
314
            props.first.hidden = is_true(attr.second.value);
344✔
315
        }
316
        else if (string_equal(attr.first.name(), "customFormat"))
5,111✔
317
        {
318
            props.first.custom_format = is_true(attr.second.value);
696✔
319
        }
320
        else if (string_equal(attr.first.name(), "ph"))
4,415!
321
        {
322
            is_true(attr.second.value);
×
323
        }
324
        else if (string_equal(attr.first.name(), "r"))
4,415✔
325
        {
326
            xlnt::detail::parse(attr.second.value, props.second);
2,280✔
327
        }
328
        else if (string_equal(attr.first.name(), "customHeight"))
2,135✔
329
        {
330
            props.first.custom_height = is_true(attr.second.value);
1,462✔
331
        }
332
        else if (string_equal(attr.first.name(), "collapsed"))
673✔
333
        {
334
            props.first.collapsed = is_true(attr.second.value);
334✔
335
        }
336
        else if (string_equal(attr.first.name(), "outlineLevel"))
339!
337
        {
338
            props.first.outline_level = 0;
339✔
339
            xlnt::detail::parse(attr.second.value, props.first.outline_level.get());
339✔
340
        }
341
    }
342

343
    int level = 1;
2,280✔
344
    while (level > 0)
7,048✔
345
    {
346
        xml::parser::event_type e = parser->next();
4,768✔
347
        switch (e)
4,768!
348
        {
349
        case xml::parser::start_element: {
2,132✔
350
            parsed_cells.push_back(parse_cell(static_cast<xlnt::row_t>(props.second), parser, array_formulae, shared_formulae));
2,132✔
351
            break;
2,132✔
352
        }
353
        case xml::parser::end_element: {
2,280✔
354
            --level;
2,280✔
355
            break;
2,280✔
356
        }
357
        case xml::parser::characters: {
356✔
358
            // ignore whitespace
359
            break;
356✔
360
        }
361
        case xml::parser::start_namespace_decl:
×
362
        case xml::parser::start_attribute:
363
        case xml::parser::end_namespace_decl:
364
        case xml::parser::end_attribute:
365
        case xml::parser::eof:
366
        default: {
NEW
367
            throw xlnt::exception("unexpected XML parsing event " + std::to_string(e));
×
368
        }
369
        }
370
    }
371
    return props;
2,280✔
372
}
×
373

374
// <sheetData> inside <worksheet> element
375
Sheet_Data parse_sheet_data(xml::parser *parser, std::unordered_map<std::string, std::string> &array_formulae, std::unordered_map<int, std::string> &shared_formulae)
126✔
376
{
377
    Sheet_Data sheet_data;
126✔
378
    int level = 1; // nesting level
126✔
379
        // 1 == <sheetData>
380
        // 2 == <row>
381

382
    while (level > 0)
2,532✔
383
    {
384
        xml::parser::event_type e = parser->next();
2,406✔
385
        switch (e)
2,406!
386
        {
387
        case xml::parser::start_element: {
2,280✔
388
            sheet_data.parsed_rows.push_back(parse_row(parser, sheet_data.parsed_cells, array_formulae, shared_formulae));
2,280✔
389
            break;
2,280✔
390
        }
391
        case xml::parser::end_element: {
126✔
392
            --level;
126✔
393
            break;
126✔
394
        }
395
        case xml::parser::characters: {
×
396
            // ignore, whitespace formatting normally
397
            break;
×
398
        }
399
        case xml::parser::start_namespace_decl:
×
400
        case xml::parser::start_attribute:
401
        case xml::parser::end_namespace_decl:
402
        case xml::parser::end_attribute:
403
        case xml::parser::eof:
404
        default: {
NEW
405
            throw xlnt::exception("unexpected XML parsing event " + std::to_string(e));
×
406
        }
407
        }
408
    }
409
    return sheet_data;
126✔
410
}
×
411

412
} // namespace
413

414
/*
415
class parsing_context
416
{
417
public:
418
    parsing_context(xlnt::detail::zip_file_reader &archive, const std::string &filename)
419
        : parser_(stream_, filename)
420
    {
421
    }
422

423
    xml::parser &parser();
424

425
private:
426
    std::istream stream_;
427
    xml::parser parser_;
428
};
429
*/
430

431
namespace xlnt {
432
namespace detail {
433

434
xlsx_consumer::xlsx_consumer(workbook &target)
119✔
435
    : target_(target),
119✔
436
      parser_(nullptr)
119✔
437
{
438
}
119✔
439

440
xlsx_consumer::~xlsx_consumer()
119✔
441
{
442
    if (target_.impl().stylesheet_.is_set())
119✔
443
    {
444
        // re-enable garbage collection, but do not run the garbage collection immediately, to allow a successful roundtrip without losing non-used formats.
445
        target_.impl().stylesheet_.get().garbage_collection_enabled = true;
88✔
446
    }
447
}
119✔
448

449
void xlsx_consumer::read(std::istream &source)
103✔
450
{
451
    archive_.reset(new izstream(source));
103!
452
    populate_workbook(false);
95✔
453
}
94✔
454

455
void xlsx_consumer::open(std::istream &source)
3✔
456
{
457
    archive_.reset(new izstream(source));
3!
458
    populate_workbook(true);
3✔
459
}
3✔
460

461
cell xlsx_consumer::read_cell()
153✔
462
{
463
    return cell(streaming_cell_.get());
153✔
464
}
465

466
void xlsx_consumer::read_worksheet(const std::string &rel_id)
126✔
467
{
468
    read_worksheet_begin(rel_id);
126✔
469

470
    if (!streaming_)
126!
471
    {
472
        read_worksheet_sheetdata();
126✔
473
        read_worksheet_end(rel_id);
126✔
474
    }
475
}
126✔
476

477
void read_defined_names(worksheet ws, std::vector<defined_name> defined_names)
129✔
478
{
479
    for (auto &name : defined_names)
157✔
480
    {
481
        if (name.sheet_id != ws.id() - 1)
28✔
482
        {
483
            continue;
21✔
484
        }
485

486
        if (name.name == "_xlnm.Print_Titles")
7✔
487
        {
488
            // Basic print titles parser
489
            // A print title definition looks like "'Sheet3'!$B:$E,'Sheet3'!$2:$4"
490
            // There are three cases: columns only, rows only, and both (separated by a comma).
491
            // For this reason, we loop up to two times parsing each component.
492
            // Titles may be quoted (with single quotes) or unquoted. We ignore them for now anyways.
493
            // References are always absolute.
494
            // Move this into a separate function if it needs to be used in other places.
495
            auto i = std::size_t(0);
3✔
496
            for (auto count = 0; count < 2; count++)
4!
497
            {
498
                // Split into components based on "!", ":", and "," characters
499
                auto j = i;
4✔
500
                i = name.value.find('!', j);
4✔
501
                auto title = name.value.substr(j, i - j);
4✔
502
                j = i + 2; // skip "!$"
4✔
503
                i = name.value.find(':', j);
4✔
504
                auto from = name.value.substr(j, i - j);
4✔
505
                j = i + 2; // skip ":$"
4✔
506
                i = name.value.find(',', j);
4✔
507
                auto to = name.value.substr(j, i - j);
4✔
508

509
                // Apply to the worksheet
510
                if (isalpha(from.front())) // alpha=>columns
4✔
511
                {
512
                    ws.print_title_cols(from, to);
2✔
513
                }
514
                else // numeric=>rows
515
                {
516
                    row_t start = 0;
2✔
517
                    row_t end = 0;
2✔
518

519
                    if (detail::parse(from, start) == std::errc() &&
4!
520
                        detail::parse(to, end) == std::errc())
2!
521
                    {
522
                        ws.print_title_rows(start, end);
2✔
523
                    }
524
                }
525

526
                // Check for end condition
527
                if (i == std::string::npos)
4✔
528
                {
529
                    break;
3✔
530
                }
531

532
                i++; // skip "," for next iteration
1✔
533
            }
10✔
534
        }
535
        else if (name.name == "_xlnm._FilterDatabase")
4✔
536
        {
537
            auto i = name.value.find("!");
1✔
538
            auto ref = name.value.substr(i + 1);
1✔
539
            if (is_valid_reference(ref))
1!
540
            {
541
                ws.auto_filter(ref);
1✔
542
            }
543
        }
1✔
544
        else if (name.name == "_xlnm.Print_Area")
3!
545
        {
546
            auto i = name.value.find("!");
3✔
547
            auto ref = name.value.substr(i + 1);
3✔
548
            if (is_valid_reference(ref))
3✔
549
            {
550
                ws.print_area(ref);
2✔
551
            }
552
        }
3✔
553
    }
554
}
129✔
555

556
std::string xlsx_consumer::read_worksheet_begin(const std::string &rel_id)
129✔
557
{
558
    if (streaming_ && streaming_cell_ == nullptr)
129!
559
    {
560
        streaming_cell_.reset(new detail::cell_impl());
3!
561
    }
562

563
    array_formulae_.clear();
129✔
564
    shared_formulae_.clear();
129✔
565

566
    auto title = std::find_if(target_.d_->sheet_title_rel_id_map_.begin(),
129✔
567
        target_.d_->sheet_title_rel_id_map_.end(),
129✔
568
        [&](const std::pair<std::string, std::string> &p) {
204✔
569
            return p.second == rel_id;
204✔
570
        })->first;
129✔
571

572
    auto ws = worksheet(current_worksheet_);
129✔
573

574
    expect_start_element(qn("spreadsheetml", "worksheet"), xml::content::complex); // CT_Worksheet
516✔
575
    skip_attributes({qn("mc", "Ignorable")});
516!
576

577
    read_defined_names(ws, defined_names_);
129✔
578

579
    while (in_element(qn("spreadsheetml", "worksheet")))
2,513!
580
    {
581
        auto current_worksheet_element = expect_start_element(xml::content::complex);
596✔
582

583
        if (current_worksheet_element == qn("spreadsheetml", "sheetPr")) // CT_SheetPr 0-1
2,384✔
584
        {
585
            sheet_pr props;
45✔
586
            if (parser().attribute_present("syncHorizontal"))
135!
587
            { // optional, boolean, false
588
                props.sync_horizontal.set(parser().attribute<bool>("syncHorizontal"));
×
589
            }
590
            if (parser().attribute_present("syncVertical"))
135!
591
            { // optional, boolean, false
592
                props.sync_vertical.set(parser().attribute<bool>("syncVertical"));
×
593
            }
594
            if (parser().attribute_present("syncRef"))
135!
595
            { // optional, ST_Ref, false
596
                props.sync_ref.set(cell_reference(parser().attribute("syncRef")));
×
597
            }
598
            if (parser().attribute_present("transitionEvaluation"))
135!
599
            { // optional, boolean, false
600
                props.transition_evaluation.set(parser().attribute<bool>("transitionEvaluation"));
×
601
            }
602
            if (parser().attribute_present("transitionEntry"))
135!
603
            { // optional, boolean, false
604
                props.transition_entry.set(parser().attribute<bool>("transitionEntry"));
×
605
            }
606
            if (parser().attribute_present("published"))
135!
607
            { // optional, boolean, true
608
                props.published.set(parser().attribute<bool>("published"));
×
609
            }
610
            if (parser().attribute_present("codeName"))
135✔
611
            { // optional, string
612
                props.code_name.set(parser().attribute<std::string>("codeName"));
9✔
613
            }
614
            if (parser().attribute_present("filterMode"))
135✔
615
            { // optional, boolean, false
616
                props.filter_mode.set(parser().attribute<bool>("filterMode"));
57✔
617
            }
618
            if (parser().attribute_present("enableFormatConditionsCalculation"))
135!
619
            { // optional, boolean, true
620
                props.enable_format_condition_calculation.set(parser().attribute<bool>("enableFormatConditionsCalculation"));
×
621
            }
622
            while (in_element(current_worksheet_element))
91✔
623
            {
624
                auto sheet_pr_child_element = expect_start_element(xml::content::simple);
46✔
625

626
                if (sheet_pr_child_element == qn("spreadsheetml", "tabColor")) // CT_Color 0-1
184!
627
                {
628
                    read_color();
×
629
                }
630
                else if (sheet_pr_child_element == qn("spreadsheetml", "outlinePr")) // CT_OutlinePr 0-1
184✔
631
                {
632
                    if (parser().attribute_present("applyStyles"))
51✔
633
                    {
634
                        props.apply_styles.set(is_true(parser().attribute("applyStyles")));
30✔
635
                    }
636
                    if (parser().attribute_present("summaryBelow"))
51!
637
                    {
638
                        props.summary_below.set(is_true(parser().attribute("summaryBelow")));
51✔
639
                    }
640
                    if (parser().attribute_present("summaryRight"))
51!
641
                    {
642
                        props.summary_right.set(is_true(parser().attribute("summaryRight")));
51✔
643
                    }
644
                    if (parser().attribute_present("showOutlineSymbols"))
51✔
645
                    {
646
                        props.show_outline_symbols.set(is_true(parser().attribute("showOutlineSymbols")));
30✔
647
                    }
648
                }
649
                else if (sheet_pr_child_element == qn("spreadsheetml", "pageSetUpPr")) // CT_PageSetUpPr 0-1
116!
650
                {
651
                    skip_attribute("autoPageBreaks"); // optional, boolean, true
58✔
652
                    skip_attribute("fitToPage"); // optional, boolean, false
58✔
653
                }
654
                else
655
                {
656
                    unexpected_element(sheet_pr_child_element);
×
657
                }
658

659
                expect_end_element(sheet_pr_child_element);
46✔
660
            }
46✔
661
            ws.d_->sheet_properties_.set(props);
45✔
662
        }
45✔
663
        else if (current_worksheet_element == qn("spreadsheetml", "dimension")) // CT_SheetDimension 0-1
2,204✔
664
        {
665
            skip_remaining_content(current_worksheet_element);
128✔
666
        }
667
        else if (current_worksheet_element == qn("spreadsheetml", "sheetViews")) // CT_SheetViews 0-1
1,692✔
668
        {
669
            while (in_element(current_worksheet_element))
236✔
670
            {
671
                expect_start_element(qn("spreadsheetml", "sheetView"), xml::content::complex); // CT_SheetView 1+
472✔
672

673
                sheet_view new_view;
118✔
674
                new_view.id(parser().attribute<std::size_t>("workbookViewId"));
236✔
675

676
                if (parser().attribute_present("showGridLines")) // default="true"
354✔
677
                {
678
                    new_view.show_grid_lines(is_true(parser().attribute("showGridLines")));
69✔
679
                }
680
                if (parser().attribute_present("topLeftCell"))
354✔
681
                {
682
                    new_view.top_left_cell(cell_reference(parser().attribute("topLeftCell")));
66✔
683
                }
684

685
                if (parser().attribute_present("defaultGridColor")) // default="true"
354✔
686
                {
687
                    new_view.default_grid_color(is_true(parser().attribute("defaultGridColor")));
60✔
688
                }
689

690
                if (parser().attribute_present("view")
354!
691
                    && parser().attribute("view") != "normal")
280!
692
                {
693
                    new_view.type(parser().attribute("view") == "pageBreakPreview"
12!
694
                            ? sheet_view_type::page_break_preview
695
                            : sheet_view_type::page_layout);
696
                }
697

698
                if (parser().attribute_present("zoomScale"))
354✔
699
                {
700
                    new_view.zoom_scale(parser().attribute<int>("zoomScale"));
57✔
701
                }
702

703
                if (parser().attribute_present("tabSelected")
354!
704
                    && is_true(parser().attribute("tabSelected")))
408!
705
                {
706
                    target_.d_->view_.get().active_tab = ws.id() - 1;
79✔
707
                }
708

709
                skip_attributes({"windowProtection", "showFormulas", "showRowColHeaders", "showZeros", "rightToLeft", "showRuler", "showOutlineSymbols", "showWhiteSpace",
236✔
710
                    "view", "topLeftCell", "colorId", "zoomScaleNormal", "zoomScaleSheetLayoutView",
711
                    "zoomScalePageLayoutView"});
712

713
                while (in_element(qn("spreadsheetml", "sheetView")))
894✔
714
                {
715
                    auto sheet_view_child_element = expect_start_element(xml::content::simple);
76✔
716

717
                    if (sheet_view_child_element == qn("spreadsheetml", "pane")) // CT_Pane 0-1
304✔
718
                    {
719
                        pane new_pane;
3✔
720

721
                        if (parser().attribute_present("topLeftCell"))
9!
722
                        {
723
                            new_pane.top_left_cell = cell_reference(parser().attribute("topLeftCell"));
9✔
724
                        }
725

726
                        if (parser().attribute_present("xSplit"))
9✔
727
                        {
728
                            new_pane.x_split = parser().attribute<column_t::index_t>("xSplit");
6✔
729
                        }
730

731
                        if (parser().attribute_present("ySplit"))
9!
732
                        {
733
                            new_pane.y_split = parser().attribute<row_t>("ySplit");
9✔
734
                        }
735

736
                        if (parser().attribute_present("activePane"))
9!
737
                        {
738
                            new_pane.active_pane = parser().attribute<pane_corner>("activePane");
9✔
739
                        }
740

741
                        if (parser().attribute_present("state"))
9!
742
                        {
743
                            new_pane.state = parser().attribute<pane_state>("state");
9✔
744
                        }
745

746
                        new_view.pane(new_pane);
3✔
747
                    }
3✔
748
                    else if (sheet_view_child_element == qn("spreadsheetml", "selection")) // CT_Selection 0-4
292!
749
                    {
750
                        selection current_selection;
73✔
751

752
                        if (parser().attribute_present("activeCell"))
219✔
753
                        {
754
                            current_selection.active_cell(parser().attribute("activeCell"));
210✔
755
                        }
756

757
                        if (parser().attribute_present("sqref"))
219!
758
                        {
759
                            current_selection.sqref(parser().attribute("sqref"));
219✔
760
                        }
761

762
                        if (parser().attribute_present("pane"))
219✔
763
                        {
764
                            current_selection.pane(parser().attribute<pane_corner>("pane"));
60✔
765
                        }
766

767
                        new_view.add_selection(current_selection);
73✔
768

769
                        skip_remaining_content(sheet_view_child_element);
73✔
770
                    }
73✔
771
                    else if (sheet_view_child_element == qn("spreadsheetml", "pivotSelection")) // CT_PivotSelection 0-4
×
772
                    {
773
                        skip_remaining_content(sheet_view_child_element);
×
774
                    }
775
                    else if (sheet_view_child_element == qn("spreadsheetml", "extLst")) // CT_ExtensionList 0-1
×
776
                    {
777
                        skip_remaining_content(sheet_view_child_element);
×
778
                    }
779
                    else
780
                    {
781
                        unexpected_element(sheet_view_child_element);
×
782
                    }
783

784
                    expect_end_element(sheet_view_child_element);
76✔
785
                }
76✔
786

787
                expect_end_element(qn("spreadsheetml", "sheetView"));
354✔
788

789
                ws.d_->views_.push_back(new_view);
118✔
790
            }
118✔
791
        }
792
        else if (current_worksheet_element == qn("spreadsheetml", "sheetFormatPr")) // CT_SheetFormatPr 0-1
1,220✔
793
        {
794
            if (parser().attribute_present("baseColWidth"))
384✔
795
            {
796
                ws.d_->format_properties_.base_col_width =
85✔
797
                    xlnt::detail::deserialise(parser().attribute("baseColWidth"));
255✔
798
            }
799
            if (parser().attribute_present("defaultColWidth"))
384✔
800
            {
801
                ws.d_->format_properties_.default_column_width =
20✔
802
                    xlnt::detail::deserialise(parser().attribute("defaultColWidth"));
60✔
803
            }
804
            if (parser().attribute_present("defaultRowHeight"))
384!
805
            {
806
                ws.d_->format_properties_.default_row_height =
256✔
807
                    xlnt::detail::deserialise(parser().attribute("defaultRowHeight"));
384✔
808
            }
809

810
            if (parser().attribute_present(qn("x14ac", "dyDescent")))
640✔
811
            {
812
                ws.d_->format_properties_.dy_descent =
62✔
813
                    xlnt::detail::deserialise(parser().attribute(qn("x14ac", "dyDescent")));
310✔
814
            }
815

816
            skip_attributes();
128✔
817
        }
818
        else if (current_worksheet_element == qn("spreadsheetml", "cols")) // CT_Cols 0+
708✔
819
        {
820
            while (in_element(qn("spreadsheetml", "cols")))
728✔
821
            {
822
                expect_start_element(qn("spreadsheetml", "col"), xml::content::simple);
488✔
823

824
                skip_attributes(std::vector<std::string>{"collapsed", "outlineLevel"});
854!
825

826
                column_t::index_t min = 0;
122✔
827
                column_t::index_t max = 0;
122✔
828
                bool ok = detail::parse(parser().attribute("min"), min) == std::errc();
244✔
829
                ok = detail::parse(parser().attribute("max"), max) == std::errc() && ok;
244!
830

831
#ifdef THROW_ON_INVALID_XML
832
                if (!ok)
833
                {
834
                    throw xlnt::invalid_parameter("spreadsheetml invalid min/max (min \"" + parser().attribute("min") + "\", max \"" + parser().attribute("max") + "\")");
835
                }
836
#endif
837

838
                // avoid uninitialised warnings in GCC by using a lambda to make the conditional initialisation
839
                optional<double> width = [this](xml::parser &p) -> xlnt::optional<double> {
122✔
840
                    if (p.attribute_present("width"))
244!
841
                    {
842
                        return (xlnt::detail::deserialise(p.attribute("width")) * 7 - 5) / 7;
244✔
843
                    }
844
                    return xlnt::optional<double>();
×
845
                }(parser());
122✔
846
                // avoid uninitialised warnings in GCC by using a lambda to make the conditional initialisation
847
                optional<std::size_t> column_style = [](xml::parser &p) -> xlnt::optional<std::size_t> {
122✔
848
                    if (p.attribute_present("style"))
244✔
849
                    {
850
                        return p.attribute<std::size_t>("style");
154✔
851
                    }
852
                    return xlnt::optional<std::size_t>();
45✔
853
                }(parser());
122✔
854

855
                auto custom = parser().attribute_present("customWidth")
244✔
856
                    ? is_true(parser().attribute("customWidth"))
448!
857
                    : false;
122✔
858
                auto hidden = parser().attribute_present("hidden")
244✔
859
                    ? is_true(parser().attribute("hidden"))
306!
860
                    : false;
122✔
861
                auto best_fit = parser().attribute_present("bestFit")
366✔
862
                    ? is_true(parser().attribute("bestFit"))
266!
863
                    : false;
122✔
864

865
                expect_end_element(qn("spreadsheetml", "col"));
366✔
866

867
                for (auto column = min; column <= max; column++)
91,872✔
868
                {
869
                    column_properties props;
91,750✔
870

871
                    if (width.is_set())
91,750!
872
                    {
873
                        props.width = width.get();
91,750✔
874
                    }
875

876
                    if (column_style.is_set())
91,750✔
877
                    {
878
                        props.style = column_style.get();
91,680✔
879
                    }
880

881
                    props.hidden = hidden;
91,750✔
882
                    props.custom_width = custom;
91,750✔
883
                    props.best_fit = best_fit;
91,750✔
884
                    ws.add_column_properties(column, props);
91,750✔
885
                }
91,750✔
886
            }
122✔
887
        }
888
        else if (current_worksheet_element == qn("spreadsheetml", "sheetData")) // CT_SheetData 1
516!
889
        {
890
            return title;
129✔
891
        }
892

893
        expect_end_element(current_worksheet_element);
467✔
894
    }
596✔
895

896
    return title;
×
897
}
258!
898

899
void xlsx_consumer::read_worksheet_sheetdata()
126✔
900
{
901
    if (stack_.back() != qn("spreadsheetml", "sheetData"))
504!
902
    {
903
        return;
×
904
    }
905

906
    auto ws_data = parse_sheet_data(parser_, array_formulae_, shared_formulae_);
126✔
907
    // NOTE: parse->construct are seperated here and could easily be threaded
908
    // with a SPSC queue for what is likely to be an easy performance win
909
    for (auto &row : ws_data.parsed_rows)
2,406✔
910
    {
911
        current_worksheet_->row_properties_.emplace(row.second, std::move(row.first));
2,280✔
912
    }
913
    auto impl = detail::cell_impl();
126✔
914
    for (Cell &cell : ws_data.parsed_cells)
2,258✔
915
    {
916
        impl.parent_ = current_worksheet_;
2,132✔
917
        impl.column_ = cell.ref.column;
2,132✔
918
        impl.row_ = cell.ref.row;
2,132✔
919
        detail::cell_impl *ws_cell_impl = &current_worksheet_->cell_map_.emplace(cell_reference(impl.column_, impl.row_), std::move(impl)).first->second;
2,132✔
920
        if (cell.style_index != -1)
2,132✔
921
        {
922
            ws_cell_impl->format_ = target_.format(static_cast<size_t>(cell.style_index)).d_;
851✔
923
        }
924
        if (cell.cell_metadata_idx != -1)
2,132✔
925
        {
926
        }
927
        ws_cell_impl->phonetics_visible_ = cell.is_phonetic;
2,132✔
928
        if (!cell.formula_string.empty())
2,132✔
929
        {
930
            ws_cell_impl->formula_ = cell.formula_string[0] == '=' ? cell.formula_string.substr(1) : std::move(cell.formula_string);
64!
931
        }
932
        if (!cell.value.empty())
2,132✔
933
        {
934
            ws_cell_impl->type_ = cell.type;
1,630✔
935
            switch (cell.type)
1,630!
936
            {
937
            case cell::type::boolean: {
×
938
                ws_cell_impl->value_numeric_ = is_true(cell.value) ? 1.0 : 0.0;
×
939
                break;
×
940
            }
941
            case cell::type::empty:
83✔
942
            case cell::type::number:
943
            case cell::type::date: {
944
                ws_cell_impl->value_numeric_ = xlnt::detail::deserialise(cell.value);
83✔
945
                break;
83✔
946
            }
947
            case cell::type::shared_string: {
1,522✔
948
                long long value = -1;
1,522✔
949
                if (xlnt::detail::parse(cell.value, value) == std::errc())
1,522!
950
                {
951
                    ws_cell_impl->value_numeric_ = static_cast<double>(value);
1,522✔
952
                }
953
                break;
1,522✔
954
            }
955
            case cell::type::inline_string: {
4✔
956
                ws_cell_impl->value_text_ = std::move(cell.value);
4✔
957
                break;
4✔
958
            }
959
            case cell::type::formula_string: {
21✔
960
                ws_cell_impl->value_text_ = std::move(cell.value);
21✔
961
                break;
21✔
962
            }
963
            case cell::type::error: {
×
964
                ws_cell_impl->value_text_.plain_text(cell.value, false);
×
965
                break;
×
966
            }
967
            }
968
        }
969
    }
970
    stack_.pop_back();
126✔
971

972

973
}
126✔
974

975
worksheet xlsx_consumer::read_worksheet_end(const std::string &rel_id)
127✔
976
{
977
    auto &manifest = target_.manifest();
127✔
978

979
    const auto workbook_rel = manifest.relationship(path("/"), relationship_type::office_document);
127✔
980
    const auto sheet_rel = manifest.relationship(workbook_rel.target().path(), rel_id);
127✔
981
    path sheet_path(sheet_rel.source().path().parent().append(sheet_rel.target().path()));
127✔
982
    auto hyperlinks = manifest.relationships(sheet_path, xlnt::relationship_type::hyperlink);
127✔
983

984
    auto ws = worksheet(current_worksheet_);
127✔
985

986
    while (in_element(qn("spreadsheetml", "worksheet")))
1,627✔
987
    {
988
        auto current_worksheet_element = expect_start_element(xml::content::complex);
248✔
989

990
        if (current_worksheet_element == qn("spreadsheetml", "sheetCalcPr")) // CT_SheetCalcPr 0-1
992!
991
        {
992
            skip_remaining_content(current_worksheet_element);
×
993
        }
994
        else if (current_worksheet_element == qn("spreadsheetml", "sheetProtection")) // CT_SheetProtection 0-1
992✔
995
        {
996
            skip_remaining_content(current_worksheet_element);
2✔
997
        }
998
        else if (current_worksheet_element == qn("spreadsheetml", "protectedRanges")) // CT_ProtectedRanges 0-1
984!
999
        {
1000
            skip_remaining_content(current_worksheet_element);
×
1001
        }
1002
        else if (current_worksheet_element == qn("spreadsheetml", "scenarios")) // CT_Scenarios 0-1
984!
1003
        {
1004
            skip_remaining_content(current_worksheet_element);
×
1005
        }
1006
        else if (current_worksheet_element == qn("spreadsheetml", "autoFilter")) // CT_AutoFilter 0-1
984✔
1007
        {
1008
            ws.auto_filter(xlnt::range_reference(parser().attribute("ref")));
2✔
1009
            // auto filter complex
1010
            skip_remaining_content(current_worksheet_element);
1✔
1011
        }
1012
        else if (current_worksheet_element == qn("spreadsheetml", "sortState")) // CT_SortState 0-1
980!
1013
        {
1014
            skip_remaining_content(current_worksheet_element);
×
1015
        }
1016
        else if (current_worksheet_element == qn("spreadsheetml", "dataConsolidate")) // CT_DataConsolidate 0-1
980!
1017
        {
1018
            skip_remaining_content(current_worksheet_element);
×
1019
        }
1020
        else if (current_worksheet_element == qn("spreadsheetml", "customSheetViews")) // CT_CustomSheetViews 0-1
980!
1021
        {
1022
            skip_remaining_content(current_worksheet_element);
×
1023
        }
1024
        else if (current_worksheet_element == qn("spreadsheetml", "mergeCells")) // CT_MergeCells 0-1
980✔
1025
        {
1026
            parser().attribute_map();
2✔
1027

1028
            while (in_element(qn("spreadsheetml", "mergeCells")))
18✔
1029
            {
1030
                expect_start_element(qn("spreadsheetml", "mergeCell"), xml::content::simple);
8✔
1031
                ws.merge_cells(range_reference(parser().attribute("ref")));
6✔
1032
                expect_end_element(qn("spreadsheetml", "mergeCell"));
8✔
1033
            }
1034
        }
1035
        else if (current_worksheet_element == qn("spreadsheetml", "phoneticPr")) // CT_PhoneticPr 0-1
972✔
1036
        {
1037
            phonetic_pr phonetic_properties(parser().attribute<std::uint32_t>("fontId"));
24✔
1038
            if (parser().attribute_present("type"))
36✔
1039
            {
1040
                phonetic_properties.type(phonetic_pr::type_from_string(parser().attribute("type")));
30✔
1041
            }
1042
            if (parser().attribute_present("alignment"))
36!
1043
            {
1044
                phonetic_properties.alignment(phonetic_pr::alignment_from_string(parser().attribute("alignment")));
×
1045
            }
1046
            current_worksheet_->phonetic_properties_.set(phonetic_properties);
12✔
1047
        }
12✔
1048
        else if (current_worksheet_element == qn("spreadsheetml", "conditionalFormatting")) // CT_ConditionalFormatting 0+
924!
1049
        {
1050
            skip_remaining_content(current_worksheet_element);
×
1051
        }
1052
        else if (current_worksheet_element == qn("spreadsheetml", "dataValidations")) // CT_DataValidations 0-1
924!
1053
        {
1054
            skip_remaining_content(current_worksheet_element);
×
1055
        }
1056
        else if (current_worksheet_element == qn("spreadsheetml", "hyperlinks")) // CT_Hyperlinks 0-1
924✔
1057
        {
1058
            while (in_element(current_worksheet_element))
51✔
1059
            {
1060
                // CT_Hyperlink
1061
                expect_start_element(qn("spreadsheetml", "hyperlink"), xml::content::simple);
144✔
1062

1063
                auto cell = ws.cell(parser().attribute("ref"));
72✔
1064

1065
                if (parser().attribute_present(qn("r", "id")))
180✔
1066
                {
1067
                    auto hyperlink_rel_id = parser().attribute(qn("r", "id"));
116✔
1068
                    auto hyperlink_rel = std::find_if(hyperlinks.begin(), hyperlinks.end(),
29✔
1069
                        [&](const relationship &r) { return r.id() == hyperlink_rel_id; });
50✔
1070

1071
                    if (hyperlink_rel != hyperlinks.end())
29!
1072
                    {
1073
                        auto url = hyperlink_rel->target().path().string();
29✔
1074

1075
                        if (cell.has_value())
29!
1076
                        {
1077
                            cell.hyperlink(url, cell.value<std::string>());
29✔
1078
                        }
1079
                        else
1080
                        {
1081
                            cell.hyperlink(url);
×
1082
                        }
1083
                    }
29✔
1084
                }
29✔
1085
                else if (parser().attribute_present("location"))
21!
1086
                {
1087
                    auto hyperlink = hyperlink_impl();
7✔
1088

1089
                    auto location = parser().attribute("location");
14✔
1090
                    hyperlink.relationship = relationship("", relationship_type::hyperlink,
7✔
1091
                        uri(""), uri(location), target_mode::internal);
42✔
1092

1093
                    if (parser().attribute_present("display"))
21!
1094
                    {
1095
                        hyperlink.display = parser().attribute("display");
21✔
1096
                    }
1097

1098
                    if (parser().attribute_present("tooltip"))
21!
1099
                    {
1100
                        hyperlink.tooltip = parser().attribute("tooltip");
×
1101
                    }
1102

1103
                    cell.d_->hyperlink_ = hyperlink;
7✔
1104
                }
7✔
1105

1106
                expect_end_element(qn("spreadsheetml", "hyperlink"));
144✔
1107
            }
1108
        }
1109
        else if (current_worksheet_element == qn("spreadsheetml", "printOptions")) // CT_PrintOptions 0-1
864✔
1110
        {
1111
            print_options opts;
22✔
1112
            if (parser().attribute_present("gridLines"))
66✔
1113
            {
1114
                opts.print_grid_lines.set(parser().attribute<bool>("gridLines"));
54✔
1115
            }
1116
            if (parser().attribute_present("gridLinesSet"))
66✔
1117
            {
1118
                opts.grid_lines_set.set(parser().attribute<bool>("gridLinesSet"));
54✔
1119
            }
1120
            if (parser().attribute_present("headings"))
66✔
1121
            {
1122
                opts.print_headings.set(parser().attribute<bool>("headings"));
54✔
1123
            }
1124
            if (parser().attribute_present("horizontalCentered"))
66✔
1125
            {
1126
                opts.horizontal_centered.set(parser().attribute<bool>("horizontalCentered"));
63✔
1127
            }
1128
            if (parser().attribute_present("verticalCentered"))
66✔
1129
            {
1130
                opts.vertical_centered.set(parser().attribute<bool>("verticalCentered"));
54✔
1131
            }
1132
            ws.d_->print_options_.set(opts);
22✔
1133
            skip_remaining_content(current_worksheet_element);
22✔
1134
        }
22✔
1135
        else if (current_worksheet_element == qn("spreadsheetml", "pageMargins")) // CT_PageMargins 0-1
776✔
1136
        {
1137
            page_margins margins;
110✔
1138

1139
            margins.top(xlnt::detail::deserialise(parser().attribute("top")));
220✔
1140
            margins.bottom(xlnt::detail::deserialise(parser().attribute("bottom")));
220✔
1141
            margins.left(xlnt::detail::deserialise(parser().attribute("left")));
220✔
1142
            margins.right(xlnt::detail::deserialise(parser().attribute("right")));
220✔
1143
            margins.header(xlnt::detail::deserialise(parser().attribute("header")));
220✔
1144
            margins.footer(xlnt::detail::deserialise(parser().attribute("footer")));
220✔
1145

1146
            ws.page_margins(margins);
110✔
1147
        }
1148
        else if (current_worksheet_element == qn("spreadsheetml", "pageSetup")) // CT_PageSetup 0-1
336✔
1149
        {
1150
            page_setup setup;
35✔
1151
            if (parser().attribute_present("orientation"))
105!
1152
            {
1153
                setup.orientation_.set(parser().attribute<orientation>("orientation"));
105✔
1154
            }
1155
            if (parser().attribute_present("horizontalDpi"))
105✔
1156
            {
1157
                setup.horizontal_dpi_.set(parser().attribute<std::size_t>("horizontalDpi"));
87✔
1158
            }
1159
            if (parser().attribute_present("verticalDpi"))
105✔
1160
            {
1161
                setup.vertical_dpi_.set(parser().attribute<std::size_t>("verticalDpi"));
93✔
1162
            }
1163
            if (parser().attribute_present("paperSize"))
105✔
1164
            {
1165
                setup.paper_size(static_cast<xlnt::paper_size>(parser().attribute<std::size_t>("paperSize")));
81✔
1166
            }
1167
            if (parser().attribute_present("scale"))
105✔
1168
            {
1169
                setup.scale(parser().attribute<double>("scale"));
69✔
1170
            }
1171
            if (parser().attribute_present(qn("r", "id")))
175✔
1172
            {
1173
                setup.rel_id(parser().attribute(qn("r", "id")));
25✔
1174
            }
1175
            ws.page_setup(setup);
35✔
1176
            skip_remaining_content(current_worksheet_element);
35✔
1177
        }
35✔
1178
        else if (current_worksheet_element == qn("spreadsheetml", "headerFooter")) // CT_HeaderFooter 0-1
196✔
1179
        {
1180
            header_footer hf;
28✔
1181

1182
            hf.align_with_margins(!parser().attribute_present("alignWithMargins")
84!
1183
                || is_true(parser().attribute("alignWithMargins")));
90!
1184
            hf.scale_with_doc(!parser().attribute_present("alignWithMargins")
84!
1185
                || is_true(parser().attribute("alignWithMargins")));
90!
1186
            auto different_odd_even = parser().attribute_present("differentOddEven")
56!
1187
                && is_true(parser().attribute("differentOddEven"));
92!
1188
            auto different_first = parser().attribute_present("differentFirst")
56!
1189
                && is_true(parser().attribute("differentFirst"));
92!
1190

1191
            optional<std::array<optional<rich_text>, 3>> odd_header;
28✔
1192
            optional<std::array<optional<rich_text>, 3>> odd_footer;
28✔
1193
            optional<std::array<optional<rich_text>, 3>> even_header;
28✔
1194
            optional<std::array<optional<rich_text>, 3>> even_footer;
28✔
1195
            optional<std::array<optional<rich_text>, 3>> first_header;
28✔
1196
            optional<std::array<optional<rich_text>, 3>> first_footer;
28✔
1197

1198
            using xlnt::detail::decode_header_footer;
1199

1200
            while (in_element(current_worksheet_element))
76✔
1201
            {
1202
                auto current_hf_element = expect_start_element(xml::content::simple);
48✔
1203

1204
                if (current_hf_element == qn("spreadsheetml", "oddHeader"))
192✔
1205
                {
1206
                    odd_header = decode_header_footer(read_text());
23✔
1207
                }
1208
                else if (current_hf_element == qn("spreadsheetml", "oddFooter"))
100!
1209
                {
1210
                    odd_footer = decode_header_footer(read_text());
25✔
1211
                }
1212
                else if (current_hf_element == qn("spreadsheetml", "evenHeader"))
×
1213
                {
1214
                    even_header = decode_header_footer(read_text());
×
1215
                }
1216
                else if (current_hf_element == qn("spreadsheetml", "evenFooter"))
×
1217
                {
1218
                    even_footer = decode_header_footer(read_text());
×
1219
                }
1220
                else if (current_hf_element == qn("spreadsheetml", "firstHeader"))
×
1221
                {
1222
                    first_header = decode_header_footer(read_text());
×
1223
                }
1224
                else if (current_hf_element == qn("spreadsheetml", "firstFooter"))
×
1225
                {
1226
                    first_footer = decode_header_footer(read_text());
×
1227
                }
1228
                else
1229
                {
1230
                    unexpected_element(current_hf_element);
×
1231
                }
1232

1233
                expect_end_element(current_hf_element);
48✔
1234
            }
48✔
1235

1236
            for (std::size_t i = 0; i < 3; ++i)
112✔
1237
            {
1238
                auto loc = i == 0 ? header_footer::location::left
140✔
1239
                                  : i == 1 ? header_footer::location::center : header_footer::location::right;
56✔
1240

1241
                if (different_odd_even)
84!
1242
                {
1243
                    if (odd_header.is_set()
×
1244
                        && odd_header.get().at(i).is_set()
×
1245
                        && even_header.is_set()
×
1246
                        && even_header.get().at(i).is_set())
×
1247
                    {
1248
                        hf.odd_even_header(loc, odd_header.get().at(i).get(), even_header.get().at(i).get());
×
1249
                    }
1250

1251
                    if (odd_footer.is_set()
×
1252
                        && odd_footer.get().at(i).is_set()
×
1253
                        && even_footer.is_set()
×
1254
                        && even_footer.get().at(i).is_set())
×
1255
                    {
1256
                        hf.odd_even_footer(loc, odd_footer.get().at(i).get(), even_footer.get().at(i).get());
×
1257
                    }
1258
                }
1259
                else
1260
                {
1261
                    if (odd_header.is_set() && odd_header.get().at(i).is_set())
84✔
1262
                    {
1263
                        hf.header(loc, odd_header.get().at(i).get());
23✔
1264
                    }
1265

1266
                    if (odd_footer.is_set() && odd_footer.get().at(i).is_set())
84✔
1267
                    {
1268
                        hf.footer(loc, odd_footer.get().at(i).get());
25✔
1269
                    }
1270
                }
1271

1272
                if (different_first)
1273
                {
1274
                }
1275
            }
1276

1277
            ws.header_footer(hf);
28✔
1278
        }
28✔
1279
        else if (current_worksheet_element == qn("spreadsheetml", "rowBreaks")) // CT_PageBreak 0-1
84!
1280
        {
1281
            auto count = parser().attribute_present("count") ? parser().attribute<std::size_t>("count") : 0;
×
1282
            auto manual_break_count = parser().attribute_present("manualBreakCount")
×
1283
                ? parser().attribute<std::size_t>("manualBreakCount")
×
1284
                : 0;
×
1285

1286
            while (in_element(qn("spreadsheetml", "rowBreaks")))
×
1287
            {
1288
                expect_start_element(qn("spreadsheetml", "brk"), xml::content::simple);
×
1289

1290
                if (parser().attribute_present("id"))
×
1291
                {
1292
                    ws.page_break_at_row(parser().attribute<row_t>("id"));
×
1293
                    --count;
×
1294
                }
1295

1296
                if (parser().attribute_present("man") && is_true(parser().attribute("man")))
×
1297
                {
1298
                    --manual_break_count;
×
1299
                }
1300

1301
                skip_attributes({"min", "max", "pt"});
×
1302
                expect_end_element(qn("spreadsheetml", "brk"));
×
1303
            }
1304
        }
1305
        else if (current_worksheet_element == qn("spreadsheetml", "colBreaks")) // CT_PageBreak 0-1
84!
1306
        {
1307
            auto count = parser().attribute_present("count") ? parser().attribute<std::size_t>("count") : 0;
×
1308
            auto manual_break_count = parser().attribute_present("manualBreakCount")
×
1309
                ? parser().attribute<std::size_t>("manualBreakCount")
×
1310
                : 0;
×
1311

1312
            while (in_element(qn("spreadsheetml", "colBreaks")))
×
1313
            {
1314
                expect_start_element(qn("spreadsheetml", "brk"), xml::content::simple);
×
1315

1316
                if (parser().attribute_present("id"))
×
1317
                {
1318
                    ws.page_break_at_column(parser().attribute<column_t::index_t>("id"));
×
1319
                    --count;
×
1320
                }
1321

1322
                if (parser().attribute_present("man") && is_true(parser().attribute("man")))
×
1323
                {
1324
                    --manual_break_count;
×
1325
                }
1326

1327
                skip_attributes({"min", "max", "pt"});
×
1328
                expect_end_element(qn("spreadsheetml", "brk"));
×
1329
            }
1330
        }
1331
        else if (current_worksheet_element == qn("spreadsheetml", "customProperties")) // CT_CustomProperties 0-1
84!
1332
        {
1333
            skip_remaining_content(current_worksheet_element);
×
1334
        }
1335
        else if (current_worksheet_element == qn("spreadsheetml", "cellWatches")) // CT_CellWatches 0-1
84!
1336
        {
1337
            skip_remaining_content(current_worksheet_element);
×
1338
        }
1339
        else if (current_worksheet_element == qn("spreadsheetml", "ignoredErrors")) // CT_IgnoredErrors 0-1
84!
1340
        {
1341
            skip_remaining_content(current_worksheet_element);
×
1342
        }
1343
        else if (current_worksheet_element == qn("spreadsheetml", "smartTags")) // CT_SmartTags 0-1
84!
1344
        {
1345
            skip_remaining_content(current_worksheet_element);
×
1346
        }
1347
        else if (current_worksheet_element == qn("spreadsheetml", "drawing")) // CT_Drawing 0-1
84✔
1348
        {
1349
            if (parser().attribute_present(qn("r", "id")))
15!
1350
            {
1351
                auto drawing_rel_id = parser().attribute(qn("r", "id"));
12✔
1352
                ws.d_->drawing_rel_id_ = drawing_rel_id;
3✔
1353
            }
3✔
1354
        }
1355
        else if (current_worksheet_element == qn("spreadsheetml", "legacyDrawing"))
72✔
1356
        {
1357
            skip_remaining_content(current_worksheet_element);
14✔
1358
        }
1359
        else if (current_worksheet_element == qn("spreadsheetml", "extLst"))
16!
1360
        {
1361
            ext_list extensions(parser(), current_worksheet_element.namespace_());
4✔
1362
            ws.d_->extension_list_.set(extensions);
4✔
1363
        }
4✔
1364
        else
1365
        {
1366
            unexpected_element(current_worksheet_element);
×
1367
        }
1368

1369
        expect_end_element(current_worksheet_element);
248✔
1370
    }
248✔
1371

1372
    expect_end_element(qn("spreadsheetml", "worksheet"));
381✔
1373

1374
    if (manifest.has_relationship(sheet_path, xlnt::relationship_type::comments))
127✔
1375
    {
1376
        auto comments_part = manifest.canonicalize({workbook_rel, sheet_rel,
84!
1377
            manifest.relationship(sheet_path, xlnt::relationship_type::comments)});
14✔
1378

1379
        auto receive = xml::parser::receive_default;
14✔
1380
        auto comments_part_streambuf = archive_->open(comments_part);
14✔
1381
        std::istream comments_part_stream(comments_part_streambuf.get());
14✔
1382
        xml::parser parser(comments_part_stream, comments_part.string(), receive);
14✔
1383
        parser_ = &parser;
14✔
1384

1385
        read_comments(ws);
14✔
1386

1387
        if (manifest.has_relationship(sheet_path, xlnt::relationship_type::vml_drawing))
14!
1388
        {
1389
            auto vml_drawings_part = manifest.canonicalize({workbook_rel, sheet_rel,
84!
1390
                manifest.relationship(sheet_path, xlnt::relationship_type::vml_drawing)});
14✔
1391

1392
            auto vml_drawings_part_streambuf = archive_->open(comments_part);
14✔
1393
            std::istream vml_drawings_part_stream(comments_part_streambuf.get());
14✔
1394
            xml::parser vml_parser(vml_drawings_part_stream, vml_drawings_part.string(), receive);
14✔
1395
            parser_ = &vml_parser;
14✔
1396

1397
            read_vml_drawings(ws);
14✔
1398
        }
14✔
1399
    }
14✔
1400

1401
    if (manifest.has_relationship(sheet_path, xlnt::relationship_type::drawings))
127✔
1402
    {
1403
        auto drawings_part = manifest.canonicalize({workbook_rel, sheet_rel,
18!
1404
            manifest.relationship(sheet_path, xlnt::relationship_type::drawings)});
3✔
1405

1406
        auto receive = xml::parser::receive_default;
3✔
1407
        auto drawings_part_streambuf = archive_->open(drawings_part);
3✔
1408
        std::istream drawings_part_stream(drawings_part_streambuf.get());
3✔
1409
        xml::parser parser(drawings_part_stream, drawings_part.string(), receive);
3✔
1410
        parser_ = &parser;
3✔
1411

1412
        read_drawings(ws, drawings_part);
3✔
1413
    }
3✔
1414

1415
    if (manifest.has_relationship(sheet_path, xlnt::relationship_type::printer_settings))
127✔
1416
    {
1417
        read_part({workbook_rel, sheet_rel,
25!
1418
            manifest.relationship(sheet_path,
1419
                relationship_type::printer_settings)});
1420
    }
1421

1422
    for (auto array_formula : array_formulae_)
133✔
1423
    {
1424
        for (auto row : ws.range(array_formula.first))
18✔
1425
        {
1426
            for (auto cell : row)
24✔
1427
            {
1428
                cell.formula(array_formula.second);
12✔
1429
            }
1430
        }
6✔
1431
    }
6✔
1432

1433
    return ws;
254✔
1434
}
163!
1435

1436
xml::parser &xlsx_consumer::parser()
211,721✔
1437
{
1438
    return *parser_;
211,721✔
1439
}
1440

1441
bool xlsx_consumer::has_cell()
155✔
1442
{
1443
    auto ws = worksheet(current_worksheet_);
155✔
1444

1445
    while (streaming_cell_ // we're not at the end of the file
155✔
1446
           && !in_element(qn("spreadsheetml", "row"))) // we're at the end of a row, or between rows
985!
1447
    {
1448
        if (parser().peek() == xml::parser::event_type::end_element
44✔
1449
            && stack_.back() == qn("spreadsheetml", "row"))
252!
1450
        {
1451
            // We're at the end of a row.
1452
            expect_end_element(qn("spreadsheetml", "row"));
164✔
1453
            // ... and keep parsing.
1454
        }
1455

1456
        if (parser().peek() == xml::parser::event_type::end_element
44✔
1457
            && stack_.back() == qn("spreadsheetml", "sheetData"))
96!
1458
        {
1459
            // End of sheet. Mark it by setting streaming_cell_ to nullptr, so we never get here again.
1460
            expect_end_element(qn("spreadsheetml", "sheetData"));
6✔
1461
            streaming_cell_.reset(nullptr);
2✔
1462
            break;
2✔
1463
        }
1464

1465
        expect_start_element(qn("spreadsheetml", "row"), xml::content::complex); // CT_Row
168✔
1466
        row_t row_index = 0;
42✔
1467
        bool ok = detail::parse(parser().attribute("r"), row_index) == std::errc();
84✔
1468

1469
        if (!ok)
42✔
1470
        {
1471
#ifdef THROW_ON_INVALID_XML
1472
            throw xlnt::invalid_parameter("cannot parse row index \"" + parser().attribute("r") + "\"");
1473
#endif
1474
        }
1475
        auto &row_properties = ws.row_properties(row_index);
42✔
1476

1477
        if (parser().attribute_present("ht"))
126✔
1478
        {
1479
            row_properties.height = xlnt::detail::deserialise(parser().attribute("ht"));
21✔
1480
        }
1481

1482
        if (parser().attribute_present("customHeight"))
126!
1483
        {
1484
            row_properties.custom_height = is_true(parser().attribute("customHeight"));
×
1485
        }
1486

1487
        if (parser().attribute_present("hidden") && is_true(parser().attribute("hidden")))
128!
1488
        {
1489
            row_properties.hidden = true;
1✔
1490
        }
1491

1492
        if (parser().attribute_present(qn("x14ac", "dyDescent")))
210✔
1493
        {
1494
            row_properties.dy_descent = xlnt::detail::deserialise(parser().attribute(qn("x14ac", "dyDescent")));
175✔
1495
        }
1496

1497
        if (parser().attribute_present("spans"))
126✔
1498
        {
1499
            row_properties.spans = parser().attribute("spans");
111✔
1500
        }
1501

1502
        if (parser().attribute_present("outlineLevel"))
126✔
1503
        {
1504
            row_properties.outline_level = parser().attribute<uint16_t>("outlineLevel");
3✔
1505
        }
1506

1507
        if (parser().attribute_present("collapsed"))
126✔
1508
        {
1509
            row_properties.collapsed = is_true(parser().attribute("collapsed"));
3✔
1510
        }
1511

1512

1513
        skip_attributes({"customFormat", "s", "customFont",
126✔
1514
            "thickTop", "thickBot", "ph"});
1515
    }
1516

1517
    if (!streaming_cell_)
155✔
1518
    {
1519
        // We're at the end of the worksheet
1520
        return false;
2✔
1521
    }
1522

1523
    expect_start_element(qn("spreadsheetml", "c"), xml::content::complex);
612✔
1524

1525
    assert(streaming_);
153!
1526
    streaming_cell_.reset(new detail::cell_impl()); // Clean cell state - otherwise it might contain information from the previously streamed cell.
153!
1527
    auto cell = xlnt::cell(streaming_cell_.get());
153✔
1528
    auto reference = cell_reference(parser().attribute("r"));
306✔
1529
    cell.d_->parent_ = current_worksheet_;
153✔
1530
    cell.d_->column_ = reference.column_index();
153✔
1531
    cell.d_->row_ = reference.row();
153✔
1532

1533
    if (parser().attribute_present("ph"))
459!
1534
    {
1535
        cell.d_->phonetics_visible_ = parser().attribute<bool>("ph");
×
1536
    }
1537

1538
    auto has_type = parser().attribute_present("t");
306✔
1539
    auto type = has_type ? parser().attribute("t") : "n";
306!
1540

1541
    if (parser().attribute_present("s"))
459✔
1542
    {
1543
        size_t s = 0;
32✔
1544
        bool ok = detail::parse(parser().attribute("s"), s) == std::errc();
64✔
1545

1546
        if (!ok)
32✔
1547
        {
1548
#ifdef THROW_ON_INVALID_XML
1549
            throw xlnt::invalid_parameter("cannot parse format index \"" + parser().attribute("s") + "\"");
1550
#endif
1551
        }
1552

1553
        cell.format(target_.format(s));
32✔
1554
    }
1555

1556
    auto has_value = false;
153✔
1557
    auto value_string = std::string();
153✔
1558
    auto formula_string = std::string();
153✔
1559

1560
    while (in_element(qn("spreadsheetml", "c")))
1,377✔
1561
    {
1562
        auto current_element = expect_start_element(xml::content::mixed);
153✔
1563

1564
        if (current_element == qn("spreadsheetml", "v")) // s:ST_Xstring
612✔
1565
        {
1566
            has_value = true;
152✔
1567
            value_string = read_text();
152✔
1568
        }
1569
        else if (current_element == qn("spreadsheetml", "f")) // CT_CellFormula
4!
1570
        {
1571
            auto has_shared_formula = false;
×
1572
            auto has_array_formula = false;
×
1573
            auto is_master_cell = false;
×
1574
            auto shared_formula_index = 0;
×
1575
            auto formula_range = range_reference();
×
1576

1577
            if (parser().attribute_present("t"))
×
1578
            {
1579
                auto formula_type = parser().attribute("t");
×
1580
                if (formula_type == "shared")
×
1581
                {
1582
                    has_shared_formula = true;
×
1583
                    shared_formula_index = parser().attribute<int>("si");
×
1584
                    if (parser().attribute_present("ref"))
×
1585
                    {
1586
                        is_master_cell = true;
×
1587
                    }
1588
                }
1589
                else if (formula_type == "array")
×
1590
                {
1591
                    has_array_formula = true;
×
1592
                    formula_range = range_reference(parser().attribute("ref"));
×
1593
                    is_master_cell = true;
×
1594
                }
1595
            }
×
1596

1597
            skip_attributes({"aca", "dt2D", "dtr", "del1", "del2", "r1",
×
1598
                "r2", "ca", "bx"});
1599

1600
            formula_string = read_text();
×
1601

1602
            if (is_master_cell)
×
1603
            {
1604
                if (has_shared_formula)
×
1605
                {
1606
                    shared_formulae_[shared_formula_index] = formula_string;
×
1607
                }
1608
                else if (has_array_formula)
×
1609
                {
1610
                    array_formulae_[formula_range.to_string()] = formula_string;
×
1611
                }
1612
            }
1613
            else if (has_shared_formula)
×
1614
            {
1615
                auto shared_formula = shared_formulae_.find(shared_formula_index);
×
1616
                if (shared_formula != shared_formulae_.end())
×
1617
                {
1618
                    formula_string = shared_formula->second;
×
1619
                }
1620
            }
1621
        }
1622
        else if (current_element == qn("spreadsheetml", "is")) // CT_Rst
4!
1623
        {
1624
            expect_start_element(qn("spreadsheetml", "t"), xml::content::simple);
4✔
1625
            has_value = true;
1✔
1626
            value_string = read_text();
1✔
1627
            expect_end_element(qn("spreadsheetml", "t"));
4✔
1628
        }
1629
        else
1630
        {
1631
            unexpected_element(current_element);
×
1632
        }
1633

1634
        expect_end_element(current_element);
153✔
1635
    }
153✔
1636

1637
    expect_end_element(qn("spreadsheetml", "c"));
459✔
1638

1639
    if (!formula_string.empty())
153!
1640
    {
1641
        cell.formula(formula_string);
×
1642
    }
1643

1644
    if (has_value)
153!
1645
    {
1646
        if (type == "str")
153!
1647
        {
1648
            cell.d_->value_text_ = value_string;
×
1649
            cell.data_type(cell::type::formula_string);
×
1650
        }
1651
        else if (type == "inlineStr")
153✔
1652
        {
1653
            cell.d_->value_text_ = value_string;
1✔
1654
            cell.data_type(cell::type::inline_string);
1✔
1655
        }
1656
        else if (type == "s")
152!
1657
        {
1658
            cell.d_->value_numeric_ = xlnt::detail::deserialise(value_string);
152✔
1659
            cell.data_type(cell::type::shared_string);
152✔
1660
        }
1661
        else if (type == "b") // boolean
×
1662
        {
1663
            cell.value(is_true(value_string));
×
1664
        }
1665
        else if (type == "n") // numeric
×
1666
        {
1667
            cell.value(xlnt::detail::deserialise(value_string));
×
1668
        }
1669
        else if (!value_string.empty() && value_string[0] == '#')
×
1670
        {
1671
            cell.error(value_string);
×
1672
        }
1673
    }
1674

1675
    return true;
153✔
1676
}
153✔
1677

1678
std::vector<relationship> xlsx_consumer::read_relationships(const path &part)
1,212✔
1679
{
1680
    const auto part_rels_path = part.parent().append("_rels").append(part.filename() + ".rels").relative_to(path("/"));
6,060✔
1681

1682
    std::vector<xlnt::relationship> relationships;
1,212✔
1683
    if (!archive_->has_file(part_rels_path)) return relationships;
1,212✔
1684

1685
    auto rels_streambuf = archive_->open(part_rels_path);
242✔
1686
    std::istream rels_stream(rels_streambuf.get());
242✔
1687
    xml::parser parser(rels_stream, part_rels_path.string());
242✔
1688
    parser_ = &parser;
242✔
1689

1690
    expect_start_element(qn("relationships", "Relationships"), xml::content::complex);
968✔
1691

1692
    while (in_element(qn("relationships", "Relationships")))
4,442✔
1693
    {
1694
        expect_start_element(qn("relationships", "Relationship"), xml::content::simple);
4,040✔
1695

1696
        const auto target_mode = parser.attribute_present("TargetMode")
1,616✔
1697
            ? parser.attribute<xlnt::target_mode>("TargetMode")
1,676!
1698
            : xlnt::target_mode::internal;
808✔
1699
        auto target = xlnt::uri(parser.attribute("Target"));
808✔
1700

1701
        if (target.path().is_absolute() && target_mode == xlnt::target_mode::internal)
808!
1702
        {
1703
            target = uri(target.path().relative_to(path(part.string()).resolve(path("/"))).string());
21✔
1704
        }
1705

1706
        relationships.emplace_back(parser.attribute("Id"),
808✔
1707
            parser.attribute<xlnt::relationship_type>("Type"),
1,616✔
1708
            xlnt::uri(part.string()), target, target_mode);
1,616✔
1709

1710
        expect_end_element(qn("relationships", "Relationship"));
2,424✔
1711
    }
808✔
1712

1713
    expect_end_element(qn("relationships", "Relationships"));
726✔
1714
    parser_ = nullptr;
242✔
1715

1716
    return relationships;
242✔
1717
}
1,212✔
1718

1719
void xlsx_consumer::read_stylesheet (const std::string& xml)
1✔
1720
{
1721
    std::istringstream part_stream(xml);
1✔
1722
    xml::parser parser(part_stream, "/xl/styles.xml");
1✔
1723
    parser_ = &parser;
1✔
1724
    read_stylesheet();
1✔
1725
    parser_ = nullptr;
1✔
1726
}
1✔
1727

1728
void xlsx_consumer::read_part(const std::vector<relationship> &rel_chain)
690✔
1729
{
1730
    const auto &manifest = target_.manifest();
690✔
1731
    const auto part_path = manifest.canonicalize(rel_chain);
690✔
1732
    auto part_streambuf = archive_->open(part_path);
690✔
1733
    std::istream part_stream(part_streambuf.get());
690✔
1734
    xml::parser parser(part_stream, part_path.string());
690✔
1735
    parser_ = &parser;
690✔
1736

1737
    switch (rel_chain.back().type())
690!
1738
    {
1739
    case relationship_type::core_properties:
87✔
1740
        read_core_properties();
87✔
1741
        break;
87✔
1742

1743
    case relationship_type::extended_properties:
87✔
1744
        read_extended_properties();
87✔
1745
        break;
87✔
1746

1747
    case relationship_type::custom_properties:
6✔
1748
        read_custom_properties();
6✔
1749
        break;
6✔
1750

1751
    case relationship_type::office_document:
98✔
1752
        read_office_document(manifest.content_type(part_path));
99✔
1753
        break;
97✔
1754

1755
    case relationship_type::connections:
×
1756
        read_connections();
×
1757
        break;
×
1758

1759
    case relationship_type::custom_xml_mappings:
×
1760
        read_custom_xml_mappings();
×
1761
        break;
×
1762

1763
    case relationship_type::external_workbook_references:
×
1764
        read_external_workbook_references();
×
1765
        break;
×
1766

1767
    case relationship_type::pivot_table:
×
1768
        read_pivot_table();
×
1769
        break;
×
1770

1771
    case relationship_type::shared_workbook_revision_headers:
×
1772
        read_shared_workbook_revision_headers();
×
1773
        break;
×
1774

1775
    case relationship_type::volatile_dependencies:
×
1776
        read_volatile_dependencies();
×
1777
        break;
×
1778

1779
    case relationship_type::shared_string_table:
67✔
1780
        read_shared_string_table();
67✔
1781
        break;
67✔
1782

1783
    case relationship_type::stylesheet:
87✔
1784
        read_stylesheet();
87✔
1785
        break;
87✔
1786

1787
    case relationship_type::theme:
75✔
1788
        read_theme();
75✔
1789
        break;
75✔
1790

1791
    case relationship_type::chartsheet:
×
1792
        read_chartsheet(rel_chain.back().id());
×
1793
        break;
×
1794

1795
    case relationship_type::dialogsheet:
×
1796
        read_dialogsheet(rel_chain.back().id());
×
1797
        break;
×
1798

1799
    case relationship_type::worksheet:
126✔
1800
        read_worksheet(rel_chain.back().id());
126✔
1801
        break;
126✔
1802

1803
    case relationship_type::thumbnail:
45✔
1804
        read_image(part_path);
45✔
1805
        break;
45✔
1806

1807
    case relationship_type::calculation_chain:
×
1808
        read_calculation_chain();
×
1809
        break;
×
1810

1811
    case relationship_type::hyperlink:
×
1812
        break;
×
1813

1814
    case relationship_type::comments:
×
1815
        break;
×
1816

1817
    case relationship_type::vml_drawing:
×
1818
        break;
×
1819

1820
    case relationship_type::unknown:
3✔
1821
        break;
3✔
1822

1823
    case relationship_type::printer_settings:
5✔
1824
        read_binary(part_path);
5✔
1825
        break;
5✔
1826

1827
    case relationship_type::custom_property:
×
1828
        break;
×
1829

1830
    case relationship_type::drawings:
×
1831
        break;
×
1832

1833
    case relationship_type::pivot_table_cache_definition:
×
1834
        break;
×
1835

1836
    case relationship_type::pivot_table_cache_records:
×
1837
        break;
×
1838

1839
    case relationship_type::query_table:
×
1840
        break;
×
1841

1842
    case relationship_type::shared_workbook:
×
1843
        break;
×
1844

1845
    case relationship_type::revision_log:
×
1846
        break;
×
1847

1848
    case relationship_type::shared_workbook_user_data:
×
1849
        break;
×
1850

1851
    case relationship_type::single_cell_table_definitions:
×
1852
        break;
×
1853

1854
    case relationship_type::table_definition:
×
1855
        break;
×
1856

1857
    case relationship_type::vbaproject:
2✔
1858
        read_binary(part_path);
2✔
1859
        break;
2✔
1860

1861
    case relationship_type::image:
2✔
1862
        read_image(part_path);
2✔
1863
        break;
2✔
1864
    }
1865

1866
    parser_ = nullptr;
689✔
1867
}
693✔
1868

1869
void xlsx_consumer::populate_workbook(bool streaming)
98✔
1870
{
1871
    streaming_ = streaming;
98✔
1872

1873
    target_.clear();
98✔
1874

1875
    read_content_types();
98✔
1876
    const auto root_path = path("/");
98✔
1877

1878
    for (const auto &package_rel : read_relationships(root_path))
424✔
1879
    {
1880
        manifest().register_relationship(package_rel);
326✔
1881
    }
98✔
1882

1883
    for (auto package_rel : manifest().relationships(root_path))
424✔
1884
    {
1885
        if (package_rel.type() == relationship_type::office_document)
326✔
1886
        {
1887
            // Read the workbook after all the other package parts
1888
            continue;
98✔
1889
        }
1890

1891
        read_part({package_rel});
684!
1892
    }
424✔
1893

1894
    for (const auto &relationship_source_string : archive_->files())
1,212✔
1895
    {
1896
        for (const auto &part_rel : read_relationships(path(relationship_source_string)))
1,596✔
1897
        {
1898
            manifest().register_relationship(part_rel);
482✔
1899
        }
1,114✔
1900
    }
98✔
1901

1902
    read_part({manifest().relationship(root_path,
295✔
1903
        relationship_type::office_document)});
1904
}
425!
1905

1906
// Package Parts
1907

1908
void xlsx_consumer::read_content_types()
98✔
1909
{
1910
    auto &manifest = target_.manifest();
98✔
1911
    auto content_types_streambuf = archive_->open(path("[Content_Types].xml"));
196✔
1912
    std::istream content_types_stream(content_types_streambuf.get());
98✔
1913
    xml::parser parser(content_types_stream, "[Content_Types].xml");
98✔
1914
    parser_ = &parser;
98✔
1915

1916
    expect_start_element(qn("content-types", "Types"), xml::content::complex);
392✔
1917

1918
    while (in_element(qn("content-types", "Types")))
4,366✔
1919
    {
1920
        auto current_element = expect_start_element(xml::content::complex);
969✔
1921

1922
        if (current_element == qn("content-types", "Default"))
3,876✔
1923
        {
1924
            auto extension = parser.attribute("Extension");
498✔
1925
            auto content_type = parser.attribute("ContentType");
249✔
1926
            manifest.register_default_type(extension, content_type);
249✔
1927
        }
249✔
1928
        else if (current_element == qn("content-types", "Override"))
2,880!
1929
        {
1930
            auto part_name = parser.attribute("PartName");
1,440✔
1931
            auto content_type = parser.attribute("ContentType");
720✔
1932
            manifest.register_override_type(path(part_name), content_type);
720✔
1933
        }
720✔
1934
        else
1935
        {
1936
            unexpected_element(current_element);
×
1937
        }
1938

1939
        expect_end_element(current_element);
969✔
1940
    }
969✔
1941

1942
    expect_end_element(qn("content-types", "Types"));
294✔
1943
}
98✔
1944

1945
void xlsx_consumer::read_core_properties()
87✔
1946
{
1947
    //qn("extended-properties", "Properties");
1948
    //qn("custom-properties", "Properties");
1949
    expect_start_element(qn("core-properties", "coreProperties"), xml::content::complex);
348✔
1950

1951
    while (in_element(qn("core-properties", "coreProperties")))
2,119✔
1952
    {
1953
        const auto property_element = expect_start_element(xml::content::simple);
421✔
1954
        const auto prop = detail::from_string<core_property>(property_element.name());
421✔
1955
        if (prop == core_property::created || prop == core_property::modified)
421✔
1956
        {
1957
            skip_attribute(qn("xsi", "type"));
648✔
1958
        }
1959
        target_.core_property(prop, read_text());
421✔
1960
        expect_end_element(property_element);
421✔
1961
    }
421✔
1962

1963
    expect_end_element(qn("core-properties", "coreProperties"));
261✔
1964
}
87✔
1965

1966
void xlsx_consumer::read_extended_properties()
87✔
1967
{
1968
    expect_start_element(qn("extended-properties", "Properties"), xml::content::complex);
348✔
1969

1970
    while (in_element(qn("extended-properties", "Properties")))
3,411✔
1971
    {
1972
        const auto property_element = expect_start_element(xml::content::mixed);
744✔
1973
        const auto prop = detail::from_string<extended_property>(property_element.name());
744✔
1974
        target_.extended_property(prop, read_variant());
744✔
1975
        expect_end_element(property_element);
744✔
1976
    }
744✔
1977

1978
    expect_end_element(qn("extended-properties", "Properties"));
261✔
1979
}
87✔
1980

1981
void xlsx_consumer::read_custom_properties()
6✔
1982
{
1983
    expect_start_element(qn("custom-properties", "Properties"), xml::content::complex);
24✔
1984

1985
    while (in_element(qn("custom-properties", "Properties")))
58✔
1986
    {
1987
        const auto property_element = expect_start_element(xml::content::complex);
7✔
1988
        const auto prop = parser().attribute("name");
14✔
1989
        const auto format_id = parser().attribute("fmtid");
14✔
1990
        const auto property_id = parser().attribute("pid");
14✔
1991
        target_.custom_property(prop, read_variant());
7✔
1992
        expect_end_element(property_element);
7✔
1993
    }
7✔
1994

1995
    expect_end_element(qn("custom-properties", "Properties"));
18✔
1996
}
6✔
1997

1998
void xlsx_consumer::read_office_document(const std::string &content_type) // CT_Workbook
98✔
1999
{
2000
    if (content_type !=
196✔
2001
            "application/vnd."
2002
            "openxmlformats-officedocument.spreadsheetml.sheet.main+xml"
98✔
2003
        && content_type !=
3✔
2004
            "application/vnd."
2005
            "openxmlformats-officedocument.spreadsheetml.template.main+xml"
3!
2006
        && content_type !=
101✔
2007
            "application/vnd."
2008
            "ms-excel.sheet.macroEnabled.main+xml")
3✔
2009
    {
2010
        throw xlnt::invalid_file(content_type);
1✔
2011
    }
2012

2013
    target_.d_->calculation_properties_.clear();
97✔
2014

2015
    expect_start_element(qn("workbook", "workbook"), xml::content::complex);
485✔
2016
    skip_attribute(qn("mc", "Ignorable"));
291✔
2017

2018
    while (in_element(qn("workbook", "workbook")))
2,737✔
2019
    {
2020
        auto current_workbook_element = expect_start_element(xml::content::complex);
563✔
2021

2022
        if (current_workbook_element == qn("workbook", "fileVersion")) // CT_FileVersion 0-1
2,252✔
2023
        {
2024
            detail::workbook_impl::file_version_t file_version;
79✔
2025
            bool has_any_attribute = false;
79✔
2026

2027
            if (parser().attribute_present("appName"))
237!
2028
            {
2029
                file_version.app_name = parser().attribute("appName");
158✔
2030
                has_any_attribute = true;
79✔
2031
            }
2032

2033
            if (parser().attribute_present("lastEdited"))
237✔
2034
            {
2035
                file_version.last_edited = parser().attribute("lastEdited");
136✔
2036
                has_any_attribute = true;
68✔
2037
            }
2038

2039
            if (parser().attribute_present("lowestEdited"))
237✔
2040
            {
2041
                file_version.lowest_edited = parser().attribute("lowestEdited");
136✔
2042
                has_any_attribute = true;
68✔
2043
            }
2044

2045
            if (parser().attribute_present("rupBuild"))
237✔
2046
            {
2047
                file_version.rup_build = parser().attribute("rupBuild");
136✔
2048
                has_any_attribute = true;
68✔
2049
            }
2050

2051
            skip_attribute("codeName");
79✔
2052

2053
            if (has_any_attribute)
79!
2054
            {
2055
                target_.d_->file_version_ = std::move(file_version);
79✔
2056
            }
2057
        }
79✔
2058
        else if (current_workbook_element == qn("workbook", "fileSharing")) // CT_FileSharing 0-1
1,936!
2059
        {
2060
            skip_remaining_content(current_workbook_element);
×
2061
        }
2062
        else if (current_workbook_element == qn("mc", "AlternateContent"))
1,936✔
2063
        {
2064
            while (in_element(qn("mc", "AlternateContent")))
351✔
2065
            {
2066
                auto alternate_content_element = expect_start_element(xml::content::complex);
39✔
2067

2068
                if (alternate_content_element == qn("mc", "Choice")
234!
2069
                    && parser().attribute_present("Requires")
156!
2070
                    && parser().attribute("Requires") == "x15")
273!
2071
                {
2072
                    auto x15_element = expect_start_element(xml::content::simple);
39✔
2073

2074
                    if (x15_element == qn("x15ac", "absPath"))
156!
2075
                    {
2076
                        target_.d_->abs_path_ = parser().attribute("url");
117✔
2077
                    }
2078

2079
                    skip_remaining_content(x15_element);
39✔
2080
                    expect_end_element(x15_element);
39✔
2081
                }
39✔
2082

2083
                skip_remaining_content(alternate_content_element);
39✔
2084
                expect_end_element(alternate_content_element);
39✔
2085
            }
39✔
2086
        }
2087
        else if (current_workbook_element == qn("workbook", "workbookPr")) // CT_WorkbookPr 0-1
1,780✔
2088
        {
2089
            target_.base_date(parser().attribute_present("date1904") // optional, bool=false
393!
2090
                        && is_true(parser().attribute("date1904"))
112!
2091
                    ? calendar::mac_1904
2092
                    : calendar::windows_1900);
2093
            skip_attribute("showObjects"); // optional, ST_Objects="all"
194✔
2094
            skip_attribute("showBorderUnselectedTables"); // optional, bool=true
194✔
2095
            skip_attribute("filterPrivacy"); // optional, bool=false
194✔
2096
            skip_attribute("promptedSolutions"); // optional, bool=false
194✔
2097
            skip_attribute("showInkAnnotation"); // optional, bool=true
194✔
2098
            skip_attribute("backupFile"); // optional, bool=false
194✔
2099
            skip_attribute("saveExternalLinkValues"); // optional, bool=true
194✔
2100
            skip_attribute("updateLinks"); // optional, ST_UpdateLinks="userSet"
194✔
2101
            skip_attribute("codeName"); // optional, string
194✔
2102
            skip_attribute("hidePivotFieldList"); // optional, bool=false
194✔
2103
            skip_attribute("showPivotChartFilter"); // optional, bool=false
194✔
2104
            skip_attribute("allowRefreshQuery"); // optional, bool=false
194✔
2105
            skip_attribute("publishItems"); // optional, bool=false
194✔
2106
            skip_attribute("checkCompatibility"); // optional, bool=false
194✔
2107
            skip_attribute("autoCompressPictures"); // optional, bool=true
194✔
2108
            skip_attribute("refreshAllConnections"); // optional, bool=false
194✔
2109
            skip_attribute("defaultThemeVersion"); // optional, uint
194✔
2110
            skip_attribute("dateCompatibility"); // optional, bool (undocumented)
194✔
2111
        }
2112
        else if (current_workbook_element == qn("workbook", "workbookProtection")) // CT_WorkbookProtection 0-1
1,392✔
2113
        {
2114
            skip_remaining_content(current_workbook_element);
15✔
2115
        }
2116
        else if (current_workbook_element == qn("workbook", "bookViews")) // CT_BookViews 0-1
1,332✔
2117
        {
2118
            while (in_element(qn("workbook", "bookViews")))
774✔
2119
            {
2120
                expect_start_element(qn("workbook", "workbookView"), xml::content::simple);
344✔
2121
                skip_attributes({"firstSheet", "showHorizontalScroll",
172✔
2122
                    "showSheetTabs", "showVerticalScroll"});
2123

2124
                workbook_view view;
86✔
2125

2126
                if (parser().attribute_present("xWindow"))
258✔
2127
                {
2128
                    view.x_window = parser().attribute<int>("xWindow");
240✔
2129
                }
2130

2131
                if (parser().attribute_present("yWindow"))
258✔
2132
                {
2133
                    view.y_window = parser().attribute<int>("yWindow");
240✔
2134
                }
2135

2136
                if (parser().attribute_present("windowWidth"))
258✔
2137
                {
2138
                    view.window_width = parser().attribute<std::size_t>("windowWidth");
243✔
2139
                }
2140

2141
                if (parser().attribute_present("windowHeight"))
258✔
2142
                {
2143
                    view.window_height = parser().attribute<std::size_t>("windowHeight");
243✔
2144
                }
2145

2146
                if (parser().attribute_present("tabRatio"))
258✔
2147
                {
2148
                    view.tab_ratio = parser().attribute<std::size_t>("tabRatio");
186✔
2149
                }
2150

2151
                if (parser().attribute_present("activeTab"))
258✔
2152
                {
2153
                    view.active_tab = parser().attribute<std::size_t>("activeTab");
34✔
2154
                    target_.d_->active_sheet_index_.set(view.active_tab.get());
17✔
2155
                }
2156

2157
                target_.view(view);
86✔
2158

2159
                skip_attributes();
86✔
2160
                expect_end_element(qn("workbook", "workbookView"));
258✔
2161
            }
86✔
2162
        }
2163
        else if (current_workbook_element == qn("workbook", "sheets")) // CT_Sheets 1
988✔
2164
        {
2165
            std::size_t index = 0;
97✔
2166

2167
            while (in_element(qn("workbook", "sheets")))
1,001✔
2168
            {
2169
                expect_start_element(qn("spreadsheetml", "sheet"), xml::content::simple);
516✔
2170

2171
                auto title = parser().attribute("name");
258✔
2172

2173
                sheet_title_index_map_[title] = index++;
129✔
2174
                sheet_title_id_map_[title] = parser().attribute<std::size_t>("sheetId");
258✔
2175
                target_.d_->sheet_title_rel_id_map_[title] = parser().attribute(qn("r", "id"));
516✔
2176

2177
                bool hidden = parser().attribute<std::string>("state", "") == "hidden";
516✔
2178
                target_.d_->sheet_hidden_.push_back(hidden);
129✔
2179

2180
                expect_end_element(qn("spreadsheetml", "sheet"));
387✔
2181
            }
129✔
2182
        }
2183
        else if (current_workbook_element == qn("workbook", "functionGroups")) // CT_FunctionGroups 0-1
600✔
2184
        {
2185
            skip_remaining_content(current_workbook_element);
1✔
2186
        }
2187
        else if (current_workbook_element == qn("workbook", "externalReferences")) // CT_ExternalReferences 0-1
596✔
2188
        {
2189
            skip_remaining_content(current_workbook_element);
1✔
2190
        }
2191
        else if (current_workbook_element == qn("workbook", "definedNames")) // CT_DefinedNames 0-1
592✔
2192
        {
2193
            while (in_element(qn("workbook", "definedNames")))
80✔
2194
            {
2195
                expect_start_element(qn("spreadsheetml", "definedName"), xml::content::mixed);
40✔
2196

2197
                defined_name name;
10✔
2198
                name.name = parser().attribute("name");
20✔
2199
                if (parser().attribute_present("localSheetId"))
30✔
2200
                {
2201
                    name.sheet_id = parser().attribute<std::size_t>("localSheetId");
27✔
2202
                }
2203
                if (parser().attribute_present("hidden"))
30✔
2204
                {
2205
                    name.hidden = is_true(parser().attribute("hidden"));
3✔
2206
                }
2207
                parser().attribute_map(); // skip remaining attributes
10✔
2208
                name.value = read_text();
10✔
2209
                defined_names_.push_back(name);
10✔
2210

2211
                expect_end_element(qn("spreadsheetml", "definedName"));
30✔
2212
            }
10✔
2213
        }
2214
        else if (current_workbook_element == qn("workbook", "calcPr")) // CT_CalcPr 0-1
560✔
2215
        {
2216
            xlnt::calculation_properties calc_props;
84✔
2217
            if (parser().attribute_present("calcId"))
252✔
2218
            {
2219
                calc_props.calc_id = parser().attribute<std::size_t>("calcId");
216✔
2220
            }
2221
            if (parser().attribute_present("concurrentCalc"))
252✔
2222
            {
2223
                calc_props.concurrent_calc = is_true(parser().attribute("concurrentCalc"));
144✔
2224
            }
2225
            target_.calculation_properties(calc_props);
84✔
2226
            parser().attribute_map(); // skip remaining
84✔
2227
        }
2228
        else if (current_workbook_element == qn("workbook", "oleSize")) // CT_OleSize 0-1
224!
2229
        {
2230
            skip_remaining_content(current_workbook_element);
×
2231
        }
2232
        else if (current_workbook_element == qn("workbook", "customWorkbookViews")) // CT_CustomWorkbookViews 0-1
224!
2233
        {
2234
            skip_remaining_content(current_workbook_element);
×
2235
        }
2236
        else if (current_workbook_element == qn("workbook", "pivotCaches")) // CT_PivotCaches 0-1
224!
2237
        {
2238
            skip_remaining_content(current_workbook_element);
×
2239
        }
2240
        else if (current_workbook_element == qn("workbook", "smartTagPr")) // CT_SmartTagPr 0-1
224!
2241
        {
2242
            skip_remaining_content(current_workbook_element);
×
2243
        }
2244
        else if (current_workbook_element == qn("workbook", "smartTagTypes")) // CT_SmartTagTypes 0-1
224!
2245
        {
2246
            skip_remaining_content(current_workbook_element);
×
2247
        }
2248
        else if (current_workbook_element == qn("workbook", "webPublishing")) // CT_WebPublishing 0-1
224!
2249
        {
2250
            skip_remaining_content(current_workbook_element);
×
2251
        }
2252
        else if (current_workbook_element == qn("workbook", "fileRecoveryPr")) // CT_FileRecoveryPr 0+
224!
2253
        {
2254
            skip_remaining_content(current_workbook_element);
×
2255
        }
2256
        else if (current_workbook_element == qn("workbook", "webPublishObjects")) // CT_WebPublishObjects 0-1
224!
2257
        {
2258
            skip_remaining_content(current_workbook_element);
×
2259
        }
2260
        else if (current_workbook_element == qn("workbook", "extLst")) // CT_ExtensionList 0-1
224✔
2261
        {
2262
            while (in_element(qn("workbook", "extLst")))
404✔
2263
            {
2264
                auto extension_element = expect_start_element(xml::content::complex);
46✔
2265

2266
                if (extension_element == qn("workbook", "ext")
276!
2267
                    && parser().attribute_present("uri")
184!
2268
                    && parser().attribute("uri") == "{7523E5D3-25F3-A5E0-1632-64F254C22452}")
322!
2269
                {
2270
                    auto arch_id_extension_element = expect_start_element(xml::content::simple);
21✔
2271

2272
                    if (arch_id_extension_element == qn("mx", "ArchID"))
84!
2273
                    {
2274
                        target_.d_->arch_id_flags_ = parser().attribute<std::size_t>("Flags");
63✔
2275
                    }
2276

2277
                    skip_remaining_content(arch_id_extension_element);
21✔
2278
                    expect_end_element(arch_id_extension_element);
21✔
2279
                }
21✔
2280

2281
                skip_remaining_content(extension_element);
46✔
2282
                expect_end_element(extension_element);
46✔
2283
            }
46✔
2284
        }
2285
        else
2286
        {
2287
            unexpected_element(current_workbook_element);
12✔
2288
        }
2289

2290
        expect_end_element(current_workbook_element);
563✔
2291
    }
563✔
2292

2293
    expect_end_element(qn("workbook", "workbook"));
291✔
2294

2295
    auto workbook_rel = manifest().relationship(path("/"), relationship_type::office_document);
194✔
2296
    auto workbook_path = workbook_rel.target().path();
97✔
2297

2298
    const auto rel_types = {
97✔
2299
        relationship_type::shared_string_table,
2300
        relationship_type::stylesheet,
2301
        relationship_type::theme,
2302
        relationship_type::vbaproject,
2303
    };
97✔
2304

2305
    for (auto rel_type : rel_types)
485✔
2306
    {
2307
        if (manifest().has_relationship(workbook_path, rel_type))
388✔
2308
        {
2309
            read_part({workbook_rel,
924!
2310
                manifest().relationship(workbook_path, rel_type)});
462✔
2311
        }
2312
    }
2313

2314
    for (auto worksheet_rel : manifest().relationships(workbook_path, relationship_type::worksheet))
226✔
2315
    {
2316
        auto title = std::find_if(target_.d_->sheet_title_rel_id_map_.begin(),
129✔
2317
            target_.d_->sheet_title_rel_id_map_.end(),
129✔
2318
            [&](const std::pair<std::string, std::string> &p) {
204✔
2319
                return p.second == worksheet_rel.id();
204✔
2320
            })->first;
129✔
2321

2322
        auto id = sheet_title_id_map_[title];
129✔
2323
        auto index = sheet_title_index_map_[title];
129✔
2324

2325
        auto insertion_iter = target_.d_->worksheets_.begin();
129✔
2326
        while (insertion_iter != target_.d_->worksheets_.end()
148✔
2327
            && sheet_title_index_map_[insertion_iter->title_] < index)
148✔
2328
        {
2329
            ++insertion_iter;
19✔
2330
        }
2331

2332
        current_worksheet_ = &*target_.d_->worksheets_.emplace(insertion_iter, &target_, id, title);
129✔
2333

2334
        if (!streaming_)
129✔
2335
        {
2336
            read_part({workbook_rel, worksheet_rel});
504!
2337
        }
2338
    }
226✔
2339
}
454!
2340

2341
// Write Workbook Relationship Target Parts
2342

2343
void xlsx_consumer::read_calculation_chain()
×
2344
{
2345
}
×
2346

2347
void xlsx_consumer::read_chartsheet(const std::string & /*title*/)
×
2348
{
2349
}
×
2350

2351
void xlsx_consumer::read_connections()
×
2352
{
2353
}
×
2354

2355
void xlsx_consumer::read_custom_property()
×
2356
{
2357
}
×
2358

2359
void xlsx_consumer::read_custom_xml_mappings()
×
2360
{
2361
}
×
2362

2363
void xlsx_consumer::read_dialogsheet(const std::string & /*title*/)
×
2364
{
2365
}
×
2366

2367
void xlsx_consumer::read_external_workbook_references()
×
2368
{
2369
}
×
2370

2371
void xlsx_consumer::read_pivot_table()
×
2372
{
2373
}
×
2374

2375
void xlsx_consumer::read_shared_string_table()
67✔
2376
{
2377
    expect_start_element(qn("spreadsheetml", "sst"), xml::content::complex);
268✔
2378
    skip_attributes({"count"});
134✔
2379

2380
    while (in_element(qn("spreadsheetml", "sst")))
3,343✔
2381
    {
2382
        expect_start_element(qn("spreadsheetml", "si"), xml::content::complex);
3,760✔
2383
        auto rt = read_rich_text(qn("spreadsheetml", "si"));
2,256✔
2384
        target_.add_shared_string(rt, true);
752✔
2385
        expect_end_element(qn("spreadsheetml", "si"));
2,256✔
2386
    }
752✔
2387

2388
    expect_end_element(qn("spreadsheetml", "sst"));
201✔
2389

2390
#ifdef THROW_ON_INVALID_XML
2391
    if (parser().attribute_present("uniqueCount"))
2392
    {
2393
        std::size_t unique_count = parser().attribute<std::size_t>("uniqueCount");
2394
        if (unique_count != target_.shared_strings().size())
2395
        {
2396
            throw invalid_file("shared string sizes don't match (expected " + std::to_string(unique_count) + ", got " + std::to_string(target_.shared_strings().size()) + ")");
2397
        }
2398
    }
2399
#endif
2400
}
67✔
2401

2402
void xlsx_consumer::read_shared_workbook_revision_headers()
×
2403
{
2404
}
×
2405

2406
void xlsx_consumer::read_shared_workbook()
×
2407
{
2408
}
×
2409

2410
void xlsx_consumer::read_shared_workbook_user_data()
×
2411
{
2412
}
×
2413

2414
void xlsx_consumer::read_stylesheet()
88✔
2415
{
2416
    target_.impl().stylesheet_ = detail::stylesheet();
88✔
2417
    auto &stylesheet = target_.impl().stylesheet_.get();
88✔
2418
    stylesheet.garbage_collection_enabled = false; // garbage collection isn't allowed while reading the file, as other parts of the xlsx file reference to the index of a format in the stylesheet. If garbage collection is enabled, all formats will be deleted immediately (before the format usage is read).
88✔
2419

2420
    expect_start_element(qn("spreadsheetml", "styleSheet"), xml::content::complex);
352✔
2421
    skip_attributes({qn("mc", "Ignorable")});
352!
2422

2423
    std::vector<std::pair<style_impl, std::size_t>> styles;
88✔
2424
    std::vector<std::pair<format_impl, std::size_t>> format_records;
88✔
2425
    std::vector<std::pair<format_impl, std::size_t>> style_records;
88✔
2426

2427
    while (in_element(qn("spreadsheetml", "styleSheet")))
3,416✔
2428
    {
2429
        auto current_style_element = expect_start_element(xml::content::complex);
744✔
2430

2431
        if (current_style_element == qn("spreadsheetml", "borders"))
2,976✔
2432
        {
2433
            auto &borders = stylesheet.borders;
87✔
2434
            optional<std::size_t> count;
87✔
2435
            if (parser().attribute_present("count"))
261!
2436
            {
2437
                count = parser().attribute<std::size_t>("count");
174✔
2438
                borders.reserve(xlnt::detail::clip_reserve_elements(count.get()));
87✔
2439
            }
2440

2441
            while (in_element(qn("spreadsheetml", "borders")))
1,207✔
2442
            {
2443
                borders.push_back(xlnt::border());
193✔
2444
                auto &border = borders.back();
193✔
2445

2446
                expect_start_element(qn("spreadsheetml", "border"), xml::content::complex);
772✔
2447

2448
                auto diagonal = diagonal_direction::neither;
193✔
2449

2450
                if (parser().attribute_present("diagonalDown") && parser().attribute("diagonalDown") == "1")
601!
2451
                {
2452
                    diagonal = diagonal_direction::down;
×
2453
                }
2454

2455
                if (parser().attribute_present("diagonalUp") && parser().attribute("diagonalUp") == "1")
601!
2456
                {
2457
                    diagonal = diagonal == diagonal_direction::down ? diagonal_direction::both : diagonal_direction::up;
×
2458
                }
2459

2460
                if (diagonal != diagonal_direction::neither)
193!
2461
                {
2462
                    border.diagonal(diagonal);
×
2463
                }
2464

2465
                while (in_element(qn("spreadsheetml", "border")))
4,789✔
2466
                {
2467
                    auto current_side_element = expect_start_element(xml::content::complex);
956✔
2468

2469
                    xlnt::border::border_property side;
956✔
2470

2471
                    if (parser().attribute_present("style"))
2,868✔
2472
                    {
2473
                        side.style(parser().attribute<xlnt::border_style>("style"));
840✔
2474
                    }
2475

2476
                    if (in_element(current_side_element))
956✔
2477
                    {
2478
                        expect_start_element(qn("spreadsheetml", "color"), xml::content::complex);
1,116✔
2479
                        side.color(read_color());
279✔
2480
                        expect_end_element(qn("spreadsheetml", "color"));
1,116✔
2481
                    }
2482

2483
                    expect_end_element(current_side_element);
956✔
2484

2485
                    auto side_type = xml::value_traits<xlnt::border_side>::parse(current_side_element.name(), parser());
956✔
2486
                    border.side(side_type, side);
956✔
2487
                }
956✔
2488

2489
                expect_end_element(qn("spreadsheetml", "border"));
772✔
2490
            }
2491

2492
#ifdef THROW_ON_INVALID_XML
2493
            if (count.is_set() && count != borders.size())
2494
            {
2495
                throw xlnt::invalid_file("border counts don't match (expected " + std::to_string(count.get()) + ", got " + std::to_string(borders.size()) + ")");
2496
            }
2497
#endif
2498
        }
87✔
2499
        else if (current_style_element == qn("spreadsheetml", "fills"))
2,628✔
2500
        {
2501
            auto &fills = stylesheet.fills;
88✔
2502
            optional<std::size_t> count;
88✔
2503
            if (parser().attribute_present("count"))
264!
2504
            {
2505
                count = parser().attribute<std::size_t>("count");
176✔
2506
                fills.reserve(xlnt::detail::clip_reserve_elements(count.get()));
88✔
2507
            }
2508

2509
            while (in_element(qn("spreadsheetml", "fills")))
2,412✔
2510
            {
2511
                fills.push_back(xlnt::fill());
493✔
2512
                auto &new_fill = fills.back();
493✔
2513

2514
                expect_start_element(qn("spreadsheetml", "fill"), xml::content::complex);
2,465✔
2515

2516
                if (in_element(qn("spreadsheetml", "fill")))
1,972✔
2517
                {
2518

2519
                    auto fill_element = expect_start_element(xml::content::complex);
492✔
2520

2521
                    if (fill_element == qn("spreadsheetml", "patternFill"))
1,968✔
2522
                    {
2523
                        xlnt::pattern_fill pattern;
491✔
2524

2525
                        if (parser().attribute_present("patternType"))
1,473✔
2526
                        {
2527
                            pattern.type(parser().attribute<xlnt::pattern_fill_type>("patternType"));
974✔
2528

2529
                            while (in_element(qn("spreadsheetml", "patternFill")))
4,651✔
2530
                            {
2531
                                auto pattern_type_element = expect_start_element(xml::content::complex);
554✔
2532

2533
                                if (pattern_type_element == qn("spreadsheetml", "fgColor"))
2,216✔
2534
                                {
2535
                                    pattern.foreground(read_color());
317✔
2536
                                }
2537
                                else if (pattern_type_element == qn("spreadsheetml", "bgColor"))
948!
2538
                                {
2539
                                    pattern.background(read_color());
237✔
2540
                                }
2541
                                else
2542
                                {
2543
                                    unexpected_element(pattern_type_element);
×
2544
                                }
2545

2546
                                expect_end_element(pattern_type_element);
554✔
2547
                            }
554✔
2548
                        }
2549

2550
                        new_fill = pattern;
491✔
2551
                    }
491✔
2552
                    else if (fill_element == qn("spreadsheetml", "gradientFill"))
4!
2553
                    {
2554
                        xlnt::gradient_fill gradient;
1✔
2555

2556
                        if (parser().attribute_present("type"))
3!
2557
                        {
2558
                            gradient.type(parser().attribute<xlnt::gradient_fill_type>("type"));
×
2559
                        }
2560
                        else
2561
                        {
2562
                            gradient.type(xlnt::gradient_fill_type::linear);
1✔
2563
                        }
2564

2565
                        while (in_element(qn("spreadsheetml", "gradientFill")))
5!
2566
                        {
2567
                            expect_start_element(qn("spreadsheetml", "stop"), xml::content::complex);
×
2568
                            auto position = xlnt::detail::deserialise(parser().attribute("position"));
×
2569
                            expect_start_element(qn("spreadsheetml", "color"), xml::content::complex);
×
2570
                            auto color = read_color();
×
2571
                            expect_end_element(qn("spreadsheetml", "color"));
×
2572
                            expect_end_element(qn("spreadsheetml", "stop"));
×
2573

2574
                            gradient.add_stop(position, color);
×
2575
                        }
×
2576

2577
                        new_fill = gradient;
1✔
2578
                    }
1✔
2579
                    else
2580
                    {
2581
                        unexpected_element(fill_element);
×
2582
                    }
2583

2584
                    expect_end_element(fill_element);
492✔
2585
                }
492✔
2586

2587
                expect_end_element(qn("spreadsheetml", "fill"));
1,972✔
2588
            }
2589

2590
#ifdef THROW_ON_INVALID_XML
2591
            if (count.is_set() && count != fills.size())
2592
            {
2593
                throw xlnt::invalid_file("fill counts don't match (expected " + std::to_string(count.get()) + ", got " + std::to_string(fills.size()) + ")");
2594
            }
2595
#endif
2596
        }
88✔
2597
        else if (current_style_element == qn("spreadsheetml", "fonts"))
2,276✔
2598
        {
2599
            auto &fonts = stylesheet.fonts;
87✔
2600
            optional<std::size_t> count;
87✔
2601
            if (parser().attribute_present("count"))
261!
2602
            {
2603
                count = parser().attribute<std::size_t>("count");
174✔
2604
                fonts.reserve(xlnt::detail::clip_reserve_elements(count.get()));
87✔
2605
            }
2606

2607
            if (parser().attribute_present(qn("x14ac", "knownFonts")))
435✔
2608
            {
2609
                target_.enable_known_fonts();
47✔
2610
            }
2611

2612
            while (in_element(qn("spreadsheetml", "fonts")))
2,583✔
2613
            {
2614
                fonts.push_back(xlnt::font());
537✔
2615
                auto &new_font = stylesheet.fonts.back();
537✔
2616

2617
                expect_start_element(qn("spreadsheetml", "font"), xml::content::complex);
2,148✔
2618

2619
                while (in_element(qn("spreadsheetml", "font")))
13,825✔
2620
                {
2621
                    auto font_property_element = expect_start_element(xml::content::simple);
2,785✔
2622

2623
                    if (font_property_element == qn("spreadsheetml", "sz"))
11,140✔
2624
                    {
2625
                        new_font.size(xlnt::detail::deserialise(parser().attribute("val")));
1,608✔
2626
                    }
2627
                    else if (font_property_element == qn("spreadsheetml", "name"))
8,996✔
2628
                    {
2629
                        new_font.name(parser().attribute("val"));
1,611✔
2630
                    }
2631
                    else if (font_property_element == qn("spreadsheetml", "color"))
6,848✔
2632
                    {
2633
                        new_font.color(read_color());
478✔
2634
                    }
2635
                    else if (font_property_element == qn("spreadsheetml", "family"))
4,936✔
2636
                    {
2637
                        new_font.family(parser().attribute<std::size_t>("val"));
1,314✔
2638
                    }
2639
                    else if (font_property_element == qn("spreadsheetml", "scheme"))
3,184✔
2640
                    {
2641
                        new_font.scheme(parser().attribute("val"));
1,254✔
2642
                    }
2643
                    else if (font_property_element == qn("spreadsheetml", "b"))
1,512✔
2644
                    {
2645
                        if (parser().attribute_present("val"))
366✔
2646
                        {
2647
                            new_font.bold(is_true(parser().attribute("val")));
27✔
2648
                        }
2649
                        else
2650
                        {
2651
                            new_font.bold(true);
113✔
2652
                        }
2653
                    }
2654
                    else if (font_property_element == qn("spreadsheetml", "vertAlign"))
1,024✔
2655
                    {
2656
                        auto vert_align = parser().attribute("val");
54✔
2657

2658
                        if (vert_align == "superscript")
27✔
2659
                        {
2660
                            new_font.superscript(true);
9✔
2661
                        }
2662
                        else if (vert_align == "subscript")
18!
2663
                        {
2664
                            new_font.subscript(true);
18✔
2665
                        }
2666
                    }
27✔
2667
                    else if (font_property_element == qn("spreadsheetml", "strike"))
916✔
2668
                    {
2669
                        if (parser().attribute_present("val"))
60!
2670
                        {
2671
                            new_font.strikethrough(is_true(parser().attribute("val")));
×
2672
                        }
2673
                        else
2674
                        {
2675
                            new_font.strikethrough(true);
20✔
2676
                        }
2677
                    }
2678
                    else if (font_property_element == qn("spreadsheetml", "outline"))
836!
2679
                    {
2680
                        if (parser().attribute_present("val"))
×
2681
                        {
2682
                            new_font.outline(is_true(parser().attribute("val")));
×
2683
                        }
2684
                        else
2685
                        {
2686
                            new_font.outline(true);
×
2687
                        }
2688
                    }
2689
                    else if (font_property_element == qn("spreadsheetml", "shadow"))
836!
2690
                    {
2691
                        if (parser().attribute_present("val"))
×
2692
                        {
2693
                            new_font.shadow(is_true(parser().attribute("val")));
×
2694
                        }
2695
                        else
2696
                        {
2697
                            new_font.shadow(true);
×
2698
                        }
2699
                    }
2700
                    else if (font_property_element == qn("spreadsheetml", "i"))
836✔
2701
                    {
2702
                        if (parser().attribute_present("val"))
138!
2703
                        {
2704
                            new_font.italic(is_true(parser().attribute("val")));
×
2705
                        }
2706
                        else
2707
                        {
2708
                            new_font.italic(true);
46✔
2709
                        }
2710
                    }
2711
                    else if (font_property_element == qn("spreadsheetml", "u"))
652✔
2712
                    {
2713
                        if (parser().attribute_present("val"))
204✔
2714
                        {
2715
                            new_font.underline(parser().attribute<xlnt::font::underline_style>("val"));
132✔
2716
                        }
2717
                        else
2718
                        {
2719
                            new_font.underline(xlnt::font::underline_style::single);
24✔
2720
                        }
2721
                    }
2722
                    else if (font_property_element == qn("spreadsheetml", "charset"))
380!
2723
                    {
2724
                        if (parser().attribute_present("val"))
285!
2725
                        {
2726
                            parser().attribute("val");
285✔
2727
                        }
2728
                    }
2729
                    else
2730
                    {
2731
                        unexpected_element(font_property_element);
×
2732
                    }
2733

2734
                    expect_end_element(font_property_element);
2,785✔
2735
                }
2,785✔
2736

2737
                expect_end_element(qn("spreadsheetml", "font"));
2,148✔
2738
            }
2739

2740
#ifdef THROW_ON_INVALID_XML
2741
            if (count.is_set() && count != stylesheet.fonts.size())
2742
            {
2743
                throw xlnt::invalid_file("stylesheet font counts don't match (expected " + std::to_string(count.get()) + ", got " + std::to_string(stylesheet.fonts.size()) + ")");
2744
            }
2745
#endif
2746
        }
87✔
2747
        else if (current_style_element == qn("spreadsheetml", "numFmts"))
1,928✔
2748
        {
2749
            auto &number_formats = stylesheet.number_formats;
19✔
2750
            optional<std::size_t> count;
19✔
2751
            if (parser().attribute_present("count"))
57!
2752
            {
2753
                count = parser().attribute<std::size_t>("count");
38✔
2754
                number_formats.reserve(xlnt::detail::clip_reserve_elements(count.get()));
19✔
2755
            }
2756

2757
            while (in_element(qn("spreadsheetml", "numFmts")))
219✔
2758
            {
2759
                expect_start_element(qn("spreadsheetml", "numFmt"), xml::content::simple);
124✔
2760

2761
                auto format_string = parser().attribute("formatCode");
62✔
2762

2763
                if (format_string == "GENERAL")
31!
2764
                {
2765
                    format_string = "General";
×
2766
                }
2767

2768
                xlnt::number_format nf;
31✔
2769

2770
                nf.format_string(format_string);
31✔
2771
                nf.id(parser().attribute<std::size_t>("numFmtId"));
93✔
2772

2773
                expect_end_element(qn("spreadsheetml", "numFmt"));
93✔
2774

2775
                number_formats.push_back(nf);
31✔
2776
            }
31✔
2777

2778
#ifdef THROW_ON_INVALID_XML
2779
            if (count.is_set() && count != number_formats.size())
2780
            {
2781
                throw xlnt::invalid_file("number format counts don't match (expected " + std::to_string(count.get()) + ", got " + std::to_string(number_formats.size()) + ")");
2782
            }
2783
#endif
2784
        }
19✔
2785
        else if (current_style_element == qn("spreadsheetml", "cellStyles"))
1,852✔
2786
        {
2787
            optional<std::size_t> count;
86✔
2788
            if (parser().attribute_present("count"))
258!
2789
            {
2790
                count = parser().attribute<std::size_t>("count");
172✔
2791
                styles.reserve(xlnt::detail::clip_reserve_elements(count.get()));
86✔
2792
            }
2793

2794
            while (in_element(qn("spreadsheetml", "cellStyles")))
3,142✔
2795
            {
2796
                auto &data = *styles.emplace(styles.end());
678✔
2797

2798
                expect_start_element(qn("spreadsheetml", "cellStyle"), xml::content::simple);
2,712✔
2799

2800
                data.first.name = parser().attribute("name");
1,356✔
2801
                data.second = parser().attribute<std::size_t>("xfId");
1,356✔
2802

2803
                if (parser().attribute_present("builtinId"))
2,034✔
2804
                {
2805
                    data.first.builtin_id = parser().attribute<std::size_t>("builtinId");
1,692✔
2806
                }
2807

2808
                if (parser().attribute_present("hidden"))
2,034✔
2809
                {
2810
                    data.first.hidden_style = is_true(parser().attribute("hidden"));
12✔
2811
                }
2812

2813
                if (parser().attribute_present("customBuiltin"))
2,034✔
2814
                {
2815
                    data.first.custom_builtin = is_true(parser().attribute("customBuiltin"));
1,263✔
2816
                }
2817

2818
                expect_end_element(qn("spreadsheetml", "cellStyle"));
2,712✔
2819
            }
2820

2821
#ifdef THROW_ON_INVALID_XML
2822
            if (count.is_set() && count != styles.size())
2823
            {
2824
                throw xlnt::invalid_file("style counts don't match (expected " + std::to_string(count.get()) + ", got " + std::to_string(styles.size()) + ")");
2825
            }
2826
#endif
2827
        }
86✔
2828
        else if (current_style_element == qn("spreadsheetml", "cellStyleXfs")
2,262!
2829
            || current_style_element == qn("spreadsheetml", "cellXfs"))
2,668!
2830
        {
2831
            auto in_style_records = current_style_element.name() == "cellStyleXfs";
174✔
2832
            optional<std::size_t> count;
174✔
2833
            if (parser().attribute_present("count"))
522!
2834
            {
2835
                count = parser().attribute<std::size_t>("count");
348✔
2836
                if (in_style_records)
174✔
2837
                {
2838
                    style_records.reserve(xlnt::detail::clip_reserve_elements(count.get()));
87✔
2839
                }
2840
                else
2841
                {
2842
                    format_records.reserve(xlnt::detail::clip_reserve_elements(count.get()));
87✔
2843
                }
2844
            }
2845

2846
            while (in_element(current_style_element))
1,448✔
2847
            {
2848
                expect_start_element(qn("spreadsheetml", "xf"), xml::content::complex);
5,096✔
2849

2850
                auto &record = *(!in_style_records
1,274✔
2851
                        ? format_records.emplace(format_records.end())
1,274✔
2852
                        : style_records.emplace(style_records.end()));
1,274✔
2853

2854
                if (parser().attribute_present("applyBorder"))
3,822✔
2855
                {
2856
                    record.first.border_applied = is_true(parser().attribute("applyBorder"));
2,034✔
2857
                }
2858
                record.first.border_id = parser().attribute_present("borderId")
2,548✔
2859
                    ? parser().attribute<std::size_t>("borderId")
5,064!
2860
                    : optional<std::size_t>();
1,274✔
2861

2862
                if (parser().attribute_present("applyFill"))
3,822✔
2863
                {
2864
                    record.first.fill_applied = is_true(parser().attribute("applyFill"));
486✔
2865
                }
2866
                record.first.fill_id = parser().attribute_present("fillId")
2,548✔
2867
                    ? parser().attribute<std::size_t>("fillId")
5,050!
2868
                    : optional<std::size_t>();
1,274✔
2869

2870
                if (parser().attribute_present("applyFont"))
3,822✔
2871
                {
2872
                    record.first.font_applied = is_true(parser().attribute("applyFont"));
1,509✔
2873
                }
2874
                record.first.font_id = parser().attribute_present("fontId")
2,548✔
2875
                    ? parser().attribute<std::size_t>("fontId")
5,094!
2876
                    : optional<std::size_t>();
1,274✔
2877

2878
                if (parser().attribute_present("applyNumberFormat"))
3,822✔
2879
                {
2880
                    record.first.number_format_applied = is_true(parser().attribute("applyNumberFormat"));
1,665✔
2881
                }
2882
                record.first.number_format_id = parser().attribute_present("numFmtId")
2,548✔
2883
                    ? parser().attribute<std::size_t>("numFmtId")
5,080!
2884
                    : optional<std::size_t>();
1,274✔
2885

2886
                auto apply_alignment_present = parser().attribute_present("applyAlignment");
2,548✔
2887
                if (apply_alignment_present)
1,274✔
2888
                {
2889
                    record.first.alignment_applied = is_true(parser().attribute("applyAlignment"));
2,436✔
2890
                }
2891

2892
                auto apply_protection_present = parser().attribute_present("applyProtection");
2,548✔
2893
                if (apply_protection_present)
1,274✔
2894
                {
2895
                    record.first.protection_applied = is_true(parser().attribute("applyProtection"));
1,929✔
2896
                }
2897

2898
                record.first.pivot_button_ = parser().attribute_present("pivotButton")
2,548!
2899
                    && is_true(parser().attribute("pivotButton"));
2,556!
2900
                record.first.quote_prefix_ = parser().attribute_present("quotePrefix")
2,548!
2901
                    && is_true(parser().attribute("quotePrefix"));
2,556!
2902

2903
                if (parser().attribute_present("xfId"))
3,822✔
2904
                {
2905
                    record.second = parser().attribute<std::size_t>("xfId");
1,254✔
2906
                }
2907

2908
                while (in_element(qn("spreadsheetml", "xf")))
8,598✔
2909
                {
2910
                    auto xf_child_element = expect_start_element(xml::content::simple);
557✔
2911

2912
                    if (xf_child_element == qn("spreadsheetml", "alignment"))
2,228✔
2913
                    {
2914
                        record.first.alignment_id = stylesheet.alignments.size();
530✔
2915
                        auto &alignment = *stylesheet.alignments.emplace(stylesheet.alignments.end());
530✔
2916

2917
                        if (parser().attribute_present("wrapText"))
1,590✔
2918
                        {
2919
                            alignment.wrap(is_true(parser().attribute("wrapText")));
285✔
2920
                        }
2921

2922
                        if (parser().attribute_present("shrinkToFit"))
1,590✔
2923
                        {
2924
                            alignment.shrink(is_true(parser().attribute("shrinkToFit")));
123✔
2925
                        }
2926

2927
                        if (parser().attribute_present("indent"))
1,590✔
2928
                        {
2929
                            alignment.indent(parser().attribute<int>("indent"));
120✔
2930
                        }
2931

2932
                        if (parser().attribute_present("textRotation"))
1,590✔
2933
                        {
2934
                            alignment.rotation(parser().attribute<int>("textRotation"));
93✔
2935
                        }
2936

2937
                        if (parser().attribute_present("vertical"))
1,590✔
2938
                        {
2939
                            alignment.vertical(parser().attribute<xlnt::vertical_alignment>("vertical"));
1,365✔
2940
                        }
2941

2942
                        if (parser().attribute_present("horizontal"))
1,590✔
2943
                        {
2944
                            alignment.horizontal(parser().attribute<xlnt::horizontal_alignment>("horizontal"));
342✔
2945
                        }
2946

2947
                        if (parser().attribute_present("readingOrder"))
1,590✔
2948
                        {
2949
                            parser().attribute<int>("readingOrder");
3✔
2950
                        }
2951
                    }
2952
                    else if (xf_child_element == qn("spreadsheetml", "protection"))
108!
2953
                    {
2954
                        record.first.protection_id = stylesheet.protections.size();
27✔
2955
                        auto &protection = *stylesheet.protections.emplace(stylesheet.protections.end());
27✔
2956

2957
                        protection.locked(parser().attribute_present("locked")
81!
2958
                            && is_true(parser().attribute("locked")));
135!
2959
                        protection.hidden(parser().attribute_present("hidden")
108!
2960
                            && is_true(parser().attribute("hidden")));
125!
2961
                    }
2962
                    else
2963
                    {
2964
                        unexpected_element(xf_child_element);
×
2965
                    }
2966

2967
                    expect_end_element(xf_child_element);
557✔
2968
                }
557✔
2969

2970
                expect_end_element(qn("spreadsheetml", "xf"));
5,096✔
2971
            }
2972

2973
#ifdef THROW_ON_INVALID_XML
2974
            if (count.is_set() && ((in_style_records && count != style_records.size())
2975
                || (!in_style_records && count != format_records.size())))
2976
            {
2977
                throw xlnt::invalid_file("format record counts don't match (expected " + std::to_string(count.get()) + ", got " + std::to_string(format_records.size()) + ")");
2978
            }
2979
#endif
2980
        }
174✔
2981
        else if (current_style_element == qn("spreadsheetml", "dxfs"))
812✔
2982
        {
2983
            std::size_t processed = 0;
70✔
2984

2985
            while (in_element(current_style_element))
70!
2986
            {
2987
                auto current_element = expect_start_element(xml::content::mixed);
×
2988
                skip_remaining_content(current_element);
×
2989
                expect_end_element(current_element);
×
2990
                ++processed;
×
2991
            }
×
2992

2993
#ifdef THROW_ON_INVALID_XML
2994
            if (parser().attribute_present("count"))
2995
            {
2996
                std::size_t count = parser().attribute<std::size_t>("count");
2997
                if (count != processed)
2998
                {
2999
                    throw xlnt::invalid_file("differential format record counts don't match (expected " + std::to_string(count) + ", got " + std::to_string(processed) + ")");
3000
                }
3001
            }
3002
#endif
3003
        }
3004
        else if (current_style_element == qn("spreadsheetml", "tableStyles"))
532✔
3005
        {
3006
            skip_attribute("defaultTableStyle");
148✔
3007
            skip_attribute("defaultPivotStyle");
74✔
3008

3009
            std::size_t processed = 0;
74✔
3010

3011
            while (in_element(qn("spreadsheetml", "tableStyles")))
370!
3012
            {
3013
                auto current_element = expect_start_element(xml::content::complex);
×
3014
                skip_remaining_content(current_element);
×
3015
                expect_end_element(current_element);
×
3016
                ++processed;
×
3017
            }
×
3018

3019
#ifdef THROW_ON_INVALID_XML
3020
            if (parser().attribute_present("count"))
3021
            {
3022
                std::size_t count = parser().attribute<std::size_t>("count");
3023
                if (count != processed)
3024
                {
3025
                    throw xlnt::invalid_file("table style counts don't match (expected " + std::to_string(count) + ", got " + std::to_string(processed) + ")");
3026
                }
3027
            }
3028
#endif
3029
        }
3030
        else if (current_style_element == qn("spreadsheetml", "extLst"))
236✔
3031
        {
3032
            while (in_element(qn("spreadsheetml", "extLst")))
568✔
3033
            {
3034
                expect_start_element(qn("spreadsheetml", "ext"), xml::content::complex);
288✔
3035

3036
                const auto uri = parser().attribute("uri");
144✔
3037

3038
                if (uri == "{EB79DEF2-80B8-43e5-95BD-54CBDDF9020C}") // slicerStyles
72✔
3039
                {
3040
                    expect_start_element(qn("x14", "slicerStyles"), xml::content::simple);
224✔
3041
                    stylesheet.default_slicer_style = parser().attribute("defaultSlicerStyle");
168✔
3042
                    expect_end_element(qn("x14", "slicerStyles"));
224✔
3043
                }
3044
                else
3045
                {
3046
                    skip_remaining_content(qn("spreadsheetml", "ext"));
64✔
3047
                }
3048

3049
                expect_end_element(qn("spreadsheetml", "ext"));
216✔
3050
            }
72✔
3051
        }
3052
        else if (current_style_element == qn("spreadsheetml", "colors")) // CT_Colors 0-1
12!
3053
        {
3054
            while (in_element(qn("spreadsheetml", "colors")))
27✔
3055
            {
3056
                auto colors_child_element = expect_start_element(xml::content::complex);
3✔
3057

3058
                if (colors_child_element == qn("spreadsheetml", "indexedColors")) // CT_IndexedColors 0-1
12!
3059
                {
3060
                    while (in_element(colors_child_element))
93✔
3061
                    {
3062
                        expect_start_element(qn("spreadsheetml", "rgbColor"), xml::content::simple);
360✔
3063
                        stylesheet.colors.push_back(read_color());
90✔
3064
                        expect_end_element(qn("spreadsheetml", "rgbColor"));
360✔
3065
                    }
3066
                }
3067
                else if (colors_child_element == qn("spreadsheetml", "mruColors")) // CT_MRUColors
×
3068
                {
3069
                    skip_remaining_content(colors_child_element);
×
3070
                }
3071
                else
3072
                {
3073
                    unexpected_element(colors_child_element);
×
3074
                }
3075

3076
                expect_end_element(colors_child_element);
3✔
3077
            }
3✔
3078
        }
3079
        else
3080
        {
3081
            unexpected_element(current_style_element);
×
3082
        }
3083

3084
        expect_end_element(current_style_element);
744✔
3085
    }
744✔
3086

3087
    expect_end_element(qn("spreadsheetml", "styleSheet"));
264✔
3088

3089
    std::size_t xf_id = 0;
88✔
3090

3091
    for (const auto &record : style_records)
921✔
3092
    {
3093
        auto style_iter = std::find_if(styles.begin(), styles.end(),
833✔
3094
            [&xf_id](const std::pair<style_impl, std::size_t> &s) { return s.second == xf_id; });
21,239✔
3095
        ++xf_id;
833✔
3096

3097
        if (style_iter == styles.end()) continue;
833✔
3098

3099
        auto new_style = stylesheet.create_style(style_iter->first.name);
678✔
3100

3101
        new_style.d_->pivot_button_ = style_iter->first.pivot_button_;
678✔
3102
        new_style.d_->quote_prefix_ = style_iter->first.quote_prefix_;
678✔
3103
        new_style.d_->formatting_record_id = style_iter->first.formatting_record_id;
678✔
3104
        new_style.d_->hidden_style = style_iter->first.hidden_style;
678✔
3105
        new_style.d_->custom_builtin = style_iter->first.custom_builtin;
678✔
3106
        new_style.d_->hidden_style = style_iter->first.hidden_style;
678✔
3107
        new_style.d_->builtin_id = style_iter->first.builtin_id;
678✔
3108
        new_style.d_->outline_style = style_iter->first.outline_style;
678✔
3109

3110
        new_style.d_->alignment_applied = record.first.alignment_applied;
678✔
3111
        new_style.d_->alignment_id = record.first.alignment_id;
678✔
3112
        new_style.d_->border_applied = record.first.border_applied;
678✔
3113
        new_style.d_->border_id = record.first.border_id;
678✔
3114
        new_style.d_->fill_applied = record.first.fill_applied;
678✔
3115
        new_style.d_->fill_id = record.first.fill_id;
678✔
3116
        new_style.d_->font_applied = record.first.font_applied;
678✔
3117
        new_style.d_->font_id = record.first.font_id;
678✔
3118
        new_style.d_->number_format_applied = record.first.number_format_applied;
678✔
3119
        new_style.d_->number_format_id = record.first.number_format_id;
678✔
3120
    }
3121

3122
    std::size_t record_index = 0;
88✔
3123

3124
    for (const auto &record : format_records)
529✔
3125
    {
3126
        stylesheet.format_impls.emplace_back();
441✔
3127
        auto &new_format = *stylesheet.format_impls.back();
441✔
3128

3129
        new_format.id = record_index++;
441✔
3130
        new_format.parent = &stylesheet;
441✔
3131

3132
        new_format.alignment_id = record.first.alignment_id;
441✔
3133
        new_format.alignment_applied = record.first.alignment_applied;
441✔
3134
        new_format.border_id = record.first.border_id;
441✔
3135
        new_format.border_applied = record.first.border_applied;
441✔
3136
        new_format.fill_id = record.first.fill_id;
441✔
3137
        new_format.fill_applied = record.first.fill_applied;
441✔
3138
        new_format.font_id = record.first.font_id;
441✔
3139
        new_format.font_applied = record.first.font_applied;
441✔
3140
        new_format.number_format_id = record.first.number_format_id;
441✔
3141
        new_format.number_format_applied = record.first.number_format_applied;
441✔
3142
        new_format.protection_id = record.first.protection_id;
441✔
3143
        new_format.protection_applied = record.first.protection_applied;
441✔
3144
        new_format.pivot_button_ = record.first.pivot_button_;
441✔
3145
        new_format.quote_prefix_ = record.first.quote_prefix_;
441✔
3146

3147
        set_style_by_xfid(styles, record.second, new_format.style);
441✔
3148
    }
3149
}
264!
3150

3151
void xlsx_consumer::read_theme()
75✔
3152
{
3153
    auto workbook_rel = manifest().relationship(path("/"),
225✔
3154
        relationship_type::office_document);
75✔
3155
    auto theme_rel = manifest().relationship(workbook_rel.target().path(),
75✔
3156
        relationship_type::theme);
75✔
3157
    auto theme_path = manifest().canonicalize({workbook_rel, theme_rel});
375!
3158

3159
    target_.theme(theme());
75✔
3160

3161
    if (manifest().has_relationship(theme_path, relationship_type::image))
75✔
3162
    {
3163
        read_part({workbook_rel, theme_rel,
10!
3164
            manifest().relationship(theme_path,
4✔
3165
                relationship_type::image)});
3166
    }
3167
}
152!
3168

3169
void xlsx_consumer::read_volatile_dependencies()
×
3170
{
3171
}
×
3172

3173
// Sheet Relationship Target Parts
3174

3175
void xlsx_consumer::read_vml_drawings(worksheet /*ws*/)
14✔
3176
{
3177
}
14✔
3178

3179
void xlsx_consumer::read_comments(worksheet ws)
14✔
3180
{
3181
    std::vector<std::string> authors;
14✔
3182

3183
    expect_start_element(qn("spreadsheetml", "comments"), xml::content::complex);
70✔
3184
    // name space can be ignored
3185
    skip_attribute(qn("mc", "Ignorable"));
42✔
3186
    expect_start_element(qn("spreadsheetml", "authors"), xml::content::complex);
56✔
3187

3188
    while (in_element(qn("spreadsheetml", "authors")))
126✔
3189
    {
3190
        expect_start_element(qn("spreadsheetml", "author"), xml::content::simple);
56✔
3191
        authors.push_back(read_text());
14✔
3192
        expect_end_element(qn("spreadsheetml", "author"));
56✔
3193
    }
3194

3195
    expect_end_element(qn("spreadsheetml", "authors"));
42✔
3196
    expect_start_element(qn("spreadsheetml", "commentList"), xml::content::complex);
56✔
3197

3198
    while (in_element(xml::qname(qn("spreadsheetml", "commentList"))))
182✔
3199
    {
3200
        expect_start_element(qn("spreadsheetml", "comment"), xml::content::complex);
140✔
3201

3202
        skip_attribute("shapeId");
28✔
3203
        auto cell_ref = parser().attribute("ref");
56✔
3204
        auto author_id = parser().attribute<std::size_t>("authorId");
56✔
3205

3206
        expect_start_element(qn("spreadsheetml", "text"), xml::content::complex);
112✔
3207

3208
        ws.cell(cell_ref).comment(comment(read_rich_text(qn("spreadsheetml", "text")), authors.at(author_id)));
140✔
3209

3210
        expect_end_element(qn("spreadsheetml", "text"));
112✔
3211

3212
        if (in_element(xml::qname(qn("spreadsheetml", "comment"))))
112!
3213
        {
3214
            expect_start_element(qn("mc", "AlternateContent"), xml::content::complex);
×
3215
            skip_remaining_content(qn("mc", "AlternateContent"));
×
3216
            expect_end_element(qn("mc", "AlternateContent"));
×
3217
        }
3218

3219
        expect_end_element(qn("spreadsheetml", "comment"));
84✔
3220
    }
28✔
3221

3222
    expect_end_element(qn("spreadsheetml", "commentList"));
56✔
3223
    expect_end_element(qn("spreadsheetml", "comments"));
42✔
3224
}
14✔
3225

3226
void xlsx_consumer::read_drawings(worksheet ws, const path &part)
3✔
3227
{
3228
    auto images = manifest().relationships(part, relationship_type::image);
3✔
3229

3230
    auto sd = drawing::spreadsheet_drawing(parser());
3✔
3231

3232
    for (const auto &image_rel_id : sd.get_embed_ids())
5✔
3233
    {
3234
        auto image_rel = std::find_if(images.begin(), images.end(),
2✔
3235
            [&](const relationship &r) { return r.id() == image_rel_id; });
2✔
3236

3237
        if (image_rel != images.end())
2!
3238
        {
3239
            const auto url = image_rel->target().path().resolve(part.parent());
2✔
3240

3241
            read_image(url);
2✔
3242
        }
2✔
3243
    }
3✔
3244

3245
    ws.d_->drawing_ = sd;
3✔
3246
}
3✔
3247

3248
// Unknown Parts
3249

3250
void xlsx_consumer::read_unknown_parts()
×
3251
{
3252
}
×
3253

3254
void xlsx_consumer::read_unknown_relationships()
×
3255
{
3256
}
×
3257

3258
void xlsx_consumer::read_image(const xlnt::path &image_path)
49✔
3259
{
3260
    auto image_streambuf = archive_->open(image_path);
49✔
3261
    vector_ostreambuf buffer(target_.d_->images_[image_path.string()]);
49✔
3262
    std::ostream out_stream(&buffer);
49✔
3263
    out_stream << image_streambuf.get();
49✔
3264
}
49✔
3265

3266
void xlsx_consumer::read_binary(const xlnt::path &binary_path)
7✔
3267
{
3268
    auto binary_streambuf = archive_->open(binary_path);
7✔
3269
    vector_ostreambuf buffer(target_.d_->binaries_[binary_path.string()]);
7✔
3270
    std::ostream out_stream(&buffer);
7✔
3271
    out_stream << binary_streambuf.get();
7✔
3272
}
7✔
3273

3274
std::string xlsx_consumer::read_text()
5,347✔
3275
{
3276
    auto text = std::string();
5,347✔
3277

3278
    while (parser().peek() == xml::parser::event_type::characters)
7,947✔
3279
    {
3280
        parser().next_expect(xml::parser::event_type::characters);
2,600✔
3281
        text.append(parser().value());
2,600✔
3282
    }
3283

3284
    return text;
5,347✔
3285
}
×
3286

3287
variant xlsx_consumer::read_variant()
998✔
3288
{
3289
    auto value = variant(read_text());
998✔
3290

3291
    if (in_element(stack_.back()))
998✔
3292
    {
3293
        auto element = expect_start_element(xml::content::mixed);
392✔
3294
        auto text = read_text();
392✔
3295

3296
        if (element == qn("vt", "lpwstr") || element == qn("vt", "lpstr"))
3,500!
3297
        {
3298
            value = variant(text);
181✔
3299
        }
3300
        if (element == qn("vt", "i4"))
1,568✔
3301
        {
3302
            int number = -1;
73✔
3303
            if (detail::parse(text, number) != std::errc())
73!
3304
            {
3305
#ifdef THROW_ON_INVALID_XML
3306
                throw xlnt::invalid_parameter("cannot parse variant of type i4 from \"" + text + "\"");
3307
#endif
3308
            }
3309
            else
3310
            {
3311
                value = variant(number);
73✔
3312
            }
3313
        }
3314
        if (element == qn("vt", "bool"))
1,568!
3315
        {
3316
            value = variant(is_true(text));
×
3317
        }
3318
        else if (element == qn("vt", "vector"))
1,568✔
3319
        {
3320
            auto size = parser().attribute<std::size_t>("size");
276✔
3321
            auto base_type = parser().attribute("baseType");
276✔
3322

3323
            std::vector<variant> vector;
138✔
3324

3325
            for (auto i = std::size_t(0); i < size; ++i)
385✔
3326
            {
3327
                if (base_type == "variant")
247✔
3328
                {
3329
                    expect_start_element(qn("vt", "variant"), xml::content::complex);
730✔
3330
                }
3331

3332
                vector.push_back(read_variant());
247✔
3333

3334
                if (base_type == "variant")
247✔
3335
                {
3336
                    expect_end_element(qn("vt", "variant"));
438✔
3337
                    read_text();
146✔
3338
                }
3339
            }
3340

3341
            value = variant(vector);
138✔
3342
        }
138✔
3343

3344
        expect_end_element(element);
392✔
3345
        read_text();
392✔
3346
    }
392✔
3347

3348
    return value;
998✔
3349
}
×
3350

3351
void xlsx_consumer::skip_attributes(const std::vector<std::string> &names)
435✔
3352
{
3353
    for (const auto &name : names)
2,994✔
3354
    {
3355
        if (parser().attribute_present(name))
2,559✔
3356
        {
3357
            parser().attribute(name);
368✔
3358
        }
3359
    }
3360
}
435✔
3361

3362
void xlsx_consumer::skip_attributes(const std::vector<xml::qname> &names)
217✔
3363
{
3364
    for (const auto &name : names)
434✔
3365
    {
3366
        if (parser().attribute_present(name))
217✔
3367
        {
3368
            parser().attribute(name);
122✔
3369
        }
3370
    }
3371
}
217✔
3372

3373
void xlsx_consumer::skip_attributes()
1,567✔
3374
{
3375
    parser().attribute_map();
1,567✔
3376
}
1,567✔
3377

3378
void xlsx_consumer::skip_attribute(const xml::qname &name)
273✔
3379
{
3380
    if (parser().attribute_present(name))
273✔
3381
    {
3382
        parser().attribute(name);
203✔
3383
    }
3384
}
273✔
3385

3386
void xlsx_consumer::skip_attribute(const std::string &name)
2,059✔
3387
{
3388
    if (parser().attribute_present(name))
2,059✔
3389
    {
3390
        parser().attribute(name);
218✔
3391
    }
3392
}
2,059✔
3393

3394
void xlsx_consumer::skip_remaining_content(const xml::qname &name)
516✔
3395
{
3396
    // start by assuming we've already parsed the opening tag
3397

3398
    skip_attributes();
516✔
3399
    read_text();
516✔
3400

3401
    // continue until the closing tag is reached
3402
    while (in_element(name))
567✔
3403
    {
3404
        auto child_element = expect_start_element(xml::content::mixed);
51✔
3405
        skip_remaining_content(child_element);
51✔
3406
        expect_end_element(child_element);
51✔
3407
        read_text(); // trailing character content (usually whitespace)
51✔
3408
    }
51✔
3409
}
516✔
3410

3411
bool xlsx_consumer::in_element(const xml::qname &name)
25,279✔
3412
{
3413
    return parser().peek() != xml::parser::event_type::end_element
25,279✔
3414
        && stack_.back() == name;
25,279✔
3415
}
3416

3417
xml::qname xlsx_consumer::expect_start_element(xml::content content)
11,713✔
3418
{
3419
    parser().next_expect(xml::parser::event_type::start_element);
11,713✔
3420
    parser().content(content);
11,713✔
3421
    stack_.push_back(parser().qname());
11,713✔
3422

3423
    const auto xml_space = qn("xml", "space");
35,139✔
3424
    preserve_space_ = parser().attribute_present(xml_space) ? parser().attribute(xml_space) == "preserve" : false;
11,713✔
3425

3426
    return stack_.back();
23,426✔
3427
}
11,713✔
3428

3429
void xlsx_consumer::expect_start_element(const xml::qname &name, xml::content content)
7,123✔
3430
{
3431
    parser().next_expect(xml::parser::event_type::start_element, name);
7,123✔
3432
    parser().content(content);
7,123✔
3433
    stack_.push_back(name);
7,123✔
3434

3435
    const auto xml_space = qn("xml", "space");
21,369✔
3436
    preserve_space_ = parser().attribute_present(xml_space) ? parser().attribute(xml_space) == "preserve" : false;
7,123✔
3437
}
7,123✔
3438

3439
void xlsx_consumer::expect_end_element(const xml::qname &name)
18,706✔
3440
{
3441
    parser().attribute_map();
18,706✔
3442
    parser().next_expect(xml::parser::event_type::end_element, name);
18,706✔
3443
    stack_.pop_back();
18,706✔
3444
}
18,706✔
3445

3446
void xlsx_consumer::unexpected_element(const xml::qname &name)
12✔
3447
{
3448
#ifdef THROW_ON_INVALID_XML
3449
    throw xlnt::exception("unexpected element \"" + name.string() + "\"");
3450
#else
3451
    skip_remaining_content(name);
12✔
3452
#endif
3453
}
12✔
3454

3455
rich_text xlsx_consumer::read_rich_text(const xml::qname &parent)
780✔
3456
{
3457
    const auto &xmlns = parent.namespace_();
780✔
3458
    rich_text t;
780✔
3459

3460
    while (in_element(parent))
1,617✔
3461
    {
3462
        auto text_element = expect_start_element(xml::content::mixed);
837✔
3463
        const auto xml_space = qn("xml", "space");
2,511✔
3464
        const auto preserve_space = parser().attribute_present(xml_space)
837✔
3465
            ? parser().attribute(xml_space) == "preserve"
837✔
3466
            : false;
837✔
3467
        skip_attributes();
837✔
3468
        auto text = read_text();
837✔
3469

3470
        if (text_element == xml::qname(xmlns, "t"))
1,674✔
3471
        {
3472
            t.plain_text(text, preserve_space);
750✔
3473
        }
3474
        else if (text_element == xml::qname(xmlns, "r"))
174✔
3475
        {
3476
            rich_text_run run;
51✔
3477
            run.preserve_space = preserve_space;
51✔
3478

3479
            while (in_element(xml::qname(xmlns, "r")))
357✔
3480
            {
3481
                auto run_element = expect_start_element(xml::content::mixed);
102✔
3482
                auto run_text = read_text();
102✔
3483

3484
                if (run_element == xml::qname(xmlns, "rPr"))
204✔
3485
                {
3486
                    run.second = xlnt::font();
51✔
3487

3488
                    while (in_element(xml::qname(xmlns, "rPr")))
601✔
3489
                    {
3490
                        auto current_run_property_element = expect_start_element(xml::content::simple);
224✔
3491

3492
                        if (current_run_property_element == xml::qname(xmlns, "sz"))
448✔
3493
                        {
3494
                            run.second.get().size(xlnt::detail::deserialise(parser().attribute("val")));
153✔
3495
                        }
3496
                        else if (current_run_property_element == xml::qname(xmlns, "rFont"))
346✔
3497
                        {
3498
                            run.second.get().name(parser().attribute("val"));
153✔
3499
                        }
3500
                        else if (current_run_property_element == xml::qname(xmlns, "color"))
244✔
3501
                        {
3502
                            run.second.get().color(read_color());
51✔
3503
                        }
3504
                        else if (current_run_property_element == xml::qname(xmlns, "family"))
142✔
3505
                        {
3506
                            run.second.get().family(parser().attribute<std::size_t>("val"));
42✔
3507
                        }
3508
                        else if (current_run_property_element == xml::qname(xmlns, "charset"))
114!
3509
                        {
3510
                            run.second.get().charset(parser().attribute<std::size_t>("val"));
×
3511
                        }
3512
                        else if (current_run_property_element == xml::qname(xmlns, "scheme"))
114✔
3513
                        {
3514
                            run.second.get().scheme(parser().attribute("val"));
39✔
3515
                        }
3516
                        else if (current_run_property_element == xml::qname(xmlns, "b"))
88✔
3517
                        {
3518
                            run.second.get().bold(parser().attribute_present("val")
108!
3519
                                    ? is_true(parser().attribute("val"))
36!
3520
                                    : true);
3521
                        }
3522
                        else if (current_run_property_element == xml::qname(xmlns, "i"))
16!
3523
                        {
3524
                            run.second.get().italic(parser().attribute_present("val")
×
3525
                                    ? is_true(parser().attribute("val"))
×
3526
                                    : true);
3527
                        }
3528
                        else if (current_run_property_element == xml::qname(xmlns, "u"))
16✔
3529
                        {
3530
                            if (parser().attribute_present("val"))
18!
3531
                            {
3532
                                run.second.get().underline(parser().attribute<font::underline_style>("val"));
×
3533
                            }
3534
                            else
3535
                            {
3536
                                run.second.get().underline(font::underline_style::single);
6✔
3537
                            }
3538
                        }
3539
                        else if (current_run_property_element == xml::qname(xmlns, "strike"))
4!
3540
                        {
3541
                            run.second.get().strikethrough(parser().attribute_present("val")
6!
3542
                                    ? is_true(parser().attribute("val"))
2!
3543
                                    : true);
3544
                        }
3545
                        else
3546
                        {
3547
                            unexpected_element(current_run_property_element);
×
3548
                        }
3549

3550
                        expect_end_element(current_run_property_element);
224✔
3551
                        read_text();
224✔
3552
                    }
224✔
3553
                }
3554
                else if (run_element == xml::qname(xmlns, "t"))
102!
3555
                {
3556
                    run.first = run_text;
51✔
3557
                }
3558
                else
3559
                {
3560
                    unexpected_element(run_element);
×
3561
                }
3562

3563
                read_text();
102✔
3564
                expect_end_element(run_element);
102✔
3565
                read_text();
102✔
3566
            }
102✔
3567

3568
            t.add_run(run);
51✔
3569
        }
51✔
3570
        else if (text_element == xml::qname(xmlns, "rPh"))
72✔
3571
        {
3572
            phonetic_run pr;
2✔
3573
            pr.start = parser().attribute<std::uint32_t>("sb");
4✔
3574
            pr.end = parser().attribute<std::uint32_t>("eb");
4✔
3575

3576
            expect_start_element(xml::qname(xmlns, "t"), xml::content::simple);
4✔
3577
            pr.text = read_text();
2✔
3578

3579
            if (parser().attribute_present(xml_space))
2!
3580
            {
3581
                pr.preserve_space = parser().attribute(xml_space) == "preserve";
2✔
3582
            }
3583

3584
            expect_end_element(xml::qname(xmlns, "t"));
2✔
3585

3586
            t.add_phonetic_run(pr);
2✔
3587
        }
2✔
3588
        else if (text_element == xml::qname(xmlns, "phoneticPr"))
68!
3589
        {
3590
            phonetic_pr ph(parser().attribute<phonetic_pr::font_id_t>("fontId"));
68✔
3591
            if (parser().attribute_present("type"))
102✔
3592
            {
3593
                ph.type(phonetic_pr::type_from_string(parser().attribute("type")));
96✔
3594
            }
3595
            if (parser().attribute_present("alignment"))
102!
3596
            {
3597
                ph.alignment(phonetic_pr::alignment_from_string(parser().attribute("alignment")));
×
3598
            }
3599
            t.phonetic_properties(ph);
34✔
3600
        }
34✔
3601
        else
3602
        {
3603
            unexpected_element(text_element);
×
3604
        }
3605

3606
        read_text();
837✔
3607
        expect_end_element(text_element);
837✔
3608
    }
837✔
3609

3610
    return t;
780✔
3611
}
×
3612

3613
xlnt::color xlsx_consumer::read_color()
1,452✔
3614
{
3615
    xlnt::color result;
1,452✔
3616

3617
    if (parser().attribute_present("auto") && is_true(parser().attribute("auto")))
4,364!
3618
    {
3619
        result.auto_(true);
4✔
3620
        return result;
4✔
3621
    }
3622

3623
    if (parser().attribute_present("rgb"))
4,344✔
3624
    {
3625
        result = xlnt::rgb_color(parser().attribute("rgb"));
1,404✔
3626
    }
3627
    else if (parser().attribute_present("theme"))
2,940✔
3628
    {
3629
        result = xlnt::theme_color(parser().attribute<std::size_t>("theme"));
1,869✔
3630
    }
3631
    else if (parser().attribute_present("indexed"))
1,071!
3632
    {
3633
        result = xlnt::indexed_color(parser().attribute<std::size_t>("indexed"));
1,071✔
3634
    }
3635

3636
    if (parser().attribute_present("tint"))
4,344✔
3637
    {
3638
        result.tint(xlnt::detail::deserialise(parser().attribute("tint")));
597✔
3639
    }
3640

3641
    return result;
1,448✔
3642
}
×
3643

3644
manifest &xlsx_consumer::manifest()
2,122✔
3645
{
3646
    return target_.manifest();
2,122✔
3647
}
3648

3649
} // namespace detail
3650
} // 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