// // Created by Kenneth Balslev on 19/08/2020. // // ===== External Includes ===== // #include // ===== OpenXLSX Includes ===== // #include "XLCell.hpp" #include "XLCellValue.hpp" #include "XLException.hpp" #include "XLSharedStrings.hpp" using namespace OpenXLSX; /** * @details Constructor. Default implementation has been used. * @pre * @post */ XLCellValue::XLCellValue() = default; /** * @details Copy constructor. The default implementation will be used. * @pre The object to be copied must be valid. * @post A valid copy is constructed. */ XLCellValue::XLCellValue(const OpenXLSX::XLCellValue& other) = default; /** * @details Move constructor. The default implementation will be used. * @pre The object to be copied must be valid. * @post A valid copy is constructed. */ XLCellValue::XLCellValue(OpenXLSX::XLCellValue&& other) noexcept = default; /** * @details Destructor. The default implementation will be used * @pre None. * @post The object is destructed. */ XLCellValue::~XLCellValue() = default; /** * @details Copy assignment operator. The default implementation will be used. * @pre The object to be copied must be a valid object. * @post A the copied-to object is valid. */ XLCellValue& OpenXLSX::XLCellValue::operator=(const OpenXLSX::XLCellValue& other) = default; /** * @details Move assignment operator. The default implementation will be used. * @pre The object to be moved must be a valid object. * @post The moved-to object is valid. */ XLCellValue& OpenXLSX::XLCellValue::operator=(OpenXLSX::XLCellValue&& other) noexcept = default; /** * @details Constructor taking a XLCellValueProxy object as argument. The object is constructed by extracting the value * from the proxy object, and constructing the XLCellValue object from that. * @pre The argument must be a valid XLCellValueProxy object, i.e. the getValue() method must return a valid value. * @post A valid XLCellValue object is created. * @todo: Can the XLCellValueProxy object be invalid? */ XLCellValue::XLCellValue(const XLCellValueProxy& proxy) : XLCellValue(proxy.getValue()) {} /** * @details Clears the contents of the XLCellValue object. Setting the value to an empty string is not sufficient * (as an empty string is still a valid string). The m_type variable must also be set to XLValueType::Empty. * @pre * @post */ XLCellValue& XLCellValue::clear() { m_type = XLValueType::Empty; m_value = ""; return *this; } /** * @details Sets the value type to XLValueType::Error. The value will be set to an empty string. * @pre * @post */ XLCellValue& XLCellValue::setError() { m_type = XLValueType::Error; m_value = ""; return *this; } /** * @details Get the value type of the current object. * @pre * @post */ XLValueType XLCellValue::type() const { return m_type; } /** * @details Get the value type of the current object, as a string representation * @pre * @post */ std::string XLCellValue::typeAsString() const { switch (type()) { case XLValueType::Empty: return "empty"; case XLValueType::Boolean: return "boolean"; case XLValueType::Integer: return "integer"; case XLValueType::Float: return "float"; case XLValueType::String: return "string"; default: return "error"; } } /** * @details Constructor * @pre The cell and cellNode pointers must not be nullptr and must point to valid objects. * @post A valid XLCellValueProxy has been created. */ XLCellValueProxy::XLCellValueProxy(XLCell* cell, XMLNode* cellNode) : m_cell(cell), m_cellNode(cellNode) { assert(cell); // NOLINT assert(cellNode); // NOLINT assert(!cellNode->empty()); // NOLINT } /** * @details Destructor. Default implementation has been used. * @pre * @post */ XLCellValueProxy::~XLCellValueProxy() = default; /** * @details Copy constructor. Default implementation has been used. * @pre * @post */ XLCellValueProxy::XLCellValueProxy(const XLCellValueProxy& other) = default; /** * @details Move constructor. Default implementation has been used. * @pre * @post */ XLCellValueProxy::XLCellValueProxy(XLCellValueProxy&& other) noexcept = default; /** * @details Copy assignment operator. The function is implemented in terms of the templated * value assignment operators, i.e. it is the XLCellValue that is that is copied, * not the object itself. * @pre * @post */ XLCellValueProxy& XLCellValueProxy::operator=(const XLCellValueProxy& other) { if (&other != this) { *this = other.getValue(); } return *this; } /** * @details Move assignment operator. Default implementation has been used. * @pre * @post */ XLCellValueProxy& XLCellValueProxy::operator=(XLCellValueProxy&& other) noexcept = default; /** * @details Implicitly convert the XLCellValueProxy object to a XLCellValue object. * @pre * @post */ XLCellValueProxy::operator XLCellValue() { return getValue(); } /** * @details Clear the contents of the cell. This removes all children of the cell node. * @pre The m_cellNode must not be null, and must point to a valid XML cell node object. * @post The cell node must be valid, but empty. */ XLCellValueProxy& XLCellValueProxy::clear() { // ===== Check that the m_cellNode is valid. assert(m_cellNode); // NOLINT assert(!m_cellNode->empty()); // NOLINT // ===== Remove the type attribute m_cellNode->remove_attribute("t"); // ===== Disable space preservation (only relevant for strings). m_cellNode->remove_attribute(" xml:space"); // ===== Remove the value node. m_cellNode->remove_child("v"); return *this; } /** * @details Set the cell value to a error state. This will remove all children and attributes, except * the type attribute, which is set to "e" * @pre The m_cellNode must not be null, and must point to a valid XML cell node object. * @post The cell node must be valid. */ XLCellValueProxy& XLCellValueProxy::setError() { // ===== Check that the m_cellNode is valid. assert(m_cellNode); // NOLINT assert(!m_cellNode->empty()); // NOLINT // ===== If the cell node doesn't have a type attribute, create it. if (!m_cellNode->attribute("t")) m_cellNode->append_attribute("t"); // ===== Set the type to "e", i.e. error m_cellNode->attribute("t").set_value("e"); // ===== Disable space preservation (only relevant for strings). m_cellNode->remove_attribute(" xml:space"); // ===== Remove the value node. m_cellNode->remove_child("v"); return *this; } /** * @details Get the value type for the cell. * @pre The m_cellNode must not be null, and must point to a valid XML cell node object. * @post No change should be made. */ XLValueType XLCellValueProxy::type() const { // ===== Check that the m_cellNode is valid. assert(m_cellNode); // NOLINT assert(!m_cellNode->empty()); // NOLINT // ===== If neither a Type attribute or a getValue node is present, the cell is empty. if (!m_cellNode->attribute("t") && !m_cellNode->child("v")) return XLValueType::Empty; // ===== If a Type attribute is not present, but a value node is, the cell contains a number. else if ((!m_cellNode->attribute("t") || (strcmp(m_cellNode->attribute("t").value(), "n") == 0 && m_cellNode->child("v") != nullptr))) { std::string numberString = m_cellNode->child("v").text().get(); if (numberString.find('.') != std::string::npos || numberString.find("E-") != std::string::npos || numberString.find("e-") != std::string::npos) return XLValueType::Float; else return XLValueType::Integer; } // ===== If the cell is of type "s", the cell contains a shared string. else if (m_cellNode->attribute("t") != nullptr && strcmp(m_cellNode->attribute("t").value(), "s") == 0) return XLValueType::String; // NOLINT // ===== If the cell is of type "inlineStr", the cell contains an inline string. else if (m_cellNode->attribute("t") != nullptr && strcmp(m_cellNode->attribute("t").value(), "inlineStr") == 0) return XLValueType::String; // ===== If the cell is of type "str", the cell contains an ordinary string. else if (m_cellNode->attribute("t") != nullptr && strcmp(m_cellNode->attribute("t").value(), "str") == 0) return XLValueType::String; // ===== If the cell is of type "b", the cell contains a boolean. else if (m_cellNode->attribute("t") != nullptr && strcmp(m_cellNode->attribute("t").value(), "b") == 0) return XLValueType::Boolean; // ===== Otherwise, the cell contains an error. else return XLValueType::Error; // the m_typeAttribute has the ValueAsString "e" } /** * @details Get the value type of the current object, as a string representation. * @pre * @post */ std::string XLCellValueProxy::typeAsString() const { switch (type()) { case XLValueType::Empty: return "empty"; case XLValueType::Boolean: return "boolean"; case XLValueType::Integer: return "integer"; case XLValueType::Float: return "float"; case XLValueType::String: return "string"; default: return "error"; } } /** * @details Set cell to an integer value. This is private helper function for setting the cell value * directly in the underlying XML file. * @pre The m_cellNode must not be null, and must point to a valid XMLNode object. * @post The underlying XMLNode has been updated correctly, representing an integer value. */ void XLCellValueProxy::setInteger(int64_t numberValue) { // ===== Check that the m_cellNode is valid. assert(m_cellNode); // NOLINT assert(!m_cellNode->empty()); // NOLINT // ===== If the cell node doesn't have a value child node, create it. if (!m_cellNode->child("v")) m_cellNode->append_child("v"); // ===== The type ("t") attribute is not required for number values. m_cellNode->remove_attribute("t"); // ===== Set the text of the value node. m_cellNode->child("v").text().set(numberValue); // ===== Disable space preservation (only relevant for strings). m_cellNode->child("v").remove_attribute(m_cellNode->child("v").attribute("xml:space")); } /** * @details Set the cell to a bool value. This is private helper function for setting the cell value * directly in the underlying XML file. * @pre The m_cellNode must not be null, and must point to a valid XMLNode object. * @post The underlying XMLNode has been updated correctly, representing an bool value. */ void XLCellValueProxy::setBoolean(bool numberValue) { // ===== Check that the m_cellNode is valid. assert(m_cellNode); // NOLINT assert(!m_cellNode->empty()); // NOLINT // ===== If the cell node doesn't have a type child node, create it. if (!m_cellNode->attribute("t")) m_cellNode->append_attribute("t"); // ===== If the cell node doesn't have a value child node, create it. if (!m_cellNode->child("v")) m_cellNode->append_child("v"); // ===== Set the type attribute. m_cellNode->attribute("t").set_value("b"); // ===== Set the text of the value node. m_cellNode->child("v").text().set(numberValue ? 1 : 0); // ===== Disable space preservation (only relevant for strings). m_cellNode->child("v").remove_attribute(m_cellNode->child("v").attribute("xml:space")); } /** * @details Set the cell to a floating point value. This is private helper function for setting the cell value * directly in the underlying XML file. * @pre The m_cellNode must not be null, and must point to a valid XMLNode object. * @post The underlying XMLNode has been updated correctly, representing a floating point value. */ void XLCellValueProxy::setFloat(double numberValue) { // ===== Check that the m_cellNode is valid. assert(m_cellNode); // NOLINT assert(!m_cellNode->empty()); // NOLINT // ===== If the cell node doesn't have a value child node, create it. if (!m_cellNode->child("v")) m_cellNode->append_child("v"); // ===== The type ("t") attribute is not required for number values. m_cellNode->remove_attribute("t"); // ===== Set the text of the value node. m_cellNode->child("v").text().set(numberValue); // ===== Disable space preservation (only relevant for strings). m_cellNode->child("v").remove_attribute(m_cellNode->child("v").attribute("xml:space")); } /** * @details Set the cell to a string value. This is private helper function for setting the cell value * directly in the underlying XML file. * @pre The m_cellNode must not be null, and must point to a valid XMLNode object. * @post The underlying XMLNode has been updated correctly, representing a string value. */ void XLCellValueProxy::setString(const char* stringValue) { // ===== Check that the m_cellNode is valid. assert(m_cellNode); // NOLINT assert(!m_cellNode->empty()); // NOLINT // ===== If the cell node doesn't have a type child node, create it. if (!m_cellNode->attribute("t")) m_cellNode->append_attribute("t"); // ===== If the cell node doesn't have a value child node, create it. if (!m_cellNode->child("v")) m_cellNode->append_child("v"); // ===== Set the type attribute. m_cellNode->attribute("t").set_value("s"); // ===== Get or create the index in the XLSharedStrings object. auto index = (m_cell->m_sharedStrings->stringExists(stringValue) ? m_cell->m_sharedStrings->getStringIndex(stringValue) : m_cell->m_sharedStrings->appendString(stringValue)); // ===== Set the text of the value node. m_cellNode->child("v").text().set(index); // IMPLEMENTATION FOR EMBEDDED STRINGS: // m_cellNode->attribute("t").set_value("str"); // m_cellNode->child("v").text().set(stringValue); // // auto s = std::string_view(stringValue); // if (s.front() == ' ' || s.back() == ' ') { // if (!m_cellNode->attribute("xml:space")) m_cellNode->append_attribute("xml:space"); // m_cellNode->attribute("xml:space").set_value("preserve"); // } } /** * @details Get a copy of the XLCellValue object for the cell. This is private helper function for returning an * XLCellValue object corresponding to the cell value. * @pre The m_cellNode must not be null, and must point to a valid XMLNode object. * @post No changes should be made. */ XLCellValue XLCellValueProxy::getValue() const { // ===== Check that the m_cellNode is valid. assert(m_cellNode); // NOLINT assert(!m_cellNode->empty()); // NOLINT switch (type()) { case XLValueType::Empty: return XLCellValue().clear(); case XLValueType::Float: return XLCellValue{ m_cellNode->child("v").text().as_double() }; case XLValueType::Integer: return XLCellValue{ m_cellNode->child("v").text().as_llong() }; case XLValueType::String: if (strcmp(m_cellNode->attribute("t").value(), "s") == 0) return XLCellValue{ m_cell->m_sharedStrings->getString(static_cast(m_cellNode->child("v").text().as_ullong())) }; else if (strcmp(m_cellNode->attribute("t").value(), "str") == 0) return XLCellValue{ m_cellNode->child("v").text().get() }; else throw XLException("Unknown string type"); case XLValueType::Boolean: return XLCellValue{ m_cellNode->child("v").text().as_bool() }; default: return XLCellValue().setError(); } }