OpenXLSX 1.10.0
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 += c;
52 }
53 }
54 return result;
55 }
56
62 inline void appendEscaped(std::string& out, std::string_view sv)
63 {
64 for (char c : sv) {
65 switch (c) {
66 case '<':
67 out += "&lt;";
68 break;
69 case '>':
70 out += "&gt;";
71 break;
72 case '&':
73 out += "&amp;";
74 break;
75 case '"':
76 out += "&quot;";
77 break;
78 case '\'':
79 out += "&apos;";
80 break;
81 default:
82 out += c;
83 break;
84 }
85 }
86 }
87
96 inline uint16_t extractColumnFromCellRef(const char* cellRef) noexcept
97 {
98 if (cellRef == nullptr or *cellRef == '\0') return 0;
99
100 uint32_t colNo = 0;
101 const char* p = cellRef;
102
103 // Parse uppercase letters only (A-Z)
104 while (*p >= 'A' and *p <= 'Z') {
105 colNo = colNo * 26 + static_cast<uint32_t>(*p - 'A' + 1);
106 ++p;
107 }
108
109 // Return 0 if no letters were found, or if column exceeds MAX_COLS
110 return (colNo > 0 and colNo <= MAX_COLS) ? static_cast<uint16_t>(colNo) : 0;
111 }
112
120 inline char* columnToLetters(uint16_t colNo, char* buffer) noexcept
121 {
122 char temp[4];
123 int idx = 0;
124
125 while (colNo > 0) {
126 --colNo; // Convert to 0-indexed for modulo
127 temp[idx++] = static_cast<char>('A' + (colNo % 26));
128 colNo /= 26;
129 }
130
131 // Reverse the result into buffer
132 int j = 0;
133 while (idx > 0) { buffer[j++] = temp[--idx]; }
134 buffer[j] = '\0';
135
136 return buffer;
137 }
138
147 #if defined(__GNUC__) || defined(__clang__)
148__attribute__((always_inline)) inline char* makeCellAddress(uint32_t row, uint16_t col, char* buffer) noexcept
149#elif defined(_MSC_VER)
150__forceinline char* makeCellAddress(uint32_t row, uint16_t col, char* buffer) noexcept
151#else
152inline char* makeCellAddress(uint32_t row, uint16_t col, char* buffer) noexcept
153#endif
154 {
155 // Generate column letters
156 char* p = buffer;
157 char colLetters[4];
158 columnToLetters(col, colLetters);
159
160 // Copy column letters
161 for (const char* c = colLetters; *c; ++c) { *p++ = *c; }
162
163 // Convert row number to string
164 char rowStr[12];
165 int idx = 0;
166 uint32_t r = row;
167 do {
168 rowStr[idx++] = static_cast<char>('0' + (r % 10));
169 r /= 10;
170 }
171 while (r > 0);
172
173 // Append row digits in reverse order
174 while (idx > 0) { *p++ = rowStr[--idx]; }
175 *p = '\0';
176
177 return buffer;
178 }
179
184 template<class T>
185 void ignore(const T&)
186 {}
187
193 inline std::string XLValueTypeString(XLValueType t)
194 {
195 using namespace std::literals::string_literals;
196 switch (t) {
198 return "Empty"s;
200 return "Boolean"s;
202 return "Integer"s;
204 return "Float"s;
206 return "Error"s;
208 return "String"s;
210 return "RichText"s;
211 }
212 throw XLInternalError(__func__ + "Invalid XLValueType."s);
213 }
214
220 inline std::string xml_node_type_string(pugi::xml_node_type t)
221 {
222 using namespace std::literals::string_literals;
223 switch (t) {
224 case pugi::node_null:
225 return "node_null"s;
226 case pugi::node_document:
227 return "node_document"s;
228 case pugi::node_element:
229 return "node_element"s;
230 case pugi::node_pcdata:
231 return "node_pcdata"s;
232 case pugi::node_cdata:
233 return "node_cdata"s;
234 case pugi::node_comment:
235 return "node_comment"s;
236 case pugi::node_pi:
237 return "node_pi"s;
238 case pugi::node_declaration:
239 return "node_declaration"s;
240 case pugi::node_doctype:
241 return "node_doctype"s;
242 }
243 throw XLInternalError("Invalid XML node type.");
244 }
245
251 inline std::string XLContentTypeString(OpenXLSX::XLContentType const& t)
252 {
253 using namespace OpenXLSX;
254 switch (t) {
256 return "Workbook";
258 return "Relationships";
260 return "WorkbookMacroEnabled";
262 return "Worksheet";
264 return "Chartsheet";
266 return "ExternalLink";
268 return "Theme";
270 return "Styles";
272 return "SharedStrings";
274 return "Drawing";
276 return "Chart";
278 return "ChartStyle";
280 return "ChartColorStyle";
282 return "ControlProperties";
284 return "CalculationChain";
286 return "VBAProject";
288 return "Slicer";
290 return "SlicerCache";
292 return "PivotTable";
294 return "PivotCacheDefinition";
296 return "PivotCacheRecords";
297
299 return "CoreProperties";
301 return "ExtendedProperties";
303 return "CustomProperties";
305 return "VMLDrawing";
307 return "Comments";
309 return "Table";
311 return "ThreadedComments";
313 return "Persons";
315 return "Hyperlink";
317 return "Unknown";
318 }
319 return "invalid";
320 }
321
328 {
329 using namespace OpenXLSX;
330 switch (t) {
332 return "CoreProperties";
334 return "ExtendedProperties";
336 return "CustomProperties";
338 return "Workbook";
340 return "Worksheet";
342 return "Chartsheet";
344 return "Dialogsheet";
346 return "Macrosheet";
348 return "CalculationChain";
350 return "ExternalLink";
352 return "ExternalLinkPath";
354 return "Theme";
356 return "Styles";
358 return "Chart";
360 return "ChartStyle";
362 return "ChartColorStyle";
364 return "Image";
366 return "Drawing";
368 return "VMLDrawing";
370 return "SharedStrings";
372 return "PrinterSettings";
374 return "VBAProject";
376 return "Slicer";
378 return "SlicerCache";
380 return "PivotTable";
382 return "PivotCacheDefinition";
384 return "PivotCacheRecords";
385
387 return "ControlProperties";
389 return "Comments";
391 return "Table";
393 return "ThreadedComments";
395 return "Person";
397 return "Hyperlink";
399 return "Unknown";
400 }
401 return "invalid";
402 }
403
407 inline XMLNode getRowNode(XMLNode sheetDataNode, uint32_t rowNumber, uint32_t* hintRowNumber = nullptr, XMLNode* hintRowNode = nullptr)
408 {
409 if (rowNumber < 1 or rowNumber > OpenXLSX::MAX_ROWS) { // 2024-05-28: added range check
410 using namespace std::literals::string_literals;
411 throw XLCellAddressError("rowNumber "s + std::to_string(rowNumber) + " is outside valid range [1;"s +
412 std::to_string(OpenXLSX::MAX_ROWS) + "]"s);
413 }
414
415 // ===== O(1) Hint Fast-Path
416 if (hintRowNumber && hintRowNode && *hintRowNumber == rowNumber && !hintRowNode->empty()) {
417 return *hintRowNode;
418 }
419
420 XMLNode result;
421
422 // ===== Optimized Hint Crawl (if the target row is exactly the next row or very close after the hint)
423 if (hintRowNumber && hintRowNode && !hintRowNode->empty() && rowNumber > *hintRowNumber) {
424 result = *hintRowNode;
425 while (!result.empty() && result.attribute("r").as_ullong() < rowNumber) {
426 result = result.next_sibling_of_type(pugi::node_element);
427 }
428 if (result.empty() || result.attribute("r").as_ullong() > rowNumber) {
429 if (result.empty()) result = sheetDataNode.append_child("row");
430 else result = sheetDataNode.insert_child_before("row", result);
431 result.append_attribute("r") = rowNumber;
432 }
433 }
434 else if (hintRowNumber && hintRowNode && !hintRowNode->empty() && rowNumber < *hintRowNumber) {
435 result = *hintRowNode;
436 while (!result.empty() && result.attribute("r").as_ullong() > rowNumber) {
437 result = result.previous_sibling_of_type(pugi::node_element);
438 }
439 if (result.empty() || result.attribute("r").as_ullong() < rowNumber) {
440 if (result.empty()) result = sheetDataNode.prepend_child("row");
441 else result = sheetDataNode.insert_child_after("row", result);
442 result.append_attribute("r") = rowNumber;
443 }
444 }
445 else {
446 // ===== Original Binary/Edge crawling fallback
447 result = sheetDataNode.last_child_of_type(pugi::node_element);
448
449 if (result.empty() or (rowNumber > result.attribute("r").as_ullong())) {
450 result = sheetDataNode.append_child("row");
451 result.append_attribute("r") = rowNumber;
452 }
453 else if (result.attribute("r").as_ullong() - rowNumber < rowNumber) {
454 while (not result.empty() and (result.attribute("r").as_ullong() > rowNumber))
455 result = result.previous_sibling_of_type(pugi::node_element);
456 if (result.empty() or (result.attribute("r").as_ullong() != rowNumber)) {
457 if (result.empty())
458 result = sheetDataNode.prepend_child("row");
459 else
460 result = sheetDataNode.insert_child_after("row", result);
461 result.append_attribute("r") = rowNumber;
462 }
463 }
464 else {
465 result = sheetDataNode.first_child_of_type(pugi::node_element);
466 while (result.attribute("r").as_ullong() < rowNumber) result = result.next_sibling_of_type(pugi::node_element);
467 if (result.attribute("r").as_ullong() > rowNumber) {
468 result = sheetDataNode.insert_child_before("row", result);
469 result.append_attribute("r") = rowNumber;
470 }
471 }
472 }
473
474 // Update the hint for next time
475 if (hintRowNumber && hintRowNode) {
476 *hintRowNumber = rowNumber;
477 *hintRowNode = result;
478 }
479
480 return result;
481 }
482
495 inline std::vector<XLStyleIndex> getColumnStyles(XMLNode rowNode, uint16_t count)
496 {
497 std::vector<XLStyleIndex> styles(count, XLDefaultCellFormat);
498 XMLNode cols = rowNode.parent().parent().child("cols");
499 if (not cols.empty()) {
500 XMLNode col = cols.first_child_of_type(pugi::node_element);
501 while (not col.empty()) {
502 int minCol = col.attribute("min").as_int(MAX_COLS + 1);
503 int maxCol = col.attribute("max").as_int(0);
504 uint32_t style = col.attribute("style").as_uint(XLDefaultCellFormat);
505
506 for (int i = minCol; i <= maxCol && i <= count; ++i) {
507 if (i > 0) styles[i - 1] = style;
508 }
509 col = col.next_sibling_of_type(pugi::node_element);
510 }
511 }
512 return styles;
513 }
514
515 inline XLStyleIndex getColumnStyle(XMLNode rowNode, uint16_t colNo)
516 {
517 XMLNode cols = rowNode.parent().parent().child("cols");
518 if (not cols.empty()) {
519 XMLNode col = cols.first_child_of_type(pugi::node_element);
520 while (not col.empty()) {
521 if (col.attribute("min").as_int(MAX_COLS + 1) <= colNo and col.attribute("max").as_int(0) >= colNo) // found
522 return col.attribute("style").as_uint(XLDefaultCellFormat);
523 col = col.next_sibling_of_type(pugi::node_element);
524 }
525 }
526 return XLDefaultCellFormat; // if no col style was found
527 }
528
539 inline void setDefaultCellAttributes(XMLNode cellNode,
540 const std::string& cellRef,
541 XMLNode rowNode,
542 uint16_t colNo,
543 std::vector<XLStyleIndex> const& colStyles = {})
544 {
545 cellNode.append_attribute("r").set_value(cellRef.c_str());
546 XMLAttribute rowStyle = rowNode.attribute("s");
547 XLStyleIndex cellStyle; // scope declaration
548 if (rowStyle.empty()) { // if no explicit row style is set
549 if (colStyles.size() > 0) // if colStyles were provided
550 cellStyle =
551 (colNo > colStyles.size() ? XLDefaultCellFormat : colStyles[colNo - 1]); // determine cellStyle from colStyles
552 else // else: no colStyles provided
553 cellStyle = getColumnStyle(rowNode, colNo); // so perform a lookup
554 }
555 else // else: an explicit row style is set
556 cellStyle = rowStyle.as_uint(XLDefaultCellFormat); // use the row style
557
558 if (cellStyle != XLDefaultCellFormat) // if cellStyle was determined as not the default style (no point in setting that)
559 cellNode.append_attribute("s").set_value(cellStyle);
560 }
561
566 inline void setDefaultCellAttributes(XMLNode cellNode,
567 const char* cellRef,
568 XMLNode rowNode,
569 uint16_t colNo,
570 std::vector<XLStyleIndex> const& colStyles = {})
571 {
572 cellNode.append_attribute("r").set_value(cellRef);
573 XMLAttribute rowStyle = rowNode.attribute("s");
574 XLStyleIndex cellStyle;
575 if (rowStyle.empty()) {
576 if (colStyles.size() > 0)
577 cellStyle = (colNo > colStyles.size() ? XLDefaultCellFormat : colStyles[colNo - 1]);
578 else
579 cellStyle = getColumnStyle(rowNode, colNo);
580 }
581 else
582 cellStyle = rowStyle.as_uint(XLDefaultCellFormat);
583
584 if (cellStyle != XLDefaultCellFormat) cellNode.append_attribute("s").set_value(cellStyle);
585 }
586
598 uint16_t columnNumber,
599 uint32_t rowNumber = 0,
600 std::vector<XLStyleIndex> const& colStyles = {},
601 uint16_t* hintColNumber = nullptr,
602 XMLNode* hintCellNode = nullptr)
603 {
604 if (columnNumber < 1 or columnNumber > OpenXLSX::MAX_COLS) { // 2024-08-05: added range check
605 using namespace std::literals::string_literals;
606 throw XLException("XLWorksheet::column: columnNumber "s + std::to_string(columnNumber) + " is outside allowed range [1;"s +
607 std::to_string(MAX_COLS) + "]"s);
608 }
609 if (rowNode.empty()) return XMLNode{}; // 2024-05-28: return an empty node in case of empty rowNode
610
611 if (!rowNumber) rowNumber = rowNode.attribute("r").as_uint(); // if not provided, determine from rowNode
612
613 // ===== O(1) Hint Fast-Path
614 if (hintColNumber && hintCellNode && *hintColNumber == columnNumber && !hintCellNode->empty() && hintCellNode->parent() == rowNode) {
615 return *hintCellNode;
616 }
617
618 XMLNode cellNode;
619
620 // ===== Optimized Hint Crawl
621 if (hintColNumber && hintCellNode && !hintCellNode->empty() && hintCellNode->parent() == rowNode) {
622 cellNode = *hintCellNode;
623 if (columnNumber > *hintColNumber) {
624 uint16_t currentCol = *hintColNumber;
625 while (!cellNode.empty() && currentCol < columnNumber) {
626 cellNode = cellNode.next_sibling_of_type(pugi::node_element);
627 currentCol = cellNode.empty() ? 0 : extractColumnFromCellRef(cellNode.attribute("r").value());
628 }
629 if (currentCol > columnNumber || cellNode.empty()) {
630 if (cellNode.empty()) cellNode = rowNode.append_child("c");
631 else cellNode = rowNode.insert_child_before("c", cellNode);
632 char cellAddrBuf[16];
633 makeCellAddress(rowNumber, columnNumber, cellAddrBuf);
634 setDefaultCellAttributes(cellNode, cellAddrBuf, rowNode, columnNumber, colStyles);
635 }
636 goto update_hint;
637 }
638 else if (columnNumber < *hintColNumber) {
639 uint16_t currentCol = *hintColNumber;
640 while (!cellNode.empty() && currentCol > columnNumber) {
641 cellNode = cellNode.previous_sibling_of_type(pugi::node_element);
642 currentCol = cellNode.empty() ? 0 : extractColumnFromCellRef(cellNode.attribute("r").value());
643 }
644 if (currentCol < columnNumber || cellNode.empty()) {
645 if (cellNode.empty()) cellNode = rowNode.prepend_child("c");
646 else cellNode = rowNode.insert_child_after("c", cellNode);
647 char cellAddrBuf[16];
648 makeCellAddress(rowNumber, columnNumber, cellAddrBuf);
649 setDefaultCellAttributes(cellNode, cellAddrBuf, rowNode, columnNumber, colStyles);
650 }
651 goto update_hint;
652 }
653 }
654
655 // ===== Original Fallback Crawl
656 cellNode = rowNode.last_child_of_type(pugi::node_element);
657 {
658 uint16_t lastCellCol = cellNode.empty() ? 0 : extractColumnFromCellRef(cellNode.attribute("r").value());
659
660 if (cellNode.empty() or (lastCellCol < columnNumber)) {
661 cellNode = rowNode.append_child("c");
662 char cellAddrBuf[16];
663 makeCellAddress(rowNumber, columnNumber, cellAddrBuf);
664 setDefaultCellAttributes(cellNode, cellAddrBuf, rowNode, columnNumber, colStyles);
665 }
666 else if (lastCellCol - columnNumber < columnNumber) {
667 uint16_t currentCol = lastCellCol;
668 while (not cellNode.empty() and (currentCol > columnNumber)) {
669 cellNode = cellNode.previous_sibling_of_type(pugi::node_element);
670 currentCol = cellNode.empty() ? 0 : extractColumnFromCellRef(cellNode.attribute("r").value());
671 }
672 if (cellNode.empty() or (currentCol < columnNumber)) {
673 if (cellNode.empty()) cellNode = rowNode.prepend_child("c");
674 else cellNode = rowNode.insert_child_after("c", cellNode);
675 char cellAddrBuf[16];
676 makeCellAddress(rowNumber, columnNumber, cellAddrBuf);
677 setDefaultCellAttributes(cellNode, cellAddrBuf, rowNode, columnNumber, colStyles);
678 }
679 }
680 else {
681 cellNode = rowNode.first_child_of_type(pugi::node_element);
682 uint16_t currentCol = extractColumnFromCellRef(cellNode.attribute("r").value());
683 while (currentCol < columnNumber) {
684 cellNode = cellNode.next_sibling_of_type(pugi::node_element);
685 currentCol = extractColumnFromCellRef(cellNode.attribute("r").value());
686 }
687 if (currentCol > columnNumber) {
688 cellNode = rowNode.insert_child_before("c", cellNode);
689 char cellAddrBuf[16];
690 makeCellAddress(rowNumber, columnNumber, cellAddrBuf);
691 setDefaultCellAttributes(cellNode, cellAddrBuf, rowNode, columnNumber, colStyles);
692 }
693 }
694 }
695
696update_hint:
697 if (hintColNumber && hintCellNode) {
698 *hintColNumber = columnNumber;
699 *hintCellNode = cellNode;
700 }
701 return cellNode;
702 }
703
713 constexpr const int SORT_INDEX_NOT_FOUND = -1;
714 inline int findStringInVector(std::string const& nodeName, std::vector<std::string_view> const& nodeOrder)
715 {
716 for (size_t i = 0; i < nodeOrder.size(); ++i)
717 if (nodeName == nodeOrder[i]) return static_cast<int>(i);
719 }
720
728 inline void copyLeadingWhitespaces(XMLNode& parent, XMLNode fromNode, XMLNode toNode)
729 {
730 fromNode = fromNode.previous_sibling(); // move to preceeding whitespace node, if any
731 // loop from back to front, inserting in the same order before toNode
732 while (fromNode.type() == pugi::node_pcdata) { // loop ends on pugi::node_element or node_null
733 toNode = parent.insert_child_before(pugi::node_pcdata, toNode); // prepend as toNode a new pcdata node
734 toNode.set_value(fromNode.value()); // with the value of fromNode
735 fromNode = fromNode.previous_sibling();
736 }
737 }
738
750 std::string const& nodeName,
751 std::vector<std::string_view> const& nodeOrder = {},
752 bool force_ns = false)
753 {
754 if (parent.empty()) return XMLNode{};
755
756 XMLNode nextNode = parent.first_child_of_type(pugi::node_element);
757 if (nextNode.empty())
758 return parent.prepend_child(nodeName.c_str(), force_ns); // nothing to sort, whitespaces "belong" to parent closing tag
759
760 XMLNode node{}; // empty until successfully created;
761
762 int nodeSortIndex = (nodeOrder.size() > 1 ? findStringInVector(nodeName, nodeOrder) : SORT_INDEX_NOT_FOUND);
763 if (nodeSortIndex !=
764 SORT_INDEX_NOT_FOUND) { // can't sort anything if nodeOrder contains less than 2 entries or does not contain nodeName
765 // ===== Find first node to follow nodeName per nodeOrder
766 while (not nextNode.empty() and findStringInVector(nextNode.name(), nodeOrder) < nodeSortIndex)
767 nextNode = nextNode.next_sibling_of_type(pugi::node_element);
768 // ===== Evaluate search result
769 if (not nextNode.empty()) { // found nodeName or a node before which nodeName should be inserted
770 if (nextNode.name() == nodeName) // if nodeName was found
771 node = nextNode; // use existing node
772 else { // else: a node was found before which nodeName must be inserted
773 node =
774 parent.insert_child_before(nodeName.c_str(), nextNode, force_ns); // insert before nextNode without whitespaces
775 copyLeadingWhitespaces(parent, node, nextNode);
776 }
777 }
778 // else: no node was found before which nodeName should be inserted: proceed as usual
779 }
780 else // no possibility to perform ordered insert - attempt to locate existing node:
781 node = parent.child(nodeName.c_str());
782
783 if (node.empty()) { // neither nodeName, nor a node following per nodeOrder was found
784 // ===== There is no reference to perform an ordered insert for nodeName
785 nextNode =
786 parent.last_child_of_type(pugi::node_element); // at least one element node must exist, tested at begin of function
787 node = parent.insert_child_after(nodeName.c_str(),
788 nextNode,
789 force_ns); // append as the last element node, but before final whitespaces
790 copyLeadingWhitespaces(parent, nextNode, node); // duplicate the prefix whitespaces of nextNode to node
791 }
792
793 return node;
794 }
795
796 inline XMLAttribute appendAndGetAttribute(XMLNode& node, std::string const& attrName, std::string const& attrDefaultVal)
797 {
798 if (node.empty()) return XMLAttribute{};
799 XMLAttribute attr = node.attribute(attrName.c_str());
800 if (attr.empty()) {
801 attr = node.append_attribute(attrName.c_str());
802 attr.set_value(attrDefaultVal.c_str());
803 }
804 return attr;
805 }
806
807 inline XMLAttribute appendAndSetAttribute(XMLNode& node, std::string const& attrName, std::string const& attrVal)
808 {
809 if (node.empty()) return XMLAttribute{};
810 XMLAttribute attr = node.attribute(attrName.c_str());
811 if (attr.empty()) attr = node.append_attribute(attrName.c_str());
812 attr.set_value(attrVal.c_str()); // silently fails on empty attribute, which is intended here
813 return attr;
814 }
815
826 std::string const& nodeName,
827 std::string const& attrName,
828 std::string const& attrDefaultVal,
829 std::vector<std::string_view> const& nodeOrder = {})
830 {
831 if (parent.empty()) return XMLAttribute{};
832 XMLNode node = appendAndGetNode(parent, nodeName, nodeOrder);
833 return appendAndGetAttribute(node, attrName, attrDefaultVal);
834 }
835
847 std::string const& nodeName,
848 std::string const& attrName,
849 std::string const& attrVal,
850 bool removeAttributes = XLKeepAttributes,
851 std::vector<std::string_view> const& nodeOrder = {})
852 {
853 if (parent.empty()) return XMLAttribute{};
854 XMLNode node = appendAndGetNode(parent, nodeName, nodeOrder);
855 if (removeAttributes) node.remove_attributes();
856 return appendAndSetAttribute(node, attrName, attrVal);
857 }
858
869 inline bool getBoolAttributeWhenOmittedMeansTrue(XMLNode& parent, std::string const& tagName, std::string const& attrName = "val")
870 {
871 if (parent.empty()) return false; // can't do anything
872 XMLNode tagNode = parent.child(tagName.c_str());
873 if (tagNode.empty()) return false; // if tag does not exist: return false
874 XMLAttribute valAttr = tagNode.attribute(attrName.c_str());
875 if (valAttr.empty()) { // if no attribute with attrName exists: default to true
876 appendAndSetAttribute(tagNode, attrName, "true"); // explicitly create & set attribute
877 return true;
878 }
879 // if execution gets here: attribute with attrName exists
880 return valAttr.as_bool(); // return attribute value
881 }
882
888 inline std::pair<uint32_t, uint32_t> getImageDimensions(const std::string& data)
889 {
890 if (data.size() < 8) return {0, 0};
891
892 // Check for PNG
893 if (reinterpret_cast<const uint8_t*>(data.data())[0] == 0x89 and data.substr(1, 3) == "PNG") {
894 if (data.size() < 24) return {0, 0};
895 // IHDR chunk starts at offset 12. Width at 16, height at 20 (4 bytes each, big-endian)
896 const uint8_t* p = reinterpret_cast<const uint8_t*>(data.data());
897 uint32_t w = (static_cast<uint32_t>(p[16]) << 24) | (static_cast<uint32_t>(p[17]) << 16) | (static_cast<uint32_t>(p[18]) << 8) |
898 static_cast<uint32_t>(p[19]);
899 uint32_t h = (static_cast<uint32_t>(p[20]) << 24) | (static_cast<uint32_t>(p[21]) << 16) | (static_cast<uint32_t>(p[22]) << 8) |
900 static_cast<uint32_t>(p[23]);
901 return {w, h};
902 }
903
904 // Check for JPEG
905 if (reinterpret_cast<const uint8_t*>(data.data())[0] == 0xFF and reinterpret_cast<const uint8_t*>(data.data())[1] == 0xD8) {
906 size_t offset = 2;
907 while (offset + 4 < data.size()) {
908 const uint8_t* p = reinterpret_cast<const uint8_t*>(data.data()) + offset;
909 if (p[0] != 0xFF) break;
910 uint8_t marker = p[1];
911 uint32_t length = (static_cast<uint32_t>(p[2]) << 8) | static_cast<uint32_t>(p[3]);
912
913 // SOF0 (Start Of Frame 0) marker is 0xFFC0. Others are 0xFFC1-0xFFC3, 0xFFC5-0xFFC7, 0xFFC9-0xFFCB, 0xFFCD-0xFFCF
914 if ((marker >= 0xC0 and marker <= 0xC3) or (marker >= 0xC5 and marker <= 0xC7) or (marker >= 0xC9 and marker <= 0xCB) ||
915 (marker >= 0xCD and marker <= 0xCF))
916 {
917 if (offset + 9 > data.size()) return {0, 0};
918 // Height is at offset 5, width at 7 (2 bytes each, big-endian)
919 uint32_t h = (static_cast<uint32_t>(p[5]) << 8) | static_cast<uint32_t>(p[6]);
920 uint32_t w = (static_cast<uint32_t>(p[7]) << 8) | static_cast<uint32_t>(p[8]);
921 return {w, h};
922 }
923 offset += length + 2;
924 }
925 }
926
927 return {0, 0};
928 }
929
930} // namespace OpenXLSX
931
932#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:193
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:185
void copyLeadingWhitespaces(XMLNode &parent, XMLNode fromNode, XMLNode toNode)
copy all leading pc_data nodes from fromNode to toNode
Definition XLUtilities.hpp:728
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:846
std::string XLContentTypeString(OpenXLSX::XLContentType const &t)
Get a string representation of OpenXLSX::XLContentType.
Definition XLUtilities.hpp:251
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:220
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:152
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:539
constexpr uint16_t MAX_COLS
Definition XLConstants.hpp:8
XMLAttribute appendAndGetAttribute(XMLNode &node, std::string const &attrName, std::string const &attrDefaultVal)
Definition XLUtilities.hpp:796
XLStyleIndex getColumnStyle(XMLNode rowNode, uint16_t colNo)
Definition XLUtilities.hpp:515
int findStringInVector(std::string const &nodeName, std::vector< std::string_view > const &nodeOrder)
Definition XLUtilities.hpp:714
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:327
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:825
constexpr const int SORT_INDEX_NOT_FOUND
find the index of nodeName in nodeOrder
Definition XLUtilities.hpp:713
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:495
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:888
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:807
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:597
void appendEscaped(std::string &out, std::string_view sv)
Fast XML escape: appends escaped characters to out to avoid intermediate allocations.
Definition XLUtilities.hpp:62
XMLNode getRowNode(XMLNode sheetDataNode, uint32_t rowNumber, uint32_t *hintRowNumber=nullptr, XMLNode *hintRowNode=nullptr)
Definition XLUtilities.hpp:407
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:120
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:869
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:749
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:96
constexpr const bool XLKeepAttributes
Definition XLUtilities.hpp:21