2024-07-11 major cleanup

This commit is contained in:
Lars Uffmann 2024-07-11 01:38:32 +02:00
parent f85f7f1bd6
commit f07956dcfe
63 changed files with 1745 additions and 581 deletions

107
.gitignore vendored
View File

@ -1,16 +1,97 @@
# System Files and Directories
.idea/
.DS_Store
# ignore everything
/*
# Build Directories
*cmake-build*/
# include generic files
!/.gitignore
!/LICENSE
!/NOTICE
!/README.md
# Visual Studio
.vs/
out/
# include root directory files
!/cmake-cleanup.sh
!/CMakeLists.txt
!/Makefile.GNU
!/make-gnu.sh
!/vcpkg.json
# other
*.so
*.dylib
*.a
*.xlsx
# include Benchmarks folder
!/Benchmarks
# exclude everything *in* Benchmarks folder
/Benchmarks/*
# re-include specifically what is desired from Benchmarks folder
!/Benchmarks/*.cpp
!/Benchmarks/*.txt
# include Documentation folder
!/Documentation
# exclude everything *in* Documentation folder
/Documentation/*
# re-include specifically what is desired from Documentation folder
!/Documentation/*.txt
!/Documentation/*.css
!/Documentation/*.in
!/Documentation/*.xml
# include Examples folder
!/Examples
# exclude everything *in* Examples folder
/Examples/*
# re-include specifically what is desired from Examples folder
!/Examples/cmake
!/Examples/cmake/*
!/Examples/CMakeLists.txt
!/Examples/*.cpp
!/Examples/external
!/Examples/external/*
# include gnu-make-crutch folder
!/gnu-make-crutch
# exclude everything *in* gnu-make-crutch folder
/gnu-make-crutch/*
# re-include specifically what is desired from gnu-make-crutch folder
!/gnu-make-crutch/OpenXLSX-Exports.hpp
# include OpenXLSX folder
!/OpenXLSX
# exclude everything *in* OpenXLSX folder
/OpenXLSX/*
# re-include specifically what is desired from OpenXLSX folder
!/OpenXLSX/CMakeLists.txt
!/OpenXLSX/external
!/OpenXLSX/external/*
!/OpenXLSX/headers
!/OpenXLSX/headers/*
!/OpenXLSX/OpenXLSXConfig.cmake
!/OpenXLSX/OpenXLSX.hpp
!/OpenXLSX/sources
!/OpenXLSX/sources/*
# include Tests folder
!/Tests
# exclude everything *in* Tests folder
/Tests/*
# re-include specifically what is desired from Tests folder
!/Tests/catch
!/Tests/catch/*.hpp
!/Tests/CMakeLists.txt
!/Tests/*.cpp
# ## OLD gitignore rules below:
#
# # System Files and Directories
# .idea/
# .DS_Store
#
# # Build Directories
# *cmake-build*/
#
# # Visual Studio
# .vs/
# out/
#
# # other
# *.so
# *.dylib
# *.a
# *.xlsx

0
CMakeLists.txt Executable file → Normal file
View File

380
Makefile.GNU Normal file
View File

@ -0,0 +1,380 @@
# GNU Makefile for OpenXLSX modules & demos
# Version: 2024-07-08 20:45 CEST
# NOTE: lib and library are available as aliases for OpenXLSX, to build the static library file
TARGETS=OpenXLSX Demo1 Demo2 Demo3 Demo4 Demo5 Demo6 Demo7 Demo8
DEMOS_SRC_DIR=Examples
BIN_DIR=output
CC=gcc
CXX=g++
# /usr/bin/ar: create archive without symbol table
AR=ar
# /usr/bin/ranlib: add symbol table to archive created with ar
RANLIB=ranlib
OPTIMIZATION_FLAGS=
#OPTIMIZATION_FLAGS=-O3
# === BEGIN: Detect OS platform based on gcc compiler defines ===
# Actual detection command:
DETECT_CMD="echo | $(CC) -dM -E -"
# Test commands to "simulate" a platform
# DETECT_CMD="echo \"\#define WIN32 1\""
# DETECT_CMD="echo \"\#define _WIN32 1\""
# DETECT_CMD="echo \"\#define __WIN32__ 1\""
# DETECT_CMD="echo \"\#define _WIN64 1\""
# DETECT_CMD="printf '\#define WIN32 1\n\#define _WIN32 1\n\#define __WIN32__ 1\n\#define _WIN64 1'" # test all defines together with line breaks
# DETECT_CMD="echo \"\#define __linux__ 1\""
# DETECT_CMD="echo \"\#define __CYGWIN__ 1\""
__linux__ := $(shell "$(DETECT_CMD)" | grep "\b__linux__\b")
ifdef __linux__
Linux=yes
else
Linux=no
endif
WIN32 := $(shell "$(DETECT_CMD)" | grep "\bWIN32\b")
_WIN32 := $(shell "$(DETECT_CMD)" | grep "\b_WIN32\b")
__WIN32__ := $(shell "$(DETECT_CMD)" | grep "\b__WIN32__\b")
_WIN64 := $(shell "$(DETECT_CMD)" | grep "\b_WIN64\b")
ifdef WIN32
Windows=yes
else ifdef _WIN32
Windows=yes
else ifdef __WIN32__
Windows=yes
else ifdef _WIN64
Windows=yes
else
Windows=no
endif
__CYGWIN__ := $(shell "$(DETECT_CMD)" | grep "\b__CYGWIN__\b")
ifdef __CYGWIN__
Cygwin=yes
else
Cygwin=no
endif
# === END: detect OS platform based on gcc compiler defines ===
OPENXLSX_DIR=OpenXLSX
SRC_DIR=sources
INCLUDE_DIR=headers
OBJ_DIR=obj
STATIC_LIBRARY=libOpenXLSX.a
USE_NOWIDE=no
ifeq ("$(Windows)", "yes")
# enable nowide on windows. CAUTION: do not append this comment to the variable assignment, it will break the ifeq check below
USE_NOWIDE=yes
endif
# SHARED_SUBDIR=shared
EXTERNAL_SUBDIR=external
PUGIXML_SUBDIR=$(EXTERNAL_SUBDIR)/pugixml
ZIPPY_SUBDIR=$(EXTERNAL_SUBDIR)/zippy
# library / utility objects
# OBJS_LICENSE=license.o
# OBJS_SHARED=$(OBJS_LICENSE)
OBJS_PUGIXML= # used as header-only module
OBJS_ZIPPY= # header-only module
OBJS_OPENXLSX=XLCell.o XLCellIterator.o XLCellRange.o XLCellReference.o XLCellValue.o XLColor.o XLColumn.o XLContentTypes.o XLDateTime.o XLDocument.o XLFormula.o XLProperties.o XLRelationships.o XLRow.o XLRowData.o XLSharedStrings.o XLSheet.o XLWorkbook.o XLXmlData.o XLXmlFile.o XLXmlParser.o XLZipArchive.o
# create a version of OBJS_OPENXLSX that already has the correct prefix so that it can be used for linking without further modification
OBJS_OPENXLSX_PREFIXED=$(addprefix $(OBJ_DIR)/$(OPENXLSX_DIR)/,$(OBJS_OPENXLSX))
# OBJS_DEMOS=$(OBJS_OPENXLSX_PREFIXED) # no longer needed - using static library instead
OBJS_DEMOS=$(BIN_DIR)/$(STATIC_LIBRARY) # use static library for linking demos
PROJECT_FLAGS=
ifeq ("$(USE_NOWIDE)", "yes")
PROJECT_FLAGS=-DENABLE_NOWIDE
endif
# additional include directories for 1) OPENXLSX project headers and 2) external nowide (parent) folder
ADDITIONAL_INCLUDE_FLAGS=-I$(OPENXLSX_DIR) -I$(OPENXLSX_DIR)/$(EXTERNAL_SUBDIR)
# application objects
OBJS_DEMO1=Demo1.o
OBJS_OPENXLSX_DEMO1=$(OBJS_DEMOS)
LDLIBS_DEMO1=
OBJS_DEMO2=Demo2.o
OBJS_OPENXLSX_DEMO2=$(OBJS_DEMOS)
LDLIBS_DEMO2=
OBJS_DEMO3=Demo3.o
OBJS_OPENXLSX_DEMO3=$(OBJS_DEMOS)
LDLIBS_DEMO3=
OBJS_DEMO4=Demo4.o
OBJS_OPENXLSX_DEMO4=$(OBJS_DEMOS)
LDLIBS_DEMO4=
OBJS_DEMO5=Demo5.o
OBJS_OPENXLSX_DEMO5=$(OBJS_DEMOS)
LDLIBS_DEMO5=
OBJS_DEMO6=Demo6.o
OBJS_OPENXLSX_DEMO6=$(OBJS_DEMOS)
LDLIBS_DEMO6=
OBJS_DEMO7=Demo7.o
OBJS_OPENXLSX_DEMO7=$(OBJS_DEMOS)
LDLIBS_DEMO7=
OBJS_DEMO8=Demo8.o
OBJS_OPENXLSX_DEMO8=$(OBJS_DEMOS)
LDLIBS_DEMO8=
SANITIZE_FLAGS=
# SANITIZE_FLAGS=-fsanitize=address
# SANITIZE_FLAGS=-fsanitize=address -fsanitize=leak
SANITIZE_LIBS=
# SANITIZE_LIBS=-llsan
# 2024-07-08 warning state:
# misleading-indentation: 0 warnings
# unused-function: 1 warning: OpenXLSX/sources/XLProperties.cpp:69: std::vector<std::string> headingPairsCategoriesStrings(XMLNode docNode)
# sign-compare: 7 warnings
# unknown-pragmas: 1586(!)
DISABLED_WARNING_FLAGS=-Wno-unknown-pragmas -Wno-sign-compare
GENERIC_FLAGS=$(ADDITIONAL_INCLUDE_FLAGS) $(PROJECT_FLAGS) -fno-common $(SANITIZE_FLAGS) $(OPTIMIZATION_FLAGS) -Wall -Wformat -Wformat-signedness $(DISABLED_WARNING_FLAGS)
# WARNING: there be dragons here:
# GENERIC_FLAGS=-I$(INCLUDE_DIR) -fno-common $(OPTIMIZATION_FLAGS) -Wall -Wformat -Wformat-signedness -Wpedantic -Wextra -Werror
CPPFLAGS=$(GENERIC_FLAGS) -std=c++17
# CPPFLAGS=$(GENERIC_FLAGS) -std=c++17 -D_GNU_SOURCE -D_POSIX_C_SOURCE=200809L -D_XOPEN_SOURCE=700
LDFLAGS=$(SANITIZE_FLAGS)
# LDFLAGS=-fno-common
# TEST: truncate binary to actually used code (https://gcc.gnu.org/onlinedocs/gnat_ugn/Compilation-options.html)
# GENERIC_FLAGS+= -ffunction-sections -fdata-sections
# LDFLAGS+= -Wl,--gc-sections
# precompiled libs go here
LDLIBS=$(SANITIZE_LIBS)
# LDLIBS=-lrt -pthread -lboost_program_options $(SANITIZE_LIBS) # example to add libraries if needed
## additional tools for text substitution:
# call tools like so:
# VAR = MixedCaseText
# LOWER_VAR = $(call lc,$(VAR))
# create all lowercase variable:
# lc = $(subst A,a,$(subst B,b,$(subst C,c,$(subst D,d,$(subst E,e,$(subst F,f,$(subst G,g,$(subst H,h,$(subst I,i,$(subst J,j,$(subst K,k,$(subst L,l,$(subst M,m,$(subst N,n,$(subst O,o,$(subst P,p,$(subst Q,q,$(subst R,r,$(subst S,s,$(subst T,t,$(subst U,u,$(subst V,v,$(subst W,w,$(subst X,x,$(subst Y,y,$(subst Z,z,$1))))))))))))))))))))))))))
# create all uppercase variable:
uc = $(subst a,A,$(subst b,B,$(subst c,C,$(subst d,D,$(subst e,E,$(subst f,F,$(subst g,G,$(subst h,H,$(subst i,I,$(subst j,J,$(subst k,K,$(subst l,L,$(subst m,M,$(subst n,N,$(subst o,O,$(subst p,P,$(subst q,Q,$(subst r,R,$(subst s,S,$(subst t,T,$(subst u,U,$(subst v,V,$(subst w,W,$(subst x,X,$(subst y,Y,$(subst z,Z,$1))))))))))))))))))))))))))
## gnu make override to define the default make directive, only takes a single target
.DEFAULT_GOAL := default
default: $(TARGETS)
all: $(TARGETS)
# the $(NOOP) rules are only needed for the dependency / forward to $(BIN_DIR)
Demo1: $(BIN_DIR)/Demo1
$(NOOP)
Demo2: $(BIN_DIR)/Demo2
$(NOOP)
Demo3: $(BIN_DIR)/Demo3
$(NOOP)
Demo4: $(BIN_DIR)/Demo4
$(NOOP)
Demo5: $(BIN_DIR)/Demo5
$(NOOP)
Demo6: $(BIN_DIR)/Demo6
$(NOOP)
Demo7: $(BIN_DIR)/Demo7
$(NOOP)
Demo8: $(BIN_DIR)/Demo8
$(NOOP)
# re-direct from aliases
lib: OpenXLSX
library: OpenXLSX
# static library target
OpenXLSX: $(BIN_DIR)/$(STATIC_LIBRARY)
# delete existing file, create static library archive file & add symbol table to archive
# NOTE: if existing library file is not explicitly deleted, ar will fail to update a modified object file
$(BIN_DIR)/$(STATIC_LIBRARY): $(OBJS_OPENXLSX_PREFIXED) | $(BIN_DIR)
rm -f $@ # explicitly delete existing library file
$(AR) qc $@ $(addprefix $(OBJ_DIR)/$(OPENXLSX_DIR)/,$(OBJS_OPENXLSX))
$(RANLIB) $@
# complex rule for BIN_DIR targets: include objects from OBJS_<target> and libraries from LDLIBS_<target>
.SECONDEXPANSION:
$(BIN_DIR)/%: $$(addprefix $(OBJ_DIR)/,$$(OBJS_$$(call uc,$$*))) $$(OBJS_OPENXLSX_$$(call uc,$$*)) | $(BIN_DIR)
$(CXX) $(LDFLAGS) $^ $(LDLIBS) $(LDLIBS_$(call uc,$*)) -o $@
# rule for .cpp files in DEMOS_SRC_DIR
$(OBJ_DIR)/%.o: $(addprefix $(DEMOS_SRC_DIR)/,%.cpp) | $(OBJ_DIR)
$(CXX) $(CPPFLAGS) -I$(OPENXLSX_DIR)/$(INCLUDE_DIR) -c $< -o $@
# rule for OpenXLSX .cpp files
$(OBJ_DIR)/$(OPENXLSX_DIR)/%.o: $(OPENXLSX_DIR)/$(SRC_DIR)/%.cpp | $(OBJ_DIR) $(OBJ_DIR)/$(OPENXLSX_DIR)/ $(OPENXLSX_DIR)/OpenXLSX-Exports.hpp
$(CXX) $(CPPFLAGS) -I$(OPENXLSX_DIR)/$(INCLUDE_DIR) -I$(OPENXLSX_DIR)/$(PUGIXML_SUBDIR) -I$(OPENXLSX_DIR)/$(ZIPPY_SUBDIR) -c $< -o $@
# rule for pugixml .cpp files: N/A, as pugixml is used as header only
$(OBJ_DIR)/$(PUGIXML_SUBDIR)/%.o: $(OPENXLSX_DIR)/$(PUGIXML_SUBDIR)/%.cpp | $(OBJ_DIR) $(OBJ_DIR)/$(PUGIXML_SUBDIR)/
$(CXX) $(CPPFLAGS) -I$(OPENXLSX_DIR)/$(PUGIXML_SUBDIR) -c $< -o $@
# rule for zippy .cpp files: N/A, as zippy is header only
$(OBJ_DIR)/$(ZIPPY_SUBDIR)/%.o: $(OPENXLSX_DIR)/$(ZIPPY_SUBDIR)/%.cpp | $(OBJ_DIR) $(OBJ_DIR)/$(ZIPPY_SUBDIR)/
$(CXX) $(CPPFLAGS) -I$(OPENXLSX_DIR)/$(ZIPPY_SUBDIR) -c $< -o $@
$(OPENXLSX_DIR)/OpenXLSX-Exports.hpp:
cp gnu-make-crutch/OpenXLSX-Exports.hpp $@
# # rule for shared .cpp files
# $(OBJ_DIR)/$(SHARED_SUBDIR)/%.o: $(OPENXLSX_DIR)/$(SRC_DIR)/$(SHARED_SUBDIR)/%.cpp | $(OBJ_DIR) $(OBJ_DIR)/$(SHARED_SUBDIR)/
# $(CXX) $(CPPFLAGS) -I$(INCLUDE_DIR)/$(SHARED_SUBDIR) -c $< -o $@
# create BIN_DIR if not existing
$(BIN_DIR):
mkdir $@
# create OBJ_DIR if not existing
$(OBJ_DIR):
mkdir $@
# $(OBJ_DIR)/$(SHARED_SUBDIR)/:
# mkdir $@
$(OBJ_DIR)/$(OPENXLSX_DIR)/:
mkdir $@
$(OBJ_DIR)/$(PUGIXML_SUBDIR)/:
mkdir $@
$(OBJ_DIR)/$(ZIPPY_SUBDIR)/:
mkdir $@
# .SECONDARY with no prerequisites causes all targets to be treated
# as secondary (i.e., no target is removed because it is considered
# intermediate).
.SECONDARY:
# indicate all rules without target output
.PHONY: default all clean cleanObjects cleanTargets echo lib library OpenXLSX
clean: cleanObjects cleanTargets
cleanObjects:
rm -f $(addprefix $(OBJ_DIR)/,$(OBJS_DEMO1) $(OBJS_DEMO2) $(OBJS_DEMO3) $(OBJS_DEMO4) $(OBJS_DEMO5) $(OBJS_DEMO6) $(OBJS_DEMO7) $(OBJS_DEMO8)) \
$(BIN_DIR)/$(STATIC_LIBRARY) \
$(OBJS_OPENXLSX_PREFIXED) \
$(addprefix $(OBJ_DIR)/$(PUGIXML_SUBDIR)/,$(OBJS_PUGIXML)) \
$(addprefix $(OBJ_DIR)/$(ZIPPY_SUBDIR)/,$(OBJS_ZIPPY))
# $(addprefix $(OBJ_DIR)/$(SHARED_SUBDIR)/,$(OBJS_SHARED))
cleanTargets:
rm -f $(addprefix $(BIN_DIR)/,$(TARGETS))
# have a look at the established configuration
echo:
@echo "OpenXLSX Makefile"
@echo "----------------------------------------------"
@echo "TARGETS: $(TARGETS)"
@echo "DEMOS_SRC_DIR: $(DEMOS_SRC_DIR)"
@echo "BIN_DIR: $(BIN_DIR)"
@echo
@echo "CC : $(CC)"
@echo "CXX : $(CXX)"
@echo "AR : $(AR)"
@echo "RANLIB: $(RANLIB)"
@echo
@echo "OPTIMIZATION_FLAGS: $(OPTIMIZATION_FLAGS)"
@echo
@echo "Platform detection variables:"
@echo "-----------------------------"
@echo 'DETECT_CMD: $(DETECT_CMD)'
@echo
@echo "__linux__ : $(__linux__)"
@echo "WIN32 : $(WIN32)"
@echo "_WIN32 : $(_WIN32)"
@echo "__WIN32__ : $(__WIN32__)"
@echo "_WIN64 : $(_WIN64)"
@echo "__CYGWIN__: $(__CYGWIN__)"
@echo
@echo "Platform detection results:"
@echo "---------------------------"
@echo "Linux : $(Linux)"
@echo "Windows: $(Windows)"
@echo "Cygwin : $(Cygwin)"
@echo
@echo "OpenXLSX project configuration:"
@echo "-------------------------------"
@echo "OPENXLSX_DIR: $(OPENXLSX_DIR)"
@echo "SRC_DIR: $(SRC_DIR)"
@echo "INCLUDE_DIR: $(INCLUDE_DIR)"
@echo "OBJ_DIR: $(OBJ_DIR)"
@echo "STATIC_LIBRARY: $(STATIC_LIBRARY)"
@echo
@echo "USE_NOWIDE: $(USE_NOWIDE)"
@echo
# @echo "SHARED_SUBDIR: $(SHARED_SUBDIR)"
@echo "EXTERNAL_SUBDIR: $(EXTERNAL_SUBDIR)"
@echo "PUGIXML_SUBDIR: $(PUGIXML_SUBDIR)"
@echo "ZIPPY_SUBDIR: $(ZIPPY_SUBDIR)"
@echo
# @echo "OBJS_LICENSE: $(OBJS_LICENSE)"
# @echo "OBJS_SHARED: $(OBJS_SHARED)"
@echo "OBJS_PUGIXML: $(OBJS_PUGIXML)"
@echo "OBJS_ZIPPY: $(OBJS_ZIPPY)"
@echo "OBJS_OPENXLSX: $(OBJS_OPENXLSX)"
@echo "OBJS_OPENXLSX_PREFIXED: $(OBJS_OPENXLSX_PREFIXED)"
@echo "OBJS_DEMOS: $(OBJS_DEMOS)"
@echo
@echo "PROJECT_FLAGS: $(PROJECT_FLAGS)"
@echo "ADDITIONAL_INCLUDE_FLAGS: $(ADDITIONAL_INCLUDE_FLAGS)"
@echo
@echo "OBJS_DEMO1: $(OBJS_DEMO1)"
@echo "OBJS_OPENXLSX_DEMO1: $(OBJS_OPENXLSX_DEMO1)"
@echo "LDLIBS_DEMO1: $(LDLIBS_DEMO1)"
@echo "OBJS_DEMO2: $(OBJS_DEMO2)"
@echo "OBJS_OPENXLSX_DEMO2: $(OBJS_OPENXLSX_DEMO2)"
@echo "LDLIBS_DEMO2: $(LDLIBS_DEMO2)"
@echo "OBJS_DEMO3: $(OBJS_DEMO3)"
@echo "OBJS_OPENXLSX_DEMO3: $(OBJS_OPENXLSX_DEMO3)"
@echo "LDLIBS_DEMO3: $(LDLIBS_DEMO3)"
@echo "OBJS_DEMO4: $(OBJS_DEMO4)"
@echo "OBJS_OPENXLSX_DEMO4: $(OBJS_OPENXLSX_DEMO4)"
@echo "LDLIBS_DEMO4: $(LDLIBS_DEMO4)"
@echo "OBJS_DEMO5: $(OBJS_DEMO5)"
@echo "OBJS_OPENXLSX_DEMO5: $(OBJS_OPENXLSX_DEMO5)"
@echo "LDLIBS_DEMO5: $(LDLIBS_DEMO5)"
@echo "OBJS_DEMO6: $(OBJS_DEMO6)"
@echo "OBJS_OPENXLSX_DEMO6: $(OBJS_OPENXLSX_DEMO6)"
@echo "LDLIBS_DEMO6: $(LDLIBS_DEMO6)"
@echo "OBJS_DEMO7: $(OBJS_DEMO7)"
@echo "OBJS_OPENXLSX_DEMO7: $(OBJS_OPENXLSX_DEMO7)"
@echo "LDLIBS_DEMO7: $(LDLIBS_DEMO7)"
@echo "OBJS_DEMO8: $(OBJS_DEMO8)"
@echo "OBJS_OPENXLSX_DEMO8: $(OBJS_OPENXLSX_DEMO8)"
@echo "LDLIBS_DEMO8: $(LDLIBS_DEMO8)"
@echo
@echo "SANITIZE_FLAGS: $(SANITIZE_FLAGS)"
@echo "SANITIZE_LIBS: $(SANITIZE_LIBS)"
@echo "DISABLED_WARNING_FLAGS: $(DISABLED_WARNING_FLAGS)"
@echo "GENERIC_FLAGS: $(GENERIC_FLAGS)"
@echo "CPPFLAGS: $(CPPFLAGS)"
@echo
@echo "LDFLAGS: $(LDFLAGS)"
@echo "LDLIBS: $(LDLIBS)"

