OpenXLSX 1.9.1
Loading...
Searching...
No Matches
XLUtilities.hpp
Go to the documentation of this file.
1#ifndef OPENXLSX_XLUTILITIES_HPP
2#define OPENXLSX_XLUTILITIES_HPP
3
4#include <fstream>
5#include <pugixml.hpp>
6#include <string> // 2024-04-25 needed for xml_node_type_string
7#include <string_view> // std::string_view
8#include <vector> // std::vector< std::string_view >
9
10#include "XLCellReference.hpp"
11#include "XLCellValue.hpp" // OpenXLSX::XLValueType
12#include "XLConstants.hpp" // 2024-05-28 OpenXLSX::MAX_ROWS
13#include "XLContentTypes.hpp" // OpenXLSX::XLContentType
14#include "XLRelationships.hpp" // OpenXLSX::XLRelationshipType
15#include "XLStyles.hpp" // OpenXLSX::XLStyleIndex
16#include "XLXmlParser.hpp"
17
18namespace OpenXLSX
19{
20 constexpr const bool XLRemoveAttributes = true; // helper variables for appendAndSetNodeAttribute, parameter removeAttributes
21 constexpr const bool XLKeepAttributes = false; //
22
28 inline bool isCleanXmlString(std::string_view sv) noexcept
29 {
30 for (char c : sv) {
31 unsigned char uc = static_cast<unsigned char>(c);
32 if (uc < 0x20 && uc != 0x09 && uc != 0x0A && uc != 0x0D) {
33 return false;
34 }
35 }
36 return true;
37 }
38
44 inline std::string sanitizeXmlString(std::string_view sv)
45 {
46 std::string result;
47 result.reserve(sv.size());
48 for (char c : sv) {
49 unsigned char uc = static_cast<unsigned char>(c);
50 if (uc >= 0x20 || uc == 0x09 || uc == 0x0A || uc == 0x0D) {
51 result.push_back(c);
52 }
53 }
54 return result;
55 }
56
65 inline uint16_t extractColumnFromCellRef(const char* cellRef) noexcept
66 {
67 if (cellRef == nullptr or *cellRef == '\0') return 0;
68
69 uint32_t colNo = 0;
70 const char* p = cellRef;
71
72 // Parse uppercase letters only (A-Z)
73 while (*p >= 'A' and *p <= 'Z') {
74 colNo = colNo * 26 + static_cast<uint32_t>(*p - 'A' + 1);
75 ++p;
76 }
77
78 // Return 0 if no letters were found, or if column exceeds MAX_COLS
79 return (colNo > 0 and colNo <= MAX_COLS) ? static_cast<uint16_t>(colNo) : 0;
80 }
81
89 inline char* columnToLetters(uint16_t colNo, char* buffer) noexcept
90 {
91 char temp[4];
92 int idx = 0;
93
94 while (colNo > 0) {
95 --colNo; // Convert to 0-indexed for modulo
96 temp[idx++] = static_cast<char>('A' + (colNo % 26));
97 colNo /= 26;
98 }
99
100 // Reverse the result into buffer
101 int j = 0;
102 while (idx > 0) { buffer[j++] = temp[--idx]; }
103 buffer[j] = '\0';
104
105 return buffer;
106 }
107
116 #if defined(__GNUC__) || defined(__clang__)
117__attribute__((always_inline)) inline char* makeCellAddress(uint32_t row, uint16_t col, char* buffer) noexcept
118#elif defined(_MSC_VER)
119__forceinline char* makeCellAddress(uint32_t row, uint16_t col, char* buffer) noexcept
120#else
121inline char* makeCellAddress(uint32_t row, uint16_t col, char* buffer) noexcept
122#endif
123 {
124 // Generate column letters
125 char* p = buffer;
126 char colLetters[4];
127 columnToLetters(col, colLetters);
128
129 // Copy column letters
130 for (const char* c = colLetters; *c; ++c) { *p++ = *c; }
131
132 // Convert row number to string
133 char rowStr[12];
134 int idx = 0;
135 uint32_t r = row;
136 do {
137 rowStr[idx++] = static_cast<char>('0' + (r % 10));
138 r /= 10;
139 }
140 while (r > 0);
141
142 // Append row digits in reverse order
143 while (idx > 0) { *p++ = rowStr[--idx]; }
144 *p = '\0';
145
146 return buffer;
147 }
148
153 template<class T>
154 void ignore(const T&)
155 {}
156
162 inline std::string XLValueTypeString(XLValueType t)
163 {
164 using namespace std::literals::string_literals;
165 switch (t) {
167 return "Empty"s;
169 return "Boolean"s;
171 return "Integer"s;
173 return "Float"s;
175 return "Error"s;
177 return "String"s;
179 return "RichText"s;
180 }
181 throw XLInternalError(__func__ + "Invalid XLValueType."s);
182 }
183
189 inline std::string xml_node_type_string(pugi::xml_node_type t)
190 {
191 using namespace std::literals::string_literals;
192 switch (t) {
193 case pugi::node_null:
194 return "node_null"s;
195 case pugi::node_document:
196 return "node_document"s;
197 case pugi::node_element:
198 return "node_element"s;
199 case pugi::node_pcdata:
200 return "node_pcdata"s;
201 case pugi::node_cdata:
202 return "node_cdata"s;
203 case pugi::node_comment:
204 return "node_comment"s;
205 case pugi::node_pi:
206 return "node_pi"s;
207 case pugi::node_declaration:
208 return "node_declaration"s;
209 case pugi::node_doctype:
210 return "node_doctype"s;
211 }
212 throw XLInternalError("Invalid XML node type.");
213 }
214
220 inline std::string XLContentTypeString(OpenXLSX::XLContentType const& t)
221 {
222 using namespace OpenXLSX;
223 switch (t) {
225 return "Workbook";
227 return "Relationships";
229 return "WorkbookMacroEnabled";
231 return "Worksheet";
233 return "Chartsheet";
235 return "ExternalLink";
237 return "Theme";
239 return "Styles";
241 return "SharedStrings";
243 return "Drawing";
245 return "Chart";
247 return "ChartStyle";
249 return "ChartColorStyle";
251 return "ControlProperties";
253 return "CalculationChain";
255 return "VBAProject";
257 return "Slicer";
259 return "SlicerCache";
261 return "PivotTable";
263 return "PivotCacheDefinition";
265 return "PivotCacheRecords";
266
268 return "CoreProperties";
270 return "ExtendedProperties";
272 return "CustomProperties";
274 return "VMLDrawing";
276 return "Comments";
278 return "Table";
280 return "ThreadedComments";
282 return "Persons";
284 return "Hyperlink";
286 return "Unknown";
287 }
288 return "invalid";
289 }
290
297 {
298 using namespace OpenXLSX;
299 switch (t) {
301 return "CoreProperties";
303 return "ExtendedProperties";
305 return "CustomProperties";
307 return "Workbook";
309 return "Worksheet";
311 return "Chartsheet";
313 return "Dialogsheet";
315 return "Macrosheet";
317 return "CalculationChain";
319 return "ExternalLink";
321 return "ExternalLinkPath";
323 return "Theme";
325 return "Styles";
327 return "Chart";
329 return "ChartStyle";
331 return "ChartColorStyle";
333 return "Image";
335 return "Drawing";
337 return "VMLDrawing";
339 return "SharedStrings";
341 return "PrinterSettings";
343 return "VBAProject";
345 return "Slicer";
347 return "SlicerCache";
349 return "PivotTable";
351 return "PivotCacheDefinition";
353 return "PivotCacheRecords";
354
356 return "ControlProperties";
358 return "Comments";
360 return "Table";
362 return "ThreadedComments";
364 return "Person";
366 return "Hyperlink";
368 return "Unknown";
369 }
370 return "invalid";
371 }
372
376 inline XMLNode getRowNode(XMLNode sheetDataNode, uint32_t rowNumber, uint32_t* hintRowNumber = nullptr, XMLNode* hintRowNode = nullptr)
377 {
378 if (rowNumber < 1 or rowNumber > OpenXLSX::MAX_ROWS) { // 2024-05-28: added range check
379 using namespace std::literals::string_literals;
380 throw XLCellAddressError("rowNumber "s + std::to_string(rowNumber) + " is outside valid range [1;"s +
381 std::to_string(OpenXLSX::MAX_ROWS) + "]"s);
382 }
383
384 // ===== O(1) Hint Fast-Path
385 if (hintRowNumber && hintRowNode && *hintRowNumber == rowNumber && !hintRowNode->empty()) {
386 return *hintRowNode;
387 }
388
389 XMLNode result;
390
391 // ===== Optimized Hint Crawl (if the target row is exactly the next row or very close after the hint)
392 if (hintRowNumber && hintRowNode && !hintRowNode->empty() && rowNumber > *hintRowNumber) {
393 result = *hintRowNode;
394 while (!result.empty() && result.attribute("r").as_ullong() < rowNumber) {
395 result = result.next_sibling_of_type(pugi::node_element);
396 }
397 if (result.empty() || result.attribute("r").as_ullong() > rowNumber) {
398 if (result.empty()) result = sheetDataNode.append_child("row");
399 else result = sheetDataNode.insert_child_before("row", result);
400 result.append_attribute("r") = rowNumber;
401 }
402 }
403 else if (hintRowNumber && hintRowNode && !hintRowNode->empty() && rowNumber < *hintRowNumber) {
404 result = *hintRowNode;
405 while (!result.empty() && result.attribute("r").as_ullong() > rowNumber) {
406 result = result.previous_sibling_of_type(pugi::node_element);
407 }
408 if (result.empty() || result.attribute("r").as_ullong() < rowNumber) {
409 if (result.empty()) result = sheetDataNode.prepend_child("row");
410 else result = sheetDataNode.insert_child_after("row", result);
411 result.append_attribute("r") = rowNumber;
412 }
413 }
414 else {
415 // ===== Original Binary/Edge crawling fallback
416 result = sheetDataNode.last_child_of_type(pugi::node_element);
417
418 if (result.empty() or (rowNumber > result.attribute("r").as_ullong())) {
419 result = sheetDataNode.append_child("row");
420 result.append_attribute("r") = rowNumber;
421 }
422 else if (result.attribute("r").as_ullong() - rowNumber < rowNumber) {
423 while (not result.empty() and (result.attribute("r").as_ullong() > rowNumber))
424 result = result.previous_sibling_of_type(pugi::node_element);
425 if (result.empty() or (result.attribute("r").as_ullong() != rowNumber)) {
426 if (result.empty())
427 result = sheetDataNode.prepend_child("row");
428 else
429 result = sheetDataNode.insert_child_after("row", result);
430 result.append_attribute("r") = rowNumber;
431 }
432 }
433 else {
434 result = sheetDataNode.first_child_of_type(pugi::node_element);
435 while (result.attribute("r").as_ullong() < rowNumber) result = result.next_sibling_of_type(pugi::node_element);
436 if (result.attribute("r").as_ullong() > rowNumber) {
437 result = sheetDataNode.insert_child_before("row", result);
438 result.append_attribute("r") = rowNumber;
439 }
440 }
441 }
442
443 // Update the hint for next time
444 if (hintRowNumber && hintRowNode) {
445 *hintRowNumber = rowNumber;
446 *hintRowNode = result;
447 }
448
449 return result;
450 }
451
464 inline std::vector<XLStyleIndex> getColumnStyles(XMLNode rowNode, uint16_t count)
465 {
466 std::vector<XLStyleIndex> styles(count, XLDefaultCellFormat);
467 XMLNode cols = rowNode.parent().parent().child("cols");
468 if (not cols.empty()) {
469 XMLNode col = cols.first_child_of_type(pugi::node_element);
470 while (not col.empty()) {
471 int minCol = col.attribute("min").as_int(MAX_COLS + 1);
472 int maxCol = col.attribute("max").as_int(0);
473 uint32_t style = col.attribute("style").as_uint(XLDefaultCellFormat);
474
475 for (int i = minCol; i <= maxCol && i <= count; ++i) {
476 if (i > 0) styles[i - 1] = style;
477 }
478 col = col.next_sibling_of_type(pugi::node_element);
479 }
480 }
481 return styles;
482 }
483
484 inline XLStyleIndex getColumnStyle(XMLNode rowNode, uint16_t colNo)
485 {
486 XMLNode cols = rowNode.parent().parent().child("cols");
487 if (not cols.empty()) {
488 XMLNode col = cols.first_child_of_type(pugi::node_element);
489 while (not col.empty()) {
490 if (col.attribute("min").as_int(MAX_COLS + 1) <= colNo and col.attribute("max").as_int(0) >= colNo) // found
491 return col.attribute("style").as_uint(XLDefaultCellFormat);
492 col = col.next_sibling_of_type(pugi::node_element);
493 }
494 }
495 return XLDefaultCellFormat; // if no col style was found
496 }
497
508 inline void setDefaultCellAttributes(XMLNode cellNode,
509 const std::string& cellRef,
510 XMLNode rowNode,
511 uint16_t colNo,
512 std::vector<XLStyleIndex> const& colStyles = {})
513 {
514 cellNode.append_attribute("r").set_value(cellRef.c_str());
515 XMLAttribute rowStyle = rowNode.attribute("s");
516 XLStyleIndex cellStyle; // scope declaration
517 if (rowStyle.empty()) { // if no explicit row style is set
518 if (colStyles.size() > 0) // if colStyles were provided
519 cellStyle =
520 (colNo > colStyles.size() ? XLDefaultCellFormat : colStyles[colNo - 1]); // determine cellStyle from colStyles
521 else // else: no colStyles provided
522 cellStyle = getColumnStyle(rowNode, colNo); // so perform a lookup
523 }
524 else // else: an explicit row style is set
525 cellStyle = rowStyle.as_uint(XLDefaultCellFormat); // use the row style
526
527 if (cellStyle != XLDefaultCellFormat) // if cellStyle was determined as not the default style (no point in setting that)
528 cellNode.append_attribute("s").set_value(cellStyle);
529 }
530
535 inline void setDefaultCellAttributes(XMLNode cellNode,
536 const char* cellRef,
537 XMLNode rowNode,
538 uint16_t colNo,
539 std::vector<XLStyleIndex> const& colStyles = {})
540 {
541 cellNode.append_attribute("r").set_value(cellRef);
542 XMLAttribute rowStyle = rowNode.attribute("s");
543 XLStyleIndex cellStyle;
544 if (rowStyle.empty()) {
545 if (colStyles.size() > 0)
546 cellStyle = (colNo > colStyles.size() ? XLDefaultCellFormat : colStyles[colNo - 1]);
547 else
548 cellStyle = getColumnStyle(rowNode, colNo);
549 }
550 else
551 cellStyle = rowStyle.as_uint(XLDefaultCellFormat);
552
553 if (cellStyle != XLDefaultCellFormat) cellNode.append_attribute("s").set_value(cellStyle);
554 }
555
567 uint16_t columnNumber,
568 uint32_t rowNumber = 0,
569 std::vector<XLStyleIndex> const& colStyles = {},
570 uint16_t* hintColNumber = nullptr,
571 XMLNode* hintCellNode = nullptr)
572 {
573 if (columnNumber < 1 or columnNumber > OpenXLSX::MAX_COLS) { // 2024-08-05: added range check
574 using namespace std::literals::string_literals;
575 throw XLException("XLWorksheet::column: columnNumber "s + std::to_string(columnNumber) + " is outside allowed range [1;"s +
576 std::to_string(MAX_COLS) + "]"s);
577 }
578 if (rowNode.empty()) return XMLNode{}; // 2024-05-28: return an empty node in case of empty rowNode
579
580 if (!rowNumber) rowNumber = rowNode.attribute("r").as_uint(); // if not provided, determine from rowNode
581
582 // ===== O(1) Hint Fast-Path
583 if (hintColNumber && hintCellNode && *hintColNumber == columnNumber && !hintCellNode->empty() && hintCellNode->parent() == rowNode) {
584 return *hintCellNode;
585 }
586
587 XMLNode cellNode;
588
589 // ===== Optimized Hint Crawl
590 if (hintColNumber && hintCellNode && !hintCellNode->empty() && hintCellNode->parent() == rowNode) {
591 cellNode = *hintCellNode;
592 if (columnNumber > *hintColNumber) {
593 uint16_t currentCol = *hintColNumber;
594 while (!cellNode.empty() && currentCol < columnNumber) {
595 cellNode = cellNode.next_sibling_of_type(pugi::node_element);
596 currentCol = cellNode.empty() ? 0 : extractColumnFromCellRef(cellNode.attribute("r").value());
597 }
598 if (currentCol > columnNumber || cellNode.empty()) {
599 if (cellNode.empty()) cellNode = rowNode.append_child("c");
600 else cellNode = rowNode.insert_child_before("c", cellNode);
601 char cellAddrBuf[16];
602 makeCellAddress(rowNumber, columnNumber, cellAddrBuf);
603 setDefaultCellAttributes(cellNode, cellAddrBuf, rowNode, columnNumber, colStyles);
604 }
605 goto update_hint;
606 }
607 else if (columnNumber < *hintColNumber) {
608 uint16_t currentCol = *hintColNumber;
609 while (!cellNode.empty() && currentCol > columnNumber) {
610 cellNode = cellNode.previous_sibling_of_type(pugi::node_element);
611 currentCol = cellNode.empty() ? 0 : extractColumnFromCellRef(cellNode.attribute("r").value());
612 }
613 if (currentCol < columnNumber || cellNode.empty()) {
614 if (cellNode.empty()) cellNode = rowNode.prepend_child("c");
615 else cellNode = rowNode.insert_child_after("c", cellNode);
616 char cellAddrBuf[16];
617 makeCellAddress(rowNumber, columnNumber, cellAddrBuf);
618 setDefaultCellAttributes(cellNode, cellAddrBuf, rowNode, columnNumber, colStyles);
619 }
620 goto update_hint;
621 }
622 }
623
624 // ===== Original Fallback Crawl
625 cellNode = rowNode.last_child_of_type(pugi::node_element);
626 {
627 uint16_t lastCellCol = cellNode.empty() ? 0 : extractColumnFromCellRef(cellNode.attribute("r").value());
628
629 if (cellNode.empty() or (lastCellCol < columnNumber)) {
630 cellNode = rowNode.append_child("c");
631 char cellAddrBuf[16];
632 makeCellAddress(rowNumber, columnNumber, cellAddrBuf);
633 setDefaultCellAttributes(cellNode, cellAddrBuf, rowNode, columnNumber, colStyles);
634 }
635 else if (lastCellCol - columnNumber < columnNumber) {
636 uint16_t currentCol = lastCellCol;
637 while (not cellNode.empty() and (currentCol > columnNumber)) {
638 cellNode = cellNode.previous_sibling_of_type(pugi::node_element);
639 currentCol = cellNode.empty() ? 0 : extractColumnFromCellRef(cellNode.attribute("r").value());
640 }
641 if (cellNode.empty() or (currentCol < columnNumber)) {
642 if (cellNode.empty()) cellNode = rowNode.prepend_child("c");
643 else cellNode = rowNode.insert_child_after("c", cellNode);
644 char cellAddrBuf[16];
645 makeCellAddress(rowNumber, columnNumber, cellAddrBuf);
646 setDefaultCellAttributes(cellNode, cellAddrBuf, rowNode, columnNumber, colStyles);
647 }
648 }
649 else {
650 cellNode = rowNode.first_child_of_type(pugi::node_element);
651 uint16_t currentCol = extractColumnFromCellRef(cellNode.attribute("r").value());
652 while (currentCol < columnNumber) {
653 cellNode = cellNode.next_sibling_of_type(pugi::node_element);
654 currentCol = extractColumnFromCellRef(cellNode.attribute("r").value());
655 }
656 if (currentCol > columnNumber) {
657 cellNode = rowNode.insert_child_before("c", cellNode);
658 char cellAddrBuf[16];
659 makeCellAddress(rowNumber, columnNumber, cellAddrBuf);
660 setDefaultCellAttributes(cellNode, cellAddrBuf, rowNode, columnNumber, colStyles);
661 }
662 }
663 }
664
665update_hint:
666 if (hintColNumber && hintCellNode) {
667 *hintColNumber = columnNumber;
668 *hintCellNode = cellNode;
669 }
670 return cellNode;
671 }
672
682 constexpr const int SORT_INDEX_NOT_FOUND = -1;
683 inline int findStringInVector(std::string const& nodeName, std::vector<std::string_view> const& nodeOrder)
684 {
685 for (size_t i = 0; i < nodeOrder.size(); ++i)
686 if (nodeName == nodeOrder[i]) return static_cast<int>(i);
688 }
689
697 inline void copyLeadingWhitespaces(XMLNode& parent, XMLNode fromNode, XMLNode toNode)
698 {
699 fromNode = fromNode.previous_sibling(); // move to preceeding whitespace node, if any
700 // loop from back to front, inserting in the same order before toNode
701 while (fromNode.type() == pugi::node_pcdata) { // loop ends on pugi::node_element or node_null
702 toNode = parent.insert_child_before(pugi::node_pcdata, toNode); // prepend as toNode a new pcdata node
703 toNode.set_value(fromNode.value()); // with the value of fromNode
704 fromNode = fromNode.previous_sibling();
705 }
706 }
707
719 std::string const& nodeName,
720 std::vector<std::string_view> const& nodeOrder = {},
721 bool force_ns = false)
722 {
723 if (parent.empty()) return XMLNode{};
724
725 XMLNode nextNode = parent.first_child_of_type(pugi::node_element);
726 if (nextNode.empty())
727 return parent.prepend_child(nodeName.c_str(), force_ns); // nothing to sort, whitespaces "belong" to parent closing tag
728
729 XMLNode node{}; // empty until successfully created;
730
731 int nodeSortIndex = (nodeOrder.size() > 1 ? findStringInVector(nodeName, nodeOrder) : SORT_INDEX_NOT_FOUND);
732 if (nodeSortIndex !=
733 SORT_INDEX_NOT_FOUND) { // can't sort anything if nodeOrder contains less than 2 entries or does not contain nodeName
734 // ===== Find first node to follow nodeName per nodeOrder
735 while (not nextNode.empty() and findStringInVector(nextNode.name(), nodeOrder) < nodeSortIndex)
736 nextNode = nextNode.next_sibling_of_type(pugi::node_element);
737 // ===== Evaluate search result
738 if (not nextNode.empty()) { // found nodeName or a node before which nodeName should be inserted
739 if (nextNode.name() == nodeName) // if nodeName was found
740 node = nextNode; // use existing node
741 else { // else: a node was found before which nodeName must be inserted
742 node =
743 parent.insert_child_before(nodeName.c_str(), nextNode, force_ns); // insert before nextNode without whitespaces
744 copyLeadingWhitespaces(parent, node, nextNode);
745 }
746 }
747 // else: no node was found before which nodeName should be inserted: proceed as usual
748 }
749 else // no possibility to perform ordered insert - attempt to locate existing node:
750 node = parent.child(nodeName.c_str());
751
752 if (node.empty()) { // neither nodeName, nor a node following per nodeOrder was found
753 // ===== There is no reference to perform an ordered insert for nodeName
754 nextNode =
755 parent.last_child_of_type(pugi::node_element); // at least one element node must exist, tested at begin of function
756 node = parent.insert_child_after(nodeName.c_str(),
757 nextNode,
758 force_ns); // append as the last element node, but before final whitespaces
759 copyLeadingWhitespaces(parent, nextNode, node); // duplicate the prefix whitespaces of nextNode to node
760 }
761
762 return node;
763 }
764
765 inline XMLAttribute appendAndGetAttribute(XMLNode& node, std::string const& attrName, std::string const& attrDefaultVal)
766 {
767 if (node.empty()) return XMLAttribute{};
768 XMLAttribute attr = node.attribute(attrName.c_str());
769 if (attr.empty()) {
770 attr = node.append_attribute(attrName.c_str());
771 attr.set_value(attrDefaultVal.c_str());
772 }
773 return attr;
774 }
775
776 inline XMLAttribute appendAndSetAttribute(XMLNode& node, std::string const& attrName, std::string const& attrVal)
777 {
778 if (node.empty()) return XMLAttribute{};
779 XMLAttribute attr = node.attribute(attrName.c_str());
780 if (attr.empty()) attr = node.append_attribute(attrName.c_str());
781 attr.set_value(attrVal.c_str()); // silently fails on empty attribute, which is intended here
782 return attr;
783 }
784
795 std::string const& nodeName,
796 std::string const& attrName,
797 std::string const& attrDefaultVal,
798 std::vector<std::string_view> const& nodeOrder = {})
799 {
800 if (parent.empty()) return XMLAttribute{};
801 XMLNode node = appendAndGetNode(parent, nodeName, nodeOrder);
802 return appendAndGetAttribute(node, attrName, attrDefaultVal);
803 }
804
816 std::string const& nodeName,
817 std::string const& attrName,
818 std::string const& attrVal,
819 bool removeAttributes = XLKeepAttributes,
820 std::vector<std::string_view> const& nodeOrder = {})
821 {
822 if (parent.empty()) return XMLAttribute{};
823 XMLNode node = appendAndGetNode(parent, nodeName, nodeOrder);
824 if (removeAttributes) node.remove_attributes();
825 return appendAndSetAttribute(node, attrName, attrVal);
826 }
827
838 inline bool getBoolAttributeWhenOmittedMeansTrue(XMLNode& parent, std::string const& tagName, std::string const& attrName = "val")
839 {
840 if (parent.empty()) return false; // can't do anything
841 XMLNode tagNode = parent.child(tagName.c_str());
842 if (tagNode.empty()) return false; // if tag does not exist: return false
843 XMLAttribute valAttr = tagNode.attribute(attrName.c_str());
844 if (valAttr.empty()) { // if no attribute with attrName exists: default to true
845 appendAndSetAttribute(tagNode, attrName, "true"); // explicitly create & set attribute
846 return true;
847 }
848 // if execution gets here: attribute with attrName exists
849 return valAttr.as_bool(); // return attribute value
850 }
851
857 inline std::pair<uint32_t, uint32_t> getImageDimensions(const std::string& data)
858 {
859 if (data.size() < 8) return {0, 0};
860
861 // Check for PNG
862 if (reinterpret_cast<const uint8_t*>(data.data())[0] == 0x89 and data.substr(1, 3) == "PNG") {
863 if (data.size() < 24) return {0, 0};
864 // IHDR chunk starts at offset 12. Width at 16, height at 20 (4 bytes each, big-endian)
865 const uint8_t* p = reinterpret_cast<const uint8_t*>(data.data());
866 uint32_t w = (static_cast<uint32_t>(p[16]) << 24) | (static_cast<uint32_t>(p[17]) << 16) | (static_cast<uint32_t>(p[18]) << 8) |
867 static_cast<uint32_t>(p[19]);
868 uint32_t h = (static_cast<uint32_t>(p[20]) << 24) | (static_cast<uint32_t>(p[21]) << 16) | (static_cast<uint32_t>(p[22]) << 8) |
869 static_cast<uint32_t>(p[23]);
870 return {w, h};
871 }
872
873 // Check for JPEG
874 if (reinterpret_cast<const uint8_t*>(data.data())[0] == 0xFF and reinterpret_cast<const uint8_t*>(data.data())[1] == 0xD8) {
875 size_t offset = 2;
876 while (offset + 4 < data.size()) {
877 const uint8_t* p = reinterpret_cast<const uint8_t*>(data.data()) + offset;
878 if (p[0] != 0xFF) break;
879 uint8_t marker = p[1];
880 uint32_t length = (static_cast<uint32_t>(p[2]) << 8) | static_cast<uint32_t>(p[3]);
881
882 // SOF0 (Start Of Frame 0) marker is 0xFFC0. Others are 0xFFC1-0xFFC3, 0xFFC5-0xFFC7, 0xFFC9-0xFFCB, 0xFFCD-0xFFCF
883 if ((marker >= 0xC0 and marker <= 0xC3) or (marker >= 0xC5 and marker <= 0xC7) or (marker >= 0xC9 and marker <= 0xCB) ||
884 (marker >= 0xCD and marker <= 0xCF))
885 {
886 if (offset + 9 > data.size()) return {0, 0};
887 // Height is at offset 5, width at 7 (2 bytes each, big-endian)
888 uint32_t h = (static_cast<uint32_t>(p[5]) << 8) | static_cast<uint32_t>(p[6]);
889 uint32_t w = (static_cast<uint32_t>(p[7]) << 8) | static_cast<uint32_t>(p[8]);
890 return {w, h};
891 }
892 offset += length + 2;
893 }
894 }
895
896 return {0, 0};
897 }
898
899} // namespace OpenXLSX
900
901#endif // OPENXLSX_XLUTILITIES_HPP
Definition XLXmlParser.hpp:84
XMLNode last_child_of_type(pugi::xml_node_type type_=pugi::node_element) const
get last node child that matches type
Definition XLXmlParser.cpp:63
XMLNode child(const pugi::char_t *name_) const
Definition XLXmlParser.hpp:150
XMLNode prepend_child(pugi::xml_node_type type_)
Definition XLXmlParser.hpp:161
XMLNode previous_sibling_of_type(pugi::xml_node_type type_=pugi::node_element) const
get previous node sibling that matches type
Definition XLXmlParser.cpp:95
XMLNode append_child(pugi::xml_node_type type_)
Definition XLXmlParser.hpp:160
XMLNode previous_sibling(const pugi::char_t *name_) const
Definition XLXmlParser.hpp:153
XMLNode next_sibling_of_type(pugi::xml_node_type type_=pugi::node_element) const
get next node sibling that matches type
Definition XLXmlParser.cpp:87
XMLNode insert_child_before(pugi::xml_node_type type_, const xml_node &node)
Definition XLXmlParser.hpp:167
XMLNode insert_child_after(pugi::xml_node_type type_, const xml_node &node)
Definition XLXmlParser.hpp:166
const pugi::char_t * name() const
get node name while stripping namespace, if so configured (name_begin > 0)
Definition XLXmlParser.hpp:135
XMLNode parent() const
for all functions returning xml_node: invoke base class function, but with a return type of XMLNode (...
Definition XLXmlParser.hpp:146
XMLNode first_child_of_type(pugi::xml_node_type type_=pugi::node_element) const
get first node child that matches type
Definition XLXmlParser.cpp:52
Definition XLException.hpp:50
Definition XLException.hpp:23
Definition XLException.hpp:68
Definition IZipArchive.hpp:18
std::string XLValueTypeString(XLValueType t)
Get a string representation of pugi::xml_node_type.
Definition XLUtilities.hpp:162
constexpr uint32_t MAX_ROWS
Definition XLConstants.hpp:9
void ignore(const T &)
Get rid of compiler warnings about unused variables (-Wunused-variable) or unused parameters (-Wunusu...
Definition XLUtilities.hpp:154
void copyLeadingWhitespaces(XMLNode &parent, XMLNode fromNode, XMLNode toNode)
copy all leading pc_data nodes from fromNode to toNode
Definition XLUtilities.hpp:697
XMLAttribute appendAndSetNodeAttribute(XMLNode &parent, std::string const &nodeName, std::string const &attrName, std::string const &attrVal, bool removeAttributes=XLKeepAttributes, std::vector< std::string_view > const &nodeOrder={})
ensure that node with nodeName exists in parent, has an attribute with attrName, set attribute value ...
Definition XLUtilities.hpp:815
std::string XLContentTypeString(OpenXLSX::XLContentType const &t)
Get a string representation of OpenXLSX::XLContentType.
Definition XLUtilities.hpp:220
constexpr const XLStyleIndex XLDefaultCellFormat
Definition XLStyles.hpp:49
std::string xml_node_type_string(pugi::xml_node_type t)
Get a string representation of pugi::xml_node_type.
Definition XLUtilities.hpp:189
constexpr const bool XLRemoveAttributes
Definition XLUtilities.hpp:20
XLContentType
Definition XLContentTypes.hpp:28
XLRelationshipType
An enum of the possible relationship (or XML document) types used in relationship (....
Definition XLRelationships.hpp:58
pugi::xml_attribute XMLAttribute
Definition XLXmlParser.hpp:64
size_t XLStyleIndex
Definition XLStyles.hpp:31
char * makeCellAddress(uint32_t row, uint16_t col, char *buffer) noexcept
Generate cell address string directly without creating XLCellReference object. Performance optimizati...
Definition XLUtilities.hpp:121
void setDefaultCellAttributes(XMLNode cellNode, const std::string &cellRef, XMLNode rowNode, uint16_t colNo, std::vector< XLStyleIndex > const &colStyles={})
set the cell reference, and a default cell style attribute if and only if row or column style is !...
Definition XLUtilities.hpp:508
constexpr uint16_t MAX_COLS
Definition XLConstants.hpp:8
XMLAttribute appendAndGetAttribute(XMLNode &node, std::string const &attrName, std::string const &attrDefaultVal)
Definition XLUtilities.hpp:765
XLStyleIndex getColumnStyle(XMLNode rowNode, uint16_t colNo)
Definition XLUtilities.hpp:484
int findStringInVector(std::string const &nodeName, std::vector< std::string_view > const &nodeOrder)
Definition XLUtilities.hpp:683
XLValueType
Enum defining the valid value types for a an Excel spreadsheet cell.
Definition XLCellValue.hpp:37
std::string XLRelationshipTypeString(OpenXLSX::XLRelationshipType const &t)
Get a string representation of OpenXLSX::XLRelationshipType.
Definition XLUtilities.hpp:296
XMLAttribute appendAndGetNodeAttribute(XMLNode &parent, std::string const &nodeName, std::string const &attrName, std::string const &attrDefaultVal, std::vector< std::string_view > const &nodeOrder={})
ensure that node with nodeName exists in parent, has an attribute with attrName and return that attri...
Definition XLUtilities.hpp:794
constexpr const int SORT_INDEX_NOT_FOUND
find the index of nodeName in nodeOrder
Definition XLUtilities.hpp:682
std::vector< XLStyleIndex > getColumnStyles(XMLNode rowNode, uint16_t count)
get the style attribute s for the indicated column, if any is set
Definition XLUtilities.hpp:464
std::pair< uint32_t, uint32_t > getImageDimensions(const std::string &data)
Extract image dimensions (width and height) from raw image data (PNG/JPEG)
Definition XLUtilities.hpp:857
bool isCleanXmlString(std::string_view sv) noexcept
Checks if a string contains only valid XML 1.0 characters.
Definition XLUtilities.hpp:28
XMLAttribute appendAndSetAttribute(XMLNode &node, std::string const &attrName, std::string const &attrVal)
Definition XLUtilities.hpp:776
XMLNode getCellNode(XMLNode rowNode, uint16_t columnNumber, uint32_t rowNumber=0, std::vector< XLStyleIndex > const &colStyles={}, uint16_t *hintColNumber=nullptr, XMLNode *hintCellNode=nullptr)
Retrieve the xml node representing the cell at the given row and column. If the node doesn't exist,...
Definition XLUtilities.hpp:566
XMLNode getRowNode(XMLNode sheetDataNode, uint32_t rowNumber, uint32_t *hintRowNumber=nullptr, XMLNode *hintRowNode=nullptr)
Definition XLUtilities.hpp:376
char * columnToLetters(uint16_t colNo, char *buffer) noexcept
Convert column number to column letter string (A, B, ..., Z, AA, AB, ..., XFD) Performance optimizati...
Definition XLUtilities.hpp:89
bool getBoolAttributeWhenOmittedMeansTrue(XMLNode &parent, std::string const &tagName, std::string const &attrName="val")
special bool attribute getter function for tags that should have a val="true" or val="false" attribut...
Definition XLUtilities.hpp:838
XMLNode appendAndGetNode(XMLNode &parent, std::string const &nodeName, std::vector< std::string_view > const &nodeOrder={}, bool force_ns=false)
ensure that node with nodeName exists in parent and return it
Definition XLUtilities.hpp:718
std::string sanitizeXmlString(std::string_view sv)
Removes invalid XML 1.0 control characters from a string to prevent file corruption.
Definition XLUtilities.hpp:44
uint16_t extractColumnFromCellRef(const char *cellRef) noexcept
Lightweight function to extract column number from a cell reference string. This is a performance-opt...
Definition XLUtilities.hpp:65
constexpr const bool XLKeepAttributes
Definition XLUtilities.hpp:21