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

xlnt-community / xlnt / 1d4ee6f6-21a4-4379-9731-a97ba8c57175

25 Jan 2026 09:50AM UTC coverage: 83.928% (+1.1%) from 82.793%
1d4ee6f6-21a4-4379-9731-a97ba8c57175

Pull #87

circleci

doomlaur
Improve docuentation on exceptions thrown for invalid worksheet titles
Pull Request #87: Improve documentation when exceptions are thrown (fixes #81)

15263 of 19954 branches covered (76.49%)

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

15 existing lines in 5 files now uncovered.

12491 of 14883 relevant lines covered (83.93%)

12216.5 hits per line

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

76.32
./source/detail/cryptography/compound_document.cpp
1
// Copyright (C) 2016-2022 Thomas Fussell
2
// Copyright (C) 2002-2007 Ariya Hidayat (ariya@kde.org).
3
// Copyright (c) 2024-2025 xlnt-community
4
//
5
// Redistribution and use in source and binary forms, with or without
6
// modification, are permitted provided that the following conditions
7
// are met:
8
//
9
// 1. Redistributions of source code must retain the above copyright
10
// notice, this list of conditions and the following disclaimer.
11
// 2. Redistributions in binary form must reproduce the above copyright
12
// notice, this list of conditions and the following disclaimer in the
13
// documentation and/or other materials provided with the distribution.
14
//
15
// THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
16
// IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
17
// OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
18
// IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
19
// INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
20
// NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
21
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
22
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
23
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
24
// THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
25

26
#include <algorithm>
27
#include <array>
28
#include <cstring>
29
#include <iostream>
30
#include <locale>
31
#include <string>
32
#include <vector>
33

34
#include <xlnt/utils/exceptions.hpp>
35
#include <detail/binary.hpp>
36
#include <detail/cryptography/compound_document.hpp>
37
#include <detail/unicode.hpp>
38

39
// NOTE: compound files are not part of the OOXML specification (ECMA-376).
40
// This implementation is based on the "[MS-CFB]: Compound File Binary File Format" specification.
41
namespace {
42

43
using namespace xlnt::detail;
44

45
int compare_keys(const std::string &left, const std::string &right)
8✔
46
{
47
    auto to_lower = [](std::string s) {
16✔
48
        static const auto locale = std::locale();
16!
49
        std::use_facet<std::ctype<char>>(locale).tolower(&s[0], &s[0] + s.size());
16✔
50

51
        return s;
16✔
52
    };
53

54
    return to_lower(left).compare(to_lower(right));
8✔
55
}
56

57
std::vector<std::string> split_path(const std::string &path)
12✔
58
{
59
    auto split = std::vector<std::string>();
12✔
60
    auto current = path.find('/');
12✔
61
    auto prev = std::size_t(0);
12✔
62

63
    while (current != std::string::npos)
24✔
64
    {
65
        split.push_back(path.substr(prev, current - prev));
12✔
66
        prev = current + 1;
12✔
67
        current = path.find('/', prev);
12✔
68
    }
69

70
    split.push_back(path.substr(prev));
12✔
71

72
    return split;
12✔
73
}
×
74

75
std::string join_path(const std::vector<std::string> &path)
244✔
76
{
77
    auto joined = std::string();
244✔
78

79
    for (auto part : path)
328✔
80
    {
81
        joined.append(part);
84✔
82
        joined.push_back('/');
84✔
83
    }
84✔
84

85
    return joined;
244✔
86
}
×
87

88
const sector_id FreeSector = -1;
89
const sector_id EndOfChain = -2;
90
const sector_id SATSector = -3;
91
//const sector_id MSATSector = -4;
92

93
const directory_id End = -1;
94

95
} // namespace
96

