OpenXLSX/library/sources/XLCellValue.cpp
2021-08-19 21:46:03 +02:00

459 lines
16 KiB
C++

//
// Created by Kenneth Balslev on 19/08/2020.
//
// ===== External Includes ===== //
#include <pugixml.hpp>
// ===== 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<uint32_t>(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();
}
}