View File

@ -108,6 +108,7 @@ set(OPENXLSX_SOURCES
${CMAKE_CURRENT_LIST_DIR}/sources/XLWorkbook.cpp
${CMAKE_CURRENT_LIST_DIR}/sources/XLXmlData.cpp
${CMAKE_CURRENT_LIST_DIR}/sources/XLXmlFile.cpp
${CMAKE_CURRENT_LIST_DIR}/sources/XLXmlParser.cpp
${CMAKE_CURRENT_LIST_DIR}/sources/XLZipArchive.cpp
)
@ -116,7 +117,7 @@ set(OPENXLSX_SOURCES
# STATIC AND SHARED LIBRARY
# Check that the input is valid
#=======================================================================================================================
if(NOT ${OPENXLSX_LIBRARY_TYPE} STREQUAL "STATIC" AND NOT ${OPENXLSX_LIBRARY_TYPE} STREQUAL "SHARED")
if(NOT "${OPENXLSX_LIBRARY_TYPE}" STREQUAL "STATIC" AND NOT "${OPENXLSX_LIBRARY_TYPE}" STREQUAL "SHARED")
message( FATAL_ERROR "Invalid library type. Must be SHARED or STATIC." )
endif()
@ -124,7 +125,7 @@ endif()
# STATIC LIBRARY
# Define the static library
#=======================================================================================================================
if (${OPENXLSX_LIBRARY_TYPE} STREQUAL "STATIC")
if ("${OPENXLSX_LIBRARY_TYPE}" STREQUAL "STATIC")
add_library(OpenXLSX STATIC "")
add_library(OpenXLSX::OpenXLSX ALIAS OpenXLSX)
target_sources(OpenXLSX PRIVATE ${OPENXLSX_SOURCES})
@ -152,7 +153,7 @@ endif ()
# SHARED LIBRARY
# Define the shared library
#=======================================================================================================================
if (${OPENXLSX_LIBRARY_TYPE} STREQUAL "SHARED")
if ("${OPENXLSX_LIBRARY_TYPE}" STREQUAL "SHARED")
add_library(OpenXLSX SHARED "")
add_library(OpenXLSX::OpenXLSX ALIAS OpenXLSX)
target_sources(OpenXLSX PRIVATE ${OPENXLSX_SOURCES})

View File

@ -60,4 +60,4 @@ YM M9 MM MM MM MM MM d' `MM. MM MM d' `MM.
#include "headers/XLWorkbook.hpp"
#include "headers/XLZipArchive.hpp"
#endif // OPENXLSX_OPENXLSX_HPP
#endif // OPENXLSX_OPENXLSX_HPP

View File

@ -14,8 +14,6 @@
#ifndef SOURCE_PUGIXML_CPP
#define SOURCE_PUGIXML_CPP
#include <iostream>
#include "pugixml.hpp"
#include <stdlib.h>
@ -5705,64 +5703,6 @@ namespace pugi
return xml_node();
}
/* BEGIN 2024-04-26 Lars Uffmann: added next_sibling_of_type, previous_sibling_of_type */
PUGI_IMPL_FN xml_node xml_node::next_sibling_of_type(xml_node_type t) const
{
if (_root) {
xml_node_struct* next = _root->next_sibling;
// while (next && (xml_node(next).type() != t)) next = next->next_sibling;
while (next && (PUGI_IMPL_NODETYPE(next) != t)) next = next->next_sibling; // faster than creating an xml_node object to access the .type() method?
if( next )
return xml_node(next);
}
return xml_node(); // if no node matching type t was found: return an empty node
}
PUGI_IMPL_FN xml_node xml_node::previous_sibling_of_type(xml_node_type t) const
{
// return prev->next_sibling ? xml_node(prev) : xml_node();
if (_root) {
xml_node_struct* prev = _root->prev_sibling_c;
// while (prev->next_sibling && (xml_node(prev).type() != t)) prev = prev->prev_sibling_c;
while (prev->next_sibling && (PUGI_IMPL_NODETYPE(prev) != t)) prev = prev->prev_sibling_c; // faster than creating an xml_node object to access the .type() method?
if( prev->next_sibling )
return xml_node(prev);
}
return xml_node(); // if no node matching type t was found: return an empty node
}
PUGI_IMPL_FN xml_node xml_node::next_sibling_of_type(const char_t* name_, xml_node_type t) const
{
if (_root) {
for (xml_node_struct* i = _root->next_sibling; i; i = i->next_sibling)
{
const char_t* iname = i->name;
// if (iname && impl::strequal(name_, iname) && (xml_node(i).type() == t))
if (iname && impl::strequal(name_, iname) && (PUGI_IMPL_NODETYPE(i) == t)) // faster than creating an xml_node object to access the .type() method?
return xml_node(i);
}
}
return xml_node(); // if no node matching type t was found: return an empty node
}
PUGI_IMPL_FN xml_node xml_node::previous_sibling_of_type(const char_t* name_, xml_node_type t) const
{
if (_root) {
for (xml_node_struct* i = _root->prev_sibling_c; i->next_sibling; i = i->prev_sibling_c)
{
const char_t* iname = i->name;
// if (iname && impl::strequal(name_, iname) && (xml_node(i).type() == t))
if (iname && impl::strequal(name_, iname) && (PUGI_IMPL_NODETYPE(i) == t)) // faster than creating an xml_node object to access the .type() method?
return xml_node(i);
}
}
return xml_node(); // if no node matching type t was found: return an empty node
}
/* END 2024-04-26 Lars Uffmann: added next_sibling_of_type, previous_sibling_of_type */
PUGI_IMPL_FN xml_attribute xml_node::attribute(const char_t* name_, xml_attribute& hint_) const
{
xml_attribute_struct* hint = hint_._attr;
@ -5873,47 +5813,6 @@ namespace pugi
return first ? xml_node(first->prev_sibling_c) : xml_node();
}
/* BEGIN 2024-04-25 Lars Uffmann: added first_child_of_type, last_child_of_type */
PUGI_IMPL_FN xml_node xml_node::first_child_of_type(xml_node_type t) const
{
if (_root) {
auto x = first_child();
auto l = last_child();
while(x != l && x.type() != t) x = x.next_sibling();
if( x.type() == t )
return xml_node(x);
}
return xml_node(); // if no node matching type t was found: return an empty node
}
PUGI_IMPL_FN xml_node xml_node::last_child_of_type(xml_node_type t) const
{
if (_root) {
auto f = first_child();
auto x = last_child();
while (x != f && x.type() != t) x = x.previous_sibling();
if( x.type() == t )
return xml_node(x);
}
return xml_node(); // if no node matching type t was found: return an empty node
}
/* END 2024-04-25 Lars Uffmann: added first_child_of_type, last_child_of_type */
/* BEGIN 2024-04-28 Lars Uffmann: added child_count_of_type */
PUGI_IMPL_FN size_t xml_node::child_count_of_type(xml_node_type type) const
{
size_t counter = 0;
if (_root) {
auto c = first_child_of_type( type );
while( c ) {
++counter;
c = c.next_sibling_of_type( type );
}
}
return counter;
}
/* END 2024-04-28 Lars Uffmann: added child_count_of_type */
PUGI_IMPL_FN bool xml_node::set_name(const char_t* rhs)
{
xml_node_type type_ = _root ? PUGI_IMPL_NODETYPE(_root) : node_null;
@ -7546,7 +7445,7 @@ namespace pugi
{
impl::xml_buffered_writer buffered_writer(writer, encoding);
if ((flags & format_write_bom) && encoding != encoding_latin1)
if ((flags & format_write_bom) && buffered_writer.encoding != encoding_latin1)
{
// BOM always represents the codepoint U+FEFF, so just write it in native encoding
#ifdef PUGIXML_WCHAR_MODE
@ -7560,7 +7459,7 @@ namespace pugi
if (!(flags & format_no_declaration) && !impl::has_declaration(_root))
{
buffered_writer.write_string(PUGIXML_TEXT("<?xml version=\"1.0\""));
if (encoding == encoding_latin1) buffered_writer.write_string(PUGIXML_TEXT(" encoding=\"ISO-8859-1\""));
if (buffered_writer.encoding == encoding_latin1) buffered_writer.write_string(PUGIXML_TEXT(" encoding=\"ISO-8859-1\""));
buffered_writer.write('?', '>');
if (!(flags & format_raw)) buffered_writer.write('\n');
}

View File

@ -532,19 +532,6 @@ namespace pugi
xml_node next_sibling() const;
xml_node previous_sibling() const;
// 2024-04-25 Lars Uffmann: added first_child_of_type, last_child_of_type
xml_node first_child_of_type(xml_node_type t = node_element) const;
xml_node last_child_of_type(xml_node_type t = node_element) const;
// 2024-04-28 Lars Uffmann: added child_count_of_type
size_t child_count_of_type(xml_node_type type = node_element) const;
// 2024-04-26 Lars Uffmann: added next_sibling_of_type, previous_sibling_of_type
xml_node next_sibling_of_type(xml_node_type t = node_element) const;
xml_node previous_sibling_of_type(xml_node_type t = node_element) const;
xml_node next_sibling_of_type(const char_t* name_, xml_node_type t = node_element) const;
xml_node previous_sibling_of_type(const char_t* name_, xml_node_type t = node_element) const;
// Get parent node
xml_node parent() const;

View File

@ -27,14 +27,14 @@
# include <direct.h>
#endif
#ifdef ENABLE_NOWIDE // TODO TBD: test this on windows
#ifdef ENABLE_NOWIDE // DONE: test this on windows
# include <nowide/cstdio.hpp>
# define FILESYSTEM_NAMESPACE nowide
#else
# define FILESYSTEM_NAMESPACE std
#endif
namespace
namespace ns_miniz
{
/* miniz.c 2.0.8 - public domain deflate/inflate, zlib-subset, ZIP reading/writing/appending, PNG writing
See "unlicense" statement at the end of this file.
@ -9691,10 +9691,12 @@ handle_failure:
#endif /*#ifndef MINIZ_NO_ARCHIVE_APIS*/
} // namespace
} // namespace ns_miniz
namespace Zippy
{
using namespace ns_miniz;
/**
* @brief The ZipRuntimeError class is a custom exception class derived from the std::runtime_error class.
* @details In case of an error in the Zippy library, an ZipRuntimeError object will be thrown, with a message
@ -10752,7 +10754,7 @@ namespace Zippy
// pull request #191, support AmigaOS style paths
# ifdef __amigaos__
constexpr const char * localFolder = "\"\"/"; // local folder on AmigaOS is ""
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 .
@ -10848,7 +10850,10 @@ namespace Zippy
// ===== If data has not been extracted from the archive (i.e., m_EntryData is empty),
// ===== extract the data from the archive to the ZipEntry object.
if (result->m_EntryData.empty()) {
result->m_EntryData.resize(result->UncompressedSize());
if (result->UncompressedSize())
result->m_EntryData.resize(result->UncompressedSize());
else
result->m_EntryData.resize(1); // 2024-06-03 BUFIX: std::vector::data() can be nullptr when ::size() is 0, leading to a failure to load an empty file
mz_zip_reader_extract_file_to_mem(&m_Archive, name.c_str(), result->m_EntryData.data(), result->m_EntryData.size(), 0);
}

View File

@ -50,6 +50,8 @@ YM M9 MM MM MM MM MM d' `MM. MM MM d' `MM.
#pragma warning(disable : 4251)
#pragma warning(disable : 4275)
#include <iostream> // std::ostream
#include <ostream> // std::basic_ostream
#include <memory>
// ===== OpenXLSX Includes ===== //
@ -217,11 +219,25 @@ namespace OpenXLSX
XLCellAssignable() : XLCell() {}
/**
* @brief Inherit all constructors with parameters from XLCell
* @brief Copy constructor. Constructs an assignable XLCell from an existing cell
* @param other the cell to construct from
*/
template<class base>
explicit XLCellAssignable(base b) : XLCell(b)
{}
XLCellAssignable (XLCell const & other);
/**
* @brief Move constructor. Constructs an assignable XLCell from a temporary (r)value
* @param other the cell to construct from
*/
XLCellAssignable (XLCell && other);
// /**
// * @brief Inherit all constructors with parameters from XLCell
// */
// template<class base>
// // explicit XLCellAssignable(base b) : XLCell(b)
// // NOTE: BUG: explicit keyword triggers tons of compiler errors when << operator attempts to use an XLCell (implicit conversion works because << is overloaded for XLCellAssignable)
// XLCellAssignable(base b) : XLCell(b)
// {}
/**
* @brief Copy assignment operator
@ -263,7 +279,20 @@ namespace OpenXLSX
inline bool operator!=(const XLCell& lhs, const XLCell& rhs) { return !XLCell::isEqual(lhs, rhs); }
/**
* @brief
* @brief ostream output of XLCell content as string
* @param os the ostream destination
* @param c the cell to output to the stream
* @return
*/
inline std::ostream& operator<<(std::ostream& os, const XLCell& c)
{
os << c.getString();
// TODO: send to stream different data types based on cell data type
return os;
}
/**
* @brief ostream output of XLCellAssignable content as string
* @param os the ostream destination
* @param c the cell to output to the stream
* @return

View File

@ -145,6 +145,12 @@ namespace OpenXLSX
*/
bool operator!=(const XLCellIterator& rhs) const;
/**
* @brief determine whether iterator is at 1 beyond the last cell in range
* @return
*/
const bool endReached() const { return m_endReached; }
/**
* @brief
* @param last
@ -152,15 +158,32 @@ namespace OpenXLSX
*/
uint64_t distance(const XLCellIterator& last);
/**
* @brief get the XLCellReference::address corresponding to the current iterator position
* @return an XLCellReference::address, with m_bottomRight.col() + 1 for the beyond-the-end iterator
*/
const std::string address() const;
private:
std::unique_ptr<XMLNode> m_dataNode; /**< */
XLCellReference m_topLeft; /**< The cell reference of the first cell in the range */
XLCellReference m_bottomRight; /**< The cell reference of the last cell in the range */
XLCell m_currentCell; /**< */
XLSharedStrings m_sharedStrings; /**< */
bool m_endReached { false }; /**< */
bool m_endReached; /**< */
};
/**
* @brief ostream output of XLIterator position as XLCellReference::address
* @param os the ostream destination
* @param it the XLIterator whose position to send to the stream
* @return
*/
inline std::ostream& operator<<(std::ostream& os, const XLCellIterator& it)
{
os << it.address();
return os;
}
} // namespace OpenXLSX
// ===== Template specialization for std::distance.

View File

@ -166,4 +166,4 @@ namespace OpenXLSX
} // namespace OpenXLSX
#pragma warning(pop)
#endif // OPENXLSX_XLCELLRANGE_HPP
#endif // OPENXLSX_XLCELLRANGE_HPP

View File

@ -284,7 +284,7 @@ namespace OpenXLSX
try {
return std::visit(VisitXLCellValueTypeToString(), m_value);
}
catch (std::string s) {
catch (...) { // 2024-05-27: was catch( string s ) - must have been a typo, currently nothing throws a string here
throw XLValueTypeError("XLCellValue object is not convertible to string.");
}
}

View File

@ -52,6 +52,7 @@ YM M9 MM MM MM MM MM d' `MM. MM MM d' `MM.
// ===== External Includes ===== //
#include <any>
#include <cstdint> // uint8_t
#include <map>
#include <string>
@ -67,6 +68,8 @@ namespace OpenXLSX
SetSheetIndex,
SetSheetActive,
ResetCalcChain,
CheckAndFixCoreProperties,
CheckAndFixExtendedProperties,
AddSharedStrings,
AddWorksheet,
AddChartsheet,

View File

@ -51,6 +51,7 @@ YM M9 MM MM MM MM MM d' `MM. MM MM d' `MM.
#pragma warning(disable : 4275)
// ===== External Includes ===== //
#include <cstdint> // uint8_t
#include <memory>
#include <string>
#include <vector>

View File

