From 65d4fc34346be5ad521c89ae76c498b24316e558 Mon Sep 17 00:00:00 2001 From: Julius Knorr Date: Fri, 5 Jun 2026 22:31:32 +0200 Subject: [PATCH] feat(xlsx): support Excel cell checkboxes in conversion Resolve the featurePropertyBag xfComplement chain into a per-xf checkbox flag on read, carry it through Editor.bin (c_oSerXfsTypes::CellControl), and regenerate the canonical featurePropertyBag part plus xf extLst on write so checkbox cells round-trip xlsx <-> bin. AI-assistant: Claude Code 2.1.163 (claude-opus-4-8) Signed-off-by: Julius Knorr --- .../Sheets/Common/BinReaderWriterDefines.h | 3 +- OOXML/Binary/Sheets/Reader/BinaryWriterS.cpp | 35 +++ OOXML/Binary/Sheets/Writer/BinaryReaderS.cpp | 37 ++++ .../Linux/DocxFormatLib/CMakeLists.txt | 2 + .../Linux/DocxFormatLib/DocxFormatLib.pro | 2 + .../DocxFormatLib/DocxFormatLib.vcxproj | 2 + OOXML/XlsxFormat/FileFactory_Spreadsheet.cpp | 5 + OOXML/XlsxFormat/FileTypes_Spreadsheet.cpp | 4 + OOXML/XlsxFormat/FileTypes_Spreadsheet.h | 2 + OOXML/XlsxFormat/Styles/Xfs.cpp | 46 +++- OOXML/XlsxFormat/Styles/Xfs.h | 7 +- .../Workbook/FeaturePropertyBag.cpp | 199 ++++++++++++++++++ .../XlsxFormat/Workbook/FeaturePropertyBag.h | 68 ++++++ 13 files changed, 408 insertions(+), 4 deletions(-) create mode 100644 OOXML/XlsxFormat/Workbook/FeaturePropertyBag.cpp create mode 100644 OOXML/XlsxFormat/Workbook/FeaturePropertyBag.h diff --git a/OOXML/Binary/Sheets/Common/BinReaderWriterDefines.h b/OOXML/Binary/Sheets/Common/BinReaderWriterDefines.h index 40589bd693..006a392bad 100644 --- a/OOXML/Binary/Sheets/Common/BinReaderWriterDefines.h +++ b/OOXML/Binary/Sheets/Common/BinReaderWriterDefines.h @@ -137,7 +137,8 @@ namespace BinXlsxRW QuotePrefix = 11, XfId = 12, Aligment = 13, - Protection = 14 + Protection = 14, + CellControl = 15 };} namespace c_oSerProtectionTypes {enum c_oSerProtectionTypes { diff --git a/OOXML/Binary/Sheets/Reader/BinaryWriterS.cpp b/OOXML/Binary/Sheets/Reader/BinaryWriterS.cpp index 4889d983af..930c3de6fd 100644 --- a/OOXML/Binary/Sheets/Reader/BinaryWriterS.cpp +++ b/OOXML/Binary/Sheets/Reader/BinaryWriterS.cpp @@ -65,6 +65,7 @@ #include "../../../XlsxFormat/Styles/TableStyles.h" #include "../../../XlsxFormat/Timelines/Timeline.h" #include "../../../XlsxFormat/Workbook/Metadata.h" +#include "../../../XlsxFormat/Workbook/FeaturePropertyBag.h" #include "../../../XlsxFormat/Table/Table.h" #include "../../../XlsxFormat/Workbook/CustomsXml.h" #include "../../../XlsxFormat/RichData/RdRichData.h" @@ -1446,6 +1447,13 @@ void BinaryStyleTableWriter::WriteXfs(const OOX::Spreadsheet::CXfs& xfs) m_oBcw.m_oStream.WriteBYTE(c_oSerPropLenType::Byte); m_oBcw.m_oStream.WriteBOOL(xfs.m_oPivotButton->ToBool()); } + //CellControl (checkbox) + if (false != xfs.m_oCellControl.IsInit()) + { + m_oBcw.m_oStream.WriteBYTE(c_oSerXfsTypes::CellControl); + m_oBcw.m_oStream.WriteBYTE(c_oSerPropLenType::Byte); + m_oBcw.m_oStream.WriteBOOL(*xfs.m_oCellControl); + } //XfId if (false != xfs.m_oXfId.IsInit()) { @@ -9336,6 +9344,33 @@ void BinaryFileWriter::WriteContent(OOX::Document *pDocument, NSFontCutter::CEmb //Styles if(pStyles) { + if (pWorkbook) + { + //resolve xf checkbox controls via the workbook featurePropertyBag part + smart_ptr pFile = pWorkbook->Find(OOX::Spreadsheet::FileTypes::FeaturePropertyBag); + OOX::Spreadsheet::CFeaturePropertyBagFile* pBagFile = dynamic_cast(pFile.GetPointer()); + if (pBagFile) + { + if (pStyles->m_oCellXfs.IsInit()) + { + for (size_t i = 0; i < pStyles->m_oCellXfs->m_arrItems.size(); ++i) + { + OOX::Spreadsheet::CXfs* pXfs = pStyles->m_oCellXfs->m_arrItems[i]; + if ((pXfs) && (pXfs->m_oXfComplementIndex.IsInit())) + pXfs->m_oCellControl = pBagFile->IsCheckboxComplement(*pXfs->m_oXfComplementIndex); + } + } + if (pStyles->m_oCellStyleXfs.IsInit()) + { + for (size_t i = 0; i < pStyles->m_oCellStyleXfs->m_arrItems.size(); ++i) + { + OOX::Spreadsheet::CXfs* pXfs = pStyles->m_oCellStyleXfs->m_arrItems[i]; + if ((pXfs) && (pXfs->m_oXfComplementIndex.IsInit())) + pXfs->m_oCellControl = pBagFile->IsCheckboxComplement(*pXfs->m_oXfComplementIndex); + } + } + } + } nCurPos = WriteTableStart(c_oSerTableTypes::Styles); BinaryStyleTableWriter oBinaryStyleTableWriter(m_oBcw->m_oStream, pEmbeddedFontsManager); oBinaryStyleTableWriter.Write(*pStyles, pXlsx ? pXlsx->GetTheme() : NULL, m_oFontProcessor); diff --git a/OOXML/Binary/Sheets/Writer/BinaryReaderS.cpp b/OOXML/Binary/Sheets/Writer/BinaryReaderS.cpp index cdd28f8c58..41b073a05f 100644 --- a/OOXML/Binary/Sheets/Writer/BinaryReaderS.cpp +++ b/OOXML/Binary/Sheets/Writer/BinaryReaderS.cpp @@ -65,6 +65,7 @@ #include "../../../XlsxFormat/Controls/Controls.h" #include "../../../XlsxFormat/Timelines/Timeline.h" #include "../../../XlsxFormat/Workbook/Metadata.h" +#include "../../../XlsxFormat/Workbook/FeaturePropertyBag.h" #include "../../../XlsxFormat/Workbook/CustomsXml.h" #include "../../../XlsxFormat/RichData/RdRichData.h" @@ -1972,6 +1973,10 @@ int BinaryStyleTableReader::ReadXfs(BYTE type, long length, void* poResult) pXfs->m_oXfId.Init(); pXfs->m_oXfId->SetValue(m_oBufferedStream.GetLong()); } + else if (c_oSerXfsTypes::CellControl == type) + { + pXfs->m_oCellControl = m_oBufferedStream.GetBool(); + } else res = c_oSerConstants::ReadUnknown; return res; @@ -9932,6 +9937,38 @@ int BinaryFileReader::ReadMainTable(OOX::Spreadsheet::CXlsx& oXlsx, NSBinPptxRW: if (c_oSerConstants::ReadOk != res) return res; } + OOX::Spreadsheet::CXlsb* pXlsb = dynamic_cast(&oXlsx); + if (oXlsx.m_pStyles && oXlsx.m_pWorkbook && (!pXlsb || !pXlsb->m_bWriteToXlsb)) + { + //checkbox xfs reference the canonical featurePropertyBag part - create it on demand + bool bHasCellControl = false; + if (oXlsx.m_pStyles->m_oCellXfs.IsInit()) + { + for (size_t i = 0; !bHasCellControl && i < oXlsx.m_pStyles->m_oCellXfs->m_arrItems.size(); ++i) + { + OOX::Spreadsheet::CXfs* pXfs = oXlsx.m_pStyles->m_oCellXfs->m_arrItems[i]; + if ((pXfs) && (pXfs->m_oCellControl.IsInit()) && (*pXfs->m_oCellControl)) + bHasCellControl = true; + } + } + if (oXlsx.m_pStyles->m_oCellStyleXfs.IsInit()) + { + for (size_t i = 0; !bHasCellControl && i < oXlsx.m_pStyles->m_oCellStyleXfs->m_arrItems.size(); ++i) + { + OOX::Spreadsheet::CXfs* pXfs = oXlsx.m_pStyles->m_oCellStyleXfs->m_arrItems[i]; + if ((pXfs) && (pXfs->m_oCellControl.IsInit()) && (*pXfs->m_oCellControl)) + bHasCellControl = true; + } + } + if (bHasCellControl) + { + smart_ptr oBagFile(new OOX::Spreadsheet::CFeaturePropertyBagFile(NULL)); + oBagFile->OOX::File::m_pMainDocument = oXlsx.m_pWorkbook->OOX::File::m_pMainDocument; + + smart_ptr oFile = oBagFile.smart_dynamic_cast(); + oXlsx.m_pWorkbook->Add(oFile); + } + } for (boost::unordered_map::const_iterator pPair = mapMedia.begin(); pPair != mapMedia.end(); ++pPair) { delete pPair->second; diff --git a/OOXML/Projects/Linux/DocxFormatLib/CMakeLists.txt b/OOXML/Projects/Linux/DocxFormatLib/CMakeLists.txt index a766e424e7..df66446258 100644 --- a/OOXML/Projects/Linux/DocxFormatLib/CMakeLists.txt +++ b/OOXML/Projects/Linux/DocxFormatLib/CMakeLists.txt @@ -168,6 +168,7 @@ add_library(DocxFormatLib STATIC ${OOXML_ROOT_DIR}/XlsxFormat/Workbook/CustomsXml.cpp ${OOXML_ROOT_DIR}/XlsxFormat/Workbook/DefinedNames.cpp ${OOXML_ROOT_DIR}/XlsxFormat/Workbook/ExternalReferences.cpp + ${OOXML_ROOT_DIR}/XlsxFormat/Workbook/FeaturePropertyBag.cpp ${OOXML_ROOT_DIR}/XlsxFormat/Workbook/Metadata.cpp ${OOXML_ROOT_DIR}/XlsxFormat/Workbook/Sheets.cpp ${OOXML_ROOT_DIR}/XlsxFormat/Workbook/Workbook.cpp @@ -359,6 +360,7 @@ add_library(DocxFormatLib STATIC ${OOXML_ROOT_DIR}/XlsxFormat/Workbook/CustomsXml.h ${OOXML_ROOT_DIR}/XlsxFormat/Workbook/DefinedNames.h ${OOXML_ROOT_DIR}/XlsxFormat/Workbook/ExternalReferences.h + ${OOXML_ROOT_DIR}/XlsxFormat/Workbook/FeaturePropertyBag.h ${OOXML_ROOT_DIR}/XlsxFormat/Workbook/Metadata.h ${OOXML_ROOT_DIR}/XlsxFormat/Workbook/Sheets.h ${OOXML_ROOT_DIR}/XlsxFormat/Workbook/Workbook.h diff --git a/OOXML/Projects/Linux/DocxFormatLib/DocxFormatLib.pro b/OOXML/Projects/Linux/DocxFormatLib/DocxFormatLib.pro index dbd6222ff3..2bad62df02 100644 --- a/OOXML/Projects/Linux/DocxFormatLib/DocxFormatLib.pro +++ b/OOXML/Projects/Linux/DocxFormatLib/DocxFormatLib.pro @@ -187,6 +187,7 @@ SOURCES += \ ../../../XlsxFormat/Drawing/Pos.cpp \ ../../../XlsxFormat/ExternalLinks/ExternalLinkPath.cpp \ ../../../XlsxFormat/ExternalLinks/ExternalLinks.cpp \ + ../../../XlsxFormat/Workbook/FeaturePropertyBag.cpp \ ../../../XlsxFormat/Workbook/Metadata.cpp \ ../../../XlsxFormat/RichData/RdRichData.cpp \ ../../../XlsxFormat/Ole/OleObjects.cpp \ @@ -393,6 +394,7 @@ HEADERS += \ ../../../XlsxFormat/Slicer/SlicerCacheExt.h \ ../../../XlsxFormat/Slicer/Slicer.h \ ../../../XlsxFormat/NamedSheetViews/NamedSheetViews.h \ + ../../../XlsxFormat/Workbook/FeaturePropertyBag.h \ ../../../XlsxFormat/Workbook/Metadata.h \ ../../../XlsxFormat/RichData/RdRichValue.h \ ../../../VsdxFormat/Vsdx.h \ diff --git a/OOXML/Projects/Windows/DocxFormatLib/DocxFormatLib.vcxproj b/OOXML/Projects/Windows/DocxFormatLib/DocxFormatLib.vcxproj index 590c28f5d9..259ceaf464 100644 --- a/OOXML/Projects/Windows/DocxFormatLib/DocxFormatLib.vcxproj +++ b/OOXML/Projects/Windows/DocxFormatLib/DocxFormatLib.vcxproj @@ -396,6 +396,7 @@ + @@ -567,6 +568,7 @@ + diff --git a/OOXML/XlsxFormat/FileFactory_Spreadsheet.cpp b/OOXML/XlsxFormat/FileFactory_Spreadsheet.cpp index 9436504d95..04a8fcb368 100644 --- a/OOXML/XlsxFormat/FileFactory_Spreadsheet.cpp +++ b/OOXML/XlsxFormat/FileFactory_Spreadsheet.cpp @@ -54,6 +54,7 @@ #include "Timelines/Timeline.h" #include "RichData/RdRichData.h" #include "Workbook/Metadata.h" +#include "Workbook/FeaturePropertyBag.h" #include "Table/Table.h" #include "Table/QueryTable.h" @@ -183,6 +184,8 @@ namespace OOX return smart_ptr(new CTimelineCacheFile(pMain, oRootPath, oFileName)); else if (oRelation.Type() == FileTypes::Metadata) return smart_ptr(new CMetadataFile(pMain, oRootPath, oFileName)); + else if (oRelation.Type() == FileTypes::FeaturePropertyBag) + return smart_ptr(new CFeaturePropertyBagFile(pMain, oRootPath, oFileName)); else if (oRelation.Type() == FileTypes::RdRichValueStructure) return smart_ptr(new CRdRichValueStructureFile(pMain, oRootPath, oFileName)); else if (oRelation.Type() == FileTypes::RdRichValue) @@ -325,6 +328,8 @@ namespace OOX return smart_ptr(new CTimelineCacheFile(pMain, oRootPath, oFileName)); else if (pRelation->Type() == FileTypes::Metadata) return smart_ptr(new CMetadataFile(pMain, oRootPath, oFileName)); + else if (pRelation->Type() == FileTypes::FeaturePropertyBag) + return smart_ptr(new CFeaturePropertyBagFile(pMain, oRootPath, oFileName)); else if (pRelation->Type() == FileTypes::RdRichValueStructure) return smart_ptr(new CRdRichValueStructureFile(pMain, oRootPath, oFileName)); else if (pRelation->Type() == FileTypes::RdRichValue) diff --git a/OOXML/XlsxFormat/FileTypes_Spreadsheet.cpp b/OOXML/XlsxFormat/FileTypes_Spreadsheet.cpp index 25f335b7d3..adb1b10bdc 100644 --- a/OOXML/XlsxFormat/FileTypes_Spreadsheet.cpp +++ b/OOXML/XlsxFormat/FileTypes_Spreadsheet.cpp @@ -161,6 +161,10 @@ namespace OOX L"application/vnd.openxmlformats-officedocument.spreadsheetml.sheetMetadata+xml", L"http://schemas.openxmlformats.org/officeDocument/2006/relationships/sheetMetadata"); + const FileType FeaturePropertyBag(L"featurePropertyBag", L"featurePropertyBag.xml", + L"application/vnd.ms-excel.featurepropertybag+xml", + L"http://schemas.microsoft.com/office/2022/11/relationships/FeaturePropertyBag"); + const FileType RdRichValue (L"richData", L"rdrichvalue.xml", L"application/vnd.ms-excel.rdrichvalue+xml", diff --git a/OOXML/XlsxFormat/FileTypes_Spreadsheet.h b/OOXML/XlsxFormat/FileTypes_Spreadsheet.h index 58609816ee..179a439916 100644 --- a/OOXML/XlsxFormat/FileTypes_Spreadsheet.h +++ b/OOXML/XlsxFormat/FileTypes_Spreadsheet.h @@ -90,6 +90,8 @@ namespace OOX extern const FileType Metadata; + extern const FileType FeaturePropertyBag; + extern const FileType RdRichValue; extern const FileType RdRichValueStructure; diff --git a/OOXML/XlsxFormat/Styles/Xfs.cpp b/OOXML/XlsxFormat/Styles/Xfs.cpp index 718ba74311..467a47ce56 100644 --- a/OOXML/XlsxFormat/Styles/Xfs.cpp +++ b/OOXML/XlsxFormat/Styles/Xfs.cpp @@ -351,12 +351,20 @@ namespace OOX WritingStringNullableAttrBool(L"applyProtection", m_oApplyProtection); WritingStringNullableAttrBool(L"quotePrefix", m_oQuotePrefix); WritingStringNullableAttrBool(L"pivotButton", m_oPivotButton); - if (m_oAligment.IsInit() || m_oProtection.IsInit()) + bool bCellControl = m_oCellControl.IsInit() ? *m_oCellControl : m_oXfComplementIndex.IsInit(); + if (m_oAligment.IsInit() || m_oProtection.IsInit() || bCellControl) { writer.WriteString(_T(">")); if (m_oAligment.IsInit())m_oAligment->toXML(writer); if (m_oProtection.IsInit())m_oProtection->toXML(writer); + if (bCellControl) + { + // references the canonical checkbox chain in featurePropertyBag.xml + writer.WriteString(L"\ +"); + } writer.WriteString(_T("")); } @@ -378,6 +386,42 @@ namespace OOX m_oAligment = oReader; else if( _T("protection") == sName ) m_oProtection = oReader; + else if( _T("extLst") == sName ) + ReadExtLst(oReader); + } + } + void CXfs::ReadExtLst(XmlUtils::CXmlLiteReader& oReader) + { + if (oReader.IsEmptyNode()) + return; + + int nExtLstDepth = oReader.GetDepth(); + while (oReader.ReadNextSiblingNode(nExtLstDepth)) + { + if (L"ext" != XmlUtils::GetNameNoNS(oReader.GetName()) || oReader.IsEmptyNode()) + continue; + + int nExtDepth = oReader.GetDepth(); + while (oReader.ReadNextSiblingNode(nExtDepth)) + { + if (L"xfComplement" == XmlUtils::GetNameNoNS(oReader.GetName())) + { + if (oReader.MoveToFirstAttribute()) + { + std::wstring wsName = oReader.GetName(); + while (!wsName.empty()) + { + if (L"i" == wsName) + m_oXfComplementIndex = XmlUtils::GetInteger(oReader.GetText()); + + if (!oReader.MoveToNextAttribute()) + break; + wsName = oReader.GetName(); + } + oReader.MoveToElement(); + } + } + } } } EElementType CXfs::getType () const diff --git a/OOXML/XlsxFormat/Styles/Xfs.h b/OOXML/XlsxFormat/Styles/Xfs.h index e7dc059c62..12bd45a222 100644 --- a/OOXML/XlsxFormat/Styles/Xfs.h +++ b/OOXML/XlsxFormat/Styles/Xfs.h @@ -112,8 +112,6 @@ namespace OOX nullable m_oLocked; }; - //нереализован: - // class CXfs : public WritingElement { public: @@ -137,6 +135,7 @@ namespace OOX private: void ReadAttributes(XmlUtils::CXmlLiteReader& oReader); void ReadAttributes(XLS::BaseObjectPtr& obj); + void ReadExtLst(XmlUtils::CXmlLiteReader& oReader); public: nullable m_oApplyAlignment; @@ -157,6 +156,10 @@ namespace OOX nullable m_oAligment; nullable m_oProtection; + // cell checkbox (resolved from the featurePropertyBag xfComplement extension) + nullable_bool m_oCellControl; + // raw index, resolved via CFeaturePropertyBagFile + nullable_int m_oXfComplementIndex; }; class CCellXfs : public WritingElementWithChilds diff --git a/OOXML/XlsxFormat/Workbook/FeaturePropertyBag.cpp b/OOXML/XlsxFormat/Workbook/FeaturePropertyBag.cpp new file mode 100644 index 0000000000..a479fd5b7d --- /dev/null +++ b/OOXML/XlsxFormat/Workbook/FeaturePropertyBag.cpp @@ -0,0 +1,199 @@ +/* + * (c) Copyright Ascensio System SIA 2010-2023 + * + * 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. In accordance with + * Section 7(a) of the GNU AGPL its Section 15 shall be amended to the effect + * that Ascensio System SIA expressly excludes the warranty of non-infringement + * of any third-party rights. + * + * 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: http://www.gnu.org/licenses/agpl-3.0.html + * + * The interactive user interfaces in modified source and object code versions + * of the Program must display Appropriate Legal Notices, as required under + * Section 5 of the GNU AGPL version 3. + * + * All the Product's GUI elements, including illustrations and icon sets, as + * well as technical writing content are licensed under the terms of the + * Creative Commons Attribution-ShareAlike 4.0 International. See the License + * terms at http://creativecommons.org/licenses/by-sa/4.0/legalcode + * + */ +#include "FeaturePropertyBag.h" + +#include "../FileTypes_Spreadsheet.h" + +#include "../../../DesktopEditor/common/File.h" +#include "../../../DesktopEditor/xml/include/xmlutils.h" +#include "../../Base/Unit.h" + +namespace OOX +{ + namespace Spreadsheet + { + CFeaturePropertyBagFile::CFeaturePropertyBagFile(OOX::Document* pMain) : OOX::File(pMain) + { + } + CFeaturePropertyBagFile::CFeaturePropertyBagFile(OOX::Document* pMain, const CPath& oRootPath, const CPath& oPath) : OOX::File(pMain) + { + read(oRootPath, oPath); + } + CFeaturePropertyBagFile::~CFeaturePropertyBagFile() + { + } + void CFeaturePropertyBagFile::read(const CPath& oPath) + { + CPath oRootPath; + read(oRootPath, oPath); + } + const OOX::FileType CFeaturePropertyBagFile::type() const + { + return OOX::Spreadsheet::FileTypes::FeaturePropertyBag; + } + const CPath CFeaturePropertyBagFile::DefaultDirectory() const + { + return type().DefaultDirectory(); + } + const CPath CFeaturePropertyBagFile::DefaultFileName() const + { + return type().DefaultFileName(); + } + void CFeaturePropertyBagFile::read(const CPath& oRootPath, const CPath& oPath) + { + XmlUtils::CXmlLiteReader oReader; + + if (!oReader.FromFile(oPath.GetPath())) + return; + if (!oReader.ReadNextNode()) + return; + if (L"FeaturePropertyBags" != XmlUtils::GetNameNoNS(oReader.GetName())) + return; + if (oReader.IsEmptyNode()) + return; + + int nBagsDepth = oReader.GetDepth(); + while (oReader.ReadNextSiblingNode(nBagsDepth)) + { + if (L"bag" != XmlUtils::GetNameNoNS(oReader.GetName())) + continue; + + Bag oBag; + if (oReader.MoveToFirstAttribute()) + { + std::wstring wsName = oReader.GetName(); + while (!wsName.empty()) + { + if (L"type" == wsName) + oBag.sType = oReader.GetText(); + + if (!oReader.MoveToNextAttribute()) + break; + wsName = oReader.GetName(); + } + oReader.MoveToElement(); + } + if (false == oReader.IsEmptyNode()) + { + int nBagDepth = oReader.GetDepth(); + while (oReader.ReadNextSiblingNode(nBagDepth)) + { + std::wstring sName = XmlUtils::GetNameNoNS(oReader.GetName()); + if (L"bagId" == sName) + { + std::wstring sKey; + if (oReader.MoveToFirstAttribute()) + { + std::wstring wsName = oReader.GetName(); + while (!wsName.empty()) + { + if (L"k" == wsName) + sKey = oReader.GetText(); + + if (!oReader.MoveToNextAttribute()) + break; + wsName = oReader.GetName(); + } + oReader.MoveToElement(); + } + oBag.mapBagIds[sKey] = XmlUtils::GetInteger(oReader.GetText3()); + } + else if (L"a" == sName) + { + if (oReader.IsEmptyNode()) + continue; + + int nArrayDepth = oReader.GetDepth(); + while (oReader.ReadNextSiblingNode(nArrayDepth)) + { + if (L"bagId" == XmlUtils::GetNameNoNS(oReader.GetName())) + oBag.arrMappedBagIds.push_back(XmlUtils::GetInteger(oReader.GetText3())); + } + } + } + } + m_arrBags.push_back(oBag); + } + } + bool CFeaturePropertyBagFile::IsCheckboxComplement(int nIndex) const + { + // xfComplement i -> XFComplements/MappedFeaturePropertyBags[i] -> XFComplement + // -> XFControls -> CellControl -> bag type "Checkbox" + const Bag* pComplements = NULL; + for (size_t i = 0; i < m_arrBags.size(); ++i) + { + if (L"XFComplements" == m_arrBags[i].sType) + { + pComplements = &m_arrBags[i]; + break; + } + } + if (NULL == pComplements || nIndex < 0 || nIndex >= (int)pComplements->arrMappedBagIds.size()) + return false; + + int nComplementId = pComplements->arrMappedBagIds[nIndex]; + if (nComplementId < 0 || nComplementId >= (int)m_arrBags.size()) + return false; + + const Bag& oComplement = m_arrBags[nComplementId]; + std::map::const_iterator itControls = oComplement.mapBagIds.find(L"XFControls"); + if (oComplement.sType != L"XFComplement" || itControls == oComplement.mapBagIds.end()) + return false; + + int nControlsId = itControls->second; + if (nControlsId < 0 || nControlsId >= (int)m_arrBags.size()) + return false; + + const Bag& oControls = m_arrBags[nControlsId]; + std::map::const_iterator itControl = oControls.mapBagIds.find(L"CellControl"); + if (oControls.sType != L"XFControls" || itControl == oControls.mapBagIds.end()) + return false; + + int nControlId = itControl->second; + if (nControlId < 0 || nControlId >= (int)m_arrBags.size()) + return false; + + return L"Checkbox" == m_arrBags[nControlId].sType; + } + void CFeaturePropertyBagFile::write(const CPath& oPath, const CPath& oDirectory, CContentTypes& oContent) const + { + // canonical content: a single Checkbox control chain; every checkbox xf + // references it via + std::wstring sXml = L"" + L"" + L"" + L"0" + L"1" + L"" + L"2" + L"" + L""; + + NSFile::CFileBinary::SaveToFile(oPath.GetPath(), sXml); + + oContent.Registration(type().OverrideType(), oDirectory, oPath.GetFilename()); + } + } +} // namespace OOX diff --git a/OOXML/XlsxFormat/Workbook/FeaturePropertyBag.h b/OOXML/XlsxFormat/Workbook/FeaturePropertyBag.h new file mode 100644 index 0000000000..f167bdc4e1 --- /dev/null +++ b/OOXML/XlsxFormat/Workbook/FeaturePropertyBag.h @@ -0,0 +1,68 @@ +/* + * (c) Copyright Ascensio System SIA 2010-2023 + * + * 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. In accordance with + * Section 7(a) of the GNU AGPL its Section 15 shall be amended to the effect + * that Ascensio System SIA expressly excludes the warranty of non-infringement + * of any third-party rights. + * + * 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: http://www.gnu.org/licenses/agpl-3.0.html + * + * The interactive user interfaces in modified source and object code versions + * of the Program must display Appropriate Legal Notices, as required under + * Section 5 of the GNU AGPL version 3. + * + * All the Product's GUI elements, including illustrations and icon sets, as + * well as technical writing content are licensed under the terms of the + * Creative Commons Attribution-ShareAlike 4.0 International. See the License + * terms at http://creativecommons.org/licenses/by-sa/4.0/legalcode + * + */ +#pragma once + +#include "../../DocxFormat/File.h" + +#include +#include + +namespace OOX +{ + namespace Spreadsheet + { + // xl/featurePropertyBag/featurePropertyBag.xml + // Stores feature property bags; currently the only consumer is the cell checkbox + // feature: an extLst references an XFComplements entry which resolves through + // XFComplement -> XFControls (CellControl) to a bag of type "Checkbox". + class CFeaturePropertyBagFile : public OOX::File + { + public: + CFeaturePropertyBagFile(OOX::Document* pMain); + CFeaturePropertyBagFile(OOX::Document* pMain, const CPath& oRootPath, const CPath& oPath); + virtual ~CFeaturePropertyBagFile(); + + virtual void read(const CPath& oPath); + virtual void read(const CPath& oRootPath, const CPath& oPath); + virtual void write(const CPath& oPath, const CPath& oDirectory, CContentTypes& oContent) const; + + virtual const OOX::FileType type() const; + virtual const CPath DefaultDirectory() const; + virtual const CPath DefaultFileName() const; + + // true if the XFComplements entry at nIndex resolves to a Checkbox cell control + bool IsCheckboxComplement(int nIndex) const; + + private: + struct Bag + { + std::wstring sType; + std::map mapBagIds; // N + std::vector arrMappedBagIds; // N... + }; + std::vector m_arrBags; + }; + } +} // namespace OOX