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

Reply via email to