97
namespace xlnt {
98
namespace detail {
99

100
/// <summary>
101
/// Allows a std::vector to be read through a std::istream.
102
/// </summary>
103
class compound_document_istreambuf : public std::streambuf
104
{
105
    using int_type = std::streambuf::int_type;
106

107
public:
108
    compound_document_istreambuf(const compound_document_entry &entry, compound_document &document)
64✔
109
        : entry_(entry),
128✔
110
          document_(document),
64✔
111
          sector_writer_(current_sector_),
64✔
112
          position_(0)
64✔
113
    {
114
    }
64✔
115

116
    compound_document_istreambuf(const compound_document_istreambuf &) = delete;
117
    compound_document_istreambuf &operator=(const compound_document_istreambuf &) = delete;
118

119
    ~compound_document_istreambuf() override;
120

121
private:
122
    std::streamsize xsgetn(char *c, std::streamsize count) override
779✔
123
    {
124
        auto bytes_read = std::streamsize(0);
779✔
125

126
        const auto sector_chain = short_stream() ? document_.ssat_ : document_.sat_;
779✔
127
        const auto chain = document_.follow_chain(entry_.start, sector_chain);
779✔
128
        const auto sector_size = short_stream() ? document_.short_sector_size() : document_.sector_size();
779✔
129
        auto current_sector = chain[position_ / sector_size];
779✔
130
        auto remaining = std::min(std::size_t(entry_.size) - position_, std::size_t(count));
779✔
131

132
        while (remaining)
3,781✔
133
        {
134
            if (current_sector_.empty() || chain[position_ / sector_size] != current_sector)
3,002✔
135
            {
136
                current_sector = chain[position_ / sector_size];
2,272✔
137
                sector_writer_.reset();
2,272✔
138
                if (short_stream())
2,272✔
139
                {
140
                    document_.read_short_sector(current_sector, sector_writer_);
192✔
141
                }
142
                else
143
                {
144
                    document_.read_sector(current_sector, sector_writer_);
2,080✔
145
                }
146
            }
147

148
            const auto available = std::min(entry_.size - position_,
6,004✔
149
                sector_size - position_ % sector_size);
3,002✔
150
            const auto to_read = std::min(available, std::size_t(remaining));
3,002✔
151

152
            auto start = current_sector_.begin() + static_cast<std::ptrdiff_t>(position_ % sector_size);
3,002✔
153
            auto end = start + static_cast<std::ptrdiff_t>(to_read);
3,002✔
154

155
            for (auto i = start; i < end; ++i)
1,074,752✔
156
            {
157
                *(c++) = static_cast<char>(*i);
1,071,750✔
158
            }
159

160
            remaining -= to_read;
3,002✔
161
            position_ += to_read;
3,002✔
162
            bytes_read += to_read;
3,002✔
163
        }
164

165
        if (position_ < entry_.size && chain[position_ / sector_size] != current_sector)
779✔
166
        {
167
            current_sector = chain[position_ / sector_size];
26✔
168
            sector_writer_.reset();
26✔
169
            if (short_stream())
26!
170
            {
171
                document_.read_short_sector(current_sector, sector_writer_);
26✔
172
            }
173
            else
174
            {
175
                document_.read_sector(current_sector, sector_writer_);
×
176
            }
177
        }
178

179
        return bytes_read;
779✔
180
    }
779✔
181

182
    bool short_stream()
3,856✔
183
    {
184
        return entry_.size < document_.header_.threshold;
3,856✔
185
    }
186

187
    int_type underflow() override
×
188
    {
189
        if (position_ >= entry_.size)
×
190
        {
191
            return traits_type::eof();
×
192
        }
193

194
        auto old_position = position_;
×
195
        auto result = '\0';
×
196
        xsgetn(&result, 1);
×
197
        position_ = old_position;
×
198

199
        return result;
×
200
    }
201

202
    int_type uflow() override
×
203
    {
204
        auto result = underflow();
×
205
        ++position_;
×
206

207
        return result;
×
208
    }
209

210
    std::streamsize showmanyc() override
×
211
    {
212
        if (position_ == entry_.size)
×
213
        {
214
            return static_cast<std::streamsize>(-1);
×
215
        }
216

217
        return static_cast<std::streamsize>(entry_.size - position_);
×
218
    }
219

220
    std::streampos seekoff(std::streamoff off, std::ios_base::seekdir way, std::ios_base::openmode) override
52✔
221
    {
222
        if (way == std::ios_base::beg)
52!
223
        {
224
            position_ = 0;
×
225
        }
226
        else if (way == std::ios_base::end)
52!
227
        {
228
            position_ = entry_.size;
×
229
        }
230

231
        if (off < 0)
52!
232
        {
233
            if (static_cast<std::size_t>(-off) > position_)
×
234
            {
235
                position_ = 0;
×
236
                return static_cast<std::ptrdiff_t>(-1);
×
237
            }
238
            else
239
            {
240
                position_ -= static_cast<std::size_t>(-off);
×
241
            }
242
        }
243
        else if (off > 0)
52!
244
        {
245
            if (static_cast<std::size_t>(off) + position_ > entry_.size)
×
246
            {
247
                position_ = entry_.size;
×
248
                return static_cast<std::ptrdiff_t>(-1);
×
249
            }
250
            else
251
            {
252
                position_ += static_cast<std::size_t>(off);
×
253
            }
254
        }
255

256
        return static_cast<std::ptrdiff_t>(position_);
52✔
257
    }
258

259
    std::streampos seekpos(std::streampos sp, std::ios_base::openmode) override
×
260
    {
261
        if (sp < 0)
×
262
        {
263
            position_ = 0;
×
264
        }
265
        else if (static_cast<std::size_t>(sp) > entry_.size)
×
266
        {
267
            position_ = entry_.size;
×
268
        }
269
        else
270
        {
271
            position_ = static_cast<std::size_t>(sp);
×
272
        }
273

274
        return static_cast<std::ptrdiff_t>(position_);
×
275
    }
276

277
private:
278
    const compound_document_entry &entry_;
279
    compound_document &document_;
280
    std::vector<byte> current_sector_;
281
    binary_writer<byte> sector_writer_;
282
    std::size_t position_;
283
};
284

285
compound_document_istreambuf::~compound_document_istreambuf()
128✔
286
{
287
}
128✔
288

289
/// <summary>
290
/// Allows a std::vector to be written through a std::ostream.
291
/// </summary>
292
class compound_document_ostreambuf : public std::streambuf
293
{
294
    using int_type = std::streambuf::int_type;
295

296
public:
297
    compound_document_ostreambuf(compound_document_entry &entry, compound_document &document)
8✔
298
        : entry_(entry),
16✔
299
          document_(document),
8✔
300
          current_sector_(document.header_.threshold),
8✔
301
          sector_reader_(current_sector_),
8✔
302
          position_(0)
16✔
303
    {
304
        setp(reinterpret_cast<char *>(current_sector_.data()),
8✔
305
            reinterpret_cast<char *>(current_sector_.data() + current_sector_.size()));
8✔
306
    }
8✔
307

308
    compound_document_ostreambuf(const compound_document_ostreambuf &) = delete;
309
    compound_document_ostreambuf &operator=(const compound_document_ostreambuf &) = delete;
310

311
    ~compound_document_ostreambuf() override;
312

313
private:
314
    int sync() override
986✔
315
    {
316
        auto written = static_cast<std::size_t>(pptr() - pbase());
986✔
317

318
        if (written == std::size_t(0))
986✔
319
        {
320
            return 0;
4✔
321
        }
322

323
        sector_reader_.reset();
982✔
324

325
        if (short_stream())
982✔
326
        {
327
            if (position_ + written >= document_.header_.threshold)
8✔
328
            {
329
                convert_to_long_stream();
4✔
330
            }
331
            else
332
            {
333
                if (entry_.start < 0)
4!
334
                {
335
                    auto num_sectors = (position_ + written + document_.short_sector_size() - 1) / document_.short_sector_size();
4✔
336
                    chain_ = document_.allocate_short_sectors(num_sectors);
4✔
337
                    entry_.start = chain_.front();
4✔
338
                }
339

340
                for (auto link : chain_)
80✔
341
                {
342
                    document_.write_short_sector(sector_reader_, link);
76✔
343
                    sector_reader_.offset(sector_reader_.offset() + document_.short_sector_size());
76✔
344
                }
345
            }
346
        }
347
        else
348
        {
349
            const auto sector_index = position_ / document_.sector_size();
974✔
350
            document_.write_sector(sector_reader_, chain_[sector_index]);
974✔
351
        }
352

353
        position_ += written;
982✔
354
        entry_.size = std::max(entry_.size, static_cast<std::uint32_t>(position_));
982✔
355
        document_.write_directory();
982✔
356

357
        std::fill(current_sector_.begin(), current_sector_.end(), byte(0));
982✔
358
        setp(reinterpret_cast<char *>(current_sector_.data()),
982✔
359
            reinterpret_cast<char *>(current_sector_.data() + current_sector_.size()));
982✔
360

361
        return 0;
982✔
362
    }
363

364
    bool short_stream()
1,956✔
365
    {
366
        return entry_.size < document_.header_.threshold;
1,956✔
367
    }
368

369
    int_type overflow(int_type c = traits_type::eof()) override
974✔
370
    {
371
        sync();
974✔
372

373
        if (short_stream())
974!
374
        {
375
            auto next_sector = document_.allocate_short_sector();
×
376
            document_.ssat_[static_cast<std::size_t>(chain_.back())] = next_sector;
×
377
            chain_.push_back(next_sector);
×
378
            document_.write_ssat();
×
379
        }
380
        else
381
        {
382
            auto next_sector = document_.allocate_sector();
974✔
383
            document_.sat_[static_cast<std::size_t>(chain_.back())] = next_sector;
974✔
384
            chain_.push_back(next_sector);
974✔
385
            document_.write_sat();
974✔
386
        }
387

388
        auto value = static_cast<std::uint8_t>(c);
974✔
389

390
        if (c != traits_type::eof())
974!
391
        {
392
            current_sector_[position_ % current_sector_.size()] = value;
974✔
393
        }
394

395
        pbump(1);
974✔
396

397
        return traits_type::to_int_type(static_cast<char>(value));
974✔
398
    }
399

400
    void convert_to_long_stream()
4✔
401
    {
402
        sector_reader_.reset();
4✔
403

404
        auto num_sectors = current_sector_.size() / document_.sector_size();
4✔
405
        auto new_chain = document_.allocate_sectors(num_sectors);
4✔
406

407
        for (auto link : new_chain)
36✔
408
        {
409
            document_.write_sector(sector_reader_, link);
32✔
410
            sector_reader_.offset(sector_reader_.offset() + document_.short_sector_size());
32✔
411
        }
412

413
        current_sector_.resize(document_.sector_size(), 0);
4✔
414
        std::fill(current_sector_.begin(), current_sector_.end(), byte(0));
4✔
415

416
        if (entry_.start < 0)
4!
417
        {
418
            // TODO: deallocate short sectors here
419
            if (document_.header_.num_short_sectors == 0)
4!
420
            {
421
                document_.entries_[0].start = EndOfChain;
×
422
            }
423
        }
424

425
        chain_ = new_chain;
4✔
426
        entry_.start = chain_.front();
4✔
427
        document_.write_directory();
4✔
428
    }
4✔
429

430
    std::streampos seekoff(std::streamoff off, std::ios_base::seekdir way, std::ios_base::openmode) override
×
431
    {
432
        if (way == std::ios_base::beg)
×
433
        {
434
            position_ = 0;
×
435
        }
436
        else if (way == std::ios_base::end)
×
437
        {
438
            position_ = entry_.size;
×
439
        }
440

441
        if (off < 0)
×
442
        {
443
            if (static_cast<std::size_t>(-off) > position_)
×
444
            {
445
                position_ = 0;
×
446
                return static_cast<std::ptrdiff_t>(-1);
×
447
            }
448
            else
449
            {
450
                position_ -= static_cast<std::size_t>(-off);
×
451
            }
452
        }
453
        else if (off > 0)
×
454
        {
455
            if (static_cast<std::size_t>(off) + position_ > entry_.size)
×
456
            {
457
                position_ = entry_.size;
×
458
                return static_cast<std::ptrdiff_t>(-1);
×
459
            }
460
            else
461
            {
462
                position_ += static_cast<std::size_t>(off);
×
463
            }
464
        }
465

466
        return static_cast<std::ptrdiff_t>(position_);
×
467
    }
468

469
    std::streampos seekpos(std::streampos sp, std::ios_base::openmode) override
×
470
    {
471
        if (sp < 0)
×
472
        {
473
            position_ = 0;
×
474
        }
475
        else if (static_cast<std::size_t>(sp) > entry_.size)
×
476
        {
477
            position_ = entry_.size;
×
478
        }
479
        else
480
        {
481
            position_ = static_cast<std::size_t>(sp);
×
482
        }
483

484
        return static_cast<std::ptrdiff_t>(position_);
×
485
    }
486

487
private:
488
    compound_document_entry &entry_;
489
    compound_document &document_;
490
    std::vector<byte> current_sector_;
491
    binary_reader<byte> sector_reader_;
492
    std::size_t position_;
493
    sector_chain chain_;
494
};
495

496
compound_document_ostreambuf::~compound_document_ostreambuf()
16✔
497
{
498
    sync();
8✔
499
}
16✔
500

501
compound_document::compound_document(std::ostream &out)
4✔
502
    : out_(&out),
4✔
503
      stream_in_(nullptr),
4✔
504
      stream_out_(nullptr)
8✔
505
{
506
    header_.msat.fill(FreeSector);
4✔
507
    write_header();
4✔
508
    insert_entry("/Root Entry", compound_document_entry::entry_type::RootStorage);
4✔
509
}
4✔
510

511
compound_document::compound_document(std::istream &in)
32✔
512
    : in_(&in),
32✔
513
      stream_in_(nullptr),
32✔
514
      stream_out_(nullptr)
64✔
515
{
516
    read_header();
32✔
517
    read_msat();
32✔
518
    read_sat();
32✔
519
    read_ssat();
32✔
520
    read_directory();
32✔
521
}
32✔
522

523
compound_document::~compound_document()
36✔
524
{
525
    close();
36✔
526
}
36✔
527

528
void compound_document::close()
36✔
529
{
530
    stream_out_buffer_.reset(nullptr);
36✔
531
}
36✔
532

533
std::size_t compound_document::sector_size()
41,548✔
534
{
535
    return static_cast<std::size_t>(1) << header_.sector_size_power;
41,548✔
536
}
537

538
std::size_t compound_document::short_sector_size()
1,424✔
539
{
540
    return static_cast<std::size_t>(1) << header_.short_sector_size_power;
1,424✔
541
}
542

543
std::istream &compound_document::open_read_stream(const std::string &name)
64✔
544
{
545
    if (!contains_entry(name, compound_document_entry::entry_type::UserStream))
64!
546
    {
NEW
547
        throw xlnt::exception("compound document entry of type UserStream not found at path: " + name);
×
548
    }
549

550
    const auto entry_id = find_entry(name, compound_document_entry::entry_type::UserStream);
64✔
551
    const auto &entry = entries_.at(static_cast<std::size_t>(entry_id));
64✔
552

553
    stream_in_buffer_.reset(new compound_document_istreambuf(entry, *this));
64!
554
    stream_in_.rdbuf(stream_in_buffer_.get());
64✔
555

556
    return stream_in_;
64✔
557
}
558

559
std::ostream &compound_document::open_write_stream(const std::string &name)
8✔
560
{
561
    auto entry_id = contains_entry(name, compound_document_entry::entry_type::UserStream)
8✔
562
        ? find_entry(name, compound_document_entry::entry_type::UserStream)
8!
563
        : insert_entry(name, compound_document_entry::entry_type::UserStream);
8✔
564
    auto &entry = entries_.at(static_cast<std::size_t>(entry_id));
8✔
565

566
    stream_out_buffer_.reset(new compound_document_ostreambuf(entry, *this));
8!
567
    stream_out_.rdbuf(stream_out_buffer_.get());
8✔
568

569
    return stream_out_;
8✔
570
}
571

572
template <typename T>
573
void compound_document::write_sector(binary_reader<T> &reader, sector_id id)
10,579✔
574
{
575
    out_->seekp(static_cast<std::ptrdiff_t>(sector_data_start() + sector_size() * static_cast<std::size_t>(id)));
10,579✔
576
    out_->write(reinterpret_cast<const char *>(reader.data() + reader.offset()),
10,579✔
577
        static_cast<std::ptrdiff_t>(std::min(sector_size(), reader.bytes() - reader.offset())));
10,579✔
578
}
10,579✔
579

580
template <typename T>
581
void compound_document::write_short_sector(binary_reader<T> &reader, sector_id id)
76✔
582
{
583
    auto chain = follow_chain(entries_[0].start, sat_);
76✔
584
    auto sector_id = chain[static_cast<std::size_t>(id) / (sector_size() / short_sector_size())];
76✔
585
    auto sector_offset = static_cast<std::size_t>(id) % (sector_size() / short_sector_size()) * short_sector_size();
76✔
586
    out_->seekp(static_cast<std::ptrdiff_t>(sector_data_start() + sector_size() * static_cast<std::size_t>(sector_id) + sector_offset));
76✔
587
    out_->write(reinterpret_cast<const char *>(reader.data() + reader.offset()),
76✔
588
        static_cast<std::ptrdiff_t>(std::min(short_sector_size(), reader.bytes() - reader.offset())));
76✔
589
}
76✔
590

591
template <typename T>
592
void compound_document::read_sector(sector_id id, binary_writer<T> &writer)
2,743✔
593
{
594
    in_->seekg(static_cast<std::ptrdiff_t>(sector_data_start() + sector_size() * static_cast<std::size_t>(id)));
2,743✔
595
    std::vector<byte> sector(sector_size(), 0);
2,743✔
596
    in_->read(reinterpret_cast<char *>(sector.data()), static_cast<std::ptrdiff_t>(sector_size()));
2,743✔
597
    writer.append(sector);
2,743✔
598
}
2,743✔
599

600
template <typename T>
601
void compound_document::read_sector_chain(sector_id start, binary_writer<T> &writer)
602
{
603
    for (auto link : follow_chain(start, sat_))
604
    {
605
        read_sector(link, writer);
606
    }
607
}
608

609
template <typename T>
610
void compound_document::read_sector_chain(sector_id start, binary_writer<T> &writer, sector_id offset, std::size_t count)
611
{
612
    auto chain = follow_chain(start, sat_);
613

614
    for (auto i = std::size_t(0); i < count; ++i)
615
    {
616
        read_sector(chain[offset + i], writer);
617
    }
618
}
619

620
template <typename T>
621
void compound_document::read_short_sector(sector_id id, binary_writer<T> &writer)
218✔
622
{
623
    const auto container_chain = follow_chain(entries_[0].start, sat_);
218✔
624
    auto container = std::vector<byte>();
218✔
625
    auto container_writer = binary_writer<byte>(container);
218✔
626

627
    for (auto sector : container_chain)
790✔
628
    {
629
        read_sector(sector, container_writer);
572✔
630
    }
631

632
    auto container_reader = binary_reader<byte>(container);
218✔
633
    container_reader.offset(static_cast<std::size_t>(id) * short_sector_size());
218✔
634

635
    writer.append(container_reader, short_sector_size());
218✔
636
}
218✔
637

638
template <typename T>
639
void compound_document::read_short_sector_chain(sector_id start, binary_writer<T> &writer)
640
{
641
    for (auto link : follow_chain(start, ssat_))
642
    {
643
        read_short_sector(link, writer);
644
    }
645
}
646

647
template <typename T>
648
void compound_document::read_short_sector_chain(sector_id start, binary_writer<T> &writer, sector_id offset, std::size_t count)
649
{
650
    auto chain = follow_chain(start, ssat_);
651

652
    for (auto i = std::size_t(0); i < count; ++i)
653
    {
654
        read_short_sector(chain[offset + i], writer);
655
    }
656
}
657

658
sector_id compound_document::allocate_sector()
1,026✔
659
{
660
    const auto sectors_per_sector = sector_size() / sizeof(sector_id);
1,026✔
661
    auto next_free_iter = std::find(sat_.begin(), sat_.end(), FreeSector);
1,026✔
662

663
    if (next_free_iter == sat_.end())
1,026✔
664
    {
665
        auto next_msat_index = header_.num_msat_sectors;
11✔
666
        auto new_sat_sector_id = sector_id(sat_.size());
11✔
667

668
        msat_.push_back(new_sat_sector_id);
11✔
669
        write_msat();
11✔
670

671
        header_.msat[msat_.size() - 1] = new_sat_sector_id;
11✔
672
        ++header_.num_msat_sectors;
11✔
673
        write_header();
11✔
674

675
        sat_.resize(sat_.size() + sectors_per_sector, FreeSector);
11✔
676
        sat_[static_cast<std::size_t>(new_sat_sector_id)] = SATSector;
11✔
677

678
        auto sat_reader = binary_reader<sector_id>(sat_);
11✔
679
        sat_reader.offset(next_msat_index * sectors_per_sector);
11✔
680
        write_sector(sat_reader, new_sat_sector_id);
11✔
681

682
        next_free_iter = std::find(sat_.begin(), sat_.end(), FreeSector);
11✔
683
    }
11✔
684

685
    auto next_free = sector_id(next_free_iter - sat_.begin());
1,026✔
686
    sat_[static_cast<std::size_t>(next_free)] = EndOfChain;
1,026✔
687

688
    write_sat();
1,026✔
689

690
    auto empty_sector = std::vector<byte>(sector_size());
1,026✔
691
    auto empty_sector_reader = binary_reader<byte>(empty_sector);
1,026✔
692
    write_sector(empty_sector_reader, next_free);
1,026✔
693

694
    return next_free;
1,026✔
695
}
1,026✔
696

697
sector_chain compound_document::allocate_sectors(std::size_t count)
4✔
698
{
699
    if (count == std::size_t(0)) return {};
4!
700

701
    auto chain = sector_chain();
4✔
702
    auto current = allocate_sector();
4✔
703

704
    for (auto i = std::size_t(1); i < count; ++i)
32✔
705
    {
706
        chain.push_back(current);
28✔
707
        auto next = allocate_sector();
28✔
708
        sat_[static_cast<std::size_t>(current)] = next;
28✔
709
        current = next;
28✔
710
    }
711

712
    chain.push_back(current);
4✔
713
    write_sat();
4✔
714

715
    return chain;
4✔
716
}
4✔
717

718
sector_chain compound_document::follow_chain(sector_id start, const sector_chain &table)
5,505✔
719
{
720
    auto chain = sector_chain();
5,505✔
721
    auto current = start;
5,505✔
722

723
    while (current >= 0)
248,373✔
724
    {
725
        chain.push_back(current);
242,868✔
726
        current = table[static_cast<std::size_t>(current)];
242,868✔
727
    }
728

729
    return chain;
5,505✔
730
}
×
731

732
sector_chain compound_document::allocate_short_sectors(std::size_t count)
4✔
733
{
734
    if (count == std::size_t(0)) return {};
4!
735

736
    auto chain = sector_chain();
4✔
737
    auto current = allocate_short_sector();
4✔
738

739
    for (auto i = std::size_t(1); i < count; ++i)
76✔
740
    {
741
        chain.push_back(current);
72✔
742
        auto next = allocate_short_sector();
72✔
743
        ssat_[static_cast<std::size_t>(current)] = next;
72✔
744
        current = next;
72✔
745
    }
746

747
    chain.push_back(current);
4✔
748
    write_ssat();
4✔
749

750
    return chain;
4✔
751
}
4✔
752

753
sector_id compound_document::allocate_short_sector()
76✔
754
{
755
    const auto sectors_per_sector = sector_size() / sizeof(sector_id);
76✔
756
    auto next_free_iter = std::find(ssat_.begin(), ssat_.end(), FreeSector);
76✔
757

758
    if (next_free_iter == ssat_.end())
76✔
759
    {
760
        auto new_ssat_sector_id = allocate_sector();
4✔
761

762
        if (header_.ssat_start < 0)
4!
763
        {
764
            header_.ssat_start = new_ssat_sector_id;
4✔
765
        }
766
        else
767
        {
768
            auto ssat_chain = follow_chain(header_.ssat_start, sat_);
×
769
            sat_[static_cast<std::size_t>(ssat_chain.back())] = new_ssat_sector_id;
×
770
            write_sat();
×
771
        }
×
772

773
        write_header();
4✔
774

775
        auto old_size = ssat_.size();
4✔
776
        ssat_.resize(old_size + sectors_per_sector, FreeSector);
4✔
777

778
        auto ssat_reader = binary_reader<sector_id>(ssat_);
4✔
779
        ssat_reader.offset(old_size / sectors_per_sector);
4✔
780
        write_sector(ssat_reader, new_ssat_sector_id);
4✔
781

782
        next_free_iter = std::find(ssat_.begin(), ssat_.end(), FreeSector);
4✔
783
    }
4✔
784

785
    ++header_.num_short_sectors;
76✔
786
    write_header();
76✔
787

788
    auto next_free = sector_id(next_free_iter - ssat_.begin());
76✔
789
    ssat_[static_cast<std::size_t>(next_free)] = EndOfChain;
76✔
790

791
    write_ssat();
76✔
792

793
    const auto short_sectors_per_sector = sector_size() / short_sector_size();
76✔
794
    const auto required_container_sectors = static_cast<std::size_t>(next_free) / short_sectors_per_sector + std::size_t(1);
76✔
795

796
    if (required_container_sectors > 0)
76!
797
    {
798
        if (entries_[0].start < 0)
76✔
799
        {
800
            entries_[0].start = allocate_sector();
4✔
801
            write_entry(0);
4✔
802
        }
803

804
        auto container_chain = follow_chain(entries_[0].start, sat_);
76✔
805

806
        if (required_container_sectors > container_chain.size())
76✔
807
        {
808
            sat_[static_cast<std::size_t>(container_chain.back())] = allocate_sector();
8✔
809
            write_sat();
8✔
810
        }
811
    }
76✔
812

813
    return next_free;
76✔
814
}
815

816
directory_id compound_document::next_empty_entry()
12✔
817
{
818
    auto entry_id = directory_id(0);
12✔
819

820
    for (; entry_id < directory_id(entries_.size()); ++entry_id)
24✔
821
    {
822
        auto &entry = entries_[static_cast<std::size_t>(entry_id)];
20✔
823

824
        if (entry.type == compound_document_entry::entry_type::Empty)
20✔
825
        {
826
            return entry_id;
8✔
827
        }
828
    }
829

830
    // entry_id is now equal to entries_.size()
831

832
    if (header_.directory_start < 0)
4!
833
    {
834
        header_.directory_start = allocate_sector();
4✔
835
    }
836
    else
837
    {
838
        auto directory_chain = follow_chain(header_.directory_start, sat_);
×
839
        sat_[static_cast<std::size_t>(directory_chain.back())] = allocate_sector();
×
840
        write_sat();
×
841
    }
×
842

843
    const auto entries_per_sector = sector_size()
4✔
844
        / sizeof(compound_document_entry);
4✔
845

846
    for (auto i = std::size_t(0); i < entries_per_sector; ++i)
20✔
847
    {
848
        auto empty_entry = compound_document_entry();
16✔
849
        empty_entry.type = compound_document_entry::entry_type::Empty;
16✔
850
        entries_.push_back(empty_entry);
16✔
851
        write_entry(entry_id + directory_id(i));
16✔
852
    }
853

854
    return entry_id;
4✔
855
}
856

857
directory_id compound_document::insert_entry(
12✔
858
    const std::string &name,
859
    compound_document_entry::entry_type type)
860
{
861
    auto entry_id = next_empty_entry();
12✔
862
    auto &entry = entries_[static_cast<std::size_t>(entry_id)];
12✔
863

864
    auto parent_id = directory_id(0);
12✔
865
    auto split = split_path(name);
12✔
866
    auto filename = split.back();
12✔
867
    split.pop_back();
12✔
868

869
    if (split.size() > 1)
12!
870
    {
NEW
871
        auto joined_path = join_path(split);
×
NEW
872
        parent_id = find_entry(joined_path, compound_document_entry::entry_type::UserStorage);
×
873

874
        if (parent_id < 0)
×
875
        {
NEW
876
            throw xlnt::exception("compound document entry of type UserStorage not found at path: " + joined_path);
×
877
        }
878

879
        parent_storage_[entry_id] = parent_id;
×
UNCOV
880
    }
×
881

882
    entry.name(filename);
12✔
883
    entry.type = type;
12✔
884

885
    tree_insert(entry_id, parent_id);
12✔
886
    write_directory();
12✔
887

888
    return entry_id;
12✔
889
}
12✔
890

891
std::size_t compound_document::sector_data_start()
17,610✔
892
{
893
    return sizeof(compound_document_header);
17,610✔
894
}
895

896
bool compound_document::contains_entry(const std::string &path,
72✔
897
    compound_document_entry::entry_type type)
898
{
899
    return find_entry(path, type) >= 0;
72✔
900
}
901

902
directory_id compound_document::find_entry(const std::string &name,
136✔
903
    compound_document_entry::entry_type type)
904
{
905
    if (type == compound_document_entry::entry_type::RootStorage
136✔
906
        && (name == "/" || name == "/Root Entry")) return 0;
136!
907

908
    auto entry_id = directory_id(0);
136✔
909

910
    for (auto &entry : entries_)
456✔
911
    {
912
        if (entry.type == type && tree_path(entry_id) == name)
448!
913
        {
914
            return entry_id;
128✔
915
        }
916

917
        ++entry_id;
320✔
918
    }
919

920
    return End;
8✔
921
}
922

923
void compound_document::print_directory()
×
924
{
925
    auto entry_id = directory_id(0);
×
926

927
    for (auto &entry : entries_)
×
928
    {
929
        if (entry.type == compound_document_entry::entry_type::UserStream)
×
930
        {
931
            std::cout << tree_path(entry_id) << std::endl;
×
932
        }
933

934
        ++entry_id;
×
935
    }
936
}
×
937

938
void compound_document::write_directory()
998✔
939
{
940
    for (auto entry_id = std::size_t(0); entry_id < entries_.size(); ++entry_id)
4,990✔
941
    {
942
        write_entry(directory_id(entry_id));
3,992✔
943
    }
944
}
998✔
945

946
void compound_document::read_directory()
32✔
947
{
948
    const auto entries_per_sector = sector_size() / sizeof(compound_document_entry);
32✔
949
    const auto num_entries = follow_chain(header_.directory_start, sat_).size() * entries_per_sector;
32✔
950

951
    for (auto entry_id = std::size_t(0); entry_id < num_entries; ++entry_id)
232✔
952
    {
953
        entries_.push_back(compound_document_entry());
200✔
954
        read_entry(directory_id(entry_id));
200✔
955
    }
956

957
    auto stack = std::vector<directory_id>();
32✔
958
    auto storage_siblings = std::vector<directory_id>();
32✔
959
    auto stream_siblings = std::vector<directory_id>();
32✔
960

961
    auto directory_stack = std::vector<directory_id>();
32✔
962
    directory_stack.push_back(directory_id(0));
32✔
963

964
    while (!directory_stack.empty())
100✔
965
    {
966
        auto current_storage_id = directory_stack.back();
68✔
967
        directory_stack.pop_back();
68✔
968

969
        if (tree_child(current_storage_id) < 0) continue;
68!
970

971
        auto storage_stack = std::vector<directory_id>();
68✔
972
        auto storage_root_id = tree_child(current_storage_id);
68✔
973
        parent_[storage_root_id] = End;
68✔
974
        storage_stack.push_back(storage_root_id);
68✔
975

976
        while (!storage_stack.empty())
204✔
977
        {
978
            auto current_entry_id = storage_stack.back();
136✔
979
            auto current_entry = entries_[static_cast<std::size_t>(current_entry_id)];
136✔
980
            storage_stack.pop_back();
136✔
981

982
            parent_storage_[current_entry_id] = current_storage_id;
136✔
983

984
            if (current_entry.type == compound_document_entry::entry_type::UserStorage)
136✔
985
            {
986
                directory_stack.push_back(current_entry_id);
36✔
987
            }
988

989
            if (tree_left(current_entry_id) >= 0)
136✔
990
            {
991
                storage_stack.push_back(tree_left(current_entry_id));
18✔
992
                tree_parent(tree_left(current_entry_id)) = current_entry_id;
18✔
993
            }
994

995
            if (tree_right(current_entry_id) >= 0)
136✔
996
            {
997
                storage_stack.push_back(tree_right(current_entry_id));
50✔
998
                tree_parent(tree_right(current_entry_id)) = current_entry_id;
50✔
999
            }
1000
        }
1001
    }
68✔
1002
}
32✔
1003

1004
void compound_document::tree_insert(directory_id new_id, directory_id storage_id)
12✔
1005
{
1006
    using entry_color = compound_document_entry::entry_color;
1007

1008
    parent_storage_[new_id] = storage_id;
12✔
1009

1010
    tree_left(new_id) = End;
12✔
1011
    tree_right(new_id) = End;
12✔
1012

1013
    if (tree_root(new_id) == End)
12✔
1014
    {
1015
        if (new_id != 0)
8✔
1016
        {
1017
            tree_root(new_id) = new_id;
4✔
1018
        }
1019

1020
        tree_color(new_id) = entry_color::Black;
8✔
1021
        tree_parent(new_id) = End;
8✔
1022

1023
        return;
8✔
1024
    }
1025

1026
    // normal tree insert
1027
    // (will probably unbalance the tree, fix after)
1028
    auto x = tree_root(new_id);
4✔
1029
    auto y = End;
4✔
1030

1031
    while (x >= 0)
8✔
1032
    {
1033
        y = x;
4✔
1034

1035
        if (compare_keys(tree_key(new_id), tree_key(x)) > 0)
4!
1036
        {
1037
            x = tree_right(x);
×
1038
        }
1039
        else
1040
        {
1041
            x = tree_left(x);
4✔
1042
        }
1043
    }
1044

1045
    tree_parent(new_id) = y;
4✔
1046

1047
    if (compare_keys(tree_key(new_id), tree_key(y)) > 0)
4!
1048
    {
1049
        tree_right(y) = new_id;
×
1050
    }
1051
    else
1052
    {
1053
        tree_left(y) = new_id;
4✔
1054
    }
1055

1056
    tree_insert_fixup(new_id);
4✔
1057
}
1058

1059
std::string compound_document::tree_path(directory_id id)
244✔
1060
{
1061
    auto storage_id = parent_storage_[id];
244✔
1062
    auto result = std::vector<std::string>();
244✔
1063

1064
    while (storage_id > 0)
328✔
1065
    {
1066
        storage_id = parent_storage_[storage_id];
84✔
1067
        result.push_back(entries_[static_cast<std::size_t>(storage_id)].name());
84✔
1068
    }
1069

1070
    return "/" + join_path(result) + entries_[static_cast<std::size_t>(id)].name();
488✔
1071
}
244✔
1072

1073
void compound_document::tree_rotate_left(directory_id x)
×
1074
{
1075
    auto y = tree_right(x);
×
1076

1077
    // turn y's left subtree into x's right subtree
1078
    tree_right(x) = tree_left(y);
×
1079

1080
    if (tree_left(y) != End)
×
1081
    {
1082
        tree_parent(tree_left(y)) = x;
×
1083
    }
1084

1085
    // link x's parent to y
1086
    tree_parent(y) = tree_parent(x);
×
1087

1088
    if (tree_parent(x) == End)
×
1089
    {
1090
        tree_root(x) = y;
×
1091
    }
1092
    else if (x == tree_left(tree_parent(x)))
×
1093
    {
1094
        tree_left(tree_parent(x)) = y;
×
1095
    }
1096
    else
1097
    {
1098
        tree_right(tree_parent(x)) = y;
×
1099
    }
1100

1101
    // put x on y's left
1102
    tree_left(y) = x;
×
1103
    tree_parent(x) = y;
×
1104
}
×
1105

1106
void compound_document::tree_rotate_right(directory_id y)
×
1107
{
1108
    auto x = tree_left(y);
×
1109

1110
    // turn x's right subtree into y's left subtree
1111
    tree_left(y) = tree_right(x);
×
1112

1113
    if (tree_right(x) != End)
×
1114
    {
1115
        tree_parent(tree_right(x)) = y;
×
1116
    }
1117

1118
    // link y's parent to x
1119
    tree_parent(x) = tree_parent(y);
×
1120

1121
    if (tree_parent(y) == End)
×
1122
    {
1123
        tree_root(y) = x;
×
1124
    }
1125
    else if (y == tree_left(tree_parent(y)))
×
1126
    {
1127
        tree_left(tree_parent(y)) = x;
×
1128
    }
1129
    else
1130
    {
1131
        tree_right(tree_parent(y)) = x;
×
1132
    }
1133

1134
    // put y on x's right
1135
    tree_right(x) = y;
×
1136
    tree_parent(y) = x;
×
1137
}
×
1138

1139
void compound_document::tree_insert_fixup(directory_id x)
4✔
1140
{
1141
    using entry_color = compound_document_entry::entry_color;
1142

1143
    tree_color(x) = entry_color::Red;
4✔
1144

1145
    while (x != tree_root(x) && tree_color(tree_parent(x)) == entry_color::Red)
4!
1146
    {
1147
        if (tree_parent(x) == tree_left(tree_parent(tree_parent(x))))
×
1148
        {
1149
            auto y = tree_right(tree_parent(tree_parent(x)));
×
1150

1151
            if (y >= 0 && tree_color(y) == entry_color::Red)
×
1152
            {
1153
                // case 1
1154
                tree_color(tree_parent(x)) = entry_color::Black;
×
1155
                tree_color(y) = entry_color::Black;
×
1156
                tree_color(tree_parent(tree_parent(x))) = entry_color::Red;
×
1157
                x = tree_parent(tree_parent(x));
×
1158
            }
1159
            else
1160
            {
1161
                if (x == tree_right(tree_parent(x)))
×
1162
                {
1163
                    // case 2
1164
                    x = tree_parent(x);
×
1165
                    tree_rotate_left(x);
×
1166
                }
1167

1168
                // case 3
1169
                tree_color(tree_parent(x)) = entry_color::Black;
×
1170
                tree_color(tree_parent(tree_parent(x))) = entry_color::Red;
×
1171
                tree_rotate_right(tree_parent(tree_parent(x)));
×
1172
            }
1173
        }
1174
        else // same as above with left and right switched
1175
        {
1176
            auto y = tree_left(tree_parent(tree_parent(x)));
×
1177

1178
            if (y >= 0 && tree_color(y) == entry_color::Red)
×
1179
            {
1180
                //case 1
1181
                tree_color(tree_parent(x)) = entry_color::Black;
×
1182
                tree_color(y) = entry_color::Black;
×
1183
                tree_color(tree_parent(tree_parent(x))) = entry_color::Red;
×
1184
                x = tree_parent(tree_parent(x));
×
1185
            }
1186
            else
1187
            {
1188
                if (x == tree_left(tree_parent(x)))
×
1189
                {
1190
                    // case 2
1191
                    x = tree_parent(x);
×
1192
                    tree_rotate_right(x);
×
1193
                }
1194

1195
                // case 3
1196
                tree_color(tree_parent(x)) = entry_color::Black;
×
1197
                tree_color(tree_parent(tree_parent(x))) = entry_color::Red;
×
1198
                tree_rotate_left(tree_parent(tree_parent(x)));
×
1199
            }
1200
        }
1201
    }
1202

1203
    tree_color(tree_root(x)) = entry_color::Black;
4✔
1204
}
4✔
1205

1206
directory_id &compound_document::tree_left(directory_id id)
192✔
1207
{
1208
    return entries_[static_cast<std::size_t>(id)].prev;
192✔
1209
}
1210

1211
directory_id &compound_document::tree_right(directory_id id)
248✔
1212
{
1213
    return entries_[static_cast<std::size_t>(id)].next;
248✔
1214
}
1215

1216
directory_id &compound_document::tree_parent(directory_id id)
84✔
1217
{
1218
    return parent_[id];
84✔
1219
}
1220

1221
directory_id &compound_document::tree_root(directory_id id)
28✔
1222
{
1223
    return tree_child(parent_storage_[id]);
28✔
1224
}
1225

1226
directory_id &compound_document::tree_child(directory_id id)
164✔
1227
{
1228
    return entries_[static_cast<std::size_t>(id)].child;
164✔
1229
}
1230

1231
std::string compound_document::tree_key(directory_id id)
16✔
1232
{
1233
    return entries_[static_cast<std::size_t>(id)].name();
16✔
1234
}
1235

1236
compound_document_entry::entry_color &compound_document::tree_color(directory_id id)
20✔
1237
{
1238
    return entries_[static_cast<std::size_t>(id)].color;
20✔
1239
}
1240

1241
void compound_document::read_header()
32✔
1242
{
1243
    in_->seekg(0, std::ios::beg);
32✔
1244
    in_->read(reinterpret_cast<char *>(&header_), sizeof(compound_document_header));
32✔
1245
}
32✔
1246

1247
void compound_document::read_msat()
32✔
1248
{
1249
    msat_.clear();
32✔
1250

1251
    auto msat_sector = header_.extra_msat_start;
32✔
1252
    auto msat_writer = binary_writer<sector_id>(msat_);
32✔
1253

1254
    for (auto i = std::uint32_t(0); i < header_.num_msat_sectors; ++i)
91✔
1255
    {
1256
        if (i < std::uint32_t(109))
59!
1257
        {
1258
            msat_writer.write(header_.msat[i]);
59✔
1259
        }
1260
        else
1261
        {
1262
            read_sector(msat_sector, msat_writer);
×
1263

1264
            msat_sector = msat_.back();
×
1265
            msat_.pop_back();
×
1266
        }
1267
    }
1268
}
32✔
1269

1270
void compound_document::read_sat()
32✔
1271
{
1272
    sat_.clear();
32✔
1273
    auto sat_writer = binary_writer<sector_id>(sat_);
32✔
1274

1275
    for (auto msat_sector : msat_)
91✔
1276
    {
1277
        read_sector(msat_sector, sat_writer);
59✔
1278
    }
1279
}
32✔
1280

1281
void compound_document::read_ssat()
32✔
1282
{
1283
    ssat_.clear();
32✔
1284
    auto ssat_writer = binary_writer<sector_id>(ssat_);
32✔
1285

1286
    for (auto ssat_sector : follow_chain(header_.ssat_start, sat_))
64✔
1287
    {
1288
        read_sector(ssat_sector, ssat_writer);
32✔
1289
    }
32✔
1290
}
32✔
1291

1292
void compound_document::read_entry(directory_id id)
200✔
1293
{
1294
    const auto directory_chain = follow_chain(header_.directory_start, sat_);
200✔
1295
    const auto entries_per_sector = sector_size() / sizeof(compound_document_entry);
200✔
1296
    const auto directory_sector = directory_chain[static_cast<std::size_t>(id) / entries_per_sector];
200✔
1297
    const auto offset = sector_size() * static_cast<std::size_t>(directory_sector)
200✔
1298
        + ((static_cast<std::size_t>(id) % entries_per_sector) * sizeof(compound_document_entry));
200✔
1299

1300
    in_->seekg(static_cast<std::ptrdiff_t>(sector_data_start() + offset), std::ios::beg);
200✔
1301
    in_->read(reinterpret_cast<char *>(&entries_[static_cast<std::size_t>(id)]), sizeof(compound_document_entry));
200✔
1302
}
200✔
1303

1304
void compound_document::write_header()
95✔
1305
{
1306
    out_->seekp(0, std::ios::beg);
95✔
1307
    out_->write(reinterpret_cast<char *>(&header_), sizeof(compound_document_header));
95✔
1308
}
95✔
1309

1310
void compound_document::write_msat()
11✔
1311
{
1312
    auto msat_sector = header_.extra_msat_start;
11✔
1313

1314
    for (auto i = std::uint32_t(0); i < header_.num_msat_sectors; ++i)
39✔
1315
    {
1316
        if (i < std::uint32_t(109))
28!
1317
        {
1318
            header_.msat[i] = msat_[i];
28✔
1319
        }
1320
        else
1321
        {
1322
            auto sector = std::vector<sector_id>();
×
1323
            auto sector_writer = binary_writer<sector_id>(sector);
×
1324

1325
            read_sector(msat_sector, sector_writer);
×
1326

1327
            msat_sector = sector.back();
×
1328
            sector.pop_back();
×
1329

1330
            std::copy(sector.begin(), sector.end(), std::back_inserter(msat_));
×
1331
        }
×
1332
    }
1333
}
11✔
1334

1335
void compound_document::write_sat()
2,012✔
1336
{
1337
    auto sector_reader = binary_reader<sector_id>(sat_);
2,012✔
1338

1339
    for (auto sat_sector : msat_)
10,464✔
1340
    {
1341
        write_sector(sector_reader, sat_sector);
8,452✔
1342
    }
1343
}
2,012✔
1344

1345
void compound_document::write_ssat()
80✔
1346
{
1347
    auto sector_reader = binary_reader<sector_id>(ssat_);
80✔
1348

1349
    for (auto ssat_sector : follow_chain(header_.ssat_start, sat_))
160✔
1350
    {
1351
        write_sector(sector_reader, ssat_sector);
80✔
1352
    }
80✔
1353
}
80✔
1354

1355
void compound_document::write_entry(directory_id id)
4,012✔
1356
{
1357
    const auto directory_chain = follow_chain(header_.directory_start, sat_);
4,012✔
1358
    const auto entries_per_sector = sector_size() / sizeof(compound_document_entry);
4,012✔
1359
    const auto directory_sector = directory_chain[static_cast<std::size_t>(id) / entries_per_sector];
4,012✔
1360
    const auto offset = sector_data_start() + sector_size() * static_cast<std::size_t>(directory_sector)
4,012✔
1361
        + ((static_cast<std::size_t>(id) % entries_per_sector) * sizeof(compound_document_entry));
4,012✔
1362

1363
    out_->seekp(static_cast<std::ptrdiff_t>(offset), std::ios::beg);
4,012✔
1364
    out_->write(reinterpret_cast<char *>(&entries_[static_cast<std::size_t>(id)]), sizeof(compound_document_entry));
4,012✔
1365
}
4,012✔
1366

1367
} // namespace detail
1368
} // 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