2025-05-04 XLDocument: remove a dependency on boost::nowide

This commit is contained in:
Lars Uffmann 2025-05-04 16:52:54 +02:00
parent e460477e74
commit 3ed972e1e0
No known key found for this signature in database
4 changed files with 139 additions and 107 deletions

View File

@ -0,0 +1,119 @@
#ifndef OPENXLSX_TOOLS_H
#define OPENXLSX_TOOLS_H
#include <random> // std::random_device, std::mt19937, std::uniform_int_distribution
#include <string> // std::string
#include <sys/stat.h> // for stat, to test if a file exists and whether a file is a directory
// don't use "stat" directly because windows has compatibility-breaking defines
#if defined(_WIN32) // moved below includes to make it absolutely clear that this is module-local
# define STAT _stat // _stat should be available in standard environment on Windows
# define STATSTRUCT struct _stat // struct _stat also exists - split the two names in case the struct _stat must not be used on windows
#else
# define STAT stat
# define STATSTRUCT struct stat
#endif
namespace OpenXLSX
{
/* 2025-05-04: moved a set of file system related utility functions to this new header file so that Unicode support on windows (nowide)
* can be implemented in a central location */
/**
* @brief Test if path exists as either a file or a directory
* @param path Check for existence of this
* @return true if path exists as a file or directory
*/
inline bool pathExists(const std::string& path)
{
STATSTRUCT info;
if (STAT(path.c_str(), &info ) == 0) // test if path exists
return true;
return false;
}
#ifdef __GNUC__ // conditionally enable GCC specific pragmas to suppress unused function warning
# pragma GCC diagnostic push
# pragma GCC diagnostic ignored "-Wunused-function"
#endif // __GNUC__
/**
* @brief Test if fileName exists and is not a directory
* @param fileName The path to check for existence (as a file)
* @return true if fileName exists and is a file, otherwise false
*/
inline bool fileExists(const std::string& fileName)
{
STATSTRUCT info;
if (STAT(fileName.c_str(), &info ) == 0) // test if path exists
if ((info.st_mode & S_IFDIR) == 0) // test if it is NOT a directory
return true;
return false;
}
inline bool isDirectory(const std::string& fileName)
{
STATSTRUCT info;
if (STAT(fileName.c_str(), &info ) == 0) // test if path exists
if ((info.st_mode & S_IFDIR) != 0) // test if it is a directory
return true;
return false;
}
#ifdef __GNUC__ // conditionally enable GCC specific pragmas to suppress unused function warning
# pragma GCC diagnostic pop
#endif // __GNUC__
/**
* @brief Generates a random filename, which is used to generate a temporary archive when modifying and saving
* archive files.
* @param length The length of the filename to create.
* @return Returns the generated filename, appended with '.tmp'.
*/
inline std::string GenerateRandomName(int length)
{
std::string letters = "abcdefghijklmnopqrstuvwxyz0123456789";
std::random_device rand_dev;
std::mt19937 generator(rand_dev());
std::uniform_int_distribution<int> distr(0, letters.size() - 1);
std::string result;
for (int i = 0; i < length; ++i) {
result += letters[distr(generator)];
}
return result + ".tmp";
}
/**
* @brief Generates a random filename with a call to GenerateRandomName, prepending the same path in which filename is located
* @param filename determine "same path" from this file
* @param length The length of the filename to create.
* @return Returns the generated random filename in the same path as filename, appended with '.tmp'.
* @note this function accounts for amigaos and windows specific drive / directory separators
*/
inline std::string GenerateRandomNameInSamePath(std::string filename, int length)
{
if (filename.empty()) filename = "./"; // local folder
# ifdef _WIN32
std::replace( filename.begin(), filename.end(), '\\', '/' ); // pull request #210, alternate fix: fopen etc work fine with forward slashes
# endif
// ===== Determine path of the current file
size_t pathPos = filename.rfind('/');
// pull request #191, support AmigaOS style paths
# ifdef __amigaos__
constexpr const char * localFolder = ""; // local folder on AmigaOS can not be explicitly expressed in a path
if (pathPos == std::string::npos) pathPos = filename.rfind(':'); // if no '/' found, attempt to find amiga drive root path
# else
constexpr const char * localFolder = "./"; // local folder on _WIN32 && __linux__ is .
# endif
std::string tempPath{};
if (pathPos != std::string::npos) tempPath = filename.substr(0, pathPos + 1);
else tempPath = localFolder; // prepend explicit identification of local folder in case path did not contain a folder
// ===== Generate a random file name with the same path as the current file
return tempPath + GenerateRandomName(length);
}
} // namespace OpenXLSX
#endif // OPENXLSX_TOOLS_H

View File