@ -51,6 +51,8 @@ YM M9 MM MM MM MM MM d' `MM. MM MM d' `MM.
#pragma warning(disable : 4275)
// ===== External Includes ===== //
#include <algorithm> // std::find_if
#include <list>
#include <string>
// ===== OpenXLSX Includes ===== //
@ -65,8 +67,6 @@ YM M9 MM MM MM MM MM d' `MM. MM MM d' `MM.
#include "XLXmlData.hpp"
#include "XLZipArchive.hpp"
#include <list>
namespace OpenXLSX
{
/**
@ -189,8 +189,9 @@ namespace OpenXLSX
/**
* @brief Get the filename of the current document, e.g. "spreadsheet.xlsx".
* @return A std::string with the filename.
* @note 2024-06-03: function can't return as reference to const because filename as a substr of m_filePath can be a temporary
*/
const std::string& name() const;
const std::string name() const;
/**
* @brief Get the full path of the current document, e.g. "drive/blah/spreadsheet.xlsx"

View File

@ -152,4 +152,4 @@ namespace OpenXLSX
} // namespace OpenXLSX
#pragma warning(pop)
#endif // OPENXLSX_XLEXCEPTION_HPP
#endif // OPENXLSX_XLEXCEPTION_HPP

View File

@ -225,9 +225,9 @@ namespace OpenXLSX
*/
template<
typename T,
typename = std::enable_if_t<std::is_same_v<std::decay_t<T>, XLFormula> || std::is_same_v<std::decay_t<T>, std::string> ||
std::is_same_v<std::decay_t<T>, std::string_view> || std::is_same_v<std::decay_t<T>, const char*> ||
std::is_same_v<std::decay_t<T>, char*>>>
typename = std::enable_if_t<std::is_same_v<std::decay_t<T>, XLFormula> || std::is_same_v<std::decay_t<T>, std::string> ||
std::is_same_v<std::decay_t<T>, std::string_view> || std::is_same_v<std::decay_t<T>, const char*> ||
std::is_same_v<std::decay_t<T>, char*>>>
XLFormulaProxy& operator=(T formula)
{
if constexpr (std::is_same_v<std::decay_t<T>, XLFormula>)

View File

@ -64,6 +64,13 @@ namespace OpenXLSX
*/
class OPENXLSX_EXPORT XLProperties : public XLXmlFile
{
private:
/**
* @brief constructor helper function: create core.xml content from template
* @param workbook
*/
void createFromTemplate();
//----------------------------------------------------------------------------------------------------------------------
// Public Member Functions
//----------------------------------------------------------------------------------------------------------------------
@ -159,6 +166,13 @@ namespace OpenXLSX
*/
class OPENXLSX_EXPORT XLAppProperties : public XLXmlFile
{
private:
/**
* @brief constructor helper function: create app.xml content from template
* @param workbook
*/
void createFromTemplate(XMLDocument const & workbookXml);
//--------------------------------------------------------------------------------------------------------------
// Public Member Functions
//--------------------------------------------------------------------------------------------------------------
@ -169,6 +183,13 @@ namespace OpenXLSX
*/
XLAppProperties() = default;
/**
* @brief enable XLAppProperties to re-create a worksheet list in docProps/app.xml <TitlesOfParts> element from workbookXml
* @param xmlData
* @param workbook
*/
explicit XLAppProperties(XLXmlData* xmlData, XMLDocument const & workbookXml);
/**
* @brief
* @param xmlData

View File

@ -94,7 +94,18 @@ namespace OpenXLSX
ControlProperties,
Unknown
};
} // namespace OpenXLSX
namespace OpenXLSX_XLRelationships { // special namespace to avoid naming conflict with another GetStringFromType function
using namespace OpenXLSX;
/**
* @brief helper function, used only within module and from XLProperties.cpp / XLAppProperties::createFromTemplate
* @param type the XLRelationshipType for which to return the correct XML string
*/
std::string GetStringFromType(XLRelationshipType type);
} // namespace OpenXLSX_XLRelationships
namespace OpenXLSX {
/**
* @brief An encapsulation of a relationship item, i.e. an XML file in the document, its type and an ID number.
*/

View File

@ -51,6 +51,8 @@ YM M9 MM MM MM MM MM d' `MM. MM MM d' `MM.
#pragma warning(disable : 4275)
#include <deque>
#include <limits> // std::numeric_limits
#include <ostream> // std::basic_ostream
#include <string>
// ===== OpenXLSX Includes ===== //
@ -59,6 +61,8 @@ YM M9 MM MM MM MM MM d' `MM. MM MM d' `MM.
namespace OpenXLSX
{
constexpr size_t XLMaxSharedStrings = std::numeric_limits< int32_t >::max();
/**
* @brief This class encapsulate the Excel concept of Shared Strings. In Excel, instead of havig individual strings
* in each cell, cells have a reference to an entry in the SharedStrings register. This results in smalle file
@ -133,12 +137,12 @@ namespace OpenXLSX
* @param index
* @return
*/
const char* getString(uint32_t index) const;
const char* getString(int32_t index) const;
/**
* @brief Append a new string to the list of shared strings.
* @param str The string to append.
* @return A long int with the index of the appended string
* @return An int32_t with the index of the appended string
*/
int32_t appendString(const std::string& str);
@ -149,12 +153,20 @@ namespace OpenXLSX
* shared string indices for the cells in the spreadsheet. Instead use this member functions, which clears
* the contents of the string, but keeps the XMLNode holding the string.
*/
void clearString(uint64_t index);
void clearString(int32_t index);
// 2024-06-18 TBD if this is ever needed
// /**
// * @brief check m_stringCache is initialized
// * @return true if m_stringCache != nullptr, false otherwise
// * @note 2024-05-28 added function to enable other classes to check m_stringCache status
// */
// bool initialized() const { return m_stringCache != nullptr; }
/**
* @brief print the XML contents of the shared strings document using the underlying XMLNode print function
*/
void print(std::basic_ostream<char, std::char_traits<char>>& ostr);
void print(std::basic_ostream<char>& ostr) const;
private:
std::deque<std::string>* m_stringCache {}; /** < Each string must have an unchanging memory address; hence the use of std::deque */

View File

@ -51,6 +51,8 @@ YM M9 MM MM MM MM MM d' `MM. MM MM d' `MM.
#pragma warning(disable : 4275)
// ===== External Includes ===== //
#include <cstdint> // uint8_t, uint16_t, uint32_t
#include <ostream> // std::basic_ostream
#include <type_traits>
#include <variant>
@ -730,7 +732,7 @@ namespace OpenXLSX
/**
* @brief print the XML contents of the XLSheet using the underlying XMLNode print function
*/
void print(std::basic_ostream<char, std::char_traits<char> >& ostr);
void print(std::basic_ostream<char>& ostr) const;
//----------------------------------------------------------------------------------------------------------------------
// Private Member Variables

View File

@ -51,6 +51,7 @@ YM M9 MM MM MM MM MM d' `MM. MM MM d' `MM.
#pragma warning(disable : 4275)
// ===== External Includes ===== //
#include <ostream> // std::basic_ostream
#include <vector>
// ===== OpenXLSX Includes ===== //
@ -313,7 +314,7 @@ namespace OpenXLSX
/**
* @brief print the XML contents of the workbook.xml using the underlying XMLNode print function
*/
void print(std::basic_ostream<char, std::char_traits<char>>& os);
void print(std::basic_ostream<char>& ostr) const;
private: // ---------- Private Member Functions ---------- //
/**

View File

@ -46,22 +46,143 @@ YM M9 MM MM MM MM MM d' `MM. MM MM d' `MM.
#ifndef OPENXLSX_XLXMLPARSER_HPP
#define OPENXLSX_XLXMLPARSER_HPP
namespace pugi
{
class xml_node;
class xml_attribute;
class xml_document;
} // namespace pugi
// ===== pugixml.hpp needed for pugi::impl::xml_memory_page_type_mask, pugi::xml_node_type, pugi::char_t, pugi::node_element, pugi::xml_node, pugi::xml_attribute, pugi::xml_document
#include <external/pugixml/pugixml.hpp> // not sure why the full include path is needed within the header file
// 2024-05-29: forward declarations now pointless with include on pugixml being needed anyways
// namespace pugi
// {
// class xml_node;
// class xml_attribute;
// class xml_document;
// } // namespace pugi
namespace OpenXLSX
{
// class OpenXLSX_xml_node : public pugi::xml_node {
// using xml_node::xml_node; // use all constructors of pugi::xml_node
// TBD: can first_child_element and last_child_element be implemented here to skip whitespace nodes?
// };
// using XMLNode = OpenXLSX_xml_node;
using XMLNode = pugi::xml_node;
using XMLAttribute = pugi::xml_attribute;
using XMLDocument = pugi::xml_document;
// ===== Copy definition of PUGI_IMPL_NODETYPE, which is defined in pugixml.cpp, within a namespace, and somehow doesn't work here
# define PUGI_IMPL_NODETYPE(n) static_cast<pugi::xml_node_type>((n)->header & pugi::impl::xml_memory_page_type_mask)
// disable this line to use original (non-augmented) pugixml
# define PUGI_AUGMENTED
// ===== Using statements to switch between pugixml and augmented pugixml implementation
# ifdef PUGI_AUGMENTED
// ===== Forward declarations for using statements below
class OpenXLSX_xml_node;
class OpenXLSX_xml_document;
using XMLNode = OpenXLSX_xml_node;
using XMLAttribute = pugi::xml_attribute;
using XMLDocument = OpenXLSX_xml_document;
# else
using XMLNode = pugi::xml_node;
using XMLAttribute = pugi::xml_attribute;
using XMLDocument = pugi::xml_document;
# endif
// ===== Custom OpenXLSX_xml_node to add functionality to pugi::xml_node
class OpenXLSX_xml_node : public pugi::xml_node {
public:
/**
* @brief Default constructor. Constructs a null object.
*/
OpenXLSX_xml_node() : pugi::xml_node() {}
/**
* @brief Inherit all constructors with parameters from pugi::xml_node
*/
template<class base>
// explicit OpenXLSX_xml_node(base b) : xml_node(b) // TBD
OpenXLSX_xml_node(base b) : pugi::xml_node(b)
{}
// ===== BEGIN: Wrappers for xml_node member functions to ensure OpenXLSX_xml_node return values
// ===== CAUTION: this section is incomplete, only implementing those functions actually used by OpenXLSX to date
/**
* @brief for all functions: invoke the base class function, but with a return type of OpenXLSX_xml_node
*/
XMLNode parent() { return pugi::xml_node::parent(); }
XMLNode child(const pugi::char_t* name) const { return pugi::xml_node::child(name); }
template <typename Predicate> XMLNode find_child(Predicate pred) const { return pugi::xml_node::find_child(pred); }
// ===== END: Wrappers for xml_node member functions
/**
* @brief get first node child that matches type
* @param type_ the pugi::xml_node_type to match
* @return a valid child matching the node type or an empty XMLNode
*/
XMLNode first_child_of_type(pugi::xml_node_type type_ = pugi::node_element) const;
/**
* @brief get last node child that matches type
* @param type_ the pugi::xml_node_type to match
* @return a valid child matching the node type or an empty XMLNode
*/
XMLNode last_child_of_type(pugi::xml_node_type type_ = pugi::node_element) const;
/**
* @brief count node children that match type
* @param type_ the pugi::xml_node_type to match
* @return the amount of node children matching type
*/
size_t child_count_of_type(pugi::xml_node_type type_ = pugi::node_element) const;
/**
* @brief get next node sibling that matches type
* @param type_ the pugi::xml_node_type to match
* @return a valid sibling matching the node type or an empty XMLNode
*/
XMLNode next_sibling_of_type(pugi::xml_node_type type_ = pugi::node_element) const;
/**
* @brief get previous node sibling that matches type
* @param type_ the pugi::xml_node_type to match
* @return a valid sibling matching the node type or an empty XMLNode
*/
XMLNode previous_sibling_of_type(pugi::xml_node_type type_ = pugi::node_element) const;
/**
* @brief get next node sibling that matches name_ and type
* @param name_ the xml_node::name() to match
* @param type_ the pugi::xml_node_type to match
* @return a valid sibling matching the node type or an empty XMLNode
*/
XMLNode next_sibling_of_type(const pugi::char_t* name_, pugi::xml_node_type type_ = pugi::node_element) const;
/**
* @brief get previous node sibling that matches name_ and type
* @param name_ the xml_node::name() to match
* @param type_ the pugi::xml_node_type to match
* @return a valid sibling matching the node type or an empty XMLNode
*/
XMLNode previous_sibling_of_type(const pugi::char_t* name_, pugi::xml_node_type type_ = pugi::node_element) const;
};
// ===== Custom OpenXLSX_xml_document to override relevant pugi::xml_document member functions with OpenXLSX_xml_node return value
class OpenXLSX_xml_document : public pugi::xml_document {
public:
/**
* @brief Default constructor. Constructs a null object.
*/
OpenXLSX_xml_document() : pugi::xml_document() {}
/**
* @brief Inherit all constructors with parameters from pugi::xml_document
*/
template<class base>
// explicit OpenXLSX_xml_document(base b) : xml_document(b) // TBD
OpenXLSX_xml_document(base b) : pugi::xml_document(b)
{}
// ===== BEGIN: Wrappers for xml_document member functions to ensure OpenXLSX_xml_node return values
// ===== CAUTION: this section is incomplete, only implementing those functions actually used by OpenXLSX to date
/**
* @brief for all functions: invoke the base class function, but with a return type of OpenXLSX_xml_node
*/
XMLNode document_element() const { return pugi::xml_document::document_element(); }
// ===== END: Wrappers for xml_document member functions
};
} // namespace OpenXLSX
#endif // OPENXLSX_XLXMLPARSER_HPP

View File

@ -136,8 +136,6 @@ void XLCell::copyFrom(XLCell const& other)
using namespace std::literals::string_literals;
if (!m_cellNode) {
// copyFrom invoked by empty XLCell: create a new cell with reference & m_cellNode from other
std::cout << "copyFrom invoked by empty XLCell - creating a new cell with reference " << other.cellReference().address()
<< std::endl;
m_cellNode = std::make_unique<XMLNode>(*other.m_cellNode);
m_sharedStrings = other.m_sharedStrings;
m_valueProxy = XLCellValueProxy(this, m_cellNode.get());
@ -148,12 +146,14 @@ void XLCell::copyFrom(XLCell const& other)
if ((&other != this) && (*other.m_cellNode == *m_cellNode)) // nothing to do
return;
// ===== If m_cellNode points to a different XML node than other
if ((&other != this) && (*other.m_cellNode != *m_cellNode)) {
m_cellNode->remove_children();
for (XMLNode child = other.m_cellNode->first_child(); !child.empty(); child = child.next_sibling()) m_cellNode->append_copy(child);
for (auto attr = m_cellNode->first_attribute(); !attr.empty(); attr = attr.next_attribute())
// ===== Copy all XML attributes that are not the cell reference ("r") and all XML child nodes
for (XMLNode child = other.m_cellNode->first_child(); not child.empty(); child = child.next_sibling()) m_cellNode->append_copy(child);
for (auto attr = m_cellNode->first_attribute(); not attr.empty(); attr = attr.next_attribute())
if (strcmp(attr.name(), "r") != 0) m_cellNode->remove_attribute(attr);
for (auto attr = other.m_cellNode->first_attribute(); !attr.empty(); attr = attr.next_attribute())
for (auto attr = other.m_cellNode->first_attribute(); not attr.empty(); attr = attr.next_attribute())
if (strcmp(attr.name(), "r") != 0) m_cellNode->append_copy(attr);
}
}
@ -161,7 +161,7 @@ void XLCell::copyFrom(XLCell const& other)
/**
* @details
*/
XLCell::operator bool() const { return m_cellNode && *m_cellNode; }
XLCell::operator bool() const { return m_cellNode && (not m_cellNode->empty() ); } // ===== 2024-05-28: replaced explicit bool evaluation
/**
* @details This function returns a const reference to the cellReference property.
@ -197,6 +197,16 @@ XLFormulaProxy& XLCell::formula() { return m_formulaProxy; }
*/
void XLCell::print(std::basic_ostream<char>& ostr) const { m_cellNode->print(ostr); }
/**
* @details
*/
XLCellAssignable::XLCellAssignable (XLCell const & other) : XLCell(other) {}
/**
* @details
*/
XLCellAssignable::XLCellAssignable (XLCell && other) : XLCell(std::move(other)) {}
/**
* @details
*/

View File

@ -62,10 +62,13 @@ XLCellIterator::XLCellIterator(const XLCellRange& cellRange, XLIteratorLocation
: m_dataNode(std::make_unique<XMLNode>(*cellRange.m_dataNode)),
m_topLeft(cellRange.m_topLeft),
m_bottomRight(cellRange.m_bottomRight),
m_sharedStrings(cellRange.m_sharedStrings)
m_sharedStrings(cellRange.m_sharedStrings),
m_endReached(false)
{
if (loc == XLIteratorLocation::End)
if (loc == XLIteratorLocation::End) {
m_currentCell = XLCell();
m_endReached = true;
}
else {
m_currentCell = XLCell(getCellNode(getRowNode(*m_dataNode, m_topLeft.row()), m_topLeft.column()), m_sharedStrings);
}
@ -84,7 +87,8 @@ XLCellIterator::XLCellIterator(const XLCellIterator& other)
m_topLeft(other.m_topLeft),
m_bottomRight(other.m_bottomRight),
m_currentCell(other.m_currentCell),
m_sharedStrings(other.m_sharedStrings)
m_sharedStrings(other.m_sharedStrings),
m_endReached(other.m_endReached)
{}
/**
@ -103,6 +107,7 @@ XLCellIterator& XLCellIterator::operator=(const XLCellIterator& other)
m_bottomRight = other.m_bottomRight;
m_currentCell = other.m_currentCell;
m_sharedStrings = other.m_sharedStrings;
m_endReached = other.m_endReached;
}
return *this;
@ -118,6 +123,9 @@ XLCellIterator& XLCellIterator::operator=(XLCellIterator&& other) noexcept = def
*/
XLCellIterator& XLCellIterator::operator++()
{
if (m_endReached)
throw XLInputError("XLCellIterator: tried to increment beyond end operator");
auto ref = m_currentCell.cellReference();
// ===== Determine the cell reference for the next cell.
@ -128,11 +136,15 @@ XLCellIterator& XLCellIterator::operator++()
else
ref = XLCellReference(ref.row() + 1, m_topLeft.column());
// 2024-06-03 TBD TODO: why ref > m_bottomRight - that shouldn't be possible? --> added exception to test for this
if (ref > m_bottomRight)
throw XLInternalError("XLCellIterator became > m_bottomRight - this should not happen!");
if (m_endReached)
m_currentCell = XLCell();
else if (ref > m_bottomRight || ref.row() == m_currentCell.cellReference().row()) {
else if (ref > m_bottomRight || ref.row() == m_currentCell.cellReference().row()) { // TBD: remove ref > m_bottomRight condition unless I overlooked something
auto node = m_currentCell.m_cellNode->next_sibling_of_type(pugi::node_element);
if (!node || XLCellReference(node.attribute("r").value()) != ref) {
if (node.empty() || XLCellReference(node.attribute("r").value()) != ref) {
node = m_currentCell.m_cellNode->parent().insert_child_after("c", *m_currentCell.m_cellNode);
node.append_attribute("r").set_value(ref.address().c_str());
}
@ -140,13 +152,12 @@ XLCellIterator& XLCellIterator::operator++()
}
else if (ref.row() > m_currentCell.cellReference().row()) {
auto rowNode = m_currentCell.m_cellNode->parent().next_sibling_of_type(pugi::node_element);
if (!rowNode || rowNode.attribute("r").as_ullong() != ref.row()) {
if (rowNode.empty() || rowNode.attribute("r").as_ullong() != ref.row()) {
rowNode = m_currentCell.m_cellNode->parent().parent().insert_child_after("row", m_currentCell.m_cellNode->parent());
rowNode.append_attribute("r").set_value(ref.row());
// getRowNode(*m_dataNode, ref.row());
}
m_currentCell = XLCell(getCellNode(rowNode, ref.column()), m_sharedStrings);
// ===== Pass the already known ref.row() to getCellNode so that it does not have to be fetched again
m_currentCell = XLCell(getCellNode(rowNode, ref.column(), ref.row()), m_sharedStrings);
}
else
throw XLInternalError("An internal error occured");
@ -191,14 +202,42 @@ bool XLCellIterator::operator!=(const XLCellIterator& rhs) const { return !(*thi
/**
* @details
* @todo This implementation is rather ineffecient. Consider an alternative implementation.
* @note 2024-06-03: implemented a calculated distance based on m_currentCell, m_topLeft and m_bottomRight (if m_endReached)
* accordingly, implemented defined setting of m_endReached at all times
*/
uint64_t XLCellIterator::distance(const XLCellIterator& last)
{
uint64_t result = 0;
while (*this != last) {
++result;
++(*this);
}
return result;
// ===== Determine rows and columns, taking into account beyond-the-end iterators
uint32_t row = (m_endReached ? m_bottomRight.row() : m_currentCell.cellReference().row());
uint16_t col = (m_endReached ? m_bottomRight.column() + 1 : m_currentCell.cellReference().column());
uint32_t lastRow = (last.m_endReached ? last.m_bottomRight.row() : last.m_currentCell.cellReference().row());
// ===== lastCol can store +1 for beyond-the-end iterator without overflow because MAX_COLS is less than max uint16_t
uint16_t lastCol = (last.m_endReached ? last.m_bottomRight.column() + 1 : last.m_currentCell.cellReference().column());
uint16_t rowWidth = m_bottomRight.column() - m_topLeft.column() + 1; // amount of cells in a row of the iterator range
int64_t distance = ((int64_t)(lastRow) - row) * rowWidth // row distance * rowWidth
+ (int64_t)(lastCol) - col; // + column distance (may be negative)
if (distance < 0)
throw XLInputError("XLCellIterator::distance is negative");
return static_cast<uint64_t>(distance); // after excluding negative result: cast back to positive value
/* OBSOLETE CODE:
// uint64_t result = 0;
// while (*this != last) {
// ++result;
// ++(*this);
// }
// return result;
*/
}
/**
* @details
*/
const std::string XLCellIterator::address() const
{
uint32_t row = (m_endReached ? m_bottomRight.row() : m_currentCell.cellReference().row());
uint16_t col = (m_endReached ? m_bottomRight.column() + 1 : m_currentCell.cellReference().column());
return (m_endReached ? "END(" : "") + XLCellReference(row, col).address() + (m_endReached ? ")" : "");
}

View File

@ -50,17 +50,13 @@ YM M9 MM MM MM MM MM d' `MM. MM MM d' `MM.
# include <charconv>
#endif
#include <cstdint> // pull requests #216, #232
#include <locale> // std::isdigit
// ===== OpenXLSX Includes ===== //
#include "XLCellReference.hpp"
#include "XLConstants.hpp"
#include "XLException.hpp"
#include <algorithm>
#include <cstring>
#include <iostream>
#include <unordered_map>
using namespace OpenXLSX;
constexpr uint8_t alphabetSize = 26;
@ -81,13 +77,13 @@ namespace
*/
XLCellReference::XLCellReference(const std::string& cellAddress)
{
if (!cellAddress.empty()) setAddress(cellAddress);
if (cellAddress.empty() || !addressIsValid(m_row, m_column)) { // 2024-04-25: throw exception on empty string
if (not cellAddress.empty()) setAddress(cellAddress);
if (cellAddress.empty() || not addressIsValid(m_row, m_column)) { // 2024-04-25: throw exception on empty string
throw XLCellAddressError("Cell reference is invalid");
// TODO below: possibly deprecated (if exception remains)
m_row = 1;
m_column = 1;
m_cellAddress = "A1";
// ===== 2024-05-27: below code is obsolete due to exception on invalid cellAddress
// m_row = 1;
// m_column = 1;
// m_cellAddress = "A1";
}
}
@ -182,7 +178,7 @@ XLCellReference& XLCellReference::operator--()
else if (m_column == 1 && m_row == 1) {
m_column = MAX_COLS;
m_row = MAX_ROWS;
m_cellAddress = "XFD1048576";
m_cellAddress = "XFD1048576"; // this address represents the very last cell that an excel spreadsheet can reference / support
}
return *this;
}
@ -268,7 +264,7 @@ std::string XLCellReference::rowAsString(uint32_t row)
{
#ifdef CHARCONV_ENABLED
std::array<char, 7> str {}; // NOLINT
const auto* p = std::to_chars(str.data(), str.data() + str.size(), row).ptr;
const auto* p = std::to_chars(str.data(), str.data() + str.size(), row).ptr;
return std::string { str.data(), static_cast<uint16_t>(p - str.data()) };
#else
std::string result;
@ -292,10 +288,10 @@ uint32_t XLCellReference::rowAsNumber(const std::string& row)
#ifdef CHARCONV_ENABLED
uint32_t value = 0;
std::from_chars(row.data(), row.data() + row.size(), value); // NOLINT
#else
uint32_t value = stoul(row);
#endif
return value;
#else
return stoul(row);
#endif
}
/**
@ -314,11 +310,11 @@ std::string XLCellReference::columnAsString(uint16_t column)
result += static_cast<char>((column - (alphabetSize + 1)) % alphabetSize + asciiOffset + 1);
}
// ===== If there is three letters in the Column Name:
// ===== If there are three letters in the Column Name:
else {
result += char((column - 703) / (alphabetSize * alphabetSize) + asciiOffset + 1); // NOLINT
result += char(((column - 703) / alphabetSize) % alphabetSize + asciiOffset + 1); // NOLINT
result += char((column - 703) % alphabetSize + asciiOffset + 1); // NOLINT
result += static_cast<char>((column - 703) / (alphabetSize * alphabetSize) + asciiOffset + 1); // NOLINT
result += static_cast<char>(((column - 703) / alphabetSize) % alphabetSize + asciiOffset + 1); // NOLINT
result += static_cast<char>((column - 703) % alphabetSize + asciiOffset + 1); // NOLINT
}
return result;
@ -326,48 +322,74 @@ std::string XLCellReference::columnAsString(uint16_t column)
/**
* @details Helper method to calculate the column number from column letter.
* @throws XLInputError
* @note 2024-06-03: added check for valid address
*/
uint16_t XLCellReference::columnAsNumber(const std::string& column)
{
// uint16_t result = 0;
//
// for (int16_t i = static_cast<int16_t>(column.size() - 1), j = 0; i >= 0; --i, ++j) { // NOLINT
// result += static_cast<uint16_t>((column[static_cast<uint64_t>(i)] - asciiOffset) * std::pow(alphabetSize, j));
// }
//
// return result;
uint16_t result = 0;
uint16_t factor = 1;
for (int16_t i = static_cast<int16_t>(column.size() - 1); i >= 0; --i) {
result += static_cast<uint16_t>((column[static_cast<uint64_t>(i)] - asciiOffset) * factor);
factor *= alphabetSize;
uint64_t letterCount = 0;
uint32_t colNo = 0;
for (const auto letter : column) {
if (letter >= 'A' && letter <= 'Z') { // allow only uppercase letters
++letterCount;
colNo = colNo * 26 + (letter - 'A' + 1);
}
else
break;
}
return result;
// ===== If the full string was decoded and colNo is within allowed range [1;MAX_COLS]
if(letterCount == column.length() && colNo > 0 && colNo <= MAX_COLS)
return colNo;
throw XLInputError("XLCellReference::columnAsNumber - column \"" + column + "\" is invalid");
/* 2024-06-19 OBSOLETE CODE:
// uint16_t result = 0;
// uint16_t factor = 1;
//
// for (int16_t i = static_cast<int16_t>(column.size() - 1); i >= 0; --i) {
// result += static_cast<uint16_t>((column[static_cast<uint64_t>(i)] - asciiOffset) * factor);
// factor *= alphabetSize;
// }
//
// return result;
*/
}
/**
* @details Helper method for calculating the coordinates from the cell address.
* @todo Consider checking if the given address is valid.
* @throws XLInputError
* @note 2024-06-03: added check for valid address
*/
XLCoordinates XLCellReference::coordinatesFromAddress(const std::string& address)
{
// uint64_t letterCount = 0;
// for (const auto letter : address) {
// if (letter >= 65) // NOLINT
// ++letterCount;
// else if (letter <= 57) // NOLINT
// break;
// }
//
// const auto numberCount = address.size() - letterCount;
//
// return std::make_pair(rowAsNumber(address.substr(letterCount, numberCount)), columnAsNumber(address.substr(0, letterCount)));
uint64_t letterCount = 0;
uint32_t colNo = 0;
for (const auto letter : address) {
if (letter >= 'A' && letter <= 'Z') { // allow only uppercase letters
++letterCount;
colNo = colNo * 26 + (letter - 'A' + 1);
}
else
break;
}
auto it = std::find_if(address.begin(), address.end(), ::isdigit);
auto columnPart = std::string(address.begin(), it);
auto rowPart = std::string(it, address.end());
// ===== If address contains between 1 and 3 letters and has at least 1 more character for the row
if(colNo > 0 && colNo <= MAX_COLS && address.length() > letterCount) {
size_t pos = letterCount;
uint64_t rowNo = 0;
for (; pos < address.length() && std::isdigit(address[pos]); ++pos) // check digits
rowNo = rowNo * 10 + (address[pos] - '0');
if (pos == address.length() && rowNo <= MAX_ROWS) // full address was < 4 letters + only digits
return std::make_pair(rowNo, colNo);
}
throw XLInputError("XLCellReference::coordinatesFromAddress - address \"" + address + "\" is invalid");
return std::make_pair(rowAsNumber(rowPart), columnAsNumber(columnPart));
/* 2024-06-19 OBSOLETE CODE
// auto it = std::find_if(address.begin(), address.end(), ::isdigit);
// auto columnPart = std::string(address.begin(), it);
// auto rowPart = std::string(it, address.end());
//
// return std::make_pair(rowAsNumber(rowPart), columnAsNumber(columnPart));
*/
}

View File

@ -121,9 +121,9 @@ std::string XLCellValue::typeAsString() const
*/
XLCellValueProxy::XLCellValueProxy(XLCell* cell, XMLNode* cellNode) : m_cell(cell), m_cellNode(cellNode)
{
assert(cell); // NOLINT
// assert(cellNode); // NOLINT
// assert(!cellNode->empty()); // NOLINT
assert(cell != nullptr); // NOLINT
// assert(cellNode); // NOLINT
// assert(not cellNode->empty()); // NOLINT
}
/**
@ -187,8 +187,8 @@ XLCellValueProxy::operator XLCellValue() const {
XLCellValueProxy& XLCellValueProxy::clear()
{
// ===== Check that the m_cellNode is valid.
assert(m_cellNode); // NOLINT
assert(!m_cellNode->empty()); // NOLINT
assert(m_cellNode != nullptr); // NOLINT
assert(not m_cellNode->empty()); // NOLINT
// ===== Remove the type attribute
m_cellNode->remove_attribute("t");
@ -213,8 +213,8 @@ XLCellValueProxy& XLCellValueProxy::clear()
XLCellValueProxy& XLCellValueProxy::setError(const std::string &error)
{
// ===== Check that the m_cellNode is valid.
assert(m_cellNode); // NOLINT
assert(!m_cellNode->empty()); // NOLINT
assert(m_cellNode != nullptr); // NOLINT
assert(not m_cellNode->empty()); // NOLINT
// ===== If the cell node doesn't have a type attribute, create it.
if (!m_cellNode->attribute("t")) m_cellNode->append_attribute("t");
@ -245,34 +245,34 @@ XLCellValueProxy& XLCellValueProxy::setError(const std::string &error)
XLValueType XLCellValueProxy::type() const
{
// ===== Check that the m_cellNode is valid.
assert(m_cellNode); // NOLINT
assert(!m_cellNode->empty()); // NOLINT
assert(m_cellNode != nullptr); // NOLINT
assert(not m_cellNode->empty()); // NOLINT
// ===== If neither a Type attribute or a getValue node is present, the cell is empty.
if (!m_cellNode->attribute("t") && !m_cellNode->child("v")) return XLValueType::Empty;
// ===== If a Type attribute is not present, but a value node is, the cell contains a number.
if (!m_cellNode->attribute("t") || ((strcmp(m_cellNode->attribute("t").value(), "n") == 0) && !m_cellNode->child("v").empty())) {
if (const std::string numberString = m_cellNode->child("v").text().get(); numberString.find('.') != std::string::npos || numberString.find("E-") != std::string::npos ||
numberString.find("e-") != std::string::npos)
if (m_cellNode->attribute("t").empty() || ((strcmp(m_cellNode->attribute("t").value(), "n") == 0) && not m_cellNode->child("v").empty())) {
if (const std::string numberString = m_cellNode->child("v").text().get();
numberString.find('.') != std::string::npos || numberString.find("E-") != std::string::npos || numberString.find("e-") != std::string::npos)
return XLValueType::Float;
return XLValueType::Integer;
}
// ===== If the cell is of type "s", the cell contains a shared string.
if (!m_cellNode->attribute("t").empty() && strcmp(m_cellNode->attribute("t").value(), "s") == 0)
if (not m_cellNode->attribute("t").empty() && strcmp(m_cellNode->attribute("t").value(), "s") == 0)
return XLValueType::String; // NOLINT
// ===== If the cell is of type "inlineStr", the cell contains an inline string.
if (!m_cellNode->attribute("t").empty() && strcmp(m_cellNode->attribute("t").value(), "inlineStr") == 0)
if (not m_cellNode->attribute("t").empty() && strcmp(m_cellNode->attribute("t").value(), "inlineStr") == 0)
return XLValueType::String;
// ===== If the cell is of type "str", the cell contains an ordinary string.
if (!m_cellNode->attribute("t").empty() && strcmp(m_cellNode->attribute("t").value(), "str") == 0)
if (not m_cellNode->attribute("t").empty() && strcmp(m_cellNode->attribute("t").value(), "str") == 0)
return XLValueType::String;
// ===== If the cell is of type "b", the cell contains a boolean.
if (!m_cellNode->attribute("t").empty() && strcmp(m_cellNode->attribute("t").value(), "b") == 0)
if (not m_cellNode->attribute("t").empty() && strcmp(m_cellNode->attribute("t").value(), "b") == 0)
return XLValueType::Boolean;
// ===== Otherwise, the cell contains an error.
@ -311,11 +311,11 @@ std::string XLCellValueProxy::typeAsString() const
void XLCellValueProxy::setInteger(int64_t numberValue) // NOLINT
{
// ===== Check that the m_cellNode is valid.
assert(m_cellNode); // NOLINT
assert(!m_cellNode->empty()); // NOLINT
assert(m_cellNode != nullptr); // NOLINT
assert(not m_cellNode->empty()); // NOLINT
// ===== If the cell node doesn't have a value child node, create it.
if (!m_cellNode->child("v")) m_cellNode->append_child("v");
if (m_cellNode->child("v").empty()) m_cellNode->append_child("v");
// ===== The type ("t") attribute is not required for number values.
m_cellNode->remove_attribute("t");
@ -339,14 +339,14 @@ void XLCellValueProxy::setInteger(int64_t numberValue) // NOLINT
void XLCellValueProxy::setBoolean(bool numberValue) // NOLINT
{
// ===== Check that the m_cellNode is valid.
assert(m_cellNode); // NOLINT
assert(!m_cellNode->empty()); // NOLINT
assert(m_cellNode != nullptr); // NOLINT
assert(not m_cellNode->empty()); // NOLINT
// ===== If the cell node doesn't have a type child node, create it.
if (!m_cellNode->attribute("t")) m_cellNode->append_attribute("t");
if (m_cellNode->attribute("t").empty()) m_cellNode->append_attribute("t");
// ===== If the cell node doesn't have a value child node, create it.
if (!m_cellNode->child("v")) m_cellNode->append_child("v");
if (m_cellNode->child("v").empty()) m_cellNode->append_child("v");
// ===== Set the type attribute.
m_cellNode->attribute("t").set_value("b");
@ -372,11 +372,11 @@ void XLCellValueProxy::setFloat(double numberValue)
// check for nan / inf
if (std::isfinite(numberValue)) {
// ===== Check that the m_cellNode is valid.
assert(m_cellNode); // NOLINT
assert(!m_cellNode->empty()); // NOLINT
assert(m_cellNode != nullptr); // NOLINT
assert(not m_cellNode->empty()); // NOLINT
// ===== If the cell node doesn't have a value child node, create it.
if (!m_cellNode->child("v")) m_cellNode->append_child("v");
if (m_cellNode->child("v").empty()) m_cellNode->append_child("v");
// ===== The type ("t") attribute is not required for number values.
m_cellNode->remove_attribute("t");
@ -405,14 +405,14 @@ void XLCellValueProxy::setFloat(double numberValue)
void XLCellValueProxy::setString(const char* stringValue) // NOLINT
{
// ===== Check that the m_cellNode is valid.
assert(m_cellNode); // NOLINT
assert(!m_cellNode->empty()); // NOLINT
assert(m_cellNode != nullptr); // NOLINT
assert(not m_cellNode->empty()); // NOLINT
// ===== If the cell node doesn't have a type child node, create it.
if (!m_cellNode->attribute("t")) m_cellNode->append_attribute("t");
if (m_cellNode->attribute("t").empty()) m_cellNode->append_attribute("t");
// ===== If the cell node doesn't have a value child node, create it.
if (!m_cellNode->child("v")) m_cellNode->append_child("v");
if (m_cellNode->child("v").empty()) m_cellNode->append_child("v");
// ===== Set the type attribute.
m_cellNode->attribute("t").set_value("s");
@ -451,8 +451,8 @@ void XLCellValueProxy::setString(const char* stringValue) // NOLINT
XLCellValue XLCellValueProxy::getValue() const
{
// ===== Check that the m_cellNode is valid.
assert(m_cellNode); // NOLINT
assert(!m_cellNode->empty()); // NOLINT
assert(m_cellNode != nullptr); // NOLINT
assert(not m_cellNode->empty()); // NOLINT
switch (type()) {
case XLValueType::Empty:

View File

@ -45,6 +45,7 @@ YM M9 MM MM MM MM MM d' `MM. MM MM d' `MM.
// ===== External Includes ===== //
#include <cstdint> // pull requests #216, #232
#include <ios> // std::hex
#include <sstream>
// ===== OpenXLSX Includes ===== //

View File

@ -80,13 +80,13 @@ void XLColumn::setWidth(float width) // NOLINT
{
// Set the 'Width' attribute for the Cell. If it does not exist, create it.
auto widthAtt = columnNode().attribute("width");
if (!widthAtt) widthAtt = columnNode().append_attribute("width");
if (widthAtt.empty()) widthAtt = columnNode().append_attribute("width");
widthAtt.set_value(width);
// Set the 'customWidth' attribute for the Cell. If it does not exist, create it.
auto customAtt = columnNode().attribute("customWidth");
if (!customAtt) customAtt = columnNode().append_attribute("customWidth");
if (customAtt.empty()) customAtt = columnNode().append_attribute("customWidth");
customAtt.set_value("1");
}
@ -102,7 +102,7 @@ bool XLColumn::isHidden() const { return columnNode().attribute("hidden").as_boo
void XLColumn::setHidden(bool state) // NOLINT
{
auto hiddenAtt = columnNode().attribute("hidden");
if (!hiddenAtt) hiddenAtt = columnNode().append_attribute("hidden");
if (hiddenAtt.empty()) hiddenAtt = columnNode().append_attribute("hidden");
if (state)
hiddenAtt.set_value("1");

View File

@ -289,7 +289,7 @@ std::vector<XLContentItem> XLContentTypes::getContentItems()
{
std::vector<XLContentItem> result;
XMLNode item = xmlDocument().document_element().first_child_of_type(pugi::node_element);
while (item) {
while (not item.empty()) {
if (strcmp(item.name(), "Override") == 0) result.emplace_back(item);
item = item.next_sibling_of_type(pugi::node_element);
}

View File

@ -5,8 +5,7 @@
#include "XLDateTime.hpp"
#include "XLException.hpp"
#include <cmath>
#include <cstdint>
#include <string>
#include <cstdint> // int32_t
namespace
{

View File

@ -44,6 +44,7 @@ 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
@ -58,8 +59,6 @@ YM M9 MM MM MM MM MM d' `MM. MM MM d' `MM.
#include "XLSheet.hpp"
#include "utilities/XLUtilities.hpp"
#include <algorithm>
using namespace OpenXLSX;
namespace
@ -431,7 +430,10 @@ XLDocument::XLDocument(const std::string& docPath, const IZipArchive& zipArchive
/**
* @details The destructor calls the closeDocument method before the object is destroyed.
*/
XLDocument::~XLDocument() { close(); }
XLDocument::~XLDocument()
{
if (isOpen()) close();// 2024-05-31 prevent double-close if document has been manually closed before
}
/**
* @details The openDocument method opens the .xlsx package in the following manner:
@ -443,8 +445,7 @@ XLDocument::~XLDocument() { close(); }
void XLDocument::open(const std::string& fileName)
{
// Check if a document is already open. If yes, close it.
// TODO: Consider throwing if a file is already open.
if (m_archive.isOpen()) close();
if (m_archive.isOpen()) close(); // TBD: consider throwing if a file is already open.
m_filePath = fileName;
m_archive.open(m_filePath);
@ -476,56 +477,64 @@ void XLDocument::open(const std::string& fileName)
// ===== Read shared strings table.
XMLDocument* sharedStrings = getXmlData("xl/sharedStrings.xml")->getXmlDocument();
if (!sharedStrings->document_element().attribute("uniqueCount").empty())
if (not sharedStrings->document_element().attribute("uniqueCount").empty())
sharedStrings->document_element().remove_attribute(
"uniqueCount"); // pull request #192 -> remove count & uniqueCount as they are optional
if (!sharedStrings->document_element().attribute("count").empty())
if (not sharedStrings->document_element().attribute("count").empty())
sharedStrings->document_element().remove_attribute(
"count"); // pull request #192 -> remove count & uniqueCount as they are optional
"count"); // pull request #192 -> remove count & uniqueCount as they are optional
XMLNode node =
sharedStrings->document_element().first_child_of_type(pugi::node_element); // pull request #186: Skip non-element nodes in sst.
while (node) {
while (not node.empty()) {
// ===== Validate si node name.
using namespace std::literals::string_literals;
if (node.name() != "si"s) throw XLInputError("xl/sharedStrings.xml sst node name \""s + node.name() + "\" is not \"si\""s);
// ===== Find first node_element child of si node.
if (XMLNode elem = node.first_child_of_type(pugi::node_element)) {
// ===== If shared string is a rich text string
XMLNode elem = node.first_child_of_type(pugi::node_element);
if (not elem.empty()) {
// ===== Ignore phonetic property tags
if (std::string(elem.name()) == "rPh" || std::string(elem.name()) == "phoneticPr") {}
// ===== If shared string is a rich text string
else if (std::string(elem.name()) == "r") {
std::string result;
while (elem) {
while (not elem.empty()) {
result += elem.child("t").text().get();
elem = elem.next_sibling_of_type(pugi::node_element);
}
m_sharedStringCache.emplace_back(result);
}
// ===== If shared string is a regular string
else if (std::string(elem.name()) == "t") { // 2024-05-03: support a string composed of multiple <t> nodes, because LibreOffice accepts it
else { // 2024-05-03: support a string composed of multiple <t> nodes, because LibreOffice accepts it
std::string result;
while (elem) {
// if (elem.name() != "t"s)
// throw XLInputError("xl/sharedStrings.xml si node \""s + node.name() + "\" is neiter \"r\" not \"t\""s);
while (not elem.empty()) {
if (elem.name() != "t"s)
throw XLInputError("xl/sharedStrings.xml si node \""s + node.name() + "\" is none of \"r\", \"t\", \"rPh\", \"phoneticPr\""s);
result += elem.text().get();
elem = elem.next_sibling_of_type(pugi::node_element);
}
m_sharedStringCache.emplace_back(result);
}
else
throw XLInputError("xl/sharedStrings.xml si node \""s + node.name() + "\" is neiter \"r\" not \"t\""s);
}
node = node.next_sibling_of_type(pugi::node_element);
}
// ===== Open the workbook and document property items
// TODO: If property data doesn't exist, consider creating them, instead of ignoring it.
m_coreProperties = (hasXmlData("docProps/core.xml") ? XLProperties(getXmlData("docProps/core.xml")) : XLProperties());
m_appProperties = (hasXmlData("docProps/app.xml") ? XLAppProperties(getXmlData("docProps/app.xml")) : XLAppProperties());
m_sharedStrings = XLSharedStrings(getXmlData("xl/sharedStrings.xml"), &m_sharedStringCache);
m_workbook = XLWorkbook(getXmlData("xl/workbook.xml"));
// 2024-05-31: moved XLWorkbook object creation up in code worksheets info can be used for XLAppProperties generation from scratch
// ===== 2024-06-03: creating core and extended properties if they do not exist
execCommand(XLCommand(XLCommandType::CheckAndFixCoreProperties)); // checks & fixes consistency of docProps/core.xml related data
execCommand(XLCommand(XLCommandType::CheckAndFixExtendedProperties)); // checks & fixes consistency of docProps/app.xml related data
if (!hasXmlData("docProps/core.xml") || !hasXmlData("docProps/app.xml"))
throw XLInternalError("Failed to repair docProps (core.xml and/or app.xml)");
m_coreProperties = XLProperties(getXmlData("docProps/core.xml"));
m_appProperties = XLAppProperties(getXmlData("docProps/app.xml"), m_workbook.xmlDocument());
m_sharedStrings = XLSharedStrings(getXmlData("xl/sharedStrings.xml"), &m_sharedStringCache);
}
/**
@ -553,7 +562,7 @@ void XLDocument::create(const std::string& fileName)
*/
void XLDocument::close()
{
if (m_archive) m_archive.close();
if (m_archive.isValid()) m_archive.close();
m_filePath.clear();
m_data.clear();
@ -590,9 +599,15 @@ void XLDocument::saveAs(const std::string& fileName)
/**
* @details
* @todo Currently, this method returns the full path, which is not the intention.
*/
const std::string& XLDocument::name() const { return m_filePath; }
const std::string XLDocument::name() const
{
size_t pos = m_filePath.find_last_of('/');
if (pos != std::string::npos)
return m_filePath.substr(pos + 1);
else
return m_filePath;
}
/**
* @details
@ -679,7 +694,6 @@ bool getAppVersion(const std::string& versionString, int& majorVersion, int& min
const size_t end = versionString.find_last_not_of(" \t");
if (begin != std::string::npos && dotPos != std::string::npos) {
std::string trimmedValue = versionString.substr(begin, end + 1 - begin);
const std::string strMajorVersion = versionString.substr(begin, dotPos - begin);
const std::string strMinorVersion = versionString.substr(dotPos + 1, end - dotPos);
try {
@ -844,7 +858,7 @@ bool XLDocument::execCommand(const XLCommand& command)
case XLCommandType::SetSheetIndex: {
XLQuery qry(XLQueryType::QuerySheetName);
const auto sheetName = execQuery(qry.setParam("sheetID", command.getParam<std::string>("sheetID"))).result<std::string>();
const auto sheetName = execQuery(qry.setParam("sheetID", command.getParam<std::string>("sheetID"))).result<std::string>();
m_workbook.setSheetIndex(sheetName, command.getParam<uint16_t>("sheetIndex"));
} break;
@ -859,6 +873,46 @@ bool XLDocument::execCommand(const XLCommand& command)
if (item != m_data.end()) m_data.erase(item);
} break;
case XLCommandType::CheckAndFixCoreProperties: { // does nothing if core properties are in good shape
// ===== If _rels/.rels has no entry for docProps/core.xml
if (!m_docRelationships.targetExists("docProps/core.xml"))
m_docRelationships.addRelationship(XLRelationshipType::CoreProperties, "docProps/core.xml"); // Fix m_docRelationships
// ===== If docProps/core.xml is missing
if (!m_archive.hasEntry("docProps/core.xml"))
m_archive.addEntry("docProps/core.xml", "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?>"); // create empty docProps/core.xml
// ===== XLProperties constructor will take care of adding template content
// ===== If [Content Types].xml has no relationship for docProps/core.xml
if (!hasXmlData("docProps/core.xml")) {
m_contentTypes.addOverride("/docProps/core.xml", XLContentType::CoreProperties); // add content types entry
m_data.emplace_back( // store new entry in m_data
/* parentDoc */ this,
/* xmlPath */ "docProps/core.xml",
/* xmlID */ m_docRelationships.relationshipByTarget("docProps/core.xml").id(),
/* xmlType */ XLContentType::CoreProperties);
}
} break;
case XLCommandType::CheckAndFixExtendedProperties: { // does nothing if extended properties are in good shape
// ===== If _rels/.rels has no entry for docProps/app.xml
if (!m_docRelationships.targetExists("docProps/app.xml"))
m_docRelationships.addRelationship(XLRelationshipType::ExtendedProperties, "docProps/app.xml"); // Fix m_docRelationships
// ===== If docProps/app.xml is missing
if (!m_archive.hasEntry("docProps/app.xml"))
m_archive.addEntry("docProps/app.xml", "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?>"); // create empty docProps/app.xml
// ===== XLAppProperties constructor will take care of adding template content
// ===== If [Content Types].xml has no relationship for docProps/app.xml
if (!hasXmlData("docProps/app.xml")) {
m_contentTypes.addOverride("/docProps/app.xml", XLContentType::ExtendedProperties); // add content types entry
m_data.emplace_back( // store new entry in m_data
/* parentDoc */ this,
/* xmlPath */ "docProps/app.xml",
/* xmlID */ m_docRelationships.relationshipByTarget("docProps/app.xml").id(),
/* xmlType */ XLContentType::ExtendedProperties);
}
} break;
case XLCommandType::AddSharedStrings: {
const std::string sharedStrings {
"<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
@ -1004,6 +1058,8 @@ XLQuery XLDocument::execQuery(const XLQuery& query) const
throw XLInternalError("Path does not exist in zip archive (" + query.getParam<std::string>("xmlPath") + ")");
return XLQuery(query).setResult(&*result);
}
default:
throw XLInternalError("XLDocument::execQuery: unknown query type " + std::to_string(static_cast<uint8_t>(query.type())));
}
return query; // Needed in order to suppress compiler warning
@ -1019,7 +1075,7 @@ XLQuery XLDocument::execQuery(const XLQuery& query) { return static_cast<const X
*/
XLDocument::operator bool() const
{
return !!m_archive; // NOLINT
return m_archive.isValid(); // NOLINT
}
/**

View File

@ -2,12 +2,13 @@
// Created by Kenneth Balslev on 27/08/2021.
//
// ===== OpenXLSX Includes ===== //
#include "XLFormula.hpp"
// ===== External Includes ===== //
#include <cassert>
#include <pugixml.hpp>
// ===== OpenXLSX Includes ===== //
#include "XLFormula.hpp"
#include <XLException.hpp>
#include <cassert>
using namespace OpenXLSX;
@ -121,11 +122,11 @@ std::string XLFormulaProxy::get() const { return getFormula().get(); }
XLFormulaProxy& XLFormulaProxy::clear()
{
// ===== Check that the m_cellNode is valid.
assert(m_cellNode); // NOLINT
assert(!m_cellNode->empty()); // NOLINT
assert(m_cellNode != nullptr); // NOLINT
assert(not m_cellNode->empty()); // NOLINT
// ===== Remove the value node.
if (m_cellNode->child("f")) m_cellNode->remove_child("f");
if (not m_cellNode->child("f").empty()) m_cellNode->remove_child("f");
return *this;
}
@ -136,12 +137,12 @@ XLFormulaProxy& XLFormulaProxy::clear()
void XLFormulaProxy::setFormulaString(const char* formulaString, bool resetValue) // NOLINT
{
// ===== Check that the m_cellNode is valid.
assert(m_cellNode); // NOLINT
assert(!m_cellNode->empty()); // NOLINT
assert(m_cellNode != nullptr); // NOLINT
assert(not m_cellNode->empty()); // NOLINT
// ===== If the cell node doesn't have a value child node, create it.
if (!m_cellNode->child("f")) m_cellNode->append_child("f");
if (!m_cellNode->child("v")) m_cellNode->append_child("v");
if (m_cellNode->child("f").empty()) m_cellNode->append_child("f");
if (m_cellNode->child("v").empty()) m_cellNode->append_child("v");
// ===== Remove the formula type and shared index attributes, if they exist.
m_cellNode->child("f").remove_attribute("t");
@ -169,19 +170,22 @@ void XLFormulaProxy::setFormulaString(const char* formulaString, bool resetValue
*/
XLFormula XLFormulaProxy::getFormula() const
{
assert(m_cellNode); // NOLINT
assert(!m_cellNode->empty()); // NOLINT
assert(m_cellNode != nullptr); // NOLINT
assert(not m_cellNode->empty()); // NOLINT
const auto formulaNode = m_cellNode->child("f");
// ===== If the formula node doesn't exist, return an empty XLFormula object.
if (!formulaNode) return XLFormula();
if (formulaNode.empty()) return XLFormula();
// ===== If the formula type is 'shared' or 'array', throw an exception.
if (formulaNode.attribute("t") && std::string(formulaNode.attribute("t").value()) == "shared")
throw XLFormulaError("Shared formulas not supported.");
if (formulaNode.attribute("t") && std::string(formulaNode.attribute("t").value()) == "array")
throw XLFormulaError("Array formulas not supported.");
if (not formulaNode.attribute("t").empty() ) { // 2024-05-28: de-duplicated check (only relevant for performance,
// xml_attribute::value() returns an empty string for empty attributes)
if (std::string(formulaNode.attribute("t").value()) == "shared")
throw XLFormulaError("Shared formulas not supported.");
if (std::string(formulaNode.attribute("t").value()) == "array")
throw XLFormulaError("Array formulas not supported.");
}
return XLFormula(formulaNode.text().get());
}

View File

@ -51,6 +51,7 @@ YM M9 MM MM MM MM MM d' `MM. MM MM d' `MM.
// ===== OpenXLSX Includes ===== //
#include "XLDocument.hpp"
#include "XLProperties.hpp"
#include "XLRelationships.hpp" // GetStringFromType
#include <XLException.hpp>
@ -67,15 +68,16 @@ namespace
std::vector<std::string> headingPairsCategoriesStrings(XMLNode docNode)
{
// TODO: test this again
// 2024-05-28 DONE: tested this code with two pairs in headingPairsNode
std::vector<std::string> result;
XMLNode item = headingPairsNode(docNode).first_child_of_type(pugi::node_element);
while (item) {
while (not item.empty()) {
result.push_back(item.first_child_of_type(pugi::node_element).child_value());
item = item.next_sibling_of_type(pugi::node_element)
.next_sibling_of_type(pugi::node_element); // advance two elements to skip count node
}
return std::move(result);
return result; // 2024-05-28: std::move should not be used when the operand of a return statement is the name of a local variable
// as this can prevent named return value optimization (NRVO, copy elision)
}
XMLNode sheetNames(XMLNode docNode) { return docNode.child("TitlesOfParts").first_child_of_type(pugi::node_element); }
@ -86,7 +88,58 @@ namespace
/**
* @details
*/
XLProperties::XLProperties(XLXmlData* xmlData) : XLXmlFile(xmlData) {}
void XLProperties::createFromTemplate()
{
// std::cout << "XLProperties created with empty docProps/core.xml, creating from scratch!" << std::endl;
if( m_xmlData == nullptr )
throw XLInternalError("XLProperties m_xmlData is nullptr");
// NOTE: there is no functionality in pugixml to include the standalone="yes" attribute in the <xml> element node
// ===== OpenXLSX_XLRelationships::GetStringFromType yields almost the string needed here, with added /relationships
// TBD: use hardcoded string?
std::string xmlns = OpenXLSX_XLRelationships::GetStringFromType(XLRelationshipType::CoreProperties);
const std::string rels = "/relationships/";
size_t pos = xmlns.find(rels);
if (pos != std::string::npos)
xmlns.replace(pos, rels.size(), "/");
else
xmlns = "http://schemas.openxmlformats.org/package/2006/metadata/core-properties"; // fallback to hardcoded string
XMLNode props = xmlDocument().prepend_child("cp:coreProperties");
props.append_attribute("xmlns:cp") = xmlns.c_str();
props.append_attribute("xmlns:dc") = "http://purl.org/dc/elements/1.1/";
props.append_attribute("xmlns:dcterms") = "http://purl.org/dc/terms/";
props.append_attribute("xmlns:dcmitype") = "http://purl.org/dc/dcmitype/";
props.append_attribute("xmlns:xsi") = "http://www.w3.org/2001/XMLSchema-instance";
props.append_child("dc:creator").text().set("Kenneth Balslev");
props.append_child("cp:lastModifiedBy").text().set("Kenneth Balslev");
XMLNode prop {};
prop = props.append_child("dcterms:created");
prop.append_attribute("xsi:type") = "dcterms:W3CDTF";
prop.text().set("2019-08-16T00:34:14Z");
prop = props.append_child("dcterms:modified");
prop.append_attribute("xsi:type") = "dcterms:W3CDTF";
prop.text().set("2019-08-16T00:34:26Z");
}
/**
* @details
*/
XLProperties::XLProperties(XLXmlData* xmlData) : XLXmlFile(xmlData)
{
XMLNode doc = xmlData->getXmlDocument()->document_element();
XMLNode child = doc.first_child_of_type(pugi::node_element);
size_t childCount = 0;
while (not child.empty()) {
++childCount;
child = child.next_sibling_of_type(pugi::node_element);
break; // one child is enough to determine document is not empty.
}
if( !childCount ) createFromTemplate();
}
/**
* @details
@ -98,11 +151,10 @@ XLProperties::~XLProperties() = default;
*/
void XLProperties::setProperty(const std::string& name, const std::string& value)
{
if (!m_xmlData) return;
if (m_xmlData == nullptr) return;
XMLNode node = xmlDocument().document_element().child(name.c_str());
if (node.empty())
node = xmlDocument().document_element().append_child(
name.c_str()); // changed this to append, to be in line with ::property behavior, TBD whether this should stay prepend
node = xmlDocument().document_element().append_child(name.c_str()); // .append_child, to be in line with ::property behavior
node.text().set(value.c_str());
}
@ -122,7 +174,7 @@ void XLProperties::setProperty(const std::string& name, double value) { setPrope
*/
std::string XLProperties::property(const std::string& name) const
{
if (!m_xmlData) return "";
if (m_xmlData == nullptr) return "";
XMLNode property = xmlDocument().document_element().child(name.c_str());
if (property.empty()) property = xmlDocument().document_element().append_child(name.c_str());
@ -134,11 +186,96 @@ std::string XLProperties::property(const std::string& name) const
*/
void XLProperties::deleteProperty(const std::string& name)
{
if (!m_xmlData) return;
if (m_xmlData == nullptr) return;
if (const XMLNode property = xmlDocument().document_element().child(name.c_str()); not property.empty())
xmlDocument().document_element().remove_child(property);
}
/**
* @details
*/
void XLAppProperties::createFromTemplate(XMLDocument const & workbookXml)
{
// std::cout << "XLAppProperties created with empty docProps/app.xml, creating from scratch!" << std::endl;
if( m_xmlData == nullptr )
throw XLInternalError("XLAppProperties m_xmlData is nullptr");
std::map< uint32_t, std::string > sheetsOrderedById;
auto sheet = workbookXml.document_element().child("sheets").first_child_of_type(pugi::node_element);
while (not sheet.empty()) {
std::string sheetName = sheet.attribute("name").as_string();
uint32_t sheetId = sheet.attribute("sheetId").as_uint();
sheetsOrderedById.insert(std::pair<uint32_t, std::string>(sheetId, sheetName));
sheet = sheet.next_sibling_of_type();
}
uint32_t worksheetCount = 0;
for (const auto & [key, value] : sheetsOrderedById) {
if (key != ++worksheetCount)
throw XLInputError( "xl/workbook.xml is missing sheet with sheetId=\"" + std::to_string(worksheetCount) + "\"" );
}
// NOTE: there is no functionality in pugixml to include the standalone="yes" attribute in the <xml> element node
// ===== OpenXLSX_XLRelationships::GetStringFromType yields almost the string needed here, with added /relationships
// TBD: use hardcoded string?
std::string xmlns = OpenXLSX_XLRelationships::GetStringFromType(XLRelationshipType::ExtendedProperties);
const std::string rels = "/relationships/";
size_t pos = xmlns.find(rels);
if (pos != std::string::npos)
xmlns.replace(pos, rels.size(), "/");
else
xmlns = "http://schemas.openxmlformats.org/officeDocument/2006/extended-properties"; // fallback to hardcoded string
XMLNode props = xmlDocument().prepend_child("Properties");
props.append_attribute("xmlns") = xmlns.c_str();
props.append_attribute("xmlns:vt") = "http://schemas.openxmlformats.org/officeDocument/2006/docPropsVTypes";
XMLNode prop {};
props.append_child("Application").text().set("Microsoft Macintosh Excel");
props.append_child("DocSecurity").text().set(0);
props.append_child("ScaleCrop").text().set(false);
XMLNode headingPairs = props.append_child("HeadingPairs");
XMLNode vecHP = headingPairs.append_child("vt:vector");
vecHP.append_attribute("size") = 2;
vecHP.append_attribute("baseType") = "variant";
vecHP.append_child("vt:variant").append_child("vt:lpstr").text().set("Worksheets");
vecHP.append_child("vt:variant").append_child("vt:i4").text().set(1); // TBD: should this be count of worksheets?
XMLNode sheetsVector = headingPairs.append_child("TitlesOfParts").append_child("vt:vector");
sheetsVector.append_attribute("size") = worksheetCount;
sheetsVector.append_attribute("baseType") = "lpstr";
for (const auto & [key, value] : sheetsOrderedById)
sheetsVector.append_child("vt:lpstr").text().set(value.c_str());
props.append_child("Company").text().set("");
props.append_child("LinksUpToDate").text().set(false);
props.append_child("SharedDoc").text().set(false);
props.append_child("HyperlinksChanged").text().set(false);
props.append_child("AppVersion").text().set("16.0300");
}
/**
* @details
*/
XLAppProperties::XLAppProperties(XLXmlData* xmlData, XMLDocument const & workbookXml)
: XLXmlFile(xmlData)
{
XMLNode doc = xmlData->getXmlDocument()->document_element();
XMLNode child = doc.first_child_of_type(pugi::node_element);
size_t childCount = 0;
while (not child.empty()) {
++childCount;
child = child.next_sibling_of_type(pugi::node_element);
break; // one child is enough to determine document is not empty.
}
if (!childCount) createFromTemplate(workbookXml); // create fresh docProps/app.xml
}
/**
* @details
*/
@ -154,8 +291,8 @@ XLAppProperties::~XLAppProperties() = default;
*/
void XLAppProperties::addSheetName(const std::string& title)
{
if (!m_xmlData) return;
const XMLNode theNode = sheetNames(xmlDocument().document_element()).append_child("vt:lpstr");
if (m_xmlData == nullptr) return;
XMLNode theNode = sheetNames(xmlDocument().document_element()).append_child("vt:lpstr");
theNode.text().set(title.c_str());
sheetCount(xmlDocument().document_element()).set_value(sheetCount(xmlDocument().document_element()).as_uint() + 1);
}
@ -165,15 +302,15 @@ void XLAppProperties::addSheetName(const std::string& title)
*/
void XLAppProperties::deleteSheetName(const std::string& title)
{
if (!m_xmlData) return;
XMLNode iter = sheetNames(xmlDocument().document_element()).first_child_of_type(pugi::node_element);
while (iter) {
if (iter.child_value() == title) {
sheetNames(xmlDocument().document_element()).remove_child(iter);
if (m_xmlData == nullptr) return;
XMLNode theNode = sheetNames(xmlDocument().document_element()).first_child_of_type(pugi::node_element);
while (not theNode.empty()) {
if (theNode.child_value() == title) {
sheetNames(xmlDocument().document_element()).remove_child(theNode);
sheetCount(xmlDocument().document_element()).set_value(sheetCount(xmlDocument().document_element()).as_uint() - 1);
return;
}
iter = iter.next_sibling_of_type(pugi::node_element);
theNode = theNode.next_sibling_of_type(pugi::node_element);
}
}
@ -182,14 +319,14 @@ void XLAppProperties::deleteSheetName(const std::string& title)
*/
void XLAppProperties::setSheetName(const std::string& oldTitle, const std::string& newTitle)
{
if (!m_xmlData) return;
XMLNode iter = sheetNames(xmlDocument().document_element()).first_child_of_type(pugi::node_element);
while (iter) {
if (iter.child_value() == oldTitle) {
iter.text().set(newTitle.c_str());
if (m_xmlData == nullptr) return;
XMLNode theNode = sheetNames(xmlDocument().document_element()).first_child_of_type(pugi::node_element);
while (not theNode.empty()) {
if (theNode.child_value() == oldTitle) {
theNode.text().set(newTitle.c_str());
return;
}
iter = iter.next_sibling_of_type(pugi::node_element);
theNode = theNode.next_sibling_of_type(pugi::node_element);
}
}
@ -198,31 +335,31 @@ void XLAppProperties::setSheetName(const std::string& oldTitle, const std::strin
*/
void XLAppProperties::addHeadingPair(const std::string& name, int value)
{
if (!m_xmlData) return;
if (m_xmlData == nullptr) return;
XMLNode HeadingPairsNode = headingPairsNode(xmlDocument().document_element());
XMLNode item = HeadingPairsNode.first_child_of_type(pugi::node_element);
while (!item.empty() && item.first_child_of_type(pugi::node_element).child_value() != name)
while (not item.empty() && item.first_child_of_type(pugi::node_element).child_value() != name)
item = item.next_sibling_of_type(pugi::node_element)
.next_sibling_of_type(pugi::node_element); // advance two elements to skip count node
XMLNode pairCategory = item; // could be an empty node
XMLNode pairCountValue {}; // initialize to empty node
if (!pairCategory.empty())
if (not pairCategory.empty())
pairCountValue = pairCategory.next_sibling_of_type(pugi::node_element).first_child_of_type(pugi::node_element);
else {
item = HeadingPairsNode.last_child_of_type(pugi::node_element);
if (item)
if (not item.empty())
pairCategory = HeadingPairsNode.insert_child_after("vt:variant", item);
else
pairCategory = HeadingPairsNode.append_child("vt:variant");
const XMLNode categoryName = pairCategory.append_child("vt:lpstr");
XMLNode categoryName = pairCategory.append_child("vt:lpstr");
categoryName.text().set(name.c_str());
XMLNode pairCount = HeadingPairsNode.insert_child_after("vt:variant", pairCategory);
pairCountValue = pairCount.append_child("vt:i4");
}
if (pairCountValue)
if (not pairCountValue.empty())
pairCountValue.text().set(std::to_string(value).c_str());
else {
using namespace std::literals::string_literals;
@ -236,25 +373,30 @@ void XLAppProperties::addHeadingPair(const std::string& name, int value)
*/
void XLAppProperties::deleteHeadingPair(const std::string& name)
{
if (!m_xmlData) return;
if (m_xmlData == nullptr) return;
XMLNode HeadingPairsNode = headingPairsNode(xmlDocument().document_element());
XMLNode item = HeadingPairsNode.first_child_of_type(pugi::node_element);
while (item && item.first_child_of_type(pugi::node_element).child_value() != name)
while (not item.empty() && item.first_child_of_type(pugi::node_element).child_value() != name)
item = item.next_sibling_of_type(pugi::node_element)
.next_sibling_of_type(pugi::node_element); // advance two elements to skip count node
// ===== If item with name was found, remove pair and update headingPairsSize
if (!item.empty()) {
if (const XMLNode count = item.next_sibling_of_type(pugi::node_element)) {
if (!count.empty()) { // 2024-05-02: TBD that change to loop deleting whitespaces following a count node is bug-free
while (item.next_sibling() != count)
HeadingPairsNode.remove_child(item.next_sibling()); // remove all nodes between element nodes to be deleted jointly
HeadingPairsNode.remove_child(count);
}
if (not item.empty()) {
const XMLNode count = item.next_sibling_of_type(pugi::node_element);
// ===== 2024-05-28: delete all (non-element) nodes between item and count node, *then* delete non-element nodes following a count node
if (not count.empty()) {
while (item.next_sibling() != count)
HeadingPairsNode.remove_child(item.next_sibling()); // remove nodes between item & count nodes to be deleted jointly
// ===== Delete all non-element nodes following the count node
while ((not count.next_sibling().empty()) && (count.next_sibling().type() != pugi::node_element))
HeadingPairsNode.remove_child(count.next_sibling()); // remove all non-element nodes following a count node
HeadingPairsNode.remove_child(count);
}
// ===== Apply some pretty formatting by removing all whitespace nodes before pair name.
while (item.previous_sibling().type() == pugi::node_pcdata) HeadingPairsNode.remove_child(item.previous_sibling());
// REMOVED: formatting doesn't get prettier by removing whitespaces on both sides of a pair
// while (item.previous_sibling().type() == pugi::node_pcdata) HeadingPairsNode.remove_child(item.previous_sibling());
HeadingPairsNode.remove_child(item);
headingPairsSize(xmlDocument().document_element()).set_value(HeadingPairsNode.child_count_of_type());
@ -266,18 +408,18 @@ void XLAppProperties::deleteHeadingPair(const std::string& name)
*/
void XLAppProperties::setHeadingPair(const std::string& name, int newValue)
{
if (!m_xmlData) return;
if (m_xmlData == nullptr) return;
const XMLNode HeadingPairsNode = headingPairsNode(xmlDocument().document_element());
XMLNode item = HeadingPairsNode.first_child_of_type(pugi::node_element);
while (item && item.first_child_of_type(pugi::node_element).child_value() != name)
while (not item.empty() && item.first_child_of_type(pugi::node_element).child_value() != name)
item = item.next_sibling_of_type(pugi::node_element)
.next_sibling_of_type(pugi::node_element); // advance two elements to skip count node
if (item) {
const XMLNode pairCountValue = item.next_sibling_of_type(pugi::node_element).first_child_of_type(pugi::node_element); //
if (not item.empty()) {
XMLNode pairCountValue = item.next_sibling_of_type(pugi::node_element).first_child_of_type(pugi::node_element);
using namespace std::literals::string_literals;
if (pairCountValue && (pairCountValue.name() == "vt:i4"s))
if (not pairCountValue.empty() && (pairCountValue.name() == "vt:i4"s))
pairCountValue.text().set(std::to_string(newValue).c_str());
else
throw XLInternalError("XLAppProperties::setHeadingPair: found no matching pair count value to name "s + name);
@ -289,9 +431,9 @@ void XLAppProperties::setHeadingPair(const std::string& name, int newValue)
*/
void XLAppProperties::setProperty(const std::string& name, const std::string& value)
{
if (!m_xmlData) return;
const auto property = xmlDocument().document_element().child(name.c_str());
if (!property) xmlDocument().document_element().append_child(name.c_str());
if (m_xmlData == nullptr) return;
auto property = xmlDocument().document_element().child(name.c_str());
if (property.empty()) xmlDocument().document_element().append_child(name.c_str());
property.text().set(value.c_str());
}
@ -300,11 +442,15 @@ void XLAppProperties::setProperty(const std::string& name, const std::string& va
*/
std::string XLAppProperties::property(const std::string& name) const
{
if (!m_xmlData) return "";
const auto property = xmlDocument().document_element().child(name.c_str());
if (!property) xmlDocument().document_element().append_child(name.c_str());
if (m_xmlData == nullptr) return "";
XMLNode property = xmlDocument().document_element().child(name.c_str());
if (property.empty())
property = xmlDocument().document_element().append_child(name.c_str()); // BUGFIX 2024-05-21: re-assign the newly created node to
// property, so that .text().get() is defined behavior
return property.text().get();
// NOTE 2024-05-21: this was previously defined behavior because XMLNode::text() called from an empty xml_node returns an xml_text node
// constructed on an empty _root pointer, while in turn xml_text::get() returns a PUGIXML_TEXT("") for an empty xml_text node
// However, relying on all this implicit functionality was really ugly ;)
}
/**
@ -312,9 +458,9 @@ std::string XLAppProperties::property(const std::string& name) const
*/
void XLAppProperties::deleteProperty(const std::string& name)
{
if (!m_xmlData) return;
if (m_xmlData == nullptr) return;
const auto property = xmlDocument().document_element().child(name.c_str());
if (!property) return;
if (property.empty()) return;
xmlDocument().document_element().remove_child(property);
}
@ -324,8 +470,8 @@ void XLAppProperties::deleteProperty(const std::string& name)
*/
void XLAppProperties::appendSheetName(const std::string& sheetName)
{
if (!m_xmlData) return;
const auto theNode = sheetNames(xmlDocument().document_element()).append_child("vt:lpstr");
if (m_xmlData == nullptr) return;
auto theNode = sheetNames(xmlDocument().document_element()).append_child("vt:lpstr");
theNode.text().set(sheetName.c_str());
sheetCount(xmlDocument().document_element()).set_value(sheetCount(xmlDocument().document_element()).as_uint() + 1);
}
@ -335,8 +481,8 @@ void XLAppProperties::appendSheetName(const std::string& sheetName)
*/
void XLAppProperties::prependSheetName(const std::string& sheetName)
{
if (!m_xmlData) return;
const auto theNode = sheetNames(xmlDocument().document_element()).prepend_child("vt:lpstr");
if (m_xmlData == nullptr) return;
auto theNode = sheetNames(xmlDocument().document_element()).prepend_child("vt:lpstr");
theNode.text().set(sheetName.c_str());
sheetCount(xmlDocument().document_element()).set_value(sheetCount(xmlDocument().document_element()).as_uint() + 1);
}
@ -346,7 +492,7 @@ void XLAppProperties::prependSheetName(const std::string& sheetName)
*/
void XLAppProperties::insertSheetName(const std::string& sheetName, unsigned int index)
{
if (!m_xmlData) return;
if (m_xmlData == nullptr) return;
if (index <= 1) {
prependSheetName(sheetName);
@ -358,8 +504,8 @@ void XLAppProperties::insertSheetName(const std::string& sheetName, unsigned int
// ===== If at least one sheet node exists, apply some pretty formatting by appending new sheet name between last sheet and trailing
// whitespaces.
const XMLNode lastSheet = sheetNames(xmlDocument().document_element()).last_child_of_type(pugi::node_element);
if (lastSheet) {
const XMLNode theNode = sheetNames(xmlDocument().document_element()).insert_child_after("vt:lpstr", lastSheet);
if (not lastSheet.empty()) {
XMLNode theNode = sheetNames(xmlDocument().document_element()).insert_child_after("vt:lpstr", lastSheet);
theNode.text().set(sheetName.c_str());
// ===== Update sheet count before return statement.
sheetCount(xmlDocument().document_element()).set_value(sheetCount(xmlDocument().document_element()).as_uint() + 1);
@ -371,18 +517,18 @@ void XLAppProperties::insertSheetName(const std::string& sheetName, unsigned int
XMLNode curNode = sheetNames(xmlDocument().document_element()).first_child_of_type(pugi::node_element);
unsigned idx = 1;
while (curNode) {
while (not curNode.empty()) {
if (idx == index) break;
curNode = curNode.next_sibling_of_type(pugi::node_element);
++idx;
}
if (!curNode) {
if (curNode.empty()) {
appendSheetName(sheetName);
return;
}
const XMLNode theNode = sheetNames(xmlDocument().document_element()).insert_child_before("vt:lpstr", curNode);
XMLNode theNode = sheetNames(xmlDocument().document_element()).insert_child_before("vt:lpstr", curNode);
theNode.text().set(sheetName.c_str());
sheetCount(xmlDocument().document_element()).set_value(sheetCount(xmlDocument().document_element()).as_uint() + 1);

View File

@ -44,7 +44,12 @@ YM M9 MM MM MM MM MM d' `MM. MM MM d' `MM.
*/
// ===== External Includes ===== //
#include <cstdint> // uint32_t
#include <memory> // std::make_unique
#include <pugixml.hpp>
#include <stdexcept> // std::invalid_argument
#include <string> // std::stoi, std::literals::string_literals
#include <vector> // std::vector
// ===== OpenXLSX Includes ===== //
#include "XLDocument.hpp"
@ -108,6 +113,10 @@ namespace
return type;
}
} // namespace
namespace OpenXLSX_XLRelationships { // make GetStringFromType accessible throughout the project (for use by XLAppProperties)
using namespace OpenXLSX;
std::string GetStringFromType(XLRelationshipType type)
{
std::string typeString;
@ -159,19 +168,21 @@ namespace
return typeString;
}
} // namespace OpenXLSX_XLRelationships
namespace { // re-open anonymous namespace
uint32_t GetNewRelsID(XMLNode relationshipsNode)
{
using namespace std::literals::string_literals;
// ===== workaround for pugi::xml_node currently not having an iterator for node_element only
XMLNode relationship = relationshipsNode.first_child_of_type(pugi::node_element);
uint32_t newId = 1; // default
while (!relationship.empty()) {
while (not relationship.empty()) {
uint32_t id;
try {
id = std::stoi(std::string(relationship.attribute("Id").value()).substr(3));
}
catch (std::invalid_argument e) { // expected stoi exception
catch (std::invalid_argument const & e) { // expected stoi exception
throw XLInputError("GetNewRelsID could not convert attribute Id to uint32_t ("s + e.what() + ")"s);
}
catch (...) { // catch all other errors during conversion of attribute to uint32_t
@ -181,18 +192,6 @@ namespace
relationship = relationship.next_sibling_of_type(pugi::node_element);
}
return newId;
// ===== if a node_element iterator can be implemented for pugi::xml_node, the below code can be used again
// return static_cast<uint32_t>(stoi(std::string(std::max_element(relationshipsNode.children().begin(),
// relationshipsNode.children().end(),
// [](XMLNode a, XMLNode b) {
// return stoi(std::string(a.attribute("Id").value()).substr(3))
// <
// stoi(std::string(b.attribute("Id").value()).substr(3));
// })
// ->attribute("Id")
// .value())
// .substr(3)) +
// 1);
}
} // namespace
@ -254,14 +253,14 @@ XLRelationshipItem XLRelationships::relationshipByTarget(const std::string& targ
}
/**
* @details Returns a const reference to the internal datastructure (std::map)
* @details Returns a const reference to the internal datastructure (std::vector)
*/
std::vector<XLRelationshipItem> XLRelationships::relationships() const
{
// ===== workaround for pugi::xml_node currently not having an iterator for node_element only
auto result = std::vector<XLRelationshipItem>();
XMLNode item = xmlDocument().document_element().first_child_of_type(pugi::node_element);
while (!item.empty()) {
while (not item.empty()) {
result.emplace_back(XLRelationshipItem(item));
item = item.next_sibling_of_type(pugi::node_element);
}
@ -284,13 +283,13 @@ void XLRelationships::deleteRelationship(const XLRelationshipItem& item) { delet
*/
XLRelationshipItem XLRelationships::addRelationship(XLRelationshipType type, const std::string& target)
{
const std::string typeString = GetStringFromType(type);
const std::string typeString = OpenXLSX_XLRelationships::GetStringFromType(type);
const std::string id = "rId" + std::to_string(GetNewRelsID(xmlDocument().document_element()));
// Create new node in the .rels file
XMLNode node = xmlDocument().document_element().last_child_of_type(
pugi::node_element); // pretty formatting: insert new node before whitespaces following last element node
if (node)
XMLNode node = xmlDocument().document_element().last_child_of_type(pugi::node_element); // pretty formatting: insert new node before
// whitespaces following last element node
if (not node.empty())
node = xmlDocument().document_element().insert_child_after("Relationship", node);
else
node = xmlDocument().document_element().append_child("Relationship");

View File

@ -154,13 +154,13 @@ namespace OpenXLSX
void XLRow::setHeight(float height) // NOLINT
{
// Set the 'ht' attribute for the Cell. If it does not exist, create it.
if (!m_rowNode->attribute("ht"))
if (m_rowNode->attribute("ht").empty())
m_rowNode->append_attribute("ht") = height;
else
m_rowNode->attribute("ht").set_value(height);
// Set the 'customHeight' attribute. If it does not exist, create it.
if (!m_rowNode->attribute("customHeight"))
if (m_rowNode->attribute("customHeight").empty())
m_rowNode->append_attribute("customHeight") = 1;
else
m_rowNode->attribute("customHeight").set_value(1);
@ -184,7 +184,7 @@ namespace OpenXLSX
void XLRow::setDescent(float descent)
{
// Set the 'x14ac:dyDescent' attribute. If it does not exist, create it.
if (!m_rowNode->attribute("x14ac:dyDescent"))
if (m_rowNode->attribute("x14ac:dyDescent").empty())
m_rowNode->append_attribute("x14ac:dyDescent") = descent;
else
m_rowNode->attribute("x14ac:dyDescent") = descent;
@ -205,7 +205,7 @@ namespace OpenXLSX
void XLRow::setHidden(bool state) // NOLINT
{
// Set the 'hidden' attribute. If it does not exist, create it.
if (!m_rowNode->attribute("hidden"))
if (m_rowNode->attribute("hidden").empty())
m_rowNode->append_attribute("hidden") = static_cast<int>(state);
else
m_rowNode->attribute("hidden").set_value(static_cast<int>(state));
@ -275,8 +275,10 @@ namespace OpenXLSX
bool XLRow::isEqual(const XLRow& lhs, const XLRow& rhs)
{
if (lhs.m_rowNode && !rhs.m_rowNode) return false;
if (!lhs.m_rowNode && !rhs.m_rowNode) return true;
// 2024-05-28 BUGFIX: (!lhs.m_rowNode && rhs.m_rowNode) was not evaluated, triggering a segmentation fault on dereferencing
if (static_cast<bool>(lhs.m_rowNode) != static_cast<bool>(rhs.m_rowNode)) return false;
// ===== If execution gets here, row nodes are BOTH valid or BOTH invalid / empty
if (not lhs.m_rowNode) return true; // checking one for being empty is enough to know both are empty
return *lhs.m_rowNode == *rhs.m_rowNode;
}

View File

@ -60,10 +60,13 @@ namespace OpenXLSX
* @details Constructor.
* @pre The given range and location are both valid.
* @post
* @note 2024-05-28: added support for constructing with an empty m_cellNode from an empty rowDataRange which will allow obtaining
* an XLIteratorLocation::End for such a range so that iterations can fail in a controlled manner
*/
XLRowDataIterator::XLRowDataIterator(const XLRowDataRange& rowDataRange, XLIteratorLocation loc)
: m_dataRange(std::make_unique<XLRowDataRange>(rowDataRange)),
m_cellNode(std::make_unique<XMLNode>(getCellNode(*m_dataRange->m_rowNode, m_dataRange->m_firstCol))),
m_cellNode(std::make_unique<XMLNode>(
getCellNode((m_dataRange->size() ? *m_dataRange->m_rowNode : XMLNode {}), m_dataRange->m_firstCol))),
m_currentCell(loc == XLIteratorLocation::End ? XLCell() : XLCell(*m_cellNode, m_dataRange->m_sharedStrings))
{}
@ -129,15 +132,10 @@ namespace OpenXLSX
// ===== m_currentCell is set to an empty XLCell, indicating the end of the range has been reached.
if (cellNumber > m_dataRange->m_lastCol) m_currentCell = XLCell();
// ====== If the cellNode is null (i.e. no more children in the current row node) or the column number of the cell node
// ====== If the cellNode is empty (i.e. no more children in the current row node) or the column number of the cell node
// ====== is higher than the computed column number, then insert the node.
// TODO: When checking for > cellNumber rather than != cellNumber, m_cellNode->empty() fails. Why?
// TODO: Apparently only fails when assigning containers with POD values, rather XLCellValues.
// else if (m_cellNode->empty() || XLCellReference(cellNode.attribute("r").value()).column() > cellNumber) {
// BUG BUGFIX 2024-04-26: check was for m_cellNode->empty(), allowing an invalid test for the attribute r, discovered
// because the modified XLCellReference throws an exception on invalid parameter
// this BUGFIX should explain the TODO above
else if (cellNode.empty() || XLCellReference(cellNode.attribute("r").value()).column() > cellNumber) {
cellNode = m_dataRange->m_rowNode->insert_child_after("c", *m_currentCell.m_cellNode);
cellNode.append_attribute("r").set_value(
@ -190,8 +188,10 @@ namespace OpenXLSX
*/
bool XLRowDataIterator::operator==(const XLRowDataIterator& rhs) const
{
if (m_currentCell && !rhs.m_currentCell) return false;
if (!m_currentCell && !rhs.m_currentCell) return true;
// 2024-05-28 BUGFIX: (!m_currentCell && rhs.m_currentCell) was not evaluated, triggering a segmentation fault on dereferencing
if (static_cast<bool>(m_currentCell) != static_cast<bool>(rhs.m_currentCell)) return false;
// ===== If execution gets here, current cells are BOTH valid or BOTH invalid / empty
if (not m_currentCell) return true; // checking one for being empty is enough to know both are empty
return m_currentCell == rhs.m_currentCell;
}
@ -231,7 +231,7 @@ namespace OpenXLSX
* exception.
*/
XLRowDataRange::XLRowDataRange()
: m_rowNode(),
: m_rowNode(nullptr),
m_firstCol(1), // first col of 1
m_lastCol(0), // and last col of 0 will ensure that size returns 0
m_sharedStrings()
@ -245,7 +245,7 @@ namespace OpenXLSX
* @post
*/
XLRowDataRange::XLRowDataRange(const XLRowDataRange& other)
: m_rowNode(std::make_unique<XMLNode>(*other.m_rowNode)),
: m_rowNode((other.m_rowNode != nullptr) ? std::make_unique<XMLNode>(*other.m_rowNode) : nullptr), // 2024-05-28: support for copy-construction from an empty XLDataRange
m_firstCol(other.m_firstCol),
m_lastCol(other.m_lastCol),
m_sharedStrings(other.m_sharedStrings)
@ -299,8 +299,9 @@ namespace OpenXLSX
* @details Get an iterator to the first cell in the range.
* @pre
* @post
* @note 2024-05-28: enhanced ::begin() to return an end iterator for an empty range
*/
XLRowDataIterator XLRowDataRange::begin() { return XLRowDataIterator { *this, XLIteratorLocation::Begin }; }
XLRowDataIterator XLRowDataRange::begin() { return XLRowDataIterator { *this, (size() > 0 ? XLIteratorLocation::Begin : XLIteratorLocation::End) }; }
/**
* @details Get an iterator to (one past) the last cell in the range.
@ -457,7 +458,7 @@ namespace OpenXLSX
if (numCells > 0) {
XMLNode node = lastElementChild; // avoid unneeded call to first_child_of_type by iterating backwards, vector is random
// access so it doesn't matter
while (!node.empty()) {
while (not node.empty()) {
result[XLCellReference(node.attribute("r").value()).column() - 1] = XLCell(node, m_row->m_sharedStrings).value();
node = node.previous_sibling_of_type(pugi::node_element);
}
@ -486,7 +487,7 @@ namespace OpenXLSX
// ===== Mark cell nodes for deletion
std::vector<XMLNode> toBeDeleted;
XMLNode cellNode = m_rowNode->first_child_of_type(pugi::node_element);
while (!cellNode.empty()) {
while (not cellNode.empty()) {
if (XLCellReference(cellNode.attribute("r").value()).column() <= count) {
toBeDeleted.emplace_back(cellNode);
XMLNode nextNode = cellNode.next_sibling(); // get next "regular" sibling (any type) before advancing cellNode
@ -515,11 +516,14 @@ namespace OpenXLSX
*/
void XLRowDataProxy::prependCellValue(const XLCellValue& value, uint16_t col) // NOLINT // 2024-04-30: whitespace support
{
// XMLNode first_child = m_rowNode->first_child_of_type(pugi::node_element); // pretty formatting by inserting before an existing
// first child XMLNode curNode{}; if (first_child.empty())
// ===== (disabled) Pretty formatting by inserting before an existing first child
// XMLNode first_child = m_rowNode->first_child_of_type(pugi::node_element);
// XMLNode curNode{};
// if (first_child.empty())
// curNode = m_rowNode->prepend_child("c");
// else
// curNode = m_rowNode->insert_child_before("c", first_child);
auto curNode = m_rowNode->prepend_child("c"); // this will correctly insert a new cell directly at the beginning of the row
curNode.append_attribute("r").set_value(XLCellReference(static_cast<uint32_t>(m_row->rowNumber()), col).address().c_str());
XLCell(curNode, m_row->m_sharedStrings).value() = value;

View File

@ -85,7 +85,7 @@ bool XLSharedStrings::stringExists(const std::string& str) const { return getStr
/**
* @details
*/
const char* XLSharedStrings::getString(uint32_t index) const
const char* XLSharedStrings::getString(int32_t index) const
{
if (index >= m_stringCache->size()) { // 2024-04-30: added range check
using namespace std::literals::string_literals;
@ -100,26 +100,32 @@ const char* XLSharedStrings::getString(uint32_t index) const
*/
int32_t XLSharedStrings::appendString(const std::string& str)
{
// size_t stringCacheSize = std::distance(m_stringCache->begin(), m_stringCache->end()); // any reason why .size() would not work?
size_t stringCacheSize = m_stringCache->size(); // 2024-05-31: analogous with already added range check in getString
if (stringCacheSize >= XLMaxSharedStrings) { // 2024-05-31: added range check
using namespace std::literals::string_literals;
throw XLInternalError("XLSharedStrings::"s + __func__ + ": exceeded max strings count "s + std::to_string(XLMaxSharedStrings));
}
auto textNode = xmlDocument().document_element().append_child("si").append_child("t");
if ((!str.empty()) && (str.front() == ' ' || str.back() == ' '))
textNode.append_attribute("xml:space").set_value("preserve"); // pull request #161
textNode.text().set(str.c_str());
m_stringCache->emplace_back(textNode.text().get());
m_stringCache->emplace_back(textNode.text().get()); // index of this element = previous stringCacheSize
return static_cast<int32_t>(std::distance(m_stringCache->begin(), m_stringCache->end()) - 1);
return static_cast<int32_t>(stringCacheSize);
}
/**
* @details Print the underlying XML using pugixml::xml_node::print
*/
void XLSharedStrings::print(std::basic_ostream<char, std::char_traits<char>>& ostr) { xmlDocument().document_element().print(ostr); }
void XLSharedStrings::print(std::basic_ostream<char>& ostr) const { xmlDocument().document_element().print(ostr); }
/**
* @details Clear the string at the given index. This will affect the entire spreadsheet; everywhere the shared string
* is used, it will be erased.
* @note: 2024-05-31 DONE: index now int32_t everywhere, 2 billion shared strings should be plenty
*/
// 2024-04-30 CAUTION: other functions use uint32_t index or even int32_t (getStringIndex) - should be pulled straight!
void XLSharedStrings::clearString(uint64_t index) // 2024-04-30: whitespace support
void XLSharedStrings::clearString(int32_t index) // 2024-04-30: whitespace support
{
if (index >= m_stringCache->size()) // 2024-04-30: added range check
throw XLInternalError(std::string("XLSharedStrings::") + std::string(__func__) + std::string(": index ") + std::to_string(index) +
@ -129,16 +135,20 @@ void XLSharedStrings::clearString(uint64_t index) // 2024-04-30: whitespace s
// auto iter = xmlDocument().document_element().children().begin();
// std::advance(iter, index);
// iter->text().set(""); // 2024-04-30: BUGFIX: this was never going to work, <si> entries can be plenty that need to be cleared,
// including formatting 2024-04-30 CAUTION: performance critical - with whitespace support, the function can no longer know the exact
// iterator position of the shared string to be cleared TBD what to do instead? potential solution: store the XML child position with
// each entry in m_stringCache in a std::deque<struct entry> with struct entry { std::string s; uint64_t xmlChildIndex; };
// including formatting
/* 2024-04-30 CAUTION: performance critical - with whitespace support, the function can no longer know the exact iterator position of
* the shared string to be cleared - TBD what to do instead?
* Potential solution: store the XML child position with each entry in m_stringCache in a std::deque<struct entry>
* with struct entry { std::string s; uint64_t xmlChildIndex; };
*/
XMLNode sharedStringNode = xmlDocument().document_element().first_child_of_type(pugi::node_element);
uint64_t sharedStringPos = 0;
while (sharedStringPos < index && !sharedStringNode.empty()) {
while (sharedStringPos < index && not sharedStringNode.empty()) {
sharedStringNode = sharedStringNode.next_sibling_of_type(pugi::node_element);
++sharedStringPos;
}
if (!sharedStringNode.empty()) { // index was found
if (not sharedStringNode.empty()) { // index was found
sharedStringNode.remove_children(); // clear all data and formatting
sharedStringNode.append_child("t"); // append an empty text node
}

View File

@ -240,7 +240,8 @@ XLSheet::operator XLChartsheet() const { return this->get<XLChartsheet>(); }
/**
* @details Print the underlying XML using pugixml::xml_node::print
*/
void XLSheet::print(std::basic_ostream<char, std::char_traits<char>>& ostr) { xmlDocument().document_element().print(ostr); }
void XLSheet::print(std::basic_ostream<char>& ostr) const { xmlDocument().document_element().print( ostr ); }
// ========== XLWorksheet Member Functions
@ -261,7 +262,7 @@ XLWorksheet::XLWorksheet(XLXmlData* xmlData) : XLSheetBase(xmlData)
// If Column properties are grouped, divide them into properties for individual Columns.
if (xmlDocument().document_element().child("cols").type() != pugi::node_null) {
auto currentNode = xmlDocument().document_element().child("cols").first_child_of_type(pugi::node_element);
while (!currentNode.empty()) {
while (not currentNode.empty()) {
uint16_t min {};
uint16_t max {};
try {
@ -276,7 +277,7 @@ XLWorksheet::XLWorksheet(XLXmlData* xmlData) : XLSheetBase(xmlData)
for (uint16_t i = min; i < max; i++) { // NOLINT
auto newnode = xmlDocument().document_element().child("cols").insert_child_before("col", currentNode);
auto attr = currentNode.first_attribute();
while (!attr.empty()) { // NOLINT
while (not attr.empty()) { // NOLINT
newnode.append_attribute(attr.name()) = attr.value();
attr = attr.next_attribute();
}
@ -338,8 +339,7 @@ bool XLWorksheet::setActive_impl()
*/
XLCellAssignable XLWorksheet::cell(const std::string& ref) const
{
return static_cast<XLCellAssignable>(
cell(XLCellReference(ref))); // TODO TBD if this is defined behavior: XLCellAssignable adds only methods, no member variables
return XLCellAssignable(cell(XLCellReference(ref))); // move-construct XLCellAssignable from temporary XLCell
}
/**
@ -357,50 +357,7 @@ XLCell XLWorksheet::cell(uint32_t rowNumber, uint16_t columnNumber) const
const XMLNode cellNode = getCellNode(rowNode, columnNumber, rowNumber);
return XLCell { cellNode, parentDoc().execQuery(XLQuery(XLQueryType::QuerySharedStrings)).result<XLSharedStrings>() };
/*** BEGIN obsolete section: below code is identical to XLUtilities.hpp::getCellNode ***/
// // ===== Get the last child of rowNode that is of type node_element, if any.
// auto cellNode = XMLNode();
// cellNode = rowNode.last_child_of_type(pugi::node_element);
// auto cellRef = XLCellReference(rowNumber, columnNumber); // only distinction from getCellNode: rowNumber is available directly
// as uint16_t
//
// // ===== If there are no cells in the current row, or the requested cell is beyond the last cell in the row...
// if (cellNode.empty() || (XLCellReference(cellNode.attribute("r").value()).column() < columnNumber)) {
// // ===== append a new node to the end.
// rowNode.append_child("c").append_attribute("r").set_value(cellRef.address().c_str());
// cellNode = rowNode.last_child();
// }
// // ===== If the requested node is closest to the end, start from the end and search backwards...
// else if (XLCellReference(cellNode.attribute("r").value()).column() - columnNumber < columnNumber) {
// while (!cellNode.empty() && (XLCellReference(cellNode.attribute("r").value()).column() > columnNumber)) cellNode =
// cellNode.previous_sibling_of_type(pugi::node_element);
// // ===== If the backwards search failed to locate the requested cell
// if (cellNode.empty() || (XLCellReference(cellNode.attribute("r").value()).column() < columnNumber)) {
// if (cellNode.empty()) // If between row begin and higher column number, only non-element nodes exist
// cellNode = rowNode.prepend_child("c"); // insert a new cell node at row begin. When saving, this will keep whitespace
// formatting towards next cell node
// else
// cellNode = rowNode.insert_child_after("c", cellNode);
// cellNode.append_attribute("r").set_value(cellRef.address().c_str());
// }
// }
// // ===== Otherwise, start from the beginning
// else {
// // ===== At this point, it is guaranteed that there is at least one node_element in the row that is not empty.
// cellNode = rowNode.first_child_of_type(pugi::node_element);
//
// // ===== It has been verified above that the requested columnNumber is <= the column number of the last node_element,
// therefore this loop will halt: while (XLCellReference(cellNode.attribute("r").value()).column() < columnNumber) cellNode =
// cellNode.next_sibling_of_type(pugi::node_element);
// // ===== If the forwards search failed to locate the requested cell
// if (XLCellReference(cellNode.attribute("r").value()).column() > columnNumber) {
// cellNode = rowNode.insert_child_before("c", cellNode);
// cellNode.append_attribute("r").set_value(cellRef.address().c_str());
// }
// }
//
// return XLCell{cellNode, parentDoc().execQuery(XLQuery(XLQueryType::QuerySharedStrings)).result<XLSharedStrings>()};
/*** END obsolete section ***/
/*** removed obsolete section with code identical to XLUtilities.hpp::getCellNode ***/
}
/**
@ -493,20 +450,22 @@ XLColumn XLWorksheet::column(uint16_t columnNumber) const
uint16_t minColumn {};
uint16_t maxColumn {};
if (!columnNode.empty()) {
if (not columnNode.empty()) {
minColumn = columnNode.attribute("min").as_int(); // only look it up once for multiple access
maxColumn = columnNode.attribute("max").as_int(); // "
}
// ===== If the node exists for the column, and only spans that column, then continue...
if (!columnNode.empty() && (minColumn == columnNumber) && (maxColumn == columnNumber)) {
if (not columnNode.empty() && (minColumn == columnNumber) && (maxColumn == columnNumber)) {
}
// ===== If the node exists for the column, but spans several columns, split it into individual nodes, and set columnNode to the right
// one... BUGFIX 2024-04-27 - old if condition would split a multi-column setting even if columnNumber is < minColumn (see lambda return
// value above) NOTE 2024-04-27: the column splitting for loaded files is already handled in the constructor, technically this code is
// not necessary here
else if (!columnNode.empty() && (columnNumber >= minColumn) && (minColumn != maxColumn)) {
// one...
// BUGFIX 2024-04-27 - old if condition would split a multi-column setting even if columnNumber is < minColumn (see lambda return value
// above)
// NOTE 2024-04-27: the column splitting for loaded files is already handled in the constructor, technically this code is not necessary
// here
else if (not columnNode.empty() && (columnNumber >= minColumn) && (minColumn != maxColumn)) {
// ===== Split the node in individual columns...
columnNode.attribute("min").set_value(maxColumn); // Limit the original node to a single column
for (int i = minColumn; i < maxColumn; ++i) {
@ -518,7 +477,8 @@ XLColumn XLWorksheet::column(uint16_t columnNumber) const
// min = max attribute
// // ===== Delete the original node
// columnNode = columnNode.previous_sibling_of_type(pugi::node_element); // due to insert loop, previous node should be guaranteed
// to be an element node xmlDocument().document_element().child("cols").remove_child(columnNode.next_sibling());
// // to be an element node
// xmlDocument().document_element().child("cols").remove_child(columnNode.next_sibling());
// ===== Find the node corresponding to the column number - BUGFIX 2024-04-27: loop should abort on empty node
while (not columnNode.empty() && columnNode.attribute("min").as_int() != columnNumber)
@ -528,8 +488,8 @@ XLColumn XLWorksheet::column(uint16_t columnNumber) const
"not found after splitting column nodes"s);
}
// ===== If a node for the column does NOT exist, but a node for a higher column exist...
else if (!columnNode.empty() && minColumn > columnNumber) {
// ===== If a node for the column does NOT exist, but a node for a higher column exists...
else if (not columnNode.empty() && minColumn > columnNumber) {
columnNode = xmlDocument().document_element().child("cols").insert_child_before("col", columnNode);
columnNode.append_attribute("min") = columnNumber;
columnNode.append_attribute("max") = columnNumber;
@ -546,10 +506,11 @@ XLColumn XLWorksheet::column(uint16_t columnNumber) const
columnNode.append_attribute("customWidth") = 0;
}
using namespace std::literals::string_literals;
if (columnNode.empty())
if (columnNode.empty()) {
using namespace std::literals::string_literals;
throw XLInternalError("XLWorksheet::"s + __func__ + ": was unable to find or create node for column "s +
std::to_string(columnNumber));
}
return XLColumn(columnNode);
}
@ -569,12 +530,6 @@ uint16_t XLWorksheet::columnCount() const noexcept
maxCount = std::max(cellCount, maxCount);
}
return maxCount;
// std::vector<uint16_t> counts; // Pull request: Update XLSheet.cpp with correct type #176, Explicitely cast to unsigned short int #163
// for (const auto& row : rows()) {
// counts.emplace_back(row.cellCount());
// }
// return std::max(static_cast<uint16_t>(1), *std::max_element(counts.begin(), counts.end()));
}
/**
@ -607,7 +562,8 @@ void XLWorksheet::updateSheetName(const std::string& oldName, const std::string&
// ===== Iterate through all defined names
XMLNode row = xmlDocument().document_element().child("sheetData").first_child_of_type(pugi::node_element);
for (; not row.empty(); row = row.next_sibling_of_type(pugi::node_element)) {
for (XMLNode cell = row.first_child_of_type(pugi::node_element); not cell.empty();
for (XMLNode cell = row.first_child_of_type(pugi::node_element);
not cell.empty();
cell = cell.next_sibling_of_type(pugi::node_element))
{
if (!XLCell(cell, XLSharedStrings()).hasFormula()) continue;

View File

@ -115,7 +115,8 @@ XLSheet XLWorkbook::sheet(uint16_t index) // 2024-04-30: whitespace support
// ===== Find the n-th node_element that corresponds to index
uint16_t curIndex = 0;
for (XMLNode node = sheetsNode(xmlDocument()).first_child_of_type(pugi::node_element); node.empty() == false;
for (XMLNode node = sheetsNode(xmlDocument()).first_child_of_type(pugi::node_element);
not node.empty();
node = node.next_sibling_of_type(pugi::node_element))
{
if (++curIndex == index) return sheet(node.attribute("name").as_string());
@ -178,8 +179,8 @@ void XLWorkbook::deleteNamedRanges()
/**
* @details
*/
void XLWorkbook::deleteSheet(const std::string& sheetName) // 2024-05-02: whitespace support - CAUTION: execCommand on underlying XML
// with whitespaces not verified
void XLWorkbook::deleteSheet(const std::string& sheetName) // 2024-05-02: whitespace support
// CAUTION: execCommand on underlying XML with whitespaces not verified
{
// ===== Determine ID and type of sheet, as well as current worksheet count.
auto sheetID = sheetsNode(xmlDocument()).find_child_by_attribute("name", sheetName.c_str()).attribute("r:id").value(); // NOLINT
@ -205,15 +206,14 @@ void XLWorkbook::deleteSheet(const std::string& sheetName) // 2024-05-02: whi
parentDoc().execCommand(
XLCommand(XLCommandType::DeleteSheet).setParam("sheetID", std::string(sheetID)).setParam("sheetName", sheetName));
XMLNode sheet = sheetsNode(xmlDocument()).find_child_by_attribute("name", sheetName.c_str());
if (!sheet.empty()) {
// ===== Delete all non element nodes (comments, whitespaces) following the sheet that is being deleted from workbook.xml <sheets>
// node
if (not sheet.empty()) {
// ===== Delete all non element nodes (comments, whitespaces) following the sheet being deleted from workbook.xml <sheets> node
XMLNode nonElementNode = sheet.next_sibling();
while (!nonElementNode.empty() && nonElementNode.type() != pugi::node_element) {
while (not nonElementNode.empty() && nonElementNode.type() != pugi::node_element) {
sheetsNode(xmlDocument()).remove_child(nonElementNode);
nonElementNode = nonElementNode.next_sibling();
}
sheetsNode(xmlDocument()).remove_child(sheet); // delete the actual sheet entry in
sheetsNode(xmlDocument()).remove_child(sheet); // delete the actual sheet entry
}
if (sheetIsActive(sheetID))
@ -242,6 +242,8 @@ void XLWorkbook::addWorksheet(const std::string& sheetName)
/**
* @details
* @todo If the original sheet's tabSelected attribute is set, ensure it is un-set in the clone.
* TBD: See comment in XLWorkbook::setSheetActive - should the tabSelected actually be un-set? It's not the same as the active tab,
* which does not need to be selected
*/
void XLWorkbook::cloneSheet(const std::string& existingName, const std::string& newName)
{
@ -255,7 +257,7 @@ uint16_t XLWorkbook::createInternalSheetID() // 2024-04-30: whitespace suppor
{
XMLNode sheet = xmlDocument().document_element().child("sheets").first_child_of_type(pugi::node_element);
uint32_t maxSheetIdFound = 0;
while (!sheet.empty()) {
while (not sheet.empty()) {
uint32_t thisSheetId = sheet.attribute("sheetId").as_uint();
if (thisSheetId > maxSheetIdFound) maxSheetIdFound = thisSheetId;
sheet = sheet.next_sibling_of_type(pugi::node_element);
@ -325,7 +327,7 @@ void XLWorkbook::setSheetVisibility(const std::string& sheetRID, const std::stri
int visibleSheets = 0;
// for (const auto& item : xmlDocument().document_element().child("sheets").children()) {
XMLNode item = xmlDocument().document_element().child("sheets").first_child_of_type(pugi::node_element);
while (!item.empty()) {
while (not item.empty()) {
if (std::string(item.attribute("r:id").value()) != sheetRID) {
if (isVisible(item)) ++visibleSheets;
}
@ -364,7 +366,7 @@ void XLWorkbook::setSheetVisibility(const std::string& sheetRID, const std::stri
if (hideSheet && activeTabIndex == index) { // BUGFIX 2024-04-30: previously, the active tab was re-set even if the current sheet was
// being set to "visible" (when already being visible)
XMLNode item = xmlDocument().document_element().child("sheets").first_child_of_type(pugi::node_element);
while (!item.empty()) {
while (not item.empty()) {
if (isVisible(item))
{ // BUGFIX 2024-05-01: old check was testing state != "hidden" || != "veryHidden", which was always true
activeTabAttribute.set_value(indexOfSheet(item.attribute("name").value()) - 1);
@ -377,7 +379,7 @@ void XLWorkbook::setSheetVisibility(const std::string& sheetRID, const std::stri
/**
* @details
* SOLVED: @todo In some cases (eg. if a sheet is moved to the position before the selected sheet), multiple sheets are selected when opened
* @done In some cases (eg. if a sheet is moved to the position before the selected sheet), multiple sheets are selected when opened
* in Excel.
*/
void XLWorkbook::setSheetIndex(const std::string& sheetName, unsigned int index) // 2024-05-01: whitespace support
@ -391,13 +393,13 @@ void XLWorkbook::setSheetIndex(const std::string& sheetName, unsigned int index)
XMLNode sheetToMove {}; // determine the sheet matching sheetName, if any
unsigned int sheetToMoveIndex = 0;
XMLNode existingSheet {}; // determine the sheet at index, if any
std::string activeSheet_rId {}; // determine the r:id of the sheet at activeIndex, if any
std::string activeSheet_rId {}; // determine the r:id of the sheet at activeIndex, if any
unsigned int sheetIndex = 1;
XMLNode curSheet = sheetsNode(xmlDocument()).first_child_of_type(pugi::node_element);
int thingsToFind = (activeSheetIndex > 0) ? 3 : 2; // if there is no active tab configured, no need to search for its name
while (!curSheet.empty() && thingsToFind > 0) { // permit early loop exit when all sheets are located
while (not curSheet.empty() && thingsToFind > 0) { // permit early loop exit when all sheets are located
if (sheetToMove.empty() && (curSheet.attribute("name").value() == sheetName)) {
sheetToMoveIndex = sheetIndex;
sheetToMove = curSheet;
@ -435,7 +437,7 @@ void XLWorkbook::setSheetIndex(const std::string& sheetName, unsigned int index)
// ===== Updated defined names with worksheet scopes. TBD what this does
XMLNode definedName = xmlDocument().document_element().child("definedNames").first_child_of_type(pugi::node_element);
while (!definedName.empty()) {
while (not definedName.empty()) {
// TBD: is the current definedName actually associated with the sheet that was moved?
definedName.attribute("localSheetId").set_value(sheetToMoveIndex - 1);
definedName = definedName.next_sibling_of_type(pugi::node_element);
@ -443,9 +445,8 @@ void XLWorkbook::setSheetIndex(const std::string& sheetName, unsigned int index)
// ===== Update the activeTab attribute.
if ((activeSheetIndex < std::min(index, sheetToMoveIndex)) ||
(activeSheetIndex >
std::max(index, sheetToMoveIndex))) // if the active sheet was not within the set of sheets affected by the move
return; // nothing to do
(activeSheetIndex > std::max(index, sheetToMoveIndex))) // if active sheet was not within the set of sheets affected by the move
return; // nothing to do
if (activeSheet_rId.length() > 0) setSheetActive(activeSheet_rId);
}
@ -457,7 +458,8 @@ unsigned int XLWorkbook::indexOfSheet(const std::string& sheetName) const //
{
// ===== Iterate through sheet nodes. When a match is found, return the index;
unsigned int index = 1;
for (XMLNode sheet = sheetsNode(xmlDocument()).first_child_of_type(pugi::node_element); sheet.empty() == false;
for (XMLNode sheet = sheetsNode(xmlDocument()).first_child_of_type(pugi::node_element);
not sheet.empty();
sheet = sheet.next_sibling_of_type(pugi::node_element))
{
if (sheetName == sheet.attribute("name").value()) return index;
@ -485,7 +487,8 @@ XLSheetType XLWorkbook::typeOfSheet(const std::string& sheetName) const
XLSheetType XLWorkbook::typeOfSheet(unsigned int index) const // 2024-05-01: whitespace support
{
unsigned int thisIndex = 1;
for (XMLNode sheet = sheetsNode(xmlDocument()).first_child_of_type(pugi::node_element); not sheet.empty();
for (XMLNode sheet = sheetsNode(xmlDocument()).first_child_of_type(pugi::node_element);
not sheet.empty();
sheet = sheet.next_sibling_of_type(pugi::node_element))
{
if (thisIndex == index) return typeOfSheet(sheet.attribute("name").as_string());
@ -502,8 +505,8 @@ XLSheetType XLWorkbook::typeOfSheet(unsigned int index) const // 2024-05-01:
unsigned int XLWorkbook::sheetCount() const // 2024-04-30: whitespace support
{
unsigned int count = 0;
// 2024-04-30: TBD performance issue due to whitespace support
for (XMLNode node = sheetsNode(xmlDocument()).first_child_of_type(pugi::node_element); not node.empty();
for (XMLNode node = sheetsNode(xmlDocument()).first_child_of_type(pugi::node_element);
not node.empty();
node = node.next_sibling_of_type(pugi::node_element))
++count;
return count;
@ -526,7 +529,8 @@ std::vector<std::string> XLWorkbook::sheetNames() const // 2024-05-01: whites
{
std::vector<std::string> results;
for (XMLNode item = sheetsNode(xmlDocument()).first_child_of_type(pugi::node_element); not item.empty();
for (XMLNode item = sheetsNode(xmlDocument()).first_child_of_type(pugi::node_element);
not item.empty();
item = item.next_sibling_of_type(pugi::node_element))
results.emplace_back(item.attribute("name").value());
@ -540,7 +544,8 @@ std::vector<std::string> XLWorkbook::worksheetNames() const // 2024-05-01: wh
{
std::vector<std::string> results;
for (XMLNode item = sheetsNode(xmlDocument()).first_child_of_type(pugi::node_element); not item.empty();
for (XMLNode item = sheetsNode(xmlDocument()).first_child_of_type(pugi::node_element);
not item.empty();
item = item.next_sibling_of_type(pugi::node_element))
{
XLQuery query(XLQueryType::QuerySheetType);
@ -559,7 +564,8 @@ std::vector<std::string> XLWorkbook::chartsheetNames() const // 2024-05-01: w
{
std::vector<std::string> results;
for (XMLNode item = sheetsNode(xmlDocument()).first_child_of_type(pugi::node_element); not item.empty();
for (XMLNode item = sheetsNode(xmlDocument()).first_child_of_type(pugi::node_element);
not item.empty();
item = item.next_sibling_of_type(pugi::node_element))
{
XLQuery query(XLQueryType::QuerySheetType);
@ -624,7 +630,7 @@ void XLWorkbook::updateSheetReferences(
// ===== Iterate through all defined names // TODO 2024-05-01: verify definedNames logic
XMLNode definedName = xmlDocument().document_element().child("definedNames").first_child_of_type(pugi::node_element);
for (; definedName.empty() == false; definedName = definedName.next_sibling_of_type(pugi::node_element)) {
for (; not definedName.empty(); definedName = definedName.next_sibling_of_type(pugi::node_element)) {
formula = definedName.text().get();
// ===== Skip if formula contains a '[' and ']' (means that the defined refers to external workbook)
@ -643,11 +649,11 @@ void XLWorkbook::updateSheetReferences(
*/
void XLWorkbook::setFullCalculationOnLoad()
{
auto calcPr = xmlDocument().document_element().child("calcPr");
XMLNode calcPr = xmlDocument().document_element().child("calcPr");
auto getOrCreateAttribute = [&calcPr](const char* attributeName) {
auto attr = calcPr.attribute(attributeName);
if (!attr) attr = calcPr.append_attribute(attributeName);
XMLAttribute attr = calcPr.attribute(attributeName);
if (attr.empty()) attr = calcPr.append_attribute(attributeName);
return attr;
};
@ -658,75 +664,75 @@ void XLWorkbook::setFullCalculationOnLoad()
/**
* @details
*/
void XLWorkbook::print(std::basic_ostream<char, std::char_traits<char>>& os) { xmlDocument().document_element().print(os); }
void XLWorkbook::print(std::basic_ostream<char>& ostr) const { xmlDocument().document_element().print (ostr); }
/**
* @details
*/
bool XLWorkbook::sheetIsActive(const std::string& sheetRID) const // 2024-04-30: whitespace support
{
const XMLNode workbookView = xmlDocument().document_element().child("bookViews").first_child_of_type(pugi::node_element);
const auto activeTabAttribute = workbookView.attribute("activeTab");
const auto activeTabIndex = (activeTabAttribute ? activeTabAttribute.as_uint() : 0);
const XMLNode workbookView = xmlDocument().document_element().child("bookViews").first_child_of_type(pugi::node_element);
const XMLAttribute activeTabAttribute = workbookView.attribute("activeTab");
const int32_t activeTabIndex = (not activeTabAttribute.empty() ? activeTabAttribute.as_int() : -1); // 2024-05-29 BUGFIX: activeTabAttribute was being read as_uint
if (activeTabIndex == -1) return false; // 2024-05-29 early exit: no need to try and match sheetRID if there *is* no active tab
unsigned int index = 0;
int32_t index = 0; // 2024-06-04 BUGFIX: index should support -1 as 2024-05-29 change below sets it to -1 for preventing a match with activeTabIndex
XMLNode item = sheetsNode(xmlDocument()).first_child_of_type(pugi::node_element);
while (!item.empty()) {
while (not item.empty()) {
if (std::string(item.attribute("r:id").value()) == sheetRID) break;
++index;
item = item.next_sibling_of_type(pugi::node_element);
}
if (item.empty()) index = -1; // 2024-05-29: prevent a match if activeTabIndex invalidly points to a non-existing sheet
return index == activeTabIndex;
}
/**
* @details
* @done: no exception if setSheetActive fails, instead return false
* @done: fail by returning false if sheetRID is either not found or belongs to a sheet that is not visible
* @note: this makes some bug fixes from 2024-05-29 obsolete
* @note: changed behavior: attempting to setSheetActive on a non-existing or non-visible sheet will no longer unselect the active sheet
*/
bool XLWorkbook::setSheetActive(const std::string& sheetRID) // 2024-04-30: whitespace support
{
XMLNode workbookView = xmlDocument().document_element().child("bookViews").first_child_of_type(pugi::node_element);
const auto activeTabAttribute = workbookView.attribute("activeTab");
int32_t activeTabIndex = -1; // negative == no active tab identified
if (!activeTabAttribute.empty()) activeTabIndex = activeTabAttribute.as_int();
XMLNode workbookView = xmlDocument().document_element().child("bookViews").first_child_of_type(pugi::node_element);
const XMLAttribute activeTabAttribute = workbookView.attribute("activeTab");
int32_t activeTabIndex = -1; // negative == no active tab identified
if (not activeTabAttribute.empty()) activeTabIndex = activeTabAttribute.as_int();
unsigned int index = 0;
int32_t index = 0; // index should have the same data type as activeTabIndex for comparisons
XMLNode item = sheetsNode(xmlDocument()).first_child_of_type(pugi::node_element);
while (!item.empty() && (std::string(item.attribute("r:id").value()) != sheetRID)) {
while (not item.empty() && (std::string(item.attribute("r:id").value()) != sheetRID)) {
++index;
item = item.next_sibling_of_type(pugi::node_element);
}
// ===== 2024-06-19: Fail without action if sheet is not found or sheet is not visible
if (item.empty() || !isVisible(item)) return false;
// NOTE: XLSheet XLWorkbook::sheet(uint16_t index) is using a 1-based index, while the workbookView attribute activeTab is using a
// 0-based index
// ===== If an active sheet was found, but sheetRID was not found or is not the same sheet: attempt to unselect the old active sheet.
// ===== This avoids that when opening the XLSX file in an office application, multiple sheets show as selected - TBD with Kenneth if
// this is desired behavior
// TODO: take care of (currently) index 0 when no sheetRID is found
// ===== If an active sheet was found, but sheetRID is not the same sheet: attempt to unselect the old active sheet.
if ((activeTabIndex != -1) && (index != activeTabIndex)) sheet(activeTabIndex + 1).setSelected(false); // see NOTE above
// ===== Attempting to set a hidden sheet active will remove the activeTab property from the workbook.xml sheets node
if (item.empty() ||
!isVisible(item)) // if sheet was not found or is hidden, it can not be set active. 2024-05-01 BUGFIX: veryHidden was not checked
workbookView.remove_attribute("activeTab"); // TODO TBD: throw an exception if item.empty() ?
else { // sheetRID was found and the sheet is visible
if (workbookView.attribute("activeTab").empty()) workbookView.append_attribute("activeTab");
workbookView.attribute("activeTab").set_value(index);
// sheet(index + 1).setSelected(true); // see NOTE above, however it appears that an active sheet does not have to be selected
return true; // success
}
return false; // default: sheet was set to active
// ===== Set the activeTab property for the workbook.xml sheets node
if (workbookView.attribute("activeTab").empty()) workbookView.append_attribute("activeTab");
workbookView.attribute("activeTab").set_value(index);
// sheet(index + 1).setSelected(true); // it appears that an active sheet does not have to be selected
return true; // success
}
/**
* @details evaluate a sheet node state attribute where "hidden" or "veryHidden" means not visible
* @note 2024-05-01 BUGFIX: veryHidden was not checked (in setSheetActive)
*/
bool XLWorkbook::isVisibleState(std::string const& state) const { return (state != "hidden" && state != "veryHidden"); }
/**
* @details function only returns meaningful information when used with a sheet node (or nodes with state attribute allowing values visible,
* hidden, veryHidden)
* @details function only returns meaningful information when used with a sheet node
* (or nodes with state attribute allowing values visible, hidden, veryHidden)
*/
bool XLWorkbook::isVisible(XMLNode const& sheetNode) const
{

View File

@ -45,19 +45,19 @@ YM M9 MM MM MM MM MM d' `MM. MM MM d' `MM.
// ===== External Includes ===== //
#include <pugixml.hpp>
#include <sstream>
// ===== OpenXLSX Includes ===== //
#include "XLDocument.hpp"
#include "XLXmlData.hpp"
#include <sstream>
using namespace OpenXLSX;
const unsigned int pugi_parse_settings = pugi::parse_default | pugi::parse_ws_pcdata; // TBD: | pugi::parse_comments
/**
* @details
*/
XLXmlData::XLXmlData(OpenXLSX::XLDocument* parentDoc, const std::string& xmlPath, const std::string& xmlId, XLContentType xmlType)
XLXmlData::XLXmlData(XLDocument* parentDoc, const std::string& xmlPath, const std::string& xmlId, XLContentType xmlType)
: m_parentDoc(parentDoc),
m_xmlPath(xmlPath),
m_xmlID(xmlId),
@ -77,16 +77,43 @@ XLXmlData::~XLXmlData() = default;
*/
void XLXmlData::setRawData(const std::string& data) // NOLINT
{
m_xmlDoc->load_string(data.c_str(), pugi::parse_default | pugi::parse_ws_pcdata);
m_xmlDoc->load_string(data.c_str(), pugi_parse_settings);
}
/**
* @details
* @note Default encoding for pugixml xml_document::save is pugi::encoding_auto, becomes pugi::encoding_utf8
*/
std::string XLXmlData::getRawData() const
{
XMLDocument *doc = const_cast<XMLDocument *>(getXmlDocument());
// ===== 2024-08-08: ensure that the default encoding UTF-8 is explicitly written to the XML document with a custom saving declaration
XMLNode savingDeclaration = doc->first_child();
if (savingDeclaration.empty() || savingDeclaration.type() != pugi::node_declaration) // if saving declaration node does not exist
savingDeclaration = doc->prepend_child(pugi::node_declaration); // create it
// ===== If a node_declaration could be fetched or created
if (not savingDeclaration.empty()) {
// ===== Fetch or create saving declaration attributes
XMLAttribute attrVersion = savingDeclaration.attribute("version");
if (attrVersion.empty())
attrVersion = savingDeclaration.append_attribute("version");
XMLAttribute attrEncoding = savingDeclaration.attribute("encoding");
if (attrEncoding.empty())
attrEncoding = savingDeclaration.append_attribute("encoding");
XMLAttribute attrStandalone = savingDeclaration.attribute("standalone");
if (attrStandalone.empty())
attrStandalone = savingDeclaration.append_attribute("standalone");
// ===== Set saving declaration attribute values (potentially overwriting existing values)
attrVersion = "1.0"; // version="1.0" is XML default
attrEncoding = "UTF-8"; // encoding="UTF-8" is XML default
attrStandalone = "no"; // standalone="no" is XML default
}
std::ostringstream ostr;
getXmlDocument()->save(ostr, "", pugi::format_raw);
doc->save(ostr, "", pugi::format_raw);
return ostr.str();
}
@ -136,7 +163,7 @@ XLContentType XLXmlData::getXmlType() const
XMLDocument* XLXmlData::getXmlDocument()
{
if (!m_xmlDoc->document_element())
m_xmlDoc->load_string(m_parentDoc->extractXmlFromArchive(m_xmlPath).c_str(), pugi::parse_default | pugi::parse_ws_pcdata);
m_xmlDoc->load_string(m_parentDoc->extractXmlFromArchive(m_xmlPath).c_str(), pugi_parse_settings);
return m_xmlDoc.get();
}
@ -147,7 +174,7 @@ XMLDocument* XLXmlData::getXmlDocument()
const XMLDocument* XLXmlData::getXmlDocument() const
{
if (!m_xmlDoc->document_element())
m_xmlDoc->load_string(m_parentDoc->extractXmlFromArchive(m_xmlPath).c_str(), pugi::parse_default | pugi::parse_ws_pcdata);
m_xmlDoc->load_string(m_parentDoc->extractXmlFromArchive(m_xmlPath).c_str(), pugi_parse_settings);
return m_xmlDoc.get();
}

View File

@ -0,0 +1,171 @@
/*
____ ____ ___ ____ ____ ____ ___
6MMMMb `MM( )M' `MM' 6MMMMb\`MM( )M'
8P Y8 `MM. d' MM 6M' ` `MM. d'
6M Mb __ ____ ____ ___ __ `MM. d' MM MM `MM. d'
MM MM `M6MMMMb 6MMMMb `MM 6MMb `MM. d' MM YM. `MM. d'
MM MM MM' `Mb 6M' `Mb MMM9 `Mb `MMd MM YMMMMb `MMd
MM MM MM MM MM MM MM' MM dMM. MM `Mb dMM.
MM MM MM MM MMMMMMMM MM MM d'`MM. MM MM d'`MM.
YM M9 MM MM MM MM MM d' `MM. MM MM d' `MM.
8b d8 MM. ,M9 YM d9 MM MM d' `MM. MM / L ,M9 d' `MM.
YMMMM9 MMYMMM9 YMMMM9 _MM_ _MM_M(_ _)MM_ _MMMMMMM MYMMMM9 _M(_ _)MM_
MM
MM
_MM_
Copyright (c) 2018, Kenneth Troldal Balslev
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
- Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
- Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
- Neither the name of the author nor the
names of any contributors may be used to endorse or promote products
derived from this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
// ===== External Includes ===== //
#include <pugixml.hpp>
// // ===== OpenXLSX Includes ===== //
#include "XLXmlParser.hpp"
namespace OpenXLSX
{
// ===== Copy definition of PUGI_IMPL_NODETYPE, which is defined in pugixml.cpp, within a namespace, and somehow doesn't work here
# define PUGI_IMPL_NODETYPE(n) static_cast<pugi::xml_node_type>((n)->header & pugi::impl::xml_memory_page_type_mask)
/**
* @details determine the first xml_node child whose xml_node_type matches type_
* @date 2024-04-25
*/
XMLNode XMLNode::first_child_of_type(pugi::xml_node_type type_) const
{
if (_root) {
XMLNode x = first_child();
XMLNode l = last_child();
while (x != l && x.type() != type_) x = x.next_sibling();
if (x.type() == type_)
return XMLNode(x);
}
return XMLNode(); // if no node matching type_ was found: return an empty node
}
/**
* @details determine the last xml_node child whose xml_node_type matches type_
* @date 2024-04-25
*/
XMLNode XMLNode::last_child_of_type(pugi::xml_node_type type_) const
{
if (_root) {
XMLNode f = first_child();
XMLNode x = last_child();
while (x != f && x.type() != type_) x = x.previous_sibling();
if (x.type() == type_)
return XMLNode(x);
}
return XMLNode(); // if no node matching type_ was found: return an empty node
}
/**
* @details determine amount of xml_node children child whose xml_node_type matches type_
* @date 2024-04-28
*/
size_t XMLNode::child_count_of_type(pugi::xml_node_type type_) const
{
size_t counter = 0;
if (_root) {
XMLNode c = first_child_of_type(type_);
while (!c.empty()) {
++counter;
c = c.next_sibling_of_type(type_);
}
}
return counter;
}
/**
* @details determine the next xml_node sibling whose xml_node_type matches type_
* @date 2024-04-26
*/
XMLNode XMLNode::next_sibling_of_type(pugi::xml_node_type type_) const
{
if (_root) {
pugi::xml_node_struct* next = _root->next_sibling;
while (next && (PUGI_IMPL_NODETYPE(next) != type_)) next = next->next_sibling;
if (next)
return XMLNode(next);
}
return XMLNode(); // if no node matching type_ was found: return an empty node
}
/**
* @details determine the previous xml_node sibling whose xml_node_type matches type_
* @date 2024-04-26
*/
XMLNode XMLNode::previous_sibling_of_type(pugi::xml_node_type type_) const
{
if (_root) {
pugi::xml_node_struct* prev = _root->prev_sibling_c;
while (prev->next_sibling && (PUGI_IMPL_NODETYPE(prev) != type_)) prev = prev->prev_sibling_c;
if (prev->next_sibling)
return XMLNode(prev);
}
return XMLNode(); // if no node matching type_ was found: return an empty node
}
/**
* @details determine the next xml_node sibling whose name() matches name_ and xml_node_type matches type_
* @date 2024-04-26
*/
XMLNode XMLNode::next_sibling_of_type(const pugi::char_t* name_, pugi::xml_node_type type_) const
{
if (_root) {
for (pugi::xml_node_struct* i = _root->next_sibling; i; i = i->next_sibling)
{
const pugi::char_t* iname = i->name;
if (iname && pugi::impl::strequal(name_, iname) && (PUGI_IMPL_NODETYPE(i) == type_))
return XMLNode(i);
}
}
return XMLNode(); // if no node matching type_ was found: return an empty node
}
/**
* @details determine the previous xml_node sibling whose name() matches name_ and xml_node_type matches type_
* @date 2024-04-26
*/
XMLNode XMLNode::previous_sibling_of_type(const pugi::char_t* name_, pugi::xml_node_type type_) const
{
if (_root) {
for (pugi::xml_node_struct* i = _root->prev_sibling_c; i->next_sibling; i = i->prev_sibling_c)
{
const pugi::char_t* iname = i->name;
if (iname && pugi::impl::strequal(name_, iname) && (PUGI_IMPL_NODETYPE(i) == type_))
return XMLNode(i);
}
}
return XMLNode(); // if no node matching type_ was found: return an empty node
}
} // namespace OpenXLSX

View File

@ -9,6 +9,7 @@
#include <pugixml.hpp>
#include <string> // 2024-04-25 needed for xml_node_type_string
#include "XLConstants.hpp" // 2024-05-28 OpenXLSX::MAX_ROWS
#include "XLCellReference.hpp"
#include "XLCellValue.hpp" // OpenXLSX::XLValueType
#include "XLContentTypes.hpp" // OpenXLSX::XLContentType
@ -97,6 +98,10 @@ namespace OpenXLSX
*/
inline XMLNode getRowNode(XMLNode sheetDataNode, uint32_t rowNumber)
{
if( rowNumber > OpenXLSX::MAX_ROWS ) { // 2024-05-28: added range check
using namespace std::literals::string_literals;
throw XLCellAddressError( "rowNumber "s + std::to_string( rowNumber ) + " is outside valid range" );
}
// ===== Get the last child of sheetDataNode that is of type node_element.
auto result = XMLNode();
result = sheetDataNode.last_child_of_type(pugi::node_element);
@ -111,7 +116,7 @@ namespace OpenXLSX
// ===== If the requested node is closest to the end, start from the end and search backwards.
else if (result.attribute("r").as_ullong() - rowNumber < rowNumber) {
while (!result.empty() && (result.attribute("r").as_ullong() > rowNumber)) result = result.previous_sibling_of_type(pugi::node_element);
while (not result.empty() && (result.attribute("r").as_ullong() > rowNumber)) result = result.previous_sibling_of_type(pugi::node_element);
// ===== If the backwards search failed to locate the requested row
if (result.empty() || (result.attribute("r").as_ullong() != rowNumber)) {
if (result.empty())
@ -155,6 +160,8 @@ namespace OpenXLSX
inline XMLNode getCellNode(XMLNode rowNode, uint16_t columnNumber, uint32_t rowNumber = 0 )
{
auto cellNode = XMLNode();
if( rowNode.empty() ) return cellNode; // 2024-05-28: return an empty node in case of empty rowNode
cellNode = rowNode.last_child_of_type(pugi::node_element);
if( !rowNumber ) rowNumber = rowNode.attribute("r").as_uint(); // if not provided, determine from rowNode
auto cellRef = XLCellReference(rowNumber, columnNumber);
@ -167,7 +174,8 @@ namespace OpenXLSX
}
// ===== If the requested node is closest to the end, start from the end and search backwards...
else if (XLCellReference(cellNode.attribute("r").value()).column() - columnNumber < columnNumber) {
while (!cellNode.empty() && (XLCellReference(cellNode.attribute("r").value()).column() > columnNumber)) cellNode = cellNode.previous_sibling_of_type(pugi::node_element);
while (not cellNode.empty() && (XLCellReference(cellNode.attribute("r").value()).column() > columnNumber))
cellNode = cellNode.previous_sibling_of_type(pugi::node_element);
// ===== If the backwards search failed to locate the requested cell
if (cellNode.empty() || (XLCellReference(cellNode.attribute("r").value()).column() < columnNumber)) {
if (cellNode.empty()) // If between row begin and higher column number, only non-element nodes exist
@ -183,7 +191,8 @@ namespace OpenXLSX
cellNode = rowNode.first_child_of_type(pugi::node_element);
// ===== It has been verified above that the requested columnNumber is <= the column number of the last node_element, therefore this loop will halt:
while (XLCellReference(cellNode.attribute("r").value()).column() < columnNumber) cellNode = cellNode.next_sibling_of_type(pugi::node_element);
while (XLCellReference(cellNode.attribute("r").value()).column() < columnNumber)
cellNode = cellNode.next_sibling_of_type(pugi::node_element);
// ===== If the forwards search failed to locate the requested cell
if (XLCellReference(cellNode.attribute("r").value()).column() > columnNumber) {
cellNode = rowNode.insert_child_before("c", cellNode);

72
cmake-cleanup.sh Executable file
View File

@ -0,0 +1,72 @@
#!/bin/bash
# save original internal field separator & set it to newline
DEFAULT_IFS=$IFS
IFS=$'\n'
if [ "$1" = "doit" ]; then
# delete individual known files, suppressing error output (in case they do not exist)
file="CMakeCache.txt"
recurse=""
if [ -e "$file" ]; then
rm $recurse "$file"
fi
file="cmake-log"
recurse=""
if [ -e "$file" ]; then
rm $recurse "$file"
fi
file="OpenXLSX/OpenXLSX/"
recurse="-r"
if [ -e "$file" ]; then
rm $recurse "$file"
fi
# use find command to locate files and folders that occur multiple times & then loop to delete them
CMakeFiles=`find . -name "CMakeFiles" -exec echo {} \;`
for file in $CMakeFiles; do
rm -r "$file"
done
cmake_install_cmake=`find . -name "cmake_install.cmake" -exec echo {} \;`
for file in $cmake_install_cmake; do
rm -r "$file"
done
Makefiles=`find . -name "Makefile" -exec echo {} \;`
for file in $Makefiles; do
rm -r "$file"
done
else
# echo commands for deleting individual known files
file="CMakeCache.txt"
recurse=""
if [ -e "$file" ]; then
echo "rm $recurse \"$file\""
fi
file="cmake-log"
recurse=""
if [ -e "$file" ]; then
echo "rm $recurse \"$file\""
fi
file="OpenXLSX/OpenXLSX/"
recurse="-r"
if [ -e "$file" ]; then
echo "rm $recurse \"$file\""
fi
# use find command to locate files and folders that occur multiple times & then echo commands for deleting them
CMakeFiles=`find . -name "CMakeFiles" -exec echo {} \;`
for file in $CMakeFiles; do
echo "rm -r \"$file\""
done
cmake_install_cmake=`find . -name "cmake_install.cmake" -exec echo {} \;`
for file in $cmake_install_cmake; do
echo "rm -r \"$file\""
done
Makefiles=`find . -name "Makefile" -exec echo {} \;`
for file in $Makefiles; do
echo "rm -r \"$file\""
done
fi
# restore original internal field separator
IFS=$DEFAULT_IFS

View File

@ -0,0 +1,42 @@
#ifndef OPENXLSX_EXPORT_H
#define OPENXLSX_EXPORT_H
#ifdef OPENXLSX_STATIC_DEFINE
# define OPENXLSX_EXPORT
# define OPENXLSX_HIDDEN
#else
# ifndef OPENXLSX_EXPORT
# ifdef OpenXLSX_EXPORTS
/* We are building this library */
# define OPENXLSX_EXPORT
# else
/* We are using this library */
# define OPENXLSX_EXPORT
# endif
# endif
# ifndef OPENXLSX_HIDDEN
# define OPENXLSX_HIDDEN
# endif
#endif
#ifndef OPENXLSX_DEPRECATED
# define OPENXLSX_DEPRECATED __attribute__ ((__deprecated__))
#endif
#ifndef OPENXLSX_DEPRECATED_EXPORT
# define OPENXLSX_DEPRECATED_EXPORT OPENXLSX_EXPORT OPENXLSX_DEPRECATED
#endif
#ifndef OPENXLSX_DEPRECATED_NO_EXPORT
# define OPENXLSX_DEPRECATED_NO_EXPORT OPENXLSX_HIDDEN OPENXLSX_DEPRECATED
#endif
#if 0 /* DEFINE_NO_DEPRECATED */
# ifndef OPENXLSX_NO_DEPRECATED
# define OPENXLSX_NO_DEPRECATED
# endif
#endif
#endif /* OPENXLSX_EXPORT_H */

11
make-gnu.sh Executable file
View File

@ -0,0 +1,11 @@
#!/bin/sh
# assemble all arguments with individual quote pairs to echo correctly with string preservation
ARGS=
for arg in "$@"
do
ARGS="$ARGS \"$arg\""
done
echo "make --makefile Makefile.GNU $ARGS"
make --makefile Makefile.GNU "$@"