mirror of
https://github.com/troldal/OpenXLSX.git
synced 2025-05-08 18:10:43 +08:00
2024-07-11 major cleanup
This commit is contained in:
parent
f85f7f1bd6
commit
f07956dcfe
107
.gitignore
vendored
107
.gitignore
vendored
@ -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
0
CMakeLists.txt
Executable file → Normal file
380
Makefile.GNU
Normal file
380
Makefile.GNU
Normal 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)"
|
@ -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})
|
||||
|
@ -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
|
||||
|
105
OpenXLSX/external/pugixml/pugixml.cpp
vendored
105
OpenXLSX/external/pugixml/pugixml.cpp
vendored
@ -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');
|
||||
}
|
||||
|
13
OpenXLSX/external/pugixml/pugixml.hpp
vendored
13
OpenXLSX/external/pugixml/pugixml.hpp
vendored
@ -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;
|
||||
|
||||
|
15
OpenXLSX/external/zippy/zippy.hpp
vendored
15
OpenXLSX/external/zippy/zippy.hpp
vendored
@ -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);
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
|
@ -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.
|
||||
|
@ -166,4 +166,4 @@ namespace OpenXLSX
|
||||
} // namespace OpenXLSX
|
||||
|
||||
#pragma warning(pop)
|
||||
#endif // OPENXLSX_XLCELLRANGE_HPP
|
||||
#endif // OPENXLSX_XLCELLRANGE_HPP
|
||||
|
@ -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.");
|
||||
}
|
||||
}
|
||||
|
@ -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,
|
||||
|
@ -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>
|
||||
|
@ -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"
|
||||
|
@ -152,4 +152,4 @@ namespace OpenXLSX
|
||||
} // namespace OpenXLSX
|
||||
|
||||
#pragma warning(pop)
|
||||
#endif // OPENXLSX_XLEXCEPTION_HPP
|
||||
#endif // OPENXLSX_XLEXCEPTION_HPP
|
||||
|
@ -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>)
|
||||
|
@ -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
|
||||
|
@ -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.
|
||||
*/
|
||||
|
@ -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 */
|
||||
|
@ -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
|
||||
|
@ -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 ---------- //
|
||||
/**
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
*/
|
||||
|
@ -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 ? ")" : "");
|
||||
}
|
||||
|
@ -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));
|
||||
*/
|
||||
}
|
||||
|
@ -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:
|
||||
|
@ -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 ===== //
|
||||
|
@ -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");
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -5,8 +5,7 @@
|
||||
#include "XLDateTime.hpp"
|
||||
#include "XLException.hpp"
|
||||
#include <cmath>
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
#include <cstdint> // int32_t
|
||||
|
||||
namespace
|
||||
{
|
||||
|
@ -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
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -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());
|
||||
}
|
||||
|
@ -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);
|
||||
|
@ -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");
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -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
|
||||
{
|
||||
|
@ -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();
|
||||
}
|
||||
|
171
OpenXLSX/sources/XLXmlParser.cpp
Normal file
171
OpenXLSX/sources/XLXmlParser.cpp
Normal 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
|
||||
|
@ -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
72
cmake-cleanup.sh
Executable 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
|
42
gnu-make-crutch/OpenXLSX-Exports.hpp
Normal file
42
gnu-make-crutch/OpenXLSX-Exports.hpp
Normal 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
11
make-gnu.sh
Executable 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 "$@"
|
Loading…
x
Reference in New Issue
Block a user