@ -15,6 +15,8 @@
#include <direct.h>
#endif
#include "detail/OpenXLSXFileSystemTools.hpp" // OpenXLSX::GenerateRandomNameInSamePath
// 2024-09-15: moved Zippy exceptions to top of module to be able to use them earlier - forward declaration didn't seem
// to work
namespace Zippy
@ -59,31 +61,6 @@ namespace Zippy
} // namespace Zippy
namespace Zippy::Impl
{
/**
* @brief Generates a random filename, which is used to generate a temporary archive when modifying and saving
* archive files.
* @param length The length of the filename to create.
* @return Returns the generated filenamen, appended with '.tmp'.
*/
inline std::string GenerateRandomName(int length)
{
std::string letters = "abcdefghijklmnopqrstuvwxyz0123456789";
std::random_device rand_dev;
std::mt19937 generator(rand_dev());
std::uniform_int_distribution<int> distr(0, letters.size() - 1);
std::string result;
for (int i = 0; i < length; ++i) {
result += letters[distr(generator)];
}
return result + ".tmp";
}
} // namespace Zippy::Impl
namespace Zippy
{
@ -1073,26 +1050,8 @@ namespace Zippy
if (filename.empty()) {
filename = m_ArchivePath;
}
# ifdef _WIN32
std::replace( filename.begin(), filename.end(), '\\', '/' ); // pull request #210, alternate fix: fopen etc work fine with forward slashes
# endif
// ===== Determine path of the current file
size_t pathPos = filename.rfind('/');
// pull request #191, support AmigaOS style paths
# ifdef __amigaos__
constexpr const char * localFolder = ""; // local folder on AmigaOS can not be explicitly expressed in a path
if (pathPos == std::string::npos) pathPos = filename.rfind(':'); // if no '/' found, attempt to find amiga drive root path
# else
constexpr const char * localFolder = "./"; // local folder on _WIN32 && __linux__ is .
# endif
std::string tempPath{};
if (pathPos != std::string::npos) tempPath = filename.substr(0, pathPos + 1);
else tempPath = localFolder; // prepend explicit identification of local folder in case path did not contain a folder
// ===== Generate a random file name with the same path as the current file
tempPath = tempPath + Impl::GenerateRandomName(20);
std::string tempPath = OpenXLSX::GenerateRandomNameInSamePath(filename, 20);
// ===== Prepare an temporary archive file with the random filename;
mz_zip_archive tempArchive = mz_zip_archive();

View File

@ -45,14 +45,11 @@ YM M9 MM MM MM MM MM d' `MM. MM MM d' `MM.
// ===== External Includes ===== //
#include <algorithm>
#ifdef ENABLE_NOWIDE
# include <nowide/fstream.hpp>
#endif
#if defined(_WIN32)
# include <random>
#endif
#include <pugixml.hpp>
#include <sys/stat.h> // for stat, to test if a file exists and if a file is a directory
#include <unistd.h> // unlink
#include <vector> // std::vector
// ===== OpenXLSX Includes ===== //
@ -60,17 +57,9 @@ YM M9 MM MM MM MM MM d' `MM. MM MM d' `MM.
#include "XLDocument.hpp"
#include "XLSheet.hpp"
#include "XLStyles.hpp"
#include "detail/OpenXLSXFileSystemTools.hpp" // pathExists, GenerateRandomNameInSamePath
#include "utilities/XLUtilities.hpp"
// don't use "stat" directly because windows has compatibility-breaking defines
#if defined(_WIN32) // moved below includes to make it absolutely clear that this is module-local
# define STAT _stat // _stat should be available in standard environment on Windows
# define STATSTRUCT struct _stat // struct _stat also exists - split the two names in case the struct _stat must not be used on windows
#else
# define STAT stat
# define STATSTRUCT struct stat
#endif
using namespace OpenXLSX;
namespace
@ -429,7 +418,6 @@ namespace
0x6b, 0x62, 0x6f, 0x6f, 0x6b, 0x2e, 0x78, 0x6d, 0x6c, 0x2e, 0x72, 0x65, 0x6c, 0x73, 0x50, 0x4b, 0x05, 0x06, 0x00, 0x00, 0x00, 0x00,
0x0a, 0x00, 0x0a, 0x00, 0x80, 0x02, 0x00, 0x00, 0x8c, 0x1b, 0x00, 0x00, 0x00, 0x00
};
} // namespace
XLDocument::XLDocument(const IZipArchive& zipArchive) : m_xmlSavingDeclaration{}, m_archive(zipArchive) {}
@ -623,48 +611,6 @@ void XLDocument::open(const std::string& fileName)
m_styles = XLStyles(getXmlData("xl/styles.xml"), m_suppressWarnings); // 2024-10-14: forward supress warnings setting to XLStyles
}
namespace {
/**
* @brief Test if path exists as either a file or a directory
* @param path Check for existence of this
* @return true if path exists as a file or directory
*/
bool pathExists(const std::string& path)
{
STATSTRUCT info;
if (STAT(path.c_str(), &info ) == 0) // test if path exists
return true;
return false;
}
#ifdef __GNUC__ // conditionally enable GCC specific pragmas to suppress unused function warning
# pragma GCC diagnostic push
# pragma GCC diagnostic ignored "-Wunused-function"
#endif // __GNUC__
/**
* @brief Test if fileName exists and is not a directory
* @param fileName The path to check for existence (as a file)
* @return true if fileName exists and is a file, otherwise false
*/
bool fileExists(const std::string& fileName)
{
STATSTRUCT info;
if (STAT(fileName.c_str(), &info ) == 0) // test if path exists
if ((info.st_mode & S_IFDIR) == 0) // test if it is NOT a directory
return true;
return false;
}
bool isDirectory(const std::string& fileName)
{
STATSTRUCT info;
if (STAT(fileName.c_str(), &info ) == 0) // test if path exists
if ((info.st_mode & S_IFDIR) != 0) // test if it is a directory
return true;
return false;
}
#ifdef __GNUC__ // conditionally enable GCC specific pragmas to suppress unused function warning
# pragma GCC diagnostic pop
#endif // __GNUC__
} // anonymous namespace
/**
* @details Create a new document. This is done by saving the data in XLTemplate.h in binary format.
@ -672,24 +618,28 @@ namespace {
void XLDocument::create(const std::string& fileName, bool forceOverwrite)
{
// 2024-07-26: prevent silent overwriting of existing files
if (!forceOverwrite && pathExists(fileName)) {
if (!forceOverwrite && pathExists(fileName)) { // 2025-05-04 TODO TBD: does pathExists even work with windows / unicode filenames?
using namespace std::literals::string_literals;
throw XLException("XLDocument::create: refusing to overwrite existing file "s + fileName);
}
std::string tempFileName = GenerateRandomNameInSamePath(fileName, 20);
// ===== Create a temporary output file stream.
#ifdef ENABLE_NOWIDE
nowide::ofstream outfile(fileName, std::ios::binary);
#else
std::ofstream outfile(fileName, std::ios::binary);
#endif
std::ofstream outfile(tempFileName, std::ios::binary);
// ===== Stream the binary data for an empty workbook to the output file.
// ===== Casting, in particular reinterpret_cast, is discouraged, but in this case it is unfortunately unavoidable.
outfile.write(reinterpret_cast<const char*>(templateData), templateSize); // NOLINT
outfile.close();
open(fileName);
open(tempFileName); // open the template archive from the temporary file
m_filePath = fileName; // re-configure the document file path to point to the desired fileName
// 2025-05-04: the created (empty) archive is no longer saved implicitly, to remove the XLDocument dependency on nowide::ofstream
// Instead, OpenXLSX shall rely on the underlying zip implementation (Zippy.hpp or LibZip.hpp) to support Unicode filenames
// NOTE: LibZip currently does not support Unicode filenames on Windows (no use of nowide), status of miniz is unknown
unlink(tempFileName.c_str()); // delete the temporary file used for archive creation
}
/**

View File

@ -7,6 +7,10 @@ Microsoft Excel® files, with the .xlsx format.
As the heading says - the latest "Release" that is shown on https://github.com/troldal/OpenXLSX/releases is from 2021-11-06, and severely outdated - please pull / download the latest SW version directly from the repository in its current state. Link for those that do not want to use ```git```: https://github.com/troldal/OpenXLSX/archive/refs/heads/master.zip
## (aral-matrix) 04 May 2025 - moved file system access functions (shared by zip implementations and XLDocument) to detail/OpenXLSXFileSystemTools.hpp
* in preparation for localizing (if not removing) the boost::nowide dependency, the new header file ```detail/OpenXLSXFileSystemTools.hpp``` now comprises all functions where unicode (filename) support might be relevant, to be implemented centrally - currently on the To-Do list
* ```XLDocument::create```: creating a new document no longer creates a file under that name until an explicit call of ```XLDocument::save``` or ```::saveAs```. This is to remove a dependency of XLDocument on boost::nowide on Windows.
## (aral-matrix) 01 May 2025 - replaced zippy implementation with the underlying miniz library (cmake) and added support for libzip (cmake, GNU make)
* @troldal replaced the zippy implementation with a smaller zippy-wrapper for the actual dependency, the miniz library, and added the cmake support for automatically pulling in the dependency from the miniz repository
* ```OpenXLSX/headers/detail/```: new headers ```LipZip.hpp``` (libzip wrapper) and ```Zippy.hpp``` (miniz wrapper)