mirror of
https://github.com/troldal/OpenXLSX.git
synced 2025-05-10 11:56:33 +08:00
279 lines
16 KiB
C++
279 lines
16 KiB
C++
#include <OpenXLSX.hpp>
|
|
#include <iostream>
|
|
#include <cmath>
|
|
|
|
using namespace std;
|
|
using namespace OpenXLSX;
|
|
|
|
/**
|
|
* @brief demo how to iterate over all worksheet comments by sequence in XML - for few comments, this is substantially faster than testing each cell for a non-empty comment
|
|
* @param doc the XLDocument
|
|
* @return n/a
|
|
*/
|
|
void printAllDocumentComments(XLDocument const & doc)
|
|
{
|
|
for( size_t i = 1; i <= doc.workbook().worksheetCount(); ++i ) {
|
|
auto wks = doc.workbook().worksheet(i);
|
|
if( wks.hasComments() ) {
|
|
std::cout << "worksheet(" << i << ") with name \"" << wks.name() << "\" has comments" << std::endl;
|
|
XLComments wksComments = wks.comments();
|
|
size_t commentCount = wksComments.count();
|
|
for( size_t idx = 0; idx < commentCount; ++idx ) {
|
|
XLComment com = wksComments.get(idx);
|
|
std::cout << "comment in " << com.ref() << ": \"" << com.text() << "\" (author: " << wksComments.author(com.authorId()) << ")" << std::endl;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
int main()
|
|
{
|
|
cout << "********************************************************************************\n";
|
|
cout << "DEMO PROGRAM #01: Basic Usage and XLWorksheet protection\n";
|
|
cout << "********************************************************************************\n";
|
|
|
|
// This example program illustrates basic usage of OpenXLSX, for example creation of a new workbook, and read/write
|
|
// of cell values.
|
|
|
|
// First, create a new document and access the sheet named 'Sheet1'.
|
|
// New documents contain a single worksheet named 'Sheet1'
|
|
XLDocument doc;
|
|
doc.create("./Demo01.xlsx", XLForceOverwrite);
|
|
auto wks = doc.workbook().worksheet("Sheet1");
|
|
|
|
// The individual cells can be accessed by using the .cell() method on the worksheet object.
|
|
// The .cell() method can take the cell address as a string, or alternatively take a XLCellReference
|
|
// object. By using an XLCellReference object, the cells can be accessed by row/column coordinates.
|
|
// The .cell() method returns an XLCell object.
|
|
|
|
// The .value() method of an XLCell object can be used for both getting and setting the cell value.
|
|
// Setting the value of a cell can be done by using the assignment operator on the .value() method
|
|
// as shown below. Alternatively, a .set() can be used. The cell values can be floating point numbers,
|
|
// integers, strings, and booleans. It can also accept XLDateTime objects, but this requires special
|
|
// handling (see later).
|
|
wks.cell("A1").value() = 3.14159265358979323846;
|
|
wks.cell("B1").value() = 42;
|
|
wks.cell("C1").value() = " Hello OpenXLSX! ";
|
|
wks.cell("D1").value() = true;
|
|
wks.cell("E1").value() = std::sqrt(-2); // Result is NAN, resulting in an error value in the Excel spreadsheet.
|
|
|
|
// As mentioned, the .value() method can also be used for getting tha value of a cell.
|
|
// The .value() method returns a proxy object that cannot be copied or assigned, but
|
|
// it can be implicitly converted to an XLCellValue object, as shown below.
|
|
// Unfortunately, it is not possible to use the 'auto' keyword, so the XLCellValue
|
|
// type has to be explicitly stated.
|
|
XLCellValue A1 = wks.cell("A1").value();
|
|
XLCellValue B1 = wks.cell("B1").value();
|
|
XLCellValue C1 = wks.cell("C1").value();
|
|
XLCellValue D1 = wks.cell("D1").value();
|
|
XLCellValue E1 = wks.cell("E1").value();
|
|
|
|
// The cell value can be implicitly converted to a basic c++ type. However, if the type does not
|
|
// match the type contained in the XLCellValue object (if, for example, floating point value is
|
|
// assigned to a std::string), then an XLValueTypeError exception will be thrown.
|
|
// To check which type is contained, use the .type() method, which will return a XLValueType enum
|
|
// representing the type. As a convenience, the .typeAsString() method returns the type as a string,
|
|
// which can be useful when printing to console.
|
|
double vA1 = wks.cell("A1").value();
|
|
int vB1 = wks.cell("B1").value();
|
|
std::string vC1 = wks.cell("C1").value();
|
|
bool vD1 = wks.cell("D1").value();
|
|
double vE1 = wks.cell("E1").value();
|
|
|
|
cout << "Cell A1: (" << A1.typeAsString() << ") " << vA1 << endl;
|
|
cout << "Cell B1: (" << B1.typeAsString() << ") " << vB1 << endl;
|
|
cout << "Cell C1: (" << C1.typeAsString() << ") " << vC1 << endl;
|
|
cout << "Cell D1: (" << D1.typeAsString() << ") " << vD1 << endl;
|
|
cout << "Cell E1: (" << E1.typeAsString() << ") " << vE1 << endl << endl;
|
|
|
|
// Instead of using implicit (or explicit) conversion, the underlying value can also be retrieved
|
|
// using the .get() method. This is a templated member function, which takes the desired type
|
|
// as a template argument.
|
|
cout << "Cell A1: (" << A1.typeAsString() << ") " << A1.get<double>() << endl;
|
|
cout << "Cell B1: (" << B1.typeAsString() << ") " << B1.get<int64_t>() << endl;
|
|
cout << "Cell C1: (" << C1.typeAsString() << ") " << C1.get<std::string>() << endl;
|
|
cout << "Cell D1: (" << D1.typeAsString() << ") " << D1.get<bool>() << endl;
|
|
cout << "Cell E1: (" << E1.typeAsString() << ") " << E1.get<double>() << endl << endl;
|
|
|
|
// XLCellValue objects can also be copied and assigned to other cells. This following line
|
|
// will copy and assign the value of cell C1 to cell E1. Note that only the value is copied;
|
|
// other cell properties of the target cell remain unchanged.
|
|
|
|
wks.cell("F1").value() = wks.cell(XLCellReference("C1")).value();
|
|
|
|
XLCellValue F1 = wks.cell("F1").value();
|
|
cout << "Cell F1: (" << F1.typeAsString() << ") " << F1.get<std::string>() << endl << endl;
|
|
|
|
// Date/time values is a special case. In Excel, date/time values are essentially just a
|
|
// 64-bit floating point value, that is rendered as a date/time string using special
|
|
// formatting. When retrieving the cell value, it is just a floating point value,
|
|
// and there is no way to identify it as a date/time value.
|
|
// If, however, you know it to be a date time value, or if you want to assign a date/time
|
|
// value to a cell, you can use the XLDateTime class, which falilitates conversion between
|
|
// Excel date/time serial numbers, and the std::tm struct, that is used to store
|
|
// date/time data. See https://en.cppreference.com/w/cpp/chrono/c/tm for more information.
|
|
|
|
// An XLDateTime object can be created from a std::tm object:
|
|
std::tm tm;
|
|
tm.tm_year = 121;
|
|
tm.tm_mon = 8;
|
|
tm.tm_mday = 1;
|
|
tm.tm_hour = 12;
|
|
tm.tm_min = 0;
|
|
tm.tm_sec = 0;
|
|
XLDateTime dt (tm);
|
|
// XLDateTime dt (43791.583333333299);
|
|
|
|
// The std::tm object can be assigned to a cell value in the same way as shown previously.
|
|
wks.cell("G1").value() = dt;
|
|
|
|
// And as seen previously, an XLCellValue object can be retrieved. However, the object
|
|
// will just contain a floating point value; there is no way to identify it as a date/time value.
|
|
XLCellValue G1 = wks.cell("G1").value();
|
|
cout << "Cell G1: (" << G1.typeAsString() << ") " << G1.get<double>() << endl;
|
|
|
|
// If it is known to be a date/time value, the cell value can be converted to an XLDateTime object.
|
|
auto result = G1.get<XLDateTime>();
|
|
|
|
// The Excel date/time serial number can be retrieved using the .serial() method.
|
|
cout << "Cell G1: (" << G1.typeAsString() << ") " << result.serial() << endl;
|
|
|
|
// Using the .tm() method, the corresponding std::tm object can be retrieved.
|
|
auto tmo = result.tm();
|
|
cout << "Cell G1: (" << G1.typeAsString() << ") " << std::asctime(&tmo);
|
|
|
|
std::cout << std::endl;
|
|
std::cout << "XLWorksheet comments demo" << std::endl;
|
|
std::cout << "=========================" << std::endl;
|
|
std::cout << "wks.hasComments() is " << (wks.hasComments() ? " TRUE" : "FALSE")
|
|
<< ", wks.hasVmlDrawing() is " << (wks.hasVmlDrawing() ? " TRUE" : "FALSE")
|
|
<< ", wks.hasTables() is " << (wks.hasTables() ? " TRUE" : "FALSE") << std::endl;
|
|
|
|
XLComments &wksComments = wks.comments(); // fetch comments (and create if missing)
|
|
std::cout << "wks.comments() is " << (wksComments.valid() ? "valid" : "not valid") << std::endl;
|
|
XLTables &wksTables = wks.tables(); // fetch tables (and create if missing)
|
|
std::cout << "wks.tables() is " << (wksTables.valid() ? "valid" : "not valid") << std::endl;
|
|
|
|
std::cout << "wks.hasComments() is " << (wks.hasComments() ? " TRUE" : "FALSE")
|
|
<< ", wks.hasVmlDrawing() is " << (wks.hasVmlDrawing() ? " TRUE" : "FALSE")
|
|
<< ", wks.hasTables() is " << (wks.hasTables() ? " TRUE" : "FALSE") << std::endl;
|
|
|
|
// 2025-01-17: LibreOffice displays all comments with the same, LibreOffice, author - regardless of author id - to be tested with MS Office
|
|
std::string authors[10] { "Edgar Allan Poe", "Agatha Christie", "J.R.R. Tolkien", "David Eddings", "Daniel Suarez",
|
|
/**/ "Mary Shelley", "George Orwell", "Stanislaw Lem", "Ray Bradbury", "William Shakespeare" };
|
|
|
|
for( std::string author : authors ) {
|
|
wksComments.addAuthor( author );
|
|
uint16_t authorCount = wksComments.authorCount();
|
|
std::cout << "wksComments.authorCount is " << authorCount << std::endl;
|
|
std::cout << "wksComments.author(" << (authorCount - 1) << ") is " << wksComments.author(authorCount - 1) << std::endl;
|
|
}
|
|
|
|
wksComments.deleteAuthor(7);
|
|
wksComments.deleteAuthor(5);
|
|
wksComments.deleteAuthor(3);
|
|
uint16_t authorCount = wksComments.authorCount();
|
|
std::cout << "wksComments.authorCount is " << authorCount << " after deleting authors 7, 5 and 3" << std::endl;
|
|
for( uint16_t index = 0; index < authorCount; ++index )
|
|
std::cout << "wksComments.author(" << (index) << ") is " << wksComments.author(index) << std::endl;
|
|
|
|
wksComments.set("A1", "this comment is for author #0", 0);
|
|
wksComments.set("B2", "this comment is for author #1", 1);
|
|
wksComments.shape("B2").style().show(); // bit cumbersome to access, but it reflects the XML: <v:shape style="[..];visibility=visible">[..]</v:shape>
|
|
wksComments.set("C3", "this comment is for author #2", 2);
|
|
wksComments.set("C3", "this is an updated comment for author #2", 2); // overwrite a comment
|
|
wksComments.shape("C3").style().show();
|
|
wksComments.set("D1", "this comment is for author #4", 4);
|
|
wksComments.set("E2", "this comment is also for author #4", 4);
|
|
bool deleteSuccess = wksComments.deleteComment("E2");
|
|
std::cout << "Deleting the comment in cell E2 " << ( deleteSuccess ? "succeeded" : "did not succeed" ) << "." << std::endl;
|
|
std::cout << std::endl;
|
|
std::cout << "the comment in cell C4 is \"" << wksComments.get("C4") << "\"" << std::endl;
|
|
std::cout << "the comment in cell B2 is \"" << wksComments.get("B2") << "\"" << std::endl;
|
|
std::cout << std::endl;
|
|
printAllDocumentComments(doc);
|
|
|
|
std::cout << std::endl;
|
|
std::cout << "XLWorksheet protection settings demo" << std::endl;
|
|
std::cout << "====================================" << std::endl;
|
|
std::cout << "Default worksheet " << wks.name() << " protection settings: " << std::endl
|
|
/**/ << wks.sheetProtectionSummary() << std::endl;
|
|
std::string password = "test";
|
|
std::string pwHash = ExcelPasswordHashAsString(password);
|
|
wks.setPassword(password); // set password directly
|
|
wks.setPasswordHash(pwHash); // set password via hash
|
|
wks.protectSheet(); // protects all cells that have protected=true
|
|
// protectObjects(bool set = false); // not sure what this would do
|
|
// protectScenarios(bool set = false); // not sure what this would do
|
|
wks.allowSelectUnlockedCells(); // user can select unlocked cells (default)
|
|
wks.denySelectLockedCells(); // user can not select any cells except those that are explicitly unlocked
|
|
// wks.denySelectUnlockedCells(); // somehow this setting REALLY does not make any sense
|
|
// wks.clearPassword(); // clear a password - this will not(!) disable sheet protection but will no longer require a password to unprotect the sheet in a GUI
|
|
// wks.protectSheet(false); // remove sheet protection - this will disable the other protect settings
|
|
// wks.clearSheetProtection(); // this will ALL protection settings, including the password hash
|
|
std::cout << "---" << std::endl;
|
|
std::cout << "Configured worksheet " << wks.name() << " protection settings: " << std::endl
|
|
/**/ << wks.sheetProtectionSummary() << std::endl;
|
|
|
|
// Create a new cell format that unlocks cells
|
|
XLCellFormats & cellFormats = doc.styles().cellFormats();
|
|
XLStyleIndex newCellFormatIndex = cellFormats.create( cellFormats[ wks.cell("C1").cellFormat() ] );
|
|
// cellFormats[ newCellFormatIndex ].setApplyProtection( true ); // seems to be irrelevant
|
|
// cellFormats[ newCellFormatIndex ].setLocked( true ); // locked seems to be default behavior if attribute is not set
|
|
cellFormats[ newCellFormatIndex ].setLocked( false ); // unprotect a cell
|
|
|
|
// wks.cell("D1").setCellFormat(newCellFormatIndex);
|
|
wks.range("C1:D1").setFormat(newCellFormatIndex); // unlock two cells
|
|
std::cout << "====================================" << std::endl;
|
|
std::cout << "==> the worksheet " << wks.name() << " is now protected with the password " << password << std::endl;
|
|
std::cout << std::endl;
|
|
|
|
std::cout << std::endl;
|
|
std::cout << "XLWorksheet testing for empty cells demo.." << std::endl;
|
|
std::cout << "==========================================" << std::endl;
|
|
std::cout << " using an XLCellIterator (recommended)" << std::endl;
|
|
std::cout << " -----------------------------------" << std::endl;
|
|
XLCellRange cellRange = wks.range("A1:G2");
|
|
for (XLCellIterator cellIt = cellRange.begin(); cellIt != cellRange.end(); ++cellIt) {
|
|
if (!cellIt.cellExists()) continue; // prevent cell creation by access for non-existing cells
|
|
std::cout << "cell " << cellIt.address() << " exists!" << std::endl;
|
|
}
|
|
|
|
std::cout << std::endl;
|
|
std::cout << " using XLSheet::findCell (lazy method, not recommended)" << std::endl;
|
|
std::cout << " NOTE: this method should only be used for tests of individual cells when performance is not an issue" << std::endl;
|
|
std::cout << " ------------------------------------------------------" << std::endl;
|
|
wks.cell("D2").formula().set("=A1+B1");
|
|
wks.cell("E2").formula().set(""); // setting an empty (zero-length) formula string will delete the formula node
|
|
|
|
for (int row = 1; row < 4; ++row) {
|
|
for (int col = 1; col < 8; ++col) {
|
|
XLCellReference ref(row, col);
|
|
XLCellAssignable c = wks.findCell(ref);
|
|
if (c.empty())
|
|
continue;
|
|
|
|
// It is up to the implementation to decide which tests shall be performed on a cell that exists - here are a few exemplary tests
|
|
std::string valueAsString = c.getString();
|
|
if (valueAsString == "" && !c.hasFormula())
|
|
cout << "cell " << ref.address() << " exists but has no value or formula" << std::endl;
|
|
else {
|
|
// cout << "cell " << ref.address() << " value is |" << c.value() << "|" << std::endl;
|
|
if (valueAsString.length() > 0)
|
|
cout << "cell " << ref.address() << " value is |" << valueAsString << "|" << std::endl;
|
|
if (c.hasFormula()) {
|
|
if (c.formula().get().length() > 0)
|
|
cout << "cell " << ref.address() << " formula is |" << c.formula().get() << "|" << std::endl;
|
|
else // NOTE: this state can not be created with OpenXLSX setter functions, but possibly by other libraries / programs
|
|
cout << "cell " << ref.address() << " has an empty formula" << std::endl;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
doc.save();
|
|
doc.close();
|
|
|
|
return 0;
|
|
}
|