├── .github └── workflows │ ├── codeql-analysis.yml │ └── testing.yml ├── .gitignore ├── .reuse └── dep5 ├── CMakeLists.txt ├── LICENSES ├── CC0-1.0.txt └── MIT.txt ├── README.md ├── codecov.yml ├── doc ├── Excel97-2007BinaryFileFormat(xls)Specification.pdf ├── compdocfileformat.pdf └── excelfileformat.pdf ├── read-excel-config.cmake.in ├── read-excel ├── bof.hpp ├── book.hpp ├── cell.hpp ├── compoundfile │ ├── compoundfile.hpp │ ├── compoundfile_exceptions.hpp │ ├── compoundfile_stream_and_dir.hpp │ ├── header.hpp │ ├── msat.hpp │ ├── sat.hpp │ └── utils.hpp ├── exceptions.hpp ├── formula.hpp ├── parser.hpp ├── record.hpp ├── sheet.hpp ├── storage.hpp ├── stream.hpp └── string.hpp ├── sample ├── CMakeLists.txt ├── main.cpp └── sample.xls └── test ├── CMakeLists.txt ├── bof ├── CMakeLists.txt └── main.cpp ├── book ├── CMakeLists.txt └── main.cpp ├── cell ├── CMakeLists.txt └── main.cpp ├── complex ├── CMakeLists.txt ├── benchmark.cpp └── main.cpp ├── compoundfile ├── CMakeLists.txt └── main.cpp ├── data ├── MiscOperatorTests.xls ├── big.xls ├── datetime.xls ├── sample.xls ├── strange.xls ├── stringformula.xls ├── test.xls └── verybig.xls ├── datetime ├── CMakeLists.txt └── main.cpp ├── doctest └── doctest.h ├── formula ├── CMakeLists.txt └── main.cpp ├── header ├── CMakeLists.txt └── main.cpp ├── helper └── helper.hpp ├── record ├── CMakeLists.txt └── main.cpp ├── sst ├── CMakeLists.txt └── main.cpp ├── stream ├── CMakeLists.txt ├── stream.cpp └── stream.hpp ├── string ├── CMakeLists.txt └── main.cpp └── testdocument ├── CMakeLists.txt ├── document.cpp └── document.hpp /.github/workflows/codeql-analysis.yml: -------------------------------------------------------------------------------- 1 | # For most projects, this workflow file will not need changing; you simply need 2 | # to commit it to your repository. 3 | # 4 | # You may wish to alter this file to override the set of languages analyzed, 5 | # or to provide custom queries or build logic. 6 | name: "CodeQL" 7 | 8 | on: [workflow_dispatch] 9 | 10 | jobs: 11 | analyze: 12 | name: Analyze 13 | runs-on: ubuntu-latest 14 | 15 | strategy: 16 | fail-fast: false 17 | matrix: 18 | # Override automatic language detection by changing the below list 19 | # Supported options are ['csharp', 'cpp', 'go', 'java', 'javascript', 'python'] 20 | language: ['cpp'] 21 | # Learn more... 22 | # https://docs.github.com/en/github/finding-security-vulnerabilities-and-errors-in-your-code/configuring-code-scanning#overriding-automatic-language-detection 23 | 24 | steps: 25 | - name: Checkout repository 26 | uses: actions/checkout@v3 27 | with: 28 | # We must fetch at least the immediate parents so that if this is 29 | # a pull request then we can checkout the head. 30 | fetch-depth: 2 31 | 32 | - run: git submodule update --init --recursive 33 | 34 | # Initializes the CodeQL tools for scanning. 35 | - name: Initialize CodeQL 36 | uses: github/codeql-action/init@v2 37 | with: 38 | languages: ${{ matrix.language }} 39 | # If you wish to specify custom queries, you can do so here or in a config file. 40 | # By default, queries listed here will override any specified in a config file. 41 | # Prefix the list here with "+" to use these queries and those in the config file. 42 | # queries: ./path/to/local/query, your-org/your-repo/queries@main 43 | 44 | - run: | 45 | cmake . 46 | make 47 | 48 | - name: Perform CodeQL Analysis 49 | uses: github/codeql-action/analyze@v2 50 | -------------------------------------------------------------------------------- /.github/workflows/testing.yml: -------------------------------------------------------------------------------- 1 | # For most projects, this workflow file will not need changing; you simply need 2 | # to commit it to your repository. 3 | # 4 | # You may wish to alter this file to override the set of languages analyzed, 5 | # or to provide custom queries or build logic. 6 | name: "build" 7 | 8 | on: 9 | push: 10 | branches: [master] 11 | pull_request: 12 | # The branches below must be a subset of the branches above 13 | branches: [master] 14 | 15 | jobs: 16 | linux: 17 | name: linux 18 | runs-on: ubuntu-latest 19 | 20 | strategy: 21 | fail-fast: true 22 | matrix: 23 | # Override automatic language detection by changing the below list 24 | # Supported options are ['csharp', 'cpp', 'go', 'java', 'javascript', 'python'] 25 | language: ['cpp'] 26 | # Learn more... 27 | # https://docs.github.com/en/github/finding-security-vulnerabilities-and-errors-in-your-code/configuring-code-scanning#overriding-automatic-language-detection 28 | 29 | steps: 30 | - name: Checkout repository 31 | uses: actions/checkout@v3 32 | with: 33 | # We must fetch at least the immediate parents so that if this is 34 | # a pull request then we can checkout the head. 35 | fetch-depth: 2 36 | 37 | - run: git submodule update --init --recursive 38 | 39 | - run: sudo apt install lcov 40 | 41 | - name: Build and Test 42 | run: | 43 | cmake -DENABLE_COVERAGE=ON -DCMAKE_BUILD_TYPE=Debug . 44 | make 45 | ctest --output-on-failure --verbose 46 | lcov --directory . --capture --output-file coverage.info 47 | lcov --remove coverage.info '/usr/*' --output-file coverage.info 48 | lcov --list coverage.info 49 | 50 | - name: Codecov Upload 51 | uses: codecov/codecov-action@v4 52 | with: 53 | fail_ci_if_error: true 54 | token: ${{ secrets.CODECOV_TOKEN }} 55 | 56 | windows: 57 | name: windows 58 | runs-on: windows-latest 59 | 60 | strategy: 61 | fail-fast: true 62 | matrix: 63 | language: ['cpp'] 64 | 65 | steps: 66 | - name: Checkout repository 67 | uses: actions/checkout@v3 68 | with: 69 | # We must fetch at least the immediate parents so that if this is 70 | # a pull request then we can checkout the head. 71 | fetch-depth: 2 72 | 73 | - run: git submodule update --init --recursive 74 | 75 | - name: Enable developer command prompt 76 | uses: ilammy/msvc-dev-cmd@v1 77 | 78 | - name: Build and Test 79 | run: | 80 | cmake -G "NMake Makefiles" -DCMAKE_BUILD_TYPE=Relese . 81 | nmake 82 | ctest --output-on-failure --verbose 83 | 84 | macos: 85 | name: macos 86 | runs-on: macos-latest 87 | 88 | strategy: 89 | fail-fast: true 90 | matrix: 91 | language: ['cpp'] 92 | 93 | steps: 94 | - name: Checkout repository 95 | uses: actions/checkout@v3 96 | with: 97 | # We must fetch at least the immediate parents so that if this is 98 | # a pull request then we can checkout the head. 99 | fetch-depth: 2 100 | 101 | - run: git submodule update --init --recursive 102 | 103 | - name: Build and Test 104 | run: | 105 | cmake -DCMAKE_BUILD_TYPE=Relese . 106 | make 107 | ctest --output-on-failure --verbose 108 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | /**/debug/ 3 | /**/release/ 4 | /lib/*.* 5 | !/lib/.empty 6 | /**/Makefile* 7 | *.user 8 | /**/o 9 | /**/test.* 10 | /**/sample.* 11 | !/**/sample.xls 12 | -------------------------------------------------------------------------------- /.reuse/dep5: -------------------------------------------------------------------------------- 1 | Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/ 2 | Upstream-Name: read-excel 3 | Upstream-Contact: Igor Mironchik 4 | Source: https://github.com/igormironchik/read-excel 5 | 6 | Files: .github/* .gitignore codecov.yml README.md */*.txt CMakeLists.txt read-excel-config.cmake.in sample/sample.xls test/data/big.xls test/data/datetime.xls test/data/sample.xls test/data/stringformula.xls test/data/test.xls test/data/verybig.xls 7 | Copyright: 2011-2024 Igor Mironchik 8 | License: MIT 9 | 10 | Files: test/doctest/* 11 | Copyright: 2016-2021 Viktor Kirilov 12 | License: MIT 13 | 14 | Files: doc/compdocfileformat.pdf doc/excelfileformat.pdf 15 | Copyright: Daniel Rentz 16 | License: CC0-1.0 17 | 18 | Files: test/data/MiscOperatorTests.xls test/data/strange.xls 19 | Copyright: none 20 | License: CC0-1.0 21 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | 2 | cmake_minimum_required( VERSION 3.19 ) 3 | 4 | set( EXCEL_VERSION "1.2.8" ) 5 | 6 | option( BUILD_EXAMPLES "Build examples? Default ON." ON ) 7 | option( BUILD_TESTS "Build tests? Default ON." ON ) 8 | option( BUILD_BENCHMARK "Build benchmark with libxls? Default OFF." OFF ) 9 | 10 | if( NOT CMAKE_BUILD_TYPE ) 11 | set( CMAKE_BUILD_TYPE "Release" 12 | CACHE STRING "Choose the type of build." 13 | FORCE) 14 | endif( NOT CMAKE_BUILD_TYPE ) 15 | 16 | SET( CMAKE_CXX_STANDARD 14 ) 17 | 18 | SET( CMAKE_CXX_STANDARD_REQUIRED ON ) 19 | 20 | project( read-excel ) 21 | 22 | set( CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib ) 23 | 24 | if( ${CMAKE_PROJECT_NAME} STREQUAL ${PROJECT_NAME} ) 25 | 26 | if( BUILD_EXAMPLES ) 27 | add_subdirectory( sample ) 28 | endif() 29 | 30 | if( BUILD_TESTS ) 31 | enable_testing() 32 | 33 | add_subdirectory( test ) 34 | endif() 35 | 36 | file( GLOB_RECURSE SRC read-excel/* ) 37 | 38 | add_library( read-excel INTERFACE ${SRC} ) 39 | add_library( read-excel::read-excel ALIAS read-excel ) 40 | 41 | target_include_directories( read-excel INTERFACE 42 | $ 43 | $ 44 | ) 45 | 46 | install( DIRECTORY read-excel 47 | DESTINATION include 48 | ) 49 | 50 | install( TARGETS read-excel 51 | EXPORT read-excel-targets 52 | RUNTIME DESTINATION bin 53 | ARCHIVE DESTINATION lib 54 | LIBRARY DESTINATION lib 55 | INCLUDES DESTINATION include 56 | ) 57 | 58 | install( EXPORT read-excel-targets 59 | DESTINATION lib/cmake/read-excel 60 | NAMESPACE read-excel:: 61 | ) 62 | 63 | include( CMakePackageConfigHelpers ) 64 | 65 | write_basic_package_version_file( 66 | "${CMAKE_CURRENT_BINARY_DIR}/read-excel-config-version.cmake" 67 | VERSION ${EXCEL_VERSION} 68 | COMPATIBILITY AnyNewerVersion 69 | ) 70 | 71 | configure_package_config_file( read-excel-config.cmake.in 72 | "${CMAKE_CURRENT_BINARY_DIR}/read-excel-config.cmake" 73 | INSTALL_DESTINATION lib/cmake/read-excel ) 74 | 75 | install( FILES ${PROJECT_BINARY_DIR}/read-excel-config.cmake 76 | ${PROJECT_BINARY_DIR}/read-excel-config-version.cmake 77 | DESTINATION lib/cmake/read-excel 78 | ) 79 | else() 80 | 81 | set( read-excel_INCLUDE_DIRECTORIES ${CMAKE_CURRENT_SOURCE_DIR} PARENT_SCOPE ) 82 | 83 | endif( ${CMAKE_PROJECT_NAME} STREQUAL ${PROJECT_NAME} ) 84 | -------------------------------------------------------------------------------- /LICENSES/CC0-1.0.txt: -------------------------------------------------------------------------------- 1 | Creative Commons Legal Code 2 | 3 | CC0 1.0 Universal 4 | 5 | CREATIVE COMMONS CORPORATION IS NOT A LAW FIRM AND DOES NOT PROVIDE 6 | LEGAL SERVICES. DISTRIBUTION OF THIS DOCUMENT DOES NOT CREATE AN 7 | ATTORNEY-CLIENT RELATIONSHIP. CREATIVE COMMONS PROVIDES THIS 8 | INFORMATION ON AN "AS-IS" BASIS. CREATIVE COMMONS MAKES NO WARRANTIES 9 | REGARDING THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS 10 | PROVIDED HEREUNDER, AND DISCLAIMS LIABILITY FOR DAMAGES RESULTING FROM 11 | THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS PROVIDED 12 | HEREUNDER. 13 | 14 | Statement of Purpose 15 | 16 | The laws of most jurisdictions throughout the world automatically confer 17 | exclusive Copyright and Related Rights (defined below) upon the creator 18 | and subsequent owner(s) (each and all, an "owner") of an original work of 19 | authorship and/or a database (each, a "Work"). 20 | 21 | Certain owners wish to permanently relinquish those rights to a Work for 22 | the purpose of contributing to a commons of creative, cultural and 23 | scientific works ("Commons") that the public can reliably and without fear 24 | of later claims of infringement build upon, modify, incorporate in other 25 | works, reuse and redistribute as freely as possible in any form whatsoever 26 | and for any purposes, including without limitation commercial purposes. 27 | These owners may contribute to the Commons to promote the ideal of a free 28 | culture and the further production of creative, cultural and scientific 29 | works, or to gain reputation or greater distribution for their Work in 30 | part through the use and efforts of others. 31 | 32 | For these and/or other purposes and motivations, and without any 33 | expectation of additional consideration or compensation, the person 34 | associating CC0 with a Work (the "Affirmer"), to the extent that he or she 35 | is an owner of Copyright and Related Rights in the Work, voluntarily 36 | elects to apply CC0 to the Work and publicly distribute the Work under its 37 | terms, with knowledge of his or her Copyright and Related Rights in the 38 | Work and the meaning and intended legal effect of CC0 on those rights. 39 | 40 | 1. Copyright and Related Rights. A Work made available under CC0 may be 41 | protected by copyright and related or neighboring rights ("Copyright and 42 | Related Rights"). Copyright and Related Rights include, but are not 43 | limited to, the following: 44 | 45 | i. the right to reproduce, adapt, distribute, perform, display, 46 | communicate, and translate a Work; 47 | ii. moral rights retained by the original author(s) and/or performer(s); 48 | iii. publicity and privacy rights pertaining to a person's image or 49 | likeness depicted in a Work; 50 | iv. rights protecting against unfair competition in regards to a Work, 51 | subject to the limitations in paragraph 4(a), below; 52 | v. rights protecting the extraction, dissemination, use and reuse of data 53 | in a Work; 54 | vi. database rights (such as those arising under Directive 96/9/EC of the 55 | European Parliament and of the Council of 11 March 1996 on the legal 56 | protection of databases, and under any national implementation 57 | thereof, including any amended or successor version of such 58 | directive); and 59 | vii. other similar, equivalent or corresponding rights throughout the 60 | world based on applicable law or treaty, and any national 61 | implementations thereof. 62 | 63 | 2. Waiver. To the greatest extent permitted by, but not in contravention 64 | of, applicable law, Affirmer hereby overtly, fully, permanently, 65 | irrevocably and unconditionally waives, abandons, and surrenders all of 66 | Affirmer's Copyright and Related Rights and associated claims and causes 67 | of action, whether now known or unknown (including existing as well as 68 | future claims and causes of action), in the Work (i) in all territories 69 | worldwide, (ii) for the maximum duration provided by applicable law or 70 | treaty (including future time extensions), (iii) in any current or future 71 | medium and for any number of copies, and (iv) for any purpose whatsoever, 72 | including without limitation commercial, advertising or promotional 73 | purposes (the "Waiver"). Affirmer makes the Waiver for the benefit of each 74 | member of the public at large and to the detriment of Affirmer's heirs and 75 | successors, fully intending that such Waiver shall not be subject to 76 | revocation, rescission, cancellation, termination, or any other legal or 77 | equitable action to disrupt the quiet enjoyment of the Work by the public 78 | as contemplated by Affirmer's express Statement of Purpose. 79 | 80 | 3. Public License Fallback. Should any part of the Waiver for any reason 81 | be judged legally invalid or ineffective under applicable law, then the 82 | Waiver shall be preserved to the maximum extent permitted taking into 83 | account Affirmer's express Statement of Purpose. In addition, to the 84 | extent the Waiver is so judged Affirmer hereby grants to each affected 85 | person a royalty-free, non transferable, non sublicensable, non exclusive, 86 | irrevocable and unconditional license to exercise Affirmer's Copyright and 87 | Related Rights in the Work (i) in all territories worldwide, (ii) for the 88 | maximum duration provided by applicable law or treaty (including future 89 | time extensions), (iii) in any current or future medium and for any number 90 | of copies, and (iv) for any purpose whatsoever, including without 91 | limitation commercial, advertising or promotional purposes (the 92 | "License"). The License shall be deemed effective as of the date CC0 was 93 | applied by Affirmer to the Work. Should any part of the License for any 94 | reason be judged legally invalid or ineffective under applicable law, such 95 | partial invalidity or ineffectiveness shall not invalidate the remainder 96 | of the License, and in such case Affirmer hereby affirms that he or she 97 | will not (i) exercise any of his or her remaining Copyright and Related 98 | Rights in the Work or (ii) assert any associated claims and causes of 99 | action with respect to the Work, in either case contrary to Affirmer's 100 | express Statement of Purpose. 101 | 102 | 4. Limitations and Disclaimers. 103 | 104 | a. No trademark or patent rights held by Affirmer are waived, abandoned, 105 | surrendered, licensed or otherwise affected by this document. 106 | b. Affirmer offers the Work as-is and makes no representations or 107 | warranties of any kind concerning the Work, express, implied, 108 | statutory or otherwise, including without limitation warranties of 109 | title, merchantability, fitness for a particular purpose, non 110 | infringement, or the absence of latent or other defects, accuracy, or 111 | the present or absence of errors, whether or not discoverable, all to 112 | the greatest extent permissible under applicable law. 113 | c. Affirmer disclaims responsibility for clearing rights of other persons 114 | that may apply to the Work or any use thereof, including without 115 | limitation any person's Copyright and Related Rights in the Work. 116 | Further, Affirmer disclaims responsibility for obtaining any necessary 117 | consents, permissions or other rights required for any use of the 118 | Work. 119 | d. Affirmer understands and acknowledges that Creative Commons is not a 120 | party to this document and has no duty or obligation with respect to 121 | this CC0 or use of the Work. 122 | -------------------------------------------------------------------------------- /LICENSES/MIT.txt: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 6 | 7 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 8 | 9 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 10 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![License: MIT](https://img.shields.io/badge/license-MIT-blue.svg)](https://opensource.org/licenses/MIT) 2 | 3 | > [!IMPORTANT] 4 | > 5 | > This project is not supported anymore. But can be reopened if interest will 6 | > be in it. Please, let me know if you need something. Just write me email to 7 | > `igor.mironchik at gmail.com`. 8 | 9 | This is very simple implementation of the Excel 97-2003 format (BIFF8) written in C++. 10 | Supported reading only. 11 | 12 | Partially supported reading of BIFF7 standard (Excel 95). If in global will be set BIFF7, and in worksheets 13 | will be set BIFF8, as in `test/data/strange.xls`, then such file will be loaded. This feature is tested less 14 | of all, so if you will find an issue, please commit a new issue. For BIFF7 support implemented reading of 15 | `LABEL` records, cell with non-unicode string. I found difference between the documentation that I have and 16 | actual record in XLS file, so I implemented by experimenting with real file, that opens with Libre Office, 17 | MS Office and Google Sheets, so I believe that there is an issue in the documentation of `LABEL` record. 18 | 19 | Thanks for using this library. 20 | 21 | # Comparison 22 | 23 | I found on GitHub pure C [libxls](https://github.com/libxls/libxls) library with almost identical 24 | functionality. Dry numbers say that `test/complex` test with `read-excel` runs by 326 ms, 25 | whereas this test with `libxls` runs by 302 ms, what is almost identical. 26 | But C++ this is higher abstraction, that allows to use `read-excel` more developer 27 | friendly, and `read-excel` is cross-platform out of the box. 28 | 29 | # Example 30 | 31 | ```cpp 32 | try { 33 | Excel::Book book( "sample.xls" ); 34 | 35 | Excel::Sheet * sheet = book.sheet( 0 ); 36 | 37 | std::wcout << L"There is output of the \"sample.xls\" Excel file." 38 | << std::endl << std::endl; 39 | 40 | std::wcout << L"A1 : " << sheet->cell( 0, 0 ).getString() 41 | << std::endl; 42 | std::wcout << L"A2 : " << sheet->cell( 1, 0 ).getString() 43 | << L" B2 : " << sheet->cell( 1, 1 ).getDouble() << std::endl; 44 | std::wcout << L"A3 : " << sheet->cell( 2, 0 ).getString() 45 | << L" B3 : " << sheet->cell( 2, 1 ).getDouble() << std::endl; 46 | std::wcout << L"A4 : " << sheet->cell( 3, 0 ).getString() 47 | << L" B4 : " << sheet->cell( 3, 1 ).getFormula().getDouble() 48 | << std::endl; 49 | std::wcout << L"A5 : " << sheet->cell( 4, 0 ).getString() 50 | << std::endl << L"Date mode is : " 51 | << ( book.dateMode() == Excel::Book::DateMode::Dec31_1899 ? 52 | L"count of days since 31 December 1899 :" : 53 | L"count of days since 01 January 1904 :" ) 54 | << L" B5 : " << sheet->cell( 4, 1 ).getDouble() 55 | << " days." << std::endl; 56 | 57 | std::wcout << std::endl << L"Thats all. And thanks for using this library." 58 | << std::endl; 59 | } 60 | catch( const Excel::Exception & x ) 61 | { 62 | std::wcout << x.whatAsWString() << std::endl; 63 | } 64 | catch( const std::exception & ) 65 | { 66 | std::wcout << L"Can't open file." << std::endl; 67 | } 68 | ``` 69 | -------------------------------------------------------------------------------- /codecov.yml: -------------------------------------------------------------------------------- 1 | codecov: 2 | require_ci_to_pass: yes 3 | 4 | ignore: 5 | - "test/**/*" 6 | - "lib/**/*" 7 | - "sample/**/*" 8 | -------------------------------------------------------------------------------- /doc/Excel97-2007BinaryFileFormat(xls)Specification.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/igormironchik/read-excel/de3f7ebae0e47b1f2394d6a87bd219bc6a796298/doc/Excel97-2007BinaryFileFormat(xls)Specification.pdf -------------------------------------------------------------------------------- /doc/compdocfileformat.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/igormironchik/read-excel/de3f7ebae0e47b1f2394d6a87bd219bc6a796298/doc/compdocfileformat.pdf -------------------------------------------------------------------------------- /doc/excelfileformat.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/igormironchik/read-excel/de3f7ebae0e47b1f2394d6a87bd219bc6a796298/doc/excelfileformat.pdf -------------------------------------------------------------------------------- /read-excel-config.cmake.in: -------------------------------------------------------------------------------- 1 | @PACKAGE_INIT@ 2 | 3 | set( CMAKE_CXX_STANDARD 14 ) 4 | 5 | set( read-excel_INCLUDE_DIRECTORIES "@CMAKE_INSTALL_PREFIX@/include" ) 6 | 7 | include( "${CMAKE_CURRENT_LIST_DIR}/read-excel-targets.cmake" ) 8 | -------------------------------------------------------------------------------- /read-excel/bof.hpp: -------------------------------------------------------------------------------- 1 | 2 | /* 3 | SPDX-FileCopyrightText: 2011-2024 Igor Mironchik 4 | SPDX-License-Identifier: MIT 5 | */ 6 | 7 | #ifndef EXCEL__BOF_HPP__INCLUDED 8 | #define EXCEL__BOF_HPP__INCLUDED 9 | 10 | // C++ include. 11 | #include 12 | 13 | // Excel include. 14 | #include "record.hpp" 15 | 16 | 17 | namespace Excel { 18 | 19 | // 20 | // BOF 21 | // 22 | 23 | //! BOF record in the Excel file. 24 | class BOF { 25 | public: 26 | BOF(); 27 | 28 | //! Record's code for the BOF record. 29 | static const uint16_t RecordCode = 0x0809; 30 | 31 | //! BIFF version. 32 | enum BiffVersion { 33 | BIFF8 = 0x0600, 34 | BIFF7 = 0x0500, 35 | UnknownVersion = 0x0000 36 | }; // enum BiffVersion 37 | 38 | //! Types of the Excel's substream. 39 | enum SubstreamType { 40 | UnknownType = 0x0000, 41 | WorkBookGlobals = 0x0005, 42 | VisualBasicModule = 0x0006, 43 | WorkSheet = 0x0010, 44 | Chart = 0x0020, 45 | MacroSheet = 0x0040, 46 | WorkSpace = 0x0100 47 | }; // enum SubstreamType 48 | 49 | //! \return BIFF version. 50 | BiffVersion version() const; 51 | 52 | //! \return Substream type. 53 | SubstreamType type() const; 54 | 55 | //! Parse BOF record. 56 | void parse( Record & record ); 57 | 58 | private: 59 | //! BIFF version. 60 | BiffVersion m_version; 61 | //! Substream type. 62 | SubstreamType m_type; 63 | }; // class BOF 64 | 65 | inline 66 | BOF::BOF() 67 | : m_version( UnknownVersion ) 68 | , m_type( UnknownType ) 69 | { 70 | } 71 | 72 | inline BOF::BiffVersion 73 | BOF::version() const 74 | { 75 | return m_version; 76 | } 77 | 78 | inline BOF::SubstreamType 79 | BOF::type() const 80 | { 81 | return m_type; 82 | } 83 | 84 | inline void 85 | BOF::parse( Record & record ) 86 | { 87 | int16_t version = 0; 88 | int16_t type = 0; 89 | 90 | record.dataStream().read( version, 2 ); 91 | record.dataStream().read( type, 2 ); 92 | 93 | switch( version ) 94 | { 95 | case BIFF8 : 96 | m_version = BIFF8; 97 | break; 98 | 99 | case BIFF7 : 100 | m_version = BIFF7; 101 | break; 102 | 103 | default : 104 | m_version = UnknownVersion; 105 | break; 106 | } 107 | 108 | switch( type ) 109 | { 110 | case WorkBookGlobals : 111 | m_type = WorkBookGlobals; 112 | break; 113 | 114 | case VisualBasicModule : 115 | m_type = VisualBasicModule; 116 | break; 117 | 118 | case WorkSheet : 119 | m_type = WorkSheet; 120 | break; 121 | 122 | case Chart : 123 | m_type = Chart; 124 | break; 125 | 126 | case MacroSheet : 127 | m_type = MacroSheet; 128 | break; 129 | 130 | case WorkSpace : 131 | m_type = WorkSpace; 132 | break; 133 | 134 | default : 135 | m_type = UnknownType; 136 | break; 137 | } 138 | } 139 | 140 | } /* namespace Excel */ 141 | 142 | #endif // EXCEL__BOF_HPP__INCLUDED 143 | -------------------------------------------------------------------------------- /read-excel/book.hpp: -------------------------------------------------------------------------------- 1 | 2 | /* 3 | SPDX-FileCopyrightText: 2011-2024 Igor Mironchik 4 | SPDX-License-Identifier: MIT 5 | */ 6 | 7 | #ifndef EXCEL__BOOK_HPP__INCLUDED 8 | #define EXCEL__BOOK_HPP__INCLUDED 9 | 10 | // Excel include. 11 | #include "sheet.hpp" 12 | #include "record.hpp" 13 | #include "string.hpp" 14 | #include "bof.hpp" 15 | #include "exceptions.hpp" 16 | #include "stream.hpp" 17 | #include "parser.hpp" 18 | 19 | #include "compoundfile/compoundfile.hpp" 20 | #include "compoundfile/compoundfile_exceptions.hpp" 21 | 22 | // C++ include. 23 | #include 24 | #include 25 | #include 26 | #include 27 | 28 | 29 | namespace Excel { 30 | 31 | // 32 | // Book 33 | // 34 | 35 | //! Excel WorkBook. 36 | class Book : public IStorage { 37 | public: 38 | //! Mode of the day. The base date for displaying date values. 39 | //! All dates are stored as count of days past this base date. 40 | enum class DateMode { 41 | //! Unknown. 42 | Unknown = -1, 43 | //! Base date is 1899-Dec-31 (the date value 1 represents 1900-Jan-01) 44 | Dec31_1899 = 0, 45 | //! Base date is 1904-Jan-01 (the date value 1 represents 1904-Jan-02) 46 | Jan01_1904 = 1 47 | }; // enum DateMode 48 | 49 | public: 50 | Book(); 51 | explicit Book( std::istream & stream ); 52 | explicit Book( const std::string & fileName ); 53 | ~Book(); 54 | 55 | protected: 56 | void onSharedString( size_t sstSize, size_t idx, const std::wstring & value ) override; 57 | void onDateMode( uint16_t mode ) override; 58 | void onSheet( size_t idx, const std::wstring & name ) override; 59 | void onCellSharedString( size_t sheetIdx, size_t row, size_t column, size_t sstIndex ) override; 60 | void onCell( size_t sheetIdx, size_t row, size_t column, const std::wstring & value ) override; 61 | void onCell( size_t sheetIdx, size_t row, size_t column, double value ) override; 62 | void onCell( size_t sheetIdx, const Formula & value ) override; 63 | 64 | public: 65 | //! \return Date mode. 66 | DateMode dateMode() const; 67 | 68 | //! \return Count of the sheets. 69 | size_t sheetsCount() const; 70 | 71 | //! \return Sheet with given index, 72 | //! or NULL if there is no sheet with such index. 73 | Sheet * sheet( size_t index ) const; 74 | 75 | //! Clear book. 76 | void clear(); 77 | 78 | private: 79 | //! Parsed WorkSheets. 80 | std::vector< std::unique_ptr< Sheet > > m_sheets; 81 | //! Shared string table. 82 | std::vector< std::wstring > m_sst; 83 | //! Date mode. 84 | DateMode m_dateMode; 85 | }; // class Book 86 | 87 | inline 88 | Book::Book() 89 | : m_dateMode( DateMode::Unknown ) 90 | { 91 | } 92 | 93 | inline 94 | Book::Book( std::istream & stream ) 95 | : m_dateMode( DateMode::Unknown ) 96 | { 97 | Parser::loadBook( stream, *this ); 98 | } 99 | 100 | inline 101 | Book::Book( const std::string & fileName ) 102 | : m_dateMode( DateMode::Unknown ) 103 | { 104 | std::ifstream fileStream( fileName, std::ios::in | std::ios::binary ); 105 | Parser::loadBook( fileStream, *this, fileName ); 106 | } 107 | 108 | inline void 109 | Book::clear() 110 | { 111 | m_sheets.clear(); 112 | m_sst.clear(); 113 | } 114 | 115 | inline 116 | Book::~Book() 117 | { 118 | } 119 | 120 | inline Book::DateMode 121 | Book::dateMode() const 122 | { 123 | return m_dateMode; 124 | } 125 | 126 | inline void 127 | Book::onSharedString( size_t sstSize, size_t idx, const std::wstring & value ) 128 | { 129 | m_sst.resize( sstSize ); 130 | m_sst[ idx ] = value; 131 | } 132 | 133 | inline void 134 | Book::onDateMode( uint16_t mode ) 135 | { 136 | if( mode ) 137 | m_dateMode = DateMode::Jan01_1904; 138 | else 139 | m_dateMode = DateMode::Dec31_1899; 140 | } 141 | 142 | inline void 143 | Book::onSheet( size_t idx, const std::wstring & name ) 144 | { 145 | auto sheet = std::make_unique< Sheet > ( name ); 146 | if( m_sheets.size() <= idx ) 147 | m_sheets.resize( idx + 1 ); 148 | m_sheets[ idx ] = std::move( sheet ); 149 | } 150 | 151 | inline void 152 | Book::onCellSharedString( size_t sheetIdx, size_t row, size_t column, size_t sstIndex ) 153 | { 154 | onCell( sheetIdx, row, column, m_sst[ sstIndex ] ); 155 | } 156 | 157 | inline void 158 | Book::onCell( size_t sheetIdx, size_t row, size_t column, const std::wstring & value ) 159 | { 160 | sheet( sheetIdx )->setCell( row, column, value ); 161 | } 162 | 163 | inline void 164 | Book::onCell( size_t sheetIdx, size_t row, size_t column, double value ) 165 | { 166 | sheet( sheetIdx )->setCell( row, column, value ); 167 | } 168 | 169 | inline void 170 | Book::onCell( size_t sheetIdx, const Formula & formula ) 171 | { 172 | sheet( sheetIdx )->setCell( formula.getRow(), formula.getColumn(), formula ); 173 | } 174 | 175 | inline size_t 176 | Book::sheetsCount() const 177 | { 178 | return m_sheets.size(); 179 | } 180 | 181 | inline Sheet * 182 | Book::sheet( size_t index ) const 183 | { 184 | if( index < m_sheets.size() ) 185 | return m_sheets[ index ].get(); 186 | 187 | std::wstringstream stream; 188 | stream << L"There is no such sheet with index : " << index; 189 | 190 | throw Exception( stream.str() ); 191 | } 192 | 193 | } /* namespace Excel */ 194 | 195 | #endif // EXCEL__BOOK_HPP__INCLUDED 196 | -------------------------------------------------------------------------------- /read-excel/cell.hpp: -------------------------------------------------------------------------------- 1 | 2 | /* 3 | SPDX-FileCopyrightText: 2011-2024 Igor Mironchik 4 | SPDX-License-Identifier: MIT 5 | */ 6 | 7 | #ifndef EXCEL__CELL_HPP__INCLUDED 8 | #define EXCEL__CELL_HPP__INCLUDED 9 | 10 | // Excel include. 11 | #include "formula.hpp" 12 | 13 | // C++ include 14 | #include 15 | #include 16 | 17 | 18 | namespace Excel { 19 | 20 | // 21 | // Cell 22 | // 23 | 24 | //! Excel's cell. 25 | class Cell { 26 | public: 27 | //! Type of the data. 28 | enum class DataType { 29 | //! Unknown. 30 | Unknown, 31 | //! String. 32 | String, 33 | //! Double. 34 | Double, 35 | //! Formula. 36 | Formula 37 | }; // enum class DataType 38 | 39 | Cell(); 40 | 41 | //! \return Data type. 42 | DataType dataType() const; 43 | 44 | //! \return String data in the cell. 45 | const std::wstring & getString() const; 46 | 47 | //! \return Double data in the cell. 48 | const double & getDouble() const; 49 | 50 | //! \return Formula. 51 | const Formula & getFormula() const; 52 | 53 | //! Set data. 54 | void setData( const std::wstring & d ); 55 | 56 | //! Set data. 57 | void setData( const double & d ); 58 | 59 | //! Set data. 60 | void setData( const Formula & f ); 61 | 62 | //! \return true if there is no data. 63 | bool isNull() const; 64 | 65 | private: 66 | //! Cell's string data. 67 | std::wstring m_stringData; 68 | //! Cell's double data. 69 | double m_doubleData; 70 | //! Cell's formula. 71 | Formula m_formula; 72 | //! Is data set. 73 | bool m_isNull; 74 | //! Type of the data. 75 | DataType m_type; 76 | }; // class Cell 77 | 78 | inline 79 | Cell::Cell() 80 | : m_doubleData( 0.0 ) 81 | , m_isNull( true ) 82 | , m_type( DataType::Unknown ) 83 | { 84 | } 85 | 86 | inline Cell::DataType 87 | Cell::dataType() const 88 | { 89 | return m_type; 90 | } 91 | 92 | inline const std::wstring & 93 | Cell::getString() const 94 | { 95 | return m_stringData; 96 | } 97 | 98 | inline const double & 99 | Cell::getDouble() const 100 | { 101 | return m_doubleData; 102 | } 103 | 104 | inline const Formula & 105 | Cell::getFormula() const 106 | { 107 | return m_formula; 108 | } 109 | 110 | inline void 111 | Cell::setData( const std::wstring & d ) 112 | { 113 | m_stringData = d; 114 | m_isNull = false; 115 | m_type = DataType::String; 116 | } 117 | 118 | inline void 119 | Cell::setData( const double & d ) 120 | { 121 | m_doubleData = d; 122 | m_isNull = false; 123 | m_type = DataType::Double; 124 | } 125 | 126 | inline void 127 | Cell::setData( const Formula & f ) 128 | { 129 | m_formula = f; 130 | m_isNull = false; 131 | m_type = DataType::Formula; 132 | } 133 | 134 | inline bool 135 | Cell::isNull() const 136 | { 137 | return m_isNull; 138 | } 139 | 140 | } /* namespace Excel */ 141 | 142 | #endif // EXCEL__CELL_HPP__INCLUDED 143 | -------------------------------------------------------------------------------- /read-excel/compoundfile/compoundfile.hpp: -------------------------------------------------------------------------------- 1 | 2 | /* 3 | SPDX-FileCopyrightText: 2011-2024 Igor Mironchik 4 | SPDX-License-Identifier: MIT 5 | */ 6 | 7 | #ifndef COMPOUNDFILE__COMPOUNDFILE_HPP__INCLUDED 8 | #define COMPOUNDFILE__COMPOUNDFILE_HPP__INCLUDED 9 | 10 | // CompoundFile include. 11 | #include "compoundfile_stream_and_dir.hpp" 12 | #include "header.hpp" 13 | #include "sat.hpp" 14 | #include "msat.hpp" 15 | #include "utils.hpp" 16 | #include "compoundfile_exceptions.hpp" 17 | 18 | // C++ include. 19 | #include 20 | #include 21 | #include 22 | #include 23 | 24 | 25 | namespace CompoundFile { 26 | 27 | // 28 | // File 29 | // 30 | 31 | //! Compound file. 32 | class File { 33 | public: 34 | explicit File( std::istream & stream, const std::string & fileName = "" ); 35 | explicit File( const std::string & fileName ); 36 | ~File(); 37 | 38 | //! \return Directory entry by its name. 39 | Directory directory( const std::wstring & name ) const; 40 | 41 | //! \return is Directory entry exist by its name. 42 | bool hasDirectory( const std::wstring & name ) const; 43 | 44 | //! \return Stream in the directory. 45 | std::unique_ptr< Excel::Stream > stream( const Directory & dir ); 46 | 47 | private: 48 | //! Read stream and initialize m_dirs. 49 | void initialize( const std::string& fileName ); 50 | 51 | private: 52 | //! Inner file stream. 53 | std::ifstream m_fileStream; 54 | //! Stream. 55 | std::istream & m_stream; 56 | //! Header of the compound file. 57 | Header m_header; 58 | //! SAT. 59 | SAT m_sat; 60 | //! SSAT. 61 | SAT m_ssat; 62 | //! SecID of the first sector of the short-sector stream. 63 | SecID m_shortStreamFirstSector; 64 | //! All directories defined in the compound file. 65 | std::map< int32_t, Directory > m_dirs; 66 | }; // class File 67 | 68 | 69 | // 70 | // loadSSAT 71 | // 72 | 73 | inline SAT 74 | loadSSAT( const Header & header, std::istream & stream, 75 | const SAT & sat ) 76 | { 77 | std::vector< SecID > ssat; 78 | 79 | if( header.ssatFirstSecID() != SecID::EndOfChain ) 80 | { 81 | std::vector< SecID > chain = sat.sectors( header.ssatFirstSecID() ); 82 | 83 | for( std::vector< SecID >::const_iterator it = chain.begin(), 84 | last = chain.end(); it != last; ++it ) 85 | { 86 | stream.seekg( calcFileOffset( *it, header.sectorSize() ) ); 87 | 88 | loadSATSector( stream, ssat, header.sectorSize() ); 89 | } 90 | } 91 | 92 | return SAT( ssat ); 93 | } // loadSSAT 94 | 95 | 96 | //! Size of the dir record. 97 | static const int32_t dirRecordSize = 128; 98 | 99 | 100 | // 101 | // loadChildDir 102 | // 103 | 104 | inline Directory * 105 | loadChildDir( std::map< int32_t, Directory > & dirs, 106 | int32_t dirID, Stream & stream ) 107 | { 108 | if( dirID != -1 && dirs.count(dirID) == 0 ) 109 | { 110 | stream.seek( dirID * dirRecordSize, Stream::FromBeginning ); 111 | 112 | Directory dir; 113 | dir.load( stream ); 114 | 115 | dirs[ dirID ] = dir; 116 | 117 | return &dirs[ dirID ]; 118 | } 119 | 120 | return nullptr; 121 | } // loadChildDir 122 | 123 | 124 | // 125 | // loadChildDirectories 126 | // 127 | 128 | inline void 129 | loadChildDirectories( std::map< int32_t, Directory > & dirs, 130 | const Directory parentDir, Stream & stream ) 131 | { 132 | Directory * childDir = nullptr; 133 | if ( ( childDir = loadChildDir( dirs, parentDir.leftChild(), stream ) ) ) 134 | loadChildDirectories( dirs, *childDir, stream ); 135 | if ( ( childDir = loadChildDir( dirs, parentDir.rightChild(), stream ) ) ) 136 | loadChildDirectories( dirs, *childDir, stream ); 137 | } // loadChildDirectories 138 | 139 | 140 | // 141 | // File 142 | // 143 | 144 | inline 145 | File::File( std::istream & stream, const std::string & fileName ) 146 | : m_stream( stream ) 147 | { 148 | initialize( fileName ); 149 | } 150 | 151 | inline 152 | File::File( const std::string & fileName ) 153 | : m_fileStream( fileName, std::ios::in | std::ios::binary ) 154 | , m_stream( m_fileStream ) 155 | 156 | { 157 | initialize( fileName ); 158 | } 159 | 160 | inline 161 | File::~File() 162 | { 163 | m_fileStream.close(); 164 | } 165 | 166 | inline Directory 167 | File::directory( const std::wstring & name ) const 168 | { 169 | for( std::map< int32_t, Directory >::const_iterator it = m_dirs.begin(), 170 | last = m_dirs.end(); it != last; ++it ) 171 | { 172 | if( it->second.name() == name ) 173 | return it->second; 174 | } 175 | 176 | throw Exception( std::wstring( L"There is no such directory : " ) + name ); 177 | } 178 | 179 | inline bool 180 | File::hasDirectory( const std::wstring & name ) const 181 | { 182 | for( std::map< int32_t, Directory >::const_iterator it = m_dirs.begin(), 183 | last = m_dirs.end(); it != last; ++it ) 184 | { 185 | if ( it->second.name() == name ) 186 | return true; 187 | } 188 | 189 | return false; 190 | } 191 | 192 | inline std::unique_ptr< Excel::Stream > 193 | File::stream( const Directory & dir ) 194 | { 195 | return std::make_unique< CompoundFile::Stream > ( m_header, 196 | m_sat, m_ssat, dir, m_shortStreamFirstSector, m_stream ); 197 | } 198 | 199 | inline void 200 | File::initialize( const std::string & fileName ) 201 | { 202 | if( m_stream.good() ) 203 | { 204 | m_header.load( m_stream ); 205 | 206 | MSAT msat( m_header, m_stream ); 207 | m_sat = msat.buildSAT(); 208 | 209 | m_ssat = loadSSAT( m_header, m_stream, m_sat ); 210 | 211 | Stream stream( m_header, m_sat, m_header.dirStreamSecID(), m_stream ); 212 | 213 | Directory root; 214 | root.load( stream ); 215 | 216 | m_shortStreamFirstSector = root.streamSecID(); 217 | 218 | stream.seek( root.rootNode() * dirRecordSize, Stream::FromBeginning ); 219 | 220 | Directory rootEntry; 221 | rootEntry.load( stream ); 222 | 223 | m_dirs[ root.rootNode() ] = rootEntry; 224 | 225 | loadChildDirectories( m_dirs, rootEntry, stream ); 226 | } 227 | else 228 | throw Exception( std::wstring( L"Unable to open file : " ) + 229 | std::wstring( fileName.cbegin(), fileName.cend() ) ); 230 | } 231 | 232 | } /* namespace CompoundFile */ 233 | 234 | #endif // COMPOUNDFILE__COMPOUNDFILE_HPP__INCLUDED 235 | -------------------------------------------------------------------------------- /read-excel/compoundfile/compoundfile_exceptions.hpp: -------------------------------------------------------------------------------- 1 | 2 | /* 3 | SPDX-FileCopyrightText: 2011-2024 Igor Mironchik 4 | SPDX-License-Identifier: MIT 5 | */ 6 | 7 | #ifndef COMPOUNDFILE__EXCEPTIONS_HPP__INCLUDED 8 | #define COMPOUNDFILE__EXCEPTIONS_HPP__INCLUDED 9 | 10 | 11 | // C++ include. 12 | #include 13 | #include 14 | 15 | 16 | namespace CompoundFile { 17 | 18 | // 19 | // Exception 20 | // 21 | 22 | //! Base exception in the compound file. 23 | class Exception 24 | : public std::runtime_error 25 | { 26 | public: 27 | explicit Exception( const std::wstring & what ); 28 | virtual ~Exception() noexcept; 29 | 30 | //! \return Reason of the exception. 31 | const std::wstring & whatAsWString() const noexcept; 32 | 33 | private: 34 | //! Reason of the exception. 35 | std::wstring m_what; 36 | }; // class Exception 37 | 38 | inline 39 | Exception::Exception( const std::wstring & what ) 40 | : std::runtime_error( "Use whatAsWString() method." ) 41 | , m_what( what ) 42 | { 43 | } 44 | 45 | inline 46 | Exception::~Exception() noexcept 47 | { 48 | } 49 | 50 | inline const std::wstring & 51 | Exception::whatAsWString() const noexcept 52 | { 53 | return m_what; 54 | } 55 | 56 | } /* namespace CompoundFile */ 57 | 58 | #endif // COMPOUNDFILE__EXCEPTIONS_HPP__INCLUDED 59 | -------------------------------------------------------------------------------- /read-excel/compoundfile/compoundfile_stream_and_dir.hpp: -------------------------------------------------------------------------------- 1 | 2 | /* 3 | SPDX-FileCopyrightText: 2011-2024 Igor Mironchik 4 | SPDX-License-Identifier: MIT 5 | */ 6 | 7 | #ifndef COMPOUNDFILE__STREAM_HPP__INCLUDED 8 | #define COMPOUNDFILE__STREAM_HPP__INCLUDED 9 | 10 | // CompoundFile include. 11 | #include "header.hpp" 12 | #include "sat.hpp" 13 | #include "utils.hpp" 14 | 15 | // Excel include. 16 | #include "../stream.hpp" 17 | 18 | // C++ include. 19 | #include 20 | #include 21 | 22 | 23 | namespace CompoundFile { 24 | 25 | class Stream; 26 | 27 | 28 | // 29 | // Directory 30 | // 31 | 32 | //! Directory in a compound file. 33 | class Directory { 34 | public: 35 | Directory(); 36 | 37 | //! Type of the entry. 38 | enum Type { 39 | //! Empty. 40 | Empty = 0x00, 41 | //! User storage. 42 | UserStorage = 0x01, 43 | //! User stream. 44 | UserStream = 0x02, 45 | //! LockBytes. 46 | LockBytes = 0x03, 47 | //! Property. 48 | Property = 0x04, 49 | //! Root storage. 50 | RootStorage = 0x05 51 | }; // enum Type 52 | 53 | //! \return Name of the directory. 54 | const std::wstring & name() const; 55 | 56 | //! \return Type of the directory. 57 | Type type() const; 58 | 59 | //! \return SecID of the first sector. 60 | SecID streamSecID() const; 61 | 62 | //! \return Stream size. 63 | int32_t streamSize() const; 64 | 65 | //! \return DirID of the right child node. 66 | int32_t rightChild() const; 67 | 68 | //! \return DirID of the left child node. 69 | int32_t leftChild() const; 70 | 71 | //! \return DirID of the root node. 72 | int32_t rootNode() const; 73 | 74 | //! Load directory. 75 | void load( Stream & stream ); 76 | 77 | private: 78 | //! Name of the directory. 79 | std::wstring m_name; 80 | //! Type of the directory. 81 | Type m_type; 82 | //! SecID of the first sector. 83 | SecID m_secID; 84 | //! Stream size. 85 | int32_t m_streamSize; 86 | //! DirID of the right child node. 87 | int32_t m_rightChild; 88 | //! DirID of the left child node. 89 | int32_t m_leftChild; 90 | //! DirID of the root node. 91 | int32_t m_rootNode; 92 | }; // class Directory 93 | 94 | 95 | // 96 | // Stream 97 | // 98 | 99 | //! Stream in a compound file. 100 | class Stream 101 | : public Excel::Stream 102 | { 103 | 104 | friend class File; 105 | 106 | protected: 107 | Stream( const Header & header, 108 | const SAT & sat, 109 | const SecID & secID, 110 | std::istream & stream ); 111 | 112 | public: 113 | Stream( const Header & header, 114 | const SAT & sat, 115 | const SAT & ssat, 116 | const Directory & dir, 117 | const SecID & shortStreamFirstSector, 118 | std::istream & cstream ); 119 | 120 | //! Read one byte from the stream. 121 | char getByte() override; 122 | 123 | //! \return true if EOF reached. 124 | bool eof() const override; 125 | 126 | //! Seek stream to new position. 127 | void seek( int32_t pos, SeekType type = FromBeginning ) override; 128 | 129 | //! \return Position in the stream. 130 | int32_t pos() override; 131 | 132 | private: 133 | //! Seek internal stream to the next sector. 134 | void seekToNextSector(); 135 | //! \return Offset in sectors from the beginning of the large 136 | //! stream sector. 137 | int32_t whereIsShortSector( const SecID & shortSector, 138 | SecID & largeSector ); 139 | 140 | private: 141 | //! Header. 142 | const Header & m_header; 143 | //! Large stream sectors chain. 144 | std::vector< SecID > m_largeStreamChain; 145 | //! Short stream sectors chain. 146 | std::vector< SecID > m_shortStreamChain; 147 | //! File's stream. 148 | std::istream & m_stream; 149 | 150 | //! Mode of the stream. 151 | enum Mode { 152 | //! Large stream mode. 153 | LargeStream, 154 | //! Short stream mode. 155 | ShortStream 156 | }; // enum Mode 157 | 158 | //! Mode of the stream. 159 | Mode m_mode; 160 | //! Bytes readed from the stream. 161 | int32_t m_bytesReaded; 162 | //! Size of the sector for this stream. 163 | int32_t m_sectorSize; 164 | //! Bytes readed from the current sector. 165 | int32_t m_sectorBytesReaded; 166 | //! Current SecID in the short stream. 167 | int32_t m_shortSecIDIdx; 168 | //! Current SecID in the large stream. 169 | int32_t m_largeSecIDIdx; 170 | //! Size of the stream. 171 | int32_t m_streamSize; 172 | 173 | //! Buffer. 174 | std::vector< char > m_buf; 175 | //! Posiztion in buffer. 176 | int32_t m_pos; 177 | //! Current large sector ID. 178 | SecID m_currentLargeSectorID; 179 | }; // class Stream 180 | 181 | inline 182 | Stream::Stream( const Header & header, 183 | const SAT & sat, 184 | const SecID & secID, 185 | std::istream & stream ) 186 | : Excel::Stream( header.byteOrder() ) 187 | , m_header( header ) 188 | , m_stream( stream ) 189 | , m_mode( LargeStream ) 190 | , m_bytesReaded( 0 ) 191 | , m_sectorSize( m_header.sectorSize() ) 192 | , m_sectorBytesReaded( 0 ) 193 | , m_shortSecIDIdx( 0 ) 194 | , m_largeSecIDIdx( 0 ) 195 | , m_streamSize( 0 ) 196 | , m_pos( 0 ) 197 | { 198 | m_largeStreamChain = sat.sectors( secID ); 199 | 200 | m_stream.seekg( calcFileOffset( m_largeStreamChain.front(), 201 | m_sectorSize ) ); 202 | 203 | m_currentLargeSectorID = m_largeStreamChain.front(); 204 | 205 | m_streamSize = static_cast< int32_t > ( m_largeStreamChain.size() ) * m_sectorSize; 206 | 207 | m_buf.resize( m_sectorSize ); 208 | 209 | m_stream.read( &m_buf[ 0 ], m_sectorSize ); 210 | } 211 | 212 | inline 213 | Stream::Stream( const Header & header, 214 | const SAT & sat, 215 | const SAT & ssat, 216 | const Directory & dir, 217 | const SecID & shortStreamFirstSector, 218 | std::istream & cstream ) 219 | : Excel::Stream( header.byteOrder() ) 220 | , m_header( header ) 221 | , m_stream( cstream ) 222 | , m_mode( 223 | ( dir.streamSize() < m_header.streamMinSize() ? ShortStream : LargeStream ) ) 224 | , m_bytesReaded( 0 ) 225 | , m_sectorSize( 226 | ( dir.streamSize() < m_header.streamMinSize() ? 227 | m_header.shortSectorSize() : m_header.sectorSize() ) ) 228 | , m_sectorBytesReaded( 0 ) 229 | , m_shortSecIDIdx( 0 ) 230 | , m_largeSecIDIdx( 0 ) 231 | , m_streamSize( dir.streamSize() ) 232 | , m_pos( 0 ) 233 | { 234 | m_buf.resize( m_header.sectorSize() ); 235 | 236 | if( m_mode == LargeStream ) 237 | { 238 | m_largeStreamChain = sat.sectors( dir.streamSecID() ); 239 | m_stream.seekg( calcFileOffset( m_largeStreamChain.front(), 240 | m_header.sectorSize() ) ); 241 | m_currentLargeSectorID = m_largeStreamChain.front(); 242 | 243 | m_stream.read( &m_buf[ 0 ], m_sectorSize ); 244 | } 245 | else 246 | { 247 | m_largeStreamChain = sat.sectors( shortStreamFirstSector ); 248 | m_shortStreamChain = ssat.sectors( dir.streamSecID() ); 249 | 250 | SecID largeSector; 251 | const int32_t offset = whereIsShortSector( dir.streamSecID(), 252 | largeSector ); 253 | 254 | m_currentLargeSectorID = largeSector; 255 | 256 | std::streampos pos = calcFileOffset( largeSector, 257 | m_header.sectorSize() ); 258 | 259 | m_stream.seekg( pos ); 260 | 261 | m_stream.read( &m_buf[ 0 ], m_header.sectorSize() ); 262 | 263 | m_pos = offset * m_header.shortSectorSize(); 264 | } 265 | } 266 | 267 | inline bool 268 | Stream::eof() const 269 | { 270 | return ( m_bytesReaded > m_streamSize ); 271 | } 272 | 273 | inline void 274 | Stream::seek( int32_t pos, SeekType type ) 275 | { 276 | if( type == FromCurrent ) 277 | { 278 | pos += m_bytesReaded; 279 | 280 | if( pos < 0 ) 281 | pos += m_streamSize; 282 | } 283 | else if( type == FromEnd && pos > 0 ) 284 | pos = m_streamSize - pos; 285 | else if( type == FromEnd && pos < 0 ) 286 | pos = std::abs( pos ); 287 | else if( type == FromBeginning && pos < 0 ) 288 | pos = m_streamSize - pos; 289 | 290 | if( pos >= m_streamSize ) 291 | { 292 | m_bytesReaded = m_streamSize; 293 | return; 294 | } 295 | 296 | const int32_t offset = pos % m_sectorSize; 297 | const int32_t sectorIdx = pos / m_sectorSize; 298 | 299 | m_bytesReaded = pos; 300 | m_sectorBytesReaded = offset; 301 | 302 | if( m_mode == LargeStream ) 303 | { 304 | m_largeSecIDIdx = sectorIdx; 305 | 306 | if( m_currentLargeSectorID != m_largeStreamChain.at( m_largeSecIDIdx ) ) 307 | { 308 | m_currentLargeSectorID = m_largeStreamChain.at( m_largeSecIDIdx ); 309 | 310 | m_stream.seekg( calcFileOffset( m_largeStreamChain.at( m_largeSecIDIdx ), 311 | m_sectorSize ), std::ios::beg ); 312 | 313 | m_stream.read( &m_buf[ 0 ], m_sectorSize ); 314 | } 315 | 316 | m_pos = offset; 317 | } 318 | else 319 | { 320 | const SecID shortSector = m_shortStreamChain.at( sectorIdx ); 321 | 322 | SecID largeSector; 323 | const int32_t offsetInLargeSector = whereIsShortSector( shortSector, 324 | largeSector ); 325 | 326 | m_shortSecIDIdx = sectorIdx; 327 | 328 | if( m_currentLargeSectorID != largeSector ) 329 | { 330 | m_currentLargeSectorID = largeSector; 331 | 332 | std::streampos pos = calcFileOffset( largeSector, 333 | m_header.sectorSize() ); 334 | 335 | m_stream.seekg( pos, std::ios::beg ); 336 | 337 | m_stream.read( &m_buf[ 0 ], m_header.sectorSize() ); 338 | } 339 | 340 | m_pos = offsetInLargeSector * m_header.shortSectorSize(); 341 | m_pos += offset; 342 | } 343 | } 344 | 345 | inline int32_t 346 | Stream::pos() 347 | { 348 | if( eof() ) 349 | return -1; 350 | else 351 | return m_bytesReaded; 352 | } 353 | 354 | inline int32_t 355 | Stream::whereIsShortSector( const SecID & shortSector, 356 | SecID & largeSector ) 357 | { 358 | const int32_t shortSectorsInLarge = 359 | m_header.sectorSize() / m_header.shortSectorSize(); 360 | 361 | const int32_t offset = shortSector % shortSectorsInLarge; 362 | 363 | const int32_t largeSectorIdx = shortSector / shortSectorsInLarge; 364 | 365 | largeSector = m_largeStreamChain.at( largeSectorIdx ); 366 | 367 | return offset; 368 | } 369 | 370 | inline void 371 | Stream::seekToNextSector() 372 | { 373 | if( m_mode == LargeStream ) 374 | { 375 | ++m_largeSecIDIdx; 376 | 377 | m_stream.seekg( calcFileOffset( m_largeStreamChain.at( m_largeSecIDIdx ), 378 | m_sectorSize ), std::ios::beg ); 379 | 380 | m_currentLargeSectorID = m_largeStreamChain.at( m_largeSecIDIdx ); 381 | 382 | m_stream.read( &m_buf[ 0 ], m_sectorSize ); 383 | 384 | m_pos = 0; 385 | } 386 | else 387 | { 388 | ++m_shortSecIDIdx; 389 | 390 | SecID largeSector; 391 | const int32_t offset = 392 | whereIsShortSector( m_shortStreamChain.at( m_shortSecIDIdx ), 393 | largeSector ); 394 | 395 | if( m_currentLargeSectorID != largeSector ) 396 | { 397 | m_currentLargeSectorID = largeSector; 398 | 399 | std::streampos pos = calcFileOffset( largeSector, 400 | m_header.sectorSize() ); 401 | 402 | m_stream.seekg( pos, std::ios::beg ); 403 | 404 | m_stream.read( &m_buf[ 0 ], m_header.sectorSize() ); 405 | } 406 | 407 | m_pos = offset * m_header.shortSectorSize(); 408 | } 409 | } 410 | 411 | inline char 412 | Stream::getByte() 413 | { 414 | if( m_bytesReaded > m_streamSize ) 415 | return 0xFFu; 416 | 417 | if( m_sectorBytesReaded == m_sectorSize ) 418 | { 419 | m_sectorBytesReaded = 0; 420 | 421 | seekToNextSector(); 422 | } 423 | 424 | const auto ch = m_buf.at( m_pos ); 425 | 426 | ++m_sectorBytesReaded; 427 | ++m_bytesReaded; 428 | ++m_pos; 429 | 430 | return ch; 431 | } 432 | 433 | 434 | // 435 | // Directory 436 | // 437 | 438 | inline 439 | Directory::Directory() 440 | : m_type( Empty ) 441 | , m_secID( SecID::EndOfChain ) 442 | , m_streamSize( 0 ) 443 | , m_rightChild( -1 ) 444 | , m_leftChild( -1 ) 445 | , m_rootNode( -1 ) 446 | { 447 | } 448 | 449 | inline const std::wstring & 450 | Directory::name() const 451 | { 452 | return m_name; 453 | } 454 | 455 | inline Directory::Type 456 | Directory::type() const 457 | { 458 | return m_type; 459 | } 460 | 461 | inline SecID 462 | Directory::streamSecID() const 463 | { 464 | return m_secID; 465 | } 466 | 467 | inline int32_t 468 | Directory::streamSize() const 469 | { 470 | return m_streamSize; 471 | } 472 | 473 | inline int32_t 474 | Directory::rightChild() const 475 | { 476 | return m_rightChild; 477 | } 478 | 479 | inline int32_t 480 | Directory::leftChild() const 481 | { 482 | return m_leftChild; 483 | } 484 | 485 | inline int32_t 486 | Directory::rootNode() const 487 | { 488 | return m_rootNode; 489 | } 490 | 491 | inline void 492 | Directory::load( Stream & stream ) 493 | { 494 | { 495 | const int16_t maxSymbolsInName = 32; 496 | 497 | int16_t i = 1; 498 | 499 | std::vector< uint16_t > data; 500 | data.reserve( maxSymbolsInName - 1 ); 501 | 502 | for( ; i <= maxSymbolsInName; ++i ) 503 | { 504 | uint16_t symbol = 0; 505 | stream.read( symbol, 2 ); 506 | 507 | if( symbol != 0 ) 508 | data.push_back( symbol ); 509 | else 510 | break; 511 | } 512 | 513 | m_name.assign( data.begin(), data.end() ); 514 | 515 | if( i < maxSymbolsInName ) 516 | stream.seek( ( maxSymbolsInName - i ) * 2, Stream::FromCurrent ); 517 | } 518 | 519 | stream.seek( 2, Stream::FromCurrent ); 520 | m_type = (Type)(int32_t) stream.getByte(); 521 | 522 | if( stream.eof() ) 523 | throw Exception( L"Unexpected end of file." ); 524 | 525 | stream.seek( 1, Stream::FromCurrent ); 526 | stream.read( m_leftChild, 4 ); 527 | stream.read( m_rightChild, 4 ); 528 | stream.read( m_rootNode, 4 ); 529 | stream.seek( 36, Stream::FromCurrent ); 530 | 531 | { 532 | int32_t sec = 0; 533 | stream.read( sec, 4 ); 534 | m_secID = sec; 535 | } 536 | 537 | stream.read( m_streamSize, 4 ); 538 | stream.seek( 4, Stream::FromCurrent ); 539 | } 540 | 541 | } /* namespace CompoundFile */ 542 | 543 | #endif // COMPOUNDFILE__STREAM_HPP__INCLUDED 544 | -------------------------------------------------------------------------------- /read-excel/compoundfile/header.hpp: -------------------------------------------------------------------------------- 1 | 2 | /* 3 | SPDX-FileCopyrightText: 2011-2024 Igor Mironchik 4 | SPDX-License-Identifier: MIT 5 | */ 6 | 7 | #ifndef COMPOUNDFILE__HEADER_HPP__INCLUDED 8 | #define COMPOUNDFILE__HEADER_HPP__INCLUDED 9 | 10 | // CompoundFile include. 11 | #include "sat.hpp" 12 | #include "compoundfile_exceptions.hpp" 13 | #include "utils.hpp" 14 | 15 | // Excel include. 16 | #include "../stream.hpp" 17 | 18 | // C++ include. 19 | #include 20 | #include 21 | 22 | 23 | namespace CompoundFile { 24 | 25 | // 26 | // Header 27 | // 28 | 29 | //! Header of the compound file. 30 | class Header { 31 | 32 | friend class File; 33 | 34 | public: 35 | Header(); 36 | Header( std::istream & stream ); 37 | 38 | //! \return Byte order in the file. 39 | Excel::Stream::ByteOrder byteOrder() const; 40 | 41 | //! \return Size of a sector. 42 | int32_t sectorSize() const; 43 | 44 | //! \return Size of a short-sector. 45 | int32_t shortSectorSize() const; 46 | 47 | //! \return Total number of sectors used for the sector allocation table. 48 | int32_t sectorsInSAT() const; 49 | 50 | //! \return SecID of first sector of the directory stream. 51 | SecID dirStreamSecID() const; 52 | 53 | //! \return Minimum size of a standard stream. 54 | int32_t streamMinSize() const; 55 | 56 | //! \return SecID of first sector of the short-sector allocation table. 57 | SecID ssatFirstSecID() const; 58 | 59 | //! \return Total number of sectors used for the short-sector allocation table. 60 | int32_t sectorsInSSAT() const; 61 | 62 | //! \return SecID of first sector of the master sector allocation table. 63 | SecID msatFirstSecID() const; 64 | 65 | //! \return Total number of sectors used for the master sector allocation table. 66 | int32_t sectorsInMSAT() const; 67 | 68 | protected: 69 | //! Load header from the file. 70 | void load( std::istream & stream ); 71 | 72 | private: 73 | //! Byte order. 74 | Excel::Stream::ByteOrder m_byteOrder; 75 | //! Size of a sector. 76 | int32_t m_sectorSize; 77 | //! Size of a short-sector. 78 | int32_t m_shortSectorSize; 79 | //! Total number of sectors used for the sector allocation table. 80 | int32_t m_sectorsInSAT; 81 | //! SecID of first sector of the directory stream. 82 | SecID m_dirStreamSecID; 83 | //! Minimum size of a standard stream. 84 | int32_t m_streamMinSize; 85 | //! SecID of first sector of the short-sector allocation table. 86 | SecID m_ssatFirstSecID; 87 | //! Total number of sectors used for the short-sector allocation table. 88 | int32_t m_sectorsInSSAT; 89 | //! SecID of first sector of the master sector allocation table. 90 | SecID m_msatFirstSecID; 91 | //! Total number of sectors used for the master sector allocation table. 92 | int32_t m_sectorsInMSAT; 93 | }; // class Header 94 | 95 | inline 96 | Header::Header() 97 | : m_byteOrder( Excel::Stream::LittleEndian ) 98 | , m_sectorSize( 0 ) 99 | , m_shortSectorSize( 0 ) 100 | , m_sectorsInSAT( 0 ) 101 | , m_dirStreamSecID( 0 ) 102 | , m_streamMinSize( 0 ) 103 | , m_ssatFirstSecID( 0 ) 104 | , m_sectorsInSSAT( 0 ) 105 | , m_msatFirstSecID( 0 ) 106 | , m_sectorsInMSAT( 0 ) 107 | { 108 | } 109 | 110 | inline 111 | Header::Header( std::istream & stream ) 112 | : m_byteOrder( Excel::Stream::LittleEndian ) 113 | , m_sectorSize( 0 ) 114 | , m_shortSectorSize( 0 ) 115 | , m_sectorsInSAT( 0 ) 116 | , m_dirStreamSecID( 0 ) 117 | , m_streamMinSize( 0 ) 118 | , m_ssatFirstSecID( 0 ) 119 | , m_sectorsInSSAT( 0 ) 120 | , m_msatFirstSecID( 0 ) 121 | , m_sectorsInMSAT( 0 ) 122 | { 123 | load( stream ); 124 | } 125 | 126 | inline Excel::Stream::ByteOrder 127 | Header::byteOrder() const 128 | { 129 | return m_byteOrder; 130 | } 131 | 132 | inline int32_t 133 | Header::sectorSize() const 134 | { 135 | return m_sectorSize; 136 | } 137 | 138 | inline int32_t 139 | Header::shortSectorSize() const 140 | { 141 | return m_shortSectorSize; 142 | } 143 | 144 | inline int32_t 145 | Header::sectorsInSAT() const 146 | { 147 | return m_sectorsInSAT; 148 | } 149 | 150 | inline SecID 151 | Header::dirStreamSecID() const 152 | { 153 | return m_dirStreamSecID; 154 | } 155 | 156 | inline int32_t 157 | Header::streamMinSize() const 158 | { 159 | return m_streamMinSize; 160 | } 161 | 162 | inline SecID 163 | Header::ssatFirstSecID() const 164 | { 165 | return m_ssatFirstSecID; 166 | } 167 | 168 | inline int32_t 169 | Header::sectorsInSSAT() const 170 | { 171 | return m_sectorsInSSAT; 172 | } 173 | 174 | inline SecID 175 | Header::msatFirstSecID() const 176 | { 177 | return m_msatFirstSecID; 178 | } 179 | 180 | inline int32_t 181 | Header::sectorsInMSAT() const 182 | { 183 | return m_sectorsInMSAT; 184 | } 185 | 186 | //! Compound document file identifier 187 | static const char cIdentifier[] = { 188 | static_cast< char > ( 0xD0u ), 189 | static_cast< char > ( 0xCFu ), 190 | static_cast< char > ( 0x11u ), 191 | static_cast< char > ( 0xE0u ), 192 | static_cast< char > ( 0xA1u ), 193 | static_cast< char > ( 0xB1u ), 194 | static_cast< char > ( 0x1Au ), 195 | static_cast< char > ( 0xE1u ), 196 | static_cast< char > ( 0x00u ) }; 197 | 198 | //! Byte order identifier. 199 | static const char cLittleEndian[] = { 200 | static_cast< char > ( 0xFEu ), 201 | static_cast< char > ( 0xFFu ), 202 | static_cast< char > ( 0x00u ) }; 203 | 204 | inline void 205 | Header::load( std::istream & stream ) 206 | { 207 | { 208 | char fileID[] = 209 | { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; 210 | 211 | stream.read( fileID, 8 ); 212 | 213 | if( std::strcmp( cIdentifier, fileID ) != 0 ) 214 | throw Exception( L"Wrong file identifier. " 215 | L"It isn't a compound file." ); 216 | } 217 | 218 | stream.seekg( 20, std::ios::cur ); 219 | 220 | { 221 | char byteOrder[] = { 0x00, 0x00, 0x00 }; 222 | 223 | stream.read( byteOrder, 2 ); 224 | 225 | if( std::strcmp( byteOrder, cLittleEndian ) == 0 ) 226 | m_byteOrder = Excel::Stream::LittleEndian; 227 | else 228 | m_byteOrder = Excel::Stream::BigEndian; 229 | } 230 | 231 | { 232 | int16_t sectorSizePower = 0; 233 | 234 | readData( stream, sectorSizePower, 2 ); 235 | 236 | m_sectorSize = 1 << sectorSizePower; 237 | } 238 | 239 | { 240 | int16_t shortSectorSizePower = 0; 241 | 242 | readData( stream, shortSectorSizePower, 2 ); 243 | 244 | m_shortSectorSize = 1 << shortSectorSizePower; 245 | } 246 | 247 | stream.seekg( 10, std::ios::cur ); 248 | 249 | { 250 | int32_t sectorsInSAT = 0; 251 | 252 | readData( stream, sectorsInSAT, 4 ); 253 | 254 | m_sectorsInSAT = sectorsInSAT; 255 | } 256 | 257 | { 258 | int32_t dirStreamSecID = 0; 259 | 260 | readData( stream, dirStreamSecID, 4 ); 261 | 262 | m_dirStreamSecID = dirStreamSecID; 263 | } 264 | 265 | stream.seekg( 4, std::ios::cur ); 266 | 267 | { 268 | int32_t streamMinSize = 0; 269 | 270 | readData( stream, streamMinSize, 4 ); 271 | 272 | m_streamMinSize = streamMinSize; 273 | } 274 | 275 | { 276 | int32_t ssatFirstSecID = 0; 277 | 278 | readData( stream, ssatFirstSecID, 4 ); 279 | 280 | m_ssatFirstSecID = ssatFirstSecID; 281 | } 282 | 283 | { 284 | int32_t sectorsInSSAT = 0; 285 | 286 | readData( stream, sectorsInSSAT, 4 ); 287 | 288 | m_sectorsInSSAT = sectorsInSSAT; 289 | } 290 | 291 | { 292 | int32_t msatFirstSecID = 0; 293 | 294 | readData( stream, msatFirstSecID, 4 ); 295 | 296 | m_msatFirstSecID = msatFirstSecID; 297 | } 298 | 299 | { 300 | int32_t sectorsInMSAT = 0; 301 | 302 | readData( stream, sectorsInMSAT, 4 ); 303 | 304 | m_sectorsInMSAT = sectorsInMSAT; 305 | } 306 | } 307 | 308 | } /* namespace CompoundFile */ 309 | 310 | #endif // COMPOUNDFILE__HEADER_HPP__INCLUDED 311 | -------------------------------------------------------------------------------- /read-excel/compoundfile/msat.hpp: -------------------------------------------------------------------------------- 1 | 2 | /* 3 | SPDX-FileCopyrightText: 2011-2024 Igor Mironchik 4 | SPDX-License-Identifier: MIT 5 | */ 6 | 7 | #ifndef COMPOUNDFILE__MSAT_HPP__INCLUDED 8 | #define COMPOUNDFILE__MSAT_HPP__INCLUDED 9 | 10 | // CompoundFile include. 11 | #include "sat.hpp" 12 | #include "header.hpp" 13 | #include "utils.hpp" 14 | 15 | // C++ include. 16 | #include 17 | 18 | 19 | namespace CompoundFile { 20 | 21 | // 22 | // MSAT 23 | // 24 | 25 | //! MSAT. 26 | class MSAT { 27 | 28 | public: 29 | MSAT( const Header & header, 30 | std::istream & stream ); 31 | 32 | //! \return SAT. 33 | SAT buildSAT(); 34 | 35 | private: 36 | //! Load MSAT. 37 | void loadMSAT(); 38 | 39 | private: 40 | //! Header. 41 | const Header & m_header; 42 | //! File. 43 | std::istream & m_stream; 44 | //! MSAT. 45 | std::vector< SecID > m_msat; 46 | }; // class MSAT 47 | 48 | inline 49 | MSAT::MSAT( const Header & header, 50 | std::istream & stream ) 51 | : m_header( header ) 52 | , m_stream( stream ) 53 | { 54 | loadMSAT(); 55 | } 56 | 57 | inline SAT 58 | MSAT::buildSAT() 59 | { 60 | std::vector< SecID > sat; 61 | 62 | for( std::vector< SecID >::const_iterator it = m_msat.begin(), 63 | last = m_msat.end(); it != last; ++it ) 64 | { 65 | m_stream.seekg( calcFileOffset( *it, m_header.sectorSize() ) ); 66 | 67 | loadSATSector( m_stream, sat, m_header.sectorSize() ); 68 | } 69 | 70 | return SAT( sat ); 71 | } 72 | 73 | 74 | // 75 | // loadFirst109SecIDs 76 | // 77 | 78 | //! Load first 109 SecIDs. 79 | inline std::vector< SecID > 80 | loadFirst109SecIDs( std::istream & stream ) 81 | { 82 | std::vector< SecID > msat; 83 | 84 | for( int32_t i = 0; i < 109; ++i ) 85 | { 86 | int32_t secID = 0; 87 | 88 | readData( stream, secID, 4 ); 89 | 90 | if( secID != SecID::FreeSecID ) 91 | msat.push_back( secID ); 92 | } 93 | 94 | return msat; 95 | } // loadFirst109SecIDs 96 | 97 | 98 | // 99 | // loadMSATSector 100 | // 101 | 102 | inline void 103 | loadMSATSector( std::istream & stream, std::vector< SecID > & msat, 104 | SecID & nextMSATSectorID, int32_t sectorSize ) 105 | { 106 | const int32_t secIDCount = ( sectorSize - 4 ) / 4; 107 | 108 | for( int32_t i = 0; i < secIDCount; ++i ) 109 | { 110 | int32_t secID = 0; 111 | 112 | readData( stream, secID, 4 ); 113 | 114 | msat.push_back( secID ); 115 | } 116 | 117 | int32_t nextSecID = 0; 118 | 119 | readData( stream, nextSecID, 4 ); 120 | 121 | nextMSATSectorID = nextSecID; 122 | } 123 | 124 | inline void 125 | MSAT::loadMSAT() 126 | { 127 | m_msat = loadFirst109SecIDs( m_stream ); 128 | 129 | const int32_t msatSectorsCount = m_header.sectorsInMSAT(); 130 | 131 | SecID id = m_header.msatFirstSecID(); 132 | 133 | for( int32_t i = 0; i < msatSectorsCount; ++i ) 134 | { 135 | m_stream.seekg( calcFileOffset( id, m_header.sectorSize() ) ); 136 | 137 | loadMSATSector( m_stream, m_msat, id, m_header.sectorSize() ); 138 | } 139 | } 140 | 141 | } /* namespace CompoundFile */ 142 | 143 | #endif // COMPOUNDFILE__MSAT_HPP__INCLUDED 144 | -------------------------------------------------------------------------------- /read-excel/compoundfile/sat.hpp: -------------------------------------------------------------------------------- 1 | 2 | /* 3 | SPDX-FileCopyrightText: 2011-2024 Igor Mironchik 4 | SPDX-License-Identifier: MIT 5 | */ 6 | 7 | #ifndef COMPOUNDFILE__SAT_HPP__INCLUDED 8 | #define COMPOUNDFILE__SAT_HPP__INCLUDED 9 | 10 | // C++ include. 11 | #include 12 | #include 13 | #include 14 | #include 15 | 16 | // CompoundFile include. 17 | #include "compoundfile_exceptions.hpp" 18 | 19 | 20 | namespace CompoundFile { 21 | 22 | // 23 | // SecID 24 | // 25 | 26 | //! Sector identifier. 27 | class SecID { 28 | public: 29 | enum Ids { 30 | //! Free sector identifier. 31 | FreeSecID = -1, 32 | //! End of chain of sectors identifier. 33 | EndOfChain = -2, 34 | //! SAT sector identifier. 35 | SATSecID = -3, 36 | //! MSAT sector identifier. 37 | MSATSecID = -4 38 | }; // enum Ids 39 | 40 | public: 41 | SecID(); 42 | SecID( int32_t id ); 43 | 44 | //! Convert SecID into integral value. 45 | operator int32_t () const; 46 | 47 | private: 48 | //! Integral sector identifier. 49 | int32_t m_id; 50 | }; // class SecID 51 | 52 | 53 | // 54 | // SAT 55 | // 56 | 57 | //! Sector allocation table. 58 | class SAT { 59 | public: 60 | SAT(); 61 | explicit SAT( const std::vector< SecID > & sat ); 62 | 63 | //! \return Sector allocation table. 64 | const std::vector< SecID > & sat() const; 65 | 66 | //! \return Chain of sectors for the given stream in the right order. 67 | std::vector< SecID > sectors( const SecID & firstSector ) const; 68 | 69 | private: 70 | //! SAT. 71 | std::vector< SecID > m_sat; 72 | }; // class SAT 73 | 74 | 75 | // 76 | // SecID 77 | // 78 | 79 | inline 80 | SecID::SecID() 81 | : m_id( EndOfChain ) 82 | {} 83 | 84 | inline 85 | SecID::SecID( int32_t id ) 86 | : m_id( id ) 87 | { 88 | } 89 | 90 | inline 91 | SecID::operator int32_t () const 92 | { 93 | return m_id; 94 | } 95 | 96 | 97 | // 98 | // SAT 99 | // 100 | 101 | inline 102 | SAT::SAT() 103 | { 104 | } 105 | 106 | inline 107 | SAT::SAT( const std::vector< SecID > & sat ) 108 | : m_sat( sat ) 109 | { 110 | } 111 | 112 | inline const std::vector< SecID > & 113 | SAT::sat() const 114 | { 115 | return m_sat; 116 | } 117 | 118 | inline std::vector< SecID > 119 | SAT::sectors( const SecID & firstSector ) const 120 | { 121 | if( firstSector < static_cast< int32_t > ( m_sat.size() ) ) 122 | { 123 | std::vector< SecID > result; 124 | 125 | result.push_back( firstSector ); 126 | 127 | SecID id = m_sat.at( firstSector ); 128 | 129 | while( true ) 130 | { 131 | if( id >= 0 ) 132 | result.push_back( id ); 133 | else 134 | break; 135 | 136 | id = m_sat.at( id ); 137 | } 138 | 139 | return result; 140 | } 141 | else 142 | { 143 | std::wstringstream stream; 144 | stream << L"There is no such sector with id: " << firstSector; 145 | 146 | throw Exception( stream.str() ); 147 | } 148 | } 149 | 150 | } /* namespace CompoundFile */ 151 | 152 | #endif // COMPOUNDFILE__SAT_HPP__INCLUDED 153 | -------------------------------------------------------------------------------- /read-excel/compoundfile/utils.hpp: -------------------------------------------------------------------------------- 1 | 2 | /* 3 | SPDX-FileCopyrightText: 2011-2024 Igor Mironchik 4 | SPDX-License-Identifier: MIT 5 | */ 6 | 7 | #ifndef COMPOUNDFILE__UTILS_HPP__INCLUDED 8 | #define COMPOUNDFILE__UTILS_HPP__INCLUDED 9 | 10 | // CompoundFile include. 11 | #include "sat.hpp" 12 | 13 | // C++ include. 14 | #include 15 | 16 | 17 | namespace CompoundFile { 18 | 19 | // 20 | // readData 21 | // 22 | 23 | //! Read data from the stream. 24 | template< class Type > 25 | void readData( std::istream & stream, Type & data, size_t bytes = 0 ) 26 | { 27 | data = Type(0); 28 | if( bytes == 0 ) bytes = sizeof( Type ); 29 | 30 | for( size_t i = 0; i < bytes; ++i ) 31 | { 32 | char c = 0x00; 33 | stream.get( c ); 34 | 35 | data |= (((Type) (unsigned char) c ) << 8*i); 36 | } 37 | } // readData 38 | 39 | 40 | // 41 | // calcFileOffset 42 | // 43 | 44 | //! Calculate file offset for the given sector. 45 | inline size_t 46 | calcFileOffset( const SecID & id, size_t sectorSize ) 47 | { 48 | return ( 512 + id * sectorSize ); 49 | } 50 | 51 | 52 | // 53 | // loadSATSector 54 | // 55 | 56 | //! Load SAT or SSAT sector. 57 | inline void 58 | loadSATSector( std::istream & stream, std::vector< SecID > & sat, 59 | int32_t sectorSize ) 60 | { 61 | const int32_t secIDCount = sectorSize / 4; 62 | 63 | for( int32_t i = 0; i < secIDCount; ++i ) 64 | { 65 | int32_t secID = 0; 66 | 67 | readData( stream, secID, 4 ); 68 | 69 | sat.push_back( secID ); 70 | } 71 | } // loadSATSector 72 | 73 | } /* namespace CompoundFile */ 74 | 75 | #endif // COMPOUNDFILE__UTILS_HPP__INCLUDED 76 | -------------------------------------------------------------------------------- /read-excel/exceptions.hpp: -------------------------------------------------------------------------------- 1 | 2 | /* 3 | SPDX-FileCopyrightText: 2011-2024 Igor Mironchik 4 | SPDX-License-Identifier: MIT 5 | */ 6 | 7 | #ifndef EXCEL__EXCEPTIONS_HPP__INCLUDED 8 | #define EXCEL__EXCEPTIONS_HPP__INCLUDED 9 | 10 | 11 | // C++ include. 12 | #include 13 | #include 14 | 15 | 16 | namespace Excel { 17 | 18 | // 19 | // Exception 20 | // 21 | 22 | //! Base exception in the excel document. 23 | class Exception 24 | : public std::runtime_error 25 | { 26 | public: 27 | explicit Exception( const std::wstring & what ); 28 | virtual ~Exception() noexcept; 29 | 30 | //! \return Reason of the exception. 31 | const std::wstring & whatAsWString() const noexcept; 32 | 33 | private: 34 | //! Reason of the exception. 35 | std::wstring m_what; 36 | }; // class Exception 37 | 38 | inline 39 | Exception::Exception( const std::wstring & what ) 40 | : std::runtime_error( "Use whatAsWString() method." ) 41 | , m_what( what ) 42 | { 43 | } 44 | 45 | inline 46 | Exception::~Exception() noexcept 47 | { 48 | } 49 | 50 | inline const std::wstring & 51 | Exception::whatAsWString() const noexcept 52 | { 53 | return m_what; 54 | } 55 | 56 | } /* namespace Excel */ 57 | 58 | #endif // EXCEL__EXCEPTIONS_HPP__INCLUDED 59 | -------------------------------------------------------------------------------- /read-excel/formula.hpp: -------------------------------------------------------------------------------- 1 | 2 | /* 3 | SPDX-FileCopyrightText: 2011-2024 Igor Mironchik 4 | SPDX-License-Identifier: MIT 5 | */ 6 | 7 | #ifndef EXCEL__FORMULA_HPP__INCLUDED 8 | #define EXCEL__FORMULA_HPP__INCLUDED 9 | 10 | // C++ include. 11 | #include 12 | #include 13 | 14 | // Excel include. 15 | #include "record.hpp" 16 | 17 | 18 | namespace Excel { 19 | 20 | // 21 | // Formula 22 | // 23 | 24 | //! Formula in the Excel document. 25 | class Formula { 26 | public: 27 | Formula(); 28 | explicit Formula( Record & record ); 29 | 30 | //! Possible tpes of the formula's value. 31 | enum ValueType { 32 | //! Unknown value type. 33 | UnknownValue, 34 | //! Double value. 35 | DoubleValue, 36 | //! Boolean value. 37 | BooleanValue, 38 | //! Error value. 39 | ErrorValue, 40 | //! String value. 41 | StringValue, 42 | //! Empty cell. 43 | EmptyCell 44 | }; // enum ValueType 45 | 46 | //! Error values. 47 | enum ErrorValues { 48 | //! Unknown error value. 49 | UnknownError = 0xFF, 50 | //! #NULL! 51 | Null = 0x00, 52 | //! #DIV/0! 53 | DivZero = 0x07, 54 | //! #VALUE! 55 | Value = 0x0F, 56 | //! #REF! 57 | Ref = 0x17, 58 | //! #NAME? 59 | Name = 0x1D, 60 | //! #NUM! 61 | Num = 0x24, 62 | //! #N/A 63 | NA = 0x2A 64 | }; // enum ErrorValues 65 | 66 | //! \return Type of the value. 67 | ValueType valueType() const; 68 | 69 | //! \return Double value. 70 | const double & getDouble() const; 71 | 72 | //! \return Error value. 73 | ErrorValues getErrorValue() const; 74 | 75 | //! \return Boolean value. 76 | bool getBoolean() const; 77 | 78 | //! \return String value. 79 | const std::wstring & getString() const; 80 | 81 | //! Set string value. 82 | void setString( const std::wstring & str ); 83 | 84 | //! \return Row index. 85 | int16_t getRow() const; 86 | 87 | //! \return Column index. 88 | int16_t getColumn() const; 89 | 90 | private: 91 | //! Parse record. 92 | void parse( Record & record ); 93 | 94 | private: 95 | //! Type of the value. 96 | ValueType m_valueType; 97 | //! Double value. 98 | double m_doubleValue; 99 | //! Error value. 100 | ErrorValues m_errorValue; 101 | //! Boolean value. 102 | bool m_boolValue; 103 | //! String value. 104 | std::wstring m_stringValue; 105 | //! Row index. 106 | int16_t m_row; 107 | //! Column index. 108 | int16_t m_column; 109 | }; // class Formula 110 | 111 | inline 112 | Formula::Formula() 113 | : m_valueType( UnknownValue ) 114 | , m_doubleValue( 0.0 ) 115 | , m_errorValue( UnknownError ) 116 | , m_boolValue( false ) 117 | , m_row( 0 ) 118 | , m_column( 0 ) 119 | { 120 | } 121 | 122 | inline 123 | Formula::Formula( Record & record ) 124 | : m_valueType( UnknownValue ) 125 | , m_doubleValue( 0.0 ) 126 | , m_errorValue( UnknownError ) 127 | , m_boolValue( false ) 128 | , m_row( 0 ) 129 | , m_column( 0 ) 130 | { 131 | parse( record ); 132 | } 133 | 134 | inline Formula::ValueType 135 | Formula::valueType() const 136 | { 137 | return m_valueType; 138 | } 139 | 140 | inline const double & 141 | Formula::getDouble() const 142 | { 143 | return m_doubleValue; 144 | } 145 | 146 | inline Formula::ErrorValues 147 | Formula::getErrorValue() const 148 | { 149 | return m_errorValue; 150 | } 151 | 152 | inline bool 153 | Formula::getBoolean() const 154 | { 155 | return m_boolValue; 156 | } 157 | 158 | inline const std::wstring & 159 | Formula::getString() const 160 | { 161 | return m_stringValue; 162 | } 163 | 164 | inline void 165 | Formula::setString( const std::wstring & str ) 166 | { 167 | m_stringValue = str; 168 | } 169 | 170 | inline int16_t 171 | Formula::getRow() const 172 | { 173 | return m_row; 174 | } 175 | 176 | inline int16_t 177 | Formula::getColumn() const 178 | { 179 | return m_column; 180 | } 181 | 182 | inline void 183 | Formula::parse( Record & record ) 184 | { 185 | record.dataStream().read( m_row, 2 ); 186 | record.dataStream().read( m_column, 2 ); 187 | 188 | record.dataStream().seek( 2, Stream::FromCurrent ); 189 | 190 | union { 191 | double m_double; 192 | int64_t m_long; 193 | } doubleAsLongLong; 194 | 195 | record.dataStream().read( doubleAsLongLong.m_long, 8 ); 196 | 197 | unsigned long long isBool = doubleAsLongLong.m_long & 0xFFFFFFFFFF00FFFF; 198 | 199 | if( isBool == 0xFFFF000000000001 ) 200 | { 201 | m_boolValue = ( doubleAsLongLong.m_long & 0x0000000000FF0000 ) >> 16; 202 | m_valueType = BooleanValue; 203 | 204 | return; 205 | } 206 | 207 | unsigned long long isError = doubleAsLongLong.m_long & 0xFFFFFFFFFF00FFFF; 208 | 209 | if( isError == 0xFFFF000000000002 ) 210 | { 211 | unsigned char error = 212 | static_cast< unsigned char > 213 | ( ( doubleAsLongLong.m_long & 0x0000000000FF0000 ) >> 16 ); 214 | 215 | m_errorValue = (ErrorValues) error; 216 | m_valueType = ErrorValue; 217 | 218 | return; 219 | } 220 | 221 | unsigned long long isEmpty = doubleAsLongLong.m_long & 0xFFFFFFFFFF00FFFF; 222 | 223 | if( isEmpty == 0xFFFF000000000003 ) 224 | { 225 | m_valueType = EmptyCell; 226 | 227 | return; 228 | } 229 | 230 | unsigned long long isString = doubleAsLongLong.m_long & 0xFFFFFFFFFFFFFFFF; 231 | 232 | if( isString == 0xFFFF000000000000 ) 233 | { 234 | m_valueType = StringValue; 235 | 236 | return; 237 | } 238 | 239 | m_doubleValue = doubleAsLongLong.m_double; 240 | m_valueType = DoubleValue; 241 | } 242 | 243 | } /* namespace Excel */ 244 | 245 | #endif // EXCEL__FORMULA_HPP__INCLUDED 246 | -------------------------------------------------------------------------------- /read-excel/parser.hpp: -------------------------------------------------------------------------------- 1 | 2 | /* 3 | SPDX-FileCopyrightText: 2011-2024 Igor Mironchik 4 | SPDX-License-Identifier: MIT 5 | */ 6 | 7 | #pragma once 8 | 9 | // C++ include. 10 | #include 11 | 12 | // Excel include. 13 | #include "storage.hpp" 14 | #include "sheet.hpp" 15 | 16 | #include "compoundfile/compoundfile.hpp" 17 | #include "compoundfile/compoundfile_exceptions.hpp" 18 | 19 | namespace Excel { 20 | 21 | // 22 | // Parser 23 | // 24 | 25 | //! Parser of XLS file. 26 | class Parser final { 27 | public: 28 | //! Load WorkBook from stream. 29 | static void loadBook( std::istream & fileStream, IStorage & storage, 30 | const std::string & fileName = "" ); 31 | 32 | //! Store document date mode. 33 | static void handleDateMode( Record & r, IStorage & storage ); 34 | 35 | //! Load sheets from file. 36 | static void loadGlobals( std::vector< BoundSheet > & boundSheets, 37 | Stream & stream, IStorage & storage ); 38 | 39 | //! Load boundsheet. 40 | static BoundSheet parseBoundSheet( Record & record, BOF::BiffVersion ver ); 41 | 42 | //! Load WorkSheets. 43 | static void loadWorkSheets( const std::vector< BoundSheet > & boundSheets, 44 | Stream & stream, IStorage& storage ); 45 | 46 | //! Parse shared string table. 47 | static void parseSST( Record & record, IStorage & storage ); 48 | 49 | //! Load WorkSheet. 50 | static void loadSheet( size_t sheetIdx, const BoundSheet & boundSheet, 51 | Stream & stream, IStorage & storage ); 52 | 53 | //! Handle label SST. 54 | static void handleLabelSST( Record & record, size_t sheetIdx, IStorage & storage ); 55 | 56 | //! Handle label. 57 | static void handleLabel( Record & record, size_t sheetIdx, IStorage & storage ); 58 | 59 | //! Handle RK. 60 | static void handleRK( Record & record, size_t sheetIdx, IStorage & storage ); 61 | 62 | //! Handle MULRK. 63 | static void handleMULRK( Record & record, size_t sheetIdx, IStorage & storage ); 64 | 65 | //! Handle NUMBER. 66 | static void handleNUMBER( Record & record, size_t sheetIdx, IStorage & storage ); 67 | 68 | //! Handle FORMULA. 69 | static void handleFORMULA( Record & record, Stream & stream, size_t sheetIdx, 70 | IStorage & storage ); 71 | }; // class Parser 72 | 73 | inline void 74 | Parser::loadBook( std::istream & fileStream, IStorage & storage, 75 | const std::string & fileName ) 76 | { 77 | static_assert( sizeof( double ) == 8, 78 | "Unsupported platform: double has to be 8 bytes." ); 79 | 80 | try { 81 | CompoundFile::File file( fileStream, fileName ); 82 | auto stream = file.stream( 83 | file.hasDirectory( L"Workbook" ) ? file.directory( L"Workbook" ) 84 | : file.directory( L"Book") ); 85 | 86 | std::vector< BoundSheet > boundSheets; 87 | 88 | loadGlobals( boundSheets, *stream, storage ); 89 | 90 | loadWorkSheets( boundSheets, *stream, storage ); 91 | } 92 | catch( const CompoundFile::Exception & x ) 93 | { 94 | throw Exception( x.whatAsWString() ); 95 | } 96 | } 97 | 98 | inline void 99 | Parser::handleDateMode( Record & r, IStorage & storage ) 100 | { 101 | uint16_t mode = 0; 102 | 103 | r.dataStream().read( mode, 2 ); 104 | 105 | storage.onDateMode( mode ); 106 | } 107 | 108 | inline void 109 | Parser::loadGlobals( std::vector< BoundSheet > & boundSheets, 110 | Stream & stream, IStorage & storage ) 111 | { 112 | BOF bof; 113 | 114 | while( true ) 115 | { 116 | Record r( stream ); 117 | 118 | switch( r.code() ) 119 | { 120 | case XL_BOF : 121 | bof.parse( r ); 122 | break; 123 | 124 | case XL_FILEPASS : 125 | throw Exception( L"This file is protected. Decryption is not implemented yet." ); 126 | 127 | case XL_SST : 128 | parseSST( r, storage ); 129 | break; 130 | 131 | case XL_BOUNDSHEET : 132 | boundSheets.push_back( parseBoundSheet( r, bof.version() ) ); 133 | break; 134 | 135 | case XL_DATEMODE : 136 | handleDateMode( r, storage ); 137 | break; 138 | 139 | case XL_EOF : 140 | return; 141 | 142 | case XL_UNKNOWN : 143 | throw Exception( L"Wrong format." ); 144 | 145 | default: 146 | break; 147 | } 148 | } 149 | } 150 | 151 | inline BoundSheet 152 | Parser::parseBoundSheet( Record & record, BOF::BiffVersion ver ) 153 | { 154 | int32_t pos = 0; 155 | int16_t sheetType = 0; 156 | std::wstring sheetName; 157 | 158 | record.dataStream().read( pos, 4 ); 159 | 160 | record.dataStream().read( sheetType, 2 ); 161 | 162 | sheetName = loadString( record.dataStream(), record.borders(), 1, ver ); 163 | 164 | return BoundSheet( pos, 165 | BoundSheet::convertSheetType( sheetType ), 166 | sheetName ); 167 | } 168 | 169 | inline void 170 | Parser::loadWorkSheets( const std::vector< BoundSheet > & boundSheets, 171 | Stream & stream, IStorage& storage ) 172 | { 173 | for( size_t i = 0; i < boundSheets.size(); ++i ) 174 | { 175 | if( boundSheets[i].sheetType() == BoundSheet::WorkSheet ) 176 | { 177 | storage.onSheet( i, boundSheets[i].sheetName() ); 178 | loadSheet( i, boundSheets[i], stream, storage ); 179 | } 180 | } 181 | } 182 | 183 | inline void 184 | Parser::parseSST( Record & record, IStorage & storage ) 185 | { 186 | int32_t totalStrings = 0; 187 | int32_t uniqueStrings = 0; 188 | 189 | record.dataStream().read( totalStrings, 4 ); 190 | record.dataStream().read( uniqueStrings, 4 ); 191 | 192 | std::vector< std::wstring > sst( uniqueStrings ); 193 | 194 | for( int32_t i = 0; i < uniqueStrings; ++i ) 195 | storage.onSharedString( uniqueStrings, i, 196 | loadString( record.dataStream(), record.borders() ) ); 197 | } 198 | 199 | inline void 200 | Parser::loadSheet( size_t sheetIdx, const BoundSheet & boundSheet, 201 | Stream & stream, IStorage & storage ) 202 | { 203 | stream.seek( boundSheet.BOFPosition(), Stream::FromBeginning ); 204 | BOF bof; 205 | 206 | { 207 | Record record( stream ); 208 | 209 | bof.parse( record ); 210 | } 211 | 212 | if( bof.version() != BOF::BIFF8 ) 213 | throw Exception( L"Unsupported BIFF version. BIFF8 is supported only." ); 214 | 215 | while( true ) 216 | { 217 | Record record( stream ); 218 | 219 | switch( record.code() ) 220 | { 221 | case XL_LABELSST : 222 | handleLabelSST( record, sheetIdx, storage ); 223 | break; 224 | 225 | case XL_LABEL : 226 | handleLabel( record, sheetIdx, storage ); 227 | break; 228 | 229 | case XL_RK : 230 | case XL_RK2 : 231 | handleRK( record, sheetIdx, storage ); 232 | break; 233 | 234 | case XL_MULRK : 235 | handleMULRK( record, sheetIdx, storage ); 236 | break; 237 | 238 | case XL_NUMBER : 239 | handleNUMBER( record, sheetIdx, storage ); 240 | break; 241 | 242 | case XL_FORMULA : 243 | handleFORMULA( record, stream, sheetIdx, storage ); 244 | break; 245 | 246 | case XL_EOF : 247 | return; 248 | 249 | case XL_UNKNOWN : 250 | throw Exception( L"Wrong format." ); 251 | 252 | default: 253 | break; 254 | } 255 | } 256 | } 257 | 258 | inline void 259 | Parser::handleLabelSST( Record & record, size_t sheetIdx, IStorage & storage ) 260 | { 261 | int16_t row = 0; 262 | int16_t column = 0; 263 | int16_t xfIndex = 0; 264 | int32_t sstIndex = 0; 265 | 266 | record.dataStream().read( row, 2 ); 267 | record.dataStream().read( column, 2 ); 268 | record.dataStream().read( xfIndex, 2 ); 269 | record.dataStream().read( sstIndex, 4 ); 270 | 271 | storage.onCellSharedString( sheetIdx, row, column, sstIndex ); 272 | } 273 | 274 | inline void 275 | Parser::handleLabel( Record & record, size_t sheetIdx, IStorage & storage ) 276 | { 277 | int16_t row = 0; 278 | int16_t column = 0; 279 | 280 | record.dataStream().read( row, 2 ); 281 | record.dataStream().read( column, 2 ); 282 | record.dataStream().seek( 2, Excel::Stream::FromCurrent ); 283 | const auto data = loadString( record.dataStream(), record.borders(), 2, BOF::BIFF7 ); 284 | 285 | storage.onCell( sheetIdx, row, column, data ); 286 | } 287 | 288 | // 289 | // doubleFromRK 290 | // 291 | 292 | inline double 293 | doubleFromRK( uint32_t rk ) 294 | { 295 | double num = 0; 296 | 297 | if( rk & 0x02 ) 298 | { 299 | // int32_t 300 | num = (double) (rk >> 2); 301 | } 302 | else 303 | { 304 | // hi words of IEEE num 305 | *((uint32_t *)&num+1) = rk & 0xFFFFFFFC; 306 | *((uint32_t *)&num) = 0; 307 | } 308 | 309 | if( rk & 0x01 ) 310 | // divide by 100 311 | num /= 100; 312 | 313 | return num; 314 | } // doubleFromRK 315 | 316 | inline void 317 | Parser::handleRK( Record & record, size_t sheetIdx, IStorage & storage ) 318 | { 319 | int16_t row = 0; 320 | int16_t column = 0; 321 | uint32_t rk = 0; 322 | 323 | record.dataStream().read( row, 2 ); 324 | record.dataStream().read( column, 2 ); 325 | 326 | record.dataStream().seek( 2, Stream::FromCurrent ); 327 | record.dataStream().read( rk, 4 ); 328 | 329 | storage.onCell( sheetIdx, row, column, doubleFromRK( rk ) ); 330 | } 331 | 332 | inline void 333 | Parser::handleMULRK( Record & record, size_t sheetIdx, IStorage & storage ) 334 | { 335 | int16_t row = 0; 336 | int16_t colFirst = 0; 337 | int16_t colLast = 0; 338 | 339 | record.dataStream().read( row, 2 ); 340 | record.dataStream().read( colFirst, 2 ); 341 | 342 | int32_t pos = record.dataStream().pos(); 343 | 344 | record.dataStream().seek( -2, Stream::FromEnd ); 345 | 346 | record.dataStream().read( colLast, 2 ); 347 | 348 | record.dataStream().seek( pos, Stream::FromBeginning ); 349 | 350 | const int16_t rkCount = colLast - colFirst + 1; 351 | 352 | for( int16_t i = 0; i < rkCount; ++i ) 353 | { 354 | uint32_t rk = 0; 355 | 356 | record.dataStream().seek( 2, Stream::FromCurrent ); 357 | 358 | record.dataStream().read( rk, 4 ); 359 | 360 | storage.onCell( sheetIdx, row, colFirst + i, doubleFromRK( rk ) ); 361 | } 362 | } 363 | 364 | inline void 365 | Parser::handleNUMBER( Record & record, size_t sheetIdx, IStorage & storage ) 366 | { 367 | int16_t row = 0; 368 | int16_t column = 0; 369 | 370 | record.dataStream().read( row, 2 ); 371 | record.dataStream().read( column, 2 ); 372 | 373 | record.dataStream().seek( 2, Stream::FromCurrent ); 374 | 375 | union { 376 | double m_asDouble; 377 | uint64_t m_asLongLong; 378 | } doubleAndLongLong; 379 | 380 | record.dataStream().read( doubleAndLongLong.m_asLongLong, 8 ); 381 | 382 | storage.onCell( sheetIdx, row, column, doubleAndLongLong.m_asDouble ); 383 | } 384 | 385 | inline void 386 | Parser::handleFORMULA( Record & record, Stream & stream, size_t sheetIdx, IStorage & storage ) 387 | { 388 | Formula formula( record ); 389 | 390 | if( formula.valueType() == Formula::StringValue ) 391 | { 392 | Record stringRecord( stream ); 393 | 394 | std::vector< int32_t > borders; 395 | 396 | formula.setString( loadString( stringRecord.dataStream(), borders ) ); 397 | } 398 | 399 | storage.onCell( sheetIdx, formula ); 400 | } 401 | 402 | } /* namespace Excel */ 403 | -------------------------------------------------------------------------------- /read-excel/record.hpp: -------------------------------------------------------------------------------- 1 | 2 | /* 3 | SPDX-FileCopyrightText: 2011-2024 Igor Mironchik 4 | SPDX-License-Identifier: MIT 5 | */ 6 | 7 | #ifndef EXCEL__RECORD_HPP__INCLUDED 8 | #define EXCEL__RECORD_HPP__INCLUDED 9 | 10 | // Excel include. 11 | #include "stream.hpp" 12 | #include "exceptions.hpp" 13 | 14 | // C++ include. 15 | #include 16 | #include 17 | 18 | 19 | namespace Excel { 20 | 21 | 22 | // 23 | // RecordSubstream 24 | // 25 | 26 | class RecordSubstream 27 | : public Stream 28 | { 29 | friend class Record; 30 | 31 | public: 32 | explicit RecordSubstream( Stream::ByteOrder byteOrder ); 33 | 34 | //! Read one byte from the stream. 35 | char getByte() override; 36 | 37 | //! \return true if EOF reached. 38 | bool eof() const override; 39 | 40 | //! Seek stream to new position. 41 | void seek( int32_t pos, SeekType type = FromBeginning ) override; 42 | 43 | //! \return Position in the stream. 44 | int32_t pos() override; 45 | 46 | protected: 47 | //! Write data to the stream. 48 | void write( const char * data, size_t size ); 49 | 50 | private: 51 | //! Stream. 52 | std::stringstream m_stream; 53 | }; // class RecordSubstream 54 | 55 | 56 | // 57 | // RecordType 58 | // 59 | 60 | //! Types of the Excel's records. 61 | enum RecordType { 62 | XL_BOF = 0x809, 63 | XL_EOF = 0x0A, 64 | XL_BOUNDSHEET = 0x85, 65 | XL_DIMENSION = 0x200, 66 | XL_ROW = 0x208, 67 | XL_NOTE = 0x1C, 68 | XL_TXO = 0x1B6, 69 | XL_RK = 0x7E, 70 | XL_RK2 = 0x27E, 71 | XL_MULRK = 0xBD, 72 | XL_INDEX = 0x20B, 73 | XL_SST = 0xFC, 74 | XL_EXTSST = 0xFF, 75 | XL_CONTINUE = 0x3C, 76 | XL_LABEL = 0x204, 77 | XL_LABELSST = 0xFD, 78 | XL_NUMBER = 0x203, 79 | XL_NAME = 0x18, 80 | XL_ARRAY = 0x221, 81 | XL_STRING = 0x207, 82 | XL_FORMULA = 0x06, 83 | XL_FORMAT = 0x41E, 84 | XL_XF = 0xE0, 85 | XL_BOOLERR = 0x205, 86 | XL_DATEMODE = 0x22, 87 | XL_FILEPASS = 0x2F, 88 | XL_UNKNOWN = 0xFFFF 89 | }; // enum RecordType 90 | 91 | // 92 | // Record 93 | // 94 | 95 | //! Record in the Excel file. 96 | class Record { 97 | public: 98 | Record( Stream & stream ); 99 | ~Record(); 100 | 101 | //! \return Record's code. 102 | uint16_t code() const; 103 | 104 | //! \return Record's length. 105 | uint32_t length() const; 106 | 107 | //! \return Stream with record's data. 108 | Stream & dataStream(); 109 | 110 | //! \return Borders indexes of the continue records. 111 | const std::vector< int32_t > & borders() const; 112 | 113 | private: 114 | //! Read record from the stream. 115 | void read( Stream & stream ); 116 | 117 | private: 118 | //! Record's code. 119 | uint16_t m_code; 120 | //! Record's length. 121 | uint32_t m_length; 122 | //! Record's substream. 123 | RecordSubstream m_stream; 124 | //! Borders indexes of the continue records. 125 | std::vector< int32_t > m_borders; 126 | }; // class Record 127 | 128 | // 129 | // RecordSubstream 130 | // 131 | 132 | inline 133 | RecordSubstream::RecordSubstream( Stream::ByteOrder byteOrder ) 134 | : Stream( byteOrder ) 135 | { 136 | } 137 | 138 | inline char 139 | RecordSubstream::getByte() 140 | { 141 | char ch; 142 | m_stream.get( ch ); 143 | 144 | return ch; 145 | } 146 | 147 | inline bool 148 | RecordSubstream::eof() const 149 | { 150 | return m_stream.eof(); 151 | } 152 | 153 | inline void 154 | RecordSubstream::seek( int32_t pos, SeekType type ) 155 | { 156 | std::ios::seekdir dir; 157 | 158 | if( type == Stream::FromBeginning ) 159 | dir = std::ios::beg; 160 | else if( type == Stream::FromCurrent ) 161 | dir = std::ios::cur; 162 | else 163 | dir = std::ios::end; 164 | 165 | m_stream.seekg( pos, dir ); 166 | } 167 | 168 | inline int32_t 169 | RecordSubstream::pos() 170 | { 171 | return static_cast< int32_t > ( m_stream.tellg() ); 172 | } 173 | 174 | inline void 175 | RecordSubstream::write( const char * data, size_t size ) 176 | { 177 | m_stream.write( data, size ); 178 | } 179 | 180 | 181 | // 182 | // Record 183 | // 184 | 185 | inline 186 | Record::Record( Stream & stream ) 187 | : m_code( 0 ) 188 | , m_length( 0 ) 189 | , m_stream( stream.byteOrder() ) 190 | { 191 | read( stream ); 192 | } 193 | 194 | inline 195 | Record::~Record() 196 | { 197 | } 198 | 199 | inline void 200 | Record::read( Stream & stream ) 201 | { 202 | stream.read( m_code, 2 ); 203 | stream.read( m_length, 2 ); 204 | 205 | uint16_t nextRecordCode = 0; 206 | uint16_t nextRecordLength = 0; 207 | 208 | std::vector< char > data; 209 | 210 | if( m_length ) 211 | { 212 | data.reserve( m_length ); 213 | 214 | for( size_t i = 0; i < m_length; ++i ) 215 | { 216 | data.push_back( stream.getByte() ); 217 | 218 | if( stream.eof() ) 219 | throw Exception( L"Unexpected end of file." ); 220 | } 221 | } 222 | 223 | try { 224 | stream.read( nextRecordCode, 2 ); 225 | } 226 | catch( const Exception & ) 227 | { 228 | nextRecordCode = XL_UNKNOWN; 229 | } 230 | 231 | while( nextRecordCode == XL_CONTINUE ) 232 | { 233 | m_borders.push_back( m_length ); 234 | 235 | stream.read( nextRecordLength, 2 ); 236 | 237 | data.reserve( data.size() + nextRecordLength ); 238 | 239 | if( nextRecordLength ) 240 | { 241 | for( uint16_t i = 0; i < nextRecordLength; ++i ) 242 | { 243 | data.push_back( stream.getByte() ); 244 | 245 | if( stream.eof() ) 246 | throw Exception( L"Unexpected end of file." ); 247 | } 248 | } 249 | 250 | m_length += nextRecordLength; 251 | 252 | try { 253 | stream.read( nextRecordCode, 2 ); 254 | } 255 | catch( const Exception & ) 256 | { 257 | nextRecordCode = XL_UNKNOWN; 258 | } 259 | } 260 | 261 | if( !stream.eof() ) 262 | stream.seek( -2, Stream::FromCurrent ); 263 | 264 | if( data.size() ) 265 | m_stream.write( &data[ 0 ], data.size() ); 266 | } 267 | 268 | inline uint16_t 269 | Record::code() const 270 | { 271 | return m_code; 272 | } 273 | 274 | inline uint32_t 275 | Record::length() const 276 | { 277 | return m_length; 278 | } 279 | 280 | inline Stream & 281 | Record::dataStream() 282 | { 283 | return m_stream; 284 | } 285 | 286 | inline const std::vector< int32_t > & 287 | Record::borders() const 288 | { 289 | return m_borders; 290 | } 291 | 292 | } /* namespace Excel */ 293 | 294 | #endif // EXCEL__RECORD_HPP__INCLUDED 295 | 296 | -------------------------------------------------------------------------------- /read-excel/sheet.hpp: -------------------------------------------------------------------------------- 1 | 2 | /* 3 | SPDX-FileCopyrightText: 2011-2024 Igor Mironchik 4 | SPDX-License-Identifier: MIT 5 | */ 6 | 7 | #ifndef EXCEL__SHEET_HPP__INCLUDED 8 | #define EXCEL__SHEET_HPP__INCLUDED 9 | 10 | // Excel include. 11 | #include "cell.hpp" 12 | #include "record.hpp" 13 | #include "bof.hpp" 14 | #include "formula.hpp" 15 | #include "string.hpp" 16 | #include "exceptions.hpp" 17 | #include "storage.hpp" 18 | 19 | // C++ include. 20 | #include 21 | #include 22 | 23 | 24 | namespace Excel { 25 | 26 | // 27 | // BoundSheet 28 | // 29 | 30 | //! BoundSheet record. 31 | class BoundSheet { 32 | public: 33 | //! Sheet's type. 34 | enum SheetType { 35 | //! Worksheet. 36 | WorkSheet = 0x0000, 37 | //! Excel 4.0 macro sheet. 38 | MacroSheet = 0x0100, 39 | //! Chart. 40 | Chart = 0x0200, 41 | //! Visual Basic module. 42 | VisualBasicModule = 0x0600 43 | }; // enum SheetType 44 | 45 | BoundSheet( int32_t pos, 46 | SheetType type, const std::wstring & name ); 47 | 48 | //! \return BOF position. 49 | int32_t BOFPosition() const; 50 | 51 | //! \return Sheet's type. 52 | SheetType sheetType() const; 53 | 54 | //! \return Sheet's name. 55 | const std::wstring & sheetName() const; 56 | 57 | //! Convert 2-bytes type field to the SheetType. 58 | static SheetType convertSheetType( int16_t type ); 59 | 60 | private: 61 | //! BOF position. 62 | int32_t m_BOFPosition; 63 | //! Sheet's type. 64 | SheetType m_sheetType; 65 | //! Sheet's name. 66 | std::wstring m_sheetName; 67 | }; // class BoundSheet 68 | 69 | 70 | // 71 | // Sheet 72 | // 73 | 74 | //! Excel's sheet. 75 | class Sheet { 76 | public: 77 | explicit Sheet( const std::wstring & name ); 78 | 79 | //! \return Cell. 80 | const Cell & cell( size_t row, size_t column ) const; 81 | 82 | //! \return Row's count. 83 | size_t rowsCount() const; 84 | 85 | //! \return Column's count. 86 | size_t columnsCount() const; 87 | 88 | //! Set cell. 89 | template< typename Value > 90 | void setCell( size_t row, size_t column, Value value ); 91 | 92 | //! \return Name of the sheet. 93 | const std::wstring & sheetName() const; 94 | 95 | private: 96 | //! Init cell's table with given cell. 97 | void initCell( size_t row, size_t column ); 98 | 99 | private: 100 | //! Cells. 101 | std::vector< std::vector< Cell > > m_cells; 102 | //! Dummy cell. 103 | Cell m_dummyCell; 104 | //! Column's count; 105 | size_t m_columnsCount; 106 | //! Name of the sheet. 107 | std::wstring m_name; 108 | }; // class Sheet 109 | 110 | // 111 | // BoundSheet 112 | // 113 | 114 | //! Sheet's type. 115 | enum SheetType { 116 | //! Worksheet. 117 | WorkSheet = 0x0000, 118 | //! Excel 4.0 macro sheet. 119 | MacroSheet = 0x0100, 120 | //! Chart. 121 | Chart = 0x0200, 122 | //! Visual Basic module. 123 | VisualBasicModule = 0x0600 124 | }; // enum SheetType 125 | 126 | inline 127 | BoundSheet::BoundSheet( int32_t pos, 128 | SheetType type, const std::wstring & name ) 129 | : m_BOFPosition( pos ) 130 | , m_sheetType( type ) 131 | , m_sheetName( name ) 132 | { 133 | } 134 | 135 | inline int32_t 136 | BoundSheet::BOFPosition() const 137 | { 138 | return m_BOFPosition; 139 | } 140 | 141 | inline BoundSheet::SheetType 142 | BoundSheet::sheetType() const 143 | { 144 | return m_sheetType; 145 | } 146 | 147 | inline const std::wstring & 148 | BoundSheet::sheetName() const 149 | { 150 | return m_sheetName; 151 | } 152 | 153 | inline BoundSheet::SheetType 154 | BoundSheet::convertSheetType( int16_t type ) 155 | { 156 | return ( (SheetType) ( type & 0xFF00 ) ); 157 | } 158 | 159 | 160 | // 161 | // Sheet 162 | // 163 | 164 | inline 165 | Sheet::Sheet( const std::wstring & name ) 166 | : m_columnsCount( 0 ) 167 | , m_name( name ) 168 | { 169 | } 170 | 171 | template< typename Value > 172 | inline void 173 | Sheet::setCell( size_t row, size_t column, Value value ) 174 | { 175 | initCell( row, column ); 176 | m_cells[ row ][ column ].setData( value ); 177 | } 178 | 179 | inline const std::wstring & 180 | Sheet::sheetName() const 181 | { 182 | return m_name; 183 | } 184 | 185 | inline void 186 | Sheet::initCell( size_t row, size_t column ) 187 | { 188 | if( m_cells.size() < row + 1 ) 189 | m_cells.resize( row + 1 ); 190 | 191 | if( m_cells[ row ].size() < column + 1 ) 192 | { 193 | bool all = false; 194 | 195 | if( column + 1 > m_columnsCount ) 196 | { 197 | m_columnsCount = column + 1; 198 | all = true; 199 | } 200 | 201 | if( all ) 202 | { 203 | for( std::vector< std::vector< Cell > >::iterator it = m_cells.begin(), 204 | last = m_cells.end(); it != last; ++it ) 205 | { 206 | it->resize( m_columnsCount ); 207 | } 208 | } 209 | else 210 | m_cells.back().resize( m_columnsCount ); 211 | } 212 | } 213 | 214 | inline const Cell & 215 | Sheet::cell( size_t row, size_t column ) const 216 | { 217 | if( m_cells.size() > 0 ) 218 | { 219 | if( m_cells.size() - 1 < row || m_cells[ 0 ].size() - 1 < column ) 220 | return m_dummyCell; 221 | else 222 | return m_cells[ row ][ column ]; 223 | } 224 | 225 | return m_dummyCell; 226 | } 227 | 228 | inline size_t 229 | Sheet::rowsCount() const 230 | { 231 | return m_cells.size(); 232 | } 233 | 234 | inline size_t 235 | Sheet::columnsCount() const 236 | { 237 | return m_columnsCount; 238 | } 239 | 240 | } /* namespace Excel */ 241 | 242 | #endif // EXCEL__SHEET_HPP__INCLUDED 243 | 244 | -------------------------------------------------------------------------------- /read-excel/storage.hpp: -------------------------------------------------------------------------------- 1 | 2 | /* 3 | SPDX-FileCopyrightText: 2011-2024 Igor Mironchik 4 | SPDX-License-Identifier: MIT 5 | */ 6 | 7 | #pragma once 8 | 9 | // C++ include. 10 | #include 11 | 12 | // Excel include. 13 | #include "formula.hpp" 14 | 15 | namespace Excel { 16 | 17 | // 18 | // IStorage 19 | // 20 | 21 | //! Excel storage interface. 22 | struct IStorage { 23 | virtual ~IStorage() = default; 24 | 25 | protected: 26 | friend class Parser; 27 | 28 | //! Handler of SST. 29 | virtual void onSharedString( size_t sstSize, size_t idx, const std::wstring & value ) = 0; 30 | //! Handler of date mode. 31 | virtual void onDateMode( uint16_t mode ) = 0; 32 | //! Handler of sheet. 33 | virtual void onSheet( size_t idx, const std::wstring & value ) = 0; 34 | //! Handler of cell with text from SST. 35 | virtual void onCellSharedString( size_t sheetIdx, size_t row, size_t column, size_t sstIndex ) = 0; 36 | //! Handler of cell with text. 37 | virtual void onCell( size_t sheetIdx, size_t row, size_t column, const std::wstring & value ) = 0; 38 | //! Handler of cell with double. 39 | virtual void onCell( size_t sheetIdx, size_t row, size_t column, double value ) = 0; 40 | //! Handler of cell with formula. 41 | virtual void onCell( size_t sheetIdx, const Formula & value ) = 0; 42 | }; // struct IStorage 43 | 44 | struct EmptyStorage : public IStorage { 45 | protected: 46 | void onSharedString( size_t sstSize, size_t idx, const std::wstring & value ) override {} 47 | void onDateMode( uint16_t mode ) override {} 48 | void onSheet( size_t idx, const std::wstring & value ) override {} 49 | void onCellSharedString( size_t sheetIdx, size_t row, size_t column, size_t sstIndex ) override {} 50 | void onCell( size_t sheetIdx, size_t row, size_t column, const std::wstring & value ) override {} 51 | void onCell( size_t sheetIdx, size_t row, size_t column, double value ) override {} 52 | void onCell( size_t sheetIdx, const Formula & value ) override {} 53 | }; // struct EmptyStorage 54 | 55 | } /* namespace Excel */ 56 | -------------------------------------------------------------------------------- /read-excel/stream.hpp: -------------------------------------------------------------------------------- 1 | 2 | /* 3 | SPDX-FileCopyrightText: 2011-2024 Igor Mironchik 4 | SPDX-License-Identifier: MIT 5 | */ 6 | 7 | #ifndef EXCEL__STREAM_HPP__INCLUDED 8 | #define EXCEL__STREAM_HPP__INCLUDED 9 | 10 | // C++ include. 11 | #include 12 | 13 | // read-excel include. 14 | #include "exceptions.hpp" 15 | 16 | namespace Excel { 17 | 18 | // 19 | // Stream 20 | // 21 | 22 | //! Stream interface. 23 | class Stream { 24 | public: 25 | //! Byte order identifier. 26 | enum ByteOrder { 27 | //! Big-Endian. 28 | BigEndian, 29 | //! Little-Endian. 30 | LittleEndian 31 | }; // enum ByteOrder 32 | 33 | private: 34 | 35 | // 36 | // SystemByteOrder 37 | // 38 | 39 | //! System's byte order. 40 | class SystemByteOrder { 41 | public: 42 | static ByteOrder byteOrder() 43 | { 44 | static const int16_t word = 0x0001; 45 | static const char * byte = (const char *) &word; 46 | 47 | return ( byte[ 0 ] ? LittleEndian : BigEndian ); 48 | } 49 | }; // class SystemByteOrder 50 | 51 | public: 52 | 53 | explicit Stream( ByteOrder byteOrder ); 54 | virtual ~Stream(); 55 | 56 | //! \return Byte order in the stream. 57 | ByteOrder byteOrder() const; 58 | 59 | //! Read one byte from the stream. 60 | virtual char getByte() = 0; 61 | 62 | //! \return true if EOF reached. 63 | virtual bool eof() const = 0; 64 | 65 | //! Seek type. 66 | enum SeekType { 67 | //! Seek from the beginning of the stream. 68 | FromBeginning, 69 | //! Seek from the current position in the stream. 70 | FromCurrent, 71 | //! Seek from the end of the stream. 72 | FromEnd 73 | }; // enum SeekType 74 | 75 | //! Seek stream to new position. 76 | virtual void seek( int32_t pos, SeekType type = FromBeginning ) = 0; 77 | 78 | //! \return Position in the stream. 79 | virtual int32_t pos() = 0; 80 | 81 | //! Read data from the stream. 82 | template< typename Type > 83 | void read( Type & retVal, int32_t bytes = 0 ) 84 | { 85 | retVal = Type(0); 86 | if( bytes == 0 ) bytes = sizeof( Type ); 87 | 88 | for( int32_t i = 0; i < bytes; ++i ) 89 | { 90 | Type c = (Type)(unsigned char) getByte(); 91 | 92 | if( eof() ) 93 | throw Exception( L"Unexpected end of file." ); 94 | 95 | if( SystemByteOrder::byteOrder() != m_byteOrder ) 96 | retVal |= ( c << 8 * ( bytes - i - 1 ) ); 97 | else 98 | retVal |= ( c << 8 * i ); 99 | } 100 | } 101 | 102 | private: 103 | //! Byte order. 104 | ByteOrder m_byteOrder; 105 | }; // class Stream 106 | 107 | inline 108 | Stream::Stream( ByteOrder byteOrder ) 109 | : m_byteOrder( byteOrder ) 110 | { 111 | } 112 | 113 | inline 114 | Stream::~Stream() 115 | { 116 | } 117 | 118 | inline Stream::ByteOrder 119 | Stream::byteOrder() const 120 | { 121 | return m_byteOrder; 122 | } 123 | 124 | } /* namespace Excel */ 125 | 126 | #endif // EXCEL__STREAM_HPP__INCLUDED 127 | -------------------------------------------------------------------------------- /read-excel/string.hpp: -------------------------------------------------------------------------------- 1 | 2 | /* 3 | SPDX-FileCopyrightText: 2011-2024 Igor Mironchik 4 | SPDX-License-Identifier: MIT 5 | */ 6 | 7 | #ifndef EXCEL__STRING_HPP__INCLUDED 8 | #define EXCEL__STRING_HPP__INCLUDED 9 | 10 | // C++ include. 11 | #include 12 | #include 13 | #include 14 | #include 15 | 16 | // Excel include. 17 | #include "stream.hpp" 18 | #include "bof.hpp" 19 | 20 | 21 | namespace Excel { 22 | 23 | //! Is high byte present in character. 24 | static const char IsHighByte = 0x01; 25 | 26 | //! Is extended string follows. 27 | static const char IsExtString = 0x04; 28 | 29 | //! Is rich string follows. 30 | static const char IsRichString = 0x08; 31 | 32 | 33 | // 34 | // isHighByte 35 | // 36 | 37 | inline bool 38 | isHighByte( char options ) 39 | { 40 | return ( options & IsHighByte ); 41 | } // isHighByte 42 | 43 | 44 | // 45 | // isExtString 46 | // 47 | 48 | inline bool 49 | isExtString( char options ) 50 | { 51 | return ( options & IsExtString ); 52 | } // isExtString 53 | 54 | 55 | // 56 | // isRichString 57 | // 58 | 59 | inline bool 60 | isRichString( char options ) 61 | { 62 | return ( options & IsRichString ); 63 | } // isRichString 64 | 65 | 66 | // 67 | // isSkipByte 68 | // 69 | 70 | /*! 71 | If string spleated between two or more records 72 | there is one byte on the border wich should be 73 | skipped. 74 | */ 75 | 76 | inline bool 77 | isSkipByte( int32_t pos, const std::vector< int32_t > & borders ) 78 | { 79 | return( std::find( borders.begin(), borders.end(), pos ) != borders.end() ); 80 | } // isSkipByte 81 | 82 | 83 | // 84 | // loadString 85 | // 86 | 87 | //! Load string from the stream. 88 | inline std::wstring 89 | loadString( Stream & stream, 90 | const std::vector< int32_t > & borders, 91 | int32_t lengthFieldSize = 2, 92 | BOF::BiffVersion biffVer = BOF::BIFF8 ) 93 | { 94 | int16_t charactersCount = 0; 95 | char options = 0; 96 | int16_t formattingRuns = 0; 97 | int32_t extStringLength = 0; 98 | 99 | stream.read( charactersCount, lengthFieldSize ); 100 | 101 | if( biffVer == BOF::BIFF7 && charactersCount > 255 ) 102 | throw Exception( L"Wrong format of XLS file." ); 103 | 104 | if( biffVer == BOF::BIFF8 ) 105 | { 106 | stream.read( options, 1 ); 107 | 108 | if( isRichString( options ) ) 109 | stream.read( formattingRuns, 2 ); 110 | 111 | if( isExtString( options ) ) 112 | stream.read( extStringLength, 4 ); 113 | } 114 | 115 | int16_t bytesPerChar = ( biffVer == BOF::BIFF8 ? ( isHighByte( options ) ? 2 : 1 ) : 1 ); 116 | 117 | std::vector< uint16_t > stringData( charactersCount ); 118 | 119 | for( int16_t i = 0; i < charactersCount; ++i ) 120 | { 121 | if( isSkipByte( stream.pos(), borders ) ) 122 | { 123 | stream.read( options, 1 ); 124 | bytesPerChar = ( biffVer == BOF::BIFF8 ? ( isHighByte( options ) ? 2 : 1 ) : 1 ); 125 | } 126 | 127 | stream.read( stringData[ i ], bytesPerChar ); 128 | } 129 | 130 | if( formattingRuns > 0 ) 131 | { 132 | const size_t dummySize = formattingRuns * 4; 133 | std::vector< char > dummy( dummySize ); 134 | 135 | for( size_t i = 0; i < dummySize; ++i ) 136 | stream.read( dummy[ i ], 1 ); 137 | } 138 | 139 | if( extStringLength > 0 ) 140 | { 141 | const size_t dummySize = extStringLength; 142 | std::vector< char > dummy( dummySize ); 143 | 144 | for( size_t i = 0; i < dummySize; ++i ) 145 | stream.read( dummy[ i ], 1 ); 146 | } 147 | 148 | std::wstring str; 149 | str.assign( stringData.begin(), stringData.end() ); 150 | 151 | return str; 152 | } 153 | 154 | } /* namespace Excel */ 155 | 156 | #endif // EXCEL__STRING_HPP__INCLUDED 157 | -------------------------------------------------------------------------------- /sample/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | 2 | project( sample ) 3 | 4 | set( SRC main.cpp ) 5 | 6 | file( COPY sample.xls 7 | DESTINATION ${CMAKE_CURRENT_BINARY_DIR} ) 8 | 9 | include_directories( ${CMAKE_CURRENT_SOURCE_DIR} 10 | ${CMAKE_CURRENT_SOURCE_DIR}/.. ) 11 | 12 | add_executable( sample ${SRC} ) 13 | -------------------------------------------------------------------------------- /sample/main.cpp: -------------------------------------------------------------------------------- 1 | 2 | /* 3 | SPDX-FileCopyrightText: 2011-2024 Igor Mironchik 4 | SPDX-License-Identifier: MIT 5 | */ 6 | 7 | 8 | // Excel include. 9 | #include 10 | #include 11 | 12 | // C++ include. 13 | #include 14 | 15 | 16 | int main() 17 | { 18 | try { 19 | Excel::Book book( "sample.xls" ); 20 | 21 | Excel::Sheet * sheet = book.sheet( 0 ); 22 | 23 | std::wcout << L"There is output of the \"sample.xls\" Excel file." 24 | << std::endl << std::endl; 25 | 26 | std::wcout << L"A1 : " << sheet->cell( 0, 0 ).getString() 27 | << std::endl; 28 | std::wcout << L"A2 : " << sheet->cell( 1, 0 ).getString() 29 | << L" B2 : " << sheet->cell( 1, 1 ).getDouble() << std::endl; 30 | std::wcout << L"A3 : " << sheet->cell( 2, 0 ).getString() 31 | << L" B3 : " << sheet->cell( 2, 1 ).getDouble() << std::endl; 32 | std::wcout << L"A4 : " << sheet->cell( 3, 0 ).getString() 33 | << L" B4 : " << sheet->cell( 3, 1 ).getFormula().getDouble() 34 | << std::endl; 35 | std::wcout << L"A5 : " << sheet->cell( 4, 0 ).getString() 36 | << std::endl << L"Date mode is : " 37 | << ( book.dateMode() == Excel::Book::DateMode::Dec31_1899 ? 38 | L"count of days since 31 December 1899 :" : 39 | L"count of days since 01 January 1904 :" ) 40 | << L" B5 : " << sheet->cell( 4, 1 ).getDouble() 41 | << " days." << std::endl; 42 | 43 | std::wcout << std::endl << L"Thats all. And thanks for using this library." 44 | << std::endl; 45 | } 46 | catch( const Excel::Exception & x ) 47 | { 48 | std::wcout << x.whatAsWString() << std::endl; 49 | } 50 | catch( const std::exception & ) 51 | { 52 | std::wcout << L"Can't open file." << std::endl; 53 | } 54 | 55 | return 0; 56 | } 57 | -------------------------------------------------------------------------------- /sample/sample.xls: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/igormironchik/read-excel/de3f7ebae0e47b1f2394d6a87bd219bc6a796298/sample/sample.xls -------------------------------------------------------------------------------- /test/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | 2 | project( tests ) 3 | 4 | file( COPY data/big.xls 5 | DESTINATION ${CMAKE_CURRENT_BINARY_DIR}/data ) 6 | file( COPY data/datetime.xls 7 | DESTINATION ${CMAKE_CURRENT_BINARY_DIR}/data ) 8 | file( COPY data/sample.xls 9 | DESTINATION ${CMAKE_CURRENT_BINARY_DIR}/data ) 10 | file( COPY data/stringformula.xls 11 | DESTINATION ${CMAKE_CURRENT_BINARY_DIR}/data ) 12 | file( COPY data/test.xls 13 | DESTINATION ${CMAKE_CURRENT_BINARY_DIR}/data ) 14 | file( COPY data/verybig.xls 15 | DESTINATION ${CMAKE_CURRENT_BINARY_DIR}/data ) 16 | file( COPY data/strange.xls 17 | DESTINATION ${CMAKE_CURRENT_BINARY_DIR}/data ) 18 | file( COPY data/MiscOperatorTests.xls 19 | DESTINATION ${CMAKE_CURRENT_BINARY_DIR}/data ) 20 | 21 | if(NOT CMAKE_CXX_COMPILER_ID MATCHES "MSVC") 22 | add_subdirectory( testdocument ) 23 | add_subdirectory( header ) 24 | endif() 25 | 26 | add_subdirectory( stream ) 27 | add_subdirectory( bof ) 28 | add_subdirectory( book ) 29 | add_subdirectory( cell ) 30 | add_subdirectory( complex ) 31 | add_subdirectory( compoundfile ) 32 | add_subdirectory( datetime ) 33 | add_subdirectory( formula ) 34 | add_subdirectory( record ) 35 | add_subdirectory( sst ) 36 | add_subdirectory( string ) 37 | -------------------------------------------------------------------------------- /test/bof/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | 2 | project( test.bof ) 3 | 4 | if( ENABLE_COVERAGE ) 5 | set( CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -g -O0 -fprofile-arcs -ftest-coverage" ) 6 | set( CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} --coverage" ) 7 | endif( ENABLE_COVERAGE ) 8 | 9 | set( SRC main.cpp ) 10 | 11 | include_directories( ${CMAKE_CURRENT_SOURCE_DIR} 12 | ${CMAKE_CURRENT_SOURCE_DIR}/.. 13 | ${CMAKE_CURRENT_SOURCE_DIR}/../.. ) 14 | 15 | add_executable( test.bof ${SRC} ) 16 | 17 | target_link_libraries( test.bof teststream ) 18 | 19 | add_test( NAME test.bof 20 | COMMAND ${CMAKE_CURRENT_BINARY_DIR}/test.bof 21 | WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR} ) 22 | -------------------------------------------------------------------------------- /test/bof/main.cpp: -------------------------------------------------------------------------------- 1 | 2 | /* 3 | SPDX-FileCopyrightText: 2011-2024 Igor Mironchik 4 | SPDX-License-Identifier: MIT 5 | */ 6 | 7 | // Excel include. 8 | #include 9 | #include 10 | 11 | // unit test helper. 12 | #define DOCTEST_CONFIG_IMPLEMENT_WITH_MAIN 13 | #include 14 | #include 15 | #include 16 | 17 | 18 | const auto data = make_data( 19 | 0x09u, 0x08u, 0x10u, 0x00u, 20 | 0x00u, 0x06u, 0x10u, 0x00u, 0xBBu, 0x0Du, 0xCCu, 0x07u, 21 | 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, 0x00u 22 | ); // data 23 | 24 | 25 | TEST_CASE( "test_bof" ) 26 | { 27 | TestStream stream( &data[ 0 ], 20 ); 28 | 29 | Excel::Record record( stream ); 30 | 31 | Excel::BOF bof; 32 | bof.parse( record ); 33 | 34 | REQUIRE( bof.version() == Excel::BOF::BIFF8 ); 35 | REQUIRE( bof.type() == Excel::BOF::WorkSheet ); 36 | } 37 | -------------------------------------------------------------------------------- /test/book/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | 2 | project( test.book ) 3 | 4 | if( ENABLE_COVERAGE ) 5 | set( CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -g -O0 -fprofile-arcs -ftest-coverage" ) 6 | set( CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} --coverage" ) 7 | endif( ENABLE_COVERAGE ) 8 | 9 | set( SRC main.cpp ) 10 | 11 | include_directories( ${CMAKE_CURRENT_SOURCE_DIR} 12 | ${CMAKE_CURRENT_SOURCE_DIR}/.. 13 | ${CMAKE_CURRENT_SOURCE_DIR}/../.. ) 14 | 15 | add_executable( test.book ${SRC} ) 16 | 17 | add_test( NAME test.book 18 | COMMAND ${CMAKE_CURRENT_BINARY_DIR}/test.book 19 | WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/../.. ) 20 | -------------------------------------------------------------------------------- /test/book/main.cpp: -------------------------------------------------------------------------------- 1 | 2 | /* 3 | SPDX-FileCopyrightText: 2011-2024 Igor Mironchik 4 | SPDX-License-Identifier: MIT 5 | */ 6 | 7 | // Excel include. 8 | #include 9 | 10 | // C++ include. 11 | #include 12 | 13 | // unit test helper. 14 | #define DOCTEST_CONFIG_IMPLEMENT_WITH_MAIN 15 | #include 16 | 17 | 18 | TEST_CASE( "test_book" ) 19 | { 20 | Excel::Book book( "test/data/test.xls" ); 21 | 22 | REQUIRE( book.sheetsCount() == 1 ); 23 | 24 | Excel::Sheet * sheet = book.sheet( 0 ); 25 | 26 | REQUIRE( sheet->rowsCount() == 3 ); 27 | REQUIRE( sheet->columnsCount() == 4 ); 28 | 29 | REQUIRE( sheet->cell( 0, 0 ).getString() == L"String #1" ); 30 | REQUIRE( sheet->cell( 1, 0 ).getString() == L"String #2" ); 31 | REQUIRE( sheet->cell( 2, 0 ).getString() == L"String #3" ); 32 | 33 | REQUIRE( std::fabs( sheet->cell( 0, 1 ).getDouble() - 1.0 ) < 1E-9 ); 34 | REQUIRE( std::fabs( sheet->cell( 1, 1 ).getDouble() - 2.0 ) < 1E-9 ); 35 | REQUIRE( std::fabs( sheet->cell( 2, 1 ).getDouble() - 3.0 ) < 1E-9 ); 36 | 37 | REQUIRE( std::fabs( sheet->cell( 0, 2 ).getDouble() - 0.1 ) < 1E-9 ); 38 | REQUIRE( std::fabs( sheet->cell( 1, 2 ).getDouble() - 0.2 ) < 1E-9 ); 39 | REQUIRE( std::fabs( sheet->cell( 2, 2 ).getDouble() - 0.3 ) < 1E-9 ); 40 | 41 | REQUIRE( std::fabs( sheet->cell( 0, 3 ).getFormula().getDouble() - 1.1 ) < 1E-9 ); 42 | REQUIRE( std::fabs( sheet->cell( 1, 3 ).getFormula().getDouble() - 2.2 ) < 1E-9 ); 43 | REQUIRE( std::fabs( sheet->cell( 2, 3 ).getFormula().getDouble() - 3.3 ) < 1E-9 ); 44 | 45 | book.clear(); 46 | 47 | REQUIRE( book.sheetsCount() == 0 ); 48 | } 49 | 50 | TEST_CASE( "test_book_via_stream" ) 51 | { 52 | std::ifstream fileStream( "test/data/strange.xls", std::ios::in | std::ios::binary ); 53 | Excel::Book book( fileStream ); 54 | 55 | REQUIRE( book.sheetsCount() == 3 ); 56 | 57 | Excel::Sheet * sheet = book.sheet( 0 ); 58 | const auto text = sheet->cell( 0, 0 ).getString(); 59 | REQUIRE( text.find( L"Somefile" ) != std::wstring::npos ); 60 | REQUIRE( text.find( L"abcd" ) == 0 ); 61 | REQUIRE( text.length() == 255 ); 62 | } 63 | 64 | struct CustomStorage : public Excel::EmptyStorage { 65 | std::wstring m_text; 66 | std::wstring m_sheetName; 67 | void onSharedString( size_t sstSize, size_t idx, const std::wstring & value ) override; 68 | void onSheet( size_t idx, const std::wstring & value ) override; 69 | }; // struct CustomStorage 70 | 71 | inline void 72 | CustomStorage::onSharedString( size_t , size_t , const std::wstring & value ) 73 | { 74 | m_text += value; 75 | } 76 | 77 | inline void 78 | CustomStorage::onSheet( size_t , const std::wstring & value ) 79 | { 80 | m_sheetName = value; 81 | } 82 | 83 | TEST_CASE( "test_book_custom_storage" ) 84 | { 85 | std::ifstream fileStream( "test/data/test.xls", std::ios::in | std::ios::binary ); 86 | CustomStorage storage; 87 | Excel::Parser::loadBook( fileStream, storage ); 88 | 89 | REQUIRE( storage.m_text.find( L"String #1" ) != std::wstring::npos ); 90 | REQUIRE( storage.m_sheetName == L"Sheet" ); 91 | } 92 | 93 | TEST_CASE( "test_book_empty_storage" ) 94 | { 95 | std::ifstream fileStream( "test/data/test.xls", std::ios::in | std::ios::binary ); 96 | Excel::EmptyStorage emptyStorage; 97 | Excel::Parser::loadBook( fileStream, emptyStorage ); 98 | } 99 | 100 | TEST_CASE( "test_very_small_book" ) 101 | { 102 | Excel::Book book( "test/data/MiscOperatorTests.xls" ); 103 | 104 | REQUIRE( book.sheetsCount() == 3 ); 105 | REQUIRE( book.sheet( 0 )->sheetName() == L"Sheet1" ); 106 | } 107 | -------------------------------------------------------------------------------- /test/cell/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | 2 | project( test.cell ) 3 | 4 | if( ENABLE_COVERAGE ) 5 | set( CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -g -O0 -fprofile-arcs -ftest-coverage" ) 6 | set( CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} --coverage" ) 7 | endif( ENABLE_COVERAGE ) 8 | 9 | set( SRC main.cpp ) 10 | 11 | include_directories( ${CMAKE_CURRENT_SOURCE_DIR} 12 | ${CMAKE_CURRENT_SOURCE_DIR}/.. 13 | ${CMAKE_CURRENT_SOURCE_DIR}/../.. ) 14 | 15 | add_executable( test.cell ${SRC} ) 16 | 17 | add_test( NAME test.cell 18 | COMMAND ${CMAKE_CURRENT_BINARY_DIR}/test.cell 19 | WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR} ) 20 | -------------------------------------------------------------------------------- /test/cell/main.cpp: -------------------------------------------------------------------------------- 1 | 2 | /* 3 | SPDX-FileCopyrightText: 2011-2024 Igor Mironchik 4 | SPDX-License-Identifier: MIT 5 | */ 6 | 7 | // Excel include. 8 | #include 9 | 10 | // unit test helper. 11 | #define DOCTEST_CONFIG_IMPLEMENT_WITH_MAIN 12 | #include 13 | 14 | 15 | TEST_CASE( "test_cell" ) 16 | { 17 | Excel::Cell cell; 18 | 19 | cell.setData( 0.123456789 ); 20 | 21 | REQUIRE( cell.getDouble() == 0.123456789 ); 22 | 23 | cell.setData( L"1234567890" ); 24 | 25 | REQUIRE( cell.getString() == L"1234567890" ); 26 | 27 | cell.setData( 1234.0 ); 28 | 29 | REQUIRE( cell.getDouble() == 1234.0 ); 30 | 31 | cell.setData( L"qwerty" ); 32 | 33 | REQUIRE( cell.getString() == L"qwerty" ); 34 | 35 | } 36 | -------------------------------------------------------------------------------- /test/complex/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | 2 | project( test.complex ) 3 | 4 | if( ENABLE_COVERAGE ) 5 | set( CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -g -O0 -fprofile-arcs -ftest-coverage" ) 6 | set( CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} --coverage" ) 7 | endif( ENABLE_COVERAGE ) 8 | 9 | set( SRC main.cpp ) 10 | 11 | include_directories( ${CMAKE_CURRENT_SOURCE_DIR} 12 | ${CMAKE_CURRENT_SOURCE_DIR}/.. 13 | ${CMAKE_CURRENT_SOURCE_DIR}/../.. ) 14 | 15 | add_executable( test.complex ${SRC} ) 16 | 17 | add_test( NAME test.complex 18 | COMMAND ${CMAKE_CURRENT_BINARY_DIR}/test.complex 19 | WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/../.. ) 20 | 21 | if( BUILD_BENCHMARK ) 22 | add_executable( test.complex.benchmark benchmark.cpp ) 23 | target_link_libraries( test.complex.benchmark PRIVATE xlsreader ) 24 | endif( BUILD_BENCHMARK ) 25 | -------------------------------------------------------------------------------- /test/complex/benchmark.cpp: -------------------------------------------------------------------------------- 1 | 2 | /* 3 | SPDX-FileCopyrightText: 2011-2024 Igor Mironchik 4 | SPDX-License-Identifier: MIT 5 | */ 6 | 7 | // Excel include. 8 | #include 9 | #include 10 | 11 | // C++ include. 12 | #include 13 | #include 14 | #include 15 | 16 | #include 17 | 18 | // unit test helper. 19 | #define DOCTEST_CONFIG_IMPLEMENT_WITH_MAIN 20 | #include 21 | 22 | using namespace std::chrono; 23 | using namespace xls; 24 | 25 | 26 | TEST_CASE( "test_complex" ) 27 | { 28 | auto start = high_resolution_clock::now(); 29 | 30 | { 31 | Excel::Book book( "test/data/sample.xls" ); 32 | 33 | REQUIRE( book.sheetsCount() == 1 ); 34 | 35 | Excel::Sheet * sheet = book.sheet( 0 ); 36 | 37 | REQUIRE( sheet->rowsCount() == 5 ); 38 | REQUIRE( sheet->columnsCount() == 2 ); 39 | 40 | REQUIRE( sheet->cell( 0, 0 ).getString() == L"This is a string." ); 41 | 42 | REQUIRE( sheet->cell( 1, 0 ).getString() == 43 | L"There is a double in the next cell: (1,2345)." ); 44 | 45 | REQUIRE( std::fabs( sheet->cell( 1, 1 ).getDouble() - 1.2345 ) < 1E-9 ); 46 | 47 | REQUIRE( sheet->cell( 2, 0 ).getString() == 48 | L"There is a double in the next cell: (5,4321)." ); 49 | 50 | REQUIRE( std::fabs( sheet->cell( 2, 1 ).getDouble() - 5.4321 ) < 1E-9 ); 51 | 52 | 53 | REQUIRE( sheet->cell( 3, 0 ).getString() == 54 | L"There is a formula in the next cell: (=B2+B3 = 6,6666)" ); 55 | 56 | REQUIRE( sheet->cell( 4, 0 ).getString() == 57 | L"There is a date and time in the next cell:" ); 58 | } 59 | 60 | { 61 | Excel::Book book( "test/data/big.xls" ); 62 | 63 | REQUIRE( book.sheetsCount() == 1 ); 64 | 65 | Excel::Sheet * sheet = book.sheet( 0 ); 66 | 67 | REQUIRE( sheet->rowsCount() == 7992 ); 68 | REQUIRE( sheet->columnsCount() == 26 ); 69 | 70 | 71 | REQUIRE( std::fabs( sheet->cell( 0, 0 ).getDouble() - 1.0 ) < 1E-9 ); 72 | 73 | REQUIRE( std::fabs( sheet->cell( 998, 25 ).getDouble() - 9.0 ) < 1E-9 ); 74 | 75 | REQUIRE( std::fabs( sheet->cell( 7991, 25 ).getDouble() - 9.0 ) < 1E-9 ); 76 | } 77 | 78 | { 79 | Excel::Book book( "test/data/verybig.xls" ); 80 | 81 | REQUIRE( book.sheetsCount() == 1 ); 82 | 83 | Excel::Sheet * sheet = book.sheet( 0 ); 84 | 85 | REQUIRE( sheet->rowsCount() == 10000 ); 86 | REQUIRE( sheet->columnsCount() == 26 ); 87 | 88 | REQUIRE( std::fabs( sheet->cell( 0, 0 ).getDouble() - 1.0 ) < 1E-9 ); 89 | } 90 | 91 | auto stop = high_resolution_clock::now(); 92 | 93 | auto duration = duration_cast< milliseconds > ( stop - start ); 94 | 95 | std::cout << "read-excel execution time is " << duration.count() << " ms." << std::endl; 96 | } 97 | 98 | TEST_CASE( "test_complex_libxls" ) 99 | { 100 | auto start = high_resolution_clock::now(); 101 | 102 | { 103 | xls_error_t error = LIBXLS_OK; 104 | xlsWorkBook * wb = xls_open_file( "test/data/sample.xls", "UTF-8", &error ); 105 | 106 | if( wb == NULL ) 107 | { 108 | printf("Error reading file: %s\n", xls_getError(error)); 109 | REQUIRE( false ); 110 | } 111 | 112 | REQUIRE( wb->sheets.count == 1 ); 113 | 114 | xlsWorkSheet * sheet = xls_getWorkSheet( wb, 0 ); 115 | error = xls_parseWorkSheet( sheet ); 116 | 117 | REQUIRE( sheet->rows.lastrow == 4 ); 118 | REQUIRE( sheet->rows.lastcol == 1 ); 119 | 120 | xlsRow * row0 = xls_row( sheet, 0 ); 121 | xlsCell * cell = &row0->cells.cell[ 0 ]; 122 | 123 | REQUIRE( strcmp( cell->str, "This is a string." ) == 0 ); 124 | 125 | xlsRow * row1 = xls_row( sheet, 1 ); 126 | cell = &row1->cells.cell[ 0 ]; 127 | 128 | REQUIRE( strcmp( cell->str, 129 | "There is a double in the next cell: (1,2345)." ) == 0 ); 130 | 131 | cell = &row1->cells.cell[ 1 ]; 132 | 133 | REQUIRE( std::fabs( cell->d - 1.2345 ) < 1E-9 ); 134 | 135 | xlsRow * row2 = xls_row( sheet, 2 ); 136 | cell = &row2->cells.cell[ 0 ]; 137 | 138 | REQUIRE( strcmp( cell->str, 139 | "There is a double in the next cell: (5,4321)." ) == 0 ); 140 | 141 | cell = &row2->cells.cell[ 1 ]; 142 | 143 | REQUIRE( std::fabs( cell->d - 5.4321 ) < 1E-9 ); 144 | 145 | xlsRow * row3 = xls_row( sheet, 3 ); 146 | cell = &row3->cells.cell[ 0 ]; 147 | 148 | REQUIRE( strcmp( cell->str, 149 | "There is a formula in the next cell: (=B2+B3 = 6,6666)" ) == 0 ); 150 | 151 | xlsRow * row4 = xls_row( sheet, 4 ); 152 | cell = &row4->cells.cell[ 0 ]; 153 | 154 | REQUIRE( strcmp( cell->str, 155 | "There is a date and time in the next cell:" ) == 0 ); 156 | 157 | xls_close_WS( sheet ); 158 | xls_close_WB( wb ); 159 | } 160 | 161 | { 162 | xls_error_t error = LIBXLS_OK; 163 | xlsWorkBook * wb = xls_open_file( "test/data/big.xls", "UTF-8", &error ); 164 | 165 | if( wb == NULL ) 166 | { 167 | printf("Error reading file: %s\n", xls_getError(error)); 168 | REQUIRE( false ); 169 | } 170 | 171 | REQUIRE( wb->sheets.count == 1 ); 172 | 173 | xlsWorkSheet * sheet = xls_getWorkSheet( wb, 0 ); 174 | error = xls_parseWorkSheet( sheet ); 175 | 176 | REQUIRE( sheet->rows.lastrow == 7991 ); 177 | REQUIRE( sheet->rows.lastcol == 25 ); 178 | 179 | xlsRow * row0 = xls_row( sheet, 0 ); 180 | xlsCell * cell = &row0->cells.cell[ 0 ]; 181 | 182 | REQUIRE( std::fabs( cell->d - 1.0 ) < 1E-9 ); 183 | 184 | xlsRow * row998 = xls_row( sheet, 998 ); 185 | cell = &row998->cells.cell[ 25 ]; 186 | 187 | REQUIRE( std::fabs( cell->d - 9.0 ) < 1E-9 ); 188 | 189 | xlsRow * row7991 = xls_row( sheet, 7991 ); 190 | cell = &row7991->cells.cell[ 25 ]; 191 | 192 | REQUIRE( std::fabs( cell->d - 9.0 ) < 1E-9 ); 193 | 194 | xls_close_WS( sheet ); 195 | xls_close_WB( wb ); 196 | } 197 | 198 | { 199 | xls_error_t error = LIBXLS_OK; 200 | xlsWorkBook * wb = xls_open_file( "test/data/verybig.xls", "UTF-8", &error ); 201 | 202 | if( wb == NULL ) 203 | { 204 | printf("Error reading file: %s\n", xls_getError(error)); 205 | REQUIRE( false ); 206 | } 207 | 208 | REQUIRE( wb->sheets.count == 1 ); 209 | 210 | xlsWorkSheet * sheet = xls_getWorkSheet( wb, 0 ); 211 | error = xls_parseWorkSheet( sheet ); 212 | 213 | REQUIRE( sheet->rows.lastrow == 9999 ); 214 | REQUIRE( sheet->rows.lastcol == 25 ); 215 | 216 | xlsRow * row0 = xls_row( sheet, 0 ); 217 | xlsCell * cell = &row0->cells.cell[ 0 ]; 218 | 219 | REQUIRE( std::fabs( cell->d - 1.0 ) < 1E-9 ); 220 | 221 | xls_close_WS( sheet ); 222 | xls_close_WB( wb ); 223 | } 224 | 225 | auto stop = high_resolution_clock::now(); 226 | 227 | auto duration = duration_cast< milliseconds > ( stop - start ); 228 | 229 | std::cout << "libxls execution time is " << duration.count() << " ms." << std::endl; 230 | } 231 | -------------------------------------------------------------------------------- /test/complex/main.cpp: -------------------------------------------------------------------------------- 1 | 2 | /* 3 | SPDX-FileCopyrightText: 2011-2024 Igor Mironchik 4 | SPDX-License-Identifier: MIT 5 | */ 6 | 7 | // Excel include. 8 | #include 9 | #include 10 | 11 | // C++ include. 12 | #include 13 | #include 14 | 15 | // unit test helper. 16 | #define DOCTEST_CONFIG_IMPLEMENT_WITH_MAIN 17 | #include 18 | 19 | using namespace std::chrono; 20 | 21 | 22 | TEST_CASE( "test_complex" ) 23 | { 24 | const auto start = high_resolution_clock::now(); 25 | 26 | { 27 | Excel::Book book( "test/data/sample.xls" ); 28 | 29 | REQUIRE( book.sheetsCount() == 1 ); 30 | 31 | Excel::Sheet * sheet = book.sheet( 0 ); 32 | 33 | REQUIRE( sheet->rowsCount() == 5 ); 34 | REQUIRE( sheet->columnsCount() == 2 ); 35 | 36 | REQUIRE( sheet->cell( 0, 0 ).dataType() == Excel::Cell::DataType::String ); 37 | REQUIRE( sheet->cell( 0, 0 ).getString() == L"This is a string." ); 38 | 39 | REQUIRE( sheet->cell( 1, 0 ).dataType() == Excel::Cell::DataType::String ); 40 | REQUIRE( sheet->cell( 1, 0 ).getString() == 41 | L"There is a double in the next cell: (1,2345)." ); 42 | REQUIRE( sheet->cell( 1, 1 ).dataType() == Excel::Cell::DataType::Double ); 43 | REQUIRE( std::fabs( sheet->cell( 1, 1 ).getDouble() - 1.2345 ) < 1E-9 ); 44 | 45 | REQUIRE( sheet->cell( 2, 0 ).dataType() == Excel::Cell::DataType::String ); 46 | REQUIRE( sheet->cell( 2, 0 ).getString() == 47 | L"There is a double in the next cell: (5,4321)." ); 48 | REQUIRE( sheet->cell( 2, 1 ).dataType() == Excel::Cell::DataType::Double ); 49 | REQUIRE( std::fabs( sheet->cell( 2, 1 ).getDouble() - 5.4321 ) < 1E-9 ); 50 | 51 | REQUIRE( sheet->cell( 3, 0 ).dataType() == Excel::Cell::DataType::String ); 52 | REQUIRE( sheet->cell( 3, 0 ).getString() == 53 | L"There is a formula in the next cell: (=B2+B3 = 6,6666)" ); 54 | REQUIRE( sheet->cell( 3, 1 ).dataType() == Excel::Cell::DataType::Formula ); 55 | REQUIRE( sheet->cell( 3, 1 ).getFormula().valueType() == 56 | Excel::Formula::DoubleValue ); 57 | REQUIRE( std::fabs( sheet->cell( 3, 1 ).getFormula().getDouble() - 6.6666 ) < 1E-9 ); 58 | 59 | REQUIRE( sheet->cell( 4, 0 ).dataType() == Excel::Cell::DataType::String ); 60 | REQUIRE( sheet->cell( 4, 0 ).getString() == 61 | L"There is a date and time in the next cell:" ); 62 | REQUIRE( book.dateMode() == Excel::Book::DateMode::Dec31_1899 ); 63 | REQUIRE( sheet->cell( 4, 1 ).dataType() == Excel::Cell::DataType::Double ); 64 | REQUIRE( std::fabs( sheet->cell( 4, 1 ).getDouble() - 43100.9999884259 ) < 1E-9 ); 65 | } 66 | 67 | { 68 | Excel::Book book( "test/data/big.xls" ); 69 | 70 | REQUIRE( book.sheetsCount() == 1 ); 71 | 72 | Excel::Sheet * sheet = book.sheet( 0 ); 73 | 74 | REQUIRE( sheet->rowsCount() == 7992 ); 75 | REQUIRE( sheet->columnsCount() == 26 ); 76 | 77 | REQUIRE( sheet->cell( 0, 0 ).dataType() == Excel::Cell::DataType::Double ); 78 | REQUIRE( std::fabs( sheet->cell( 0, 0 ).getDouble() - 1.0 ) < 1E-9 ); 79 | REQUIRE( sheet->cell( 998, 25 ).dataType() == Excel::Cell::DataType::Double ); 80 | REQUIRE( std::fabs( sheet->cell( 998, 25 ).getDouble() - 9.0 ) < 1E-9 ); 81 | REQUIRE( sheet->cell( 7991, 25 ).dataType() == Excel::Cell::DataType::Double ); 82 | REQUIRE( std::fabs( sheet->cell( 7991, 25 ).getDouble() - 9.0 ) < 1E-9 ); 83 | } 84 | 85 | { 86 | Excel::Book book( "test/data/verybig.xls" ); 87 | 88 | REQUIRE( book.sheetsCount() == 1 ); 89 | 90 | Excel::Sheet * sheet = book.sheet( 0 ); 91 | 92 | REQUIRE( sheet->rowsCount() == 10000 ); 93 | REQUIRE( sheet->columnsCount() == 26 ); 94 | 95 | REQUIRE( sheet->cell( 0, 0 ).dataType() == Excel::Cell::DataType::Double ); 96 | REQUIRE( std::fabs( sheet->cell( 0, 0 ).getDouble() - 1.0 ) < 1E-9 ); 97 | REQUIRE( sheet->cell( 9999, 25 ).dataType() == Excel::Cell::DataType::Formula ); 98 | REQUIRE( sheet->cell( 9999, 25 ).getFormula().valueType() == Excel::Formula::DoubleValue ); 99 | REQUIRE( std::fabs( sheet->cell( 9999, 25 ).getFormula().getDouble() - 260000.0 ) < 1E-9 ); 100 | 101 | REQUIRE( sheet->cell( 10000, 26 ).isNull() ); 102 | 103 | REQUIRE_THROWS_AS( book.sheet( 1 ), Excel::Exception ); 104 | } 105 | 106 | { 107 | Excel::Book book( "test/data/stringformula.xls" ); 108 | 109 | REQUIRE( book.sheetsCount() == 1 ); 110 | 111 | Excel::Sheet * sheet = book.sheet( 0 ); 112 | 113 | REQUIRE( sheet->rowsCount() == 2 ); 114 | REQUIRE( sheet->columnsCount() == 3 ); 115 | 116 | REQUIRE( sheet->cell( 0, 0 ).dataType() == Excel::Cell::DataType::String ); 117 | REQUIRE( sheet->cell( 0, 0 ).getString() == L"str1" ); 118 | 119 | REQUIRE( sheet->cell( 0, 1 ).dataType() == Excel::Cell::DataType::String ); 120 | REQUIRE( sheet->cell( 0, 1 ).getString() == L"str2" ); 121 | 122 | REQUIRE( sheet->cell( 0, 2 ).dataType() == Excel::Cell::DataType::Formula ); 123 | REQUIRE( sheet->cell( 0, 2 ).getFormula().valueType() == Excel::Formula::StringValue ); 124 | REQUIRE( sheet->cell( 0, 2 ).getFormula().getString() == L"str1str2" ); 125 | 126 | REQUIRE( sheet->cell( 1, 0 ).dataType() == Excel::Cell::DataType::String ); 127 | REQUIRE( sheet->cell( 1, 0 ).getString() == L"str1" ); 128 | 129 | REQUIRE( sheet->cell( 1, 1 ).dataType() == Excel::Cell::DataType::String ); 130 | REQUIRE( sheet->cell( 1, 1 ).getString() == L"str1" ); 131 | 132 | REQUIRE( sheet->cell( 1, 2 ).dataType() == Excel::Cell::DataType::Formula ); 133 | REQUIRE( sheet->cell( 1, 2 ).getFormula().valueType() == Excel::Formula::EmptyCell ); 134 | } 135 | 136 | try { 137 | Excel::Book book( "nofile.xls" ); 138 | 139 | REQUIRE( false ); 140 | } 141 | catch( const Excel::Exception & x ) 142 | { 143 | REQUIRE( x.whatAsWString() == L"Unable to open file : nofile.xls" ); 144 | } 145 | 146 | const auto stop = high_resolution_clock::now(); 147 | 148 | const auto duration = duration_cast< milliseconds > ( stop - start ); 149 | 150 | std::cout << "Execution time is " << duration.count() << " ms." << std::endl; 151 | } 152 | -------------------------------------------------------------------------------- /test/compoundfile/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | 2 | project( test.compoundfile ) 3 | 4 | if( ENABLE_COVERAGE ) 5 | set( CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -g -O0 -fprofile-arcs -ftest-coverage" ) 6 | set( CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} --coverage" ) 7 | endif( ENABLE_COVERAGE ) 8 | 9 | set( SRC main.cpp ) 10 | 11 | include_directories( ${CMAKE_CURRENT_SOURCE_DIR} 12 | ${CMAKE_CURRENT_SOURCE_DIR}/.. 13 | ${CMAKE_CURRENT_SOURCE_DIR}/../.. ) 14 | 15 | add_executable( test.compoundfile ${SRC} ) 16 | 17 | add_test( NAME test.compoundfile 18 | COMMAND ${CMAKE_CURRENT_BINARY_DIR}/test.compoundfile 19 | WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/../.. ) 20 | -------------------------------------------------------------------------------- /test/compoundfile/main.cpp: -------------------------------------------------------------------------------- 1 | 2 | /* 3 | SPDX-FileCopyrightText: 2011-2024 Igor Mironchik 4 | SPDX-License-Identifier: MIT 5 | */ 6 | 7 | // Excel include. 8 | #include 9 | #include 10 | 11 | // unit test helper. 12 | #define DOCTEST_CONFIG_IMPLEMENT_WITH_MAIN 13 | #include 14 | 15 | 16 | // 17 | // test_directory 18 | // 19 | 20 | TEST_CASE( "test_directory" ) 21 | { 22 | CompoundFile::File file( "./test/data/test.xls" ); 23 | 24 | { 25 | CompoundFile::Directory dir = file.directory( L"Workbook" ); 26 | 27 | REQUIRE( dir.name() == L"Workbook" ); 28 | REQUIRE( dir.type() == CompoundFile::Directory::UserStream ); 29 | REQUIRE( dir.streamSecID() == 0x02 ); 30 | REQUIRE( dir.streamSize() == 0x3480 ); 31 | REQUIRE( dir.rightChild() == 0xFFFFFFFF ); 32 | REQUIRE( dir.leftChild() == 0x04 ); 33 | REQUIRE( dir.rootNode() == 0xFFFFFFFF ); 34 | } 35 | 36 | { 37 | const wchar_t data[] = { 38 | 0x01, 0x43, 0x6F, 0x6D, 0x70, 0x4F, 0x62, 0x6A, 0x00 39 | }; 40 | 41 | CompoundFile::Directory dir = file.directory( data ); 42 | 43 | REQUIRE( dir.name() == data ); 44 | REQUIRE( dir.type() == CompoundFile::Directory::UserStream ); 45 | REQUIRE( dir.streamSecID() == 0x08 ); 46 | REQUIRE( dir.streamSize() == 0x6D ); 47 | REQUIRE( dir.rightChild() == 0xFFFFFFFF ); 48 | REQUIRE( dir.leftChild() == 0xFFFFFFFF ); 49 | REQUIRE( dir.rootNode() == 0xFFFFFFFF ); 50 | } 51 | 52 | { 53 | const wchar_t data[] = { 54 | 0x05, 0x44, 0x6F, 0x63, 0x75, 0x6D, 0x65, 0x6E, 55 | 0x74, 0x53, 0x75, 0x6D, 0x6D, 0x61, 0x72, 0x79, 56 | 0x49, 0x6E, 0x66, 0x6F, 0x72, 0x6D, 0x61, 0x74, 57 | 0x69, 0x6F, 0x6E, 0x00 58 | }; 59 | 60 | CompoundFile::Directory dir = file.directory( data ); 61 | 62 | REQUIRE( dir.name() == data ); 63 | REQUIRE( dir.type() == CompoundFile::Directory::UserStream ); 64 | REQUIRE( dir.streamSecID() == 0x04 ); 65 | REQUIRE( dir.streamSize() == 0xEC ); 66 | REQUIRE( dir.rightChild() == 0xFFFFFFFF ); 67 | REQUIRE( dir.leftChild() == 0xFFFFFFFF ); 68 | REQUIRE( dir.rootNode() == 0xFFFFFFFF ); 69 | } 70 | 71 | { 72 | const wchar_t data[] = { 73 | 0x05, 0x53, 0x75, 0x6D, 0x6D, 0x61, 0x72, 0x79, 74 | 0x49, 0x6E, 0x66, 0x6F, 0x72, 0x6D, 0x61, 0x74, 75 | 0x69, 0x6F, 0x6E, 0x00 76 | }; 77 | 78 | CompoundFile::Directory dir = file.directory( data ); 79 | 80 | REQUIRE( dir.name() == data ); 81 | REQUIRE( dir.type() == CompoundFile::Directory::UserStream ); 82 | REQUIRE( dir.streamSecID() == 0x00 ); 83 | REQUIRE( dir.streamSize() == 0xE0 ); 84 | REQUIRE( dir.rightChild() == 0x03 ); 85 | REQUIRE( dir.leftChild() == 0x01 ); 86 | REQUIRE( dir.rootNode() == 0xFFFFFFFF ); 87 | } 88 | 89 | REQUIRE_THROWS_AS( file.directory( L"ThereIsNoSuchDir" ), 90 | CompoundFile::Exception ); 91 | } 92 | 93 | 94 | // 95 | // test_stream 96 | // 97 | 98 | TEST_CASE( "test_stream" ) 99 | { 100 | CompoundFile::File file( "./test/data/test.xls" ); 101 | 102 | std::unique_ptr< Excel::Stream > stream( file.stream( 103 | file.directory( L"Workbook" ) ) ); 104 | 105 | stream->seek( 512, Excel::Stream::FromBeginning ); 106 | 107 | REQUIRE( stream->getByte() == (char) 0x01 ); 108 | REQUIRE( stream->getByte() == (char) 0x00 ); 109 | REQUIRE( stream->getByte() == (char) 0x3F ); 110 | REQUIRE( stream->getByte() == (char) 0x00 ); 111 | 112 | stream->seek( -6, Excel::Stream::FromCurrent ); 113 | 114 | REQUIRE( stream->getByte() == (char) 0xDC ); 115 | REQUIRE( stream->getByte() == (char) 0x00 ); 116 | 117 | stream->seek( 128, Excel::Stream::FromEnd ); 118 | 119 | REQUIRE( stream->getByte() == (char) 0x66 ); 120 | REQUIRE( stream->getByte() == (char) 0x0A ); 121 | REQUIRE( stream->getByte() == (char) 0x40 ); 122 | REQUIRE( stream->getByte() == (char) 0x08 ); 123 | } 124 | 125 | 126 | // 127 | // test_stream 128 | // 129 | 130 | TEST_CASE( "test_stream_name" ) 131 | { 132 | std::ifstream fileStream( "./test/data/test.xls", std::ios::in | std::ios::binary ); 133 | CompoundFile::File file( fileStream ); 134 | 135 | std::unique_ptr< Excel::Stream > stream( file.stream( 136 | file.directory( L"Workbook" ) ) ); 137 | 138 | stream->seek( 512, Excel::Stream::FromBeginning ); 139 | 140 | REQUIRE( stream->getByte() == (char) 0x01 ); 141 | } 142 | -------------------------------------------------------------------------------- /test/data/MiscOperatorTests.xls: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/igormironchik/read-excel/de3f7ebae0e47b1f2394d6a87bd219bc6a796298/test/data/MiscOperatorTests.xls -------------------------------------------------------------------------------- /test/data/big.xls: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/igormironchik/read-excel/de3f7ebae0e47b1f2394d6a87bd219bc6a796298/test/data/big.xls -------------------------------------------------------------------------------- /test/data/datetime.xls: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/igormironchik/read-excel/de3f7ebae0e47b1f2394d6a87bd219bc6a796298/test/data/datetime.xls -------------------------------------------------------------------------------- /test/data/sample.xls: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/igormironchik/read-excel/de3f7ebae0e47b1f2394d6a87bd219bc6a796298/test/data/sample.xls -------------------------------------------------------------------------------- /test/data/strange.xls: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/igormironchik/read-excel/de3f7ebae0e47b1f2394d6a87bd219bc6a796298/test/data/strange.xls -------------------------------------------------------------------------------- /test/data/stringformula.xls: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/igormironchik/read-excel/de3f7ebae0e47b1f2394d6a87bd219bc6a796298/test/data/stringformula.xls -------------------------------------------------------------------------------- /test/data/test.xls: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/igormironchik/read-excel/de3f7ebae0e47b1f2394d6a87bd219bc6a796298/test/data/test.xls -------------------------------------------------------------------------------- /test/data/verybig.xls: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/igormironchik/read-excel/de3f7ebae0e47b1f2394d6a87bd219bc6a796298/test/data/verybig.xls -------------------------------------------------------------------------------- /test/datetime/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | 2 | project( test.datetime ) 3 | 4 | if( ENABLE_COVERAGE ) 5 | set( CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -g -O0 -fprofile-arcs -ftest-coverage" ) 6 | set( CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} --coverage" ) 7 | endif( ENABLE_COVERAGE ) 8 | 9 | set( SRC main.cpp ) 10 | 11 | include_directories( ${CMAKE_CURRENT_SOURCE_DIR} 12 | ${CMAKE_CURRENT_SOURCE_DIR}/.. 13 | ${CMAKE_CURRENT_SOURCE_DIR}/../.. ) 14 | 15 | add_executable( test.datetime ${SRC} ) 16 | 17 | add_test( NAME test.datetime 18 | COMMAND ${CMAKE_CURRENT_BINARY_DIR}/test.datetime 19 | WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/../.. ) 20 | -------------------------------------------------------------------------------- /test/datetime/main.cpp: -------------------------------------------------------------------------------- 1 | 2 | /* 3 | SPDX-FileCopyrightText: 2011-2024 Igor Mironchik 4 | SPDX-License-Identifier: MIT 5 | */ 6 | 7 | // Excel include. 8 | #include 9 | #include 10 | 11 | // unit test helper. 12 | #define DOCTEST_CONFIG_IMPLEMENT_WITH_MAIN 13 | #include 14 | 15 | 16 | TEST_CASE( "test_book" ) 17 | { 18 | Excel::Book book( "test/data/datetime.xls" ); 19 | 20 | REQUIRE( book.sheetsCount() == 1 ); 21 | 22 | Excel::Sheet * sheet = book.sheet( 0 ); 23 | 24 | REQUIRE( sheet->rowsCount() == 1 ); 25 | REQUIRE( sheet->columnsCount() == 1 ); 26 | 27 | REQUIRE( std::fabs( sheet->cell( 0, 0 ).getDouble() - 43100.9999884259 ) < 1E-9 ); 28 | } 29 | -------------------------------------------------------------------------------- /test/formula/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | 2 | project( test.formula ) 3 | 4 | if( ENABLE_COVERAGE ) 5 | set( CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -g -O0 -fprofile-arcs -ftest-coverage" ) 6 | set( CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} --coverage" ) 7 | endif( ENABLE_COVERAGE ) 8 | 9 | set( SRC main.cpp ) 10 | 11 | include_directories( ${CMAKE_CURRENT_SOURCE_DIR} 12 | ${CMAKE_CURRENT_SOURCE_DIR}/.. 13 | ${CMAKE_CURRENT_SOURCE_DIR}/../.. ) 14 | 15 | add_executable( test.formula ${SRC} ) 16 | 17 | target_link_libraries( test.formula teststream ) 18 | 19 | add_test( NAME test.formula 20 | COMMAND ${CMAKE_CURRENT_BINARY_DIR}/test.formula 21 | WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR} ) 22 | -------------------------------------------------------------------------------- /test/formula/main.cpp: -------------------------------------------------------------------------------- 1 | 2 | /* 3 | SPDX-FileCopyrightText: 2011-2024 Igor Mironchik 4 | SPDX-License-Identifier: MIT 5 | */ 6 | 7 | // Excel include. 8 | #include 9 | #include 10 | #include 11 | 12 | // unit test helper. 13 | #define DOCTEST_CONFIG_IMPLEMENT_WITH_MAIN 14 | #include 15 | #include 16 | #include 17 | 18 | 19 | const auto data1 = make_data( 20 | 0x06u, 0x00u, 0x16u, 0x00u, 21 | 0x01u, 0x00u, 0x02u, 0x00u, 0x00u, 0x00u, 22 | 0x08u, 0x07u, 0x06u, 0x05u, 0x04u, 0x03u, 0x02u, 0x01u, 23 | 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, 0x00u 24 | ); // data1 25 | 26 | const auto data2 = make_data( 27 | 0x06u, 0x00u, 0x16u, 0x00u, 28 | 0x02u, 0x00u, 0x03u, 0x00u, 0x00u, 0x00u, 29 | 0x01u, 0x00u, 0x01u, 0x00u, 0x00u, 0x00u, 0xFFu, 0xFFu, 30 | 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, 0x00u 31 | ); // data2 32 | 33 | const auto data3 = make_data( 34 | 0x06u, 0x00u, 0x16u, 0x00u, 35 | 0x03u, 0x00u, 0x04u, 0x00u, 0x00u, 0x00u, 36 | 0x02u, 0x00u, 0x2Au, 0x00u, 0x00u, 0x00u, 0xFFu, 0xFFu, 37 | 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, 0x00u 38 | ); // data3 39 | 40 | const auto data4 = make_data( 41 | 0x06u, 0x00u, 0x16u, 0x00u, 42 | 0x04u, 0x00u, 0x05u, 0x00u, 0x00u, 0x00u, 43 | 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, 0xFFu, 0xFFu, 44 | 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, 45 | 46 | 0x07u, 0x02u, 0x12u, 0x00u, 47 | 0x0Fu, 0x00u, 0x00u, 0x74u, 0x68u, 0x69u, 0x73u, 0x20u, 48 | 0x69u, 0x73u, 0x20u, 0x72u, 0x65u, 0x64u, 0x20u, 0x69u, 49 | 0x6Eu, 0x6Bu 50 | ); // data4 51 | 52 | const auto data5 = make_data( 53 | 0x06u, 0x00u, 0x16u, 0x00u, 54 | 0x02u, 0x00u, 0x03u, 0x00u, 0x00u, 0x00u, 55 | 0x01u, 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, 0xFFu, 0xFFu, 56 | 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, 0x00u 57 | ); // data5 58 | 59 | 60 | // 61 | // test_formula 62 | // 63 | 64 | TEST_CASE( "test_formula" ) 65 | { 66 | union { 67 | double asDouble; 68 | uint64_t asLongLong; 69 | } un; 70 | 71 | { 72 | TestStream stream( &data1[ 0 ], 26 ); 73 | 74 | Excel::Record record( stream ); 75 | 76 | Excel::Formula formula( record ); 77 | 78 | REQUIRE( formula.valueType() == Excel::Formula::DoubleValue ); 79 | REQUIRE( formula.getRow() == 0x01 ); 80 | REQUIRE( formula.getColumn() == 0x02 ); 81 | 82 | un.asLongLong = 0x0102030405060708; 83 | 84 | REQUIRE( formula.getDouble() == un.asDouble ); 85 | } 86 | 87 | { 88 | TestStream stream( &data2[ 0 ], 26 ); 89 | 90 | Excel::Record record( stream ); 91 | 92 | Excel::Formula formula( record ); 93 | 94 | REQUIRE( formula.valueType() == Excel::Formula::BooleanValue ); 95 | REQUIRE( formula.getRow() == 0x02 ); 96 | REQUIRE( formula.getColumn() == 0x03 ); 97 | 98 | un.asLongLong = 0xFFFF000000010001; 99 | 100 | REQUIRE( formula.getBoolean() == true ); 101 | } 102 | 103 | { 104 | TestStream stream( &data3[ 0 ], 26 ); 105 | 106 | Excel::Record record( stream ); 107 | 108 | Excel::Formula formula( record ); 109 | 110 | REQUIRE( formula.valueType() == Excel::Formula::ErrorValue ); 111 | REQUIRE( formula.getRow() == 0x03 ); 112 | REQUIRE( formula.getColumn() == 0x04 ); 113 | 114 | un.asLongLong = 0xFFFF0000002A0002; 115 | 116 | REQUIRE( formula.getErrorValue() == Excel::Formula::NA ); 117 | } 118 | 119 | { 120 | TestStream stream( &data4[ 0 ], 48 ); 121 | 122 | Excel::Record record( stream ); 123 | 124 | Excel::Formula formula( record ); 125 | 126 | Excel::Record stringRecord( stream ); 127 | std::vector< int32_t > borders; 128 | 129 | formula.setString( Excel::loadString( stringRecord.dataStream(), 130 | borders ) ); 131 | 132 | REQUIRE( formula.valueType() == Excel::Formula::StringValue ); 133 | REQUIRE( formula.getRow() == 0x04 ); 134 | REQUIRE( formula.getColumn() == 0x05 ); 135 | 136 | un.asLongLong = 0xFFFF000000000000; 137 | 138 | REQUIRE( formula.getString() == L"this is red ink" ); 139 | } 140 | 141 | { 142 | TestStream stream( &data5[ 0 ], 26 ); 143 | 144 | Excel::Record record( stream ); 145 | 146 | Excel::Formula formula( record ); 147 | 148 | REQUIRE( formula.valueType() == Excel::Formula::BooleanValue ); 149 | REQUIRE( formula.getRow() == 0x02 ); 150 | REQUIRE( formula.getColumn() == 0x03 ); 151 | 152 | un.asLongLong = 0xFFFF000000010001; 153 | 154 | REQUIRE( formula.getBoolean() == false ); 155 | } 156 | } 157 | -------------------------------------------------------------------------------- /test/header/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | 2 | project( test.header ) 3 | 4 | if( ENABLE_COVERAGE ) 5 | set( CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -g -O0 -fprofile-arcs -ftest-coverage" ) 6 | set( CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} --coverage" ) 7 | endif( ENABLE_COVERAGE ) 8 | 9 | set( SRC main.cpp ) 10 | 11 | include_directories( ${CMAKE_CURRENT_SOURCE_DIR} 12 | ${CMAKE_CURRENT_SOURCE_DIR}/.. 13 | ${CMAKE_CURRENT_SOURCE_DIR}/../.. ) 14 | 15 | add_executable( test.header ${SRC} ) 16 | 17 | target_link_libraries( test.header testdocument ) 18 | 19 | add_test( NAME test.header 20 | COMMAND ${CMAKE_CURRENT_BINARY_DIR}/test.header 21 | WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR} ) 22 | -------------------------------------------------------------------------------- /test/header/main.cpp: -------------------------------------------------------------------------------- 1 | 2 | /* 3 | SPDX-FileCopyrightText: 2011-2024 Igor Mironchik 4 | SPDX-License-Identifier: MIT 5 | */ 6 | 7 | // Excel include. 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | 14 | // unit test helper. 15 | #define DOCTEST_CONFIG_IMPLEMENT_WITH_MAIN 16 | #include 17 | 18 | // TestDocument include. 19 | #include 20 | 21 | // C++ include. 22 | #include 23 | 24 | 25 | TEST_CASE( "test_header" ) 26 | { 27 | { 28 | Document doc; 29 | 30 | CompoundFile::Header header( doc.stream() ); 31 | 32 | REQUIRE( header.byteOrder() == Excel::Stream::LittleEndian ); 33 | REQUIRE( header.sectorSize() == 512 ); 34 | REQUIRE( header.shortSectorSize() == 64 ); 35 | REQUIRE( header.sectorsInSAT() == 0x01 ); 36 | REQUIRE( header.dirStreamSecID() == 0x01 ); 37 | REQUIRE( header.streamMinSize() == 0x1000 ); 38 | REQUIRE( header.ssatFirstSecID() == 0x1D ); 39 | REQUIRE( header.sectorsInSSAT() == 0x01 ); 40 | REQUIRE( header.msatFirstSecID() == CompoundFile::SecID::EndOfChain ); 41 | REQUIRE( header.sectorsInMSAT() == 0x00 ); 42 | 43 | CompoundFile::MSAT msat( header, doc.stream() ); 44 | CompoundFile::SAT sat = msat.buildSAT(); 45 | 46 | REQUIRE( sat.sat().size() == 128 ); 47 | REQUIRE_THROWS_AS( sat.sectors( 129 ), CompoundFile::Exception ); 48 | } 49 | 50 | { 51 | std::stringstream stream( std::ios::in | std::ios::out | 52 | std::ios::binary ); 53 | 54 | const char headerData[] = { 55 | 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, 0x00u 56 | }; 57 | 58 | stream.write( headerData, 8 ); 59 | 60 | REQUIRE_THROWS_AS( CompoundFile::Header header( stream ), 61 | CompoundFile::Exception ); 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /test/helper/helper.hpp: -------------------------------------------------------------------------------- 1 | 2 | /* 3 | SPDX-FileCopyrightText: 2011-2024 Igor Mironchik 4 | SPDX-License-Identifier: MIT 5 | */ 6 | 7 | #ifndef __TEST__HELPERS_HPP__ 8 | #define __TEST__HELPERS_HPP__ 9 | 10 | // C++ include. 11 | #include 12 | 13 | 14 | template< typename... D > 15 | std::array< char, sizeof...( D ) > make_data( D... d ) 16 | { 17 | return std::array< char, sizeof...( D ) > ( { static_cast< char > ( d )... } ); 18 | } 19 | 20 | #endif // __TEST__HELPERS_HPP__ 21 | -------------------------------------------------------------------------------- /test/record/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | 2 | project( test.record ) 3 | 4 | if( ENABLE_COVERAGE ) 5 | set( CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -g -O0 -fprofile-arcs -ftest-coverage" ) 6 | set( CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} --coverage" ) 7 | endif( ENABLE_COVERAGE ) 8 | 9 | set( SRC main.cpp ) 10 | 11 | include_directories( ${CMAKE_CURRENT_SOURCE_DIR} 12 | ${CMAKE_CURRENT_SOURCE_DIR}/.. 13 | ${CMAKE_CURRENT_SOURCE_DIR}/../.. ) 14 | 15 | add_executable( test.record ${SRC} ) 16 | 17 | target_link_libraries( test.record teststream ) 18 | 19 | add_test( NAME test.record 20 | COMMAND ${CMAKE_CURRENT_BINARY_DIR}/test.record 21 | WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR} ) 22 | -------------------------------------------------------------------------------- /test/record/main.cpp: -------------------------------------------------------------------------------- 1 | 2 | /* 3 | SPDX-FileCopyrightText: 2011-2024 Igor Mironchik 4 | SPDX-License-Identifier: MIT 5 | */ 6 | 7 | // Excel include. 8 | #include 9 | 10 | // unit test helper. 11 | #define DOCTEST_CONFIG_IMPLEMENT_WITH_MAIN 12 | #include 13 | #include 14 | #include 15 | 16 | 17 | const auto data = make_data( 18 | 0xFCu, 0x00u, 0x13u, 0x00u, 19 | 0x03u, 0x00u, 0x00u, 0x00u, 0x03u, 0x00u, 0x00u, 0x00u, 20 | 0x10u, 0x00u, 0x00u, 21 | 0x53u, 0x54u, 0x53u, 0x54u, 0x53u, 0x54u, 0x53u, 0x54u, 22 | 23 | 0x3Cu, 0x00u, 0x11u, 0x00u, 24 | 0x01u, 25 | 0x53u, 0x00u, 0x54u, 0x00u, 0x53u, 0x00u, 0x54u, 0x00u, 26 | 0x53u, 0x00u, 0x54u, 0x00u, 0x53u, 0x00u, 0x54u, 0x00u, 27 | 28 | 0x3Cu, 0x00u, 0x13u, 0x00u, 29 | 0x10u, 0x00u, 0x00u, 30 | 0x51u, 0x52u, 0x51u, 0x52u, 0x51u, 0x52u, 0x51u, 0x52u, 31 | 0x51u, 0x52u, 0x51u, 0x52u, 0x51u, 0x52u, 0x51u, 0x52u, 32 | 33 | 0x3Cu, 0x00u, 0x23u, 0x00u, 34 | 0x10u, 0x00u, 0x01u, 35 | 0x51u, 0x00u, 0x52u, 0x00u, 0x51u, 0x00u, 0x52u, 0x00u, 36 | 0x51u, 0x00u, 0x52u, 0x00u, 0x51u, 0x00u, 0x52u, 0x00u, 37 | 0x51u, 0x00u, 0x52u, 0x00u, 0x51u, 0x00u, 0x52u, 0x00u, 38 | 0x51u, 0x00u, 0x52u, 0x00u, 0x51u, 0x00u, 0x52u, 0x00u 39 | ); 40 | 41 | 42 | TEST_CASE( "test_record" ) 43 | { 44 | TestStream teststream( &data[ 0 ], 106 ); 45 | 46 | Excel::Record record( teststream ); 47 | 48 | REQUIRE( record.code() == 0xFC ); 49 | REQUIRE( record.length() == 0x5A ); 50 | 51 | const std::vector< int32_t > & borders = record.borders(); 52 | 53 | REQUIRE( borders[ 0 ] == 0x13 ); 54 | REQUIRE( borders[ 1 ] == 0x24 ); 55 | REQUIRE( borders[ 2 ] == 0x37 ); 56 | REQUIRE( borders.size() == 3 ); 57 | 58 | Excel::Stream & stream = record.dataStream(); 59 | 60 | REQUIRE( stream.getByte() == (char) 0x03u ); 61 | REQUIRE( stream.getByte() == (char) 0x00u ); 62 | REQUIRE( stream.getByte() == (char) 0x00u ); 63 | REQUIRE( stream.getByte() == (char) 0x00u ); 64 | REQUIRE( stream.getByte() == (char) 0x03u ); 65 | REQUIRE( stream.getByte() == (char) 0x00u ); 66 | REQUIRE( stream.getByte() == (char) 0x00u ); 67 | REQUIRE( stream.getByte() == (char) 0x00u ); 68 | REQUIRE( stream.getByte() == (char) 0x10u ); 69 | REQUIRE( stream.getByte() == (char) 0x00u ); 70 | REQUIRE( stream.getByte() == (char) 0x00u ); 71 | REQUIRE( stream.getByte() == (char) 0x53u ); 72 | REQUIRE( stream.getByte() == (char) 0x54u ); 73 | REQUIRE( stream.getByte() == (char) 0x53u ); 74 | REQUIRE( stream.getByte() == (char) 0x54u ); 75 | REQUIRE( stream.getByte() == (char) 0x53u ); 76 | REQUIRE( stream.getByte() == (char) 0x54u ); 77 | REQUIRE( stream.getByte() == (char) 0x53u ); 78 | REQUIRE( stream.getByte() == (char) 0x54u ); 79 | REQUIRE( stream.getByte() == (char) 0x01u ); 80 | REQUIRE( stream.getByte() == (char) 0x53u ); 81 | REQUIRE( stream.getByte() == (char) 0x00u ); 82 | REQUIRE( stream.getByte() == (char) 0x54u ); 83 | REQUIRE( stream.getByte() == (char) 0x00u ); 84 | REQUIRE( stream.getByte() == (char) 0x53u ); 85 | REQUIRE( stream.getByte() == (char) 0x00u ); 86 | REQUIRE( stream.getByte() == (char) 0x54u ); 87 | REQUIRE( stream.getByte() == (char) 0x00u ); 88 | REQUIRE( stream.getByte() == (char) 0x53u ); 89 | REQUIRE( stream.getByte() == (char) 0x00u ); 90 | REQUIRE( stream.getByte() == (char) 0x54u ); 91 | REQUIRE( stream.getByte() == (char) 0x00u ); 92 | REQUIRE( stream.getByte() == (char) 0x53u ); 93 | REQUIRE( stream.getByte() == (char) 0x00u ); 94 | REQUIRE( stream.getByte() == (char) 0x54u ); 95 | REQUIRE( stream.getByte() == (char) 0x00u ); 96 | REQUIRE( stream.getByte() == (char) 0x10u ); 97 | REQUIRE( stream.getByte() == (char) 0x00u ); 98 | REQUIRE( stream.getByte() == (char) 0x00u ); 99 | REQUIRE( stream.getByte() == (char) 0x51u ); 100 | REQUIRE( stream.getByte() == (char) 0x52u ); 101 | REQUIRE( stream.getByte() == (char) 0x51u ); 102 | REQUIRE( stream.getByte() == (char) 0x52u ); 103 | REQUIRE( stream.getByte() == (char) 0x51u ); 104 | REQUIRE( stream.getByte() == (char) 0x52u ); 105 | REQUIRE( stream.getByte() == (char) 0x51u ); 106 | REQUIRE( stream.getByte() == (char) 0x52u ); 107 | REQUIRE( stream.getByte() == (char) 0x51u ); 108 | REQUIRE( stream.getByte() == (char) 0x52u ); 109 | REQUIRE( stream.getByte() == (char) 0x51u ); 110 | REQUIRE( stream.getByte() == (char) 0x52u ); 111 | REQUIRE( stream.getByte() == (char) 0x51u ); 112 | REQUIRE( stream.getByte() == (char) 0x52u ); 113 | REQUIRE( stream.getByte() == (char) 0x51u ); 114 | REQUIRE( stream.getByte() == (char) 0x52u ); 115 | REQUIRE( stream.getByte() == (char) 0x10u ); 116 | REQUIRE( stream.getByte() == (char) 0x00u ); 117 | REQUIRE( stream.getByte() == (char) 0x01u ); 118 | REQUIRE( stream.getByte() == (char) 0x51u ); 119 | REQUIRE( stream.getByte() == (char) 0x00u ); 120 | REQUIRE( stream.getByte() == (char) 0x52u ); 121 | REQUIRE( stream.getByte() == (char) 0x00u ); 122 | REQUIRE( stream.getByte() == (char) 0x51u ); 123 | REQUIRE( stream.getByte() == (char) 0x00u ); 124 | REQUIRE( stream.getByte() == (char) 0x52u ); 125 | REQUIRE( stream.getByte() == (char) 0x00u ); 126 | REQUIRE( stream.getByte() == (char) 0x51u ); 127 | REQUIRE( stream.getByte() == (char) 0x00u ); 128 | REQUIRE( stream.getByte() == (char) 0x52u ); 129 | REQUIRE( stream.getByte() == (char) 0x00u ); 130 | REQUIRE( stream.getByte() == (char) 0x51u ); 131 | REQUIRE( stream.getByte() == (char) 0x00u ); 132 | REQUIRE( stream.getByte() == (char) 0x52u ); 133 | REQUIRE( stream.getByte() == (char) 0x00u ); 134 | REQUIRE( stream.getByte() == (char) 0x51u ); 135 | REQUIRE( stream.getByte() == (char) 0x00u ); 136 | REQUIRE( stream.getByte() == (char) 0x52u ); 137 | REQUIRE( stream.getByte() == (char) 0x00u ); 138 | REQUIRE( stream.getByte() == (char) 0x51u ); 139 | REQUIRE( stream.getByte() == (char) 0x00u ); 140 | REQUIRE( stream.getByte() == (char) 0x52u ); 141 | REQUIRE( stream.getByte() == (char) 0x00u ); 142 | REQUIRE( stream.getByte() == (char) 0x51u ); 143 | REQUIRE( stream.getByte() == (char) 0x00u ); 144 | REQUIRE( stream.getByte() == (char) 0x52u ); 145 | REQUIRE( stream.getByte() == (char) 0x00u ); 146 | REQUIRE( stream.getByte() == (char) 0x51u ); 147 | REQUIRE( stream.getByte() == (char) 0x00u ); 148 | REQUIRE( stream.getByte() == (char) 0x52u ); 149 | REQUIRE( stream.getByte() == (char) 0x00u ); 150 | } 151 | -------------------------------------------------------------------------------- /test/sst/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | 2 | project( test.sst ) 3 | 4 | if( ENABLE_COVERAGE ) 5 | set( CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -g -O0 -fprofile-arcs -ftest-coverage" ) 6 | set( CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} --coverage" ) 7 | endif( ENABLE_COVERAGE ) 8 | 9 | set( SRC main.cpp ) 10 | 11 | include_directories( ${CMAKE_CURRENT_SOURCE_DIR} 12 | ${CMAKE_CURRENT_SOURCE_DIR}/.. 13 | ${CMAKE_CURRENT_SOURCE_DIR}/../.. ) 14 | 15 | add_executable( test.sst ${SRC} ) 16 | 17 | target_link_libraries( test.sst teststream ) 18 | 19 | add_test( NAME test.sst 20 | COMMAND ${CMAKE_CURRENT_BINARY_DIR}/test.sst 21 | WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR} ) 22 | -------------------------------------------------------------------------------- /test/sst/main.cpp: -------------------------------------------------------------------------------- 1 | 2 | /* 3 | SPDX-FileCopyrightText: 2011-2024 Igor Mironchik 4 | SPDX-License-Identifier: MIT 5 | */ 6 | 7 | // Excel include. 8 | #include 9 | 10 | // unit test helper. 11 | #define DOCTEST_CONFIG_IMPLEMENT_WITH_MAIN 12 | #include 13 | #include 14 | #include 15 | 16 | 17 | const auto data = make_data( 18 | 0xFCu, 0x00u, 0x13u, 0x00u, 19 | 0x03u, 0x00u, 0x00u, 0x00u, 0x03u, 0x00u, 0x00u, 0x00u, 20 | 0x10u, 0x00u, 0x00u, 21 | 0x53u, 0x54u, 0x53u, 0x54u, 0x53u, 0x54u, 0x53u, 0x54u, 22 | 23 | 0x3Cu, 0x00u, 0x11u, 0x00u, 24 | 0x01u, 25 | 0x53u, 0x00u, 0x54u, 0x00u, 0x53u, 0x00u, 0x54u, 0x00u, 26 | 0x53u, 0x00u, 0x54u, 0x00u, 0x53u, 0x00u, 0x54u, 0x00u, 27 | 28 | 0x3Cu, 0x00u, 0x13u, 0x00u, 29 | 0x10u, 0x00u, 0x00u, 30 | 0x51u, 0x52u, 0x51u, 0x52u, 0x51u, 0x52u, 0x51u, 0x52u, 31 | 0x51u, 0x52u, 0x51u, 0x52u, 0x51u, 0x52u, 0x51u, 0x52u, 32 | 33 | 0x3Cu, 0x00u, 0x23u, 0x00u, 34 | 0x10u, 0x00u, 0x01u, 35 | 0x51u, 0x00u, 0x52u, 0x00u, 0x51u, 0x00u, 0x52u, 0x00u, 36 | 0x51u, 0x00u, 0x52u, 0x00u, 0x51u, 0x00u, 0x52u, 0x00u, 37 | 0x51u, 0x00u, 0x52u, 0x00u, 0x51u, 0x00u, 0x52u, 0x00u, 38 | 0x51u, 0x00u, 0x52u, 0x00u, 0x51u, 0x00u, 0x52u, 0x00u 39 | ); // data 40 | 41 | 42 | struct TestSstStorage : public Excel::EmptyStorage { 43 | std::vector< std::wstring > m_sst; 44 | void onSharedString( size_t sstSize, size_t idx, const std::wstring & value ) override; 45 | }; // struct TestSstStorage 46 | 47 | inline void 48 | TestSstStorage::onSharedString( size_t sstSize, size_t idx, const std::wstring & value ) 49 | { 50 | m_sst.push_back( value ); 51 | } 52 | 53 | // 54 | // test_sst 55 | // 56 | 57 | TEST_CASE( "test_sst" ) 58 | { 59 | TestStream testStream( &data[ 0 ], 106 ); 60 | 61 | Excel::Record record( testStream ); 62 | 63 | TestSstStorage sst; 64 | Excel::Parser::parseSST( record, sst ); 65 | 66 | REQUIRE( sst.m_sst.size() == 3 ); 67 | 68 | REQUIRE( sst.m_sst[ 0 ] == L"STSTSTSTSTSTSTST" ); 69 | REQUIRE( sst.m_sst[ 1 ] == L"QRQRQRQRQRQRQRQR" ); 70 | REQUIRE( sst.m_sst[ 2 ] == L"QRQRQRQRQRQRQRQR" ); 71 | } 72 | -------------------------------------------------------------------------------- /test/stream/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | 2 | project( teststream ) 3 | 4 | if( ENABLE_COVERAGE ) 5 | set( CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -g -O0 -fprofile-arcs -ftest-coverage" ) 6 | set( CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} --coverage" ) 7 | endif( ENABLE_COVERAGE ) 8 | 9 | set( SRC stream.cpp ) 10 | 11 | include_directories( ${CMAKE_CURRENT_SOURCE_DIR} 12 | ${CMAKE_CURRENT_SOURCE_DIR}/.. 13 | ${CMAKE_CURRENT_SOURCE_DIR}/../.. ) 14 | 15 | add_library( teststream STATIC ${SRC} ) 16 | -------------------------------------------------------------------------------- /test/stream/stream.cpp: -------------------------------------------------------------------------------- 1 | 2 | /* 3 | SPDX-FileCopyrightText: 2011-2024 Igor Mironchik 4 | SPDX-License-Identifier: MIT 5 | */ 6 | 7 | // test include. 8 | #include 9 | 10 | // C++ include. 11 | #include 12 | 13 | 14 | // 15 | // TestStream 16 | // 17 | 18 | 19 | TestStream::TestStream( const char * data, int32_t size ) 20 | : Excel::Stream( Excel::Stream::LittleEndian ) 21 | , m_data( data ) 22 | , m_pos( 0 ) 23 | , m_size( size ) 24 | { 25 | } 26 | 27 | TestStream::~TestStream() 28 | { 29 | } 30 | 31 | char 32 | TestStream::getByte() 33 | { 34 | char byte = m_data[ m_pos ]; 35 | ++m_pos; 36 | 37 | return byte; 38 | } 39 | 40 | bool 41 | TestStream::eof() const 42 | { 43 | return ( m_pos > m_size ); 44 | } 45 | 46 | void 47 | TestStream::seek( int32_t pos, Excel::Stream::SeekType type ) 48 | { 49 | if( type == Excel::Stream::FromCurrent ) 50 | { 51 | pos += m_pos; 52 | 53 | if( pos < 0 ) 54 | pos += m_size; 55 | } 56 | else if( type == Excel::Stream::FromEnd && pos > 0 ) 57 | pos = m_size - pos; 58 | else if( type == Excel::Stream::FromEnd && pos < 0 ) 59 | pos = std::abs( pos ); 60 | else if( type == Excel::Stream::FromBeginning && pos < 0 ) 61 | pos = m_size - pos; 62 | 63 | if( pos >= m_size ) 64 | { 65 | m_pos = m_size; 66 | return; 67 | } 68 | 69 | m_pos = pos; 70 | } 71 | 72 | int32_t 73 | TestStream::pos() 74 | { 75 | return m_pos; 76 | } 77 | -------------------------------------------------------------------------------- /test/stream/stream.hpp: -------------------------------------------------------------------------------- 1 | 2 | /* 3 | SPDX-FileCopyrightText: 2011-2024 Igor Mironchik 4 | SPDX-License-Identifier: MIT 5 | */ 6 | 7 | // Excel include. 8 | #include 9 | 10 | // C++ include. 11 | #include 12 | 13 | 14 | // 15 | // TestStream 16 | // 17 | 18 | //! Test stream. 19 | class TestStream 20 | : public Excel::Stream 21 | { 22 | public: 23 | TestStream( const char * data, int32_t size ); 24 | virtual ~TestStream(); 25 | 26 | //! Read one byte from the stream. 27 | char getByte() override; 28 | 29 | //! \return true if EOF reached. 30 | bool eof() const override; 31 | 32 | //! Seek stream to new position. 33 | void seek( int32_t pos, Excel::Stream::SeekType type = 34 | Excel::Stream::FromBeginning ) override; 35 | 36 | //! \return Position in the stream. 37 | int32_t pos() override; 38 | 39 | private: 40 | //! Data. 41 | const char * m_data; 42 | //! Position in the stream. 43 | int32_t m_pos; 44 | //! Size of the stream; 45 | int32_t m_size; 46 | }; // class TestStream 47 | -------------------------------------------------------------------------------- /test/string/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | 2 | project( test.string ) 3 | 4 | if( ENABLE_COVERAGE ) 5 | set( CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -g -O0 -fprofile-arcs -ftest-coverage" ) 6 | set( CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} --coverage" ) 7 | endif( ENABLE_COVERAGE ) 8 | 9 | set( SRC main.cpp ) 10 | 11 | include_directories( ${CMAKE_CURRENT_SOURCE_DIR} 12 | ${CMAKE_CURRENT_SOURCE_DIR}/.. 13 | ${CMAKE_CURRENT_SOURCE_DIR}/../.. ) 14 | 15 | add_executable( test.string ${SRC} ) 16 | 17 | target_link_libraries( test.string teststream ) 18 | 19 | add_test( NAME test.string 20 | COMMAND ${CMAKE_CURRENT_BINARY_DIR}/test.string 21 | WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR} ) 22 | -------------------------------------------------------------------------------- /test/string/main.cpp: -------------------------------------------------------------------------------- 1 | 2 | /* 3 | SPDX-FileCopyrightText: 2011-2024 Igor Mironchik 4 | SPDX-License-Identifier: MIT 5 | */ 6 | 7 | // Excel include. 8 | #include 9 | 10 | // C++ include. 11 | #include 12 | 13 | // unit test helper. 14 | #define DOCTEST_CONFIG_IMPLEMENT_WITH_MAIN 15 | #include 16 | #include 17 | #include 18 | 19 | 20 | const auto data = make_data( 21 | 0x0Fu, 0x00u, 0x08u, 0x02u, 0x00u, 0x74u, 0x68u, 0x69u, 22 | 0x73u, 0x20u, 0x69u, 0x73u, 0x20u, 0x72u, 0x65u, 0x64u, 23 | 0x20u, 0x69u, 0x6Eu, 0x6Bu, 0x08u, 0x00u, 0x06u, 0x00u, 24 | 0x0Bu, 0x00u, 0x05u, 0x00u, 25 | 26 | 0x0Fu, 0x00u, 0x00u, 0x74u, 0x68u, 0x69u, 0x73u, 0x20u, 27 | 0x69u, 0x73u, 0x20u, 0x72u, 0x65u, 0x64u, 0x20u, 0x69u, 28 | 0x6Eu, 0x6Bu, 29 | 30 | 0x0Fu, 0x00u, 0x01u, 0x74u, 0x00u, 0x68u, 0x00u, 0x69u, 31 | 0x00u, 0x73u, 0x00u, 0x20u, 0x00u, 0x69u, 0x00u, 0x73u, 32 | 0x00u, 0x20u, 0x00u, 0x72u, 0x00u, 0x65u, 0x00u, 0x64u, 33 | 0x00u, 0x20u, 0x00u, 0x69u, 0x00u, 0x6Eu, 0x00u, 0x6Bu, 34 | 0x00u, 35 | 36 | 0x0Fu, 0x00u, 0x04u, 0x04u, 0x00u, 0x00u, 0x00u, 0x74u, 37 | 0x68u, 0x69u, 0x73u, 0x20u, 0x69u, 0x73u, 0x20u, 0x72u, 38 | 0x65u, 0x64u, 0x20u, 0x69u, 0x6Eu, 0x6Bu, 0x00u, 0x00u, 39 | 0x00u, 0x00u, 40 | 41 | 0x0Fu, 0x00u, 0x0Cu, 0x02u, 0x00u, 0x04u, 0x00u, 0x00u, 42 | 0x00u, 0x74u, 0x68u, 0x69u, 0x73u, 0x20u, 0x69u, 0x73u, 43 | 0x20u, 0x72u, 0x65u, 0x64u, 0x20u, 0x69u, 0x6Eu, 0x6Bu, 44 | 0x08u, 0x00u, 0x06u, 0x00u, 0x0Bu, 0x00u, 0x05u, 0x00u, 45 | 0x00u, 0x00u, 0x00u, 0x00u, 46 | 47 | 0x0Fu, 0x00u, 0x00u, 0x74u, 0x68u, 0x69u, 0x73u, 0x20u, 48 | 0x69u, 0x73u, 0x20u, 0x72u, 0x65u, 0x64u, 0x20u, 0x69u, 49 | 0x6Eu, 0x6Bu 50 | ); // data 51 | 52 | 53 | TEST_CASE( "test_string" ) 54 | { 55 | std::vector< int32_t > borders; 56 | std::wstring str; 57 | 58 | TestStream stream( &data[ 0 ], 159 ); 59 | 60 | str = Excel::loadString( stream, borders ); 61 | 62 | REQUIRE( str == L"this is red ink" ); 63 | 64 | str = Excel::loadString( stream, borders ); 65 | 66 | REQUIRE( str == L"this is red ink" ); 67 | 68 | str = Excel::loadString( stream, borders ); 69 | 70 | REQUIRE( str == L"this is red ink" ); 71 | 72 | str = Excel::loadString( stream, borders ); 73 | 74 | REQUIRE( str == L"this is red ink" ); 75 | 76 | str = Excel::loadString( stream, borders ); 77 | 78 | REQUIRE( str == L"this is red ink" ); 79 | 80 | str = Excel::loadString( stream, borders ); 81 | 82 | REQUIRE( str == L"this is red ink" ); 83 | } 84 | -------------------------------------------------------------------------------- /test/testdocument/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | 2 | project( testdocument ) 3 | 4 | if( ENABLE_COVERAGE ) 5 | set( CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -g -O0 -fprofile-arcs -ftest-coverage" ) 6 | set( CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} --coverage" ) 7 | endif( ENABLE_COVERAGE ) 8 | 9 | set( SRC document.cpp ) 10 | 11 | include_directories( ${CMAKE_CURRENT_SOURCE_DIR} 12 | ${CMAKE_CURRENT_SOURCE_DIR}/../.. ) 13 | 14 | add_library( testdocument STATIC ${SRC} ) 15 | -------------------------------------------------------------------------------- /test/testdocument/document.hpp: -------------------------------------------------------------------------------- 1 | 2 | /* 3 | SPDX-FileCopyrightText: 2011-2024 Igor Mironchik 4 | SPDX-License-Identifier: MIT 5 | */ 6 | 7 | 8 | #ifndef TEST__DOCUMENT_HPP__INCLUDED 9 | #define TEST__DOCUMENT_HPP__INCLUDED 10 | 11 | // C++ include. 12 | #include 13 | 14 | 15 | // 16 | // Document 17 | // 18 | 19 | //! Test document. 20 | class Document { 21 | public: 22 | Document(); 23 | 24 | //! \return Document's stream. 25 | std::stringstream & stream(); 26 | 27 | private: 28 | //! Document's stream. 29 | std::stringstream m_stream; 30 | }; // class Document 31 | 32 | #endif // TEST__DOCUMENT_HPP__INCLUDED 33 | --------------------------------------------------------------------------------