chart2/inc/ChartModel.hxx | 6 chart2/qa/extras/chart2export3.cxx | 54 +++++++ chart2/qa/extras/data/pptx/1904NullDate.pptx |binary chart2/source/controller/chartapiwrapper/ChartDocumentWrapper.cxx | 68 +++++++++- chart2/source/model/main/ChartModel.cxx | 21 +++ oox/inc/drawingml/chart/chartspacemodel.hxx | 1 oox/source/drawingml/chart/chartspaceconverter.cxx | 10 + oox/source/drawingml/chart/chartspacefragment.cxx | 3 oox/source/drawingml/chart/chartspacemodel.cxx | 3 oox/source/export/chartexport.cxx | 12 + 10 files changed, 173 insertions(+), 5 deletions(-)
New commits: commit d70c3f5b92134073c21e940bce3feb3e7b5efef5 Author: Markus Mohrhard <markus.mohrh...@googlemail.com> AuthorDate: Mon Jul 14 21:02:39 2025 +0800 Commit: Xisco Fauli <xiscofa...@libreoffice.org> CommitDate: Thu Aug 7 18:09:54 2025 +0200 OOXML handling of 1904 null date for charts with internal data provider Change-Id: Id7b70bd8d0f471fc56ffb3c8ce43a01a871491bb Reviewed-on: https://gerrit.libreoffice.org/c/core/+/187835 Tested-by: Jenkins Reviewed-by: Markus Mohrhard <markus.mohrh...@googlemail.com> Signed-off-by: Xisco Fauli <xiscofa...@libreoffice.org> Reviewed-on: https://gerrit.libreoffice.org/c/core/+/188702 diff --git a/chart2/inc/ChartModel.hxx b/chart2/inc/ChartModel.hxx index 36972c4b1acc..762807c73b72 100644 --- a/chart2/inc/ChartModel.hxx +++ b/chart2/inc/ChartModel.hxx @@ -24,6 +24,7 @@ #include <com/sun/star/util/XModifiable.hpp> #include <com/sun/star/util/XCloseable.hpp> #include <com/sun/star/util/XUpdatable.hpp> +#include <com/sun/star/util/DateTime.hpp> #include <com/sun/star/document/XDocumentPropertiesSupplier.hpp> #include <com/sun/star/document/XUndoManagerSupplier.hpp> #include <com/sun/star/lang/XMultiServiceFactory.hpp> @@ -185,6 +186,8 @@ private: ChartColorPaletteType m_eColorPaletteType; sal_uInt32 m_nColorPaletteIndex; + std::optional<css::util::DateTime> m_aNullDate; + private: //private methods @@ -517,6 +520,9 @@ public: void applyColorPaletteToDataSeries(const ChartColorPalette& rColorPalette); void onDocumentThemeChanged(); + std::optional<css::util::DateTime> getNullDate() const; + void changeNullDate(const css::util::DateTime& aNullDate); + private: void dumpAsXml(xmlTextWriterPtr pWriter) const; diff --git a/chart2/qa/extras/chart2export3.cxx b/chart2/qa/extras/chart2export3.cxx index bf0f5e15432a..fd96286e7c86 100644 --- a/chart2/qa/extras/chart2export3.cxx +++ b/chart2/qa/extras/chart2export3.cxx @@ -13,6 +13,7 @@ #include <com/sun/star/awt/FontWeight.hpp> #include <com/sun/star/awt/FontSlant.hpp> #include <com/sun/star/awt/FontUnderline.hpp> +#include <com/sun/star/text/XTextRange.hpp> using uno::Reference; using beans::XPropertySet; @@ -887,15 +888,64 @@ CPPUNIT_TEST_FIXTURE(Chart2ExportTest3, testODSFormattedChartTitles) CPPUNIT_TEST_FIXTURE(Chart2ExportTest3, testTdf148117) { - // The document contains a line chart with "Between tick marks" X axis position. loadFromFile(u"pptx/tdf148117.pptx"); - // Check formatted strings after export. save(u"Impress MS PowerPoint 2007 XML"_ustr); xmlDocUniquePtr pXmlDoc = parseExport(u"ppt/charts/chart1.xml"_ustr); assertXPath(pXmlDoc, "/c:chartSpace/c:date1904", "val", u"0"); } +CPPUNIT_TEST_FIXTURE(Chart2ExportTest3, test1904NullDate) +{ + loadFromFile(u"pptx/1904NullDate.pptx"); + saveAndReload(u"Impress MS PowerPoint 2007 XML"_ustr); + + Reference<chart2::XChartDocument> xChartDoc(getChartDocFromDrawImpress(0, 0), uno::UNO_QUERY); + Reference<beans::XPropertySet> xPropSet(xChartDoc, uno::UNO_QUERY); + + util::DateTime aDateTime; + xPropSet->getPropertyValue("NullDate") >>= aDateTime; + + CPPUNIT_ASSERT_EQUAL(1904, static_cast<int>(aDateTime.Year)); + CPPUNIT_ASSERT_EQUAL(1, static_cast<int>(aDateTime.Month)); + CPPUNIT_ASSERT_EQUAL(1, static_cast<int>(aDateTime.Day)); + + uno::Reference<drawing::XDrawPageSupplier> xDrawPageSupplier(xChartDoc, uno::UNO_QUERY); + uno::Reference<drawing::XDrawPage> xDrawPage = xDrawPageSupplier->getDrawPage(); + uno::Reference<drawing::XShapes> xShapes(xDrawPage->getByIndex(0), uno::UNO_QUERY); + CPPUNIT_ASSERT(xShapes.is()); + OUString sAxisShapeName = u"CID/D=0:CS=0:Axis=0,0"_ustr; + uno::Reference<drawing::XShape> xXAxis = getShapeByName(xShapes, sAxisShapeName, + // Axis occurs twice in chart xshape representation so need to get the one related to labels + [](const uno::Reference<drawing::XShape>& rXShape) -> bool + { + uno::Reference<drawing::XShapes> xAxisShapes(rXShape, uno::UNO_QUERY); + CPPUNIT_ASSERT(xAxisShapes.is()); + uno::Reference<drawing::XShape> xChildShape(xAxisShapes->getByIndex(0), uno::UNO_QUERY); + uno::Reference< drawing::XShapeDescriptor > xShapeDescriptor(xChildShape, uno::UNO_QUERY_THROW); + return (xShapeDescriptor->getShapeType() == "com.sun.star.drawing.TextShape"); + }); + CPPUNIT_ASSERT(xXAxis.is()); + + uno::Reference<container::XIndexAccess> xIndexAccess(xXAxis, UNO_QUERY_THROW); + sal_Int32 nAxisLabelsCount = xIndexAccess->getCount(); + + // Check axis labels's text + std::vector<OUString> aExpectedLabels = { + u"1/1/2006"_ustr, + u"1/1/2007"_ustr, + u"1/1/2008"_ustr, + u"1/1/2009"_ustr, + }; + for (sal_Int32 nLabelIndex = 0; nLabelIndex < nAxisLabelsCount; ++nLabelIndex) + { + // Check text + uno::Reference<text::XTextRange> xLabel(xIndexAccess->getByIndex(nLabelIndex), uno::UNO_QUERY); + OUString aLabelName = xLabel->getString(); + CPPUNIT_ASSERT_EQUAL(aExpectedLabels[nLabelIndex], aLabelName); + } +} + CPPUNIT_PLUGIN_IMPLEMENT(); /* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/chart2/qa/extras/data/pptx/1904NullDate.pptx b/chart2/qa/extras/data/pptx/1904NullDate.pptx new file mode 100644 index 000000000000..d479c7c69c00 Binary files /dev/null and b/chart2/qa/extras/data/pptx/1904NullDate.pptx differ diff --git a/chart2/source/controller/chartapiwrapper/ChartDocumentWrapper.cxx b/chart2/source/controller/chartapiwrapper/ChartDocumentWrapper.cxx index 0450207e3255..45088fddaa79 100644 --- a/chart2/source/controller/chartapiwrapper/ChartDocumentWrapper.cxx +++ b/chart2/source/controller/chartapiwrapper/ChartDocumentWrapper.cxx @@ -626,6 +626,72 @@ Any WrappedHasSubTitleProperty::getPropertyDefault( const Reference< beans::XPro return aRet; } +namespace { + +//PROP_DOCUMENT_NULL_DATE +class WrappedNullDateProperty : public WrappedProperty +{ +public: + explicit WrappedNullDateProperty(std::shared_ptr<Chart2ModelContact> spChart2ModelContact); + + virtual void setPropertyValue( const css::uno::Any& rOuterValue, const css::uno::Reference< css::beans::XPropertySet >& xInnerPropertySet ) const override; + + virtual css::uno::Any getPropertyValue( const css::uno::Reference< css::beans::XPropertySet >& xInnerPropertySet ) const override; + + virtual css::uno::Any getPropertyDefault( const css::uno::Reference< css::beans::XPropertyState >& xInnerPropertyState ) const override; + +private: //member + std::shared_ptr< Chart2ModelContact > m_spChart2ModelContact; +}; + +} + +WrappedNullDateProperty::WrappedNullDateProperty(std::shared_ptr<Chart2ModelContact> spChart2ModelContact) + : WrappedProperty(u"NullDate"_ustr, OUString()) + , m_spChart2ModelContact(std::move( spChart2ModelContact )) +{ +} + +void WrappedNullDateProperty::setPropertyValue( const Any& rOuterValue, const Reference< beans::XPropertySet >& /*xInnerPropertySet*/ ) const +{ + util::DateTime aDateTime; + if( ! (rOuterValue >>= aDateTime) ) + throw lang::IllegalArgumentException(u"Property NullDate requires value of type util::DateTime"_ustr, nullptr, 0 ); + + try + { + m_spChart2ModelContact->getDocumentModel()->changeNullDate(aDateTime); + } + catch (const uno::Exception&) + { + DBG_UNHANDLED_EXCEPTION("chart2"); + } +} + +Any WrappedNullDateProperty::getPropertyValue( const Reference< beans::XPropertySet >& /*xInnerPropertySet*/ ) const +{ + Any aRet; + try + { + std::optional<css::util::DateTime> moDateTime = m_spChart2ModelContact->getDocumentModel()->getNullDate(); + if (moDateTime) + { + aRet <<= *moDateTime; + } + } + catch (const uno::Exception&) + { + DBG_UNHANDLED_EXCEPTION("chart2"); + } + return aRet; +} + +Any WrappedNullDateProperty::getPropertyDefault( const Reference< beans::XPropertyState >& /*xInnerPropertyState*/ ) const +{ + Any aRet; + return aRet; +} + ChartDocumentWrapper::ChartDocumentWrapper( const Reference< uno::XComponentContext > & xContext ) : m_spChart2ModelContact( std::make_shared<Chart2ModelContact>( xContext ) ), @@ -1401,7 +1467,7 @@ std::vector< std::unique_ptr<WrappedProperty> > ChartDocumentWrapper::createWrap aWrappedProperties.emplace_back( new WrappedBaseDiagramProperty( *this ) ); aWrappedProperties.emplace_back( new WrappedAdditionalShapesProperty( *this ) ); aWrappedProperties.emplace_back( new WrappedRefreshAddInAllowedProperty( *this ) ); - aWrappedProperties.emplace_back( new WrappedIgnoreProperty(u"NullDate"_ustr,Any() ) ); // i99104 + aWrappedProperties.emplace_back( new WrappedNullDateProperty( m_spChart2ModelContact ) ); aWrappedProperties.emplace_back( new WrappedIgnoreProperty(u"EnableComplexChartTypes"_ustr, uno::Any(true) ) ); aWrappedProperties.emplace_back( new WrappedIgnoreProperty(u"EnableDataTableDialog"_ustr, uno::Any(true) ) ); diff --git a/chart2/source/model/main/ChartModel.cxx b/chart2/source/model/main/ChartModel.cxx index 976a81dd1e1e..1ff6b842189d 100644 --- a/chart2/source/model/main/ChartModel.cxx +++ b/chart2/source/model/main/ChartModel.cxx @@ -1264,6 +1264,10 @@ Reference< util::XNumberFormatsSupplier > const & ChartModel::getNumberFormatsSu if( !m_xOwnNumberFormatsSupplier.is() ) { m_apSvNumberFormatter.reset( new SvNumberFormatter( m_xContext, LANGUAGE_SYSTEM ) ); + if (m_aNullDate) + { + m_apSvNumberFormatter->ChangeNullDate(m_aNullDate->Day, m_aNullDate->Month, m_aNullDate->Year); + } m_xOwnNumberFormatsSupplier = new SvNumberFormatsSupplierObj( m_apSvNumberFormatter.get() ); //pOwnNumberFormatter->ChangeStandardPrec( 15 ); todo? } @@ -1605,6 +1609,23 @@ void ChartModel::onDocumentThemeChanged() } } +void ChartModel::changeNullDate(const css::util::DateTime& aNullDate) +{ + if (m_aNullDate == aNullDate) + return; + + m_aNullDate = aNullDate; + if (m_apSvNumberFormatter) + { + m_apSvNumberFormatter->ChangeNullDate(aNullDate.Day, aNullDate.Month, aNullDate.Year); + } +} + +std::optional<css::util::DateTime> ChartModel::getNullDate() const +{ + return m_aNullDate; +} + } // namespace chart extern "C" SAL_DLLPUBLIC_EXPORT css::uno::XInterface * diff --git a/oox/inc/drawingml/chart/chartspacemodel.hxx b/oox/inc/drawingml/chart/chartspacemodel.hxx index dda0f577ae32..e3fafe8e6e91 100644 --- a/oox/inc/drawingml/chart/chartspacemodel.hxx +++ b/oox/inc/drawingml/chart/chartspacemodel.hxx @@ -58,6 +58,7 @@ struct ChartSpaceModel bool mbPlotVisOnly; /// True = plot visible cells in a sheet only. bool mbShowLabelsOverMax;/// True = show labels over chart maximum. bool mbPivotChart; /// True = pivot chart. + bool mbDate1904; /// True = default is 1904 date system explicit ChartSpaceModel(bool bMSO2007Doc); ~ChartSpaceModel(); diff --git a/oox/source/drawingml/chart/chartspaceconverter.cxx b/oox/source/drawingml/chart/chartspaceconverter.cxx index b3bc7d4a8e21..3904bff73d43 100644 --- a/oox/source/drawingml/chart/chartspaceconverter.cxx +++ b/oox/source/drawingml/chart/chartspaceconverter.cxx @@ -28,6 +28,7 @@ #include <com/sun/star/chart2/XDataSeriesContainer.hpp> #include <com/sun/star/chart2/XTitled.hpp> #include <com/sun/star/drawing/XDrawPageSupplier.hpp> +#include <com/sun/star/util/DateTime.hpp> #include <oox/core/xmlfilterbase.hxx> #include <oox/drawingml/chart/chartconverter.hxx> #include <oox/token/properties.hxx> @@ -313,6 +314,15 @@ void ChartSpaceConverter::convertFromModel( const Reference< XShapes >& rxExtern PropertySet aProps( xChartDoc->getDiagram() ); aProps.setProperty( PROP_ExternalData , uno::Any(mrModel.maSheetPath) ); } + + if (mrModel.mbDate1904) { + util::DateTime aNullDate(0,0,0,0,1,1,1904, false); + Any aAny; + aAny <<= aNullDate; + Reference< css::chart::XChartDocument > xChartDoc( getChartDocument(), UNO_QUERY ); + Reference< beans::XPropertySet > xDocPropSet(xChartDoc, UNO_QUERY ); + xDocPropSet->setPropertyValue(u"NullDate"_ustr, aAny); + } } } // namespace oox::drawingml::chart diff --git a/oox/source/drawingml/chart/chartspacefragment.cxx b/oox/source/drawingml/chart/chartspacefragment.cxx index 88ee3c5bdf41..9c6106bd9c05 100644 --- a/oox/source/drawingml/chart/chartspacefragment.cxx +++ b/oox/source/drawingml/chart/chartspacefragment.cxx @@ -59,6 +59,9 @@ ContextHandlerRef ChartSpaceFragment::onCreateContext( sal_Int32 nElement, const case C_TOKEN( chartSpace ): switch( nElement ) { + case C_TOKEN ( date1904 ): + mrModel.mbDate1904 = rAttribs.getBool( XML_val, false ); + return nullptr; case C_TOKEN( chart ): return this; case C_TOKEN( spPr ): diff --git a/oox/source/drawingml/chart/chartspacemodel.cxx b/oox/source/drawingml/chart/chartspacemodel.cxx index 3aa7366ff82e..9e81cbfe6e33 100644 --- a/oox/source/drawingml/chart/chartspacemodel.cxx +++ b/oox/source/drawingml/chart/chartspacemodel.cxx @@ -28,7 +28,8 @@ ChartSpaceModel::ChartSpaceModel(bool bMSO2007Doc) : mbAutoTitleDel( !bMSO2007Doc ), // difference between OOXML spec and MSO 2007 mbPlotVisOnly( !bMSO2007Doc ), mbShowLabelsOverMax( !bMSO2007Doc ), - mbPivotChart( false ) + mbPivotChart( false ), + mbDate1904(false) { } diff --git a/oox/source/export/chartexport.cxx b/oox/source/export/chartexport.cxx index 7ed7544f9358..1c39254d4076 100644 --- a/oox/source/export/chartexport.cxx +++ b/oox/source/export/chartexport.cxx @@ -1264,7 +1264,17 @@ void ChartExport::exportChartSpace( const Reference< css::chart::XChartDocument } else { - pFS->singleElement(FSNS(XML_c, XML_date1904), XML_val, "0"); + Reference< XPropertySet > xPropSet(xChartDoc, UNO_QUERY); + Any aNullDate = xPropSet->getPropertyValue("NullDate"); + util::DateTime aDate; + if ((aNullDate >>= aDate) && (aDate.Year == 1904 && aDate.Month == 1 && aDate.Day == 1)) + { + pFS->singleElement(FSNS(XML_c, XML_date1904), XML_val, "1"); + } + else + { + pFS->singleElement(FSNS(XML_c, XML_date1904), XML_val, "0"); + } } // TODO: get the correct editing language