mirror of
https://github.com/troldal/OpenXLSX.git
synced 2025-05-09 02:11:09 +08:00
2025-01-17 comments support is now working - testing stage
This commit is contained in:
parent
d5ec0ce78e
commit
6476b030f4
@ -121,6 +121,52 @@ int main()
|
||||
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 #1", 0);
|
||||
wksComments.set("B2", "this comment is for author #2", 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 #3", 2);
|
||||
wksComments.set("C3", "this is an updated comment for author #3", 2); // overwrite a comment
|
||||
wksComments.shape("C3").style().show();
|
||||
wksComments.set("D1", "this comment is for author #5", 4);
|
||||
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;
|
||||
std::cout << "XLWorksheet protection settings demo" << std::endl;
|
||||
std::cout << "====================================" << std::endl;
|
||||
@ -152,46 +198,9 @@ int main()
|
||||
|
||||
// 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 << "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-13: XLComments in preparation: needs ^%$%#^ vmlDrawing support next :(
|
||||
// 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 #1", 0);
|
||||
// wksComments.set("B2", "this comment is for author #2", 1);
|
||||
// wksComments.set("C3", "this comment is for author #3", 2);
|
||||
// wksComments.set("D1", "this comment is for author #5", 4);
|
||||
|
||||
doc.save();
|
||||
doc.close();
|
||||
|
@ -58,19 +58,9 @@ YM M9 MM MM MM MM MM d' `MM. MM MM d' `MM.
|
||||
#include "XLXmlData.hpp"
|
||||
#include "XLXmlFile.hpp"
|
||||
|
||||
// workbook XML: <sheets><sheet>: get sheetId where name == <worksheet name>
|
||||
// --> sheetId should be equal comment ID ## in xl/comments##.xml
|
||||
// new comments XML:
|
||||
// TODO:
|
||||
// add to [Content_Types].xml:
|
||||
// <Override PartName="/xl/comments1.xml" ContentType="application/vnd.openxmlformats-officedocument.spreadsheetml.comments+xml"/>
|
||||
// create file xl/worksheets/_rels/sheet1.xml.rels, including directory, if it does not exist
|
||||
// add to sheet relationships:
|
||||
// <Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships">
|
||||
// <Relationship Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/comments" Target="/xl/comments1.xml" Id="R31ace4858b334db2"/>
|
||||
// </Relationships>
|
||||
// Id seems to be unused, so... whatever :) but should use GetNewRelsID
|
||||
// TBD if the XLRelationships functionality can be re-used, or GetNewRelsID needs to be exported (currently local to the module in
|
||||
// an anonymous namespace)
|
||||
|
||||
namespace OpenXLSX
|
||||
{
|
||||
@ -154,14 +144,12 @@ namespace OpenXLSX
|
||||
|
||||
bool deleteComment(const std::string& cellRef);
|
||||
|
||||
std::string get(const std::string& cellRef) const;
|
||||
|
||||
/**
|
||||
* @brief get the comment (if any) for the referenced cell
|
||||
* @param cellRef the cell address to check
|
||||
* @return the comment for this cell - an empty string if no comment is set
|
||||
*/
|
||||
std::string get(std::string cellRef) const;
|
||||
std::string get(std::string const& cellRef) const;
|
||||
|
||||
/**
|
||||
* @brief set the comment for the referenced cell
|
||||
@ -170,7 +158,12 @@ namespace OpenXLSX
|
||||
* @param authorId set this author
|
||||
* @return true upon success, false on failure
|
||||
*/
|
||||
bool set(std::string cellRef, std::string comment, uint16_t authorId = 0);
|
||||
bool set(std::string const& cellRef, std::string const& comment, uint16_t authorId = 0);
|
||||
|
||||
/**
|
||||
* @brief get the XLShape object for this comment
|
||||
*/
|
||||
XLShape shape(std::string const& cellRef);
|
||||
|
||||
/**
|
||||
* @brief Print the XML contents of this XLComments instance using the underlying XMLNode print function
|
||||
|
@ -9,6 +9,10 @@ namespace OpenXLSX
|
||||
{
|
||||
inline constexpr uint16_t MAX_COLS = 16'384;
|
||||
inline constexpr uint32_t MAX_ROWS = 1'048'576;
|
||||
|
||||
// anchoring a comment shape below these values was not possible in LibreOffice - TBC with MS Office
|
||||
inline constexpr uint16_t MAX_SHAPE_ANCHOR_COLUMN = 13067; // column "SHO"
|
||||
inline constexpr uint32_t MAX_SHAPE_ANCHOR_ROW = 852177;
|
||||
} // namespace OpenXLSX
|
||||
|
||||
#endif // OPENXLSX_XLCONSTANTS_HPP
|
||||
|
@ -68,6 +68,9 @@ namespace OpenXLSX
|
||||
// <div style="text-align:left;"/>
|
||||
// </v:textbox>
|
||||
|
||||
extern const std::string ShapeNodeName; // = "v:shape"
|
||||
extern const std::string ShapeTypeNodeName; // = "v:shapetype"
|
||||
|
||||
// NOTE: numerical values of XLShapeTextVAlign and XLShapeTextHAlign are shared with the same alignments from XLAlignmentStyle (XLStyles.hpp)
|
||||
enum class XLShapeTextVAlign : uint8_t {
|
||||
Center = 3, // value="center", both
|
||||
@ -181,6 +184,91 @@ namespace OpenXLSX
|
||||
};
|
||||
|
||||
|
||||
struct XLShapeStyleAttribute {
|
||||
std::string name;
|
||||
std::string value;
|
||||
};
|
||||
|
||||
class OPENXLSX_EXPORT XLShapeStyle {
|
||||
public:
|
||||
/**
|
||||
* @brief
|
||||
*/
|
||||
XLShapeStyle();
|
||||
|
||||
/**
|
||||
* @brief Constructor. Init XLShapeStyle properties from styleAttribute
|
||||
* @param styleAttribute a string with the value of the style attribute of a v:shape element
|
||||
*/
|
||||
explicit XLShapeStyle(const std::string& styleAttribute);
|
||||
|
||||
/**
|
||||
* @brief Constructor. Init XLShapeStyle properties from styleAttribute and link to the attribute so that setter functions directly modify it
|
||||
* @param styleAttribute an XMLAttribute constructed with the style attribute of a v:shape element
|
||||
*/
|
||||
explicit XLShapeStyle(const XMLAttribute& styleAttribute);
|
||||
|
||||
private:
|
||||
/**
|
||||
* @brief get index of an attribute name within m_nodeOrder
|
||||
* @return index of attribute in m_nodeOrder
|
||||
* @return -1 if not found
|
||||
*/
|
||||
int16_t attributeOrderIndex(std::string const& attributeName) const;
|
||||
/**
|
||||
* @brief XLShapeStyle internal generic getter & setter functions
|
||||
*/
|
||||
XLShapeStyleAttribute getAttribute(std::string const& attributeName, std::string const& valIfNotFound = "") const;
|
||||
bool setAttribute(std::string const& attributeName, std::string const& attributeValue);
|
||||
// bool setAttribute(XLShapeStyleAttribute const& attribute);
|
||||
|
||||
public:
|
||||
/**
|
||||
* @brief XLShapeStyle getter functions
|
||||
*/
|
||||
std::string position() const;
|
||||
uint16_t marginLeft() const;
|
||||
uint16_t marginTop() const;
|
||||
uint16_t width() const;
|
||||
uint16_t height() const;
|
||||
std::string msoWrapStyle() const;
|
||||
std::string vTextAnchor() const;
|
||||
bool hidden() const;
|
||||
bool visible() const;
|
||||
|
||||
std::string raw() const { return m_style; }
|
||||
|
||||
/**
|
||||
* @brief XLShapeStyle setter functions
|
||||
*/
|
||||
bool setPosition(std::string newPosition);
|
||||
bool setMarginLeft(uint16_t newMarginLeft);
|
||||
bool setMarginTop(uint16_t newMarginTop);
|
||||
bool setWidth(uint16_t newWidth);
|
||||
bool setHeight(uint16_t newHeight);
|
||||
bool setMsoWrapStyle(std::string newMsoWrapStyle);
|
||||
bool setVTextAnchor(std::string newVTextAnchor);
|
||||
bool hide(); // set visibility:hidden
|
||||
bool show(); // set visibility:visible
|
||||
|
||||
bool setRaw(std::string newStyle) { m_style = newStyle; return true; }
|
||||
|
||||
private:
|
||||
mutable std::string m_style; // mutable so getter functions can update it from m_styleAttribute if the latter is not empty
|
||||
std::unique_ptr<XMLAttribute> m_styleAttribute;
|
||||
inline static const std::vector< std::string_view > m_nodeOrder = {
|
||||
"position",
|
||||
"margin-left",
|
||||
"margin-top",
|
||||
"width",
|
||||
"height",
|
||||
"mso-wrap-style",
|
||||
"v-text-anchor",
|
||||
"visibility"
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
class OPENXLSX_EXPORT XLShape {
|
||||
friend class XLVmlDrawing; // for access to m_shapeNode in XLVmlDrawing::addShape
|
||||
public: // ---------- Public Member Functions ---------- //
|
||||
@ -234,9 +322,7 @@ namespace OpenXLSX
|
||||
bool stroked() const; // v:shape attribute stroked "t" ("f"?)
|
||||
std::string type() const; // v:shape attribute type, link to v:shapetype attribute id
|
||||
bool allowInCell() const; // v:shape attribute o:allowincell "f"
|
||||
std::string style() const; // v:shape attribute style
|
||||
// // Example: style="position:absolute;margin-left:258pt;margin-top:0pt;width:81.65pt;height:50.9pt;mso-wrap-style:none;v-text-anchor:middle;visibility:hidden">
|
||||
// // ==> should probably create a subclass XLShapeStyle to further disassemble this attribute
|
||||
XLShapeStyle style(); // v:shape attribute style, but constructed from the XMLAttribute
|
||||
|
||||
// XLShapeShadow& shadow(); // v:shape subnode v:shadow
|
||||
// XLShapeFill& fill(); // v:shape subnode v:fill
|
||||
@ -251,11 +337,12 @@ namespace OpenXLSX
|
||||
* @return true for success, false for failure
|
||||
*/
|
||||
// NOTE: setShapeId is not available because shape id is managed by the parent class in createShape
|
||||
bool setFillColor(std::string newFillColor);
|
||||
bool setFillColor(std::string const& newFillColor);
|
||||
bool setStroked(bool set);
|
||||
bool setType(std::string newType);
|
||||
bool setType(std::string const& newType);
|
||||
bool setAllowInCell(bool set);
|
||||
bool setStyle(std::string newStyle);
|
||||
bool setStyle(std::string const& newStyle);
|
||||
bool setStyle(XLShapeStyle const& newStyle);
|
||||
|
||||
private: // ---------- Private Member Variables ---------- //
|
||||
std::unique_ptr<XMLNode> m_shapeNode; /**< An XMLNode object with the v:shape item */
|
||||
@ -275,6 +362,7 @@ namespace OpenXLSX
|
||||
class OPENXLSX_EXPORT XLVmlDrawing : public XLXmlFile
|
||||
{
|
||||
friend class XLWorksheet; // for access to XLXmlFile::getXmlPath
|
||||
friend class XLComments; // for access to firstShapeNode
|
||||
public:
|
||||
/**
|
||||
* @brief Constructor
|
||||
|
@ -327,11 +327,12 @@ std::string XLComments::get(const std::string& cellRef) const
|
||||
/**
|
||||
* @details TODO: write doxygen headers for functions in this module
|
||||
*/
|
||||
bool XLComments::set(std::string cellRef, std::string commentText, uint16_t authorId)
|
||||
bool XLComments::set(std::string const& cellRef, std::string const& commentText, uint16_t authorId)
|
||||
{
|
||||
XLCellReference destRef(cellRef);
|
||||
uint32_t destRow = destRef.row();
|
||||
uint16_t destCol = destRef.column();
|
||||
bool newCommentCreated = false; // if false, try to find an existing shape before creating one
|
||||
|
||||
using namespace std::literals::string_literals;
|
||||
XMLNode comment = m_commentList.first_child_of_type(pugi::node_element);
|
||||
@ -353,12 +354,14 @@ bool XLComments::set(std::string cellRef, std::string commentText, uint16_t auth
|
||||
comment = m_commentList.insert_child_after("comment", comment); // insert new comment at end of list
|
||||
copyLeadingWhitespaces(m_commentList, comment.previous_sibling(), comment); // and copy whitespaces prefix from previous comment
|
||||
}
|
||||
newCommentCreated = true;
|
||||
}
|
||||
else {
|
||||
XLCellReference ref(comment.attribute("ref").value());
|
||||
if(ref.row() != destRow || ref.column() != destCol) { // if node has to be inserted *before* this one
|
||||
comment = m_commentList.insert_child_before("comment", comment); // insert new comment
|
||||
copyLeadingWhitespaces(m_commentList, comment, comment.next_sibling()); // and copy whitespaces prefix from next node
|
||||
newCommentCreated = true;
|
||||
}
|
||||
else // node exists / was found
|
||||
comment.remove_children(); // clear node content
|
||||
@ -373,22 +376,82 @@ bool XLComments::set(std::string cellRef, std::string commentText, uint16_t auth
|
||||
tNode.prepend_child(pugi::node_pcdata).set_value(commentText.c_str()); // finally, insert <t> node_pcdata value
|
||||
|
||||
if (m_vmlDrawing->valid()) {
|
||||
std::cout << "XLWorksheet::vmlDrawing: m_vmlDrawing is accessible and valid!" << std::endl;
|
||||
XLShape cShape{};
|
||||
bool newShapeNeeded = newCommentCreated; // on new comments: create new shape
|
||||
if (!newCommentCreated) {
|
||||
try {
|
||||
cShape = shape(cellRef); // for existing comments, try to access existing shape
|
||||
}
|
||||
catch (XLException const& e) {
|
||||
newShapeNeeded = true; // not found: create fresh
|
||||
}
|
||||
}
|
||||
if (newShapeNeeded)
|
||||
cShape = m_vmlDrawing->createShape();
|
||||
|
||||
XLShape cShape = m_vmlDrawing->createShape();
|
||||
std::cout << "created a new comment shape with id " << cShape.shapeId() << std::endl;
|
||||
cShape.setFillColor("#ffffc0");
|
||||
cShape.setStroked(true);
|
||||
// setType: already done by XLVmlDrawing::createShape
|
||||
cShape.setAllowInCell(false);
|
||||
cShape.setStyle(
|
||||
"position:absolute;margin-left:258pt;margin-top:0pt;width:81.65pt;height:50.9pt;mso-wrap-style:none;v-text-anchor:middle;visibility:hidden"
|
||||
);
|
||||
{
|
||||
// XLShapeStyle shapeStyle("position:absolute;margin-left:100pt;margin-top:0pt;width:50pt;height:50.0pt;mso-wrap-style:none;v-text-anchor:middle;visibility:hidden");
|
||||
XLShapeStyle shapeStyle{}; // default construct with meaningful values
|
||||
cShape.setStyle(shapeStyle);
|
||||
}
|
||||
|
||||
XLShapeClientData clientData = cShape.clientData();
|
||||
clientData.setObjectType("Note");
|
||||
clientData.setMoveWithCells();
|
||||
clientData.setSizeWithCells();
|
||||
clientData.setAnchor("3, 23, 0, 0, 4, 25, 3, 5");
|
||||
|
||||
{
|
||||
constexpr const uint16_t leftColOffset = 1;
|
||||
constexpr const uint16_t widthCols = 2;
|
||||
constexpr const uint16_t topRowOffset = 1;
|
||||
constexpr const uint16_t heightRows = 2;
|
||||
|
||||
uint16_t anchorLeftCol, anchorRightCol;
|
||||
if( OpenXLSX::MAX_COLS - destCol > leftColOffset + widthCols ) {
|
||||
anchorLeftCol = (destCol - 1) + leftColOffset;
|
||||
anchorRightCol = (destCol - 1) + leftColOffset + widthCols;
|
||||
}
|
||||
else { // if anchor would overflow MAX_COLS: move column anchor to the left of destCol
|
||||
anchorLeftCol = (destCol - 1) - leftColOffset - widthCols;
|
||||
anchorRightCol = (destCol - 1) - leftColOffset;
|
||||
}
|
||||
|
||||
uint32_t anchorTopRow, anchorBottomRow;
|
||||
if( OpenXLSX::MAX_ROWS - destRow > topRowOffset + heightRows ) {
|
||||
anchorTopRow = (destRow - 1) + topRowOffset;
|
||||
anchorBottomRow = (destRow - 1) + topRowOffset + heightRows;
|
||||
}
|
||||
else { // if anchor would overflow MAX_ROWS: move row anchor to the top of destCol
|
||||
anchorTopRow = (destRow - 1) - topRowOffset - heightRows;
|
||||
anchorBottomRow = (destRow - 1) - topRowOffset;
|
||||
}
|
||||
if (anchorRightCol > MAX_SHAPE_ANCHOR_COLUMN)
|
||||
std::cout << "XLComments::set WARNING: anchoring comment shapes beyond column "s
|
||||
/**/ + XLCellReference::columnAsString(MAX_SHAPE_ANCHOR_COLUMN) + " may not get displayed correctly (LO Calc, TBD in Excel)"s << std::endl;
|
||||
if (anchorBottomRow > MAX_SHAPE_ANCHOR_ROW)
|
||||
std::cout << "XLComments::set WARNING: anchoring comment shapes beyond row "s
|
||||
/**/ + std::to_string(MAX_SHAPE_ANCHOR_ROW ) + " may not get displayed correctly (LO Calc, TBD in Excel)"s << std::endl;
|
||||
|
||||
uint16_t anchorLeftOffsetInCell = 10, anchorRightOffsetInCell = 10;
|
||||
uint16_t anchorTopOffsetInCell = 5, anchorBottomOffsetInCell = 5;
|
||||
|
||||
// clientData.setAnchor("3, 23, 0, 0, 4, 25, 3, 5");
|
||||
using namespace std::literals::string_literals;
|
||||
clientData.setAnchor(
|
||||
std::to_string(anchorLeftCol) + ","s
|
||||
+ std::to_string(anchorLeftOffsetInCell) + ","s
|
||||
+ std::to_string(anchorTopRow) + ","s
|
||||
+ std::to_string(anchorTopOffsetInCell) + ","s
|
||||
+ std::to_string(anchorRightCol) + ","s
|
||||
+ std::to_string(anchorRightOffsetInCell) + ","s
|
||||
+ std::to_string(anchorBottomRow) + ","s
|
||||
+ std::to_string(anchorBottomOffsetInCell)
|
||||
);
|
||||
}
|
||||
clientData.setAutoFill(false);
|
||||
clientData.setTextVAlign(XLShapeTextVAlign::Top);
|
||||
clientData.setTextHAlign(XLShapeTextHAlign::Left);
|
||||
@ -416,6 +479,33 @@ bool XLComments::set(std::string cellRef, std::string commentText, uint16_t auth
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @details
|
||||
*/
|
||||
XLShape XLComments::shape(std::string const& cellRef)
|
||||
{
|
||||
if (!m_vmlDrawing->valid())
|
||||
throw XLException("XLComments::shape: can not access any shapes when VML Drawing object is invalid");
|
||||
|
||||
XLCellReference destRef(cellRef);
|
||||
uint32_t destRow = destRef.row() - 1; // for accessing a shape: x:Row and x:Column are zero-indexed
|
||||
uint16_t destCol = destRef.column() - 1; // ..
|
||||
|
||||
XMLNode shape = m_vmlDrawing->firstShapeNode();
|
||||
while (not shape.empty()) {
|
||||
if ((destRow == shape.child("x:ClientData").child("x:Row").text().as_uint())
|
||||
&&(destCol == shape.child("x:ClientData").child("x:Column").text().as_uint()))
|
||||
return XLShape(shape);
|
||||
do {
|
||||
shape = shape.next_sibling_of_type(pugi::node_element);
|
||||
} while (not shape.empty() && shape.name() != OpenXLSX::ShapeNodeName);
|
||||
}
|
||||
|
||||
using namespace std::literals::string_literals;
|
||||
throw XLException("XLComments::shape: not found for cell "s + cellRef + " - was XLComment::set invoked first?"s);
|
||||
return XLShape();
|
||||
}
|
||||
|
||||
/**
|
||||
* @details Print the underlying XML using pugixml::xml_node::print
|
||||
*/
|
||||
|
@ -516,19 +516,60 @@ void XLDocument::open(const std::string& fileName)
|
||||
|
||||
bool isWorkbookPath = (item.path().substr(1) == workbookPath); // determine once, use thrice
|
||||
if (!isWorkbookPath && item.path().substr(0, 4) == "/xl/") {
|
||||
if (item.path().substr(4, 7) == "comment") {
|
||||
if( !m_suppressWarnings )
|
||||
std::cout << "XLDocument::" << __func__ << ": ignoring comment xml file " << item.path() << std::endl;
|
||||
}
|
||||
else if ((item.path().substr(4, 16) == "worksheets/sheet")
|
||||
if ((item.path().substr(4, 16) == "worksheets/sheet")
|
||||
||(item.path().substr(4) == "sharedStrings.xml")
|
||||
||(item.path().substr(4) == "styles.xml")
|
||||
||(item.path().substr(4, 7) == "comment")
|
||||
||(item.path().substr(4, 18) == "drawing/vmlDrawing")
|
||||
||(item.path().substr(4, 12) == "tables/table")
|
||||
||(item.path().substr(4, 11) == "theme/theme"))
|
||||
{
|
||||
m_data.emplace_back(/* parentDoc */ this,
|
||||
/* xmlPath */ item.path().substr(1),
|
||||
/* xmlID */ m_wbkRelationships.relationshipByTarget(item.path().substr(4)).id(),
|
||||
/* xmlType */ item.type());
|
||||
|
||||
/*** BEGIN: dirty workaround to load worksheet dependencies ***/
|
||||
if (item.path().substr(4, 16) == "worksheets/sheet") {
|
||||
// TODO: implement actual content type overrides (and/or check sheet dependencies from sheet relationships)
|
||||
size_t pos = item.path().rfind(".xml");
|
||||
if (pos != std::string::npos) {
|
||||
uint16_t sheetXmlNo = static_cast<uint16_t>(std::stoi(
|
||||
item.path().substr(20, pos - 20) // determine number between /xl/worksheets/sheet and .xml
|
||||
));
|
||||
// std::cout << "loaded a worksheet with sheetXmlNo " << sheetXmlNo << std::endl;
|
||||
|
||||
using namespace std::literals::string_literals;
|
||||
std::string relsFilename = "xl/worksheets/_rels/sheet"s + std::to_string(sheetXmlNo) + ".xml.rels"s;
|
||||
if (m_archive.hasEntry(relsFilename)) {
|
||||
// only add to m_data if not already contained
|
||||
if (m_data.end() == std::find_if(m_data.begin(), m_data.end(), [&](const XLXmlData& theItem) { return theItem.getXmlPath() == relsFilename; }))
|
||||
m_data.emplace_back(this, relsFilename, "", XLContentType::CustomProperties);
|
||||
}
|
||||
|
||||
std::string vmlDrawingFilename = "xl/drawings/vmlDrawing"s + std::to_string(sheetXmlNo) + ".vml"s;
|
||||
if (m_archive.hasEntry(vmlDrawingFilename)) {
|
||||
// only add to m_data if not already contained
|
||||
if (m_data.end() == std::find_if(m_data.begin(), m_data.end(), [&](const XLXmlData& theItem) { return theItem.getXmlPath() == vmlDrawingFilename; }))
|
||||
m_data.emplace_back(this, vmlDrawingFilename, "", XLContentType::VMLDrawing);
|
||||
}
|
||||
|
||||
std::string commentsFilename = "xl/comments"s + std::to_string(sheetXmlNo) + ".xml"s;
|
||||
if (m_archive.hasEntry(commentsFilename)) {
|
||||
// only add to m_data if not already contained
|
||||
if (m_data.end() == std::find_if(m_data.begin(), m_data.end(), [&](const XLXmlData& theItem) { return theItem.getXmlPath() == commentsFilename; }))
|
||||
m_data.emplace_back(this, commentsFilename, "", XLContentType::Comments);
|
||||
}
|
||||
|
||||
std::string tablesFilename = "xl/tables/table"s + std::to_string(sheetXmlNo) + ".xml"s;
|
||||
if (m_archive.hasEntry(tablesFilename)) {
|
||||
// only add to m_data if not already contained
|
||||
if (m_data.end() == std::find_if(m_data.begin(), m_data.end(), [&](const XLXmlData& theItem) { return theItem.getXmlPath() == tablesFilename; }))
|
||||
m_data.emplace_back(this, tablesFilename, "", XLContentType::Table);
|
||||
}
|
||||
}
|
||||
}
|
||||
/*** END: dirty workaround to load worksheet dependencies ***/
|
||||
}
|
||||
else {
|
||||
if( !m_suppressWarnings )
|
||||
|
@ -234,6 +234,166 @@ bool XLShapeClientData::setColumn (uint16_t newColumn)
|
||||
{ return appendAndGetNode(*m_clientDataNode, "x:Column", m_nodeOrder, XLForceNamespace).text().set(newColumn); }
|
||||
|
||||
|
||||
// ========== XLShape Member Functions
|
||||
/**
|
||||
* @details
|
||||
*/
|
||||
XLShapeStyle::XLShapeStyle()
|
||||
: m_style(""),
|
||||
m_styleAttribute(std::make_unique<XMLAttribute>())
|
||||
{
|
||||
setPosition("absolute");
|
||||
setMarginLeft(100);
|
||||
setMarginTop(0);
|
||||
setWidth(80);
|
||||
setHeight(50);
|
||||
setMsoWrapStyle("none");
|
||||
setVTextAnchor("middle");
|
||||
hide();
|
||||
}
|
||||
|
||||
/**
|
||||
* @details
|
||||
*/
|
||||
XLShapeStyle::XLShapeStyle(const std::string& styleAttribute)
|
||||
: m_style(styleAttribute),
|
||||
m_styleAttribute(std::make_unique<XMLAttribute>())
|
||||
{}
|
||||
|
||||
/**
|
||||
* @details
|
||||
*/
|
||||
XLShapeStyle::XLShapeStyle(const XMLAttribute& styleAttribute)
|
||||
: m_style(""),
|
||||
m_styleAttribute(std::make_unique<XMLAttribute>(styleAttribute))
|
||||
{}
|
||||
|
||||
/**
|
||||
* @details find attributeName in m_nodeOrder, then return index
|
||||
*/
|
||||
int16_t XLShapeStyle::attributeOrderIndex(std::string const& attributeName) const
|
||||
{
|
||||
// std::string m_style{"position:absolute;margin-left:100pt;margin-top:0pt;width:50pt;height:50.0pt;mso-wrap-style:none;v-text-anchor:middle;visibility:hidden"};
|
||||
auto attributeIterator = std::find(m_nodeOrder.begin(), m_nodeOrder.end(), attributeName);
|
||||
if (attributeIterator == m_nodeOrder.end())
|
||||
return -1;
|
||||
return attributeIterator - m_nodeOrder.begin();
|
||||
}
|
||||
|
||||
/**
|
||||
* @details
|
||||
*/
|
||||
XLShapeStyleAttribute XLShapeStyle::getAttribute(std::string const& attributeName, std::string const& valIfNotFound) const
|
||||
{
|
||||
if (attributeOrderIndex(attributeName) == -1) {
|
||||
using namespace std::literals::string_literals;
|
||||
throw XLInternalError("XLShapeStyle.getAttribute: attribute "s + attributeName + " is not defined in class"s);
|
||||
}
|
||||
|
||||
// if attribute is linked, re-read m_style each time in case the underlying XML has changed
|
||||
if (not m_styleAttribute->empty()) m_style = std::string(m_styleAttribute->value());
|
||||
|
||||
size_t lastPos = 0;
|
||||
XLShapeStyleAttribute result;
|
||||
result.name = ""; // indicates "not found"
|
||||
result.value = ""; // default in case attribute name is found but has no value
|
||||
|
||||
do {
|
||||
size_t pos = m_style.find(';', lastPos);
|
||||
std::string attrPair = m_style.substr(lastPos, pos - lastPos);
|
||||
if (attrPair.length() > 0) {
|
||||
size_t separatorPos = attrPair.find(':');
|
||||
if (attributeName == attrPair.substr(0, separatorPos)) { // found!
|
||||
result.name = attributeName;
|
||||
if( separatorPos != std::string::npos )
|
||||
result.value = attrPair.substr(separatorPos + 1);
|
||||
break;
|
||||
}
|
||||
}
|
||||
lastPos = pos+1;
|
||||
} while (lastPos < m_style.length());
|
||||
if (lastPos >= m_style.length()) // attribute was not found
|
||||
result.value = valIfNotFound; // -> return default value
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* @details
|
||||
*/
|
||||
bool XLShapeStyle::setAttribute(std::string const& attributeName, std::string const& attributeValue)
|
||||
{
|
||||
int16_t attrIndex = attributeOrderIndex(attributeName);
|
||||
if (attrIndex == -1) {
|
||||
using namespace std::literals::string_literals;
|
||||
throw XLInternalError("XLShapeStyle.setAttribute: attribute "s + attributeName + " is not defined in class"s);
|
||||
}
|
||||
|
||||
// if attribute is linked, re-read m_style each time in case the underlying XML has changed
|
||||
if (not m_styleAttribute->empty()) m_style = std::string(m_styleAttribute->value());
|
||||
|
||||
size_t lastPos = 0;
|
||||
size_t appendPos = 0;
|
||||
do {
|
||||
size_t pos = m_style.find(';', lastPos);
|
||||
|
||||
std::string attrPair = m_style.substr(lastPos, pos - lastPos);
|
||||
if (attrPair.length() > 0) {
|
||||
size_t separatorPos = attrPair.find(':');
|
||||
int16_t thisAttrIndex = attributeOrderIndex(attrPair.substr(0, separatorPos));
|
||||
if (thisAttrIndex >= attrIndex) { // can insert or update
|
||||
appendPos = (thisAttrIndex == attrIndex ? pos : lastPos); // if match: append from following attribute, if not found, append from current (insert)
|
||||
break;
|
||||
}
|
||||
}
|
||||
if(pos == std::string::npos)
|
||||
lastPos = pos;
|
||||
else
|
||||
lastPos = pos + 1;
|
||||
} while (lastPos < m_style.length());
|
||||
if (lastPos >= m_style.length() || appendPos > m_style.length()) { // if attribute needs to be appended or was found at last position
|
||||
appendPos = m_style.length(); // then nothing remains from m_style to follow (appended) attribute
|
||||
|
||||
// for semi-colon logic, lastPos needs to be capped at string length (so that it can be 0 for empty string)
|
||||
if (lastPos > m_style.length()) lastPos = m_style.length();
|
||||
}
|
||||
|
||||
using namespace std::literals::string_literals;
|
||||
m_style = m_style.substr(0, lastPos) + ((lastPos != 0 && appendPos == m_style.length()) ? ";"s : ""s) // prepend ';' if attribute is appended to non-empty string
|
||||
/**/ + attributeName + ":"s + attributeValue // insert attribute:value pair
|
||||
/**/ + (appendPos < m_style.length() ? ";"s : ""s) + m_style.substr(appendPos); // append ';' if attribute is inserted before other data
|
||||
|
||||
// if attribute is linked, update it with the new style value
|
||||
if (not m_styleAttribute->empty()) m_styleAttribute->set_value(m_style.c_str());
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @details XLShapeStyle getter functions
|
||||
*/
|
||||
std::string XLShapeStyle::position () const { return getAttribute("position" ).value ; }
|
||||
uint16_t XLShapeStyle::marginLeft () const { return static_cast<uint16_t>(std::stoi(getAttribute("margin-left" ).value)); }
|
||||
uint16_t XLShapeStyle::marginTop () const { return static_cast<uint16_t>(std::stoi(getAttribute("margin-top" ).value)); }
|
||||
uint16_t XLShapeStyle::width () const { return static_cast<uint16_t>(std::stoi(getAttribute("width" ).value)); }
|
||||
uint16_t XLShapeStyle::height () const { return static_cast<uint16_t>(std::stoi(getAttribute("height" ).value)); }
|
||||
std::string XLShapeStyle::msoWrapStyle() const { return getAttribute("mso-wrap-style").value ; }
|
||||
std::string XLShapeStyle::vTextAnchor () const { return getAttribute("v-text-anchor" ).value ; }
|
||||
bool XLShapeStyle::hidden () const { return ("hidden" == getAttribute("visibility" ).value ); }
|
||||
bool XLShapeStyle::visible () const { return !hidden(); }
|
||||
|
||||
/**
|
||||
* @details XLShapeStyle setter functions
|
||||
*/
|
||||
bool XLShapeStyle::setPosition (std::string newPosition) { return setAttribute("position", newPosition ); }
|
||||
bool XLShapeStyle::setMarginLeft (uint16_t newMarginLeft) { return setAttribute("margin-left", std::to_string(newMarginLeft) + std::string("pt")); }
|
||||
bool XLShapeStyle::setMarginTop (uint16_t newMarginTop) { return setAttribute("margin-top", std::to_string(newMarginTop) + std::string("pt")); }
|
||||
bool XLShapeStyle::setWidth (uint16_t newWidth) { return setAttribute("width", std::to_string(newWidth) + std::string("pt")); }
|
||||
bool XLShapeStyle::setHeight (uint16_t newHeight) { return setAttribute("height", std::to_string(newHeight) + std::string("pt")); }
|
||||
bool XLShapeStyle::setMsoWrapStyle(std::string newMsoWrapStyle) { return setAttribute("mso-wrap-style", newMsoWrapStyle ); }
|
||||
bool XLShapeStyle::setVTextAnchor (std::string newVTextAnchor) { return setAttribute("v-text-anchor", newVTextAnchor ); }
|
||||
bool XLShapeStyle::hide () { return setAttribute("visibility", "hidden" ); }
|
||||
bool XLShapeStyle::show () { return setAttribute("visibility", "visible" ); }
|
||||
|
||||
// ========== XLShape Member Functions
|
||||
XLShape::XLShape() : m_shapeNode(std::make_unique<XMLNode>(XMLNode())) {}
|
||||
|
||||
@ -242,12 +402,12 @@ XLShape::XLShape(const XMLNode& node) : m_shapeNode(std::make_unique<XMLNode>(no
|
||||
/**
|
||||
* @details getter functions: return the shape's attributes
|
||||
*/
|
||||
std::string XLShape::shapeId() const { return m_shapeNode->attribute("id").value(); } // const for the user, managed by parent class XLVmlDrawing
|
||||
std::string XLShape::fillColor() const { return appendAndGetAttribute(*m_shapeNode, "fillcolor", "#ffffc0").value(); }
|
||||
bool XLShape::stroked() const { return appendAndGetAttribute(*m_shapeNode, "stroked", "t" ).as_bool(); }
|
||||
std::string XLShape::type() const { return appendAndGetAttribute(*m_shapeNode, "type", "" ).value(); }
|
||||
bool XLShape::allowInCell() const { return appendAndGetAttribute(*m_shapeNode, "o:allowincell", "f" ).as_bool(); }
|
||||
std::string XLShape::style() const { return appendAndGetAttribute(*m_shapeNode, "style", "" ).value(); }
|
||||
std::string XLShape::shapeId() const { return m_shapeNode->attribute("id").value() ; } // const for user, managed by parent
|
||||
std::string XLShape::fillColor() const { return appendAndGetAttribute(*m_shapeNode, "fillcolor", "#ffffc0").value() ; }
|
||||
bool XLShape::stroked() const { return appendAndGetAttribute(*m_shapeNode, "stroked", "t" ).as_bool() ; }
|
||||
std::string XLShape::type() const { return appendAndGetAttribute(*m_shapeNode, "type", "" ).value() ; }
|
||||
bool XLShape::allowInCell() const { return appendAndGetAttribute(*m_shapeNode, "o:allowincell", "f" ).as_bool() ; }
|
||||
XLShapeStyle XLShape::style() { return XLShapeStyle(appendAndGetAttribute(*m_shapeNode, "style", "" ) ); }
|
||||
|
||||
XLShapeClientData XLShape::clientData() {
|
||||
return XLShapeClientData(appendAndGetNode(*m_shapeNode, "x:ClientData", m_nodeOrder, XLForceNamespace));
|
||||
@ -256,11 +416,12 @@ XLShapeClientData XLShape::clientData() {
|
||||
/**
|
||||
* @details setter functions: assign the shape's attributes
|
||||
*/
|
||||
bool XLShape::setFillColor (std::string newFillColor) { return appendAndSetAttribute(*m_shapeNode, "fillcolor", newFillColor ).empty() == false; }
|
||||
bool XLShape::setStroked (bool set) { return appendAndSetAttribute(*m_shapeNode, "stroked", (set ? "t" : "f")).empty() == false; }
|
||||
bool XLShape::setType (std::string newType) { return appendAndSetAttribute(*m_shapeNode, "type", newType ).empty() == false; }
|
||||
bool XLShape::setAllowInCell(bool set) { return appendAndSetAttribute(*m_shapeNode, "o:allowincell", (set ? "t" : "f")).empty() == false; }
|
||||
bool XLShape::setStyle (std::string newStyle) { return appendAndSetAttribute(*m_shapeNode, "style", newStyle ).empty() == false; }
|
||||
bool XLShape::setFillColor (std::string const& newFillColor) { return appendAndSetAttribute(*m_shapeNode, "fillcolor", newFillColor ).empty() == false; }
|
||||
bool XLShape::setStroked (bool set) { return appendAndSetAttribute(*m_shapeNode, "stroked", (set ? "t" : "f")).empty() == false; }
|
||||
bool XLShape::setType (std::string const& newType) { return appendAndSetAttribute(*m_shapeNode, "type", newType ).empty() == false; }
|
||||
bool XLShape::setAllowInCell(bool set) { return appendAndSetAttribute(*m_shapeNode, "o:allowincell", (set ? "t" : "f")).empty() == false; }
|
||||
bool XLShape::setStyle (std::string const& newStyle) { return appendAndSetAttribute(*m_shapeNode, "style", newStyle ).empty() == false; }
|
||||
bool XLShape::setStyle (XLShapeStyle const& newStyle) { return setStyle( newStyle.raw() ); }
|
||||
|
||||
|
||||
// ========== XLVmlDrawing Member Functions
|
||||
@ -353,8 +514,6 @@ XLVmlDrawing::XLVmlDrawing(XLXmlData* xmlData)
|
||||
}
|
||||
appendAndGetAttribute(pathNode, "gradientshapeok", "t");
|
||||
appendAndGetAttribute(pathNode, "o:connecttype", "rect");
|
||||
|
||||
std::cout << "m_defaultShapeTypeId is " << m_defaultShapeTypeId << std::endl;
|
||||
}
|
||||
|
||||
|
||||
|
@ -1640,19 +1640,25 @@ XLVmlDrawing& XLWorksheet::vmlDrawing()
|
||||
XMLAttribute xdrNamespace = appendAndGetAttribute(docElement, "xmlns:xdr", "");
|
||||
xdrNamespace = "http://schemas.openxmlformats.org/drawingml/2006/spreadsheetDrawing";
|
||||
|
||||
uint16_t sheetXmlNo = sheetXmlNumber();
|
||||
if (!parentDoc().hasSheetRelationships(sheetXmlNo))
|
||||
std::ignore = relationships(); // create sheet relationships if not existing
|
||||
|
||||
std::cout << "worksheet drawing for sheetId " << sheetXmlNo << std::endl;
|
||||
std::ignore = relationships(); // create sheet relationships if not existing
|
||||
|
||||
// ===== Trigger parentDoc to create drawing XML file and return it
|
||||
uint16_t sheetXmlNo = sheetXmlNumber();
|
||||
m_vmlDrawing = parentDoc().sheetVmlDrawing(sheetXmlNo); // fetch drawing for this worksheet
|
||||
if (!m_vmlDrawing.valid())
|
||||
throw XLException("XLWorksheet::vmlDrawing(): could not create drawing XML");
|
||||
std::string drawingRelativePath = getPathARelativeToPathB( m_vmlDrawing.getXmlPath(), getXmlPath() );
|
||||
XLRelationshipItem vmlDrawingRelationship;
|
||||
if (!m_relationships.targetExists(drawingRelativePath))
|
||||
m_relationships.addRelationship(XLRelationshipType::VMLDrawing, drawingRelativePath);
|
||||
vmlDrawingRelationship = m_relationships.addRelationship(XLRelationshipType::VMLDrawing, drawingRelativePath);
|
||||
else
|
||||
vmlDrawingRelationship = m_relationships.relationshipByTarget(drawingRelativePath);
|
||||
if (vmlDrawingRelationship.empty())
|
||||
throw XLException("XLWorksheet::vmlDrawing(): could not add determine sheet relationship for VML Drawing");
|
||||
XMLNode legacyDrawing = appendAndGetNode(docElement, "legacyDrawing", m_nodeOrder);
|
||||
if (legacyDrawing.empty())
|
||||
throw XLException("XLWorksheet::vmlDrawing(): could not add <legacyDrawing> element to worksheet XML");
|
||||
appendAndSetAttribute(legacyDrawing, "r:id", vmlDrawingRelationship.id());
|
||||
}
|
||||
|
||||
return m_vmlDrawing;
|
||||
@ -1664,24 +1670,14 @@ XLVmlDrawing& XLWorksheet::vmlDrawing()
|
||||
XLComments& XLWorksheet::comments()
|
||||
{
|
||||
if (!m_comments.valid()) {
|
||||
std::ignore = relationships(); // create sheet relationships if not existing
|
||||
// ===== Unfortunately, xl/vmlDrawing#.vml is needed to support comments: append xdr namespace attribute to worksheet if not present
|
||||
std::ignore = vmlDrawing(); // create sheet VML drawing if not existing
|
||||
|
||||
if (!m_vmlDrawing.valid()) {
|
||||
// ===== Unfortunately, xl/vmlDrawing#.vml is needed to support comments: append xdr namespace attribute to worksheet if not present
|
||||
std::ignore = vmlDrawing(); // create sheet VML drawing if not existing
|
||||
}
|
||||
|
||||
// test code for new shape object:
|
||||
// uint32_t newShapeIndex = m_vmlDrawing.createShape();
|
||||
// std::cout << "created a new XLShape with index " << newShapeIndex << std::endl;
|
||||
// XLShape shape = m_vmlDrawing.shape(newShapeIndex);
|
||||
|
||||
uint16_t sheetXmlNo = sheetXmlNumber();
|
||||
if (!parentDoc().hasSheetRelationships(sheetXmlNo))
|
||||
std::ignore = relationships(); // create sheet relationships if not existing
|
||||
|
||||
std::cout << "worksheet comments for sheetId " << sheetXmlNo << std::endl;
|
||||
|
||||
// ===== Trigger parentDoc to create comment XML file and return it
|
||||
uint16_t sheetXmlNo = sheetXmlNumber();
|
||||
// std::cout << "worksheet comments for sheetId " << sheetXmlNo << std::endl;
|
||||
m_comments = parentDoc().sheetComments(sheetXmlNo); // fetch comments for this worksheet
|
||||
if (!m_comments.valid())
|
||||
throw XLException("XLWorksheet::comments(): could not create comments XML");
|
||||
@ -1700,13 +1696,12 @@ XLComments& XLWorksheet::comments()
|
||||
XLTables& XLWorksheet::tables()
|
||||
{
|
||||
if (!m_tables.valid()) {
|
||||
uint16_t sheetXmlNo = sheetXmlNumber();
|
||||
if (!parentDoc().hasSheetRelationships(sheetXmlNo))
|
||||
std::ignore = relationships(); // create sheet relationships if not existing
|
||||
std::ignore = relationships(); // create sheet relationships if not existing
|
||||
|
||||
std::cout << "worksheet tables for sheetId " << sheetXmlNo << std::endl;
|
||||
|
||||
// ===== Trigger parentDoc to create tables XML file and return it
|
||||
uint16_t sheetXmlNo = sheetXmlNumber();
|
||||
// std::cout << "worksheet tables for sheetId " << sheetXmlNo << std::endl;
|
||||
m_tables = parentDoc().sheetTables(sheetXmlNo); // fetch tables for this worksheet
|
||||
if (!m_tables.valid())
|
||||
throw XLException("XLWorksheet::tables(): could not create tables XML");
|
||||
|
16
README.md
16
README.md
@ -7,6 +7,22 @@ Microsoft Excel® files, with the .xlsx format.
|
||||
|
||||
As the heading says - the latest "Release" that is shown on https://github.com/troldal/OpenXLSX/releases is from 2021-11-06, and severely outdated - please pull / download the latest SW version directly from the repository in its current state. Link for those that do not want to use ```git```: https://github.com/troldal/OpenXLSX/archive/refs/heads/master.zip
|
||||
|
||||
## (aral-matrix) 17 January 2025 - Basic support for comments is implemented - testing stage
|
||||
The support for comments is now in a stage where it can be used - please refer to Demo01 line 124ff for a quick "howto".
|
||||
This feature is still in alpha stage, with a few open TBDs:
|
||||
* the library currently assumes that comment entries are well sorted, first by row, then by column - if this is not the case, existing comments might not be readable / be found
|
||||
* the VML Drawing support is basic at best, for lack of documentation. I have made most features available through ```XLShape XLComments::shape(std::string const& cellRef)``` - for methods of XLShape please refer to the ```XLDrawing``` header file
|
||||
* missing support in XLShape for now:
|
||||
```
|
||||
// XLShapeShadow& shadow(); // v:shape subnode v:shadow
|
||||
// XLShapeFill& fill(); // v:shape subnode v:fill
|
||||
// XLShapeStroke& stroke(); // v:shape subnode v:stroke
|
||||
// XLShapePath& path(); // v:shape subnode v:path
|
||||
// XLShapeTextbox& textbox(); // v:shape subnode v:textbox
|
||||
```
|
||||
* missing: library still needs to add ```Override``` entries for the worksheet dependencies (comments, vmlDrawing, table)
|
||||
* to be replaced with proper logic: library (XLDocument) currently loads worksheets dependencies in a workaround
|
||||
|
||||
## (aral-matrix) 14 January 2025 - Support for comments is work in progress - unstable :)
|
||||
As the title says - I am working on comments support and will do the occasional commit to save my progress (aside from local backups). I will only submit patches that should not affect existing code stability, but if I break something (last patch I forgot to add a new module dependency to the cmake instructions), apologies for that - feel free to open an issue right away & I will try to fix it ASAP.
|
||||
I hope to finalize comments support (and possibly tables / filters) by the weekend.
|
||||
|
Loading…
x
Reference in New Issue
Block a user