Files
core/OdfFile/Reader/Converter/xlsx_data_validation.cpp
Alexander Trofimov 76ee07f61c [copyright] Update copyright header
Co-authored-by: Alexander Trofimov <alexander.trofimov@onlyoffice.com>
Co-committed-by: Alexander Trofimov <alexander.trofimov@onlyoffice.com>
2026-05-14 08:23:56 +00:00

546 lines
16 KiB
C++

/*
* Copyright (C) Ascensio System SIA, 2009-2026
*
* This program is a free software product. You can redistribute it and/or
* modify it under the terms of the GNU Affero General Public License (AGPL)
* version 3 as published by the Free Software Foundation, together with the
* additional terms provided in the LICENSE file.
*
* This program is distributed WITHOUT ANY WARRANTY; without even the implied
* warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. For
* details, see the GNU AGPL at: https://www.gnu.org/licenses/agpl-3.0.html
*
* You can contact Ascensio System SIA by email at info@onlyoffice.com
* or by postal mail at 20A-6 Ernesta Birznieka-Upisha Street, Riga,
* LV-1050, Latvia, European Union.
*
* The interactive user interfaces in modified versions of the Program
* are required to display Appropriate Legal Notices in accordance with
* Section 5 of the GNU AGPL version 3.
*
* No trademark rights are granted under this License.
*
* All non-code elements of the Product, including illustrations,
* icon sets, and technical writing content, are licensed under the
* Creative Commons Attribution-ShareAlike 4.0 International License:
* https://creativecommons.org/licenses/by-sa/4.0/legalcode
*
* This license applies only to such non-code elements and does not
* modify or replace the licensing terms applicable to the Program's
* source code, which remains licensed under the GNU Affero General
* Public License v3.
*
* SPDX-License-Identifier: AGPL-3.0-only
*/
#include "xlsx_data_validation.h"
#include "xlsx_utils.h"
#include <vector>
#include <map>
#include <xml/simple_xml_writer.h>
#include "../../Common/CPSharedPtr.h"
#include <boost/regex.hpp>
#include <boost/algorithm/string/regex.hpp>
#include "../../Formulas/formulasconvert.h"
#include "../../../OOXML/Base/Unit.h"
namespace cpdoccore {
namespace oox {
struct xlsx_dataValidation
{
std::wstring ref;
//std::wstring activate_ref;
std::map<int, std::vector<int>> mapCells;
std::wstring type;
std::wstring formula1;
std::wstring formula2;
std::wstring operator_;
bool showErrorMessage = false;
bool showInputMessage = false;
bool showDropDown = false;
bool allowBlank = false;
std::wstring error_text;
std::wstring error_title;
std::wstring promt_text;
std::wstring promt_title;
std::wstring activate_ref;
int error_type = 0;//stop
};
typedef shared_ptr<xlsx_dataValidation>::Type xlsx_dataValidation_ptr;
class xlsx_dataValidations_context::Impl
{
private:
void prepare()
{
for (std::map<std::wstring, xlsx_dataValidation_ptr>::iterator it = mapActivateDataValidations.begin();
it != mapActivateDataValidations.end(); ++it)
{
if (it->second->activate_ref.empty())
{
//prepare
std::map<int, std::vector<std::pair<int, int>>> mapCells;
for (std::map<int, std::vector<int>>::iterator jt = it->second->mapCells.begin(); jt != it->second->mapCells.end(); ++jt)
{
std::sort(jt->second.begin(), jt->second.end());
std::vector<std::pair<int, int>> arr;
MakePairVector(jt->second, arr);
mapCells.insert(std::make_pair(jt->first, arr));
}
std::wstring activate_ref;
std::map<int, std::vector<std::pair<int, int>>>::iterator jt1 = mapCells.begin();
while (jt1 != mapCells.end())
{
for (size_t i = 0; i < jt1->second.size(); i++)
{
std::map<int, std::vector<std::pair<int, int>>>::iterator jt2 = jt1;
while (jt2 != mapCells.end())
{
std::map<int, std::vector<std::pair<int, int>>>::iterator jt2_next = jt2; ++jt2_next;
if (jt2_next == mapCells.end() || jt2->first + 1 != jt2_next->first)
break;
//size_t j = 0;
bool bFound = false;
for (std::vector<std::pair<int, int>>::iterator v_jt2 = jt2_next->second.begin();
v_jt2 != jt2_next->second.end(); ++v_jt2)
{
if (jt1->second[i].first == v_jt2->first ||
jt1->second[i].second == v_jt2->second)
{
bFound = true;
jt2_next->second.erase(v_jt2);
break;
}
}
if (false == bFound)
break;
jt2 = jt2_next;
}
if (!activate_ref.empty())
{
activate_ref += L" ";
}
activate_ref += oox::getCellAddress(jt1->first, jt1->second[i].first);
if (jt1->first != jt2->first || jt1->second[i].first != jt1->second[i].second)
activate_ref += L":" + oox::getCellAddress(jt2->first, jt1->second[i].second);
}
++jt1;
}
it->second->activate_ref = activate_ref;
}
}
}
public:
void serialize(std::wostream & _Wostream)
{
return;
if (mapActivateDataValidations.empty()) return;
prepare();
CP_XML_WRITER(_Wostream)
{
CP_XML_NODE(L"dataValidations")
{
CP_XML_ATTR(L"count", mapActivateDataValidations.size());
for (std::map<std::wstring, xlsx_dataValidation_ptr>::iterator it = mapActivateDataValidations.begin();
it != mapActivateDataValidations.end(); ++it)
{
if (std::wstring::npos != it->second->activate_ref.find(L" ")) continue;
CP_XML_NODE(L"dataValidation")
{
CP_XML_ATTR(L"sqref", it->second->activate_ref);
CP_XML_ATTR(L"allowBlank", it->second->allowBlank);
if (!it->second->operator_.empty())
{
CP_XML_ATTR(L"operator", it->second->operator_);
}
CP_XML_ATTR(L"showDropDown", it->second->showDropDown);
CP_XML_ATTR(L"showErrorMessage", it->second->showErrorMessage);
CP_XML_ATTR(L"showInputMessage", it->second->showInputMessage);
if (!it->second->type.empty())
{
CP_XML_ATTR(L"type", it->second->type);
}
if (!it->second->error_title.empty())
{
CP_XML_ATTR(L"errorTitle", it->second->error_title);
}
if (!it->second->error_text.empty())
{
CP_XML_ATTR(L"error", it->second->error_text);
}
if (!it->second->promt_title.empty())
{
CP_XML_ATTR(L"promptTitle", it->second->promt_title);
}
if (!it->second->promt_text.empty())
{
CP_XML_ATTR(L"prompt", it->second->promt_text);
}
if (!it->second->formula1.empty())
{
CP_XML_NODE(L"formula1")
{
CP_XML_STREAM() << it->second->formula1;
}
}
if (!it->second->formula2.empty())
{
CP_XML_NODE(L"formula2")
{
CP_XML_STREAM() << it->second->formula2;
}
}
}
}
}
}
}
void serialize_x14(std::wostream & _Wostream)
{
if (mapActivateDataValidations.empty()) return;
prepare();
CP_XML_WRITER(_Wostream)
{
CP_XML_NODE(L"x14:dataValidations")
{
CP_XML_ATTR(L"count", mapActivateDataValidations.size());
CP_XML_ATTR(L"xmlns:xm", L"http://schemas.microsoft.com/office/excel/2006/main");
for (std::map<std::wstring, xlsx_dataValidation_ptr>::iterator it = mapActivateDataValidations.begin();
it != mapActivateDataValidations.end(); ++it)
{
CP_XML_NODE(L"x14:dataValidation")
{
CP_XML_ATTR(L"allowBlank", it->second->allowBlank);
if (!it->second->operator_.empty())
{
CP_XML_ATTR(L"operator", it->second->operator_);
}
CP_XML_ATTR(L"showDropDown", it->second->showDropDown);
CP_XML_ATTR(L"showErrorMessage", it->second->showErrorMessage);
CP_XML_ATTR(L"showInputMessage", it->second->showInputMessage);
if (!it->second->type.empty())
{
CP_XML_ATTR(L"type", it->second->type);
}
if (!it->second->error_title.empty())
{
CP_XML_ATTR(L"errorTitle", it->second->error_title);
}
if (!it->second->error_text.empty())
{
CP_XML_ATTR(L"error", it->second->error_text);
}
switch (it->second->error_type)
{
case 1: CP_XML_ATTR(L"errorStyle", L"warning"); break;
case 2: CP_XML_ATTR(L"errorStyle", L"information"); break;
case 0:
default:
CP_XML_ATTR(L"errorStyle", L"stop"); break;
}
if (!it->second->promt_title.empty())
{
CP_XML_ATTR(L"promptTitle", it->second->promt_title);
}
if (!it->second->promt_text.empty())
{
CP_XML_ATTR(L"prompt", it->second->promt_text);
}
if (!it->second->formula1.empty())
{
CP_XML_NODE(L"x14:formula1")
{
CP_XML_NODE(L"xm:f")
{
CP_XML_STREAM() << XmlUtils::EncodeXmlString(it->second->formula1);
}
}
}
if (!it->second->formula2.empty())
{
CP_XML_NODE(L"x14:formula2")
{
CP_XML_NODE(L"xm:f")
{
CP_XML_STREAM() << XmlUtils::EncodeXmlString(it->second->formula2);
}
}
}
CP_XML_NODE(L"xm:sqref")
{
CP_XML_STREAM() << it->second->activate_ref;
}
}
}
}
}
}
std::map<std::wstring, xlsx_dataValidation_ptr> mapDataValidations; //for all tables
std::map<std::wstring, xlsx_dataValidation_ptr> mapActivateDataValidations; //in current table
private:
void MakePairVector(std::vector<int> & arInput, std::vector<std::pair<int, int>> &arOutput)
{
int pos = arInput[0];
for (size_t i = 1; i < arInput.size(); i++)
{
if (arInput[i-1] + 1 != arInput[i])
{
arOutput.push_back(std::make_pair(pos, arInput[i - 1]));
pos = arInput[i];
}
}
arOutput.push_back(std::make_pair(pos, arInput[arInput.size() - 1]));
}
};
xlsx_dataValidations_context::xlsx_dataValidations_context() :
impl_(new xlsx_dataValidations_context::Impl())
{}
xlsx_dataValidations_context::~xlsx_dataValidations_context()
{
}
void xlsx_dataValidations_context::clear()
{
impl_->mapActivateDataValidations.clear();
}
void xlsx_dataValidations_context::serialize(std::wostream & _Wostream)
{
return impl_->serialize(_Wostream);
}
void xlsx_dataValidations_context::serialize_x14(std::wostream & _Wostream)
{
return impl_->serialize_x14(_Wostream);
}
void xlsx_dataValidations_context::activate(const std::wstring & name, int col, int row/*const std::wstring & ref*/)
{
std::map<std::wstring, xlsx_dataValidation_ptr>::iterator pFindActivate = impl_->mapActivateDataValidations.find(name);
if (pFindActivate == impl_->mapActivateDataValidations.end())
{
std::map<std::wstring, xlsx_dataValidation_ptr>::iterator pFind = impl_->mapDataValidations.find(name);
xlsx_dataValidation_ptr _new;
if (pFind != impl_->mapDataValidations.end())
{
_new = pFind->second;
}
else
{
_new = xlsx_dataValidation_ptr(new xlsx_dataValidation);
}
impl_->mapActivateDataValidations.insert(std::make_pair(name, _new));
pFindActivate = impl_->mapActivateDataValidations.find(name);
}
std::map<int, std::vector<int>>::iterator pFindCol = pFindActivate->second->mapCells.find(col);
if (pFindCol == pFindActivate->second->mapCells.end())
{
std::vector<int> rows; rows.push_back(row);
pFindActivate->second->mapCells.insert(std::make_pair(col, rows));
}
else
{
pFindCol->second.push_back(row);
}
}
void xlsx_dataValidations_context::add(const std::wstring & name, const std::wstring &ref)
{
xlsx_dataValidation_ptr _new = xlsx_dataValidation_ptr(new xlsx_dataValidation);
formulasconvert::odf2oox_converter converter;
_new->ref = converter.convert_named_ref(ref, false, L";");
impl_->mapDataValidations.insert(std::make_pair(name, _new));
}
void xlsx_dataValidations_context::add_help_msg(const std::wstring & name, const std::wstring & title, const std::wstring & content, bool display)
{
std::map<std::wstring, xlsx_dataValidation_ptr>::iterator pFind = impl_->mapDataValidations.find(name);
if (pFind == impl_->mapDataValidations.end()) return;
pFind->second->showInputMessage = display;
pFind->second->promt_title = title;
pFind->second->promt_text = content;
}
void xlsx_dataValidations_context::add_error_msg(const std::wstring & name, const std::wstring & title, const std::wstring & content, bool display, int type)
{
std::map<std::wstring, xlsx_dataValidation_ptr>::iterator pFind = impl_->mapDataValidations.find(name);
if (pFind == impl_->mapDataValidations.end()) return;
pFind->second->showErrorMessage = display;
pFind->second->error_title = title;
pFind->second->error_text = content;
pFind->second->error_type = type;
}
void xlsx_dataValidations_context::add_formula(const std::wstring & name, const std::wstring & formula)
{
std::map<std::wstring, xlsx_dataValidation_ptr>::iterator pFind = impl_->mapDataValidations.find(name);
if (pFind == impl_->mapDataValidations.end()) return;
formulasconvert::odf2oox_converter converter;
std::vector<std::wstring> arrFormula;
boost::algorithm::split_regex(arrFormula, formula, boost::wregex(L"and"));
std::wstring val, val2, operator_;
for (size_t i = 0; i < arrFormula.size(); ++i)
{
std::wstring & f = arrFormula[i];
size_t pos = std::wstring::npos;
if (std::wstring::npos != (pos = f.find(L"cell-content-is-in-list"))) //oooc: , of:
{
pFind->second->type = L"list";
val = f.substr(24 + pos, f.size() - 25 - pos);
if (!val.empty() && val.substr(0, 1) == L"\"")
{//list values
std::wstring sList = converter.convert_list_values(val);
if (255 < sList.size())
sList = sList.substr(0, 255);
pFind->second->formula1 = L"\"" + sList + L"\"";
}
else
{//formula
pFind->second->formula1 = converter.convert(val);
}
}
else if (std::wstring::npos != (pos = f.find(L"is-true-formula")))
{
pFind->second->type = L"custom";
val = f.substr(16 + pos, f.size() - 17 - pos);
pFind->second->formula1 = converter.convert(val);
}
else if (std::wstring::npos != (pos = f.find(L"cell-content-is-date")))
{
pFind->second->type = L"date";
}
else if (std::wstring::npos != (pos = f.find(L"cell-content-is-decimal-number")))
{
pFind->second->type = L"decimal";
}
else if (std::wstring::npos != (pos = f.find(L"cell-content-is-time")))
{
pFind->second->type = L"time";
}
else if (std::wstring::npos != (pos = f.find(L"cell-content-is-whole-number")))
{
pFind->second->type = L"whole";
}
else if (std::wstring::npos != (pos = f.find(L"cell-content-text-length")))
{
pFind->second->type = L"textLength";
operator_ = f.substr(pos + 24);
}
else if (std::wstring::npos != (pos = f.find(L"cell-content")))
{
operator_ = f.substr(pos + 12);
}
}
if (false == operator_.empty())
{
size_t pos;
if (std::wstring::npos != (pos = operator_.find(L"-is-not-between(")))
{
pFind->second->operator_ = L"notBetween";
size_t pos2 = operator_.find(L",", pos + 15);
size_t pos3 = operator_.rfind(L")");
val = operator_.substr(pos + 15, pos2 - pos - 15);
val2 = operator_.substr(pos2 + 1, pos3 - pos2 - 1);
}
else if (std::wstring::npos != (pos = operator_.find(L"-is-between(")))
{
pFind->second->operator_ = L"between";
size_t pos2 = operator_.find(L",", pos + 12);
size_t pos3 = operator_.rfind(L")");
val = operator_.substr(pos + 12, pos2 - pos - 12);
val2 = operator_.substr(pos2 + 1, pos3 - pos2 - 1);
}
else
{
size_t split = std::wstring::npos;
if (std::wstring::npos != (pos = operator_.find(L"()==")))
{
pFind->second->operator_ = L"equal";
split = 4;
}
else if (std::wstring::npos != (pos = operator_.find(L"()!=")))
{
pFind->second->operator_ = L"notEqual";
split = 4;
}
else if (std::wstring::npos != (pos = operator_.find(L"()<=")))
{
pFind->second->operator_ = L"lessThanOrEqual";
split = 4;
}
else if (std::wstring::npos != (pos = operator_.find(L"()<")))
{
pFind->second->operator_ = L"lessThan";
split = 3;
}
else if (std::wstring::npos != (pos = operator_.find(L"()>=")))
{
pFind->second->operator_ = L"greaterThanOrEqual";
split = 4;
}
else if (std::wstring::npos != (pos = operator_.find(L"()>")))
{
pFind->second->operator_ = L"greaterThan";
split = 3;
}
if (split != std::wstring::npos)
{
val = operator_.substr(pos + split, operator_.length() - pos - split);
}
}
pFind->second->formula1 = converter.convert(val);
pFind->second->formula2 = converter.convert(val2);
}
}
}
}