diff options
Diffstat (limited to 'sc/qa/unit/subsequent_export-test.cxx')
-rw-r--r-- | sc/qa/unit/subsequent_export-test.cxx | 5210 |
1 files changed, 5210 insertions, 0 deletions
diff --git a/sc/qa/unit/subsequent_export-test.cxx b/sc/qa/unit/subsequent_export-test.cxx new file mode 100644 index 000000000..9647d0964 --- /dev/null +++ b/sc/qa/unit/subsequent_export-test.cxx @@ -0,0 +1,5210 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +#include <officecfg/Office/Common.hxx> +#include <sal/config.h> +#include <config_features.h> + +#include <sfx2/docfile.hxx> +#include <sfx2/frame.hxx> +#include <sfx2/sfxmodelfactory.hxx> +#include <sfx2/sfxsids.hrc> +#include <sfx2/docfilt.hxx> + +#include "helper/debughelper.hxx" +#include "helper/qahelper.hxx" +#include "helper/xpath.hxx" +#include "helper/shared_test_impl.hxx" + +#include <userdat.hxx> +#include <docsh.hxx> +#include <patattr.hxx> +#include <docpool.hxx> +#include <scitems.hxx> +#include <attrib.hxx> +#include <stlpool.hxx> +#include <document.hxx> +#include <formulacell.hxx> +#include <tokenarray.hxx> +#include <editutil.hxx> +#include <scopetools.hxx> +#include <cellvalue.hxx> +#include <postit.hxx> +#include <tokenstringcontext.hxx> +#include <chgtrack.hxx> +#include <validat.hxx> +#include <global.hxx> +#include <scmod.hxx> +#include <dpcache.hxx> +#include <dpobject.hxx> +#include <clipparam.hxx> + +#include <svx/svdpage.hxx> +#include <svx/svdograf.hxx> +#include <tabprotection.hxx> +#include <editeng/wghtitem.hxx> +#include <editeng/postitem.hxx> +#include <editeng/editdata.hxx> +#include <editeng/eeitem.hxx> +#include <editeng/editobj.hxx> +#include <editeng/section.hxx> +#include <editeng/crossedoutitem.hxx> +#include <editeng/borderline.hxx> +#include <editeng/escapementitem.hxx> +#include <editeng/fontitem.hxx> +#include <editeng/udlnitem.hxx> +#include <editeng/flditem.hxx> +#include <editeng/colritem.hxx> +#include <formula/grammar.hxx> +#include <unotools/useroptions.hxx> +#include <comphelper/scopeguard.hxx> +#include <unotools/syslocaleoptions.hxx> +#include <tools/datetime.hxx> +#include <tools/fldunit.hxx> +#include <svl/zformat.hxx> + +#include <test/xmltesttools.hxx> +#include <com/sun/star/drawing/XDrawPageSupplier.hpp> +#include <com/sun/star/awt/XBitmap.hpp> +#include <com/sun/star/frame/Desktop.hpp> +#include <com/sun/star/graphic/GraphicType.hpp> +#include <com/sun/star/sheet/GlobalSheetSettings.hpp> +#include <comphelper/storagehelper.hxx> + +using namespace ::com::sun::star; +using namespace ::com::sun::star::uno; + +class ScExportTest : public ScBootstrapFixture, public XmlTestTools +{ +protected: + virtual void registerNamespaces(xmlXPathContextPtr& pXmlXPathCtx) override; +public: + ScExportTest(); + + virtual void setUp() override; + virtual void tearDown() override; + + ScDocShellRef saveAndReloadPassword( ScDocShell*, const OUString&, const OUString&, const OUString&, SfxFilterFlags ); + + void test(); + void testTdf111876(); + void testPasswordExportODS(); + void testTdf134332(); + void testConditionalFormatExportODS(); + void testConditionalFormatExportXLSX(); + void testCondFormatExportCellIs(); + void testTdf99856_dataValidationTest(); + void testProtectionKeyODS_UTF16LErtlSHA1(); + void testProtectionKeyODS_UTF8SHA1(); + void testProtectionKeyODS_UTF8SHA256ODF12(); + void testProtectionKeyODS_UTF8SHA256W3C(); + void testProtectionKeyODS_XL_SHA1(); + void testColorScaleExportODS(); + void testColorScaleExportXLSX(); + void testDataBarExportODS(); + void testDataBarExportXLSX(); + void testConditionalFormatRangeListXLSX(); + void testConditionalFormatContainsTextXLSX(); + void testConditionalFormatPriorityCheckXLSX(); + void testConditionalFormatOriginXLSX(); + void testMiscRowHeightExport(); + void testNamedRangeBugfdo62729(); + void testBuiltinRangesXLSX(); + void testRichTextExportODS(); + void testRichTextCellFormatXLSX(); + void testFormulaRefSheetNameODS(); + + void testCellValuesExportODS(); + void testCellNoteExportODS(); + void testCellNoteExportXLS(); + void testFormatExportODS(); + + void testCommentExportXLSX(); + void testCommentExportXLSX_2_XLSX(); +#if HAVE_MORE_FONTS + void testCustomColumnWidthExportXLSX(); +#endif + void testXfDefaultValuesXLSX(); + void testODF13(); + void testColumnWidthResaveXLSX(); +#if HAVE_MORE_FONTS + void testColumnWidthExportFromODStoXLSX(); +#endif + void testOutlineExportXLSX(); + void testHiddenEmptyRowsXLSX(); + void testAllRowsHiddenXLSX(); + void testLandscapeOrientationXLSX(); + + void testInlineArrayXLS(); + void testEmbeddedChartODS(); + void testEmbeddedChartXLS(); + void testCellAnchoredGroupXLS(); + + void testFormulaReferenceXLS(); + void testSheetProtectionXLSX(); + void testSheetProtectionXLSB(); + + void testCellBordersXLS(); + void testCellBordersXLSX(); + void testBordersExchangeXLSX(); + void testTrackChangesSimpleXLSX(); + void testSheetTabColorsXLSX(); + + void testSharedFormulaExportXLS(); + void testSharedFormulaExportXLSX(); + void testSharedFormulaStringResultExportXLSX(); + + void testFunctionsExcel2010( sal_uLong nFormatType ); + void testFunctionsExcel2010XLSX(); + void testFunctionsExcel2010XLS(); + void testFunctionsExcel2010ODS(); + + void testCeilingFloor( sal_uLong nFormatType ); + void testCeilingFloorXLSX(); + void testCeilingFloorODSToXLSX(); + void testCeilingFloorXLS(); + void testCeilingFloorODS(); + + void testCustomXml(); + + void testRelativePathsODS(); + void testSheetProtectionODS(); + + void testSwappedOutImageExport(); + void testLinkedGraphicRT(); + void testImageWithSpecialID(); + + void testSupBookVirtualPathXLS(); + void testAbsNamedRangeHTML(); + void testSheetLocalRangeNameXLS(); + void testRelativeNamedExpressionsXLS(); + void testSheetTextBoxHyperlinkXLSX(); + void testFontSizeXLSX(); + void testSheetCharacterKerningSpaceXLSX(); + void testSheetCondensedCharacterSpaceXLSX(); + void testTextUnderlineColorXLSX(); + void testSheetRunParagraphPropertyXLSX(); + void testHiddenShapeXLS(); + void testHiddenShapeXLSX(); + void testShapeAutofitXLSX(); + void testHyperlinkXLSX(); + void testMoveCellAnchoredShapesODS(); + void testMatrixMultiplicationXLSX(); + void testPreserveTextWhitespaceXLSX(); + void testPreserveTextWhitespace2XLSX(); + void testTextDirectionXLSX(); + void testTdf66668(); + void testTdf130108(); + void testTdf76949(); + void testTdf55417(); + void testTdf129985(); + void testTdf73063(); + + xmlDocUniquePtr testTdf95640(const OUString& rFileName, sal_Int32 nSourceFormat, + sal_Int32 nDestFormat); + void testTdf95640_ods_to_xlsx(); + void testTdf95640_ods_to_xlsx_with_standard_list(); + void testTdf95640_xlsx_to_xlsx(); + + void testRefStringXLSX(); + void testRefStringConfigXLSX(); + void testRefStringUnspecified(); + void testHeaderImageODS(); + + void testTdf88657ODS(); + void testTdf41722(); + void testTdf113621(); + void testEscapeCharInNumberFormatXLSX(); + void testNatNumInNumberFormatXLSX(); + void testExponentWithoutSignFormatXLSX(); + void testExtendedLCIDXLSX(); + + void testHiddenRepeatedRowsODS(); + void testHyperlinkTargetFrameODS(); + void testOpenDocumentAsReadOnly(); + void testKeepSettingsOfBlankRows(); + + void testTdf105272(); + void testTdf118990(); + void testTdf121612(); + void testTdf112936(); + void testPivotCacheAfterExportXLSX(); + void testTdf114969XLSX(); + void testTdf115192XLSX(); + void testTdf91634XLSX(); + void testTdf115159(); + void testTdf112567(); + void testTdf112567b(); + void testTdf123645XLSX(); + void testTdf125173XLSX(); + void testTdf79972XLSX(); + void testTdf126024XLSX(); + void testTdf126177XLSX(); + void testCommentTextVAlignment(); + void testCommentTextHAlignment(); + void testValidationCopyPaste(); + + void testXltxExport(); + void testRotatedImageODS(); + void testTdf128976(); + void testTdf120502(); + void testTdf131372(); + void testTdf122331(); + void testTdf83779(); + void testTdf134817_HeaderFooterTextWith2SectionXLSX(); + + CPPUNIT_TEST_SUITE(ScExportTest); + CPPUNIT_TEST(test); + CPPUNIT_TEST(testTdf111876); + CPPUNIT_TEST(testPasswordExportODS); + CPPUNIT_TEST(testTdf134332); + CPPUNIT_TEST(testConditionalFormatExportODS); + CPPUNIT_TEST(testCondFormatExportCellIs); + CPPUNIT_TEST(testConditionalFormatExportXLSX); + CPPUNIT_TEST(testTdf99856_dataValidationTest); + CPPUNIT_TEST(testProtectionKeyODS_UTF16LErtlSHA1); + CPPUNIT_TEST(testProtectionKeyODS_UTF8SHA1); + CPPUNIT_TEST(testProtectionKeyODS_UTF8SHA256ODF12); + CPPUNIT_TEST(testProtectionKeyODS_UTF8SHA256W3C); + CPPUNIT_TEST(testProtectionKeyODS_XL_SHA1); + CPPUNIT_TEST(testColorScaleExportODS); + CPPUNIT_TEST(testColorScaleExportXLSX); + CPPUNIT_TEST(testDataBarExportODS); + CPPUNIT_TEST(testDataBarExportXLSX); + CPPUNIT_TEST(testConditionalFormatRangeListXLSX); + CPPUNIT_TEST(testConditionalFormatContainsTextXLSX); + CPPUNIT_TEST(testConditionalFormatPriorityCheckXLSX); + CPPUNIT_TEST(testConditionalFormatOriginXLSX); + CPPUNIT_TEST(testMiscRowHeightExport); + CPPUNIT_TEST(testNamedRangeBugfdo62729); + CPPUNIT_TEST(testBuiltinRangesXLSX); + CPPUNIT_TEST(testRichTextExportODS); + CPPUNIT_TEST(testRichTextCellFormatXLSX); + CPPUNIT_TEST(testFormulaRefSheetNameODS); + CPPUNIT_TEST(testCellValuesExportODS); + CPPUNIT_TEST(testCellNoteExportODS); + CPPUNIT_TEST(testCellNoteExportXLS); + CPPUNIT_TEST(testFormatExportODS); + CPPUNIT_TEST(testCommentExportXLSX); + CPPUNIT_TEST(testCommentExportXLSX_2_XLSX); +#if HAVE_MORE_FONTS + CPPUNIT_TEST(testCustomColumnWidthExportXLSX); +#endif + CPPUNIT_TEST(testXfDefaultValuesXLSX); + CPPUNIT_TEST(testODF13); + CPPUNIT_TEST(testColumnWidthResaveXLSX); +#if HAVE_MORE_FONTS + CPPUNIT_TEST(testColumnWidthExportFromODStoXLSX); +#endif + CPPUNIT_TEST(testOutlineExportXLSX); + CPPUNIT_TEST(testHiddenEmptyRowsXLSX); + CPPUNIT_TEST(testAllRowsHiddenXLSX); + CPPUNIT_TEST(testLandscapeOrientationXLSX); + CPPUNIT_TEST(testInlineArrayXLS); + CPPUNIT_TEST(testEmbeddedChartODS); + CPPUNIT_TEST(testEmbeddedChartXLS); + CPPUNIT_TEST(testCellAnchoredGroupXLS); + + CPPUNIT_TEST(testFormulaReferenceXLS); + CPPUNIT_TEST(testSheetProtectionXLSX); + CPPUNIT_TEST(testSheetProtectionXLSB); + CPPUNIT_TEST(testCellBordersXLS); + CPPUNIT_TEST(testCellBordersXLSX); + CPPUNIT_TEST(testBordersExchangeXLSX); + CPPUNIT_TEST(testTrackChangesSimpleXLSX); + CPPUNIT_TEST(testSheetTabColorsXLSX); + CPPUNIT_TEST(testSharedFormulaExportXLS); + CPPUNIT_TEST(testSharedFormulaExportXLSX); + CPPUNIT_TEST(testSharedFormulaStringResultExportXLSX); + CPPUNIT_TEST(testFunctionsExcel2010XLSX); + CPPUNIT_TEST(testFunctionsExcel2010XLS); + CPPUNIT_TEST(testFunctionsExcel2010ODS); + CPPUNIT_TEST(testCeilingFloorXLSX); + CPPUNIT_TEST(testCeilingFloorODSToXLSX); + CPPUNIT_TEST(testCeilingFloorXLS); + CPPUNIT_TEST(testCeilingFloorODS); + CPPUNIT_TEST(testCustomXml); + CPPUNIT_TEST(testRelativePathsODS); + CPPUNIT_TEST(testSheetProtectionODS); + CPPUNIT_TEST(testSupBookVirtualPathXLS); + CPPUNIT_TEST(testSwappedOutImageExport); + CPPUNIT_TEST(testLinkedGraphicRT); + CPPUNIT_TEST(testImageWithSpecialID); + CPPUNIT_TEST(testPreserveTextWhitespaceXLSX); + CPPUNIT_TEST(testPreserveTextWhitespace2XLSX); + CPPUNIT_TEST(testAbsNamedRangeHTML); + CPPUNIT_TEST(testSheetLocalRangeNameXLS); + CPPUNIT_TEST(testRelativeNamedExpressionsXLS); + CPPUNIT_TEST(testSheetTextBoxHyperlinkXLSX); + CPPUNIT_TEST(testFontSizeXLSX); + CPPUNIT_TEST(testSheetCharacterKerningSpaceXLSX); + CPPUNIT_TEST(testSheetCondensedCharacterSpaceXLSX); + CPPUNIT_TEST(testTextUnderlineColorXLSX); + CPPUNIT_TEST(testSheetRunParagraphPropertyXLSX); + CPPUNIT_TEST(testHiddenShapeXLS); + CPPUNIT_TEST(testHiddenShapeXLSX); + CPPUNIT_TEST(testShapeAutofitXLSX); + CPPUNIT_TEST(testHyperlinkXLSX); + CPPUNIT_TEST(testMoveCellAnchoredShapesODS); + CPPUNIT_TEST(testMatrixMultiplicationXLSX); + CPPUNIT_TEST(testTextDirectionXLSX); + CPPUNIT_TEST(testTdf66668); + CPPUNIT_TEST(testTdf130108); + CPPUNIT_TEST(testTdf76949); + CPPUNIT_TEST(testTdf55417); + CPPUNIT_TEST(testTdf129985); + CPPUNIT_TEST(testTdf73063); + CPPUNIT_TEST(testTdf95640_ods_to_xlsx); + CPPUNIT_TEST(testTdf95640_ods_to_xlsx_with_standard_list); + CPPUNIT_TEST(testTdf95640_xlsx_to_xlsx); + + CPPUNIT_TEST(testRefStringXLSX); + CPPUNIT_TEST(testRefStringConfigXLSX); + CPPUNIT_TEST(testRefStringUnspecified); + CPPUNIT_TEST(testHeaderImageODS); + + CPPUNIT_TEST(testTdf88657ODS); + CPPUNIT_TEST(testTdf41722); + CPPUNIT_TEST(testTdf113621); + CPPUNIT_TEST(testEscapeCharInNumberFormatXLSX); + CPPUNIT_TEST(testNatNumInNumberFormatXLSX); + CPPUNIT_TEST(testExponentWithoutSignFormatXLSX); + CPPUNIT_TEST(testExtendedLCIDXLSX); + + CPPUNIT_TEST(testHiddenRepeatedRowsODS); + CPPUNIT_TEST(testHyperlinkTargetFrameODS); + CPPUNIT_TEST(testOpenDocumentAsReadOnly); + CPPUNIT_TEST(testKeepSettingsOfBlankRows); + + CPPUNIT_TEST(testTdf105272); + CPPUNIT_TEST(testTdf118990); + CPPUNIT_TEST(testTdf121612); + CPPUNIT_TEST(testTdf112936); + CPPUNIT_TEST(testPivotCacheAfterExportXLSX); + CPPUNIT_TEST(testTdf114969XLSX); + CPPUNIT_TEST(testTdf115192XLSX); + CPPUNIT_TEST(testTdf91634XLSX); + CPPUNIT_TEST(testTdf115159); + CPPUNIT_TEST(testTdf112567); + CPPUNIT_TEST(testTdf112567b); + CPPUNIT_TEST(testTdf123645XLSX); + CPPUNIT_TEST(testTdf125173XLSX); + CPPUNIT_TEST(testTdf79972XLSX); + CPPUNIT_TEST(testTdf126024XLSX); + CPPUNIT_TEST(testTdf126177XLSX); + CPPUNIT_TEST(testCommentTextVAlignment); + CPPUNIT_TEST(testCommentTextHAlignment); + CPPUNIT_TEST(testValidationCopyPaste); + + CPPUNIT_TEST(testXltxExport); + CPPUNIT_TEST(testRotatedImageODS); + CPPUNIT_TEST(testTdf128976); + CPPUNIT_TEST(testTdf120502); + CPPUNIT_TEST(testTdf131372); + CPPUNIT_TEST(testTdf122331); + CPPUNIT_TEST(testTdf83779); + CPPUNIT_TEST(testTdf134817_HeaderFooterTextWith2SectionXLSX); + + CPPUNIT_TEST_SUITE_END(); + +private: + void testExcelCellBorders( sal_uLong nFormatType ); + + uno::Reference<uno::XInterface> m_xCalcComponent; + +}; + +void ScExportTest::registerNamespaces(xmlXPathContextPtr& pXmlXPathCtx) +{ + static const struct { xmlChar* pPrefix; xmlChar* pURI; } aNamespaces[] = + { + { BAD_CAST("w"), BAD_CAST("http://schemas.openxmlformats.org/wordprocessingml/2006/main") }, + { BAD_CAST("x"), BAD_CAST("http://schemas.openxmlformats.org/spreadsheetml/2006/main") }, + { BAD_CAST("v"), BAD_CAST("urn:schemas-microsoft-com:vml") }, + { BAD_CAST("c"), BAD_CAST("http://schemas.openxmlformats.org/drawingml/2006/chart") }, + { BAD_CAST("a"), BAD_CAST("http://schemas.openxmlformats.org/drawingml/2006/main") }, + { BAD_CAST("mc"), BAD_CAST("http://schemas.openxmlformats.org/markup-compatibility/2006") }, + { BAD_CAST("wps"), BAD_CAST("http://schemas.microsoft.com/office/word/2010/wordprocessingShape") }, + { BAD_CAST("wpg"), BAD_CAST("http://schemas.microsoft.com/office/word/2010/wordprocessingGroup") }, + { BAD_CAST("wp"), BAD_CAST("http://schemas.openxmlformats.org/drawingml/2006/wordprocessingDrawing") }, + { BAD_CAST("office"), BAD_CAST("urn:oasis:names:tc:opendocument:xmlns:office:1.0") }, + { BAD_CAST("table"), BAD_CAST("urn:oasis:names:tc:opendocument:xmlns:table:1.0") }, + { BAD_CAST("text"), BAD_CAST("urn:oasis:names:tc:opendocument:xmlns:text:1.0") }, + { BAD_CAST("style"), BAD_CAST("urn:oasis:names:tc:opendocument:xmlns:style:1.0") }, + { BAD_CAST("draw"), BAD_CAST("urn:oasis:names:tc:opendocument:xmlns:drawing:1.0") }, + { BAD_CAST("xlink"), BAD_CAST("http://www.w3c.org/1999/xlink") }, + { BAD_CAST("xdr"), BAD_CAST("http://schemas.openxmlformats.org/drawingml/2006/spreadsheetDrawing") }, + { BAD_CAST("xx"), BAD_CAST("urn:schemas-microsoft-com:office:excel") }, + { BAD_CAST("r"), BAD_CAST("http://schemas.openxmlformats.org/package/2006/relationships") }, + { BAD_CAST("number"), BAD_CAST("urn:oasis:names:tc:opendocument:xmlns:datastyle:1.0") }, + { BAD_CAST("loext"), BAD_CAST("urn:org:documentfoundation:names:experimental:office:xmlns:loext:1.0") }, + { BAD_CAST("tableooo"), BAD_CAST("http://openoffice.org/2009/table") }, + { BAD_CAST("ContentType"), BAD_CAST("http://schemas.openxmlformats.org/package/2006/content-types") }, + { BAD_CAST("x14"), BAD_CAST("http://schemas.microsoft.com/office/spreadsheetml/2009/9/main") }, + { BAD_CAST("xm"), BAD_CAST("http://schemas.microsoft.com/office/excel/2006/main") }, + }; + for(size_t i = 0; i < SAL_N_ELEMENTS(aNamespaces); ++i) + { + xmlXPathRegisterNs(pXmlXPathCtx, aNamespaces[i].pPrefix, aNamespaces[i].pURI ); + } +} + +ScDocShellRef ScExportTest::saveAndReloadPassword(ScDocShell* pShell, const OUString &rFilter, + const OUString &rUserData, const OUString& rTypeName, SfxFilterFlags nFormatType) +{ + utl::TempFile aTempFile; + aTempFile.EnableKillingFile(); + SfxMedium aStoreMedium( aTempFile.GetURL(), StreamMode::STD_WRITE ); + SotClipboardFormatId nExportFormat = SotClipboardFormatId::NONE; + if (nFormatType == ODS_FORMAT_TYPE) + nExportFormat = SotClipboardFormatId::STARCHART_8; + auto pExportFilter = std::make_shared<SfxFilter>( + rFilter, + OUString(), nFormatType, nExportFormat, rTypeName, OUString(), + rUserData, "private:factory/scalc*" ); + pExportFilter->SetVersion(SOFFICE_FILEFORMAT_CURRENT); + aStoreMedium.SetFilter(pExportFilter); + SfxItemSet* pExportSet = aStoreMedium.GetItemSet(); + uno::Sequence< beans::NamedValue > aEncryptionData = comphelper::OStorageHelper::CreatePackageEncryptionData( "test" ); + pExportSet->Put(SfxUnoAnyItem(SID_ENCRYPTIONDATA, makeAny(aEncryptionData))); + + uno::Reference< embed::XStorage > xMedStorage = aStoreMedium.GetStorage(); + ::comphelper::OStorageHelper::SetCommonStorageEncryptionData( xMedStorage, aEncryptionData ); + + pShell->DoSaveAs( aStoreMedium ); + pShell->DoClose(); + + //std::cout << "File: " << aTempFile.GetURL() << std::endl; + + SotClipboardFormatId nFormat = SotClipboardFormatId::NONE; + if (nFormatType == ODS_FORMAT_TYPE) + nFormat = SotClipboardFormatId::STARCALC_8; + + OUString aPass("test"); + return load(aTempFile.GetURL(), rFilter, rUserData, rTypeName, nFormatType, nFormat, SOFFICE_FILEFORMAT_CURRENT, &aPass); +} + +void ScExportTest::test() +{ + ScDocShell* pShell = new ScDocShell( + SfxModelFlags::EMBEDDED_OBJECT | + SfxModelFlags::DISABLE_EMBEDDED_SCRIPTS | + SfxModelFlags::DISABLE_DOCUMENT_RECOVERY); + pShell->DoInitNew(); + + ScDocument& rDoc = pShell->GetDocument(); + + rDoc.SetValue(0,0,0, 1.0); + + ScDocShellRef xDocSh = saveAndReload(pShell, FORMAT_ODS); + + CPPUNIT_ASSERT(xDocSh.is()); + ScDocument& rLoadedDoc = xDocSh->GetDocument(); + double aVal = rLoadedDoc.GetValue(0,0,0); + ASSERT_DOUBLES_EQUAL(aVal, 1.0); + xDocSh->DoClose(); +} + +void ScExportTest::testTdf111876() + { + // Document with relative path hyperlink + + ScDocShellRef xShell = loadDoc("tdf111876.", FORMAT_XLSX); + CPPUNIT_ASSERT(xShell.is()); + + ScDocShellRef xDocSh = saveAndReload(&(*xShell), FORMAT_XLSX); + CPPUNIT_ASSERT(xDocSh.is()); + + xmlDocUniquePtr pDoc = XPathHelper::parseExport2(*this, *xDocSh, m_xSFactory, "xl/worksheets/_rels/sheet1.xml.rels", FORMAT_XLSX); + CPPUNIT_ASSERT(pDoc); + OUString sTarget = getXPath(pDoc, "/r:Relationships/r:Relationship", "Target"); + + // Document is saved to the temporary directory, relative path should be different than original one + CPPUNIT_ASSERT(sTarget != "../xls/bug-fixes.xls"); + + xDocSh->DoClose(); +} + +void ScExportTest::testPasswordExportODS() +{ + ScDocShell* pShell = new ScDocShell( + SfxModelFlags::EMBEDDED_OBJECT | + SfxModelFlags::DISABLE_EMBEDDED_SCRIPTS | + SfxModelFlags::DISABLE_DOCUMENT_RECOVERY); + pShell->DoInitNew(); + + ScDocument& rDoc = pShell->GetDocument(); + + rDoc.SetValue(0, 0, 0, 1.0); + + sal_Int32 nFormat = FORMAT_ODS; + OUString aFilterName(getFileFormats()[nFormat].pFilterName, strlen(getFileFormats()[nFormat].pFilterName), RTL_TEXTENCODING_UTF8) ; + OUString aFilterType(getFileFormats()[nFormat].pTypeName, strlen(getFileFormats()[nFormat].pTypeName), RTL_TEXTENCODING_UTF8); + ScDocShellRef xDocSh = saveAndReloadPassword(pShell, aFilterName, OUString(), aFilterType, getFileFormats()[nFormat].nFormatType); + + CPPUNIT_ASSERT(xDocSh.is()); + ScDocument& rLoadedDoc = xDocSh->GetDocument(); + double aVal = rLoadedDoc.GetValue(0,0,0); + ASSERT_DOUBLES_EQUAL(aVal, 1.0); + + xDocSh->DoClose(); +} + +void ScExportTest::testTdf134332() +{ + ScDocShellRef xShell = loadDoc("tdf134332.", FORMAT_ODS); + CPPUNIT_ASSERT(xShell.is()); + + ScDocument& rDoc = xShell->GetDocument(); + + ASSERT_DOUBLES_EQUAL(190.0, rDoc.GetValue(ScAddress(0,0,0))); + + ASSERT_DOUBLES_EQUAL(238.0, rDoc.GetValue(ScAddress(0,10144,0))); + + sal_Int32 nFormat = FORMAT_ODS; + OUString aFilterName(getFileFormats()[nFormat].pFilterName, strlen(getFileFormats()[nFormat].pFilterName), RTL_TEXTENCODING_UTF8) ; + OUString aFilterType(getFileFormats()[nFormat].pTypeName, strlen(getFileFormats()[nFormat].pTypeName), RTL_TEXTENCODING_UTF8); + ScDocShellRef xDocSh = saveAndReloadPassword(static_cast<ScDocShell*>(rDoc.GetDocumentShell()), aFilterName, OUString(), + aFilterType, getFileFormats()[nFormat].nFormatType); + + // Without the fixes in place, it would have failed here + CPPUNIT_ASSERT(xDocSh.is()); + ScDocument& rLoadedDoc = xDocSh->GetDocument(); + ASSERT_DOUBLES_EQUAL(190.0, rLoadedDoc.GetValue(ScAddress(0,0,0))); + + ASSERT_DOUBLES_EQUAL(238.0, rLoadedDoc.GetValue(ScAddress(0,10144,0))); + + xDocSh->DoClose(); +} + +void ScExportTest::testConditionalFormatExportODS() +{ + ScDocShellRef xShell = loadDoc("new_cond_format_test_export.", FORMAT_ODS); + CPPUNIT_ASSERT(xShell.is()); + + ScDocShellRef xDocSh = saveAndReload(&(*xShell), FORMAT_ODS); + CPPUNIT_ASSERT(xDocSh.is()); + ScDocument& rDoc = xDocSh->GetDocument(); + OUString aCSVPath; + createCSVPath( "new_cond_format_test_export.", aCSVPath ); + testCondFile(aCSVPath, &rDoc, 0); + + xDocSh->DoClose(); +} + +void ScExportTest::testCondFormatExportCellIs() +{ + ScDocShellRef xShell = loadDoc("condFormat_cellis.", FORMAT_XLSX); + CPPUNIT_ASSERT(xShell.is()); + ScDocShellRef xDocSh = saveAndReload(&(*xShell), FORMAT_XLSX); + CPPUNIT_ASSERT(xDocSh.is()); + + ScDocument& rDoc = xDocSh->GetDocument(); + CPPUNIT_ASSERT_EQUAL(size_t(1), rDoc.GetCondFormList(0)->size()); + + ScConditionalFormat* pFormat = rDoc.GetCondFormat(0, 0, 0); + CPPUNIT_ASSERT(pFormat); + + const ScFormatEntry* pEntry = pFormat->GetEntry(0); + CPPUNIT_ASSERT(pEntry); + CPPUNIT_ASSERT_EQUAL(ScFormatEntry::Type::ExtCondition, pEntry->GetType()); + + const ScCondFormatEntry* pCondition = static_cast<const ScCondFormatEntry*>(pEntry); + CPPUNIT_ASSERT_EQUAL( ScConditionMode::Equal, pCondition->GetOperation()); + + OUString aStr = pCondition->GetExpression(ScAddress(0, 0, 0), 0); + CPPUNIT_ASSERT_EQUAL( OUString("$Sheet2.$A$1"), aStr ); + + pEntry = pFormat->GetEntry(1); + CPPUNIT_ASSERT(pEntry); + CPPUNIT_ASSERT_EQUAL(ScFormatEntry::Type::ExtCondition, pEntry->GetType()); + + pCondition = static_cast<const ScCondFormatEntry*>(pEntry); + CPPUNIT_ASSERT_EQUAL( ScConditionMode::Equal, pCondition->GetOperation()); + + aStr = pCondition->GetExpression(ScAddress(0, 0, 0), 0); + CPPUNIT_ASSERT_EQUAL( OUString("$Sheet2.$A$2"), aStr ); + + xDocSh->DoClose(); +} + +void ScExportTest::testConditionalFormatExportXLSX() +{ + ScDocShellRef xShell = loadDoc("new_cond_format_test_export.", FORMAT_XLSX); + CPPUNIT_ASSERT(xShell.is()); + + ScDocShellRef xDocSh = saveAndReload(&(*xShell), FORMAT_XLSX); + CPPUNIT_ASSERT(xDocSh.is()); + ScDocument& rDoc = xDocSh->GetDocument(); + { + OUString aCSVPath; + createCSVPath( "new_cond_format_test_export.", aCSVPath ); + testCondFile(aCSVPath, &rDoc, 0); + } + { + OUString aCSVPath; + createCSVPath( "new_cond_format_test_sheet2.", aCSVPath ); + testCondFile(aCSVPath, &rDoc, 1); + } + + xDocSh->DoClose(); +} + +void ScExportTest::testTdf99856_dataValidationTest() +{ + ScDocShellRef xShell = loadDoc("tdf99856_dataValidationTest.", FORMAT_ODS); + CPPUNIT_ASSERT_MESSAGE("Failed to load doc", xShell.is()); + + ScDocShellRef xDocSh = saveAndReload( xShell.get(), FORMAT_XLSX); + CPPUNIT_ASSERT_MESSAGE("Failed to reload doc", xDocSh.is()); + + ScDocument& rDoc = xDocSh->GetDocument(); + const ScValidationData* pData = rDoc.GetValidationEntry(2); + CPPUNIT_ASSERT(pData); + + // Excel can't open corrupt file if the list is longer than 255 characters + std::vector<ScTypedStrData> aList; + pData->FillSelectionList(aList, ScAddress(0, 1, 1)); + CPPUNIT_ASSERT_EQUAL(size_t(18), aList.size()); + CPPUNIT_ASSERT_EQUAL(OUString("18 Missis"), aList[17].GetString()); + + xDocSh->DoClose(); +} + +void ScExportTest::testProtectionKeyODS_UTF16LErtlSHA1() +{ + OUString const password("1012345678901234567890123456789012345678901234567890"); + + ScDocShellRef xShell = loadDoc("protection-key1.", FORMAT_FODS); + CPPUNIT_ASSERT_MESSAGE("Failed to load doc", xShell.is()); + + ScDocument& rDoc = xShell->GetDocument(); + ScDocProtection *const pDocProt(rDoc.GetDocProtection()); + CPPUNIT_ASSERT(pDocProt->verifyPassword(password)); + ScTableProtection *const pTabProt(rDoc.GetTabProtection(0)); + CPPUNIT_ASSERT(pTabProt->verifyPassword(password)); + + // we can't assume that the user entered the password; check that we + // round-trip the password as-is + std::shared_ptr<utl::TempFile> pXPathFile = ScBootstrapFixture::exportTo(&(*xShell), FORMAT_ODS); + xmlDocUniquePtr pXmlDoc = XPathHelper::parseExport(pXPathFile, m_xSFactory, "content.xml"); + assertXPath(pXmlDoc, "//office:spreadsheet[@table:structure-protected='true' and @table:protection-key='vbnhxyBKtPHCA1wB21zG1Oha8ZA=' and @table:protection-key-digest-algorithm='http://www.w3.org/2000/09/xmldsig#sha1']"); + assertXPath(pXmlDoc, "//table:table[@table:protected='true' and @table:protection-key='vbnhxyBKtPHCA1wB21zG1Oha8ZA=' and @table:protection-key-digest-algorithm='http://www.w3.org/2000/09/xmldsig#sha1']"); + + xShell->DoClose(); +} + +void ScExportTest::testProtectionKeyODS_UTF8SHA1() +{ + OUString const password("1012345678901234567890123456789012345678901234567890"); + + ScDocShellRef xShell = loadDoc("protection-key2.", FORMAT_FODS); + CPPUNIT_ASSERT_MESSAGE("Failed to load doc", xShell.is()); + + ScDocument& rDoc = xShell->GetDocument(); + ScDocProtection *const pDocProt(rDoc.GetDocProtection()); + CPPUNIT_ASSERT(pDocProt->verifyPassword(password)); + ScTableProtection *const pTabProt(rDoc.GetTabProtection(0)); + CPPUNIT_ASSERT(pTabProt->verifyPassword(password)); + + // we can't assume that the user entered the password; check that we + // round-trip the password as-is + std::shared_ptr<utl::TempFile> pXPathFile = ScBootstrapFixture::exportTo(&(*xShell), FORMAT_ODS); + xmlDocUniquePtr pXmlDoc = XPathHelper::parseExport(pXPathFile, m_xSFactory, "content.xml"); + assertXPath(pXmlDoc, "//office:spreadsheet[@table:structure-protected='true' and @table:protection-key='nLHas0RIwepGDaH4c2hpyIUvIS8=' and @table:protection-key-digest-algorithm='http://www.w3.org/2000/09/xmldsig#sha1']"); + assertXPath(pXmlDoc, "//table:table[@table:protected='true' and @table:protection-key='nLHas0RIwepGDaH4c2hpyIUvIS8=' and @table:protection-key-digest-algorithm='http://www.w3.org/2000/09/xmldsig#sha1']"); + + xShell->DoClose(); +} + +void ScExportTest::testProtectionKeyODS_UTF8SHA256ODF12() +{ + OUString const password("1012345678901234567890123456789012345678901234567890"); + + ScDocShellRef xShell = loadDoc("protection-key3.", FORMAT_FODS); + CPPUNIT_ASSERT_MESSAGE("Failed to load doc", xShell.is()); + + ScDocument& rDoc = xShell->GetDocument(); + ScDocProtection *const pDocProt(rDoc.GetDocProtection()); + CPPUNIT_ASSERT(pDocProt->verifyPassword(password)); + ScTableProtection *const pTabProt(rDoc.GetTabProtection(0)); + CPPUNIT_ASSERT(pTabProt->verifyPassword(password)); + + // we can't assume that the user entered the password; check that we + // round-trip the password as-is + std::shared_ptr<utl::TempFile> pXPathFile = ScBootstrapFixture::exportTo(&(*xShell), FORMAT_ODS); + xmlDocUniquePtr pXmlDoc = XPathHelper::parseExport(pXPathFile, m_xSFactory, "content.xml"); + assertXPath(pXmlDoc, "//office:spreadsheet[@table:structure-protected='true' and @table:protection-key='1tnJohagR2T0yF/v69hLPuumSTsj32CumW97nkKGuSQ=' and @table:protection-key-digest-algorithm='http://www.w3.org/2000/09/xmldsig#sha256']"); + assertXPath(pXmlDoc, "//table:table[@table:protected='true' and @table:protection-key='1tnJohagR2T0yF/v69hLPuumSTsj32CumW97nkKGuSQ=' and @table:protection-key-digest-algorithm='http://www.w3.org/2000/09/xmldsig#sha256']"); + + xShell->DoClose(); +} + +void ScExportTest::testProtectionKeyODS_UTF8SHA256W3C() +{ + OUString const password("1012345678901234567890123456789012345678901234567890"); + + ScDocShellRef xShell = loadDoc("protection-key4.", FORMAT_FODS); + CPPUNIT_ASSERT_MESSAGE("Failed to load doc", xShell.is()); + + ScDocument& rDoc = xShell->GetDocument(); + ScDocProtection *const pDocProt(rDoc.GetDocProtection()); + CPPUNIT_ASSERT(pDocProt->verifyPassword(password)); + ScTableProtection *const pTabProt(rDoc.GetTabProtection(0)); + CPPUNIT_ASSERT(pTabProt->verifyPassword(password)); + + // we can't assume that the user entered the password; check that we + // round-trip the password as-is + std::shared_ptr<utl::TempFile> pXPathFile = ScBootstrapFixture::exportTo(&(*xShell), FORMAT_ODS); + xmlDocUniquePtr pXmlDoc = XPathHelper::parseExport(pXPathFile, m_xSFactory, "content.xml"); + assertXPath(pXmlDoc, "//office:spreadsheet[@table:structure-protected='true' and @table:protection-key='1tnJohagR2T0yF/v69hLPuumSTsj32CumW97nkKGuSQ=' and @table:protection-key-digest-algorithm='http://www.w3.org/2000/09/xmldsig#sha256']"); + assertXPath(pXmlDoc, "//table:table[@table:protected='true' and @table:protection-key='1tnJohagR2T0yF/v69hLPuumSTsj32CumW97nkKGuSQ=' and @table:protection-key-digest-algorithm='http://www.w3.org/2000/09/xmldsig#sha256']"); + + xShell->DoClose(); +} + +void ScExportTest::testProtectionKeyODS_XL_SHA1() +{ + OUString const password("1012345678901234567890123456789012345678901234567890"); + + ScDocShellRef xShell = loadDoc("protection-key5.", FORMAT_FODS); + CPPUNIT_ASSERT_MESSAGE("Failed to load doc", xShell.is()); + + ScDocument& rDoc = xShell->GetDocument(); + ScDocProtection *const pDocProt(rDoc.GetDocProtection()); + CPPUNIT_ASSERT(pDocProt->verifyPassword(password)); + ScTableProtection *const pTabProt(rDoc.GetTabProtection(0)); + CPPUNIT_ASSERT(pTabProt->verifyPassword(password)); + + // we can't assume that the user entered the password; check that we + // round-trip the password as-is + std::shared_ptr<utl::TempFile> pXPathFile = ScBootstrapFixture::exportTo(&(*xShell), FORMAT_ODS); + xmlDocUniquePtr pXmlDoc = XPathHelper::parseExport(pXPathFile, m_xSFactory, "content.xml"); + assertXPath(pXmlDoc, "//office:spreadsheet[@table:structure-protected='true' and @table:protection-key='OX3WkEe79fv1PE+FUmfOLdwVoqI=' and @table:protection-key-digest-algorithm='http://docs.oasis-open.org/office/ns/table/legacy-hash-excel' and @loext:protection-key-digest-algorithm-2='http://www.w3.org/2000/09/xmldsig#sha1']"); + assertXPath(pXmlDoc, "//table:table[@table:protected='true' and @table:protection-key='OX3WkEe79fv1PE+FUmfOLdwVoqI=' and @table:protection-key-digest-algorithm='http://docs.oasis-open.org/office/ns/table/legacy-hash-excel' and @loext:protection-key-digest-algorithm-2='http://www.w3.org/2000/09/xmldsig#sha1']"); + + xShell->DoClose(); +} + +void ScExportTest::testColorScaleExportODS() +{ + ScDocShellRef xShell = loadDoc("colorscale.", FORMAT_ODS); + CPPUNIT_ASSERT(xShell.is()); + + ScDocShellRef xDocSh = saveAndReload(xShell.get(), FORMAT_ODS); + CPPUNIT_ASSERT(xDocSh.is()); + + ScDocument& rDoc = xDocSh->GetDocument(); + + testColorScale2Entry_Impl(rDoc); + testColorScale3Entry_Impl(rDoc); + + xDocSh->DoClose(); +} + +void ScExportTest::testColorScaleExportXLSX() +{ + ScDocShellRef xShell = loadDoc("colorscale.", FORMAT_XLSX); + CPPUNIT_ASSERT(xShell.is()); + + ScDocShellRef xDocSh = saveAndReload(xShell.get(), FORMAT_XLSX); + CPPUNIT_ASSERT(xDocSh.is()); + + ScDocument& rDoc = xDocSh->GetDocument(); + + testColorScale2Entry_Impl(rDoc); + testColorScale3Entry_Impl(rDoc); + + xDocSh->DoClose(); +} + +void ScExportTest::testDataBarExportODS() +{ + ScDocShellRef xShell = loadDoc("databar.", FORMAT_ODS); + CPPUNIT_ASSERT(xShell.is()); + + ScDocShellRef xDocSh = saveAndReload(xShell.get(), FORMAT_ODS); + CPPUNIT_ASSERT(xDocSh.is()); + + ScDocument& rDoc = xDocSh->GetDocument(); + + testDataBar_Impl(rDoc); + + xDocSh->DoClose(); +} + +void ScExportTest::testFormatExportODS() +{ + ScDocShellRef xShell = loadDoc("formats.", FORMAT_ODS); + CPPUNIT_ASSERT(xShell.is()); + + ScDocShellRef xDocSh = saveAndReload(xShell.get(), FORMAT_ODS); + CPPUNIT_ASSERT(xDocSh.is()); + + ScDocument& rDoc = xDocSh->GetDocument(); + + testFormats(this, &rDoc, FORMAT_ODS); + + xDocSh->DoClose(); +} + +void ScExportTest::testCommentExportXLSX() +{ + //tdf#104729 FILESAVE OpenOffice do not save author of the comment during export to .xlsx + ScDocShellRef xShell = loadDoc("comment.", FORMAT_ODS); + CPPUNIT_ASSERT(xShell.is()); + + std::shared_ptr<utl::TempFile> pXPathFile + = ScBootstrapFixture::exportTo(&(*xShell), FORMAT_XLSX); + xmlDocUniquePtr pComments + = XPathHelper::parseExport(pXPathFile, m_xSFactory, "xl/comments1.xml"); + CPPUNIT_ASSERT(pComments); + + assertXPathContent(pComments, "/x:comments/x:authors/x:author[1]", "BAKO"); + assertXPath(pComments, "/x:comments/x:authors/x:author", 1); + + assertXPathContent(pComments, "/x:comments/x:commentList/x:comment/x:text/x:r/x:t", + "Komentarz"); + + xmlDocUniquePtr pVmlDrawing + = XPathHelper::parseExport(pXPathFile, m_xSFactory, "xl/drawings/vmlDrawing1.vml"); + CPPUNIT_ASSERT(pVmlDrawing); + + //assertXPath(pVmlDrawing, "/xml/v:shapetype", "coordsize", "21600,21600"); + assertXPath(pVmlDrawing, "/xml/v:shapetype", "spt", "202"); + assertXPath(pVmlDrawing, "/xml/v:shapetype/v:stroke", "joinstyle", "miter"); + const OUString sShapeTypeId = "#" + getXPath(pVmlDrawing, "/xml/v:shapetype", "id"); + + assertXPath(pVmlDrawing, "/xml/v:shape", "type", sShapeTypeId); + assertXPath(pVmlDrawing, "/xml/v:shape/v:shadow", "color", "black"); + assertXPath(pVmlDrawing, "/xml/v:shape/v:shadow", "obscured", "t"); + + //tdf#117274 fix MSO interoperability with the secret VML shape type id + assertXPath(pVmlDrawing, "/xml/v:shapetype", "id", "_x0000_t202"); + assertXPath(pVmlDrawing, "/xml/v:shape", "type", "#_x0000_t202"); + + xShell->DoClose(); +} + +void ScExportTest::testCommentExportXLSX_2_XLSX() +{ + //tdf#117287 FILESAVE XLSX: Comments always disappear after opening the exported XLSX file with Excel + ScDocShellRef xShell = loadDoc("tdf117287_comment.", FORMAT_XLSX); + CPPUNIT_ASSERT(xShell.is()); + + + ScDocument& rDoc = xShell->GetDocument(); + ScAddress aPosC9(2, 8, 0); + ScPostIt *pNote = rDoc.GetNote(aPosC9); + + CPPUNIT_ASSERT(pNote); + CPPUNIT_ASSERT(!pNote->IsCaptionShown()); + + pNote->ShowCaption(aPosC9, true); + + std::shared_ptr<utl::TempFile> pXPathFile + = ScBootstrapFixture::exportTo(&(*xShell), FORMAT_XLSX); + xmlDocUniquePtr pComments + = XPathHelper::parseExport(pXPathFile, m_xSFactory, "xl/comments1.xml"); + CPPUNIT_ASSERT(pComments); + + assertXPathContent(pComments, "/x:comments/x:commentList/x:comment/x:text/x:r/x:t", + "visible comment"); + + xmlDocUniquePtr pVmlDrawing + = XPathHelper::parseExport(pXPathFile, m_xSFactory, "xl/drawings/vmlDrawing1.vml"); + CPPUNIT_ASSERT(pVmlDrawing); + + assertXPath(pVmlDrawing, "/xml/v:shape/x:ClientData/x:Visible", 0); + + xShell->DoClose(); +} + +#if HAVE_MORE_FONTS +void ScExportTest::testCustomColumnWidthExportXLSX() +{ + //tdf#100946 FILESAVE Excel on macOS ignored column widths in XLSX last saved by LO + ScDocShellRef xShell = loadDoc("custom_column_width.", FORMAT_ODS); + CPPUNIT_ASSERT(xShell.is()); + + std::shared_ptr<utl::TempFile> pXPathFile = ScBootstrapFixture::exportTo(&(*xShell), FORMAT_XLSX); + xmlDocUniquePtr pSheet = XPathHelper::parseExport(pXPathFile, m_xSFactory, "xl/worksheets/sheet1.xml"); + CPPUNIT_ASSERT(pSheet); + + // tdf#124741: check that we export default width, otherwise the skipped columns would have + // wrong width. Previously defaultColWidth attribute was missing + double nDefWidth + = getXPath(pSheet, "/x:worksheet/x:sheetFormatPr", "defaultColWidth").toDouble(); + CPPUNIT_ASSERT_DOUBLES_EQUAL(11.53515625, nDefWidth, 0.01); + + // First column, has everything default (width in Calc: 1280), skipped + + // Second column, has custom width (width in Calc: 1225) + assertXPath(pSheet, "/x:worksheet/x:cols/x:col[1]", "hidden", "false"); + assertXPath(pSheet, "/x:worksheet/x:cols/x:col[1]", "outlineLevel", "0"); + assertXPath(pSheet, "/x:worksheet/x:cols/x:col[1]", "customWidth", "true"); + assertXPath(pSheet, "/x:worksheet/x:cols/x:col[1]", "collapsed", "false"); + assertXPath(pSheet, "/x:worksheet/x:cols/x:col[1]", "min", "2"); + assertXPath(pSheet, "/x:worksheet/x:cols/x:col[1]", "max", "2"); + + // Third column, has everything default (width in Calc: 1280), skipped + + // Fourth column has custom width. Columns from 4 to 7 are hidden + assertXPath(pSheet, "/x:worksheet/x:cols/x:col[2]", "hidden", "true"); + assertXPath(pSheet, "/x:worksheet/x:cols/x:col[2]", "outlineLevel", "0"); + assertXPath(pSheet, "/x:worksheet/x:cols/x:col[2]", "customWidth", "true"); + assertXPath(pSheet, "/x:worksheet/x:cols/x:col[2]", "collapsed", "false"); + assertXPath(pSheet, "/x:worksheet/x:cols/x:col[2]", "min", "4"); + assertXPath(pSheet, "/x:worksheet/x:cols/x:col[2]", "max", "4"); + + // 5th column has custom width. Columns from 4 to 7 are hidden + assertXPath(pSheet, "/x:worksheet/x:cols/x:col[3]", "hidden", "true"); + assertXPath(pSheet, "/x:worksheet/x:cols/x:col[3]", "outlineLevel", "0"); + assertXPath(pSheet, "/x:worksheet/x:cols/x:col[3]", "customWidth", "true"); + assertXPath(pSheet, "/x:worksheet/x:cols/x:col[3]", "collapsed", "false"); + assertXPath(pSheet, "/x:worksheet/x:cols/x:col[3]", "min", "5"); + assertXPath(pSheet, "/x:worksheet/x:cols/x:col[3]", "max", "5"); + + // 6th and 7th columns have default width and they are hidden + assertXPath(pSheet, "/x:worksheet/x:cols/x:col[4]", "hidden", "true"); + assertXPath(pSheet, "/x:worksheet/x:cols/x:col[4]", "outlineLevel", "0"); + assertXPath(pSheet, "/x:worksheet/x:cols/x:col[4]", "customWidth", "false"); + assertXPath(pSheet, "/x:worksheet/x:cols/x:col[4]", "collapsed", "false"); + assertXPath(pSheet, "/x:worksheet/x:cols/x:col[4]", "min", "6"); + assertXPath(pSheet, "/x:worksheet/x:cols/x:col[4]", "max", "7"); + + // 8th column has everything default - skipped + + // 9th column has custom width + assertXPath(pSheet, "/x:worksheet/x:cols/x:col[5]", "hidden", "false"); + assertXPath(pSheet, "/x:worksheet/x:cols/x:col[5]", "outlineLevel", "0"); + assertXPath(pSheet, "/x:worksheet/x:cols/x:col[5]", "customWidth", "true"); + assertXPath(pSheet, "/x:worksheet/x:cols/x:col[5]", "collapsed", "false"); + assertXPath(pSheet, "/x:worksheet/x:cols/x:col[5]", "min", "9"); + assertXPath(pSheet, "/x:worksheet/x:cols/x:col[5]", "max", "9"); + + // We expected that exactly 5 unique Nodes will be produced + assertXPath(pSheet, "/x:worksheet/x:cols/x:col", 5); + + assertXPath(pSheet, "/x:worksheet/x:sheetData/x:row[1]", "hidden", "false"); + assertXPath(pSheet, "/x:worksheet/x:sheetData/x:row[1]", "outlineLevel", "0"); + assertXPath(pSheet, "/x:worksheet/x:sheetData/x:row[1]", "collapsed", "false"); + assertXPath(pSheet, "/x:worksheet/x:sheetData/x:row[1]", "customFormat", "false"); + assertXPath(pSheet, "/x:worksheet/x:sheetData/x:row[1]", "customHeight", "false"); + + xShell->DoClose(); +} +#endif + +void ScExportTest::testXfDefaultValuesXLSX() +{ + //tdf#70565 FORMATTING: User Defined Custom Formatting is not applied during importing XLSX documents + ScDocShellRef xShell = loadDoc("xf_default_values.", FORMAT_XLSX); + CPPUNIT_ASSERT(xShell.is()); + + std::shared_ptr<utl::TempFile> pXPathFile = ScBootstrapFixture::exportTo(&(*xShell), FORMAT_XLSX); + xmlDocUniquePtr pSheet = XPathHelper::parseExport(pXPathFile, m_xSFactory, "xl/styles.xml"); + CPPUNIT_ASSERT(pSheet); + + // cellStyleXfs don't need xfId, so we need to make sure it is not saved + assertXPathNoAttribute(pSheet, "/x:styleSheet/x:cellStyleXfs/x:xf[1]", "xfId"); + + // Because numFmtId fontId fillId borderId xfId are not existing during import + // it should be created during export, with values set to "0" + assertXPath(pSheet, "/x:styleSheet/x:cellXfs/x:xf[1]", "xfId", "0"); + assertXPath(pSheet, "/x:styleSheet/x:cellXfs/x:xf[2]", "xfId", "0"); + assertXPath(pSheet, "/x:styleSheet/x:cellXfs/x:xf[3]", "xfId", "0"); + assertXPath(pSheet, "/x:styleSheet/x:cellXfs/x:xf[4]", "xfId", "0"); + + // We expected that exactly 15 cellXfs:xf Nodes will be produced + assertXPath(pSheet, "/x:styleSheet/x:cellXfs/x:xf", 14); + + xShell->DoClose(); +} + +namespace { + +// TODO where to put this? +class Resetter +{ +private: + std::function<void ()> m_Func; + +public: + Resetter(std::function<void ()> const& rFunc) + : m_Func(rFunc) + { + } + ~Resetter() + { + try + { + m_Func(); + } + catch (...) // has to be reliable + { + fprintf(stderr, "resetter failed with exception\n"); + abort(); + } + } +}; + +} // namespace + +static auto verifySpreadsheet13(char const*const pTestName, ScDocShellRef& pShell) -> void +{ + ScDocument const& rDoc(pShell->GetDocument()); + // OFFICE-2173 table:tab-color + CPPUNIT_ASSERT_EQUAL_MESSAGE(pTestName, Color(0xff3838), rDoc.GetTabBgColor(0)); + // OFFICE-3857 table:scale-to-X/table:scale-to-Y + OUString styleName = rDoc.GetPageStyle(0); + ScStyleSheetPool * pStylePool = rDoc.GetStyleSheetPool(); + SfxStyleSheetBase * pStyleSheet = pStylePool->Find(styleName, SfxStyleFamily::Page); + CPPUNIT_ASSERT_MESSAGE(pTestName, pStyleSheet); + + SfxItemSet const& rSet = pStyleSheet->GetItemSet(); + ScPageScaleToItem const& rItem(rSet.Get(ATTR_PAGE_SCALETO)); + CPPUNIT_ASSERT_EQUAL_MESSAGE(pTestName, sal_uInt16(2), rItem.GetWidth()); + CPPUNIT_ASSERT_EQUAL_MESSAGE(pTestName, sal_uInt16(3), rItem.GetHeight()); +} + +void ScExportTest::testODF13() +{ + // import + ScDocShellRef pShell = loadDoc("spreadsheet13e.", FORMAT_ODS); + + // check model + verifySpreadsheet13("import", pShell); + + Resetter _([]() { + std::shared_ptr<comphelper::ConfigurationChanges> pBatch( + comphelper::ConfigurationChanges::create()); + officecfg::Office::Common::Save::ODF::DefaultVersion::set(3, pBatch); + return pBatch->commit(); + }); + + { + // export ODF 1.3 + std::shared_ptr<comphelper::ConfigurationChanges> pBatch( + comphelper::ConfigurationChanges::create()); + officecfg::Office::Common::Save::ODF::DefaultVersion::set(10, pBatch); + pBatch->commit(); + + std::shared_ptr<utl::TempFile> pXPathFile = ScBootstrapFixture::exportTo(&(*pShell), FORMAT_ODS); + + // check XML + xmlDocUniquePtr pContentXml = XPathHelper::parseExport(pXPathFile, m_xSFactory, "content.xml"); + assertXPath(pContentXml, "/office:document-content/office:automatic-styles/style:style/style:table-properties[@table:tab-color='#ff3838']"); + xmlDocUniquePtr pStylesXml = XPathHelper::parseExport(pXPathFile, m_xSFactory, "styles.xml"); + assertXPath(pStylesXml, "/office:document-styles/office:automatic-styles/style:page-layout/style:page-layout-properties[@style:scale-to-X='2']"); + assertXPath(pStylesXml, "/office:document-styles/office:automatic-styles/style:page-layout/style:page-layout-properties[@style:scale-to-Y='3']"); + + // reload + pShell = load(pXPathFile->GetURL(), "calc8", OUString(), OUString(), ODS_FORMAT_TYPE, SotClipboardFormatId::STARCALC_8); + + // check model + verifySpreadsheet13("1.3 reload", pShell); + } + { + // export ODF 1.2 Extended + std::shared_ptr<comphelper::ConfigurationChanges> pBatch( + comphelper::ConfigurationChanges::create()); + officecfg::Office::Common::Save::ODF::DefaultVersion::set(9, pBatch); + pBatch->commit(); + + std::shared_ptr<utl::TempFile> pXPathFile = ScBootstrapFixture::saveAs(&(*pShell), FORMAT_ODS); + pShell->DoClose(); + + // check XML + xmlDocUniquePtr pContentXml = XPathHelper::parseExport(pXPathFile, m_xSFactory, "content.xml"); + assertXPath(pContentXml, "/office:document-content/office:automatic-styles/style:style/style:table-properties[@tableooo:tab-color='#ff3838']"); + xmlDocUniquePtr pStylesXml = XPathHelper::parseExport(pXPathFile, m_xSFactory, "styles.xml"); + assertXPath(pStylesXml, "/office:document-styles/office:automatic-styles/style:page-layout/style:page-layout-properties[@loext:scale-to-X='2']"); + assertXPath(pStylesXml, "/office:document-styles/office:automatic-styles/style:page-layout/style:page-layout-properties[@loext:scale-to-Y='3']"); + + // reload + pShell = load(pXPathFile->GetURL(), "calc8", OUString(), OUString(), ODS_FORMAT_TYPE, SotClipboardFormatId::STARCALC_8); + + // check model + verifySpreadsheet13("1.2 Extended reload", pShell); + } + { + // export ODF 1.2 + std::shared_ptr<comphelper::ConfigurationChanges> pBatch( + comphelper::ConfigurationChanges::create()); + officecfg::Office::Common::Save::ODF::DefaultVersion::set(4, pBatch); + pBatch->commit(); + + std::shared_ptr<utl::TempFile> pXPathFile = ScBootstrapFixture::saveAs(&(*pShell), FORMAT_ODS); + pShell->DoClose(); + + // check XML + xmlDocUniquePtr pContentXml = XPathHelper::parseExport(pXPathFile, m_xSFactory, "content.xml"); + assertXPathNoAttribute(pContentXml, "/office:document-content/office:automatic-styles/style:style/style:table-properties", "tab-color"); + xmlDocUniquePtr pStylesXml = XPathHelper::parseExport(pXPathFile, m_xSFactory, "styles.xml"); + assertXPathNoAttribute(pStylesXml, "/office:document-styles/office:automatic-styles/style:page-layout[1]/style:page-layout-properties", "scale-to-X"); + assertXPathNoAttribute(pStylesXml, "/office:document-styles/office:automatic-styles/style:page-layout[1]/style:page-layout-properties", "scale-to-Y"); + + // don't reload - no point + } +} + +void ScExportTest::testColumnWidthResaveXLSX() +{ + // tdf#91475 FILESAVE: Column width is not preserved in XLSX / after round trip. + // Test if after resave .xlsx file, columns width is identical with previous one + ScDocShellRef xShell = loadDoc("different-column-width-excel2010.", FORMAT_XLSX); + CPPUNIT_ASSERT(xShell.is()); + + std::shared_ptr<utl::TempFile> pXPathFile = ScBootstrapFixture::exportTo(&(*xShell), FORMAT_XLSX); + xmlDocUniquePtr pSheet = XPathHelper::parseExport(pXPathFile, m_xSFactory, "xl/worksheets/sheet1.xml"); + CPPUNIT_ASSERT(pSheet); + + // In original Excel document the width is "24" + assertXPath(pSheet, "/x:worksheet/x:cols/x:col[1]", "width", "24"); + assertXPath(pSheet, "/x:worksheet/x:cols/x:col[1]", "customWidth", "true"); + + // In original Excel document the width is "12" + assertXPath(pSheet, "/x:worksheet/x:cols/x:col[2]", "width", "12"); + assertXPath(pSheet, "/x:worksheet/x:cols/x:col[2]", "customWidth", "true"); + + // In original Excel document the width is "6" + assertXPath(pSheet, "/x:worksheet/x:cols/x:col[3]", "width", "6"); + assertXPath(pSheet, "/x:worksheet/x:cols/x:col[3]", "customWidth", "true"); + + // In original Excel document the width is "1" + assertXPath(pSheet, "/x:worksheet/x:cols/x:col[4]", "width", "1"); + assertXPath(pSheet, "/x:worksheet/x:cols/x:col[4]", "customWidth", "true"); + + // In original Excel document the width is "250" + assertXPath(pSheet, "/x:worksheet/x:cols/x:col[5]", "width", "250"); + assertXPath(pSheet, "/x:worksheet/x:cols/x:col[5]", "customWidth", "true"); + + assertXPath(pSheet, "/x:worksheet/x:cols/x:col", 5); + + xShell->DoClose(); +} + +#if HAVE_MORE_FONTS +void ScExportTest::testColumnWidthExportFromODStoXLSX() +{ + // tdf#91475 FILESAVE: Column width is not preserved in XLSX / after round trip. + // Test if after export .ods to .xlsx format, displayed columns width + // is identical with previous (.ods) one + + ScDocShellRef xShell = loadDoc("different-column-width.", FORMAT_ODS); + + CPPUNIT_ASSERT( xShell.is() ); + + ScDocument& rOdsDoc = xShell->GetDocument(); + + // Col 1, Tab 0 (Column width 2.00 in) + sal_uInt16 nExpectedColumn0Width = rOdsDoc.GetColWidth(static_cast<SCCOL>(0), static_cast<SCTAB>(0), false); + CPPUNIT_ASSERT_EQUAL( static_cast< sal_uInt16 >( 2880 ), nExpectedColumn0Width ); + + // Col 2, Tab 0 (Column width 1.00 in) + sal_uInt16 nExpectedColumn1Width = rOdsDoc.GetColWidth(static_cast<SCCOL>(1), static_cast<SCTAB>(0), false); + CPPUNIT_ASSERT_EQUAL( static_cast< sal_uInt16 >( 1440 ), nExpectedColumn1Width ); + + // Col 3, Tab 0 (Column width 0.50 in) + sal_uInt16 nExpectedColumn2Width = rOdsDoc.GetColWidth(static_cast<SCCOL>(2), static_cast<SCTAB>(0), false); + CPPUNIT_ASSERT_EQUAL( static_cast< sal_uInt16 >( 720 ), nExpectedColumn2Width ); + + // Col 4, Tab 0 (Column width 0.25 in) + sal_uInt16 nExpectedColumn3Width = rOdsDoc.GetColWidth(static_cast<SCCOL>(3), static_cast<SCTAB>(0), false); + CPPUNIT_ASSERT_EQUAL( static_cast< sal_uInt16 >( 360 ), nExpectedColumn3Width ); + + // Col 5, Tab 0 (Column width 13.57 in) + sal_uInt16 nExpectedColumn4Width = rOdsDoc.GetColWidth(static_cast<SCCOL>(4), static_cast<SCTAB>(0), false); + CPPUNIT_ASSERT_EQUAL( static_cast< sal_uInt16 >( 19539 ), nExpectedColumn4Width ); + + // Export to .xlsx and compare column width with the .ods + // We expect that column width from .ods will be exactly the same as imported from .xlsx + + ScDocShellRef xXlsxDocSh = saveAndReload( xShell.get(), FORMAT_XLSX ); + CPPUNIT_ASSERT( xXlsxDocSh.is() ); + + ScDocument& rDoc = xXlsxDocSh->GetDocument(); + + // Col 1, Tab 0 + sal_uInt16 nCalcWidth; + nCalcWidth = rDoc.GetColWidth(static_cast<SCCOL>(0), static_cast<SCTAB>(0), false); + CPPUNIT_ASSERT_EQUAL( nExpectedColumn0Width, nCalcWidth ); + + // Col 2, Tab 0 + nCalcWidth = rDoc.GetColWidth(static_cast<SCCOL>(1), static_cast<SCTAB>(0), false); + CPPUNIT_ASSERT_EQUAL( nExpectedColumn1Width, nCalcWidth ); + + // Col 3, Tab 0 + nCalcWidth = rDoc.GetColWidth(static_cast<SCCOL>(2), static_cast<SCTAB>(0), false); + CPPUNIT_ASSERT_EQUAL( nExpectedColumn2Width, nCalcWidth ); + + // Col 4, Tab 0 + nCalcWidth = rDoc.GetColWidth(static_cast<SCCOL>(3), static_cast<SCTAB>(0), false); + CPPUNIT_ASSERT_EQUAL( nExpectedColumn3Width, nCalcWidth ); + + // Col 5, Tab 0 + nCalcWidth = rDoc.GetColWidth(static_cast<SCCOL>(4), static_cast<SCTAB>(0), false); + CPPUNIT_ASSERT_EQUAL( nExpectedColumn4Width, nCalcWidth ); + + xXlsxDocSh->DoClose(); +} +#endif + +void ScExportTest::testOutlineExportXLSX() +{ + //tdf#100347 FILESAVE FILEOPEN after exporting to .xlsx format grouping are lost + //tdf#51524 FILESAVE .xlsx and.xls looses width information for hidden/collapsed grouped columns + ScDocShellRef xShell = loadDoc("outline.", FORMAT_ODS); + CPPUNIT_ASSERT(xShell.is()); + + std::shared_ptr<utl::TempFile> pXPathFile = ScBootstrapFixture::exportTo(&(*xShell), FORMAT_XLSX); + xmlDocUniquePtr pSheet = XPathHelper::parseExport(pXPathFile, m_xSFactory, "xl/worksheets/sheet1.xml"); + CPPUNIT_ASSERT(pSheet); + + // Maximum Outline Row is 4 for this document + assertXPath(pSheet, "/x:worksheet/x:sheetFormatPr", "outlineLevelRow", "4"); + // Maximum Outline Column is 4 for this document + assertXPath(pSheet, "/x:worksheet/x:sheetFormatPr", "outlineLevelCol", "4"); + + // First XML node, creates two columns (from min=1 to max=2) + assertXPath(pSheet, "/x:worksheet/x:cols/x:col[1]", "hidden", "false"); + assertXPath(pSheet, "/x:worksheet/x:cols/x:col[1]", "outlineLevel", "1"); + assertXPath(pSheet, "/x:worksheet/x:cols/x:col[1]", "collapsed", "false"); + assertXPath(pSheet, "/x:worksheet/x:cols/x:col[1]", "min", "1"); + assertXPath(pSheet, "/x:worksheet/x:cols/x:col[1]", "max", "2"); + + assertXPath(pSheet, "/x:worksheet/x:cols/x:col[2]", "hidden", "true"); + assertXPath(pSheet, "/x:worksheet/x:cols/x:col[2]", "outlineLevel", "2"); + assertXPath(pSheet, "/x:worksheet/x:cols/x:col[2]", "collapsed", "false"); + assertXPath(pSheet, "/x:worksheet/x:cols/x:col[2]", "min", "3"); + assertXPath(pSheet, "/x:worksheet/x:cols/x:col[2]", "max", "3"); + + // Column 4 has custom width and it is hidden. We need to make sure that it is created + assertXPath(pSheet, "/x:worksheet/x:cols/x:col[3]", "hidden", "true"); + assertXPath(pSheet, "/x:worksheet/x:cols/x:col[3]", "outlineLevel", "2"); + assertXPath(pSheet, "/x:worksheet/x:cols/x:col[3]", "collapsed", "false"); + assertXPath(pSheet, "/x:worksheet/x:cols/x:col[3]", "min", "4"); + assertXPath(pSheet, "/x:worksheet/x:cols/x:col[3]", "max", "4"); + + assertXPath(pSheet, "/x:worksheet/x:cols/x:col[4]", "hidden", "true"); + assertXPath(pSheet, "/x:worksheet/x:cols/x:col[4]", "outlineLevel", "3"); + assertXPath(pSheet, "/x:worksheet/x:cols/x:col[4]", "collapsed", "false"); + assertXPath(pSheet, "/x:worksheet/x:cols/x:col[4]", "min", "5"); + assertXPath(pSheet, "/x:worksheet/x:cols/x:col[4]", "max", "6"); + + assertXPath(pSheet, "/x:worksheet/x:cols/x:col[5]", "hidden", "true"); + assertXPath(pSheet, "/x:worksheet/x:cols/x:col[5]", "outlineLevel", "4"); + assertXPath(pSheet, "/x:worksheet/x:cols/x:col[5]", "collapsed", "false"); + assertXPath(pSheet, "/x:worksheet/x:cols/x:col[5]", "min", "7"); + assertXPath(pSheet, "/x:worksheet/x:cols/x:col[5]", "max", "7"); + + // Column 8 has custom width and it is hidden. We need to make sure that it is created + assertXPath(pSheet, "/x:worksheet/x:cols/x:col[6]", "hidden", "true"); + assertXPath(pSheet, "/x:worksheet/x:cols/x:col[6]", "outlineLevel", "4"); + assertXPath(pSheet, "/x:worksheet/x:cols/x:col[6]", "collapsed", "false"); + assertXPath(pSheet, "/x:worksheet/x:cols/x:col[6]", "min", "8"); + assertXPath(pSheet, "/x:worksheet/x:cols/x:col[6]", "max", "8"); + + assertXPath(pSheet, "/x:worksheet/x:cols/x:col[7]", "hidden", "true"); + assertXPath(pSheet, "/x:worksheet/x:cols/x:col[7]", "outlineLevel", "4"); + assertXPath(pSheet, "/x:worksheet/x:cols/x:col[7]", "collapsed", "false"); + assertXPath(pSheet, "/x:worksheet/x:cols/x:col[7]", "min", "9"); + assertXPath(pSheet, "/x:worksheet/x:cols/x:col[7]", "max", "19"); + + assertXPath(pSheet, "/x:worksheet/x:cols/x:col[8]", "hidden", "true"); + assertXPath(pSheet, "/x:worksheet/x:cols/x:col[8]", "outlineLevel", "3"); + assertXPath(pSheet, "/x:worksheet/x:cols/x:col[8]", "collapsed", "true"); + assertXPath(pSheet, "/x:worksheet/x:cols/x:col[8]", "min", "20"); + assertXPath(pSheet, "/x:worksheet/x:cols/x:col[8]", "max", "20"); + + assertXPath(pSheet, "/x:worksheet/x:cols/x:col[9]", "hidden", "true"); + assertXPath(pSheet, "/x:worksheet/x:cols/x:col[9]", "outlineLevel", "3"); + assertXPath(pSheet, "/x:worksheet/x:cols/x:col[9]", "collapsed", "false"); + assertXPath(pSheet, "/x:worksheet/x:cols/x:col[9]", "min", "21"); + assertXPath(pSheet, "/x:worksheet/x:cols/x:col[9]", "max", "21"); + + assertXPath(pSheet, "/x:worksheet/x:cols/x:col[10]", "hidden", "true"); + assertXPath(pSheet, "/x:worksheet/x:cols/x:col[10]", "outlineLevel", "2"); + assertXPath(pSheet, "/x:worksheet/x:cols/x:col[10]", "collapsed", "false"); + assertXPath(pSheet, "/x:worksheet/x:cols/x:col[10]", "min", "22"); + assertXPath(pSheet, "/x:worksheet/x:cols/x:col[10]", "max", "23"); + + assertXPath(pSheet, "/x:worksheet/x:cols/x:col[11]", "hidden", "false"); + assertXPath(pSheet, "/x:worksheet/x:cols/x:col[11]", "outlineLevel", "1"); + assertXPath(pSheet, "/x:worksheet/x:cols/x:col[11]", "collapsed", "true"); + assertXPath(pSheet, "/x:worksheet/x:cols/x:col[11]", "min", "24"); + assertXPath(pSheet, "/x:worksheet/x:cols/x:col[11]", "max", "24"); + + assertXPath(pSheet, "/x:worksheet/x:cols/x:col[12]", "hidden", "false"); + assertXPath(pSheet, "/x:worksheet/x:cols/x:col[12]", "outlineLevel", "1"); + assertXPath(pSheet, "/x:worksheet/x:cols/x:col[12]", "collapsed", "false"); + assertXPath(pSheet, "/x:worksheet/x:cols/x:col[12]", "min", "25"); + assertXPath(pSheet, "/x:worksheet/x:cols/x:col[12]", "max", "26"); + + // We expected that exactly 12 unique Nodes will be produced + assertXPath(pSheet, "/x:worksheet/x:cols/x:col", 12); + + // First row is empty and default so it is not written into XML file + // so we need to save 29 rows, as it provides information about outLineLevel + assertXPath(pSheet, "/x:worksheet/x:sheetData/x:row[1]", "r", "2"); + assertXPath(pSheet, "/x:worksheet/x:sheetData/x:row[1]", "hidden", "false"); + assertXPath(pSheet, "/x:worksheet/x:sheetData/x:row[1]", "outlineLevel", "1"); + assertXPath(pSheet, "/x:worksheet/x:sheetData/x:row[1]", "collapsed", "false"); + assertXPath(pSheet, "/x:worksheet/x:sheetData/x:row[2]", "r", "3"); + assertXPath(pSheet, "/x:worksheet/x:sheetData/x:row[2]", "hidden", "false"); + assertXPath(pSheet, "/x:worksheet/x:sheetData/x:row[2]", "outlineLevel", "2"); + assertXPath(pSheet, "/x:worksheet/x:sheetData/x:row[2]", "collapsed", "false"); + assertXPath(pSheet, "/x:worksheet/x:sheetData/x:row[3]", "r", "4"); + assertXPath(pSheet, "/x:worksheet/x:sheetData/x:row[3]", "hidden", "false"); + assertXPath(pSheet, "/x:worksheet/x:sheetData/x:row[3]", "outlineLevel", "2"); + assertXPath(pSheet, "/x:worksheet/x:sheetData/x:row[3]", "collapsed", "false"); + assertXPath(pSheet, "/x:worksheet/x:sheetData/x:row[4]", "r", "5"); + assertXPath(pSheet, "/x:worksheet/x:sheetData/x:row[4]", "hidden", "false"); + assertXPath(pSheet, "/x:worksheet/x:sheetData/x:row[4]", "outlineLevel", "3"); + assertXPath(pSheet, "/x:worksheet/x:sheetData/x:row[4]", "collapsed", "false"); + assertXPath(pSheet, "/x:worksheet/x:sheetData/x:row[5]", "r", "6"); + assertXPath(pSheet, "/x:worksheet/x:sheetData/x:row[5]", "hidden", "false"); + assertXPath(pSheet, "/x:worksheet/x:sheetData/x:row[5]", "outlineLevel", "3"); + assertXPath(pSheet, "/x:worksheet/x:sheetData/x:row[5]", "collapsed", "false"); + assertXPath(pSheet, "/x:worksheet/x:sheetData/x:row[6]", "r", "7"); + assertXPath(pSheet, "/x:worksheet/x:sheetData/x:row[6]", "hidden", "true"); + assertXPath(pSheet, "/x:worksheet/x:sheetData/x:row[6]", "outlineLevel", "4"); + assertXPath(pSheet, "/x:worksheet/x:sheetData/x:row[6]", "collapsed", "false"); + assertXPath(pSheet, "/x:worksheet/x:sheetData/x:row[7]", "r", "8"); + assertXPath(pSheet, "/x:worksheet/x:sheetData/x:row[7]", "hidden", "true"); + assertXPath(pSheet, "/x:worksheet/x:sheetData/x:row[7]", "outlineLevel", "4"); + assertXPath(pSheet, "/x:worksheet/x:sheetData/x:row[7]", "collapsed", "false"); + assertXPath(pSheet, "/x:worksheet/x:sheetData/x:row[8]", "r", "9"); + assertXPath(pSheet, "/x:worksheet/x:sheetData/x:row[8]", "hidden", "true"); + assertXPath(pSheet, "/x:worksheet/x:sheetData/x:row[8]", "outlineLevel", "4"); + assertXPath(pSheet, "/x:worksheet/x:sheetData/x:row[8]", "collapsed", "false"); + // Next rows are the same as the previous one but it needs to be preserved, + // as they contain information about outlineLevel + assertXPath(pSheet, "/x:worksheet/x:sheetData/x:row[20]", "r", "21"); + assertXPath(pSheet, "/x:worksheet/x:sheetData/x:row[20]", "hidden", "true"); + assertXPath(pSheet, "/x:worksheet/x:sheetData/x:row[20]", "outlineLevel", "4"); + assertXPath(pSheet, "/x:worksheet/x:sheetData/x:row[20]", "collapsed", "false"); + assertXPath(pSheet, "/x:worksheet/x:sheetData/x:row[21]", "r", "22"); + assertXPath(pSheet, "/x:worksheet/x:sheetData/x:row[21]", "hidden", "false"); + assertXPath(pSheet, "/x:worksheet/x:sheetData/x:row[21]", "outlineLevel", "3"); + assertXPath(pSheet, "/x:worksheet/x:sheetData/x:row[21]", "collapsed", "true"); + assertXPath(pSheet, "/x:worksheet/x:sheetData/x:row[22]", "r", "23"); + assertXPath(pSheet, "/x:worksheet/x:sheetData/x:row[22]", "hidden", "false"); + assertXPath(pSheet, "/x:worksheet/x:sheetData/x:row[22]", "outlineLevel", "3"); + assertXPath(pSheet, "/x:worksheet/x:sheetData/x:row[22]", "collapsed", "false"); + + // We expected that exactly 29 Row Nodes will be produced + assertXPath(pSheet, "/x:worksheet/x:sheetData/x:row", 29); + + xShell->DoClose(); +} + +void ScExportTest::testAllRowsHiddenXLSX() +{ + ScDocShellRef xOrigDocSh = loadDoc("tdf105840_allRowsHidden.", FORMAT_XLSX); + CPPUNIT_ASSERT(xOrigDocSh.is()); + + std::shared_ptr<utl::TempFile> pXPathFile = ScBootstrapFixture::exportTo(&(*xOrigDocSh), FORMAT_XLSX); + xmlDocUniquePtr pSheet = XPathHelper::parseExport(pXPathFile, m_xSFactory, "xl/worksheets/sheet1.xml"); + CPPUNIT_ASSERT(pSheet); + assertXPath(pSheet, "/x:worksheet/x:sheetFormatPr", "zeroHeight", "true" ); + assertXPath(pSheet, "/x:worksheet/x:sheetData/x:row", 0); + + xOrigDocSh->DoClose(); +} + +void ScExportTest::testHiddenEmptyRowsXLSX() +{ + //tdf#98106 FILESAVE: Hidden and empty rows became visible when export to .XLSX + ScDocShellRef xShell = loadDoc("hidden-empty-rows.", FORMAT_ODS); + CPPUNIT_ASSERT(xShell.is()); + + std::shared_ptr<utl::TempFile> pXPathFile = ScBootstrapFixture::exportTo(&(*xShell), FORMAT_XLSX); + xmlDocUniquePtr pSheet = XPathHelper::parseExport(pXPathFile, m_xSFactory, "xl/worksheets/sheet1.xml"); + CPPUNIT_ASSERT(pSheet); + + assertXPath(pSheet, "/x:worksheet/x:sheetFormatPr", "zeroHeight", "false" ); + assertXPath(pSheet, "/x:worksheet/x:sheetData/x:row[1]", "hidden", "true"); + assertXPath(pSheet, "/x:worksheet/x:sheetData/x:row[2]", "hidden", "true"); + assertXPath(pSheet, "/x:worksheet/x:sheetData/x:row[3]", "hidden", "true"); + assertXPath(pSheet, "/x:worksheet/x:sheetData/x:row[4]", "hidden", "false"); + + xShell->DoClose(); +} + +void ScExportTest::testLandscapeOrientationXLSX() +{ + //tdf#48767 - Landscape page orientation is not loaded from .xlsx format with MS Excel, after export with Libre Office + ScDocShellRef xShell = loadDoc("hidden-empty-rows.", FORMAT_ODS); + CPPUNIT_ASSERT(xShell.is()); + + std::shared_ptr<utl::TempFile> pXPathFile = ScBootstrapFixture::exportTo(&(*xShell), FORMAT_XLSX); + xmlDocUniquePtr pSheet = XPathHelper::parseExport(pXPathFile, m_xSFactory, "xl/worksheets/sheet1.xml"); + CPPUNIT_ASSERT(pSheet); + + // the usePrinterDefaults cannot be saved to allow opening sheets in Landscape mode via MS Excel + assertXPathNoAttribute(pSheet, "/x:worksheet/x:pageSetup", "usePrinterDefaults"); + assertXPath(pSheet, "/x:worksheet/x:pageSetup", "orientation", "landscape"); + + xShell->DoClose(); +} + +void ScExportTest::testDataBarExportXLSX() +{ + ScDocShellRef xShell = loadDoc("databar.", FORMAT_XLSX); + CPPUNIT_ASSERT(xShell.is()); + + ScDocShellRef xDocSh = saveAndReload(xShell.get(), FORMAT_XLSX); + CPPUNIT_ASSERT(xDocSh.is()); + + ScDocument& rDoc = xDocSh->GetDocument(); + + testDataBar_Impl(rDoc); + + xDocSh->DoClose(); +} + +void ScExportTest::testMiscRowHeightExport() +{ + static const TestParam::RowData DfltRowData[] = + { + { 0, 4, 0, 529, 0, false }, + { 5, 10, 0, 1058, 0, false }, + { 17, 20, 0, 1767, 0, false }, + // check last couple of row in document to ensure + // they are 5.29mm ( effective default row xlsx height ) + { 1048573, 1048575, 0, 529, 0, false }, + }; + + static const TestParam::RowData EmptyRepeatRowData[] = + { + // rows 0-4, 5-10, 17-20 are all set at various + // heights, there is no content in the rows, there + // was a bug where only the first row ( of repeated rows ) + // was set after export + { 0, 4, 0, 529, 0, false }, + { 5, 10, 0, 1058, 0, false }, + { 17, 20, 0, 1767, 0, false }, + }; + + TestParam aTestValues[] = + { + // Checks that some distributed ( non-empty ) heights remain set after export (roundtrip) + // additionally there is effectively a default row height ( 5.29 mm ). So we test the + // unset rows at the end of the document to ensure the effective xlsx default height + // is set there too. + { "miscrowheights.", FORMAT_XLSX, FORMAT_XLSX, SAL_N_ELEMENTS(DfltRowData), DfltRowData }, + // Checks that some distributed ( non-empty ) heights remain set after export (to xls) + { "miscrowheights.", FORMAT_XLSX, FORMAT_XLS, SAL_N_ELEMENTS(DfltRowData), DfltRowData }, + // Checks that repreated rows ( of various heights ) remain set after export ( to xlsx ) + { "miscemptyrepeatedrowheights.", FORMAT_ODS, FORMAT_XLSX, SAL_N_ELEMENTS(EmptyRepeatRowData), EmptyRepeatRowData }, + // Checks that repreated rows ( of various heights ) remain set after export ( to xls ) + { "miscemptyrepeatedrowheights.", FORMAT_ODS, FORMAT_XLS, SAL_N_ELEMENTS(EmptyRepeatRowData), EmptyRepeatRowData }, + }; + miscRowHeightsTest( aTestValues, SAL_N_ELEMENTS(aTestValues) ); +} + +namespace { + +void setAttribute( ScFieldEditEngine& rEE, sal_Int32 nPara, sal_Int32 nStart, sal_Int32 nEnd, sal_uInt16 nType, Color nColor = COL_BLACK ) +{ + ESelection aSel; + aSel.nStartPara = aSel.nEndPara = nPara; + aSel.nStartPos = nStart; + aSel.nEndPos = nEnd; + + SfxItemSet aItemSet = rEE.GetEmptyItemSet(); + switch (nType) + { + case EE_CHAR_WEIGHT: + { + SvxWeightItem aWeight(WEIGHT_BOLD, nType); + aItemSet.Put(aWeight); + rEE.QuickSetAttribs(aItemSet, aSel); + } + break; + case EE_CHAR_ITALIC: + { + SvxPostureItem aItalic(ITALIC_NORMAL, nType); + aItemSet.Put(aItalic); + rEE.QuickSetAttribs(aItemSet, aSel); + } + break; + case EE_CHAR_STRIKEOUT: + { + SvxCrossedOutItem aCrossOut(STRIKEOUT_SINGLE, nType); + aItemSet.Put(aCrossOut); + rEE.QuickSetAttribs(aItemSet, aSel); + } + break; + case EE_CHAR_OVERLINE: + { + SvxOverlineItem aItem(LINESTYLE_DOUBLE, nType); + aItemSet.Put(aItem); + rEE.QuickSetAttribs(aItemSet, aSel); + } + break; + case EE_CHAR_UNDERLINE: + { + SvxUnderlineItem aItem(LINESTYLE_DOUBLE, nType); + aItemSet.Put(aItem); + rEE.QuickSetAttribs(aItemSet, aSel); + } + break; + case EE_CHAR_COLOR: + { + SvxColorItem aItem(nColor, nType); + aItemSet.Put(aItem); + rEE.QuickSetAttribs(aItemSet, aSel); + } + break; + default: + ; + } +} + +void setFont( ScFieldEditEngine& rEE, sal_Int32 nPara, sal_Int32 nStart, sal_Int32 nEnd, const OUString& rFontName ) +{ + ESelection aSel; + aSel.nStartPara = aSel.nEndPara = nPara; + aSel.nStartPos = nStart; + aSel.nEndPos = nEnd; + + SfxItemSet aItemSet = rEE.GetEmptyItemSet(); + SvxFontItem aItem(FAMILY_MODERN, rFontName, "", PITCH_VARIABLE, RTL_TEXTENCODING_UTF8, EE_CHAR_FONTINFO); + aItemSet.Put(aItem); + rEE.QuickSetAttribs(aItemSet, aSel); +} + +void setEscapement( ScFieldEditEngine& rEE, sal_Int32 nPara, sal_Int32 nStart, sal_Int32 nEnd, short nEsc, sal_uInt8 nRelSize ) +{ + ESelection aSel; + aSel.nStartPara = aSel.nEndPara = nPara; + aSel.nStartPos = nStart; + aSel.nEndPos = nEnd; + + SfxItemSet aItemSet = rEE.GetEmptyItemSet(); + SvxEscapementItem aItem(nEsc, nRelSize, EE_CHAR_ESCAPEMENT); + aItemSet.Put(aItem); + rEE.QuickSetAttribs(aItemSet, aSel); +} + +} + +void ScExportTest::testNamedRangeBugfdo62729() +{ + ScDocShellRef xShell = loadDoc("fdo62729.", FORMAT_ODS); + CPPUNIT_ASSERT(xShell.is()); + ScDocument& rDoc = xShell->GetDocument(); + + ScRangeName* pNames = rDoc.GetRangeName(); + //should be just a single named range + CPPUNIT_ASSERT_EQUAL(size_t(1), pNames->size()); + rDoc.DeleteTab(0); + //should be still a single named range + CPPUNIT_ASSERT_EQUAL(size_t(1), pNames->size()); + ScDocShellRef xDocSh = saveAndReload(xShell.get(), FORMAT_ODS); + xShell->DoClose(); + + CPPUNIT_ASSERT(xDocSh.is()); + ScDocument& rDoc2 = xDocSh->GetDocument(); + + pNames = rDoc2.GetRangeName(); + //after reload should still have a named range + CPPUNIT_ASSERT_EQUAL(size_t(1), pNames->size()); + + xDocSh->DoClose(); +} + +void ScExportTest::testBuiltinRangesXLSX() +{ + ScDocShellRef xShell = loadDoc("built-in_ranges.", FORMAT_XLSX); + CPPUNIT_ASSERT(xShell.is()); + ScDocShellRef xDocSh = saveAndReload(xShell.get(), FORMAT_XLSX); + CPPUNIT_ASSERT(xDocSh.is()); + xShell->DoClose(); + + xmlDocUniquePtr pDoc = XPathHelper::parseExport2(*this, *xDocSh, m_xSFactory, "xl/workbook.xml", FORMAT_XLSX); + CPPUNIT_ASSERT(pDoc); + + //assert the existing OOXML built-in names are still there + assertXPathContent(pDoc, "/x:workbook/x:definedNames/x:definedName[@name='_xlnm._FilterDatabase'][@localSheetId='0']", "'Sheet1 Test'!$A$1:$A$5"); + assertXPathContent(pDoc, "/x:workbook/x:definedNames/x:definedName[@name='_xlnm._FilterDatabase'][@localSheetId='1']", "'Sheet2 Test'!$K$10:$K$14"); + assertXPathContent(pDoc, "/x:workbook/x:definedNames/x:definedName[@name='_xlnm.Print_Area'][@localSheetId='0']", "'Sheet1 Test'!$A$1:$A$5"); + assertXPathContent(pDoc, "/x:workbook/x:definedNames/x:definedName[@name='_xlnm.Print_Area'][@localSheetId='1']", "'Sheet2 Test'!$K$10:$M$18"); + + //...and that no extra ones are added (see tdf#112571) + assertXPath(pDoc, "/x:workbook/x:definedNames/x:definedName[@name='_xlnm._FilterDatabase_0'][@localSheetId='0']", 0); + assertXPath(pDoc, "/x:workbook/x:definedNames/x:definedName[@name='_xlnm._FilterDatabase_0'][@localSheetId='1']", 0); + assertXPath(pDoc, "/x:workbook/x:definedNames/x:definedName[@name='_xlnm.Print_Area_0'][@localSheetId='0']", 0); + assertXPath(pDoc, "/x:workbook/x:definedNames/x:definedName[@name='_xlnm.Print_Area_0'][@localSheetId='1']", 0); + + xDocSh->DoClose(); +} + +void ScExportTest::testRichTextExportODS() +{ + struct + { + static bool isBold(const editeng::Section& rAttr) + { + return std::any_of(rAttr.maAttributes.begin(), rAttr.maAttributes.end(), [](const SfxPoolItem* p) { + return p->Which() == EE_CHAR_WEIGHT && + static_cast<const SvxWeightItem*>(p)->GetWeight() == WEIGHT_BOLD; }); + } + + static bool isItalic(const editeng::Section& rAttr) + { + return std::any_of(rAttr.maAttributes.begin(), rAttr.maAttributes.end(), [](const SfxPoolItem* p) { + return p->Which() == EE_CHAR_ITALIC && + static_cast<const SvxPostureItem*>(p)->GetPosture() == ITALIC_NORMAL; }); + } + + static bool isStrikeOut(const editeng::Section& rAttr) + { + return std::any_of(rAttr.maAttributes.begin(), rAttr.maAttributes.end(), [](const SfxPoolItem* p) { + return p->Which() == EE_CHAR_STRIKEOUT && + static_cast<const SvxCrossedOutItem*>(p)->GetStrikeout() == STRIKEOUT_SINGLE; }); + } + + static bool isOverline(const editeng::Section& rAttr, FontLineStyle eStyle) + { + return std::any_of(rAttr.maAttributes.begin(), rAttr.maAttributes.end(), [&eStyle](const SfxPoolItem* p) { + return p->Which() == EE_CHAR_OVERLINE && + static_cast<const SvxOverlineItem*>(p)->GetLineStyle() == eStyle; }); + } + + static bool isUnderline(const editeng::Section& rAttr, FontLineStyle eStyle) + { + return std::any_of(rAttr.maAttributes.begin(), rAttr.maAttributes.end(), [&eStyle](const SfxPoolItem* p) { + return p->Which() == EE_CHAR_UNDERLINE && + static_cast<const SvxUnderlineItem*>(p)->GetLineStyle() == eStyle; }); + } + + static bool isFont(const editeng::Section& rAttr, const OUString& rFontName) + { + return std::any_of(rAttr.maAttributes.begin(), rAttr.maAttributes.end(), [&rFontName](const SfxPoolItem* p) { + return p->Which() == EE_CHAR_FONTINFO && + static_cast<const SvxFontItem*>(p)->GetFamilyName() == rFontName; }); + } + + static bool isEscapement(const editeng::Section& rAttr, short nEsc, sal_uInt8 nRelSize) + { + return std::any_of(rAttr.maAttributes.begin(), rAttr.maAttributes.end(), + [&nEsc, &nRelSize](const SfxPoolItem* p) { + if (p->Which() != EE_CHAR_ESCAPEMENT) + return false; + const SvxEscapementItem* pItem = static_cast<const SvxEscapementItem*>(p); + return ((pItem->GetEsc() == nEsc) && (pItem->GetProportionalHeight() == nRelSize)); + }); + } + + static bool isColor(const editeng::Section& rAttr, Color nColor) + { + return std::any_of(rAttr.maAttributes.begin(), rAttr.maAttributes.end(), [&nColor](const SfxPoolItem* p) { + return p->Which() == EE_CHAR_COLOR && + static_cast<const SvxColorItem*>(p)->GetValue() == nColor; }); + } + + bool checkB2(const EditTextObject* pText) const + { + if (!pText) + return false; + + if (pText->GetParagraphCount() != 1) + return false; + + if (pText->GetText(0) != "Bold and Italic") + return false; + + std::vector<editeng::Section> aSecAttrs; + pText->GetAllSections(aSecAttrs); + if (aSecAttrs.size() != 3) + return false; + + // Check the first bold section. + const editeng::Section* pAttr = aSecAttrs.data(); + if (pAttr->mnParagraph != 0 ||pAttr->mnStart != 0 || pAttr->mnEnd != 4) + return false; + + if (pAttr->maAttributes.size() != 1 || !isBold(*pAttr)) + return false; + + // The middle section should be unformatted. + pAttr = &aSecAttrs[1]; + if (pAttr->mnParagraph != 0 ||pAttr->mnStart != 4 || pAttr->mnEnd != 9) + return false; + + if (!pAttr->maAttributes.empty()) + return false; + + // The last section should be italic. + pAttr = &aSecAttrs[2]; + if (pAttr->mnParagraph != 0 ||pAttr->mnStart != 9 || pAttr->mnEnd != 15) + return false; + + if (pAttr->maAttributes.size() != 1 || !isItalic(*pAttr)) + return false; + + return true; + } + + bool checkB4(const EditTextObject* pText) const + { + if (!pText) + return false; + + if (pText->GetParagraphCount() != 3) + return false; + + if (pText->GetText(0) != "One") + return false; + + if (pText->GetText(1) != "Two") + return false; + + if (pText->GetText(2) != "Three") + return false; + + return true; + } + + bool checkB5(const EditTextObject* pText) const + { + if (!pText) + return false; + + if (pText->GetParagraphCount() != 6) + return false; + + if (!pText->GetText(0).isEmpty()) + return false; + + if (pText->GetText(1) != "Two") + return false; + + if (pText->GetText(2) != "Three") + return false; + + if (!pText->GetText(3).isEmpty()) + return false; + + if (pText->GetText(4) != "Five") + return false; + + if (!pText->GetText(5).isEmpty()) + return false; + + return true; + } + + bool checkB6(const EditTextObject* pText) const + { + if (!pText) + return false; + + if (pText->GetParagraphCount() != 1) + return false; + + if (pText->GetText(0) != "Strike Me") + return false; + + std::vector<editeng::Section> aSecAttrs; + pText->GetAllSections(aSecAttrs); + if (aSecAttrs.size() != 2) + return false; + + // Check the first strike-out section. + const editeng::Section* pAttr = aSecAttrs.data(); + if (pAttr->mnParagraph != 0 ||pAttr->mnStart != 0 || pAttr->mnEnd != 6) + return false; + + if (pAttr->maAttributes.size() != 1 || !isStrikeOut(*pAttr)) + return false; + + // The last section should be unformatted. + pAttr = &aSecAttrs[1]; + return pAttr->mnParagraph == 0 && pAttr->mnStart == 6 && pAttr->mnEnd == 9; + } + + bool checkB7(const EditTextObject* pText) const + { + if (!pText) + return false; + + if (pText->GetParagraphCount() != 1) + return false; + + if (pText->GetText(0) != "Font1 and Font2") + return false; + + std::vector<editeng::Section> aSecAttrs; + pText->GetAllSections(aSecAttrs); + if (aSecAttrs.size() != 3) + return false; + + // First section should have "Courier" font applied. + const editeng::Section* pAttr = aSecAttrs.data(); + if (pAttr->mnParagraph != 0 ||pAttr->mnStart != 0 || pAttr->mnEnd != 5) + return false; + + if (pAttr->maAttributes.size() != 1 || !isFont(*pAttr, "Courier")) + return false; + + // Last section should have "Luxi Mono" applied. + pAttr = &aSecAttrs[2]; + if (pAttr->mnParagraph != 0 ||pAttr->mnStart != 10 || pAttr->mnEnd != 15) + return false; + + if (pAttr->maAttributes.size() != 1 || !isFont(*pAttr, "Luxi Mono")) + return false; + + return true; + } + + bool checkB8(const EditTextObject* pText) const + { + if (!pText) + return false; + + if (pText->GetParagraphCount() != 1) + return false; + + if (pText->GetText(0) != "Over and Under") + return false; + + std::vector<editeng::Section> aSecAttrs; + pText->GetAllSections(aSecAttrs); + if (aSecAttrs.size() != 3) + return false; + + // First section shoul have overline applied. + const editeng::Section* pAttr = aSecAttrs.data(); + if (pAttr->mnParagraph != 0 ||pAttr->mnStart != 0 || pAttr->mnEnd != 4) + return false; + + if (pAttr->maAttributes.size() != 1 || !isOverline(*pAttr, LINESTYLE_DOUBLE)) + return false; + + // Last section should have underline applied. + pAttr = &aSecAttrs[2]; + if (pAttr->mnParagraph != 0 ||pAttr->mnStart != 9 || pAttr->mnEnd != 14) + return false; + + if (pAttr->maAttributes.size() != 1 || !isUnderline(*pAttr, LINESTYLE_DOUBLE)) + return false; + + return true; + } + + bool checkB9(const EditTextObject* pText) const + { + if (!pText) + return false; + + if (pText->GetParagraphCount() != 1) + return false; + + if (pText->GetText(0) != "Sub and Super") + return false; + + std::vector<editeng::Section> aSecAttrs; + pText->GetAllSections(aSecAttrs); + if (aSecAttrs.size() != 3) + return false; + + // superscript + const editeng::Section* pAttr = aSecAttrs.data(); + if (pAttr->mnParagraph != 0 ||pAttr->mnStart != 0 || pAttr->mnEnd != 3) + return false; + + if (pAttr->maAttributes.size() != 1 || !isEscapement(*pAttr, 32, 64)) + return false; + + // subscript + pAttr = &aSecAttrs[2]; + if (pAttr->mnParagraph != 0 ||pAttr->mnStart != 8 || pAttr->mnEnd != 13) + return false; + + if (pAttr->maAttributes.size() != 1 || !isEscapement(*pAttr, -32, 66)) + return false; + + return true; + } + + bool checkB10(const EditTextObject* pText) const + { + if (!pText) + return false; + + if (pText->GetParagraphCount() != 1) + return false; + + if (pText->GetText(0) != "BLUE AUTO") + return false; + + std::vector<editeng::Section> aSecAttrs; + pText->GetAllSections(aSecAttrs); + if (aSecAttrs.size() != 2) + return false; + + // auto color + const editeng::Section* pAttr = &aSecAttrs[1]; + if (pAttr->mnParagraph != 0 ||pAttr->mnStart != 5 || pAttr->mnEnd != 9) + return false; + + if (pAttr->maAttributes.size() != 1 || !isColor(*pAttr, COL_AUTO)) + return false; + + return true; + } + + } aCheckFunc; + + // Start with an empty document, put one edit text cell, and make sure it + // survives the save and reload. + ScDocShellRef xOrigDocSh = loadDoc("empty.", FORMAT_ODS, true); + const EditTextObject* pEditText; + { + ScDocument& rDoc = xOrigDocSh->GetDocument(); + CPPUNIT_ASSERT_MESSAGE("This document should at least have one sheet.", rDoc.GetTableCount() > 0); + + // Insert an edit text cell. + ScFieldEditEngine* pEE = &rDoc.GetEditEngine(); + pEE->SetTextCurrentDefaults("Bold and Italic"); + // Set the 'Bold' part bold. + setAttribute(*pEE, 0, 0, 4, EE_CHAR_WEIGHT); + // Set the 'Italic' part italic. + setAttribute(*pEE, 0, 9, 15, EE_CHAR_ITALIC); + ESelection aSel; + aSel.nStartPara = aSel.nEndPara = 0; + + // Set this edit text to cell B2. + rDoc.SetEditText(ScAddress(1,1,0), pEE->CreateTextObject()); + pEditText = rDoc.GetEditText(ScAddress(1,1,0)); + CPPUNIT_ASSERT_MESSAGE("Incorrect B2 value.", aCheckFunc.checkB2(pEditText)); + } + + // Now, save and reload this document. + ScDocShellRef xNewDocSh = saveAndReload(xOrigDocSh.get(), FORMAT_ODS); + { + xOrigDocSh->DoClose(); + CPPUNIT_ASSERT(xNewDocSh.is()); + ScDocument& rDoc2 = xNewDocSh->GetDocument(); + CPPUNIT_ASSERT_MESSAGE("Reloaded document should at least have one sheet.", rDoc2.GetTableCount() > 0); + ScFieldEditEngine* pEE = &rDoc2.GetEditEngine(); + + // Make sure the content of B2 is still intact. + CPPUNIT_ASSERT_MESSAGE("Incorrect B2 value.", aCheckFunc.checkB2(pEditText)); + + // Insert a multi-line content to B4. + pEE->Clear(); + pEE->SetTextCurrentDefaults("One\nTwo\nThree"); + rDoc2.SetEditText(ScAddress(1,3,0), pEE->CreateTextObject()); + pEditText = rDoc2.GetEditText(ScAddress(1,3,0)); + CPPUNIT_ASSERT_MESSAGE("Incorrect B4 value.", aCheckFunc.checkB4(pEditText)); + } + + // Reload the doc again, and check the content of B2 and B4. + ScDocShellRef xNewDocSh2 = saveAndReload(xNewDocSh.get(), FORMAT_ODS); + { + ScDocument& rDoc3 = xNewDocSh2->GetDocument(); + ScFieldEditEngine* pEE = &rDoc3.GetEditEngine(); + xNewDocSh->DoClose(); + + pEditText = rDoc3.GetEditText(ScAddress(1,1,0)); + CPPUNIT_ASSERT_MESSAGE("B2 should be an edit text.", pEditText); + pEditText = rDoc3.GetEditText(ScAddress(1,3,0)); + CPPUNIT_ASSERT_MESSAGE("Incorrect B4 value.", aCheckFunc.checkB4(pEditText)); + + // Insert a multi-line content to B5, but this time, set some empty paragraphs. + pEE->Clear(); + pEE->SetTextCurrentDefaults("\nTwo\nThree\n\nFive\n"); + rDoc3.SetEditText(ScAddress(1,4,0), pEE->CreateTextObject()); + pEditText = rDoc3.GetEditText(ScAddress(1,4,0)); + CPPUNIT_ASSERT_MESSAGE("Incorrect B5 value.", aCheckFunc.checkB5(pEditText)); + + // Insert a text with strikethrough in B6. + pEE->Clear(); + pEE->SetTextCurrentDefaults("Strike Me"); + // Set the 'Strike' part strikethrough. + setAttribute(*pEE, 0, 0, 6, EE_CHAR_STRIKEOUT); + rDoc3.SetEditText(ScAddress(1,5,0), pEE->CreateTextObject()); + pEditText = rDoc3.GetEditText(ScAddress(1,5,0)); + CPPUNIT_ASSERT_MESSAGE("Incorrect B6 value.", aCheckFunc.checkB6(pEditText)); + + // Insert a text with different font segments in B7. + pEE->Clear(); + pEE->SetTextCurrentDefaults("Font1 and Font2"); + setFont(*pEE, 0, 0, 5, "Courier"); + setFont(*pEE, 0, 10, 15, "Luxi Mono"); + rDoc3.SetEditText(ScAddress(1,6,0), pEE->CreateTextObject()); + pEditText = rDoc3.GetEditText(ScAddress(1,6,0)); + CPPUNIT_ASSERT_MESSAGE("Incorrect B7 value.", aCheckFunc.checkB7(pEditText)); + + // Insert a text with overline and underline in B8. + pEE->Clear(); + pEE->SetTextCurrentDefaults("Over and Under"); + setAttribute(*pEE, 0, 0, 4, EE_CHAR_OVERLINE); + setAttribute(*pEE, 0, 9, 14, EE_CHAR_UNDERLINE); + rDoc3.SetEditText(ScAddress(1,7,0), pEE->CreateTextObject()); + pEditText = rDoc3.GetEditText(ScAddress(1,7,0)); + CPPUNIT_ASSERT_MESSAGE("Incorrect B8 value.", aCheckFunc.checkB8(pEditText)); + + pEE->Clear(); + pEE->SetTextCurrentDefaults("Sub and Super"); + setEscapement(*pEE, 0, 0, 3, 32, 64); + setEscapement(*pEE, 0, 8, 13, -32, 66); + rDoc3.SetEditText(ScAddress(1,8,0), pEE->CreateTextObject()); + pEditText = rDoc3.GetEditText(ScAddress(1,8,0)); + CPPUNIT_ASSERT_MESSAGE("Incorrect B9 value.", aCheckFunc.checkB9(pEditText)); + + ScPatternAttr aCellFontColor(rDoc3.GetPool()); + aCellFontColor.GetItemSet().Put(SvxColorItem(COL_BLUE, ATTR_FONT_COLOR)); + // Set font color of B10 to blue. + rDoc3.ApplyPattern(1, 9, 0, aCellFontColor); + pEE->Clear(); + pEE->SetTextCurrentDefaults("BLUE AUTO"); + // Set the color of the string "AUTO" to automatic color. + setAttribute(*pEE, 0, 5, 9, EE_CHAR_COLOR, COL_AUTO); + rDoc3.SetEditText(ScAddress(1, 9, 0), pEE->CreateTextObject()); + pEditText = rDoc3.GetEditText(ScAddress(1, 9, 0)); + CPPUNIT_ASSERT_MESSAGE("Incorrect B10 value.", aCheckFunc.checkB10(pEditText)); + } + + // Reload the doc again, and check the content of B2, B4, B6 and B7. + ScDocShellRef xNewDocSh3 = saveAndReload(xNewDocSh2.get(), FORMAT_ODS); + ScDocument& rDoc4 = xNewDocSh3->GetDocument(); + xNewDocSh2->DoClose(); + + pEditText = rDoc4.GetEditText(ScAddress(1,1,0)); + CPPUNIT_ASSERT_MESSAGE("Incorrect B2 value after save and reload.", aCheckFunc.checkB2(pEditText)); + pEditText = rDoc4.GetEditText(ScAddress(1,3,0)); + CPPUNIT_ASSERT_MESSAGE("Incorrect B4 value after save and reload.", aCheckFunc.checkB4(pEditText)); + pEditText = rDoc4.GetEditText(ScAddress(1,4,0)); + CPPUNIT_ASSERT_MESSAGE("Incorrect B5 value after save and reload.", aCheckFunc.checkB5(pEditText)); + pEditText = rDoc4.GetEditText(ScAddress(1,5,0)); + CPPUNIT_ASSERT_MESSAGE("Incorrect B6 value after save and reload.", aCheckFunc.checkB6(pEditText)); + pEditText = rDoc4.GetEditText(ScAddress(1,6,0)); + CPPUNIT_ASSERT_MESSAGE("Incorrect B7 value after save and reload.", aCheckFunc.checkB7(pEditText)); + pEditText = rDoc4.GetEditText(ScAddress(1,7,0)); + CPPUNIT_ASSERT_MESSAGE("Incorrect B8 value after save and reload.", aCheckFunc.checkB8(pEditText)); + pEditText = rDoc4.GetEditText(ScAddress(1,9,0)); + CPPUNIT_ASSERT_MESSAGE("Incorrect B10 value after save and reload.", aCheckFunc.checkB10(pEditText)); + + xNewDocSh3->DoClose(); +} + +void ScExportTest::testRichTextCellFormatXLSX() +{ + ScDocShellRef xDocSh = loadDoc("cellformat.", FORMAT_XLS); + CPPUNIT_ASSERT(xDocSh.is()); + + std::shared_ptr<utl::TempFile> pXPathFile = ScBootstrapFixture::exportTo(&(*xDocSh), FORMAT_XLSX); + xmlDocUniquePtr pSheet = XPathHelper::parseExport(pXPathFile, m_xSFactory, "xl/worksheets/sheet1.xml"); + CPPUNIT_ASSERT(pSheet); + + // make sure the only cell in this doc is assigned some formatting record + OUString aCellFormat = getXPath(pSheet, "/x:worksheet/x:sheetData/x:row/x:c", "s"); + CPPUNIT_ASSERT_MESSAGE("Cell format is missing", !aCellFormat.isEmpty()); + + xmlDocUniquePtr pStyles = XPathHelper::parseExport(pXPathFile, m_xSFactory, "xl/styles.xml"); + CPPUNIT_ASSERT(pStyles); + + OString nFormatIdx = OString::number( aCellFormat.toInt32() + 1 ); + const OString aXPath1( "/x:styleSheet/x:cellXfs/x:xf[" + nFormatIdx + "]/x:alignment" ); + // formatting record is set to wrap text + assertXPath(pStyles, aXPath1, "wrapText", "true"); + + // see what font it references + const OString aXPath2( "/x:styleSheet/x:cellXfs/x:xf[" + nFormatIdx +"]" ); + OUString aFontId = getXPath(pStyles, aXPath2, "fontId"); + OString nFontIdx = OString::number( aFontId.toInt32() + 1 ); + + // that font should be bold + const OString aXPath3("/x:styleSheet/x:fonts/x:font[" + nFontIdx + "]/x:b"); + assertXPath(pStyles, aXPath3, "val", "true"); + + xDocSh->DoClose(); +} + +void ScExportTest::testFormulaRefSheetNameODS() +{ + ScDocShellRef xDocSh = loadDoc("formula-quote-in-sheet-name.", FORMAT_ODS, true); + { + ScDocument& rDoc = xDocSh->GetDocument(); + + sc::AutoCalcSwitch aACSwitch(rDoc, true); // turn on auto calc. + rDoc.SetString(ScAddress(1,1,0), "='90''s Data'.B2"); + CPPUNIT_ASSERT_EQUAL(1.1, rDoc.GetValue(ScAddress(1,1,0))); + ASSERT_FORMULA_EQUAL(rDoc, ScAddress(1,1,0), "'90''s Data'.B2", "Wrong formula"); + } + // Now, save and reload this document. + ScDocShellRef xNewDocSh = saveAndReload(xDocSh.get(), FORMAT_ODS); + xDocSh->DoClose(); + + ScDocument& rDoc = xNewDocSh->GetDocument(); + rDoc.CalcAll(); + CPPUNIT_ASSERT_EQUAL(1.1, rDoc.GetValue(ScAddress(1,1,0))); + ASSERT_FORMULA_EQUAL(rDoc, ScAddress(1,1,0), "'90''s Data'.B2", "Wrong formula"); + + xNewDocSh->DoClose(); +} + +void ScExportTest::testCellValuesExportODS() +{ + // Start with an empty document + ScDocShellRef xOrigDocSh = loadDoc("empty.", FORMAT_ODS); + { + ScDocument& rDoc = xOrigDocSh->GetDocument(); + CPPUNIT_ASSERT_MESSAGE("This document should at least have one sheet.", rDoc.GetTableCount() > 0); + + // set a value double + rDoc.SetValue(ScAddress(0,0,0), 2.0); // A1 + + // set a formula + rDoc.SetValue(ScAddress(2,0,0), 3.0); // C1 + rDoc.SetValue(ScAddress(3,0,0), 3); // D1 + rDoc.SetString(ScAddress(4,0,0), "=10*C1/4"); // E1 + rDoc.SetValue(ScAddress(5,0,0), 3.0); // F1 + rDoc.SetString(ScAddress(7,0,0), "=SUM(C1:F1)"); //H1 + + // set a string + rDoc.SetString(ScAddress(0,2,0), "a simple line"); //A3 + + // set a digit string + rDoc.SetString(ScAddress(0,4,0), "'12"); //A5 + // set a contiguous value + rDoc.SetValue(ScAddress(0,5,0), 12.0); //A6 + // set a contiguous string + rDoc.SetString(ScAddress(0,6,0), "a string"); //A7 + // set a contiguous formula + rDoc.SetString(ScAddress(0,7,0), "=$A$6"); //A8 + } + // save and reload + ScDocShellRef xNewDocSh = saveAndReload(xOrigDocSh.get(), FORMAT_ODS); + xOrigDocSh->DoClose(); + CPPUNIT_ASSERT(xNewDocSh.is()); + ScDocument& rDoc = xNewDocSh->GetDocument(); + CPPUNIT_ASSERT_MESSAGE("Reloaded document should at least have one sheet.", rDoc.GetTableCount() > 0); + + // check value + CPPUNIT_ASSERT_EQUAL(2.0, rDoc.GetValue(0,0,0)); + CPPUNIT_ASSERT_EQUAL(3.0, rDoc.GetValue(2,0,0)); + CPPUNIT_ASSERT_EQUAL(3.0, rDoc.GetValue(3,0,0)); + CPPUNIT_ASSERT_EQUAL(7.5, rDoc.GetValue(4,0,0)); + CPPUNIT_ASSERT_EQUAL(3.0, rDoc.GetValue(5,0,0)); + + // check formula + ASSERT_FORMULA_EQUAL(rDoc, ScAddress(4,0,0), "10*C1/4", "Wrong formula =10*C1/4"); + ASSERT_FORMULA_EQUAL(rDoc, ScAddress(7,0,0), "SUM(C1:F1)", "Wrong formula =SUM(C1:F1)"); + CPPUNIT_ASSERT_EQUAL(16.5, rDoc.GetValue(7,0,0)); + + // check string + ScRefCellValue aCell; + aCell.assign(rDoc, ScAddress(0,2,0)); + CPPUNIT_ASSERT_EQUAL( CELLTYPE_STRING, aCell.meType ); + + // check for an empty cell + aCell.assign(rDoc, ScAddress(0,3,0)); + CPPUNIT_ASSERT_EQUAL( CELLTYPE_NONE, aCell.meType); + + // check a digit string + aCell.assign(rDoc, ScAddress(0,4,0)); + CPPUNIT_ASSERT_EQUAL( CELLTYPE_STRING, aCell.meType); + + //check contiguous values + CPPUNIT_ASSERT_EQUAL( 12.0, rDoc.GetValue(0,5,0) ); + CPPUNIT_ASSERT_EQUAL( OUString("a string"), rDoc.GetString(0,6,0) ); + ASSERT_FORMULA_EQUAL(rDoc, ScAddress(0,7,0), "$A$6", "Wrong formula =$A$6"); + CPPUNIT_ASSERT_EQUAL( rDoc.GetValue(0,5,0), rDoc.GetValue(0,7,0) ); + + xNewDocSh->DoClose(); +} + +void ScExportTest::testCellNoteExportODS() +{ + ScDocShellRef xOrigDocSh = loadDoc("single-note.", FORMAT_ODS); + ScAddress aPos(0,0,0); // Start with A1. + { + ScDocument& rDoc = xOrigDocSh->GetDocument(); + + CPPUNIT_ASSERT_MESSAGE("There should be a note at A1.", rDoc.HasNote(aPos)); + + aPos.IncRow(); // Move to A2. + ScPostIt* pNote = rDoc.GetOrCreateNote(aPos); + pNote->SetText(aPos, "Note One"); + pNote->SetAuthor("Author One"); + CPPUNIT_ASSERT_MESSAGE("There should be a note at A2.", rDoc.HasNote(aPos)); + } + // save and reload + ScDocShellRef xNewDocSh = saveAndReload(xOrigDocSh.get(), FORMAT_ODS); + xOrigDocSh->DoClose(); + CPPUNIT_ASSERT(xNewDocSh.is()); + ScDocument& rDoc = xNewDocSh->GetDocument(); + + aPos.SetRow(0); // Move back to A1. + CPPUNIT_ASSERT_MESSAGE("There should be a note at A1.", rDoc.HasNote(aPos)); + aPos.IncRow(); // Move to A2. + CPPUNIT_ASSERT_MESSAGE("There should be a note at A2.", rDoc.HasNote(aPos)); + + xNewDocSh->DoClose(); +} + +void ScExportTest::testCellNoteExportXLS() +{ + // Start with an empty document.s + ScDocShellRef xOrigDocSh = loadDoc("notes-on-3-sheets.", FORMAT_ODS); + { + ScDocument& rDoc = xOrigDocSh->GetDocument(); + CPPUNIT_ASSERT_EQUAL_MESSAGE("This document should have 3 sheets.", SCTAB(3), rDoc.GetTableCount()); + + // Check note's presence. + CPPUNIT_ASSERT( rDoc.HasNote(ScAddress(0,0,0))); + CPPUNIT_ASSERT(!rDoc.HasNote(ScAddress(0,1,0))); + CPPUNIT_ASSERT(!rDoc.HasNote(ScAddress(0,2,0))); + + CPPUNIT_ASSERT(!rDoc.HasNote(ScAddress(0,0,1))); + CPPUNIT_ASSERT( rDoc.HasNote(ScAddress(0,1,1))); + CPPUNIT_ASSERT(!rDoc.HasNote(ScAddress(0,2,1))); + + CPPUNIT_ASSERT(!rDoc.HasNote(ScAddress(0,0,2))); + CPPUNIT_ASSERT(!rDoc.HasNote(ScAddress(0,1,2))); + CPPUNIT_ASSERT( rDoc.HasNote(ScAddress(0,2,2))); + } + // save and reload as XLS. + ScDocShellRef xNewDocSh = saveAndReload(xOrigDocSh.get(), FORMAT_XLS); + { + xOrigDocSh->DoClose(); + CPPUNIT_ASSERT(xNewDocSh.is()); + ScDocument& rDoc = xNewDocSh->GetDocument(); + CPPUNIT_ASSERT_EQUAL_MESSAGE("This document should have 3 sheets.", SCTAB(3), rDoc.GetTableCount()); + + // Check note's presence again. + CPPUNIT_ASSERT( rDoc.HasNote(ScAddress(0,0,0))); + CPPUNIT_ASSERT(!rDoc.HasNote(ScAddress(0,1,0))); + CPPUNIT_ASSERT(!rDoc.HasNote(ScAddress(0,2,0))); + + CPPUNIT_ASSERT(!rDoc.HasNote(ScAddress(0,0,1))); + CPPUNIT_ASSERT( rDoc.HasNote(ScAddress(0,1,1))); + CPPUNIT_ASSERT(!rDoc.HasNote(ScAddress(0,2,1))); + + CPPUNIT_ASSERT(!rDoc.HasNote(ScAddress(0,0,2))); + CPPUNIT_ASSERT(!rDoc.HasNote(ScAddress(0,1,2))); + CPPUNIT_ASSERT( rDoc.HasNote(ScAddress(0,2,2))); + + xNewDocSh->DoClose(); + } +} + +namespace { + +void checkMatrixRange(ScDocument& rDoc, const ScRange& rRange) +{ + ScRange aMatRange; + ScAddress aMatOrigin; + for (SCCOL nCol = rRange.aStart.Col(); nCol <= rRange.aEnd.Col(); ++nCol) + { + for (SCROW nRow = rRange.aStart.Row(); nRow <= rRange.aEnd.Row(); ++nRow) + { + ScAddress aPos(nCol, nRow, rRange.aStart.Tab()); + bool bIsMatrix = rDoc.GetMatrixFormulaRange(aPos, aMatRange); + CPPUNIT_ASSERT_MESSAGE("Matrix expected, but not found.", bIsMatrix); + CPPUNIT_ASSERT_EQUAL_MESSAGE("Wrong matrix range.", rRange, aMatRange); + const ScFormulaCell* pCell = rDoc.GetFormulaCell(aPos); + CPPUNIT_ASSERT_MESSAGE("This must be a formula cell.", pCell); + + bIsMatrix = pCell->GetMatrixOrigin(&rDoc, aMatOrigin); + CPPUNIT_ASSERT_MESSAGE("Not a part of matrix formula.", bIsMatrix); + CPPUNIT_ASSERT_EQUAL_MESSAGE("Wrong matrix origin.", aMatRange.aStart, aMatOrigin); + } + } +} + +} + +void ScExportTest::testInlineArrayXLS() +{ + ScDocShellRef xShell = loadDoc("inline-array.", FORMAT_XLS); + CPPUNIT_ASSERT(xShell.is()); + + ScDocShellRef xDocSh = saveAndReload(xShell.get(), FORMAT_XLS); + xShell->DoClose(); + CPPUNIT_ASSERT(xDocSh.is()); + + ScDocument& rDoc = xDocSh->GetDocument(); + + // B2:C3 contains a matrix. + checkMatrixRange(rDoc, ScRange(1,1,0,2,2,0)); + + // B5:D6 contains a matrix. + checkMatrixRange(rDoc, ScRange(1,4,0,3,5,0)); + + // B8:C10 as well. + checkMatrixRange(rDoc, ScRange(1,7,0,2,9,0)); + + xDocSh->DoClose(); +} + +void ScExportTest::testEmbeddedChartODS() +{ + ScDocShellRef xShell = loadDoc("embedded-chart.", FORMAT_XLS); + CPPUNIT_ASSERT(xShell.is()); + + std::shared_ptr<utl::TempFile> pTempFile( + ScBootstrapFixture::exportTo(xShell.get(), FORMAT_ODS)); + + xmlDocUniquePtr pDoc = XPathHelper::parseExport(pTempFile, m_xSFactory, "content.xml"); + CPPUNIT_ASSERT(pDoc); + assertXPath(pDoc, + "/office:document-content/office:body/office:spreadsheet/table:table[2]/table:table-row[7]/table:table-cell[2]/draw:frame/draw:object", + "notify-on-update-of-ranges", + "Chart1.B3:Chart1.B5 Chart1.C2:Chart1.C2 Chart1.C3:Chart1.C5"); + + xShell->DoClose(); +} + +void ScExportTest::testEmbeddedChartXLS() +{ + ScDocShellRef xShell = loadDoc("embedded-chart.", FORMAT_XLS); + CPPUNIT_ASSERT(xShell.is()); + + ScDocShellRef xDocSh = saveAndReload(xShell.get(), FORMAT_XLS); + xShell->DoClose(); + CPPUNIT_ASSERT(xDocSh.is()); + + ScDocument& rDoc = xDocSh->GetDocument(); + + // Make sure the 2nd sheet is named 'Chart1'. + OUString aName; + rDoc.GetName(1, aName); + CPPUNIT_ASSERT_EQUAL(OUString("Chart1"), aName); + + const SdrOle2Obj* pOleObj = getSingleChartObject(rDoc, 1); + CPPUNIT_ASSERT_MESSAGE("Failed to retrieve a chart object from the 2nd sheet.", pOleObj); + + ScRangeList aRanges = getChartRanges(rDoc, *pOleObj); + CPPUNIT_ASSERT_MESSAGE("Label range (B3:B5) not found.", aRanges.In(ScRange(1,2,1,1,4,1))); + CPPUNIT_ASSERT_MESSAGE("Data label (C2) not found.", aRanges.In(ScAddress(2,1,1))); + CPPUNIT_ASSERT_MESSAGE("Data range (C3:C5) not found.", aRanges.In(ScRange(2,2,1,2,4,1))); + + xDocSh->DoClose(); +} + +void ScExportTest::testCellAnchoredGroupXLS() +{ + ScDocShellRef xDocSh_in = loadDoc("cell-anchored-group.", FORMAT_XLS); + CPPUNIT_ASSERT_MESSAGE("Failed to load cell-anchored-group.xls", xDocSh_in.is()); + + ScDocShellRef xDocSh = saveAndReload(&(*xDocSh_in), FORMAT_ODS); + CPPUNIT_ASSERT_MESSAGE("Failed to save and reload cell-anchored-group.ods", xDocSh.is()); + + // the document contains a group anchored on the first cell, make sure it's there in the right place + ScDocument& rDoc = xDocSh->GetDocument(); + CPPUNIT_ASSERT_MESSAGE("There should be at least one sheet.", rDoc.GetTableCount() > 0); + ScDrawLayer* pDrawLayer = rDoc.GetDrawLayer(); + SdrPage* pPage = pDrawLayer->GetPage(0); + CPPUNIT_ASSERT_MESSAGE("draw page for sheet 1 should exist.", pPage); + const size_t nCount = pPage->GetObjCount(); + CPPUNIT_ASSERT_EQUAL_MESSAGE( + "There should be 1 objects.", static_cast<size_t>(1), nCount); + + SdrObject* pObj = pPage->GetObj(0); + CPPUNIT_ASSERT_MESSAGE("Failed to get drawing object.", pObj); + ScDrawObjData* pData = ScDrawLayer::GetObjData(pObj); + CPPUNIT_ASSERT_MESSAGE("Failed to retrieve user data for this object.", pData); + CPPUNIT_ASSERT_MESSAGE("Upper left of bounding rectangle should be nonnegative.", + pData->getShapeRect().Left() >= 0 || pData->getShapeRect().Top() >= 0); + xDocSh->DoClose(); +} + +void ScExportTest::testFormulaReferenceXLS() +{ + ScDocShellRef xShell = loadDoc("formula-reference.", FORMAT_XLS); + CPPUNIT_ASSERT(xShell.is()); + + ScDocShellRef xDocSh = saveAndReload(xShell.get(), FORMAT_XLS); + xShell->DoClose(); + CPPUNIT_ASSERT(xDocSh.is()); + + ScDocument& rDoc = xDocSh->GetDocument(); + + ASSERT_FORMULA_EQUAL(rDoc, ScAddress(3,1,0), "$A$2+$B$2+$C$2", "Wrong formula in D2"); + ASSERT_FORMULA_EQUAL(rDoc, ScAddress(3,2,0), "A3+B3+C3", "Wrong formula in D3"); + ASSERT_FORMULA_EQUAL(rDoc, ScAddress(3,5,0), "SUM($A$6:$C$6)", "Wrong formula in D6"); + ASSERT_FORMULA_EQUAL(rDoc, ScAddress(3,6,0), "SUM(A7:C7)", "Wrong formula in D7"); + ASSERT_FORMULA_EQUAL(rDoc, ScAddress(3,9,0), "$Two.$A$2+$Two.$B$2+$Two.$C$2", "Wrong formula in D10"); + ASSERT_FORMULA_EQUAL(rDoc, ScAddress(3,10,0), "$Two.A3+$Two.B3+$Two.C3", "Wrong formula in D11"); + ASSERT_FORMULA_EQUAL(rDoc, ScAddress(3,13,0), "MIN($Two.$A$2:$C$2)", "Wrong formula in D14"); + ASSERT_FORMULA_EQUAL(rDoc, ScAddress(3,14,0), "MAX($Two.A3:C3)", "Wrong formula in D15"); + + xDocSh->DoClose(); +} + +void ScExportTest::testSheetProtectionXLSX() +{ + ScDocShellRef xShell = loadDoc("ProtecteSheet1234Pass.", FORMAT_XLSX); + CPPUNIT_ASSERT(xShell.is()); + + ScDocShellRef xDocSh = saveAndReload(xShell.get(), FORMAT_XLSX); + CPPUNIT_ASSERT(xDocSh.is()); + + ScDocument& rDoc = xDocSh->GetDocument(); + const ScTableProtection* pTabProtect = rDoc.GetTabProtection(0); + CPPUNIT_ASSERT(pTabProtect); + Sequence<sal_Int8> aHash = pTabProtect->getPasswordHash(PASSHASH_XL); + // check has + if (aHash.getLength() >= 2) + { + CPPUNIT_ASSERT_EQUAL(sal_uInt8(204), static_cast<sal_uInt8>(aHash[0])); + CPPUNIT_ASSERT_EQUAL(sal_uInt8(61), static_cast<sal_uInt8>(aHash[1])); + } + // we could flesh out this check I guess + CPPUNIT_ASSERT ( !pTabProtect->isOptionEnabled( ScTableProtection::OBJECTS ) ); + CPPUNIT_ASSERT ( !pTabProtect->isOptionEnabled( ScTableProtection::SCENARIOS ) ); + xDocSh->DoClose(); +} + +void ScExportTest::testSheetProtectionXLSB() +{ + ScDocShellRef xShell = loadDoc("tdf108017_calcProtection.", FORMAT_XLSB); + CPPUNIT_ASSERT(xShell.is()); + + ScDocShellRef xDocSh = saveAndReload(xShell.get(), FORMAT_XLSX); + CPPUNIT_ASSERT(xDocSh.is()); + + ScDocument& rDoc = xDocSh->GetDocument(); + const ScTableProtection* pTabProtect = rDoc.GetTabProtection(0); + CPPUNIT_ASSERT(pTabProtect); + CPPUNIT_ASSERT(pTabProtect->isOptionEnabled( ScTableProtection::SELECT_UNLOCKED_CELLS )); + CPPUNIT_ASSERT(!pTabProtect->isOptionEnabled( ScTableProtection::SELECT_LOCKED_CELLS )); + xDocSh->DoClose(); +} + +namespace { + +const char* toBorderName( SvxBorderLineStyle eStyle ) +{ + switch (eStyle) + { + case SvxBorderLineStyle::SOLID: return "SOLID"; + case SvxBorderLineStyle::DOTTED: return "DOTTED"; + case SvxBorderLineStyle::DASHED: return "DASHED"; + case SvxBorderLineStyle::DASH_DOT: return "DASH_DOT"; + case SvxBorderLineStyle::DASH_DOT_DOT: return "DASH_DOT_DOT"; + case SvxBorderLineStyle::DOUBLE_THIN: return "DOUBLE_THIN"; + case SvxBorderLineStyle::FINE_DASHED: return "FINE_DASHED"; + default: + ; + } + + return ""; +} + +} + +void ScExportTest::testExcelCellBorders( sal_uLong nFormatType ) +{ + static const struct + { + SCROW mnRow; + SvxBorderLineStyle mnStyle; + long mnWidth; + } aChecks[] = { + { 1, SvxBorderLineStyle::SOLID, 1 }, // hair + { 3, SvxBorderLineStyle::DOTTED, 15 }, // dotted + { 5, SvxBorderLineStyle::DASH_DOT_DOT, 15 }, // dash dot dot + { 7, SvxBorderLineStyle::DASH_DOT, 15 }, // dash dot + { 9, SvxBorderLineStyle::FINE_DASHED, 15 }, // dashed + { 11, SvxBorderLineStyle::SOLID, 15 }, // thin + { 13, SvxBorderLineStyle::DASH_DOT_DOT, 35 }, // medium dash dot dot + { 17, SvxBorderLineStyle::DASH_DOT, 35 }, // medium dash dot + { 19, SvxBorderLineStyle::DASHED, 35 }, // medium dashed + { 21, SvxBorderLineStyle::SOLID, 35 }, // medium + { 23, SvxBorderLineStyle::SOLID, 50 }, // thick + { 25, SvxBorderLineStyle::DOUBLE_THIN, -1 }, // double (don't check width) + }; + + ScDocShellRef xDocSh = loadDoc("cell-borders.", nFormatType); + CPPUNIT_ASSERT_MESSAGE("Failed to load file", xDocSh.is()); + { + ScDocument& rDoc = xDocSh->GetDocument(); + + for (size_t i = 0; i < SAL_N_ELEMENTS(aChecks); ++i) + { + const editeng::SvxBorderLine* pLine = nullptr; + rDoc.GetBorderLines(2, aChecks[i].mnRow, 0, nullptr, &pLine, nullptr, nullptr); + CPPUNIT_ASSERT(pLine); + CPPUNIT_ASSERT_EQUAL(toBorderName(aChecks[i].mnStyle), toBorderName(pLine->GetBorderLineStyle())); + if (aChecks[i].mnWidth >= 0) + CPPUNIT_ASSERT_EQUAL(aChecks[i].mnWidth, pLine->GetWidth()); + } + } + + ScDocShellRef xNewDocSh = saveAndReload(xDocSh.get(), nFormatType); + xDocSh->DoClose(); + ScDocument& rDoc = xNewDocSh->GetDocument(); + for (size_t i = 0; i < SAL_N_ELEMENTS(aChecks); ++i) + { + const editeng::SvxBorderLine* pLine = nullptr; + rDoc.GetBorderLines(2, aChecks[i].mnRow, 0, nullptr, &pLine, nullptr, nullptr); + CPPUNIT_ASSERT(pLine); + CPPUNIT_ASSERT_EQUAL(toBorderName(aChecks[i].mnStyle), toBorderName(pLine->GetBorderLineStyle())); + if (aChecks[i].mnWidth >= 0) + CPPUNIT_ASSERT_EQUAL(aChecks[i].mnWidth, pLine->GetWidth()); + } + + xNewDocSh->DoClose(); +} + +void ScExportTest::testCellBordersXLS() +{ + testExcelCellBorders(FORMAT_XLS); +} + +void ScExportTest::testCellBordersXLSX() +{ + testExcelCellBorders(FORMAT_XLSX); +} + +void ScExportTest::testBordersExchangeXLSX() +{ + // Document: sc/qa/unit/data/README.cellborders + + // short name for the table + const SvxBorderLineStyle None = SvxBorderLineStyle::NONE; + const SvxBorderLineStyle Solid = SvxBorderLineStyle::SOLID; + const SvxBorderLineStyle Dotted = SvxBorderLineStyle::DOTTED; + const SvxBorderLineStyle Dashed = SvxBorderLineStyle::DASHED; + const SvxBorderLineStyle FineDash = SvxBorderLineStyle::FINE_DASHED; + const SvxBorderLineStyle DashDot = SvxBorderLineStyle::DASH_DOT; + const SvxBorderLineStyle DashDoDo = SvxBorderLineStyle::DASH_DOT_DOT; + const SvxBorderLineStyle DoubThin = SvxBorderLineStyle::DOUBLE_THIN; + + const size_t nMaxCol = 18; + const size_t nMaxRow = 7; + + static struct + { + SvxBorderLineStyle BorderStyleTop, BorderStyleBottom; + long WidthTop, WidthBottom; + } aCheckBorderWidth[nMaxCol][nMaxRow] = + { +/* Line 1 2 3 4 5 6 7 + SOLID DOTTED DASHED FINE_DASHED DASH_DOT DASH_DOT_DOT DOUBLE_THIN */ +/*Width */ +/* 0,05 */ {{Solid , Solid , 1, 1}, {Dotted , Dotted , 15, 15}, {Dotted , Dotted , 15, 15}, {FineDash, FineDash, 15, 15}, {FineDash, FineDash, 15, 15}, {FineDash, FineDash, 15, 15}, {None , None , 0, 0}}, +/* 0,25 */ {{Solid , Solid , 1, 1}, {Dotted , Dotted , 15, 15}, {Dotted , Dotted , 15, 15}, {FineDash, FineDash, 15, 15}, {FineDash, FineDash, 15, 15}, {FineDash, FineDash, 15, 15}, {None , None , 0, 0}}, +/* 0,50 */ {{Solid , Solid , 1, 1}, {Dotted , Dotted , 15, 15}, {Dotted , Dotted , 15, 15}, {FineDash, FineDash, 15, 15}, {FineDash, FineDash, 15, 15}, {FineDash, FineDash, 15, 15}, {None , None , 0, 0}}, +/* 0,75 */ {{Solid , Solid , 15, 15}, {Dotted , Dotted , 15, 15}, {FineDash, FineDash, 15, 15}, {FineDash, FineDash, 15, 15}, {DashDot , DashDot , 15, 15}, {DashDoDo, DashDoDo, 15, 15}, {DoubThin, DoubThin, 35, 35}}, +/* 1,00 */ {{Solid , Solid , 15, 15}, {Dotted , Dotted , 15, 15}, {FineDash, FineDash, 15, 15}, {FineDash, FineDash, 15, 15}, {DashDot , DashDot , 15, 15}, {DashDoDo, DashDoDo, 15, 15}, {DoubThin, DoubThin, 35, 35}}, +/* 1,25 */ {{Solid , Solid , 15, 15}, {Dotted , Dotted , 15, 15}, {FineDash, FineDash, 15, 15}, {FineDash, FineDash, 15, 15}, {DashDot , DashDot , 15, 15}, {DashDoDo, DashDoDo, 15, 15}, {DoubThin, DoubThin, 35, 35}}, +/* 1,50 */ {{Solid , Solid , 15, 15}, {Dotted , Dotted , 15, 15}, {FineDash, FineDash, 15, 15}, {FineDash, FineDash, 15, 15}, {DashDot , DashDot , 15, 15}, {DashDoDo, DashDoDo, 15, 15}, {DoubThin, DoubThin, 35, 35}}, + +/* 1,75 */ {{Solid , Solid , 35, 35}, {FineDash, FineDash, 35, 35}, {Dashed , Dashed , 35, 35}, {FineDash, FineDash, 35, 35}, {DashDot , DashDot , 35, 35}, {DashDoDo, DashDoDo, 35, 35}, {DoubThin, DoubThin, 35, 35}}, +/* 2,00 */ {{Solid , Solid , 35, 35}, {FineDash, FineDash, 35, 35}, {Dashed , Dashed , 35, 35}, {FineDash, FineDash, 35, 35}, {DashDot , DashDot , 35, 35}, {DashDoDo, DashDoDo, 35, 35}, {DoubThin, DoubThin, 35, 35}}, +/* 2,25 */ {{Solid , Solid , 35, 35}, {FineDash, FineDash, 35, 35}, {Dashed , Dashed , 35, 35}, {FineDash, FineDash, 35, 35}, {DashDot , DashDot , 35, 35}, {DashDoDo, DashDoDo, 35, 35}, {DoubThin, DoubThin, 35, 35}}, + +/* 2,50 */ {{Solid , Solid , 50, 50}, {FineDash, FineDash, 35, 35}, {Dashed , Dashed , 35, 35}, {FineDash, FineDash, 35, 35}, {DashDot , DashDot , 35, 35}, {DashDoDo, DashDoDo, 35, 35}, {DoubThin, DoubThin, 35, 35}}, +/* 2,75 */ {{Solid , Solid , 50, 50}, {FineDash, FineDash, 35, 35}, {Dashed , Dashed , 35, 35}, {FineDash, FineDash, 35, 35}, {DashDot , DashDot , 35, 35}, {DashDoDo, DashDoDo, 35, 35}, {DoubThin, DoubThin, 35, 35}}, +/* 3,00 */ {{Solid , Solid , 50, 50}, {FineDash, FineDash, 35, 35}, {Dashed , Dashed , 35, 35}, {FineDash, FineDash, 35, 35}, {DashDot , DashDot , 35, 35}, {DashDoDo, DashDoDo, 35, 35}, {DoubThin, DoubThin, 35, 35}}, +/* 3,50 */ {{Solid , Solid , 50, 50}, {FineDash, FineDash, 35, 35}, {Dashed , Dashed , 35, 35}, {FineDash, FineDash, 35, 35}, {DashDot , DashDot , 35, 35}, {DashDoDo, DashDoDo, 35, 35}, {DoubThin, DoubThin, 35, 35}}, +/* 4,00 */ {{Solid , Solid , 50, 50}, {FineDash, FineDash, 35, 35}, {Dashed , Dashed , 35, 35}, {FineDash, FineDash, 35, 35}, {DashDot , DashDot , 35, 35}, {DashDoDo, DashDoDo, 35, 35}, {DoubThin, DoubThin, 35, 35}}, +/* 5,00 */ {{Solid , Solid , 50, 50}, {FineDash, FineDash, 35, 35}, {Dashed , Dashed , 35, 35}, {FineDash, FineDash, 35, 35}, {DashDot , DashDot , 35, 35}, {DashDoDo, DashDoDo, 35, 35}, {DoubThin, DoubThin, 35, 35}}, +/* 7,00 */ {{Solid , Solid , 50, 50}, {FineDash, FineDash, 35, 35}, {Dashed , Dashed , 35, 35}, {FineDash, FineDash, 35, 35}, {DashDot , DashDot , 35, 35}, {DashDoDo, DashDoDo, 35, 35}, {DoubThin, DoubThin, 35, 35}}, +/* 9,00 */ {{Solid , Solid , 50, 50}, {FineDash, FineDash, 35, 35}, {Dashed , Dashed , 35, 35}, {FineDash, FineDash, 35, 35}, {DashDot , DashDot , 35, 35}, {DashDoDo, DashDoDo, 35, 35}, {DoubThin, DoubThin, 35, 35}} + }; + + ScDocShellRef xShell = loadDoc("test_borders_export.", FORMAT_ODS); // load the ods with our Borders + CPPUNIT_ASSERT(xShell.is()); + + ScDocShellRef xDocSh = saveAndReload(&(*xShell), FORMAT_XLSX); // save the ods to xlsx and load xlsx + CPPUNIT_ASSERT(xDocSh.is()); + ScDocument& rDoc = xDocSh->GetDocument(); + + for (size_t nCol = 0; nCol < nMaxCol; ++nCol) + { + for (size_t nRow = 0; nRow < nMaxRow; ++nRow) + { + const editeng::SvxBorderLine* pLineTop = nullptr; + const editeng::SvxBorderLine* pLineBottom = nullptr; + rDoc.GetBorderLines(nCol + 2, (nRow * 2) + 8, 0, nullptr, &pLineTop, nullptr, &pLineBottom); + if((nCol < 3) && (nRow == 6)) + { // in this range no lines since minimum size to create a double is 0.5 + CPPUNIT_ASSERT(!pLineTop); + CPPUNIT_ASSERT(!pLineBottom); + continue; + } + else + { + CPPUNIT_ASSERT(pLineTop); + CPPUNIT_ASSERT(pLineBottom); + } + + CPPUNIT_ASSERT_EQUAL_MESSAGE("Top Border-Line-Style wrong", aCheckBorderWidth[nCol][nRow].BorderStyleTop, + pLineTop->GetBorderLineStyle()); + CPPUNIT_ASSERT_EQUAL_MESSAGE("Bottom Border-Line-Style wrong", aCheckBorderWidth[nCol][nRow].BorderStyleBottom, + pLineBottom->GetBorderLineStyle()); + CPPUNIT_ASSERT_EQUAL_MESSAGE("Top Width-Line wrong", aCheckBorderWidth[nCol][nRow].WidthTop, + pLineTop->GetWidth()); + CPPUNIT_ASSERT_EQUAL_MESSAGE("Bottom Width-Line wrong", aCheckBorderWidth[nCol][nRow].WidthBottom, + pLineBottom->GetWidth()); + } + } + + xDocSh->DoClose(); +} + +static OUString toString( const ScBigRange& rRange ) +{ + OUStringBuffer aBuf; + aBuf.append("(columns:"); + aBuf.append(rRange.aStart.Col()); + aBuf.append('-'); + aBuf.append(rRange.aEnd.Col()); + aBuf.append(";rows:"); + aBuf.append(rRange.aStart.Row()); + aBuf.append('-'); + aBuf.append(rRange.aEnd.Row()); + aBuf.append(";sheets:"); + aBuf.append(rRange.aStart.Tab()); + aBuf.append('-'); + aBuf.append(rRange.aEnd.Tab()); + aBuf.append(')'); + + return aBuf.makeStringAndClear(); +} + +void ScExportTest::testTrackChangesSimpleXLSX() +{ + struct CheckItem + { + sal_uLong mnActionId; + ScChangeActionType meType; + + sal_Int32 mnStartCol; + sal_Int32 mnStartRow; + sal_Int32 mnStartTab; + sal_Int32 mnEndCol; + sal_Int32 mnEndRow; + sal_Int32 mnEndTab; + + bool mbRowInsertedAtBottom; + }; + + struct + { + bool checkRange( ScChangeActionType eType, const ScBigRange& rExpected, const ScBigRange& rActual ) + { + ScBigRange aExpected(rExpected), aActual(rActual); + + switch (eType) + { + case SC_CAT_INSERT_ROWS: + { + // Ignore columns. + aExpected.aStart.SetCol(0); + aExpected.aEnd.SetCol(0); + aActual.aStart.SetCol(0); + aActual.aEnd.SetCol(0); + } + break; + default: + ; + } + + return aExpected == aActual; + } + + bool check( const ScDocument& rDoc ) + { + static const CheckItem aChecks[] = + { + { 1, SC_CAT_CONTENT , 1, 1, 0, 1, 1, 0, false }, + { 2, SC_CAT_INSERT_ROWS , 0, 2, 0, 0, 2, 0, true }, + { 3, SC_CAT_CONTENT , 1, 2, 0, 1, 2, 0, false }, + { 4, SC_CAT_INSERT_ROWS , 0, 3, 0, 0, 3, 0, true }, + { 5, SC_CAT_CONTENT , 1, 3, 0, 1, 3, 0, false }, + { 6, SC_CAT_INSERT_ROWS , 0, 4, 0, 0, 4, 0, true }, + { 7, SC_CAT_CONTENT , 1, 4, 0, 1, 4, 0, false }, + { 8, SC_CAT_INSERT_ROWS , 0, 5, 0, 0, 5, 0, true }, + { 9, SC_CAT_CONTENT , 1, 5, 0, 1, 5, 0, false }, + { 10, SC_CAT_INSERT_ROWS , 0, 6, 0, 0, 6, 0, true }, + { 11, SC_CAT_CONTENT , 1, 6, 0, 1, 6, 0, false }, + { 12, SC_CAT_INSERT_ROWS , 0, 7, 0, 0, 7, 0, true }, + { 13, SC_CAT_CONTENT , 1, 7, 0, 1, 7, 0, false }, + }; + + ScChangeTrack* pCT = rDoc.GetChangeTrack(); + if (!pCT) + { + cerr << "Change track instance doesn't exist." << endl; + return false; + } + + sal_uLong nActionMax = pCT->GetActionMax(); + if (nActionMax != 13) + { + cerr << "Unexpected highest action ID value." << endl; + return false; + } + + for (size_t i = 0; i < SAL_N_ELEMENTS(aChecks); ++i) + { + sal_uInt16 nActId = aChecks[i].mnActionId; + const ScChangeAction* pAction = pCT->GetAction(nActId); + if (!pAction) + { + cerr << "No action for action number " << nActId << " found." << endl; + return false; + } + + if (pAction->GetType() != aChecks[i].meType) + { + cerr << "Unexpected action type for action number " << nActId << "." << endl; + return false; + } + + const ScBigRange& rRange = pAction->GetBigRange(); + ScBigRange aCheck(aChecks[i].mnStartCol, aChecks[i].mnStartRow, aChecks[i].mnStartTab, + aChecks[i].mnEndCol, aChecks[i].mnEndRow, aChecks[i].mnEndTab); + + if (!checkRange(pAction->GetType(), aCheck, rRange)) + { + cerr << "Unexpected range for action number " << nActId + << ": expected=" << toString(aCheck) << " actual=" << toString(rRange) << endl; + return false; + } + + switch (pAction->GetType()) + { + case SC_CAT_INSERT_ROWS: + { + const ScChangeActionIns* p = static_cast<const ScChangeActionIns*>(pAction); + if (p->IsEndOfList() != aChecks[i].mbRowInsertedAtBottom) + { + cerr << "Unexpected end-of-list flag for action number " << nActId << "." << endl; + return false; + } + } + break; + default: + ; + } + } + + return true; + } + + bool checkRevisionUserAndTime( ScDocument& rDoc, const OUString& rOwnerName ) + { + ScChangeTrack* pCT = rDoc.GetChangeTrack(); + if (!pCT) + { + cerr << "Change track instance doesn't exist." << endl; + return false; + } + + ScChangeAction* pAction = pCT->GetLast(); + if (pAction->GetUser() != "Kohei Yoshida") + { + cerr << "Wrong user name." << endl; + return false; + } + + DateTime aDT = pAction->GetDateTime(); + if (aDT.GetYear() != 2014 || aDT.GetMonth() != 7 || aDT.GetDay() != 11) + { + cerr << "Wrong time stamp." << endl; + return false; + } + + // Insert a new record to make sure the user and date-time are correct. + rDoc.SetString(ScAddress(1,8,0), "New String"); + ScCellValue aEmpty; + pCT->AppendContent(ScAddress(1,8,0), aEmpty); + pAction = pCT->GetLast(); + if (!pAction) + { + cerr << "Failed to retrieve last revision." << endl; + return false; + } + + if (rOwnerName != pAction->GetUser()) + { + cerr << "Wrong user name." << endl; + return false; + } + + DateTime aDTNew = pAction->GetDateTime(); + if (aDTNew <= aDT) + { + cerr << "Time stamp of the new revision should be more recent than that of the last revision." << endl; + return false; + } + + return true; + } + + } aTest; + + SvtUserOptions& rUserOpt = SC_MOD()->GetUserOptions(); + rUserOpt.SetToken(UserOptToken::FirstName, "Export"); + rUserOpt.SetToken(UserOptToken::LastName, "Test"); + + OUString aOwnerName = rUserOpt.GetFirstName() + " " + rUserOpt.GetLastName(); + + // First, test the xls variant. + + ScDocShellRef xDocSh = loadDoc("track-changes/simple-cell-changes.", FORMAT_XLS); + CPPUNIT_ASSERT(xDocSh.is()); + ScDocument* pDoc = &xDocSh->GetDocument(); + bool bGood = aTest.check(*pDoc); + CPPUNIT_ASSERT_MESSAGE("Initial check failed (xls).", bGood); + + ScDocShellRef xDocSh2 = saveAndReload(xDocSh.get(), FORMAT_XLS); + xDocSh->DoClose(); + pDoc = &xDocSh2->GetDocument(); + bGood = aTest.check(*pDoc); + CPPUNIT_ASSERT_MESSAGE("Check after reload failed (xls).", bGood); + + // fdo#81445 : Check the blank value string to make sure it's "<empty>". + ScChangeTrack* pCT = pDoc->GetChangeTrack(); + CPPUNIT_ASSERT(pCT); + ScChangeAction* pAction = pCT->GetAction(1); + CPPUNIT_ASSERT(pAction); + OUString aDesc; + pAction->GetDescription(aDesc, pDoc); + CPPUNIT_ASSERT_EQUAL(OUString("Cell B2 changed from '<empty>' to '1'"), aDesc); + + bGood = aTest.checkRevisionUserAndTime(*pDoc, aOwnerName); + CPPUNIT_ASSERT_MESSAGE("Check revision and time failed after reload (xls).", bGood); + + xDocSh2->DoClose(); + + // Now, test the xlsx variant the same way. + + xDocSh = loadDoc("track-changes/simple-cell-changes.", FORMAT_XLSX); + CPPUNIT_ASSERT(xDocSh.is()); + pDoc = &xDocSh->GetDocument(); + aTest.check(*pDoc); + CPPUNIT_ASSERT_MESSAGE("Initial check failed (xlsx).", bGood); + + xDocSh2 = saveAndReload(xDocSh.get(), FORMAT_XLSX); + xDocSh->DoClose(); + pDoc = &xDocSh2->GetDocument(); + bGood = aTest.check(*pDoc); + CPPUNIT_ASSERT_MESSAGE("Check after reload failed (xlsx).", bGood); + + bGood = aTest.checkRevisionUserAndTime(*pDoc, aOwnerName); + CPPUNIT_ASSERT_MESSAGE("Check revision and time failed after reload (xlsx).", bGood); + + xDocSh2->DoClose(); +} + +void ScExportTest::testSheetTabColorsXLSX() +{ + struct + { + bool checkContent( const ScDocument& rDoc ) + { + + std::vector<OUString> aTabNames = rDoc.GetAllTableNames(); + + // green, red, blue, yellow (from left to right). + if (aTabNames.size() != 4) + { + cerr << "There should be exactly 4 sheets." << endl; + return false; + } + + const char* pNames[] = { "Green", "Red", "Blue", "Yellow" }; + for (size_t i = 0; i < SAL_N_ELEMENTS(pNames); ++i) + { + OUString aExpected = OUString::createFromAscii(pNames[i]); + if (aExpected != aTabNames[i]) + { + cerr << "incorrect sheet name: expected='" << aExpected <<"', actual='" << aTabNames[i] << "'" << endl; + return false; + } + } + + static const Color aXclColors[] = + { + 0x0000B050, // green + 0x00FF0000, // red + 0x000070C0, // blue + 0x00FFFF00, // yellow + }; + + for (size_t i = 0; i < SAL_N_ELEMENTS(aXclColors); ++i) + { + if (aXclColors[i] != rDoc.GetTabBgColor(i)) + { + cerr << "wrong sheet color for sheet " << i << endl; + return false; + } + } + + return true; + } + + } aTest; + + ScDocShellRef xDocSh = loadDoc("sheet-tab-color.", FORMAT_XLSX); + { + CPPUNIT_ASSERT_MESSAGE("Failed to load file.", xDocSh.is()); + ScDocument& rDoc = xDocSh->GetDocument(); + bool bRes = aTest.checkContent(rDoc); + CPPUNIT_ASSERT_MESSAGE("Failed on the initial content check.", bRes); + } + + ScDocShellRef xDocSh2 = saveAndReload(xDocSh.get(), FORMAT_XLSX); + CPPUNIT_ASSERT_MESSAGE("Failed to reload file.", xDocSh2.is()); + xDocSh->DoClose(); + ScDocument& rDoc = xDocSh2->GetDocument(); + bool bRes = aTest.checkContent(rDoc); + CPPUNIT_ASSERT_MESSAGE("Failed on the content check after reload.", bRes); + + xDocSh2->DoClose(); +} + +void ScExportTest::testSharedFormulaExportXLS() +{ + struct + { + bool checkContent( ScDocument& rDoc ) + { + formula::FormulaGrammar::Grammar eGram = formula::FormulaGrammar::GRAM_ENGLISH_XL_R1C1; + rDoc.SetGrammar(eGram); + sc::TokenStringContext aCxt(&rDoc, eGram); + + // Check the title row. + + OUString aActual = rDoc.GetString(0,1,0); + OUString aExpected = "Response"; + if (aActual != aExpected) + { + cerr << "Wrong content in A2: expected='" << aExpected << "', actual='" << aActual << "'" << endl; + return false; + } + + aActual = rDoc.GetString(1,1,0); + aExpected = "Response"; + if (aActual != aExpected) + { + cerr << "Wrong content in B2: expected='" << aExpected << "', actual='" << aActual << "'" << endl; + return false; + } + + // A3:A12 and B3:B12 are numbers from 1 to 10. + for (SCROW i = 0; i <= 9; ++i) + { + double fExpected = i + 1.0; + ScAddress aPos(0,i+2,0); + double fActual = rDoc.GetValue(aPos); + if (fExpected != fActual) + { + cerr << "Wrong value in A" << (i+2) << ": expected=" << fExpected << ", actual=" << fActual << endl; + return false; + } + + aPos.IncCol(); + ScFormulaCell* pFC = rDoc.GetFormulaCell(aPos); + if (!pFC) + { + cerr << "B" << (i+2) << " should be a formula cell." << endl; + return false; + } + + OUString aFormula = pFC->GetCode()->CreateString(aCxt, aPos); + aExpected = "Coefficients!RC[-1]"; + if (aFormula != aExpected) + { + cerr << "Wrong formula in B" << (i+2) << ": expected='" << aExpected << "', actual='" << aFormula << "'" << endl; + return false; + } + + fActual = rDoc.GetValue(aPos); + if (fExpected != fActual) + { + cerr << "Wrong value in B" << (i+2) << ": expected=" << fExpected << ", actual=" << fActual << endl; + return false; + } + } + + return true; + } + + } aTest; + + ScDocShellRef xDocSh = loadDoc("shared-formula/3d-reference.", FORMAT_ODS); + { + CPPUNIT_ASSERT_MESSAGE("Failed to load file.", xDocSh.is()); + ScDocument& rDoc = xDocSh->GetDocument(); + + // Check the content of the original. + bool bRes = aTest.checkContent(rDoc); + CPPUNIT_ASSERT_MESSAGE("Content check on the original document failed.", bRes); + } + + ScDocShellRef xDocSh2 = saveAndReload(xDocSh.get(), FORMAT_XLS); + xDocSh->DoClose(); + CPPUNIT_ASSERT_MESSAGE("Failed to reload file.", xDocSh2.is()); + + ScDocument& rDoc = xDocSh2->GetDocument(); + + // Check the content of the reloaded. This should be identical. + bool bRes = aTest.checkContent(rDoc); + CPPUNIT_ASSERT_MESSAGE("Content check on the reloaded document failed.", bRes); + + xDocSh2->DoClose(); +} + +void ScExportTest::testSharedFormulaExportXLSX() +{ + struct + { + bool checkContent( const ScDocument& rDoc ) + { + SCTAB nTabCount = rDoc.GetTableCount(); + if (nTabCount != 2) + { + cerr << "Document should have exactly 2 sheets. " << nTabCount << " found." << endl; + return false; + } + + // Make sure the sheet tab colors are not set. + for (SCROW i = 0; i <= 1; ++i) + { + Color aTabBgColor = rDoc.GetTabBgColor(i); + if (aTabBgColor != COL_AUTO) + { + cerr << "The tab color of Sheet " << (i+1) << " should not be explicitly set." << endl; + return false; + } + } + + // B2:B7 should show 1,2,3,4,5,6. + double fExpected = 1.0; + for (SCROW i = 1; i <= 6; ++i, ++fExpected) + { + ScAddress aPos(1,i,0); + double fVal = rDoc.GetValue(aPos); + if (fVal != fExpected) + { + cerr << "Wrong value in B" << (i+1) << ": expected=" << fExpected << ", actual=" << fVal << endl; + return false; + } + } + + // C2:C7 should show 10,20,...,60. + fExpected = 10.0; + for (SCROW i = 1; i <= 6; ++i, fExpected+=10.0) + { + ScAddress aPos(2,i,0); + double fVal = rDoc.GetValue(aPos); + if (fVal != fExpected) + { + cerr << "Wrong value in C" << (i+1) << ": expected=" << fExpected << ", actual=" << fVal << endl; + return false; + } + } + + // D2:D7 should show 1,2,...,6. + fExpected = 1.0; + for (SCROW i = 1; i <= 6; ++i, ++fExpected) + { + ScAddress aPos(3,i,0); + double fVal = rDoc.GetValue(aPos); + if (fVal != fExpected) + { + cerr << "Wrong value in D" << (i+1) << ": expected=" << fExpected << ", actual=" << fVal << endl; + return false; + } + } + + return true; + } + + } aTest; + + ScDocShellRef xDocSh = loadDoc("shared-formula/3d-reference.", FORMAT_XLSX); + { + CPPUNIT_ASSERT_MESSAGE("Failed to load file.", xDocSh.is()); + ScDocument& rDoc = xDocSh->GetDocument(); + + bool bRes = aTest.checkContent(rDoc); + CPPUNIT_ASSERT_MESSAGE("Content check on the initial document failed.", bRes); + + rDoc.CalcAll(); // Recalculate to flush all cached results. + bRes = aTest.checkContent(rDoc); + CPPUNIT_ASSERT_MESSAGE("Content check on the initial recalculated document failed.", bRes); + } + + // Save and reload, and check the content again. + ScDocShellRef xDocSh2 = saveAndReload(xDocSh.get(), FORMAT_XLSX); + xDocSh->DoClose(); + + CPPUNIT_ASSERT_MESSAGE("Failed to load file.", xDocSh2.is()); + ScDocument& rDoc = xDocSh2->GetDocument(); + rDoc.CalcAll(); // Recalculate to flush all cached results. + + bool bRes = aTest.checkContent(rDoc); + CPPUNIT_ASSERT_MESSAGE("Content check on the reloaded document failed.", bRes); + + xDocSh2->DoClose(); +} + +void ScExportTest::testSharedFormulaStringResultExportXLSX() +{ + struct + { + bool checkContent( const ScDocument& rDoc ) + { + { + // B2:B7 should show A,B,...,F. + const char* const expected[] = { "A", "B", "C", "D", "E", "F" }; + for (SCROW i = 0; i <= 5; ++i) + { + ScAddress aPos(1,i+1,0); + OUString aStr = rDoc.GetString(aPos); + OUString aExpected = OUString::createFromAscii(expected[i]); + if (aStr != aExpected) + { + cerr << "Wrong value in B" << (i+2) << ": expected='" << aExpected << "', actual='" << aStr << "'" << endl; + return false; + } + } + } + + { + // C2:C7 should show AA,BB,...,FF. + const char* const expected[] = { "AA", "BB", "CC", "DD", "EE", "FF" }; + for (SCROW i = 0; i <= 5; ++i) + { + ScAddress aPos(2,i+1,0); + OUString aStr = rDoc.GetString(aPos); + OUString aExpected = OUString::createFromAscii(expected[i]); + if (aStr != aExpected) + { + cerr << "Wrong value in C" << (i+2) << ": expected='" << aExpected << "', actual='" << aStr << "'" << endl; + return false; + } + } + } + + return true; + } + + } aTest; + + ScDocShellRef xDocSh = loadDoc("shared-formula/text-results.", FORMAT_XLSX); + { + CPPUNIT_ASSERT_MESSAGE("Failed to load file.", xDocSh.is()); + ScDocument& rDoc = xDocSh->GetDocument(); + + // Check content without re-calculation, to test cached formula results. + bool bRes = aTest.checkContent(rDoc); + CPPUNIT_ASSERT_MESSAGE("Content check on the initial document failed.", bRes); + + // Now, re-calculate and check the results. + rDoc.CalcAll(); + bRes = aTest.checkContent(rDoc); + CPPUNIT_ASSERT_MESSAGE("Content check on the initial recalculated document failed.", bRes); + } + // Reload and check again. + ScDocShellRef xDocSh2 = saveAndReload(xDocSh.get(), FORMAT_XLSX); + xDocSh->DoClose(); + CPPUNIT_ASSERT_MESSAGE("Failed to re-load file.", xDocSh2.is()); + ScDocument& rDoc = xDocSh2->GetDocument(); + + bool bRes = aTest.checkContent(rDoc); + CPPUNIT_ASSERT_MESSAGE("Content check on the reloaded document failed.", bRes); + + xDocSh2->DoClose(); +} + +void ScExportTest::testFunctionsExcel2010( sal_uLong nFormatType ) +{ + ScDocShellRef xShell = loadDoc("functions-excel-2010.", FORMAT_XLSX); + CPPUNIT_ASSERT_MESSAGE("Failed to load the document.", xShell.is()); + + ScDocShellRef xDocSh = saveAndReload(xShell.get(), nFormatType); + ScDocument& rDoc = xDocSh->GetDocument(); + rDoc.CalcAll(); // perform hard re-calculation. + + testFunctionsExcel2010_Impl(rDoc); + + xDocSh->DoClose(); +} + +void ScExportTest::testFunctionsExcel2010XLSX() +{ + testFunctionsExcel2010(FORMAT_XLSX); +} + +void ScExportTest::testFunctionsExcel2010XLS() +{ + testFunctionsExcel2010(FORMAT_XLS); +} + +void ScExportTest::testCeilingFloor( sal_uLong nFormatType ) +{ + ScDocShellRef xShell = loadDoc("ceiling-floor.", FORMAT_XLSX); + CPPUNIT_ASSERT_MESSAGE("Failed to load the document.", xShell.is()); + + ScDocShellRef xDocSh = saveAndReload(xShell.get(), nFormatType); + ScDocument& rDoc = xDocSh->GetDocument(); + rDoc.CalcAll(); // perform hard re-calculation. + + testCeilingFloor_Impl(rDoc); + + xDocSh->DoClose(); +} + +void ScExportTest::testCeilingFloorXLSX() +{ + testCeilingFloor(FORMAT_XLSX); +} + +void ScExportTest::testCeilingFloorODSToXLSX() +{ + // tdf#100011 - Cannot open sheet containing FLOOR/CEILING functions by MS Excel, after export to .xlsx + ScDocShellRef xShell = loadDoc("ceiling-floor.", FORMAT_ODS); + CPPUNIT_ASSERT_MESSAGE("Failed to load the document.", xShell.is()); + + std::shared_ptr<utl::TempFile> pXPathFile = ScBootstrapFixture::exportTo(&(*xShell), FORMAT_XLSX); + xmlDocUniquePtr pSheet = XPathHelper::parseExport(pXPathFile, m_xSFactory, "xl/workbook.xml"); + CPPUNIT_ASSERT(pSheet); + + // there shouldn't be any defined names during export of FLOOR and CEILING functions to .xlsx + assertXPath(pSheet, "/x:workbook/x:definedNames", 0); + + xShell->DoClose(); +} + +void ScExportTest::testCeilingFloorXLS() +{ + testCeilingFloor(FORMAT_XLS); +} + +void ScExportTest::testCeilingFloorODS() +{ + testCeilingFloor(FORMAT_ODS); +} + +void ScExportTest::testCustomXml() +{ + // Load document and export it to a temporary file + ScDocShellRef xShell = loadDoc("customxml.", FORMAT_XLSX); + CPPUNIT_ASSERT_MESSAGE("Failed to load the document.", xShell.is()); + + std::shared_ptr<utl::TempFile> pXPathFile = ScBootstrapFixture::exportTo(&(*xShell), FORMAT_XLSX); + xmlDocUniquePtr pXmlDoc = XPathHelper::parseExport(pXPathFile, m_xSFactory, "customXml/item1.xml"); + CPPUNIT_ASSERT(pXmlDoc); + xmlDocUniquePtr pRelsDoc = XPathHelper::parseExport(pXPathFile, m_xSFactory, "customXml/_rels/item1.xml.rels"); + CPPUNIT_ASSERT(pRelsDoc); + + // Check there is a relation to itemProps1.xml. + assertXPath(pRelsDoc, "/r:Relationships/r:Relationship", 1); + assertXPath(pRelsDoc, "/r:Relationships/r:Relationship[@Id='rId1']", "Target", "itemProps1.xml"); + + std::unique_ptr<SvStream> pStream = XPathHelper::parseExportStream(pXPathFile, m_xSFactory, "ddp/ddpfile.xen"); + CPPUNIT_ASSERT(pStream); + + xShell->DoClose(); +} + +#ifdef _WIN32 +static sal_Unicode lcl_getWindowsDrive(const OUString& aURL) +{ + static const sal_Int32 nMinLen = strlen("file:///X:/"); + if (aURL.getLength() <= nMinLen) + return 0; + const OUString aUrlStart = aURL.copy(0, nMinLen); + return (aUrlStart.startsWith("file:///") && aUrlStart.endsWith(":/")) ? aUrlStart[8] : 0; +} +#endif + +void ScExportTest::testRelativePathsODS() +{ + ScDocShellRef xDocSh = loadDoc("fdo79305.", FORMAT_ODS); + CPPUNIT_ASSERT(xDocSh.is()); + + std::shared_ptr<utl::TempFile> pTempFile = exportTo(xDocSh.get(), FORMAT_ODS); + xmlDocUniquePtr pDoc = XPathHelper::parseExport(pTempFile, m_xSFactory, "content.xml"); + CPPUNIT_ASSERT(pDoc); + OUString aURL = getXPath(pDoc, + "/office:document-content/office:body/office:spreadsheet/table:table/table:table-row[2]/table:table-cell[2]/text:p/text:a", "href"); +#ifdef _WIN32 + // if the exported document is not on the same drive then the linked document, + // there is no way to get a relative URL for the link, because ../X:/ is undefined. + if (!aURL.startsWith("..")) + { + sal_Unicode aDocDrive = lcl_getWindowsDrive(pTempFile->GetURL()); + sal_Unicode aLinkDrive = lcl_getWindowsDrive(aURL); + CPPUNIT_ASSERT_MESSAGE("document on the same drive but no relative link!", + aDocDrive != 0 && aLinkDrive != 0 && aDocDrive != aLinkDrive); + return; + } +#endif + // make sure that the URL is relative + CPPUNIT_ASSERT(aURL.startsWith("..")); + + xDocSh->DoClose(); +} + +namespace { + +void testSheetProtection_Impl(const ScDocument& rDoc) +{ + CPPUNIT_ASSERT(rDoc.IsTabProtected(0)); + ScTableProtection* pTabProtection = rDoc.GetTabProtection(0); + CPPUNIT_ASSERT(pTabProtection); + CPPUNIT_ASSERT(pTabProtection->isOptionEnabled(ScTableProtection::SELECT_UNLOCKED_CELLS)); + CPPUNIT_ASSERT(!pTabProtection->isOptionEnabled(ScTableProtection::SELECT_LOCKED_CELLS)); +} + +} + +void ScExportTest::testSheetProtectionODS() +{ + ScDocShellRef xDocSh = loadDoc("sheet-protection.", FORMAT_ODS); + CPPUNIT_ASSERT(xDocSh.is()); + + { + ScDocument& rDoc = xDocSh->GetDocument(); + testSheetProtection_Impl(rDoc); + } + + ScDocShellRef xDocSh2 = saveAndReload(xDocSh.get(), FORMAT_ODS); + { + ScDocument& rDoc = xDocSh2->GetDocument(); + testSheetProtection_Impl(rDoc); + } + + xDocSh2->DoClose(); +} + +void ScExportTest::testFunctionsExcel2010ODS() +{ + //testFunctionsExcel2010(FORMAT_ODS); +} + +void ScExportTest::testSwappedOutImageExport() +{ + const char* aFilterNames[] = { + "calc8", + "MS Excel 97", + "Calc Office Open XML", + "generic_HTML", + }; + + // Set cache size to a very small value to make sure one of the images is swapped out + std::shared_ptr< comphelper::ConfigurationChanges > xBatch(comphelper::ConfigurationChanges::create()); + officecfg::Office::Common::Cache::GraphicManager::TotalCacheSize::set(sal_Int32(1), xBatch); + xBatch->commit(); + + for( size_t nFilter = 0; nFilter < SAL_N_ELEMENTS(aFilterNames); ++nFilter ) + { + // Check whether the export code swaps in the image which was swapped out before. + ScDocShellRef xDocSh = loadDoc("document_with_two_images.", FORMAT_ODS); + + const OString sFailedMessage = OStringLiteral("Failed on filter: ") + aFilterNames[nFilter]; + CPPUNIT_ASSERT_MESSAGE(sFailedMessage.getStr(), xDocSh.is()); + + // Export the document and import again for a check + ScDocShellRef xDocSh2 = saveAndReload(xDocSh.get(), nFilter); + xDocSh->DoClose(); + + // Check whether graphic exported well after it was swapped out + uno::Reference< frame::XModel > xModel = xDocSh2->GetModel(); + uno::Reference< sheet::XSpreadsheetDocument > xDoc(xModel, UNO_QUERY_THROW); + uno::Reference< container::XIndexAccess > xIA(xDoc->getSheets(), UNO_QUERY_THROW); + uno::Reference< drawing::XDrawPageSupplier > xDrawPageSupplier( xIA->getByIndex(0), UNO_QUERY_THROW); + uno::Reference< container::XIndexAccess > xDraws(xDrawPageSupplier->getDrawPage(), UNO_QUERY_THROW); + CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), static_cast<sal_Int32>(2), xDraws->getCount()); + + uno::Reference<drawing::XShape> xImage(xDraws->getByIndex(0), uno::UNO_QUERY); + uno::Reference< beans::XPropertySet > XPropSet( xImage, uno::UNO_QUERY_THROW ); + + // Check Graphic, Size + { + uno::Reference<graphic::XGraphic> xGraphic; + XPropSet->getPropertyValue("Graphic") >>= xGraphic; + CPPUNIT_ASSERT_MESSAGE(sFailedMessage.getStr(), xGraphic.is()); + CPPUNIT_ASSERT_MESSAGE(sFailedMessage.getStr(), xGraphic->getType() != graphic::GraphicType::EMPTY); + uno::Reference<awt::XBitmap> xBitmap(xGraphic, uno::UNO_QUERY); + CPPUNIT_ASSERT_MESSAGE(sFailedMessage.getStr(), xBitmap.is()); + CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), static_cast<sal_Int32>(610), xBitmap->getSize().Width ); + CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), static_cast<sal_Int32>(381), xBitmap->getSize().Height ); + } + // Second Image + xImage.set(xDraws->getByIndex(1), uno::UNO_QUERY); + XPropSet.set( xImage, uno::UNO_QUERY_THROW ); + + // Check Graphic, Size + { + uno::Reference<graphic::XGraphic> xGraphic; + XPropSet->getPropertyValue("Graphic") >>= xGraphic; + CPPUNIT_ASSERT_MESSAGE(sFailedMessage.getStr(), xGraphic.is()); + CPPUNIT_ASSERT_MESSAGE(sFailedMessage.getStr(), xGraphic->getType() != graphic::GraphicType::EMPTY); + uno::Reference<awt::XBitmap> xBitmap(xGraphic, uno::UNO_QUERY); + CPPUNIT_ASSERT_MESSAGE(sFailedMessage.getStr(), xBitmap.is()); + CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), static_cast<sal_Int32>(900), xBitmap->getSize().Width ); + CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), static_cast<sal_Int32>(600), xBitmap->getSize().Height ); + } + xDocSh2->DoClose(); + } +} + +ScExportTest::ScExportTest() + : ScBootstrapFixture("sc/qa/unit/data") +{ +} + +void ScExportTest::setUp() +{ + test::BootstrapFixture::setUp(); + + // This is a bit of a fudge, we do this to ensure that ScGlobals::ensure, + // which is a private symbol to us, gets called + m_xCalcComponent = + getMultiServiceFactory()->createInstance("com.sun.star.comp.Calc.SpreadsheetDocument"); + CPPUNIT_ASSERT_MESSAGE("no calc component!", m_xCalcComponent.is()); +} + +void ScExportTest::tearDown() +{ + uno::Reference< lang::XComponent >( m_xCalcComponent, UNO_QUERY_THROW )->dispose(); + test::BootstrapFixture::tearDown(); +} + +void ScExportTest::testSupBookVirtualPathXLS() +{ + ScDocShellRef xShell = loadDoc("external-ref.", FORMAT_XLS); + CPPUNIT_ASSERT(xShell.is()); + + ScDocShellRef xDocSh = saveAndReload(xShell.get(), FORMAT_XLS); + xShell->DoClose(); + CPPUNIT_ASSERT(xDocSh.is()); + + ScDocument& rDoc = xDocSh->GetDocument(); + + ScAddress aPos(0,0,0); + ScTokenArray* pCode = getTokens(rDoc, aPos); + if (!pCode) + CppUnit::Asserter::fail("empty token array", CPPUNIT_SOURCELINE()); + + OUString aFormula = toString(rDoc, aPos, *pCode, rDoc.GetGrammar()); +#ifdef _WIN32 + aFormula = aFormula.copy(0, 9) + aFormula.copy(12); // strip drive letter, e.g. 'C:/' +#endif + OUString aExpectedFormula = OUStringLiteral("'file:///home/timar/Documents/external.xls'#$Sheet1.A1"); + if (aFormula != aExpectedFormula) + { + CppUnit::Asserter::failNotEqual(to_std_string(aExpectedFormula), + to_std_string(aFormula), CPPUNIT_SOURCELINE(), CppUnit::AdditionalMessage("Wrong SupBook VirtualPath URL")); + } + + xDocSh->DoClose(); +} + +void ScExportTest::testLinkedGraphicRT() +{ + // Problem was with linked images + const char* aFilterNames[] = { + "calc8", + "MS Excel 97", + "Calc Office Open XML", + "generic_HTML", + }; + + for( size_t nFilter = 0; nFilter < SAL_N_ELEMENTS(aFilterNames); ++nFilter ) + { + // Load the original file with one image + ScDocShellRef xDocSh = loadDoc("document_with_linked_graphic.", FORMAT_ODS); + const OString sFailedMessage = OStringLiteral("Failed on filter: ") + aFilterNames[nFilter]; + + // Export the document and import again for a check + ScDocShellRef xDocSh2 = saveAndReload(xDocSh.get(), nFilter); + xDocSh->DoClose(); + + // Check whether graphic imported well after export + ScDocument& rDoc = xDocSh->GetDocument(); + ScDrawLayer* pDrawLayer = rDoc.GetDrawLayer(); + CPPUNIT_ASSERT_MESSAGE( sFailedMessage.getStr(), pDrawLayer != nullptr ); + const SdrPage *pPage = pDrawLayer->GetPage(0); + CPPUNIT_ASSERT_MESSAGE( sFailedMessage.getStr(), pPage != nullptr ); + SdrGrafObj* pObject = dynamic_cast<SdrGrafObj*>(pPage->GetObj(0)); + CPPUNIT_ASSERT_MESSAGE( sFailedMessage.getStr(), pObject != nullptr ); + CPPUNIT_ASSERT_MESSAGE( sFailedMessage.getStr(), pObject->IsLinkedGraphic() ); + + const GraphicObject& rGraphicObj = pObject->GetGraphicObject(true); + CPPUNIT_ASSERT_EQUAL_MESSAGE( sFailedMessage.getStr(), int(GraphicType::Bitmap), int(rGraphicObj.GetGraphic().GetType())); + CPPUNIT_ASSERT_EQUAL_MESSAGE( sFailedMessage.getStr(), sal_uLong(864900), rGraphicObj.GetGraphic().GetSizeBytes()); + + xDocSh2->DoClose(); + } +} + +void ScExportTest::testImageWithSpecialID() +{ + const char* aFilterNames[] = { + "calc8", + "MS Excel 97", + "Calc Office Open XML", + "generic_HTML", + }; + + // Trigger swap out mechanism to test swapped state factor too. + std::shared_ptr< comphelper::ConfigurationChanges > batch(comphelper::ConfigurationChanges::create()); + officecfg::Office::Common::Cache::GraphicManager::TotalCacheSize::set(sal_Int32(1), batch); + batch->commit(); + + for( size_t nFilter = 0; nFilter < SAL_N_ELEMENTS(aFilterNames); ++nFilter ) + { + ScDocShellRef xDocSh = loadDoc("images_with_special_IDs.", FORMAT_ODS); + + const OString sFailedMessage = OStringLiteral("Failed on filter: ") + aFilterNames[nFilter]; + CPPUNIT_ASSERT_MESSAGE(sFailedMessage.getStr(), xDocSh.is()); + + // Export the document and import again for a check + ScDocShellRef xDocSh2 = saveAndReload(xDocSh.get(), nFilter); + xDocSh->DoClose(); + + // Check whether graphic was exported well + uno::Reference< frame::XModel > xModel = xDocSh2->GetModel(); + uno::Reference< sheet::XSpreadsheetDocument > xDoc(xModel, UNO_QUERY_THROW); + uno::Reference< container::XIndexAccess > xIA(xDoc->getSheets(), UNO_QUERY_THROW); + uno::Reference< drawing::XDrawPageSupplier > xDrawPageSupplier( xIA->getByIndex(0), UNO_QUERY_THROW); + uno::Reference< container::XIndexAccess > xDraws(xDrawPageSupplier->getDrawPage(), UNO_QUERY_THROW); + CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), static_cast<sal_Int32>(2), xDraws->getCount()); + + uno::Reference<drawing::XShape> xImage(xDraws->getByIndex(0), uno::UNO_QUERY); + uno::Reference< beans::XPropertySet > XPropSet( xImage, uno::UNO_QUERY_THROW ); + + // Check Graphic, Size + { + uno::Reference<graphic::XGraphic> xGraphic; + XPropSet->getPropertyValue("Graphic") >>= xGraphic; + CPPUNIT_ASSERT_MESSAGE(sFailedMessage.getStr(), xGraphic.is()); + CPPUNIT_ASSERT_MESSAGE(sFailedMessage.getStr(), xGraphic->getType() != graphic::GraphicType::EMPTY); + uno::Reference<awt::XBitmap> xBitmap(xGraphic, uno::UNO_QUERY); + CPPUNIT_ASSERT_MESSAGE(sFailedMessage.getStr(), xBitmap.is()); + CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), static_cast<sal_Int32>(610), xBitmap->getSize().Width ); + CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), static_cast<sal_Int32>(381), xBitmap->getSize().Height ); + } + // Second Image + xImage.set(xDraws->getByIndex(1), uno::UNO_QUERY); + XPropSet.set( xImage, uno::UNO_QUERY_THROW ); + + // Check Graphic, Size + { + uno::Reference<graphic::XGraphic> xGraphic; + XPropSet->getPropertyValue("Graphic") >>= xGraphic; + CPPUNIT_ASSERT_MESSAGE(sFailedMessage.getStr(), xGraphic.is()); + CPPUNIT_ASSERT_MESSAGE(sFailedMessage.getStr(), xGraphic->getType() != graphic::GraphicType::EMPTY); + uno::Reference<awt::XBitmap> xBitmap(xGraphic, uno::UNO_QUERY); + CPPUNIT_ASSERT_MESSAGE(sFailedMessage.getStr(), xBitmap.is()); + CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), static_cast<sal_Int32>(900), xBitmap->getSize().Width ); + CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), static_cast<sal_Int32>(600), xBitmap->getSize().Height ); + } + xDocSh2->DoClose(); + } +} + +void ScExportTest::testAbsNamedRangeHTML() +{ + ScDocShellRef xDocSh = loadDoc("numberformat.", FORMAT_HTML); + xDocSh->DoHardRecalc(); + ScDocShellRef xDocSh2 = saveAndReload(xDocSh.get(), FORMAT_ODS); + xDocSh->DoClose(); + xDocSh2->DoHardRecalc(); + + ScDocument& rDoc = xDocSh2->GetDocument(); + ScRangeData* pRangeData = rDoc.GetRangeName()->findByUpperName(OUString("HTML_1")); + ScSingleRefData* pRef = pRangeData->GetCode()->FirstToken()->GetSingleRef(); + // see tdf#119141 for the reason why this isn't Sheet1.HTML_1 + CPPUNIT_ASSERT_MESSAGE("HTML_1 is an absolute reference",!pRef->IsTabRel()); + + xDocSh2->DoClose(); +} + +void ScExportTest::testSheetLocalRangeNameXLS() +{ + ScDocShellRef xDocSh = loadDoc("named-ranges-local.", FORMAT_XLS); + xDocSh->DoHardRecalc(); + ScDocShellRef xDocSh2 = saveAndReload(xDocSh.get(), FORMAT_XLS); + xDocSh->DoClose(); + xDocSh2->DoHardRecalc(); + + ScDocument& rDoc = xDocSh2->GetDocument(); + ScRangeName* pRangeName = rDoc.GetRangeName(0); + CPPUNIT_ASSERT(pRangeName); + CPPUNIT_ASSERT_EQUAL(size_t(2), pRangeName->size()); + + OUString aFormula; + rDoc.GetFormula(3, 11, 0, aFormula); + CPPUNIT_ASSERT_EQUAL(OUString("=SUM(local_name2)"), aFormula); + ASSERT_DOUBLES_EQUAL(14.0, rDoc.GetValue(3, 11, 0)); + + rDoc.GetFormula(6, 4, 0, aFormula); + CPPUNIT_ASSERT_EQUAL(OUString("=local_name1"), aFormula); + + xDocSh2->DoClose(); +} + +void ScExportTest::testRelativeNamedExpressionsXLS() +{ + ScDocShellRef xDocSh = loadDoc("tdf113991_relativeNamedRanges.", FORMAT_ODS); + xDocSh->DoHardRecalc(); + ScDocShellRef xDocSh2 = saveAndReload(xDocSh.get(), FORMAT_XLS); + xDocSh->DoClose(); + xDocSh2->DoHardRecalc(); + ScDocument& rDoc = xDocSh2->GetDocument(); + + // Sheet1:G3 + ScAddress aPos(6,2,0); + CPPUNIT_ASSERT_EQUAL(1.0, rDoc.GetValue(aPos)); + ASSERT_FORMULA_EQUAL(rDoc, aPos, "single_cell_A3", nullptr); + // Sheet2:F6 + aPos = ScAddress(5,5,1); + CPPUNIT_ASSERT_EQUAL(18.0, rDoc.GetValue(aPos)); + ASSERT_FORMULA_EQUAL(rDoc, aPos, "SUM(test_conflict)", nullptr); + // Sheet2:H3 + aPos = ScAddress(7,2,1); + CPPUNIT_ASSERT_EQUAL(10.0, rDoc.GetValue(aPos)); + ASSERT_FORMULA_EQUAL(rDoc, aPos, "single_global_A3", nullptr); + // Sheet2:H6 + aPos = ScAddress(7,5,1); + CPPUNIT_ASSERT_EQUAL(75.0, rDoc.GetValue(aPos)); + ASSERT_FORMULA_EQUAL(rDoc, aPos, "SUM(A6:F6)", nullptr); + xDocSh2->DoClose(); +} + +void ScExportTest::testSheetTextBoxHyperlinkXLSX() +{ + ScDocShellRef xShell = loadDoc("textbox-hyperlink.", FORMAT_XLSX); + CPPUNIT_ASSERT(xShell.is()); + + ScDocShellRef xDocSh = saveAndReload(&(*xShell), FORMAT_XLSX); + CPPUNIT_ASSERT(xDocSh.is()); + + xmlDocUniquePtr pDoc = XPathHelper::parseExport2(*this, *xDocSh, m_xSFactory, "xl/drawings/drawing1.xml", FORMAT_XLSX); + CPPUNIT_ASSERT(pDoc); + + assertXPath(pDoc, "/xdr:wsDr[1]/xdr:twoCellAnchor[1]/xdr:sp[1]/xdr:nvSpPr[1]/xdr:cNvPr[1]/a:hlinkClick[1]", 1); + + xDocSh->DoClose(); +} + +void ScExportTest::testFontSizeXLSX() +{ + ScDocShellRef xDocSh = loadDoc("fontSize.", FORMAT_XLSX); + CPPUNIT_ASSERT(xDocSh.is()); + + xmlDocUniquePtr pDoc = XPathHelper::parseExport2(*this, *xDocSh, m_xSFactory, "xl/drawings/drawing1.xml", FORMAT_XLSX); + CPPUNIT_ASSERT(pDoc); + OUString fontSize = getXPath(pDoc, + "/xdr:wsDr/xdr:twoCellAnchor/xdr:sp[1]/xdr:txBody/a:p[1]/a:r[1]/a:rPr", "sz"); + // make sure that the font size is 18 + CPPUNIT_ASSERT_EQUAL(OUString("1800"), fontSize); + + xDocSh->DoClose(); +} + +void ScExportTest::testSheetCharacterKerningSpaceXLSX() +{ + ScDocShellRef xShell = loadDoc("textbox-CharKerningSpace.", FORMAT_XLSX); + CPPUNIT_ASSERT(xShell.is()); + + ScDocShellRef xDocSh = saveAndReload(&(*xShell), FORMAT_XLSX); + CPPUNIT_ASSERT(xDocSh.is()); + + xmlDocUniquePtr pDoc = XPathHelper::parseExport2(*this, *xDocSh, m_xSFactory, "xl/drawings/drawing1.xml", FORMAT_XLSX); + CPPUNIT_ASSERT(pDoc); + + OUString CharKerningSpace = getXPath(pDoc, + "/xdr:wsDr[1]/xdr:twoCellAnchor[1]/xdr:sp[1]/xdr:txBody[1]/a:p[1]/a:r[1]/a:rPr[1]","spc"); + + // make sure that the CharKerning is 1997. + CPPUNIT_ASSERT_EQUAL(OUString("1997"), CharKerningSpace); + + xDocSh->DoClose(); +} + +void ScExportTest::testSheetCondensedCharacterSpaceXLSX() +{ + ScDocShellRef xShell = loadDoc("textbox-CondensedCharacterSpace.", FORMAT_XLSX); + CPPUNIT_ASSERT(xShell.is()); + + ScDocShellRef xDocSh = saveAndReload(&(*xShell), FORMAT_XLSX); + CPPUNIT_ASSERT(xDocSh.is()); + + xmlDocUniquePtr pDoc = XPathHelper::parseExport2(*this, *xDocSh, m_xSFactory, "xl/drawings/drawing1.xml", FORMAT_XLSX); + CPPUNIT_ASSERT(pDoc); + + OUString CondensedCharSpace = getXPath(pDoc, + "/xdr:wsDr[1]/xdr:twoCellAnchor[1]/xdr:sp[1]/xdr:txBody[1]/a:p[1]/a:r[1]/a:rPr[1]","spc"); + + // make sure that the CondensedCharSpace is -1002. + CPPUNIT_ASSERT_EQUAL(OUString("-1002"), CondensedCharSpace); + + xDocSh->DoClose(); +} + +void ScExportTest::testTextUnderlineColorXLSX() +{ + ScDocShellRef xDocSh = loadDoc("underlineColor.", FORMAT_XLSX); + CPPUNIT_ASSERT(xDocSh.is()); + + xmlDocUniquePtr pDoc = XPathHelper::parseExport2(*this, *xDocSh, m_xSFactory, "xl/drawings/drawing1.xml", FORMAT_XLSX); + CPPUNIT_ASSERT(pDoc); + // Make sure the underline type is double line + assertXPath(pDoc, "/xdr:wsDr/xdr:twoCellAnchor[1]/xdr:sp[1]/xdr:txBody/a:p[1]/a:r[1]/a:rPr", "u", "dbl"); + + assertXPath(pDoc, "/xdr:wsDr/xdr:twoCellAnchor[1]/xdr:sp[1]/xdr:txBody/a:p[1]/a:r[1]/a:rPr", "b", "1"); + // Make sure that the underline color is RED + assertXPath(pDoc, "/xdr:wsDr/xdr:twoCellAnchor[1]/xdr:sp[1]/xdr:txBody/a:p[1]/a:r[1]/a:rPr/a:uFill/a:solidFill/a:srgbClr", "val", "ff0000"); + + // Make sure the underline type is drawn with heavy line + assertXPath(pDoc, "/xdr:wsDr/xdr:twoCellAnchor[2]/xdr:sp[1]/xdr:txBody/a:p[1]/a:r[1]/a:rPr", "u", "heavy"); + // tdf#104219 Make sure that uFill is not existing and uFillTx is set. + // It mean that color is automatic, should be the same color as the text. + assertXPath(pDoc, "/xdr:wsDr/xdr:twoCellAnchor[2]/xdr:sp[1]/xdr:txBody/a:p[1]/a:r[1]/a:rPr/a:uFill", 0); + assertXPath(pDoc, "/xdr:wsDr/xdr:twoCellAnchor[2]/xdr:sp[1]/xdr:txBody/a:p[1]/a:r[1]/a:rPr/a:uFillTx", 1); + + xDocSh->DoClose(); +} + +void ScExportTest::testSheetRunParagraphPropertyXLSX() +{ + ScDocShellRef xShell = loadDoc("TextColor.", FORMAT_XLSX); + CPPUNIT_ASSERT(xShell.is()); + + ScDocShellRef xDocSh = saveAndReload(&(*xShell), FORMAT_XLSX); + CPPUNIT_ASSERT(xDocSh.is()); + + xmlDocUniquePtr pDoc = XPathHelper::parseExport2(*this, *xDocSh, m_xSFactory, "xl/sharedStrings.xml", FORMAT_XLSX); + CPPUNIT_ASSERT(pDoc); + + OUString aColor = getXPath(pDoc, "/x:sst/x:si/x:r[1]/x:rPr[1]/x:color", "rgb"); + CPPUNIT_ASSERT_EQUAL(OUString("FFFF0000"), aColor); + + xDocSh->DoClose(); +} + +void ScExportTest::testPreserveTextWhitespaceXLSX() +{ + ScDocShellRef xShell = loadDoc("preserve-whitespace.", FORMAT_XLSX); + ScDocShellRef xDocSh = saveAndReload(&(*xShell), FORMAT_XLSX); + CPPUNIT_ASSERT(xDocSh.is()); + + xmlDocUniquePtr pDoc = XPathHelper::parseExport2(*this, *xDocSh, m_xSFactory, "xl/sharedStrings.xml", FORMAT_XLSX); + CPPUNIT_ASSERT(pDoc); + assertXPath(pDoc, "/x:sst/x:si/x:t", "space", "preserve"); + xDocSh->DoClose(); +} + +void ScExportTest::testPreserveTextWhitespace2XLSX() +{ + ScDocShellRef xShell = loadDoc("preserve_space.", FORMAT_XLSX); + ScDocShellRef xDocSh = saveAndReload(&(*xShell), FORMAT_XLSX); + CPPUNIT_ASSERT(xDocSh.is()); + + xmlDocUniquePtr pDoc = XPathHelper::parseExport2(*this, *xDocSh, m_xSFactory, "xl/sharedStrings.xml", FORMAT_XLSX); + CPPUNIT_ASSERT(pDoc); + assertXPath(pDoc, "/x:sst/x:si[1]/x:t", "space", "preserve"); + assertXPath(pDoc, "/x:sst/x:si[2]/x:r[1]/x:t", "space", "preserve"); + assertXPath(pDoc, "/x:sst/x:si[2]/x:r[2]/x:t", "space", "preserve"); + xDocSh->DoClose(); +} + +void ScExportTest::testHiddenShapeXLS() +{ + ScDocShellRef xDocSh = loadDoc("hiddenShape.", FORMAT_XLS); + CPPUNIT_ASSERT(xDocSh.is()); + + ScDocument& rDoc = xDocSh->GetDocument(); + CPPUNIT_ASSERT(rDoc.GetTableCount() > 0); + ScDrawLayer* pDrawLayer = rDoc.GetDrawLayer(); + SdrPage* pPage = pDrawLayer->GetPage(0); + CPPUNIT_ASSERT(pPage); + SdrObject* pObj = pPage->GetObj(0); + CPPUNIT_ASSERT(pObj); + CPPUNIT_ASSERT_MESSAGE("Drawing object should not be visible.", !pObj->IsVisible()); + CPPUNIT_ASSERT_MESSAGE("Drawing object should not be printable.", !pObj->IsPrintable()); + xDocSh->DoClose(); +} + +void ScExportTest::testHiddenShapeXLSX() +{ + ScDocShellRef xDocSh = loadDoc("hiddenShape.", FORMAT_XLSX); + CPPUNIT_ASSERT(xDocSh.is()); + + ScDocument& rDoc = xDocSh->GetDocument(); + CPPUNIT_ASSERT(rDoc.GetTableCount() > 0); + ScDrawLayer* pDrawLayer = rDoc.GetDrawLayer(); + SdrPage* pPage = pDrawLayer->GetPage(0); + CPPUNIT_ASSERT(pPage); + SdrObject* pObj = pPage->GetObj(0); + CPPUNIT_ASSERT(pObj); + CPPUNIT_ASSERT_MESSAGE("Drawing object should not be visible.", !pObj->IsVisible()); + CPPUNIT_ASSERT_MESSAGE("Drawing object should not be printable.", !pObj->IsPrintable()); + + xmlDocUniquePtr pDoc = XPathHelper::parseExport2(*this, *xDocSh, m_xSFactory, "xl/drawings/drawing1.xml", FORMAT_XLSX); + CPPUNIT_ASSERT(pDoc); + assertXPath(pDoc, "/xdr:wsDr/xdr:twoCellAnchor/xdr:sp[1]/xdr:nvSpPr/xdr:cNvPr", "hidden", "1"); + xDocSh->DoClose(); +} + +void ScExportTest::testShapeAutofitXLSX() +{ + ScDocShellRef xDocSh = loadDoc("testShapeAutofit.", FORMAT_XLSX); + CPPUNIT_ASSERT(xDocSh.is()); + + xmlDocUniquePtr pDoc = XPathHelper::parseExport2(*this, *xDocSh, m_xSFactory, "xl/drawings/drawing1.xml", FORMAT_XLSX); + CPPUNIT_ASSERT(pDoc); + + // TextAutoGrowHeight --> "Fit height to text" / "Resize shape to fit text" --> true + assertXPath(pDoc, "/xdr:wsDr/xdr:twoCellAnchor[1]/xdr:sp/xdr:txBody/a:bodyPr/a:spAutoFit", 1); + // TextAutoGrowHeight --> "Fit height to text" / "Resize shape to fit text" --> false + assertXPath(pDoc, "/xdr:wsDr/xdr:twoCellAnchor[2]/xdr:sp/xdr:txBody/a:bodyPr/a:noAutofit", 1); + + xDocSh->DoClose(); +} + +void ScExportTest::testHyperlinkXLSX() +{ + ScDocShellRef xDocSh = loadDoc("hyperlink.", FORMAT_XLSX); + CPPUNIT_ASSERT(xDocSh.is()); + + xmlDocUniquePtr pDoc = XPathHelper::parseExport2(*this, *xDocSh, m_xSFactory, "xl/drawings/_rels/drawing1.xml.rels", FORMAT_XLSX); + CPPUNIT_ASSERT(pDoc); + assertXPath(pDoc, "/r:Relationships/r:Relationship", "Target", "#Sheet2!A1"); + + xDocSh->DoClose(); +} + +void ScExportTest::testMoveCellAnchoredShapesODS() +{ + ScDocShellRef xDocSh = loadDoc("move-cell-anchored-shapes.", FORMAT_ODS); + CPPUNIT_ASSERT_MESSAGE("Failed to load move-cell-anchored-shapes.ods", xDocSh.is()); + + // There are two cell-anchored objects on the first sheet. + ScDocument& rDoc = xDocSh->GetDocument(); + + CPPUNIT_ASSERT_MESSAGE("There should be at least one sheet.", rDoc.GetTableCount() > 0); + + ScDrawLayer* pDrawLayer = rDoc.GetDrawLayer(); + SdrPage* pPage = pDrawLayer->GetPage(0); + CPPUNIT_ASSERT_MESSAGE("draw page for sheet 1 should exist.", pPage); + SdrObject* pObj = pPage->GetObj(0); + CPPUNIT_ASSERT_MESSAGE("Failed to get drawing object.", pObj); + + // Check cell anchor state + ScAnchorType oldType = ScDrawLayer::GetAnchorType(*pObj); + CPPUNIT_ASSERT_EQUAL_MESSAGE( "Failed to get anchor type", SCA_CELL_RESIZE, oldType); + + // Get anchor data + ScDrawObjData* pData = ScDrawLayer::GetObjData(pObj); + CPPUNIT_ASSERT_MESSAGE("Failed to retrieve user data for this object.", pData); + CPPUNIT_ASSERT_MESSAGE("Bounding rectangle should have been calculated upon import.", !pData->getShapeRect().IsEmpty()); + + ScAddress aDataStart = pData->maStart; + ScAddress aDataEnd = pData->maEnd; + + // Get non rotated anchor data + ScDrawObjData* pNData = ScDrawLayer::GetNonRotatedObjData( pObj ); + CPPUNIT_ASSERT_MESSAGE("Failed to retrieve non rotated user data for this object.", pNData); + CPPUNIT_ASSERT_MESSAGE("Bounding rectangle should have been calculated upon import.", !pNData->getShapeRect().IsEmpty()); + + ScAddress aNDataStart = pNData->maStart; + ScAddress aNDataEnd = pNData->maEnd; + CPPUNIT_ASSERT_EQUAL(aDataStart, aNDataStart); + CPPUNIT_ASSERT_EQUAL(aDataEnd , aNDataEnd); + + // Insert 2 rows. + rDoc.InsertRow(ScRange( 0, aDataStart.Row() - 1, 0, MAXCOL, aDataStart.Row(), 0)); + + // Get anchor data + pData = ScDrawLayer::GetObjData(pObj); + CPPUNIT_ASSERT_MESSAGE("Failed to retrieve user data for this object.", pData); + CPPUNIT_ASSERT_MESSAGE("Bounding rectangle should have been calculated upon import.", !pData->getShapeRect().IsEmpty()); + + // Get non rotated anchor data + pNData = ScDrawLayer::GetNonRotatedObjData( pObj ); + CPPUNIT_ASSERT_MESSAGE("Failed to retrieve non rotated user data for this object.", pNData); + CPPUNIT_ASSERT_MESSAGE("Bounding rectangle should have been calculated upon import.", !pNData->getShapeRect().IsEmpty()); + + // Check if data has moved to new rows + CPPUNIT_ASSERT_EQUAL( pData->maStart.Row(), aDataStart.Row() + 2 ); + CPPUNIT_ASSERT_EQUAL( pData->maEnd.Row(), aDataEnd.Row() + 2 ); + + CPPUNIT_ASSERT_EQUAL( pNData->maStart.Row(), aNDataStart.Row() + 2 ); + CPPUNIT_ASSERT_EQUAL( pNData->maEnd.Row(), aNDataEnd.Row() + 2 ); + + // Save the anchor data + aDataStart = pData->maStart; + aDataEnd = pData->maEnd; + aNDataStart = pNData->maStart; + aNDataEnd = pNData->maEnd; + + // Save the document and load again to check anchor persist + ScDocShellRef xDocSh1 = saveAndReload(&(*xDocSh), FORMAT_ODS); + + // There are two cell-anchored objects on the first sheet. + ScDocument& rDoc1 = xDocSh1->GetDocument(); + + CPPUNIT_ASSERT_MESSAGE("There should be at least one sheet.", rDoc1.GetTableCount() > 0); + + pDrawLayer = rDoc1.GetDrawLayer(); + pPage = pDrawLayer->GetPage(0); + CPPUNIT_ASSERT_MESSAGE("draw page for sheet 1 should exist.", pPage); + pObj = pPage->GetObj(0); + CPPUNIT_ASSERT_MESSAGE("Failed to get drawing object.", pObj); + + // Check cell anchor state + oldType = ScDrawLayer::GetAnchorType(*pObj); + CPPUNIT_ASSERT_EQUAL_MESSAGE( "Failed to get anchor type", SCA_CELL_RESIZE, oldType); + + // Get anchor data + pData = ScDrawLayer::GetObjData(pObj); + CPPUNIT_ASSERT_MESSAGE("Failed to retrieve user data for this object.", pData); + CPPUNIT_ASSERT_MESSAGE("Bounding rectangle should have been calculated upon import.", !pData->getShapeRect().IsEmpty()); + + // Get non rotated anchor data + pNData = ScDrawLayer::GetNonRotatedObjData( pObj ); + CPPUNIT_ASSERT_MESSAGE("Failed to retrieve non rotated user data for this object.", pNData); + CPPUNIT_ASSERT_MESSAGE("Bounding rectangle should have been calculated upon import.", !pNData->getShapeRect().IsEmpty()); + + // Check if data after save it + CPPUNIT_ASSERT_EQUAL(pData->maStart, aDataStart); + CPPUNIT_ASSERT_EQUAL(pData->maEnd , aDataEnd); + + CPPUNIT_ASSERT_EQUAL(pNData->maStart, aNDataStart); + CPPUNIT_ASSERT_EQUAL(pNData->maEnd , aNDataEnd); + + // Insert a column. + rDoc1.InsertCol(ScRange( aDataStart.Col(), 0 , 0 , aDataStart.Col(), MAXROW, 0 )); + + // Get anchor data + pData = ScDrawLayer::GetObjData(pObj); + CPPUNIT_ASSERT_MESSAGE("Failed to retrieve user data for this object.", pData); + CPPUNIT_ASSERT_MESSAGE("Bounding rectangle should have been calculated upon import.", !pData->getShapeRect().IsEmpty()); + + // Get non rotated anchor data + pNData = ScDrawLayer::GetNonRotatedObjData( pObj ); + CPPUNIT_ASSERT_MESSAGE("Failed to retrieve non rotated user data for this object.", pNData); + CPPUNIT_ASSERT_MESSAGE("Bounding rectangle should have been calculated upon import.", !pNData->getShapeRect().IsEmpty()); + + // Check if data has moved to new rows + CPPUNIT_ASSERT_EQUAL(pData->maStart.Col(), SCCOL(aDataStart.Col() + 1)); + CPPUNIT_ASSERT_EQUAL(pData->maEnd.Col() , SCCOL(aDataEnd.Col() + 1)); + + CPPUNIT_ASSERT_EQUAL(pNData->maStart.Col(), SCCOL(aNDataStart.Col() + 1)); + CPPUNIT_ASSERT_EQUAL(pNData->maEnd.Col() , SCCOL(aNDataEnd.Col() + 1)); + + // Save the anchor data + aDataStart = pData->maStart; + aDataEnd = pData->maEnd; + aNDataStart = pNData->maStart; + aNDataEnd = pNData->maEnd; + + // Save the document and load again to check anchor persist + ScDocShellRef xDocSh2 = saveAndReload(&(*xDocSh1), FORMAT_ODS); + + // There are two cell-anchored objects on the first sheet. + ScDocument& rDoc2 = xDocSh2->GetDocument(); + + CPPUNIT_ASSERT_MESSAGE("There should be at least one sheet.", rDoc2.GetTableCount() > 0); + + pDrawLayer = rDoc2.GetDrawLayer(); + pPage = pDrawLayer->GetPage(0); + CPPUNIT_ASSERT_MESSAGE("draw page for sheet 1 should exist.", pPage); + pObj = pPage->GetObj(0); + CPPUNIT_ASSERT_MESSAGE("Failed to get drawing object.", pObj); + + // Check cell anchor state + oldType = ScDrawLayer::GetAnchorType(*pObj); + CPPUNIT_ASSERT_EQUAL_MESSAGE( "Failed to get anchor type", SCA_CELL_RESIZE, oldType); + + // Get anchor data + pData = ScDrawLayer::GetObjData(pObj); + CPPUNIT_ASSERT_MESSAGE("Failed to retrieve user data for this object.", pData); + CPPUNIT_ASSERT_MESSAGE("Bounding rectangle should have been calculated upon import.", !pData->getShapeRect().IsEmpty()); + + // Get non rotated anchor data + pNData = ScDrawLayer::GetNonRotatedObjData( pObj ); + CPPUNIT_ASSERT_MESSAGE("Failed to retrieve non rotated user data for this object.", pNData); + CPPUNIT_ASSERT_MESSAGE("Bounding rectangle should have been calculated upon import.", !pNData->getShapeRect().IsEmpty()); + + // Check if data after save it + CPPUNIT_ASSERT_EQUAL(pData->maStart, aDataStart); + CPPUNIT_ASSERT_EQUAL(pData->maEnd , aDataEnd); + + CPPUNIT_ASSERT_EQUAL(pNData->maStart, aNDataStart); + CPPUNIT_ASSERT_EQUAL(pNData->maEnd , aNDataEnd); + + xDocSh2->DoClose(); +} + +void ScExportTest::testMatrixMultiplicationXLSX() +{ + ScDocShellRef xShell = loadDoc("matrix-multiplication.", FORMAT_XLSX); + CPPUNIT_ASSERT(xShell.is()); + + ScDocShellRef xDocSh = saveAndReload(&(*xShell), FORMAT_XLSX); + CPPUNIT_ASSERT(xDocSh.is()); + + xmlDocUniquePtr pDoc = XPathHelper::parseExport2(*this, *xDocSh, m_xSFactory, "xl/worksheets/sheet1.xml", FORMAT_XLSX); + CPPUNIT_ASSERT(pDoc); + + OUString CellFormulaRange = getXPath(pDoc, + "/x:worksheet/x:sheetData/x:row[4]/x:c/x:f","ref"); + + // make sure that the CellFormulaRange is G5:G6. + CPPUNIT_ASSERT_EQUAL(OUString("G5:G6"), CellFormulaRange); + + OUString CellFormulaType = getXPath(pDoc, + "/x:worksheet/x:sheetData/x:row[4]/x:c/x:f","t"); + + // make sure that the CellFormulaType is array. + CPPUNIT_ASSERT_EQUAL(OUString("array"), CellFormulaType); + + xDocSh->DoClose(); +} + +void ScExportTest::testRefStringXLSX() +{ + ScDocShellRef xDocSh = loadDoc("ref_string.", FORMAT_XLSX); + CPPUNIT_ASSERT_MESSAGE("Failed to open doc", xDocSh.is()); + + //make sure ref syntax gets saved for MSO-produced docs + xDocSh = saveAndReload( &(*xDocSh), FORMAT_XLSX); + CPPUNIT_ASSERT_MESSAGE("Failed to reload doc", xDocSh.is()); + + ScDocument& rDoc = xDocSh->GetDocument(); + ScCalcConfig aCalcConfig = rDoc.GetCalcConfig(); + CPPUNIT_ASSERT_EQUAL(formula::FormulaGrammar::CONV_XL_A1, aCalcConfig.meStringRefAddressSyntax); + + xDocSh->DoClose(); +} + +void ScExportTest::testRefStringConfigXLSX() +{ + // this doc is configured with CalcA1 ref syntax + ScDocShellRef xDocSh = loadDoc("empty.", FORMAT_XLSX); + CPPUNIT_ASSERT_MESSAGE("Failed to open doc", xDocSh.is()); + + xDocSh = saveAndReload( &(*xDocSh), FORMAT_XLSX); + CPPUNIT_ASSERT_MESSAGE("Failed to reload doc", xDocSh.is()); + + ScDocument& rDoc = xDocSh->GetDocument(); + ScCalcConfig aConfig = rDoc.GetCalcConfig(); + CPPUNIT_ASSERT_EQUAL_MESSAGE("String ref syntax doesn't match", formula::FormulaGrammar::CONV_OOO, + aConfig.meStringRefAddressSyntax); + + xDocSh->DoClose(); + + // this doc has no entry for ref syntax + xDocSh = loadDoc("empty-noconf.", FORMAT_XLSX); + CPPUNIT_ASSERT_MESSAGE("Failed to open 2nd doc", xDocSh.is()); + + ScDocument& rDoc2 = xDocSh->GetDocument(); + aConfig = rDoc2.GetCalcConfig(); + // therefore after import, ref syntax should be set to CalcA1 | ExcelA1 + CPPUNIT_ASSERT_EQUAL_MESSAGE("String ref syntax doesn't match", formula::FormulaGrammar::CONV_A1_XL_A1, + aConfig.meStringRefAddressSyntax); + + //set ref syntax to something else than ExcelA1 (native to xlsx format) ... + aConfig.meStringRefAddressSyntax = formula::FormulaGrammar::CONV_XL_R1C1; + rDoc2.SetCalcConfig( aConfig ); + + ScDocShellRef xNewDocSh = saveAndReload( &(*xDocSh), FORMAT_XLSX); + CPPUNIT_ASSERT_MESSAGE("Failed to reload 2nd doc", xNewDocSh.is()); + + // ... and make sure it got saved + ScDocument& rDoc3 = xNewDocSh->GetDocument(); + aConfig = rDoc3.GetCalcConfig(); + CPPUNIT_ASSERT_EQUAL_MESSAGE("String ref syntax doesn't match", formula::FormulaGrammar::CONV_XL_R1C1, + aConfig.meStringRefAddressSyntax); + + xDocSh->DoClose(); + xNewDocSh->DoClose(); +} + +void ScExportTest::testRefStringUnspecified() +{ + ScDocShell* pShell = new ScDocShell( + SfxModelFlags::EMBEDDED_OBJECT | + SfxModelFlags::DISABLE_EMBEDDED_SCRIPTS | + SfxModelFlags::DISABLE_DOCUMENT_RECOVERY); + pShell->DoInitNew(); + + ScDocument& rDoc = pShell->GetDocument(); + ScCalcConfig aConfig = rDoc.GetCalcConfig(); + CPPUNIT_ASSERT_EQUAL_MESSAGE("Default string ref syntax value doesn't match", formula::FormulaGrammar::CONV_UNSPECIFIED, + aConfig.meStringRefAddressSyntax); + + // change formula syntax (i.e. not string ref syntax) to ExcelA1 + rDoc.SetGrammar( formula::FormulaGrammar::GRAM_NATIVE_XL_A1 ); + + ScDocShellRef xDocSh = saveAndReload(pShell, FORMAT_ODS); + CPPUNIT_ASSERT_MESSAGE("Failed to reload doc", xDocSh.is()); + + // with string ref syntax at its default value, we should've saved ExcelA1 + ScDocument& rDoc2 = xDocSh->GetDocument(); + aConfig = rDoc2.GetCalcConfig(); + CPPUNIT_ASSERT_EQUAL_MESSAGE("String ref syntax doesn't match", formula::FormulaGrammar::CONV_XL_A1, + aConfig.meStringRefAddressSyntax); + + xDocSh->DoClose(); +} + +void ScExportTest::testHeaderImageODS() +{ + // Graphic as header background was lost on export. + ScDocShellRef xShell = loadDoc("header-image.", FORMAT_ODS); + ScDocShellRef xDocSh = saveAndReload(&(*xShell), FORMAT_ODS); + uno::Reference<style::XStyleFamiliesSupplier> xStyleFamiliesSupplier(xDocSh->GetModel(), uno::UNO_QUERY); + uno::Reference<container::XNameAccess> xStyleFamilies = xStyleFamiliesSupplier->getStyleFamilies(); + uno::Reference<container::XNameAccess> xPageStyles(xStyleFamilies->getByName("PageStyles"), uno::UNO_QUERY); + uno::Reference<beans::XPropertySet> xStyle(xPageStyles->getByName("Default"), uno::UNO_QUERY); + + uno::Reference<graphic::XGraphic> xGraphic; + xStyle->getPropertyValue("HeaderBackGraphic") >>= xGraphic; + CPPUNIT_ASSERT(xGraphic.is()); + xDocSh->DoClose(); +} + +void ScExportTest::testTextDirectionXLSX() +{ + ScDocShellRef xDocSh = loadDoc("writingMode.", FORMAT_XLSX); + CPPUNIT_ASSERT(xDocSh.is()); + + xmlDocUniquePtr pDoc = XPathHelper::parseExport2(*this, *xDocSh, m_xSFactory, "xl/styles.xml", FORMAT_XLSX); + CPPUNIT_ASSERT(pDoc); + + assertXPath(pDoc, "/x:styleSheet/x:cellXfs/x:xf[2]/x:alignment", "readingOrder", "1");//LTR + assertXPath(pDoc, "/x:styleSheet/x:cellXfs/x:xf[3]/x:alignment", "readingOrder", "2");//RTL + + xDocSh->DoClose(); +} + +void ScExportTest::testTdf66668() +{ + // Would hang on exporting without the fix in place + ScDocShellRef xDocSh = loadDoc("tdf66668.", FORMAT_XLSX); + CPPUNIT_ASSERT_MESSAGE("Failed to open doc", xDocSh.is()); + + xmlDocUniquePtr pDoc = XPathHelper::parseExport2(*this, *xDocSh, m_xSFactory, "xl/styles.xml", FORMAT_XLSX); + CPPUNIT_ASSERT(pDoc); + xDocSh->DoClose(); +} + +void ScExportTest::testTdf130108() +{ + ScDocShellRef xDocSh = loadDoc("tdf130108.", FORMAT_ODS); + CPPUNIT_ASSERT(xDocSh.is()); + + xmlDocUniquePtr pDoc = XPathHelper::parseExport2(*this, *xDocSh, m_xSFactory, "xl/styles.xml", FORMAT_XLSX); + CPPUNIT_ASSERT(pDoc); + + assertXPath(pDoc, "/x:styleSheet/x:dxfs/x:dxf/x:font/x:b", "val", "1"); + assertXPath(pDoc, "/x:styleSheet/x:dxfs/x:dxf/x:font/x:i", "val", "0"); + assertXPath(pDoc, "/x:styleSheet/x:dxfs/x:dxf/x:font/x:color", "rgb", "FFFFFFFF"); + assertXPath(pDoc, "/x:styleSheet/x:dxfs/x:dxf/x:font/x:sz", "val", "10"); + assertXPath(pDoc, "/x:styleSheet/x:dxfs/x:dxf/x:fill/x:patternFill/x:bgColor", "rgb", "FFCC0000"); + + xDocSh->DoClose(); +} + +void ScExportTest::testTdf76949() +{ + ScDocShellRef xDocSh = loadDoc("tdf76949.", FORMAT_ODS); + CPPUNIT_ASSERT(xDocSh.is()); + + xmlDocUniquePtr pSheet = XPathHelper::parseExport2(*this, *xDocSh, m_xSFactory, "xl/worksheets/sheet1.xml", FORMAT_XLSX); + CPPUNIT_ASSERT(pSheet); + + assertXPathContent(pSheet, "/x:worksheet/x:sheetData/x:row/x:c/x:f", "_xlfn.CHISQ.DIST(1,1,1)"); + + xDocSh->DoClose(); +} + +void ScExportTest::testTdf55417() +{ + ScDocShellRef xDocSh = loadDoc("tdf55417.", FORMAT_XLSX); + CPPUNIT_ASSERT(xDocSh.is()); + + xmlDocUniquePtr pDoc = XPathHelper::parseExport2(*this, *xDocSh, m_xSFactory, "xl/styles.xml", FORMAT_XLSX); + CPPUNIT_ASSERT(pDoc); + assertXPath(pDoc, "/x:styleSheet/x:cellXfs/x:xf[1]/x:alignment", 1); + assertXPath(pDoc, "/x:styleSheet/x:cellXfs/x:xf[2]/x:alignment", 1); + + xDocSh->DoClose(); +} + +void ScExportTest::testTdf129985() +{ + ScDocShellRef xDocSh = loadDoc("tdf129985.", FORMAT_XLSX); + CPPUNIT_ASSERT(xDocSh.is()); + + xmlDocUniquePtr pDoc = XPathHelper::parseExport2(*this, *xDocSh, m_xSFactory, "xl/styles.xml", FORMAT_XLSX); + CPPUNIT_ASSERT(pDoc); + + assertXPath(pDoc, "/x:styleSheet/x:numFmts/x:numFmt[2]", "formatCode", "m/d/yyyy"); + + xDocSh->DoClose(); +} + +void ScExportTest::testTdf73063() +{ + ScDocShellRef xDocSh = loadDoc("tdf73063.", FORMAT_XLSX); + CPPUNIT_ASSERT(xDocSh.is()); + + xmlDocUniquePtr pDoc = XPathHelper::parseExport2(*this, *xDocSh, m_xSFactory, "xl/styles.xml", FORMAT_XLSX); + CPPUNIT_ASSERT(pDoc); + + assertXPath(pDoc, "/x:styleSheet/x:numFmts/x:numFmt[2]", "formatCode", "[$-1C1A]dddd\", \"d\". \"mmmm\\ yyyy;@"); + + xDocSh->DoClose(); +} + +xmlDocUniquePtr ScExportTest::testTdf95640(const OUString& rFileName, sal_Int32 nSourceFormat, + sal_Int32 nDestFormat) +{ + ScDocShellRef xShell = loadDoc(rFileName, nSourceFormat); + CPPUNIT_ASSERT(xShell); + + auto pXPathFile = ScBootstrapFixture::exportTo(&(*xShell), nDestFormat); + xShell->DoClose(); + + return XPathHelper::parseExport(pXPathFile, m_xSFactory, "xl/worksheets/sheet1.xml"); +} + +void ScExportTest::testTdf95640_ods_to_xlsx() +{ + // Roundtripping sort options with user defined list to XLSX + xmlDocUniquePtr pDoc = testTdf95640("tdf95640.", FORMAT_ODS, FORMAT_XLSX); + + assertXPath(pDoc, "//x:worksheet/x:autoFilter", "ref", "A1:B4"); + + assertXPath(pDoc, "//x:worksheet/x:autoFilter/x:sortState/x:sortCondition", "ref", "A2:A4"); + + assertXPath(pDoc, "//x:worksheet/x:autoFilter/x:sortState/x:sortCondition", "customList", + "Jan,Feb,Mar,Apr,May,Jun,Jul,Aug,Sep,Oct,Nov,Dec"); +} + +void ScExportTest::testTdf95640_ods_to_xlsx_with_standard_list() +{ + // Roundtripping sort options with user defined list to XLSX + xmlDocUniquePtr pDoc = testTdf95640("tdf95640_standard_list.", FORMAT_ODS, FORMAT_XLSX); + + assertXPath(pDoc, "//x:worksheet/x:autoFilter", "ref", "A1:B4"); + + assertXPath(pDoc, "//x:worksheet/x:autoFilter/x:sortState/x:sortCondition", "ref", "A2:A4"); + + assertXPath(pDoc, "//x:worksheet/x:autoFilter/x:sortState/x:sortCondition", "customList", + "Sunday,Monday,Tuesday,Wednesday,Thursday,Friday,Saturday"); +} + +void ScExportTest::testTdf95640_xlsx_to_xlsx() +{ + // XLSX Roundtripping sort options with custom sort list - note + // that compared to ODS source documents above, here we _actually_ + // can use custom lists (beyond the global user defines), like + // low, medium, high + xmlDocUniquePtr pDoc = testTdf95640("tdf95640.", FORMAT_XLSX, FORMAT_XLSX); + + assertXPath(pDoc, "//x:worksheet/x:autoFilter", "ref", "A1:B4"); + + assertXPath(pDoc, "//x:worksheet/x:autoFilter/x:sortState/x:sortCondition", "ref", "A2:A4"); + + assertXPath(pDoc, "//x:worksheet/x:autoFilter/x:sortState/x:sortCondition", "customList", + "Low,Medium,High"); +} + +void ScExportTest::testTdf88657ODS() +{ + ScDocShellRef xDocSh = loadDoc("tdf88657.", FORMAT_ODS); + CPPUNIT_ASSERT(xDocSh.is()); + + xmlDocUniquePtr pDoc = XPathHelper::parseExport2(*this, *xDocSh, m_xSFactory, "styles.xml", FORMAT_ODS); + CPPUNIT_ASSERT(pDoc); + + assertXPath(pDoc, "//number:fraction", "min-denominator-digits", "3"); + + xDocSh->DoClose(); +} + +void ScExportTest::testConditionalFormatRangeListXLSX() +{ + ScDocShellRef xDocSh = loadDoc("conditionalformat_rangelist.", FORMAT_ODS); + CPPUNIT_ASSERT(xDocSh.is()); + + xmlDocUniquePtr pDoc = XPathHelper::parseExport2(*this, *xDocSh, m_xSFactory, "xl/worksheets/sheet1.xml", FORMAT_XLSX); + CPPUNIT_ASSERT(pDoc); + + assertXPath(pDoc, "//x:conditionalFormatting", "sqref", "F4 F10"); + + xDocSh->DoClose(); +} + +void ScExportTest::testConditionalFormatContainsTextXLSX() +{ + ScDocShellRef xDocSh = loadDoc("conditionalformat_containstext.", FORMAT_ODS); + CPPUNIT_ASSERT(xDocSh.is()); + + xmlDocUniquePtr pDoc = XPathHelper::parseExport2(*this, *xDocSh, m_xSFactory, "xl/worksheets/sheet1.xml", FORMAT_XLSX); + CPPUNIT_ASSERT(pDoc); + + assertXPathContent(pDoc, "//x:conditionalFormatting/x:cfRule/x:formula", "NOT(ISERROR(SEARCH(\"test\",A1)))"); + + xDocSh->DoClose(); +} + +void ScExportTest::testConditionalFormatPriorityCheckXLSX() +{ + ScDocShellRef xDocSh = loadDoc("conditional_fmt_checkpriority.", FORMAT_XLSX); + CPPUNIT_ASSERT(xDocSh.is()); + + xmlDocUniquePtr pDoc = XPathHelper::parseExport2(*this, *xDocSh, m_xSFactory, "xl/worksheets/sheet1.xml", FORMAT_XLSX); + CPPUNIT_ASSERT(pDoc); + + constexpr bool bHighPriorityExtensionA1 = true; // Should A1's extension cfRule has higher priority than normal cfRule ? + constexpr bool bHighPriorityExtensionA3 = false; // Should A3's extension cfRule has higher priority than normal cfRule ? + + size_t nA1NormalPriority = 0; + size_t nA1ExtPriority = 0; + size_t nA3NormalPriority = 0; + size_t nA3ExtPriority = 0; + + for (size_t nIdx = 1; nIdx <= 2; ++nIdx) + { + OString aIdx = OString::number(nIdx); + OUString aCellAddr = getXPath(pDoc, "//x:conditionalFormatting[" + aIdx + "]", "sqref"); + OUString aPriority = getXPath(pDoc, "//x:conditionalFormatting[" + aIdx + "]/x:cfRule", "priority"); + + CPPUNIT_ASSERT_MESSAGE("conditionalFormatting sqref must be either A1 or A3", aCellAddr == "A1" || aCellAddr == "A3"); + + if (aCellAddr == "A1") + nA1NormalPriority = aPriority.toUInt32(); + else + nA3NormalPriority = aPriority.toUInt32(); + + aCellAddr = getXPathContent(pDoc, "//x:extLst/x:ext[1]/x14:conditionalFormattings/x14:conditionalFormatting[" + aIdx + "]/xm:sqref"); + aPriority = getXPath(pDoc, "//x:extLst/x:ext[1]/x14:conditionalFormattings/x14:conditionalFormatting[" + aIdx + "]/x14:cfRule", "priority"); + + CPPUNIT_ASSERT_MESSAGE("x14:conditionalFormatting sqref must be either A1 or A3", aCellAddr == "A1" || aCellAddr == "A3"); + + if (aCellAddr == "A1") + nA1ExtPriority = aPriority.toUInt32(); + else + nA3ExtPriority = aPriority.toUInt32(); + } + + CPPUNIT_ASSERT_EQUAL_MESSAGE("Wrong priorities for A1", bHighPriorityExtensionA1, nA1ExtPriority < nA1NormalPriority); + CPPUNIT_ASSERT_EQUAL_MESSAGE("Wrong priorities for A3", bHighPriorityExtensionA3, nA3ExtPriority < nA3NormalPriority); + + xDocSh->DoClose(); +} + +void ScExportTest::testConditionalFormatOriginXLSX() +{ + ScDocShellRef xDocSh = loadDoc("conditional_fmt_origin.", FORMAT_XLSX); + CPPUNIT_ASSERT(xDocSh.is()); + + xmlDocUniquePtr pDoc = XPathHelper::parseExport2(*this, *xDocSh, m_xSFactory, "xl/worksheets/sheet1.xml", FORMAT_XLSX); + CPPUNIT_ASSERT(pDoc); + + // tdf#124953 : The range-list is B3:C6 F1:G2, origin address in the formula should be B1, not B3. + OUString aFormula = getXPathContent(pDoc, "//x:conditionalFormatting/x:cfRule/x:formula"); + CPPUNIT_ASSERT_EQUAL_MESSAGE("Wrong origin address in formula", OUString("NOT(ISERROR(SEARCH(\"BAC\",B1)))"), aFormula); + + xDocSh->DoClose(); +} + +void ScExportTest::testTdf41722() +{ + ScDocShellRef xDocSh = loadDoc("tdf41722.", FORMAT_XLSX); + CPPUNIT_ASSERT(xDocSh.is()); + + xmlDocUniquePtr pDoc = XPathHelper::parseExport2(*this, *xDocSh, m_xSFactory, "xl/worksheets/sheet1.xml", FORMAT_XLSX); + CPPUNIT_ASSERT(pDoc); + + assertXPath(pDoc, "//x:conditionalFormatting/x:cfRule[1]", "operator", "containsText"); + assertXPath(pDoc, "//x:conditionalFormatting/x:cfRule[2]", "operator", "containsText"); + assertXPath(pDoc, "//x:conditionalFormatting/x:cfRule[3]", "operator", "containsText"); + + xDocSh->DoClose(); +} + +void ScExportTest::testTdf113621() +{ + ScDocShellRef xDocSh = loadDoc("tdf113621.", FORMAT_XLSX); + CPPUNIT_ASSERT(xDocSh.is()); + + xmlDocUniquePtr pDoc = XPathHelper::parseExport2(*this, *xDocSh, m_xSFactory, "xl/worksheets/sheet1.xml", FORMAT_XLSX); + CPPUNIT_ASSERT(pDoc); + + assertXPath(pDoc, "//x:conditionalFormatting", "sqref", "A1:A1048576"); + + xDocSh->DoClose(); +} + +void ScExportTest::testEscapeCharInNumberFormatXLSX() +{ + ScDocShellRef xDocSh = loadDoc("tdf81939.", FORMAT_XLSX); + CPPUNIT_ASSERT( xDocSh.is() ); + xDocSh = saveAndReload( &(*xDocSh), FORMAT_XLSX); + CPPUNIT_ASSERT( xDocSh.is() ); + + xmlDocUniquePtr pDoc = XPathHelper::parseExport2(*this, *xDocSh, m_xSFactory, "xl/styles.xml", FORMAT_XLSX); + CPPUNIT_ASSERT(pDoc); + + const sal_Unicode cEuro (8364); // € symbol + assertXPath(pDoc, "/x:styleSheet/x:numFmts/x:numFmt[2]", "formatCode", "00\\ 00\\ 00\\ 00\\ 00"); + assertXPath(pDoc, "/x:styleSheet/x:numFmts/x:numFmt[3]", "formatCode", "00\\.00\\.00\\.000\\.0"); // tdf#81939 + // "_-* #,##0\ _€_-;\-* #,##0\ _€_-;_-* "- "_€_-;_-@_-" // tdf#81222 + OUString rFormatStrExpected ( "_-* #,##0\\ _" + OUStringChar(cEuro) + "_-;\\-* #,##0\\ _" + + OUStringChar(cEuro) + "_-;_-* \"- \"_" + OUStringChar(cEuro) + "_-;_-@_-" ); + assertXPath(pDoc, "/x:styleSheet/x:numFmts/x:numFmt[4]", "formatCode", rFormatStrExpected ); + // "_-* #,##0" €"_-;\-* #,##0" €"_-;_-* "- €"_-;_-@_-"); + rFormatStrExpected = "_-* #,##0\" " + OUStringChar(cEuro) + "\"_-;\\-* #,##0\" " + + OUStringChar(cEuro) + "\"_-;_-* \"- " + OUStringChar(cEuro) + "\"_-;_-@_-"; + assertXPath(pDoc, "/x:styleSheet/x:numFmts/x:numFmt[5]", "formatCode", rFormatStrExpected ); + // remove escape char in fraction + assertXPath(pDoc, "/x:styleSheet/x:numFmts/x:numFmt[6]", "formatCode", "# ?/?;[RED]\\-# #/#####"); + + xDocSh->DoClose(); +} + +void ScExportTest::testNatNumInNumberFormatXLSX() +{ + ScDocShellRef xDocSh = loadDoc("tdf79398_NatNum5.", FORMAT_ODS); + CPPUNIT_ASSERT( xDocSh.is() ); + xDocSh = saveAndReload( &(*xDocSh), FORMAT_XLSX); // Convert [NatNum5] to [DBNum2] in Chinese + CPPUNIT_ASSERT( xDocSh.is() ); + + xmlDocUniquePtr pDoc = XPathHelper::parseExport2(*this, *xDocSh, m_xSFactory, "xl/styles.xml", FORMAT_XLSX); + CPPUNIT_ASSERT(pDoc); + + assertXPath(pDoc, "/x:styleSheet/x:numFmts/x:numFmt[3]", "formatCode", "[DBNum2][$-804]General;[RED][DBNum2][$-804]General"); + + xDocSh->DoClose(); +} + +void ScExportTest::testExponentWithoutSignFormatXLSX() +{ + ScDocShellRef xDocSh = loadDoc("tdf102370_ExponentWithoutSign.", FORMAT_ODS); + CPPUNIT_ASSERT( xDocSh.is() ); + xDocSh = saveAndReload( &(*xDocSh), FORMAT_XLSX); + CPPUNIT_ASSERT( xDocSh.is() ); + + xDocSh = saveAndReload( &(*xDocSh), FORMAT_ODS); + CPPUNIT_ASSERT(xDocSh.is()); + + ScDocument& rDoc = xDocSh->GetDocument(); + sal_uInt32 nNumberFormat; + rDoc.GetNumberFormat(0, 0, 0, nNumberFormat); + const SvNumberformat* pNumberFormat = rDoc.GetFormatTable()->GetEntry(nNumberFormat); + const OUString& rFormatStr = pNumberFormat->GetFormatstring(); + const OUString aExpectedFormatStr = "0.00E0"; + + CPPUNIT_ASSERT_EQUAL_MESSAGE("Number format lost exponent without sign during Excel export", aExpectedFormatStr, rFormatStr); + + xDocSh->DoClose(); +} + +void ScExportTest::testExtendedLCIDXLSX() +{ + ScDocShellRef xDocSh = loadDoc("tdf36038_ExtendedLCID.", FORMAT_ODS); + CPPUNIT_ASSERT( xDocSh.is() ); + xDocSh = saveAndReload( &(*xDocSh), FORMAT_XLSX); + CPPUNIT_ASSERT( xDocSh.is() ); + + xmlDocUniquePtr pDoc = XPathHelper::parseExport2(*this, *xDocSh, m_xSFactory, "xl/styles.xml", FORMAT_XLSX); + CPPUNIT_ASSERT(pDoc); + // Check export + assertXPath(pDoc, "/x:styleSheet/x:numFmts/x:numFmt[2]", "formatCode", "[$-107041E]dd\\-mm\\-yyyy"); + assertXPath(pDoc, "/x:styleSheet/x:numFmts/x:numFmt[3]", "formatCode", "[$-D07041E]dd\\-mm\\-yyyy"); + assertXPath(pDoc, "/x:styleSheet/x:numFmts/x:numFmt[4]", "formatCode", "[$-1030411]dd\\-mm\\-ee"); + assertXPath(pDoc, "/x:styleSheet/x:numFmts/x:numFmt[5]", "formatCode", "[$-1B030411]dd\\-mm\\-ee"); + assertXPath(pDoc, "/x:styleSheet/x:numFmts/x:numFmt[6]", "formatCode", "[$-108040D]dd\\-mm\\-yyyy"); + //assertXPath(pDoc, "/x:styleSheet/x:numFmts/x:numFmt[7]", "formatCode", "[$-108040D]dd\\-mm\\-yyyy"); + assertXPath(pDoc, "/x:styleSheet/x:numFmts/x:numFmt[7]", "formatCode", "[$-1060401]dd\\-mm\\-yyyy"); + assertXPath(pDoc, "/x:styleSheet/x:numFmts/x:numFmt[8]", "formatCode", "[$-2060401]dd\\-mm\\-yyyy"); + + // Check import + ScDocument& rDoc = xDocSh->GetDocument(); + SvNumberFormatter* pNumFormatter = rDoc.GetFormatTable(); + sal_uInt32 nNumberFormat; + const OUString aLang[5] = { "[$-41E]", "[$-411]", "[$-40D]", "[$-401]", "[$-500]" }; + const OUString aCalendar[5] = { "[~buddhist]DD-MM-YYYY", "[~gengou]DD-MM-EE", "[~jewish]DD-MM-YYYY", "[~hijri]DD-MM-YYYY", "[~dangi]YYYY/MM/DD" }; + for ( sal_Int16 nCol = 1; nCol <= 2; nCol++ ) + { + for ( sal_Int16 nRow = 1; nRow <= 4; nRow++ ) + { + rDoc.GetNumberFormat(nCol, nRow, 0, nNumberFormat); + const SvNumberformat* pNumberFormat = pNumFormatter->GetEntry(nNumberFormat); + const OUString& rFormatStr = pNumberFormat->GetFormatstring(); + const OUString aExpectedFormatStr = aLang[nRow-1] + ( (nCol==2 && nRow!=3) ? OUString("[NatNum1]") : OUString() ) + aCalendar[nRow-1]; + + CPPUNIT_ASSERT_EQUAL_MESSAGE("Number format lost extended LCID during Excel export", aExpectedFormatStr, rFormatStr); + } + } + + xDocSh->DoClose(); +} + +void ScExportTest::testHiddenRepeatedRowsODS() +{ + ScDocShellRef xDocSh = loadDoc("empty.", FORMAT_ODS); + CPPUNIT_ASSERT( xDocSh.is() ); + + { + ScDocument& rDoc = xDocSh->GetDocument(); + rDoc.SetRowHidden(0, 20, 0, true); + } + + xDocSh = saveAndReload( &(*xDocSh), FORMAT_ODS); + ScDocument& rDoc = xDocSh->GetDocument(); + SCROW nFirstRow = 0; + SCROW nLastRow = 0; + bool bHidden = rDoc.RowHidden(0, 0, &nFirstRow, &nLastRow); + CPPUNIT_ASSERT(bHidden); + CPPUNIT_ASSERT_EQUAL(SCROW(0), nFirstRow); + CPPUNIT_ASSERT_EQUAL(SCROW(20), nLastRow); + xDocSh->DoClose(); +} + +void ScExportTest::testHyperlinkTargetFrameODS() +{ + ScDocShellRef xDocSh = loadDoc("hyperlink_frame.", FORMAT_ODS); + CPPUNIT_ASSERT(xDocSh.is()); + + ScDocument& rDoc = xDocSh->GetDocument(); + const EditTextObject* pEditText = rDoc.GetEditText(ScAddress(2, 5, 0)); + CPPUNIT_ASSERT(pEditText); + + const SvxFieldData* pData = pEditText->GetFieldData(0, 0, text::textfield::Type::URL); + CPPUNIT_ASSERT_MESSAGE("Failed to get the URL data.", pData && pData->GetClassId() == text::textfield::Type::URL); + + const SvxURLField* pURLData = static_cast<const SvxURLField*>(pData); + OUString aTargetFrame = pURLData->GetTargetFrame(); + CPPUNIT_ASSERT_EQUAL(OUString("_blank"), aTargetFrame); + + xmlDocUniquePtr pDoc = XPathHelper::parseExport2(*this, *xDocSh, m_xSFactory, "content.xml", FORMAT_ODS); + CPPUNIT_ASSERT(pDoc); + OUString aTargetFrameExport = getXPath(pDoc, + "/office:document-content/office:body/office:spreadsheet/table:table/table:table-row[2]/table:table-cell[2]/text:p/text:a", "target-frame-name"); + CPPUNIT_ASSERT_EQUAL(OUString("_blank"), aTargetFrameExport); + + xDocSh->DoClose(); +} + +void ScExportTest::testOpenDocumentAsReadOnly() +{ + ScDocShellRef xDocSh = loadDoc("open-as-read-only.", FORMAT_XLSX); + CPPUNIT_ASSERT(xDocSh->IsSecurityOptOpenReadOnly()); + ScDocShellRef xDocSh2 = saveAndReload(xDocSh.get(), FORMAT_XLSX); + CPPUNIT_ASSERT(xDocSh2->IsSecurityOptOpenReadOnly()); + xDocSh->DoClose(); + xDocSh2->DoClose(); +} + +void ScExportTest::testKeepSettingsOfBlankRows() +{ + ScDocShellRef xDocSh = loadDoc("tdf41425.", FORMAT_XLSX); + CPPUNIT_ASSERT(xDocSh.is()); + + std::shared_ptr<utl::TempFile> pXPathFile = ScBootstrapFixture::exportTo(&(*xDocSh), FORMAT_XLSX); + xmlDocUniquePtr pSheet = XPathHelper::parseExport(pXPathFile, m_xSFactory, "xl/worksheets/sheet1.xml"); + CPPUNIT_ASSERT(pSheet); + + // saved blank row with not default setting in A2 + assertXPath(pSheet, "/x:worksheet/x:sheetData/x:row", 2); + + xDocSh->DoClose(); +} + +void ScExportTest::testTdf105272() +{ + ScDocShellRef xDocSh = loadDoc("tdf105272.", FORMAT_XLSX); + CPPUNIT_ASSERT(xDocSh.is()); + xDocSh = saveAndReload(xDocSh.get(), FORMAT_XLSX); + ScDocument& rDoc = xDocSh->GetDocument(); + //without the fix in place,it would fail + //Expected: Table1[[#This Row],[Total]]/Table1[[#This Row],['# Athletes]] + //Actual : table1[[#this row],[total]]/table1[[#this row],['# athletes]] + + ASSERT_FORMULA_EQUAL(rDoc, ScAddress(7, 3, 0), + "Table1[[#This Row],[Total]]/Table1[[#This Row],['# Athletes]]", + "Wrong formula"); +} + +void ScExportTest::testTdf118990() +{ + ScDocShellRef xDocSh = loadDoc("tdf118990.", FORMAT_XLSX); + CPPUNIT_ASSERT(xDocSh.is()); + xDocSh = saveAndReload(xDocSh.get(), FORMAT_XLSX); + ScDocument& rDoc = xDocSh->GetDocument(); + + // TODO: also test A1, which contains a UNC reference to \\localhost\share\lookupsource.xlsx, + // but currently looses "localhost" part when normalized in INetURLObject, becoming + // file:///share/lookupsource.xlsx - which is incorrect, since it points to local filesystem + // and not to Windows network share. + + ASSERT_FORMULA_EQUAL(rDoc, ScAddress(0, 1, 0), + "VLOOKUP(B1,'file://192.168.1.1/share/lookupsource.xlsx'#$Sheet1.A1:B5,2)", + "Wrong Windows share (using host IP) URL in A2"); + + ASSERT_FORMULA_EQUAL(rDoc, ScAddress(0, 2, 0), + "VLOOKUP(B1,'file://NETWORKHOST/share/lookupsource.xlsx'#$Sheet1.A1:B5,2)", + "Wrong Windows share (using hostname) URL in A3"); + + xDocSh->DoClose(); +} + +void ScExportTest::testTdf121612() +{ + ScDocShellRef xDocSh = loadDoc("tdf121612.", FORMAT_ODS); + CPPUNIT_ASSERT(xDocSh.is()); + xDocSh = saveAndReload(xDocSh.get(), FORMAT_XLSX); + + ScDocument& rDoc = xDocSh->GetDocument(); + + // There should be a pivot table + CPPUNIT_ASSERT(rDoc.HasPivotTable()); + + // DP collection is not lost after export and has one entry + ScDPCollection* pDPColl = rDoc.GetDPCollection(); + CPPUNIT_ASSERT(pDPColl); + CPPUNIT_ASSERT_EQUAL(size_t(1), pDPColl->GetCount()); + + xDocSh->DoClose(); +} + +void ScExportTest::testTdf112936() +{ + ScDocShellRef xDocSh = loadDoc("tdf112936.", FORMAT_XLSX); + CPPUNIT_ASSERT(xDocSh.is()); + + xmlDocUniquePtr pDoc = XPathHelper::parseExport2(*this, *xDocSh, m_xSFactory, "xl/pivotCache/pivotCacheDefinition1.xml", FORMAT_XLSX); + CPPUNIT_ASSERT(pDoc); + + assertXPath(pDoc, "//x:pivotCacheDefinition", "recordCount", "4"); + assertXPath(pDoc, "//x:pivotCacheDefinition", "createdVersion", "3"); + + xDocSh->DoClose(); +} + +void ScExportTest::testXltxExport() +{ + // Create new document + ScDocShell* pShell = new ScDocShell( + SfxModelFlags::EMBEDDED_OBJECT | + SfxModelFlags::DISABLE_EMBEDDED_SCRIPTS | + SfxModelFlags::DISABLE_DOCUMENT_RECOVERY); + pShell->DoInitNew(); + + // Export as template and check content type + xmlDocUniquePtr pDoc = XPathHelper::parseExport2(*this, *pShell, m_xSFactory, "[Content_Types].xml", FORMAT_XLTX); + CPPUNIT_ASSERT(pDoc); + assertXPath(pDoc, "/ContentType:Types/ContentType:Override[@PartName='/xl/workbook.xml']", + "ContentType", "application/vnd.openxmlformats-officedocument.spreadsheetml.template.main+xml"); +} + +void ScExportTest::testPivotCacheAfterExportXLSX() +{ + ScDocShellRef xDocSh = loadDoc("numgroup_example.", FORMAT_ODS); + CPPUNIT_ASSERT(xDocSh.is()); + + // export only + std::shared_ptr<utl::TempFile> pTemp = saveAs(xDocSh.get(), FORMAT_XLSX); + + ScDocument& rDoc = xDocSh->GetDocument(); + CPPUNIT_ASSERT(rDoc.HasPivotTable()); + + // Two pivot tables + ScDPCollection* pDPColl = rDoc.GetDPCollection(); + CPPUNIT_ASSERT(pDPColl); + CPPUNIT_ASSERT_EQUAL(size_t(2), pDPColl->GetCount()); + + // One cache + ScDPCollection::SheetCaches& rSheetCaches = pDPColl->GetSheetCaches(); + CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(1), rSheetCaches.size()); + const ScDPCache* pCache = rSheetCaches.getExistingCache(ScRange(0, 0, 0, 3, 30, 0)); + CPPUNIT_ASSERT_MESSAGE("Pivot cache is expected for A1:D31 on the first sheet.", pCache); + + // See if XLSX export didn't damage group info of the 1st pivot table + const ScDPNumGroupInfo* pInfo = pCache->GetNumGroupInfo(1); + CPPUNIT_ASSERT_MESSAGE("No number group info :(", pInfo); + + xDocSh->DoClose(); +} + +void ScExportTest::testTdf114969XLSX() +{ + ScDocShellRef xDocSh = loadDoc("sheet_name_with_dots.", FORMAT_ODS); + CPPUNIT_ASSERT(xDocSh.is()); + + xmlDocUniquePtr pDoc = XPathHelper::parseExport2(*this, *xDocSh, m_xSFactory, "xl/worksheets/sheet1.xml", FORMAT_XLSX); + CPPUNIT_ASSERT(pDoc); + assertXPath(pDoc, "/x:worksheet/x:hyperlinks/x:hyperlink[1]", "location", "'1.1.1.1'!C1"); + assertXPath(pDoc, "/x:worksheet/x:hyperlinks/x:hyperlink[2]", "location", "'1.1.1.1'!C2"); + + xDocSh->DoClose(); +} + +void ScExportTest::testTdf115192XLSX() +{ + ScDocShellRef xDocSh = loadDoc("test_115192.", FORMAT_XLSX); + CPPUNIT_ASSERT(xDocSh.is()); + + xmlDocUniquePtr pDoc = XPathHelper::parseExport2(*this, *xDocSh, m_xSFactory, "xl/drawings/_rels/drawing1.xml.rels", FORMAT_XLSX); + CPPUNIT_ASSERT(pDoc); + assertXPath(pDoc, "/r:Relationships/r:Relationship[@Id='rId1']", "TargetMode", "External"); + assertXPathNoAttribute(pDoc, "/r:Relationships/r:Relationship[@Id='rId2']", "TargetMode"); + assertXPath(pDoc, "/r:Relationships/r:Relationship[@Id='rId3']", "TargetMode", "External"); + + xDocSh->DoClose(); +} + +void ScExportTest::testTdf91634XLSX() +{ + ScDocShellRef xDocSh = loadDoc("image_hyperlink.", FORMAT_XLSX); + CPPUNIT_ASSERT(xDocSh.is()); + std::shared_ptr<utl::TempFile> pXPathFile = ScBootstrapFixture::exportTo(&(*xDocSh), FORMAT_XLSX); + + xmlDocUniquePtr pDoc = XPathHelper::parseExport(pXPathFile, m_xSFactory, "xl/drawings/drawing1.xml"); + CPPUNIT_ASSERT(pDoc); + assertXPath(pDoc, "/xdr:wsDr/xdr:twoCellAnchor/xdr:pic/xdr:nvPicPr/xdr:cNvPr/a:hlinkClick", 1); + + xmlDocUniquePtr pXmlRels = XPathHelper::parseExport(pXPathFile, m_xSFactory, "xl/drawings/_rels/drawing1.xml.rels"); + CPPUNIT_ASSERT(pXmlRels); + assertXPath(pXmlRels, "/r:Relationships/r:Relationship[@Id='rId1']", "Target", "https://www.google.com/"); + assertXPath(pXmlRels, "/r:Relationships/r:Relationship[@Id='rId1']", "TargetMode", "External"); + + xDocSh->DoClose(); +} + +void ScExportTest::testValidationCopyPaste() +{ + ScDocShellRef xDocSh = loadDoc("validation-copypaste.", FORMAT_ODS); + CPPUNIT_ASSERT(xDocSh.is()); + ScDocument& rSrcDoc = xDocSh->GetDocument(); + + // Copy B1 from src doc to clip + ScDocument aClipDoc(SCDOCMODE_CLIP); + ScRange aSrcRange(1, 0, 1); + ScClipParam aClipParam(aSrcRange, false); + ScMarkData aMark(rSrcDoc.GetSheetLimits().mnMaxRow, rSrcDoc.GetSheetLimits().mnMaxCol); + aMark.SetMarkArea(aSrcRange); + rSrcDoc.CopyToClip(aClipParam, &aClipDoc, &aMark, false, false); + + // Create second document, paste B1 from clip + ScDocShell* pShell2 + = new ScDocShell(SfxModelFlags::EMBEDDED_OBJECT | SfxModelFlags::DISABLE_EMBEDDED_SCRIPTS + | SfxModelFlags::DISABLE_DOCUMENT_RECOVERY); + pShell2->DoInitNew(); + ScDocument& rDestDoc = pShell2->GetDocument(); + ScRange aDstRange(1, 0, 0); + ScMarkData aMark2(rDestDoc.GetSheetLimits().mnMaxRow, rDestDoc.GetSheetLimits().mnMaxCol); + aMark2.SetMarkArea(aDstRange); + rDestDoc.CopyFromClip(aDstRange, aMark2, InsertDeleteFlags::ALL, nullptr, &aClipDoc); + + // save as XLSX + std::shared_ptr<utl::TempFile> pXPathFile + = ScBootstrapFixture::exportTo(&(*pShell2), FORMAT_XLSX); + + // check validation + xmlDocUniquePtr pDoc + = XPathHelper::parseExport(pXPathFile, m_xSFactory, "xl/worksheets/sheet1.xml"); + CPPUNIT_ASSERT(pDoc); + assertXPathContent(pDoc, "/x:worksheet/x:dataValidations/x:dataValidation/x:formula1", "#REF!"); +} + +void ScExportTest::testTdf115159() +{ + ScDocShellRef xShell = loadDoc("tdf115159.", FORMAT_XLSX); + CPPUNIT_ASSERT(xShell.is()); + ScDocShellRef xDocSh = saveAndReload(xShell.get(), FORMAT_XLSX); + CPPUNIT_ASSERT(xDocSh.is()); + xShell->DoClose(); + + xmlDocUniquePtr pDoc = XPathHelper::parseExport2(*this, *xDocSh, m_xSFactory, "xl/workbook.xml", FORMAT_XLSX); + CPPUNIT_ASSERT(pDoc); + + //assert the existing OOXML built-in name is not duplicated + assertXPath(pDoc, "/x:workbook/x:definedNames/x:definedName", 1); + + xDocSh->DoClose(); +} + +void ScExportTest::testTdf112567() +{ + // Set the system locale to Hungarian (a language with different range separator) + SvtSysLocaleOptions aOptions; + OUString sLocaleConfigString = aOptions.GetLanguageTag().getBcp47(); + aOptions.SetLocaleConfigString("hu-HU"); + aOptions.Commit(); + comphelper::ScopeGuard g([&aOptions, &sLocaleConfigString] { + aOptions.SetLocaleConfigString(sLocaleConfigString); + aOptions.Commit(); + }); + + ScDocShellRef xShell = loadDoc("tdf112567.", FORMAT_XLSX); + CPPUNIT_ASSERT(xShell.is()); + ScDocShellRef xDocSh = saveAndReload(xShell.get(), FORMAT_XLSX); + CPPUNIT_ASSERT(xDocSh.is()); + xShell->DoClose(); + + xmlDocUniquePtr pDoc = XPathHelper::parseExport2(*this, *xDocSh, m_xSFactory, "xl/workbook.xml", FORMAT_XLSX); + CPPUNIT_ASSERT(pDoc); + + //assert the existing OOXML built-in name is not duplicated + assertXPath(pDoc, "/x:workbook/x:definedNames/x:definedName", 1); + + xDocSh->DoClose(); +} + +void ScExportTest::testTdf112567b() +{ + // Set the system locale to Hungarian (a language with different range separator) + SvtSysLocaleOptions aOptions; + OUString sLocaleConfigString = aOptions.GetLanguageTag().getBcp47(); + aOptions.SetLocaleConfigString("hu-HU"); + aOptions.Commit(); + comphelper::ScopeGuard g([&aOptions, &sLocaleConfigString] { + aOptions.SetLocaleConfigString(sLocaleConfigString); + aOptions.Commit(); + }); + + ScDocShellRef xShell = loadDoc("tdf112567.", FORMAT_ODS); + CPPUNIT_ASSERT(xShell.is()); + ScDocShellRef xDocSh = saveAndReload(xShell.get(), FORMAT_XLSX); + CPPUNIT_ASSERT(xDocSh.is()); + xShell->DoClose(); + + xmlDocUniquePtr pDoc = XPathHelper::parseExport2(*this, *xDocSh, m_xSFactory, "xl/workbook.xml", FORMAT_XLSX); + CPPUNIT_ASSERT(pDoc); + + //assert the existing OOXML built-in name is not duplicated + assertXPath(pDoc, "/x:workbook/x:definedNames/x:definedName", 1); + + //and it contains "," instead of ";" + assertXPathContent(pDoc, "/x:workbook/x:definedNames/x:definedName[1]", "Sheet1!$A:$A,Sheet1!$1:$1"); + + xDocSh->DoClose(); +} + +void ScExportTest::testTdf123645XLSX() +{ + ScDocShellRef xDocSh = loadDoc("chart_hyperlink.", FORMAT_XLSX); + CPPUNIT_ASSERT(xDocSh.is()); + std::shared_ptr<utl::TempFile> pXPathFile = ScBootstrapFixture::exportTo(&(*xDocSh), FORMAT_XLSX); + + xmlDocUniquePtr pDoc = XPathHelper::parseExport(pXPathFile, m_xSFactory, "xl/drawings/drawing1.xml"); + CPPUNIT_ASSERT(pDoc); + assertXPath(pDoc, "/xdr:wsDr/xdr:twoCellAnchor[1]/xdr:graphicFrame/xdr:nvGraphicFramePr/xdr:cNvPr/a:hlinkClick", 1); + assertXPath(pDoc, "/xdr:wsDr/xdr:twoCellAnchor[2]/xdr:graphicFrame/xdr:nvGraphicFramePr/xdr:cNvPr/a:hlinkClick", 1); + assertXPath(pDoc, "/xdr:wsDr/xdr:twoCellAnchor[3]/xdr:graphicFrame/xdr:nvGraphicFramePr/xdr:cNvPr/a:hlinkClick", 1); + + xmlDocUniquePtr pXmlRels = XPathHelper::parseExport(pXPathFile, m_xSFactory, "xl/drawings/_rels/drawing1.xml.rels"); + CPPUNIT_ASSERT(pXmlRels); + assertXPath(pXmlRels, "/r:Relationships/r:Relationship[@Id='rId1']", "TargetMode", "External"); + assertXPathNoAttribute(pXmlRels, "/r:Relationships/r:Relationship[@Id='rId3']", "TargetMode"); + assertXPath(pXmlRels, "/r:Relationships/r:Relationship[@Id='rId5']", "TargetMode", "External"); + assertXPath(pXmlRels, "/r:Relationships/r:Relationship[@Id='rId1']", "Target", "file:///C:/TEMP/test.xlsx"); + assertXPath(pXmlRels, "/r:Relationships/r:Relationship[@Id='rId3']", "Target", "#Sheet2!A1"); + assertXPath(pXmlRels, "/r:Relationships/r:Relationship[@Id='rId5']", "Target", "https://bugs.documentfoundation.org/show_bug.cgi?id=123645"); + + xDocSh->DoClose(); +} + +void ScExportTest::testTdf125173XLSX() +{ + ScDocShellRef xDocSh = loadDoc("text_box_hyperlink.", FORMAT_ODS); + CPPUNIT_ASSERT(xDocSh.is()); + std::shared_ptr<utl::TempFile> pXPathFile = ScBootstrapFixture::exportTo(&(*xDocSh), FORMAT_XLSX); + + xmlDocUniquePtr pDoc = XPathHelper::parseExport(pXPathFile, m_xSFactory, "xl/drawings/drawing1.xml"); + CPPUNIT_ASSERT(pDoc); + assertXPath(pDoc, "/xdr:wsDr/xdr:twoCellAnchor/xdr:sp/xdr:nvSpPr/xdr:cNvPr/a:hlinkClick", 1); + + xmlDocUniquePtr pXmlRels = XPathHelper::parseExport(pXPathFile, m_xSFactory, "xl/drawings/_rels/drawing1.xml.rels"); + CPPUNIT_ASSERT(pXmlRels); + assertXPath(pXmlRels, "/r:Relationships/r:Relationship[@Id='rId1']", "Target", "http://www.google.com/"); + assertXPath(pXmlRels, "/r:Relationships/r:Relationship[@Id='rId1']", "TargetMode", "External"); + + xDocSh->DoClose(); +} + +void ScExportTest::testTdf79972XLSX() +{ + ScDocShellRef xDocSh = loadDoc("tdf79972.", FORMAT_XLSX); + CPPUNIT_ASSERT(xDocSh.is()); + std::shared_ptr<utl::TempFile> pXPathFile = ScBootstrapFixture::exportTo(&(*xDocSh), FORMAT_XLSX); + + xmlDocUniquePtr pDoc = XPathHelper::parseExport(pXPathFile, m_xSFactory, "xl/worksheets/sheet1.xml"); + CPPUNIT_ASSERT(pDoc); + assertXPath(pDoc, "/x:worksheet/x:hyperlinks/x:hyperlink", "ref", "A1"); + + xmlDocUniquePtr pXmlRels = XPathHelper::parseExport(pXPathFile, m_xSFactory, "xl/worksheets/_rels/sheet1.xml.rels"); + CPPUNIT_ASSERT(pXmlRels); + assertXPath(pXmlRels, "/r:Relationships/r:Relationship", "Target", "https://bugs.documentfoundation.org/show_bug.cgi?id=79972"); + assertXPath(pXmlRels, "/r:Relationships/r:Relationship", "TargetMode", "External"); + + xDocSh->DoClose(); +} + +void ScExportTest::testTdf126024XLSX() +{ + ScDocShellRef xDocSh = loadDoc("hyperlink_formula.", FORMAT_XLSX); + CPPUNIT_ASSERT(xDocSh.is()); + std::shared_ptr<utl::TempFile> pXPathFile = ScBootstrapFixture::exportTo(&(*xDocSh), FORMAT_XLSX); + + xmlDocUniquePtr pDoc = XPathHelper::parseExport(pXPathFile, m_xSFactory, "xl/worksheets/sheet1.xml"); + CPPUNIT_ASSERT(pDoc); + assertXPath(pDoc, "/x:worksheet/x:hyperlinks/x:hyperlink", "ref", "A2"); + + xmlDocUniquePtr pXmlRels = XPathHelper::parseExport(pXPathFile, m_xSFactory, "xl/worksheets/_rels/sheet1.xml.rels"); + CPPUNIT_ASSERT(pXmlRels); + assertXPath(pXmlRels, "/r:Relationships/r:Relationship", "Target", "https://bugs.documentfoundation.org/"); + assertXPath(pXmlRels, "/r:Relationships/r:Relationship", "TargetMode", "External"); + + xDocSh->DoClose(); +} + +void ScExportTest::testTdf126177XLSX() +{ + ScDocShellRef xDocSh = loadDoc("hyperlink_export.", FORMAT_XLSX); + CPPUNIT_ASSERT(xDocSh.is()); + std::shared_ptr<utl::TempFile> pXPathFile = ScBootstrapFixture::exportTo(&(*xDocSh), FORMAT_XLSX); + + xmlDocUniquePtr pDoc = XPathHelper::parseExport(pXPathFile, m_xSFactory, "xl/worksheets/sheet1.xml"); + CPPUNIT_ASSERT(pDoc); + assertXPath(pDoc, "/x:worksheet/x:hyperlinks/x:hyperlink", "location", "Munka1!A5"); + + xmlDocUniquePtr pXmlRels = XPathHelper::parseExport(pXPathFile, m_xSFactory, "xl/worksheets/_rels/sheet1.xml.rels"); + CPPUNIT_ASSERT(pXmlRels); + OUString aTarget = getXPath(pXmlRels, "/r:Relationships/r:Relationship", "Target"); + CPPUNIT_ASSERT(aTarget.endsWith("test.xlsx")); + assertXPath(pXmlRels, "/r:Relationships/r:Relationship", "TargetMode", "External"); + + xDocSh->DoClose(); +} + +void ScExportTest::testCommentTextVAlignment() +{ + // Testing comment text alignments. + ScDocShellRef xShell = loadDoc("CommentTextVAlign.", FORMAT_ODS); + CPPUNIT_ASSERT(xShell.is()); + + std::shared_ptr<utl::TempFile> pXPathFile + = ScBootstrapFixture::exportTo(&(*xShell), FORMAT_XLSX); + + xmlDocUniquePtr pVmlDrawing + = XPathHelper::parseExport(pXPathFile, m_xSFactory, "xl/drawings/vmlDrawing1.vml"); + CPPUNIT_ASSERT(pVmlDrawing); + + assertXPathContent(pVmlDrawing, "/xml/v:shape/xx:ClientData/xx:TextVAlign", "Center"); + + xShell->DoClose(); +} + +void ScExportTest::testCommentTextHAlignment() +{ + // Testing comment text alignments. + ScDocShellRef xShell = loadDoc("CommentTextHAlign.", FORMAT_ODS); + CPPUNIT_ASSERT(xShell.is()); + + std::shared_ptr<utl::TempFile> pXPathFile + = ScBootstrapFixture::exportTo(&(*xShell), FORMAT_XLSX); + + xmlDocUniquePtr pVmlDrawing + = XPathHelper::parseExport(pXPathFile, m_xSFactory, "xl/drawings/vmlDrawing1.vml"); + CPPUNIT_ASSERT(pVmlDrawing); + + assertXPathContent(pVmlDrawing, "/xml/v:shape/xx:ClientData/xx:TextHAlign", "Center"); + + xShell->DoClose(); +} + +void ScExportTest::testRotatedImageODS() +{ + // Error was, that the length values in shapes were not + // written in the given unit into the file. + css::uno::Reference<css::sheet::XGlobalSheetSettings> xGlobalSheetSettings + = css::sheet::GlobalSheetSettings::create(comphelper::getProcessComponentContext()); + xGlobalSheetSettings->setMetric(static_cast<sal_Int16>(FieldUnit::MM)); + + ScDocShellRef xDocSh = loadDoc("tdf103092_RotatedImage.", FORMAT_ODS, true); + CPPUNIT_ASSERT(xDocSh.is()); + + std::shared_ptr<utl::TempFile> pTemp = saveAs(xDocSh.get(), FORMAT_ODS); + CPPUNIT_ASSERT(pTemp); + xmlDocUniquePtr pXmlDoc = XPathHelper::parseExport(pTemp, m_xSFactory, "content.xml"); + CPPUNIT_ASSERT(pXmlDoc); + + const OString sPathStart = "/office:document-content/office:body/office:spreadsheet/" + "table:table/table:shapes/draw:frame"; + const OUString sTransform = getXPath(pXmlDoc, sPathStart, "transform"); + // Attribute transform has the structure skew (...) rotate (...) translate (x y) + // parts are separated by blank + OUString sTranslate(sTransform.copy(sTransform.lastIndexOf('('))); + sTranslate = sTranslate.copy(1, sTranslate.getLength()-2); // remove '(' and ')' + const OUString sX(sTranslate.getToken(0, ' ')); + const OUString sY(sTranslate.getToken(1, ' ')); + CPPUNIT_ASSERT(sX.endsWith("mm") && sY.endsWith("mm")); + + xDocSh->DoClose(); +} + +void ScExportTest::testTdf128976() +{ + ScDocShellRef xShell = loadDoc("tdf128976.", FORMAT_XLS); + CPPUNIT_ASSERT(xShell.is()); + + ScDocShellRef xDocSh = saveAndReload(xShell.get(), FORMAT_XLS); + xShell->DoClose(); + CPPUNIT_ASSERT(xDocSh.is()); + + ScDocument& rDoc = xDocSh->GetDocument(); + + // Trying to save very small fractional default column width to XLS (where only integer values + // between 0 and 255 are allowed as default) resulted in negative (-1) value after correction, + // and was written as 65535 (invalid default width). As the result, all columns had large width + // when reopened: 28415 (and Excel warned about invalid format). + const sal_uInt16 nColumn0Width = rDoc.GetColWidth(SCCOL(0), SCTAB(0), false); + CPPUNIT_ASSERT_EQUAL(static_cast<sal_uInt16>(45), nColumn0Width); + + xDocSh->DoClose(); +} + +void ScExportTest::testTdf120502() +{ + // Create an empty worksheet; resize last column on its first sheet; export to XLSX, and check + // that the last exported column number is correct + css::uno::Reference<css::frame::XDesktop2> xDesktop + = css::frame::Desktop::create(comphelper::getProcessComponentContext()); + CPPUNIT_ASSERT(xDesktop); + + css::uno::Sequence<css::beans::PropertyValue> args(1); + args[0].Name = "Hidden"; + args[0].Value <<= true; + + css::uno::Reference<css::lang::XComponent> xComponent + = xDesktop->loadComponentFromURL("private:factory/scalc", "_blank", 0, args); + CPPUNIT_ASSERT(xComponent); + + // Get the document model + SfxObjectShell* pFoundShell = SfxObjectShell::GetShellFromComponent(xComponent); + CPPUNIT_ASSERT_MESSAGE("Failed to access document shell", pFoundShell); + + ScDocShellRef xShell = dynamic_cast<ScDocShell*>(pFoundShell); + CPPUNIT_ASSERT(xShell); + + ScDocument& rDoc = xShell->GetDocument(); + const SCCOL nMaxCol = rDoc.MaxCol(); // 0-based + + const auto nOldWidth = rDoc.GetColWidth(nMaxCol, 0); + rDoc.SetColWidth(nMaxCol, 0, nOldWidth + 100); + + std::shared_ptr<utl::TempFile> pXPathFile + = ScBootstrapFixture::exportTo(&(*xShell), FORMAT_XLSX); + xShell->DoClose(); + xmlDocUniquePtr pSheet1 + = XPathHelper::parseExport(pXPathFile, m_xSFactory, "xl/worksheets/sheet1.xml"); + CPPUNIT_ASSERT(pSheet1); + + // This was 1025 when nMaxCol+1 was 1024 + assertXPath(pSheet1, "/x:worksheet/x:cols/x:col", "max", OUString::number(nMaxCol + 1)); +} + +void ScExportTest::testTdf131372() +{ + ScDocShellRef xShell = loadDoc("tdf131372.", FORMAT_ODS); + CPPUNIT_ASSERT(xShell); + + auto pXPathFile = ScBootstrapFixture::exportTo(&(*xShell), FORMAT_XLSX); + + xmlDocUniquePtr pSheet = XPathHelper::parseExport(pXPathFile, m_xSFactory, "xl/worksheets/sheet1.xml"); + CPPUNIT_ASSERT(pSheet); + + assertXPathContent(pSheet, "/x:worksheet/x:sheetData/x:row/x:c[1]/x:f", "NA()"); + assertXPathContent(pSheet, "/x:worksheet/x:sheetData/x:row/x:c[2]/x:f", "#N/A"); + + xShell->DoClose(); +} + +void ScExportTest::testTdf122331() +{ + ScDocShellRef xShell = loadDoc("tdf122331.", FORMAT_ODS); + CPPUNIT_ASSERT(xShell); + + auto pXPathFile = ScBootstrapFixture::exportTo(&(*xShell), FORMAT_XLSX); + + xmlDocUniquePtr pSheet = XPathHelper::parseExport(pXPathFile, m_xSFactory, "xl/worksheets/sheet1.xml"); + CPPUNIT_ASSERT(pSheet); + + assertXPath(pSheet, "/x:worksheet/x:sheetPr", "filterMode", "true"); + assertXPath(pSheet, "/x:worksheet/x:autoFilter", "ref", "A1:B761"); + assertXPath(pSheet, "/x:worksheet/x:autoFilter/x:filterColumn", "colId", "1"); + + xShell->DoClose(); +} + +void ScExportTest::testTdf83779() +{ + // Roundtripping TRUE/FALSE constants (not functions) must convert them to functions + ScDocShellRef xShell = loadDoc("tdf83779.", FORMAT_XLSX); + CPPUNIT_ASSERT(xShell); + + auto pXPathFile = ScBootstrapFixture::exportTo(&(*xShell), FORMAT_XLSX); + + xmlDocUniquePtr pVmlDrawing + = XPathHelper::parseExport(pXPathFile, m_xSFactory, "xl/worksheets/sheet1.xml"); + CPPUNIT_ASSERT(pVmlDrawing); + + assertXPathContent(pVmlDrawing, "/x:worksheet/x:sheetData/x:row[1]/x:c/x:f", "FALSE()"); + assertXPathContent(pVmlDrawing, "/x:worksheet/x:sheetData/x:row[2]/x:c/x:f", "TRUE()"); + + xShell->DoClose(); +} + +void ScExportTest::testTdf134817_HeaderFooterTextWith2SectionXLSX() +{ + // Header/footer text with multiple selection should be exported, and imported properly + ScDocShellRef xShell = loadDoc("tdf134817_HeaderFooterTextWith2Section.", FORMAT_XLSX); + CPPUNIT_ASSERT(xShell.is()); + + ScDocShellRef xDocSh = saveAndReload(&(*xShell), FORMAT_XLSX); + CPPUNIT_ASSERT(xDocSh.is()); + + xmlDocUniquePtr pDoc = XPathHelper::parseExport2(*this, *xDocSh, m_xSFactory, "xl/worksheets/sheet1.xml", FORMAT_XLSX); + CPPUNIT_ASSERT(pDoc); + + assertXPathContent(pDoc, "/x:worksheet/x:headerFooter/x:oddHeader", "&L&\"Abadi,Regular\"&11aaa&\"Bembo,Regular\"&20bbb"); + assertXPathContent(pDoc, "/x:worksheet/x:headerFooter/x:oddFooter", "&R&\"Cambria,Regular\"&14camb&\"Dante,Regular\"&18dant"); + + xDocSh->DoClose(); +} + +CPPUNIT_TEST_SUITE_REGISTRATION(ScExportTest); + +CPPUNIT_PLUGIN_IMPLEMENT(); + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ |