From 766e4da2bbce7a6bc2ae4c9ddb5889cdee73be6f Mon Sep 17 00:00:00 2001 From: Andreas Heinisch Date: Wed, 26 Aug 2026 12:13:22 +0200 Subject: [PATCH] tdf#122473 - Import alignment and axis-based pivot area formats Change-Id: I2b90187d8e6e9788cc37320dff9035f2042b30f8 Reviewed-on: https://gerrit.libreoffice.org/c/core/+/210016 Reviewed-by: Andreas Heinisch Tested-by: Jenkins --- sc/qa/unit/PivotTableFormatsImportExport.cxx | 29 ++++++++++++++++++ .../tdf122473_PivotTableLabelRotation.xlsx | Bin 0 -> 10613 bytes sc/source/filter/inc/PivotTableFormat.hxx | 3 +- sc/source/filter/inc/stylesbuffer.hxx | 5 +++ sc/source/filter/oox/PivotTableFormat.cxx | 15 +++++++++ sc/source/filter/oox/stylesbuffer.cxx | 8 +++++ sc/source/filter/oox/stylesfragment.cxx | 7 +++-- 7 files changed, 63 insertions(+), 4 deletions(-) create mode 100644 sc/qa/unit/data/xlsx/pivot-table/tdf122473_PivotTableLabelRotation.xlsx diff --git a/sc/qa/unit/PivotTableFormatsImportExport.cxx b/sc/qa/unit/PivotTableFormatsImportExport.cxx index e107d6344db8..1438fde71b23 100644 --- a/sc/qa/unit/PivotTableFormatsImportExport.cxx +++ b/sc/qa/unit/PivotTableFormatsImportExport.cxx @@ -15,6 +15,7 @@ #include #include #include +#include #include #include #include @@ -408,6 +409,34 @@ CPPUNIT_TEST_FIXTURE(ScPivotTableFormatsImportExport, testPivotTableFormatsSubTo checkFormats(); } +CPPUNIT_TEST_FIXTURE(ScPivotTableFormatsImportExport, testTdf122473_PivotTableLabelRotation) +{ + // Contains a pivotTable with differential formatting (dxf) and direct formatting + // with only an child (textRotation) applied to two row-label cells + // ("Sum of C" rotated 90° and "Sum of D" set to stacked/vertical text). + createScDoc("xlsx/pivot-table/tdf122473_PivotTableLabelRotation.xlsx"); + auto checkFormats = [this]() { + ScDocument& rDocument = *getScDoc(); + + // Without the fix in place, this test would have failed with + // - Expected: 9000 + // - Actual : 0 + // "Sum of C" row label at F9 -> dxf 0: textRotation="90" + CPPUNIT_ASSERT_EQUAL(Degree100(9000).get(), + rDocument.GetAttr(5, 8, 0, ATTR_ROTATE_VALUE).GetValue().get()); + + // Without the fix in place, this test would have failed with + // - Expected: 1 + // - Actual : 0 + // "Sum of D" row label at F10 -> dxf 1: textRotation="255" (stacked/vertical) + CPPUNIT_ASSERT_EQUAL(true, rDocument.GetAttr(5, 9, 0, ATTR_STACKED).GetValue()); + }; + + checkFormats(); + saveAndReload(TestFilter::XLSX); + checkFormats(); +} + } // end anonymous namespace CPPUNIT_PLUGIN_IMPLEMENT(); diff --git a/sc/qa/unit/data/xlsx/pivot-table/tdf122473_PivotTableLabelRotation.xlsx b/sc/qa/unit/data/xlsx/pivot-table/tdf122473_PivotTableLabelRotation.xlsx new file mode 100644 index 0000000000000000000000000000000000000000..292f7cb05a5dd9ba940faf300855fe189afc36c9 GIT binary patch literal 10613 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 ZU3L7mJj|aoMF=1SK!69=y5!fG{|7EB6*d3> literal 0 HcmV?d00001 diff --git a/sc/source/filter/inc/PivotTableFormat.hxx b/sc/source/filter/inc/PivotTableFormat.hxx index 544ee06cc2d0..189d0049a33f 100644 --- a/sc/source/filter/inc/PivotTableFormat.hxx +++ b/sc/source/filter/inc/PivotTableFormat.hxx @@ -51,7 +51,8 @@ private: bool mbOutline = true; std::optional moOffset = std::nullopt; bool mbCollapsedLevelsAreSubtotals = false; - // TODO Axis + // tdf#122473 - import alignment and axis-based pivot area formats + std::optional moAxis = std::nullopt; std::optional moFieldPosition = std::nullopt; std::vector> maReferences; diff --git a/sc/source/filter/inc/stylesbuffer.hxx b/sc/source/filter/inc/stylesbuffer.hxx index 3ad9eea63b95..0e003fedfe6c 100644 --- a/sc/source/filter/inc/stylesbuffer.hxx +++ b/sc/source/filter/inc/stylesbuffer.hxx @@ -339,6 +339,9 @@ private: ApiAlignmentData maApiData; /// Alignment data converted to API constants. }; +// tdf#122473 - import alignment and axis-based pivot area formats +typedef std::shared_ptr AlignmentRef; + /** Contains all XML cell protection attributes, e.g. from a protection element. */ struct ProtectionModel { @@ -705,6 +708,8 @@ public: BorderRef const & createBorder( bool bAlwaysNew = true ); /** Creates a new empty fill object. */ FillRef const & createFill( bool bAlwaysNew = true ); + /** Creates a new empty alignment object. */ + AlignmentRef const& createAlignment(bool bAlwaysNew = true); /** Creates a new empty protection object. */ ProtectionRef const & createProtection( bool bAlwaysNew = true ); diff --git a/sc/source/filter/oox/PivotTableFormat.cxx b/sc/source/filter/oox/PivotTableFormat.cxx index be27ca1e2aad..ee44ccc1f497 100644 --- a/sc/source/filter/oox/PivotTableFormat.cxx +++ b/sc/source/filter/oox/PivotTableFormat.cxx @@ -12,6 +12,7 @@ #include #include #include +#include #include #include @@ -72,6 +73,8 @@ void PivotTableFormat::importPivotArea(const oox::AttributeList& rAttribs) moOffset = rAttribs.getXString(XML_offset); mbCollapsedLevelsAreSubtotals = rAttribs.getBool(XML_collapsedLevelsAreSubtotals, false); moFieldPosition = rAttribs.getUnsigned(XML_fieldPosition); + // tdf#122473 - import alignment and axis-based pivot area formats + moAxis = rAttribs.getToken(XML_axis); } void PivotTableFormat::finalizeImport() @@ -146,6 +149,18 @@ void PivotTableFormat::finalizeImport() .bHasSubtotal = bHasSubtotal }); } } + + // tdf#122473 - import alignment and axis-based pivot area formats + // There is no field or reference given. However, there exists an axis value. + // The format uses it to identify the label of the field data. Treat it as an + // explicit reference to the values field. + if (maReferences.empty() && mbLabelOnly && moAxis && *moAxis == XML_axisValues) + { + aFormat.aSelections.push_back(sc::Selection{ .bSelected = true, + .nField = sal_Int32(sc::constDataDimension), + .nIndices = {}, + .bHasSubtotal = false }); + } aFormats.add(aFormat); pSaveData->setFormats(aFormats); diff --git a/sc/source/filter/oox/stylesbuffer.cxx b/sc/source/filter/oox/stylesbuffer.cxx index 7f9ef58c358f..70e835211cf2 100644 --- a/sc/source/filter/oox/stylesbuffer.cxx +++ b/sc/source/filter/oox/stylesbuffer.cxx @@ -2453,6 +2453,14 @@ FillRef const & Dxf::createFill( bool bAlwaysNew ) return mxFill; } +// tdf#122473 - import alignment and axis-based pivot area formats +AlignmentRef const& Dxf::createAlignment(bool bAlwaysNew) +{ + if (bAlwaysNew || !mxAlignment) + mxAlignment = std::make_shared(*this); + return mxAlignment; +} + ProtectionRef const & Dxf::createProtection( bool bAlwaysNew ) { if ( bAlwaysNew || !mxProtection ) diff --git a/sc/source/filter/oox/stylesfragment.cxx b/sc/source/filter/oox/stylesfragment.cxx index c738016029c3..e4f04c7beedf 100644 --- a/sc/source/filter/oox/stylesfragment.cxx +++ b/sc/source/filter/oox/stylesfragment.cxx @@ -149,9 +149,8 @@ ContextHandlerRef DxfContext::onCreateContext( sal_Int32 nElement, const Attribu case XLS_TOKEN( fill ): return new FillContext( *this, mxDxf->createFill() ); case XLS_TOKEN( numFmt ): mxDxf->importNumFmt( rAttribs ); break; -#if 0 - case XLS_TOKEN( alignment ): mxDxf->importAlignment( rAttribs ); break; -#endif + // tdf#122473 - import alignment and axis-based pivot area formats + case XLS_TOKEN(alignment): mxDxf->createAlignment()->importAlignment(rAttribs); break; case XLS_TOKEN( protection ): mxDxf->createProtection()->importProtection( rAttribs ); break; } break; @@ -163,6 +162,8 @@ ContextHandlerRef DxfContext::onCreateContext( sal_Int32 nElement, const Attribu case XLS_TOKEN( border ): return new BorderContext( *this, mxDxf->createBorder() ); case XLS_TOKEN( fill ): return new FillContext( *this, mxDxf->createFill() ); case XLS_TOKEN( numFmt ): mxDxf->importNumFmt( rAttribs ); break; + // tdf#122473 - import alignment and axis-based pivot area formats + case XLS_TOKEN(alignment): mxDxf->createAlignment()->importAlignment(rAttribs); break; case XLS_TOKEN( protection ): mxDxf->createProtection()->importProtection( rAttribs ); break; } break;