chart2/qa/extras/chart2export2.cxx                 |   11 
 chart2/qa/extras/data/pptx/funnel-pp1.pptx         |binary
 include/oox/drawingml/chart/datasourcemodel.hxx    |   18 
 include/oox/export/chartexport.hxx                 |   13 
 include/test/xmltesttools.hxx                      |   23 
 oox/inc/drawingml/chart/chartspacemodel.hxx        |    2 
 oox/inc/drawingml/chart/datasourcecontext.hxx      |   17 
 oox/inc/drawingml/chart/plotareaconverter.hxx      |    5 
 oox/inc/drawingml/chart/seriesconverter.hxx        |    3 
 oox/inc/drawingml/chart/seriesmodel.hxx            |   19 
 oox/inc/drawingml/chart/typegroupconverter.hxx     |    8 
 oox/source/drawingml/chart/chartconverter.cxx      |    1 
 oox/source/drawingml/chart/chartspaceconverter.cxx |    3 
 oox/source/drawingml/chart/chartspacefragment.cxx  |    4 
 oox/source/drawingml/chart/datasourcecontext.cxx   |  110 +++
 oox/source/drawingml/chart/plotareaconverter.cxx   |   18 
 oox/source/drawingml/chart/seriescontext.cxx       |   40 -
 oox/source/drawingml/chart/seriesconverter.cxx     |   11 
 oox/source/drawingml/chart/seriesmodel.cxx         |    1 
 oox/source/drawingml/chart/typegroupconverter.cxx  |   30 
 oox/source/export/chartexport.cxx                  |  693 ++++++++++++++-------
 test/source/xmltesttools.cxx                       |   19 
 22 files changed, 781 insertions(+), 268 deletions(-)

New commits:
commit c84c7b3c81308fbafa273c76a75f4a71596666b8
Author:     Kurt Nordback <kurt.nordb...@collabora.com>
AuthorDate: Fri Jul 4 12:43:15 2025 -0600
Commit:     Tomaž Vajngerl <qui...@gmail.com>
CommitDate: Fri Jul 11 18:35:29 2025 +0200

    tdf#165742 Step 4.7: Add support for internal data
    
    Word and Powerpoint store data internal to the chart in .docx and .pptx
    formats, respectively. In chartex this uses the <cx:chartData> and <cx:data>
    tags, and unlike in pre-2016 charts, is at the chartSpace level rather than
    internal to individual series. Provide support for these tags and data
    structures.
    
    Note that this does not yet allow .docx and .pptx files with chartex
    contents to round-trip successfully (MSO -> LO -> MSO). Additional work
    remains needed for this.
    
    Change-Id: I1e91cd0e014787ce19303a906ff76cd6c7c4b0fa
    Reviewed-on: https://gerrit.libreoffice.org/c/core/+/187426
    Reviewed-by: Tomaž Vajngerl <qui...@gmail.com>
    Tested-by: Jenkins

diff --git a/chart2/qa/extras/chart2export2.cxx 
b/chart2/qa/extras/chart2export2.cxx
index 93492645afc4..e6cf525a2a62 100644
--- a/chart2/qa/extras/chart2export2.cxx
+++ b/chart2/qa/extras/chart2export2.cxx
@@ -170,6 +170,17 @@ CPPUNIT_TEST_FIXTURE(Chart2ExportTest2, 
testChartexTitleXLSX)
                        u"Funnel chart!");
 }
 
+CPPUNIT_TEST_FIXTURE(Chart2ExportTest2, testChartexPPTX)
+{
+    loadFromFile(u"pptx/funnel-pp1.pptx");
+    save(u"Impress Office Open XML"_ustr);
+    xmlDocUniquePtr pXmlDoc = parseExport(u"ppt/charts/chartEx1.xml"_ustr);
+    CPPUNIT_ASSERT(pXmlDoc);
+
+    assertXPath(pXmlDoc, 
"/cx:chartSpace/cx:chart/cx:plotArea/cx:plotAreaRegion/cx:series", 3, 0,
+                "layoutId", u"funnel");
+}
+
 CPPUNIT_TEST_FIXTURE(Chart2ExportTest2, testAxisTitleRotationXLSX)
 {
     loadFromFile(u"xlsx/axis_title_rotation.xlsx");
diff --git a/chart2/qa/extras/data/pptx/funnel-pp1.pptx 
b/chart2/qa/extras/data/pptx/funnel-pp1.pptx
new file mode 100644
index 000000000000..df34aa1618d0
Binary files /dev/null and b/chart2/qa/extras/data/pptx/funnel-pp1.pptx differ
diff --git a/include/oox/drawingml/chart/datasourcemodel.hxx 
b/include/oox/drawingml/chart/datasourcemodel.hxx
index 49b8dcd90717..7cc52208e6a0 100644
--- a/include/oox/drawingml/chart/datasourcemodel.hxx
+++ b/include/oox/drawingml/chart/datasourcemodel.hxx
@@ -58,6 +58,24 @@ struct DataSourceModel
                         ~DataSourceModel();
 };
 
+enum class DataSourceType: sal_Int32;
+
+// Data source for chartex
+struct DataSourceCxModel
+{
+    // Same as definition in SeriesModel
+    typedef ModelMap< DataSourceType, DataSourceModel > DataSourceMap;
+    typedef ModelMap<sal_Int32, DataSourceMap> DataMap;
+
+    // Chartex data can have three levels of lists/maps:
+    // 1. Multiple data series, each with a <cx:data> tag and indexed by id.
+    // 2. Within a series, multiple sources ("val", "cat", etc.)
+    // 3. Within a source, multiple points, with corresponding index.
+    DataMap             maSourceMap; /// Data for chartex.
+
+    explicit            DataSourceCxModel() = default;
+};
+
 
 } // namespace oox::drawingml::chart
 
diff --git a/include/oox/export/chartexport.hxx 
b/include/oox/export/chartexport.hxx
index 2d745d295986..348af941c88a 100644
--- a/include/oox/export/chartexport.hxx
+++ b/include/oox/export/chartexport.hxx
@@ -174,9 +174,8 @@ private:
     void exportChart( const css::uno::Reference<
                           css::chart::XChartDocument >& rChartDoc,
                           bool bIsChartex);
-    void exportData( const css::uno::Reference<
-                              css::chart::XChartDocument >& rChartDoc,
-                              bool bIsChartex);
+    void exportData_chartex( const css::uno::Reference<
+                              css::chart::XChartDocument >& rChartDoc);
     void exportExternalData( const css::uno::Reference<
                               css::chart::XChartDocument >& rChartDoc,
                               bool bIsChartex);
@@ -217,10 +216,12 @@ private:
     void exportUpDownBars(const css::uno::Reference< css::chart2::XChartType 
>& xChartType );
 
     void exportAllSeries(const css::uno::Reference<css::chart2::XChartType>& 
xChartType, bool& rPrimaryAxes);
-    void exportSeries(const css::uno::Reference< css::chart2::XChartType >& 
xChartType,
+    void exportSeries_chart(const css::uno::Reference< css::chart2::XChartType 
>& xChartType,
             const 
css::uno::Sequence<css::uno::Reference<css::chart2::XDataSeries> >& rSeriesSeq,
-            bool& rPrimaryAxes,
-            bool bIsChartex);
+            bool& rPrimaryAxes);
+    void exportSeries_chartex(const css::uno::Reference< 
css::chart2::XChartType >& xChartType,
+            const 
css::uno::Sequence<css::uno::Reference<css::chart2::XDataSeries> >& rSeriesSeq,
+            const char* sTypeName);
 
     void exportVaryColors(const css::uno::Reference<css::chart2::XChartType>& 
xChartType);
     void exportCandleStickSeries(
diff --git a/include/test/xmltesttools.hxx b/include/test/xmltesttools.hxx
index 8f5df7c77465..c6f10dd7edb6 100644
--- a/include/test/xmltesttools.hxx
+++ b/include/test/xmltesttools.hxx
@@ -56,6 +56,16 @@ protected:
     {
         return getXPath(pXmlDoc, sXPath.getStr(), pAttribute);
     }
+    /**
+     * Same as above, but where the expected number of nodes with
+     * the given path is nNumPaths and the desired node index is nPathIdx.
+     */
+    OUString      getXPath(const xmlDocUniquePtr& pXmlDoc, const char* pXPath, 
int nNumPaths, int nPathIdx, const char* pAttribute);
+    OUString getXPath(const xmlDocUniquePtr& pXmlDoc, const OString& sXPath, 
int nNumPaths, int nPathIdx, const char* pAttribute)
+    {
+        assert(nPathIdx < nNumPaths);
+        return getXPath(pXmlDoc, sXPath.getStr(), nNumPaths, nPathIdx, 
pAttribute);
+    }
     /**
      * Same as the assertXPathContent(), but don't assert: return the string 
instead.
      */
@@ -95,6 +105,19 @@ protected:
     {
         assertXPathAttrs(pXmlDoc, sXPath.getStr(), aPairVector);
     }
+    /**
+     * Assert that pXPath exists, returns nNumNodes nodes, and the attribute
+     * value of node number nPathIdx equals the rExpected value.
+     */
+    void          assertXPath(const xmlDocUniquePtr& pXmlDoc, const char* 
pXPath,
+                              int nNumNodes, int nNodeIdx,
+                              const char* pAttribute, std::u16string_view 
rExpectedValue);
+    void assertXPath(const xmlDocUniquePtr& pXmlDoc, const OString& sXPath,
+                     int nNumNodes, int nNodeIdx,
+                     const char* pAttribute, std::u16string_view 
rExpectedValue)
+    {
+        assertXPath(pXmlDoc, sXPath.getStr(), nNumNodes, nNodeIdx, pAttribute, 
rExpectedValue);
+    }
 
     /**
      * Given a double for the rExpected value, assert that pXPath exists, 
returns exactly one node,
diff --git a/oox/inc/drawingml/chart/chartspacemodel.hxx 
b/oox/inc/drawingml/chart/chartspacemodel.hxx
index d66abb21f9d7..dc3ab8674634 100644
--- a/oox/inc/drawingml/chart/chartspacemodel.hxx
+++ b/oox/inc/drawingml/chart/chartspacemodel.hxx
@@ -39,6 +39,7 @@ struct ChartSpaceModel
     typedef ModelRef< View3DModel >     View3DRef;
     typedef ModelRef< TitleModel >      TitleRef;
     typedef ModelRef< LegendModel >     LegendRef;
+    typedef ModelRef< DataSourceCxModel > DataSourceRef;
 
     ShapeRef            mxShapeProp;        /// Chart frame formatting.
     TextBodyRef         mxTextProp;         /// Global chart text formatting.
@@ -59,6 +60,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.
+    DataSourceRef       maCxData;           /// Data for Chartex.
 
     explicit            ChartSpaceModel(bool bMSO2007Doc);
                         ~ChartSpaceModel();
diff --git a/oox/inc/drawingml/chart/datasourcecontext.hxx 
b/oox/inc/drawingml/chart/datasourcecontext.hxx
index 7edb029eb336..eeeccb6d89b8 100644
--- a/oox/inc/drawingml/chart/datasourcecontext.hxx
+++ b/oox/inc/drawingml/chart/datasourcecontext.hxx
@@ -21,6 +21,7 @@
 #define INCLUDED_OOX_DRAWINGML_CHART_DATASOURCECONTEXT_HXX
 
 #include <memory>
+#include <oox/drawingml/chart/datasourcemodel.hxx>
 #include <drawingml/chart/chartcontextbase.hxx>
 
 class SvNumberFormatter;
@@ -86,6 +87,22 @@ public:
     virtual ::oox::core::ContextHandlerRef onCreateContext( sal_Int32 
nElement, const AttributeList& rAttribs ) override;
 };
 
+struct DataSourceCxModel;
+
+/** Handler for a chartex data source context (cx:chartData, cx:data elements).
+ */
+class DataSourceCxContext final : public ContextBase< DataSourceCxModel >
+{
+public:
+    explicit            DataSourceCxContext( 
::oox::core::ContextHandler2Helper& rParent,
+            DataSourceCxModel& rModel);
+    virtual             ~DataSourceCxContext() override;
+
+    virtual ::oox::core::ContextHandlerRef onCreateContext( sal_Int32 
nElement, const AttributeList& rAttribs ) override;
+
+    DataSourceCxModel::DataSourceMap *paCurSource;
+};
+
 
 } // namespace oox::drawingml::chart
 
diff --git a/oox/inc/drawingml/chart/plotareaconverter.hxx 
b/oox/inc/drawingml/chart/plotareaconverter.hxx
index 988405b3247c..750d91cb916c 100644
--- a/oox/inc/drawingml/chart/plotareaconverter.hxx
+++ b/oox/inc/drawingml/chart/plotareaconverter.hxx
@@ -21,6 +21,8 @@
 #define INCLUDED_OOX_DRAWINGML_CHART_PLOTAREACONVERTER_HXX
 
 #include <drawingml/chart/converterbase.hxx>
+#include <drawingml/chart/seriesmodel.hxx>
+#include <oox/drawingml/chart/datasourcemodel.hxx>
 
 namespace com::sun::star {
     namespace chart2 { class XDiagram; }
@@ -68,7 +70,8 @@ public:
     virtual             ~PlotAreaConverter() override;
 
     /** Converts the OOXML plot area model to a chart2 diagram. */
-    void                convertFromModel( View3DModel& rView3DModel );
+    void                convertFromModel( View3DModel& rView3DModel,
+            DataSourceCxModel& raDataMap );
     /** Converts the manual plot area position and size, if set. */
     void                convertPositionFromModel();
 
diff --git a/oox/inc/drawingml/chart/seriesconverter.hxx 
b/oox/inc/drawingml/chart/seriesconverter.hxx
index c8cca31d5e3e..23a2c02cc7ef 100644
--- a/oox/inc/drawingml/chart/seriesconverter.hxx
+++ b/oox/inc/drawingml/chart/seriesconverter.hxx
@@ -21,6 +21,7 @@
 #define INCLUDED_OOX_DRAWINGML_CHART_SERIESCONVERTER_HXX
 
 #include <drawingml/chart/converterbase.hxx>
+#include <oox/drawingml/chart/datasourcemodel.hxx>
 #include <drawingml/chart/seriesmodel.hxx>
 
 namespace com::sun::star {
@@ -134,7 +135,7 @@ public:
 private:
     css::uno::Reference< css::chart2::data::XLabeledDataSequence >
                         createLabeledDataSequence(
-                            SeriesModel::SourceType eSourceType,
+                            enum DataSourceType eSourceType,
                             const OUString& rRole,
                             bool bUseTextLabel );
 };
diff --git a/oox/inc/drawingml/chart/seriesmodel.hxx 
b/oox/inc/drawingml/chart/seriesmodel.hxx
index 4f48115676f0..4c791d67e613 100644
--- a/oox/inc/drawingml/chart/seriesmodel.hxx
+++ b/oox/inc/drawingml/chart/seriesmodel.hxx
@@ -174,17 +174,17 @@ struct DataPointModel
                         ~DataPointModel();
 };
 
-struct SeriesModel
+enum class DataSourceType: sal_Int32
 {
-    enum SourceType
-    {
-        CATEGORIES,         /// Data point categories.
-        VALUES,             /// Data point values.
-        POINTS,             /// Data point size (e.g. bubble size in bubble 
charts).
-        DATALABELS,         /// Data point labels.
-    };
+    CATEGORIES,         /// Data point categories.
+    VALUES,             /// Data point values.
+    POINTS,             /// Data point size (e.g. bubble size in bubble 
charts).
+    DATALABELS,         /// Data point labels.
+};
 
-    typedef ModelMap< SourceType, DataSourceModel > DataSourceMap;
+struct SeriesModel
+{
+    typedef ModelMap< DataSourceType, DataSourceModel > DataSourceMap;
     typedef ModelVector< ErrorBarModel >            ErrorBarVector;
     typedef ModelVector< TrendlineModel >           TrendlineVector;
     typedef ModelVector< DataPointModel >           DataPointVector;
@@ -208,6 +208,7 @@ struct SeriesModel
     sal_Int32           mnMarkerSize;       /// Size of the series line marker 
(2...72).
     sal_Int32           mnMarkerSymbol;     /// Series line marker symbol.
     sal_Int32           mnOrder;            /// Series order.
+    sal_Int32           mnDataId;           /// Reference to correct data 
chunk (chartex)
     bool                mbBubble3d;         /// True = show bubbles with 3D 
shade.
     bool                mbInvertNeg;        /// True = invert negative data 
points.
     bool                mbSmooth;           /// True = smooth series line.
diff --git a/oox/inc/drawingml/chart/typegroupconverter.hxx 
b/oox/inc/drawingml/chart/typegroupconverter.hxx
index e7b1e16e8cec..57382f0788cd 100644
--- a/oox/inc/drawingml/chart/typegroupconverter.hxx
+++ b/oox/inc/drawingml/chart/typegroupconverter.hxx
@@ -21,6 +21,8 @@
 #define INCLUDED_OOX_DRAWINGML_CHART_TYPEGROUPCONVERTER_HXX
 
 #include <drawingml/chart/converterbase.hxx>
+#include <oox/drawingml/chart/datasourcemodel.hxx>
+#include <drawingml/chart/seriesmodel.hxx>
 #include <com/sun/star/chart2/PieChartSubType.hpp>
 
 namespace com::sun::star {
@@ -166,6 +168,12 @@ public:
     void                convertPieExplosion( PropertySet& rPropSet, sal_Int32 
nOoxExplosion ) const;
     /** Converts of-pie types */
     css::chart2::PieChartSubType convertOfPieType(sal_Int32 nOoxOfPieType ) 
const;
+    /** Move any internal data to the appropriate series.  In chartex the data
+       (if any is internal) is given outside the series, in a child element of
+       <cx:chartSpace>. Pre-2016 charts have the data inside the series, and
+       SeriesModel and subsequent handling reflects this. So this function gets
+       the data to the right place for processing. */
+    void                moveDataToSeries(DataSourceCxModel::DataMap& 
raDataMap);
 
 private:
     /** Inserts the passed series into the chart type. Adds additional 
properties to the series. */
diff --git a/oox/source/drawingml/chart/chartconverter.cxx 
b/oox/source/drawingml/chart/chartconverter.cxx
index 3f004ffd2c9e..659132eebef2 100644
--- a/oox/source/drawingml/chart/chartconverter.cxx
+++ b/oox/source/drawingml/chart/chartconverter.cxx
@@ -118,6 +118,7 @@ Reference< XDataSequence > 
ChartConverter::createDataSequence(
         {
             // create a single-row array from constant source data
             // (multiple levels in the case of complex categories)
+            assert( rDataSeq.mnPointCount > 0);
             std::vector<Any> aRow(rDataSeq.mnLevelCount * 
rDataSeq.mnPointCount);
             for (auto const& elem : rDataSeq.maData)
                 aRow.at(elem.first) = elem.second;
diff --git a/oox/source/drawingml/chart/chartspaceconverter.cxx 
b/oox/source/drawingml/chart/chartspaceconverter.cxx
index d2d51fad3c43..37111b865194 100644
--- a/oox/source/drawingml/chart/chartspaceconverter.cxx
+++ b/oox/source/drawingml/chart/chartspaceconverter.cxx
@@ -157,7 +157,8 @@ void ChartSpaceConverter::convertFromModel( const 
Reference< XShapes >& rxExtern
     bool bMSO2007Doc = getFilter().isMSO2007Document();
     // convert plot area (container of all chart type groups)
     PlotAreaConverter aPlotAreaConv( *this, mrModel.mxPlotArea.getOrCreate() );
-    aPlotAreaConv.convertFromModel( mrModel.mxView3D.getOrCreate(bMSO2007Doc) 
);
+    aPlotAreaConv.convertFromModel( mrModel.mxView3D.getOrCreate(bMSO2007Doc),
+            mrModel.maCxData.getOrCreate());
 
     // plot area converter has created the diagram object
     Reference< XDiagram > xDiagram = getChartDocument()->getFirstDiagram();
diff --git a/oox/source/drawingml/chart/chartspacefragment.cxx 
b/oox/source/drawingml/chart/chartspacefragment.cxx
index 29e77e4b5095..f26b3cd924b4 100644
--- a/oox/source/drawingml/chart/chartspacefragment.cxx
+++ b/oox/source/drawingml/chart/chartspacefragment.cxx
@@ -24,6 +24,7 @@
 #include <drawingml/chart/chartspacemodel.hxx>
 #include <drawingml/chart/plotareacontext.hxx>
 #include <drawingml/chart/titlecontext.hxx>
+#include <drawingml/chart/datasourcecontext.hxx>
 #include <oox/core/xmlfilterbase.hxx>
 #include <oox/helper/attributelist.hxx>
 #include <oox/token/namespaces.hxx>
@@ -142,8 +143,7 @@ ContextHandlerRef ChartSpaceFragment::onCreateContext( 
sal_Int32 nElement, const
         case CX_TOKEN(chartSpace) :
             switch (nElement) {
                 case CX_TOKEN(chartData):
-                    // TODO
-                    return nullptr;
+                    return new DataSourceCxContext(*this, 
mrModel.maCxData.create());
                 case CX_TOKEN(chart):
                     return this;
                 case CX_TOKEN(spPr):
diff --git a/oox/source/drawingml/chart/datasourcecontext.cxx 
b/oox/source/drawingml/chart/datasourcecontext.cxx
index f4660f5db708..d27b715dbbc7 100644
--- a/oox/source/drawingml/chart/datasourcecontext.cxx
+++ b/oox/source/drawingml/chart/datasourcecontext.cxx
@@ -17,10 +17,11 @@
  *   the License at http://www.apache.org/licenses/LICENSE-2.0 .
  */
 
-#include <drawingml/chart/datasourcecontext.hxx>
-
 #include <oox/drawingml/chart/datasourcemodel.hxx>
 
+#include <drawingml/chart/seriesmodel.hxx>
+#include <drawingml/chart/datasourcecontext.hxx>
+
 #include <oox/core/xmlfilterbase.hxx>
 #include <oox/helper/attributelist.hxx>
 #include <oox/token/namespaces.hxx>
@@ -81,6 +82,23 @@ ContextHandlerRef DoubleSequenceContext::onCreateContext( 
sal_Int32 nElement, co
                     return this;
             }
         break;
+        case CX_TOKEN(numDim) :
+            switch (nElement) {
+                case CX_TOKEN(f):
+                    return this;
+                case CX_TOKEN(lvl):
+                    mrModel.mnPointCount = rAttribs.getInteger(XML_ptCount, 
-1);
+                    mrModel.maFormatCode = rAttribs.getString(XML_formatCode, 
"");
+                    return this;
+            }
+            break;
+        case CX_TOKEN(lvl) :
+            switch(nElement) {
+                case CX_TOKEN(pt):
+                    mnPtIndex = rAttribs.getInteger(XML_idx, -1);
+                    return this;
+            }
+            break;
     }
     return nullptr;
 }
@@ -90,12 +108,14 @@ void DoubleSequenceContext::onCharacters( const OUString& 
rChars )
     switch( getCurrentElement() )
     {
         case C_TOKEN( f ):
+        case CX_TOKEN( f ):
             mrModel.maFormula = rChars;
         break;
         case C_TOKEN( formatCode ):
             mrModel.maFormatCode = rChars;
         break;
         case C_TOKEN( v ):
+        case CX_TOKEN(pt):
             if( mnPtIndex >= 0 )
             {
                 /* Import categories as String even though it could
@@ -244,9 +264,11 @@ ContextHandlerRef StringSequenceContext::onCreateContext( 
sal_Int32 nElement, co
             break;
 
         case C_TOKEN( lvl ):
+        case CX_TOKEN( lvl ):
             switch (nElement)
             {
                 case C_TOKEN(pt):
+                case CX_TOKEN(pt):
                     mnPtIndex = rAttribs.getInteger(XML_idx, -1);
                     return this;
             }
@@ -259,6 +281,14 @@ ContextHandlerRef StringSequenceContext::onCreateContext( 
sal_Int32 nElement, co
                     return this;
             }
         break;
+        case CX_TOKEN(strDim) :
+            switch (nElement) {
+                case CX_TOKEN(f):
+                    return this;
+                case CX_TOKEN(lvl):
+                    mrModel.mnPointCount = rAttribs.getInteger(XML_ptCount, 
-1);
+                    return this;
+            }
     }
     return nullptr;
 }
@@ -275,8 +305,11 @@ void StringSequenceContext::onCharacters( const OUString& 
rChars )
                 mrModel.maFormula = rChars;
         break;
         case C_TOKEN( v ):
-            if( mnPtIndex >= 0 )
+        case CX_TOKEN(pt):
+            if( mnPtIndex >= 0 ) {
+                assert(mrModel.mnPointCount > 0);
                 mrModel.maData[ (mrModel.mnLevelCount-1) * 
mrModel.mnPointCount + mnPtIndex ] <<= rChars;
+            }
         break;
     }
 }
@@ -290,7 +323,8 @@ DataSourceContext::~DataSourceContext()
 {
 }
 
-ContextHandlerRef DataSourceContext::onCreateContext( sal_Int32 nElement, 
const AttributeList& )
+ContextHandlerRef DataSourceContext::onCreateContext( sal_Int32 nElement, const
+        AttributeList&)
 {
     switch( getCurrentElement() )
     {
@@ -330,6 +364,74 @@ ContextHandlerRef DataSourceContext::onCreateContext( 
sal_Int32 nElement, const
     return nullptr;
 }
 
+// =====
+// DataSourceCxContext: handler for chartex data sources
+// =====
+DataSourceCxContext::DataSourceCxContext( ContextHandler2Helper& rParent,
+        DataSourceCxModel& rModel ) :
+    ContextBase< DataSourceCxModel >( rParent, rModel ),
+    paCurSource(nullptr)
+{
+}
+
+DataSourceCxContext::~DataSourceCxContext()
+{
+}
+
+ContextHandlerRef DataSourceCxContext::onCreateContext(sal_Int32 nElement, 
const AttributeList& rAttribs)
+{
+    switch( getCurrentElement() )
+    {
+        case CX_TOKEN(chartData) :
+            switch (nElement) {
+                case CX_TOKEN(externalData) :
+                    return nullptr; // TODO
+                case CX_TOKEN(data) :
+                    paCurSource = 
&mrModel.maSourceMap.create(rAttribs.getInteger(XML_id, -1));
+                    return this;
+            }
+            break;
+        case CX_TOKEN(data) :
+            switch (nElement) {
+                case CX_TOKEN(numDim) :
+                {
+                    assert(paCurSource);
+                    OUString sType = rAttribs.getString(XML_type, "val");
+                    if (sType == "cat") {
+                        DataSourceModel& rDataModel = 
paCurSource->create(DataSourceType::CATEGORIES);
+                        return new DoubleSequenceContext(*this,
+                                rDataModel.mxDataSeq.create());
+                    } else {
+                        // default is VALUES
+                        DataSourceModel& rDataModel = 
paCurSource->create(DataSourceType::VALUES);
+                        return new DoubleSequenceContext(*this,
+                                rDataModel.mxDataSeq.create());
+                    }
+                    break;
+                }
+
+                case CX_TOKEN(strDim) :
+                {
+                    assert(paCurSource);
+                    OUString sType = rAttribs.getString(XML_type, "cat");
+                    if (sType == "val") {
+                        DataSourceModel& rDataModel = 
paCurSource->create(DataSourceType::VALUES);
+                        return new StringSequenceContext(*this,
+                                rDataModel.mxDataSeq.create());
+                    } else {
+                        // default is CATEGORIES
+                        DataSourceModel& rDataModel = 
paCurSource->create(DataSourceType::CATEGORIES);
+                        return new StringSequenceContext(*this,
+                                rDataModel.mxDataSeq.create());
+                    }
+                }
+            }
+            break;
+
+    }
+    return nullptr;
+}
+
 } // namespace oox::drawingml::chart
 
 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/oox/source/drawingml/chart/plotareaconverter.cxx 
b/oox/source/drawingml/chart/plotareaconverter.cxx
index 1fa8b6f9b673..95496906fdea 100644
--- a/oox/source/drawingml/chart/plotareaconverter.cxx
+++ b/oox/source/drawingml/chart/plotareaconverter.cxx
@@ -32,6 +32,7 @@
 #include <drawingml/chart/axisconverter.hxx>
 #include <drawingml/chart/plotareamodel.hxx>
 #include <drawingml/chart/typegroupconverter.hxx>
+#include <drawingml/chart/seriesmodel.hxx>
 #include <oox/core/xmlfilterbase.hxx>
 #include <oox/token/namespaces.hxx>
 #include <oox/token/properties.hxx>
@@ -71,6 +72,7 @@ public:
                             const Reference< XDiagram >& rxDiagram,
                             View3DModel& rView3DModel,
                             sal_Int32 nAxesSetIdx,
+                            DataSourceCxModel::DataMap& raSourceMap,
                             bool bSupportsVaryColorsByPoint,
                             bool bUseFixedInnerSize );
 
@@ -112,6 +114,7 @@ ModelRef< AxisModel > lclGetOrCreateAxis( const 
AxesSetModel::AxisMap& rFromAxes
 
 void AxesSetConverter::convertFromModel( const Reference< XDiagram >& 
rxDiagram,
                                         View3DModel& rView3DModel, sal_Int32 
nAxesSetIdx,
+                                        DataSourceCxModel::DataMap& 
raSourceMap,
                                         bool bSupportsVaryColorsByPoint, bool 
bUseFixedInnerSize)
 {
     // create type group converter objects for all type groups
@@ -169,6 +172,13 @@ void AxesSetConverter::convertFromModel( const Reference< 
XDiagram >& rxDiagram,
             to the data provider attached to the chart document. */
         if( xCoordSystem.is() )
         {
+            // Transfer any (chartex) data, specified at the chartSpace level,
+            // into the appropriate series. This needs to happen before the
+            // calls to AxisConverter::convertFromModel() below.
+            for (auto const& typeGroup : aTypeGroups) {
+                typeGroup->moveDataToSeries(raSourceMap);
+            }
+
             bool bMSO2007Doc = getFilter().isMSO2007Document();
             // convert all axes (create missing axis models)
             ModelRef< AxisModel > xXAxis = lclGetOrCreateAxis( mrModel.maAxes, 
API_X_AXIS, rFirstTypeGroup.getTypeInfo().mbCategoryAxis ? C_TOKEN( catAx ) : 
C_TOKEN( valAx ), bMSO2007Doc );
@@ -191,7 +201,8 @@ void AxesSetConverter::convertFromModel( const Reference< 
XDiagram >& rxDiagram,
 
             // convert all chart type groups, this converts all series data 
and formatting
             for (auto const& typeGroup : aTypeGroups)
-                typeGroup->convertFromModel( rxDiagram, xCoordSystem, 
nAxesSetIdx, bSupportsVaryColorsByPoint );
+                typeGroup->convertFromModel( rxDiagram, xCoordSystem,
+                        nAxesSetIdx,bSupportsVaryColorsByPoint );
         }
     }
     catch( Exception& )
@@ -313,7 +324,8 @@ PlotAreaConverter::~PlotAreaConverter()
 {
 }
 
-void PlotAreaConverter::convertFromModel( View3DModel& rView3DModel )
+void PlotAreaConverter::convertFromModel( View3DModel& rView3DModel,
+        DataSourceCxModel& rDataCxModel )
 {
     /*  Create the diagram object and attach it to the chart document. One
         diagram is used to carry all coordinate systems and data series. */
@@ -426,7 +438,7 @@ void PlotAreaConverter::convertFromModel( View3DModel& 
rView3DModel )
     {
         AxesSetConverter aAxesSetConv(*this, *axesSet);
         aAxesSetConv.convertFromModel(xDiagram, rView3DModel, nAxesSetIdx,
-                                      bSupportsVaryColorsByPoint, 
bUseFixedInnerSize);
+                rDataCxModel.maSourceMap, bSupportsVaryColorsByPoint, 
bUseFixedInnerSize);
         if(nAxesSetIdx == nStartAxesSetIdx)
         {
             maAutoTitle = aAxesSetConv.getAutomaticTitle();
diff --git a/oox/source/drawingml/chart/seriescontext.cxx 
b/oox/source/drawingml/chart/seriescontext.cxx
index 88688642bebe..19134c7a54a7 100644
--- a/oox/source/drawingml/chart/seriescontext.cxx
+++ b/oox/source/drawingml/chart/seriescontext.cxx
@@ -452,10 +452,10 @@ ContextHandlerRef SeriesContextBase::onCreateContext( 
sal_Int32 nElement, const
             {
                 case C_TOKEN( ext ):
                 case CX_TOKEN( ext ):
-                    if (mrModel.maSources.has( SeriesModel::DATALABELS ))
+                    if (mrModel.maSources.has( DataSourceType::DATALABELS ))
                         break;
 
-                    DataSourceModel& rLabelsSource = mrModel.maSources.create( 
SeriesModel::DATALABELS );
+                    DataSourceModel& rLabelsSource = mrModel.maSources.create( 
DataSourceType::DATALABELS );
                     if (mrModel.mxLabels.is())
                         mrModel.mxLabels->mpLabelsSource = &rLabelsSource;
                     return new DataSourceContext( *this, rLabelsSource );
@@ -483,7 +483,7 @@ ContextHandlerRef AreaSeriesContext::onCreateContext( 
sal_Int32 nElement, const
             switch( nElement )
             {
                 case C_TOKEN( cat ):
-                    return new DataSourceContext( *this, 
mrModel.maSources.create( SeriesModel::CATEGORIES ) );
+                    return new DataSourceContext( *this, 
mrModel.maSources.create( DataSourceType::CATEGORIES ) );
                 case C_TOKEN( errBars ):
                     return new ErrorBarContext( *this, 
mrModel.maErrorBars.create(bMSO2007Doc) );
                 case C_TOKEN( dLbls ):
@@ -493,7 +493,7 @@ ContextHandlerRef AreaSeriesContext::onCreateContext( 
sal_Int32 nElement, const
                 case C_TOKEN( trendline ):
                     return new TrendlineContext( *this, 
mrModel.maTrendlines.create(bMSO2007Doc) );
                 case C_TOKEN( val ):
-                    return new DataSourceContext( *this, 
mrModel.maSources.create( SeriesModel::VALUES ) );
+                    return new DataSourceContext( *this, 
mrModel.maSources.create( DataSourceType::VALUES ) );
             }
         break;
     }
@@ -518,7 +518,7 @@ ContextHandlerRef BarSeriesContext::onCreateContext( 
sal_Int32 nElement, const A
             switch( nElement )
             {
                 case C_TOKEN( cat ):
-                    return new DataSourceContext( *this, 
mrModel.maSources.create( SeriesModel::CATEGORIES ) );
+                    return new DataSourceContext( *this, 
mrModel.maSources.create( DataSourceType::CATEGORIES ) );
                 case C_TOKEN( dLbls ):
                     return new DataLabelsContext( *this, 
mrModel.mxLabels.create(bMSO2007Doc) );
                 case C_TOKEN( dPt ):
@@ -536,7 +536,7 @@ ContextHandlerRef BarSeriesContext::onCreateContext( 
sal_Int32 nElement, const A
                 case C_TOKEN( trendline ):
                     return new TrendlineContext( *this, 
mrModel.maTrendlines.create(bMSO2007Doc) );
                 case C_TOKEN( val ):
-                    return new DataSourceContext( *this, 
mrModel.maSources.create( SeriesModel::VALUES ) );
+                    return new DataSourceContext( *this, 
mrModel.maSources.create( DataSourceType::VALUES ) );
             }
         break;
     }
@@ -564,7 +564,7 @@ ContextHandlerRef BubbleSeriesContext::onCreateContext( 
sal_Int32 nElement, cons
                     mrModel.mbBubble3d = rAttribs.getBool( XML_val, 
!bMSO2007Doc );
                     return nullptr;
                 case C_TOKEN( bubbleSize ):
-                    return new DataSourceContext( *this, 
mrModel.maSources.create( SeriesModel::POINTS ) );
+                    return new DataSourceContext( *this, 
mrModel.maSources.create( DataSourceType::POINTS ) );
                 case C_TOKEN( dLbls ):
                     return new DataLabelsContext( *this, 
mrModel.mxLabels.create(bMSO2007Doc) );
                 case C_TOKEN( dPt ):
@@ -577,9 +577,9 @@ ContextHandlerRef BubbleSeriesContext::onCreateContext( 
sal_Int32 nElement, cons
                 case C_TOKEN( trendline ):
                     return new TrendlineContext( *this, 
mrModel.maTrendlines.create(bMSO2007Doc) );
                 case C_TOKEN( xVal ):
-                    return new DataSourceContext( *this, 
mrModel.maSources.create( SeriesModel::CATEGORIES ) );
+                    return new DataSourceContext( *this, 
mrModel.maSources.create( DataSourceType::CATEGORIES ) );
                 case C_TOKEN( yVal ):
-                    return new DataSourceContext( *this, 
mrModel.maSources.create( SeriesModel::VALUES ) );
+                    return new DataSourceContext( *this, 
mrModel.maSources.create( DataSourceType::VALUES ) );
             }
         break;
     }
@@ -604,7 +604,7 @@ ContextHandlerRef LineSeriesContext::onCreateContext( 
sal_Int32 nElement, const
             switch( nElement )
             {
                 case C_TOKEN( cat ):
-                    return new DataSourceContext( *this, 
mrModel.maSources.create( SeriesModel::CATEGORIES ) );
+                    return new DataSourceContext( *this, 
mrModel.maSources.create( DataSourceType::CATEGORIES ) );
                 case C_TOKEN( dLbls ):
                     return new DataLabelsContext( *this, 
mrModel.mxLabels.create(bMSO2007Doc) );
                 case C_TOKEN( dPt ):
@@ -619,7 +619,7 @@ ContextHandlerRef LineSeriesContext::onCreateContext( 
sal_Int32 nElement, const
                 case C_TOKEN( trendline ):
                     return new TrendlineContext( *this, 
mrModel.maTrendlines.create(bMSO2007Doc) );
                 case C_TOKEN( val ):
-                    return new DataSourceContext( *this, 
mrModel.maSources.create( SeriesModel::VALUES ) );
+                    return new DataSourceContext( *this, 
mrModel.maSources.create( DataSourceType::VALUES ) );
             }
         break;
     }
@@ -644,7 +644,7 @@ ContextHandlerRef PieSeriesContext::onCreateContext( 
sal_Int32 nElement, const A
             switch( nElement )
             {
                 case C_TOKEN( cat ):
-                    return new DataSourceContext( *this, 
mrModel.maSources.create( SeriesModel::CATEGORIES ) );
+                    return new DataSourceContext( *this, 
mrModel.maSources.create( DataSourceType::CATEGORIES ) );
                 case C_TOKEN( dLbls ):
                     return new DataLabelsContext( *this, 
mrModel.mxLabels.create(bMSO2007Doc) );
                 case C_TOKEN( dPt ):
@@ -653,7 +653,7 @@ ContextHandlerRef PieSeriesContext::onCreateContext( 
sal_Int32 nElement, const A
                     mrModel.mnExplosion = rAttribs.getInteger( XML_val, 0 );
                     return nullptr;
                 case C_TOKEN( val ):
-                    return new DataSourceContext( *this, 
mrModel.maSources.create( SeriesModel::VALUES ) );
+                    return new DataSourceContext( *this, 
mrModel.maSources.create( DataSourceType::VALUES ) );
             }
         break;
     }
@@ -678,7 +678,7 @@ ContextHandlerRef RadarSeriesContext::onCreateContext( 
sal_Int32 nElement, const
             switch( nElement )
             {
                 case C_TOKEN( cat ):
-                    return new DataSourceContext( *this, 
mrModel.maSources.create( SeriesModel::CATEGORIES ) );
+                    return new DataSourceContext( *this, 
mrModel.maSources.create( DataSourceType::CATEGORIES ) );
                 case C_TOKEN( dLbls ):
                     return new DataLabelsContext( *this, 
mrModel.mxLabels.create(bMSO2007Doc) );
                 case C_TOKEN( dPt ):
@@ -689,7 +689,7 @@ ContextHandlerRef RadarSeriesContext::onCreateContext( 
sal_Int32 nElement, const
                     mrModel.mbSmooth = rAttribs.getBool( XML_val, bMSO2007Doc 
);
                     return nullptr;
                 case C_TOKEN( val ):
-                    return new DataSourceContext( *this, 
mrModel.maSources.create( SeriesModel::VALUES ) );
+                    return new DataSourceContext( *this, 
mrModel.maSources.create( DataSourceType::VALUES ) );
             }
         break;
     }
@@ -727,9 +727,9 @@ ContextHandlerRef ScatterSeriesContext::onCreateContext( 
sal_Int32 nElement, con
                 case C_TOKEN( trendline ):
                     return new TrendlineContext( *this, 
mrModel.maTrendlines.create(bMSO2007Doc) );
                 case C_TOKEN( xVal ):
-                    return new DataSourceContext( *this, 
mrModel.maSources.create( SeriesModel::CATEGORIES ) );
+                    return new DataSourceContext( *this, 
mrModel.maSources.create( DataSourceType::CATEGORIES ) );
                 case C_TOKEN( yVal ):
-                    return new DataSourceContext( *this, 
mrModel.maSources.create( SeriesModel::VALUES ) );
+                    return new DataSourceContext( *this, 
mrModel.maSources.create( DataSourceType::VALUES ) );
             }
         break;
     }
@@ -753,9 +753,9 @@ ContextHandlerRef SurfaceSeriesContext::onCreateContext( 
sal_Int32 nElement, con
             switch( nElement )
             {
                 case C_TOKEN( cat ):
-                    return new DataSourceContext( *this, 
mrModel.maSources.create( SeriesModel::CATEGORIES ) );
+                    return new DataSourceContext( *this, 
mrModel.maSources.create( DataSourceType::CATEGORIES ) );
                 case C_TOKEN( val ):
-                    return new DataSourceContext( *this, 
mrModel.maSources.create( SeriesModel::VALUES ) );
+                    return new DataSourceContext( *this, 
mrModel.maSources.create( DataSourceType::VALUES ) );
             }
         break;
     }
@@ -789,7 +789,7 @@ ContextHandlerRef ChartexSeriesContext::onCreateContext( 
sal_Int32 nElement, con
                 case CX_TOKEN( dataLabels ):
                     return new DataLabelsContext( *this, 
mrModel.mxLabels.create(false) );
                 case CX_TOKEN( dataId ):
-                    // TODO
+                    mrModel.mnDataId = rAttribs.getInteger(XML_val, -1);
                     return nullptr;
                 case CX_TOKEN( layoutPr ):
                     // This looks complicated. TODO
diff --git a/oox/source/drawingml/chart/seriesconverter.cxx 
b/oox/source/drawingml/chart/seriesconverter.cxx
index 27f25057badf..c0a15a85d922 100644
--- a/oox/source/drawingml/chart/seriesconverter.cxx
+++ b/oox/source/drawingml/chart/seriesconverter.cxx
@@ -799,12 +799,12 @@ SeriesConverter::~SeriesConverter()
 
 Reference< XLabeledDataSequence > SeriesConverter::createCategorySequence( 
const OUString& rRole )
 {
-    return createLabeledDataSequence(SeriesModel::CATEGORIES, rRole, false);
+    return createLabeledDataSequence(DataSourceType::CATEGORIES, rRole, false);
 }
 
 Reference< XLabeledDataSequence > SeriesConverter::createValueSequence( const 
OUString& rRole )
 {
-    return createLabeledDataSequence( SeriesModel::VALUES, rRole, true );
+    return createLabeledDataSequence( DataSourceType::VALUES, rRole, true );
 }
 
 Reference< XDataSeries > SeriesConverter::createDataSeries( const 
TypeGroupConverter& rTypeGroup, bool bVaryColorsByPoint )
@@ -844,7 +844,8 @@ Reference< XDataSeries > SeriesConverter::createDataSeries( 
const TypeGroupConve
             // add size values of bubble charts
             if( rTypeInfo.meTypeId == TYPEID_BUBBLE )
             {
-                Reference< XLabeledDataSequence > xSizeValueSeq = 
createLabeledDataSequence( SeriesModel::POINTS, u"values-size"_ustr, true );
+                Reference< XLabeledDataSequence > xSizeValueSeq =
+                    createLabeledDataSequence( DataSourceType::POINTS, 
u"values-size"_ustr, true );
                 if( xSizeValueSeq.is() )
                     aLabeledSeqVec.push_back( xSizeValueSeq );
             }
@@ -936,7 +937,7 @@ Reference< XDataSeries > SeriesConverter::createDataSeries( 
const TypeGroupConve
         if( xLabels->maNumberFormat.maFormatCode.isEmpty() )
         {
             // Use number format code from Value series
-            DataSourceModel* pValues = mrModel.maSources.get( 
SeriesModel::VALUES ).get();
+            DataSourceModel* pValues = mrModel.maSources.get( 
DataSourceType::VALUES ).get();
             if( pValues )
                 xLabels->maNumberFormat.maFormatCode = 
pValues->mxDataSeq->maFormatCode;
         }
@@ -950,7 +951,7 @@ Reference< XDataSeries > SeriesConverter::createDataSeries( 
const TypeGroupConve
 // private --------------------------------------------------------------------
 
 Reference< XLabeledDataSequence > SeriesConverter::createLabeledDataSequence(
-        SeriesModel::SourceType eSourceType, const OUString& rRole, bool 
bUseTextLabel )
+        enum DataSourceType eSourceType, const OUString& rRole, bool 
bUseTextLabel )
 {
     DataSourceModel* pValues = mrModel.maSources.get( eSourceType ).get();
     TextModel* pTitle = bUseTextLabel ? mrModel.mxText.get() : nullptr;
diff --git a/oox/source/drawingml/chart/seriesmodel.cxx 
b/oox/source/drawingml/chart/seriesmodel.cxx
index 95b0deb225e7..194d4f6ecf3d 100644
--- a/oox/source/drawingml/chart/seriesmodel.cxx
+++ b/oox/source/drawingml/chart/seriesmodel.cxx
@@ -112,6 +112,7 @@ SeriesModel::SeriesModel(bool bMSO2007Doc) :
     mnMarkerSize( 5 ),
     mnMarkerSymbol( XML_auto ),
     mnOrder( -1 ),
+    mnDataId(-1),
     mbBubble3d( !bMSO2007Doc ),
     mbInvertNeg( !bMSO2007Doc ),
     mbSmooth( !bMSO2007Doc )
diff --git a/oox/source/drawingml/chart/typegroupconverter.cxx 
b/oox/source/drawingml/chart/typegroupconverter.cxx
index 15711e156cc2..68cb7753fd62 100644
--- a/oox/source/drawingml/chart/typegroupconverter.cxx
+++ b/oox/source/drawingml/chart/typegroupconverter.cxx
@@ -39,6 +39,7 @@
 #include <comphelper/sequence.hxx>
 #include <osl/diagnose.h>
 #include <drawingml/lineproperties.hxx>
+#include <drawingml/chart/seriesmodel.hxx>
 #include <drawingml/chart/seriesconverter.hxx>
 #include <drawingml/chart/typegroupmodel.hxx>
 #include <oox/core/xmlfilterbase.hxx>
@@ -288,16 +289,16 @@ Reference< XLabeledDataSequence > 
TypeGroupConverter::createCategorySequence()
         first series, even if it was empty. */
     for (auto const& elem : mrModel.maSeries)
     {
-        if( elem->maSources.has( SeriesModel::CATEGORIES ) )
+        if( elem->maSources.has( DataSourceType::CATEGORIES ) )
         {
             SeriesConverter aSeriesConv(*this, *elem);
             xLabeledSeq = aSeriesConv.createCategorySequence( 
u"categories"_ustr );
             if (xLabeledSeq.is())
                 break;
         }
-        else if( nMaxValues <= 0 && elem->maSources.has( SeriesModel::VALUES ) 
)
+        else if( nMaxValues <= 0 && elem->maSources.has( 
DataSourceType::VALUES ) )
         {
-            DataSourceModel *pValues = elem->maSources.get( 
SeriesModel::VALUES ).get();
+            DataSourceModel *pValues = elem->maSources.get( 
DataSourceType::VALUES ).get();
             if( pValues->mxDataSeq.is() )
                 nMaxValues = pValues->mxDataSeq->maData.size();
         }
@@ -308,9 +309,9 @@ Reference< XLabeledDataSequence > 
TypeGroupConverter::createCategorySequence()
             nMaxValues = 2;
         typedef RefVector<SeriesModel> SeriesModelVector;
         SeriesModelVector::value_type aModel = mrModel.maSeries.get(0);
-        if (!aModel->maSources.has(SeriesModel::CATEGORIES))
+        if (!aModel->maSources.has(DataSourceType::CATEGORIES))
         {
-            DataSourceModel &aSrc = aModel->maSources.create( 
SeriesModel::CATEGORIES );
+            DataSourceModel &aSrc = aModel->maSources.create( 
DataSourceType::CATEGORIES );
             DataSequenceModel &aSeq = aSrc.mxDataSeq.create();
             aSeq.mnPointCount = nMaxValues;
             for( sal_Int32 i = 0; i < nMaxValues; i++ )
@@ -324,7 +325,8 @@ Reference< XLabeledDataSequence > 
TypeGroupConverter::createCategorySequence()
 
 void TypeGroupConverter::convertFromModel( const Reference< XDiagram >& 
rxDiagram,
         const Reference< XCoordinateSystem >& rxCoordSystem,
-        sal_Int32 nAxesSetIdx, bool bSupportsVaryColorsByPoint )
+        sal_Int32 nAxesSetIdx,
+        bool bSupportsVaryColorsByPoint )
 {
     try
     {
@@ -612,6 +614,22 @@ PieChartSubType 
TypeGroupConverter::convertOfPieType(sal_Int32 nOoxOfPieType ) c
     }
 }
 
+void TypeGroupConverter::moveDataToSeries(DataSourceCxModel::DataMap& 
raDataMap)
+{
+    // For chartex, move data from rDataMap to the appropriate series. In
+    // chartex the data is given outside the series, in a child element of
+    // <cx:chartSpace>. Pre-2016 charts have the data inside the series, and
+    // SeriesModel and subsequent handling reflects this. So here we get the
+    // data to the right place for processing.
+    if (!raDataMap.empty()) {
+        // should only happen for chartex
+        for (auto const& elem : mrModel.maSeries) {
+            // This ID must be present in the map
+            assert(raDataMap.find(elem->mnDataId) != raDataMap.end());
+            elem->maSources = *(raDataMap[elem->mnDataId]);
+        }
+    }
+}
 
 // private --------------------------------------------------------------------
 
diff --git a/oox/source/export/chartexport.cxx 
b/oox/source/export/chartexport.cxx
index 9f0b875ae6f6..6628ee02227b 100644
--- a/oox/source/export/chartexport.cxx
+++ b/oox/source/export/chartexport.cxx
@@ -231,12 +231,65 @@ void outputDataPointStyleEntry(FSHelperPtr pFS)
     pFS->endElement(FSNS(XML_cs, XML_dataPoint));
 }
 
+std::vector<Sequence<Reference<chart2::XDataSeries> > > 
splitDataSeriesByAxis(const Reference< chart2::XChartType >& xChartType)
+{
+    std::vector<Sequence<Reference<chart2::XDataSeries> > > aSplitSeries;
+    std::map<sal_Int32, size_t> aMapAxisToIndex;
+
+    Reference< chart2::XDataSeriesContainer > xDSCnt(xChartType, 
uno::UNO_QUERY);
+    if (xDSCnt.is())
+    {
+        sal_Int32 nAxisIndexOfFirstSeries = -1;
+        const Sequence< Reference< chart2::XDataSeries > > 
aSeriesSeq(xDSCnt->getDataSeries());
+        for (const uno::Reference<chart2::XDataSeries>& xSeries : aSeriesSeq)
+        {
+            Reference<beans::XPropertySet> xPropSet(xSeries, uno::UNO_QUERY);
+            if (!xPropSet.is())
+                continue;
+
+            sal_Int32 nAxisIndex = -1;
+            uno::Any aAny = 
xPropSet->getPropertyValue(u"AttachedAxisIndex"_ustr);
+            aAny >>= nAxisIndex;
+            size_t nVectorPos = 0;
+            if (nAxisIndexOfFirstSeries == -1)
+            {
+                nAxisIndexOfFirstSeries = nAxisIndex;
+            }
+
+            auto it = aMapAxisToIndex.find(nAxisIndex);
+            if (it == aMapAxisToIndex.end())
+            {
+                aSplitSeries.emplace_back();
+                nVectorPos = aSplitSeries.size() - 1;
+                aMapAxisToIndex.insert(std::pair<sal_Int32, 
size_t>(nAxisIndex, nVectorPos));
+            }
+            else
+            {
+                nVectorPos = it->second;
+            }
+
+            uno::Sequence<Reference<chart2::XDataSeries> >& rAxisSeriesSeq = 
aSplitSeries[nVectorPos];
+            sal_Int32 nLength = rAxisSeriesSeq.getLength();
+            rAxisSeriesSeq.realloc(nLength + 1);
+            rAxisSeriesSeq.getArray()[nLength] = xSeries;
+        }
+        // if the first series attached to secondary axis, then export those 
series first, which are attached to primary axis
+        // also the MS Office export every time in this order
+        if (aSplitSeries.size() > 1 && nAxisIndexOfFirstSeries == 1)
+        {
+            std::swap(aSplitSeries[0], aSplitSeries[1]);
+        }
+    }
+
+    return aSplitSeries;
+}
 
 }   // unnamed namespace
 
-static Reference< chart2::data::XLabeledDataSequence > lcl_getCategories( 
const Reference< chart2::XDiagram > & xDiagram, bool& bHasDateCategories )
+static Reference< chart2::data::XLabeledDataSequence > lcl_getCategories(
+        const Reference< chart2::XDiagram > & xDiagram, bool 
*pbHasDateCategories )
 {
-    bHasDateCategories = false;
+    *pbHasDateCategories = false;
     Reference< chart2::data::XLabeledDataSequence >  xResult;
     try
     {
@@ -259,7 +312,7 @@ static Reference< chart2::data::XLabeledDataSequence > 
lcl_getCategories( const
                         chart2::ScaleData aScaleData = xAxis->getScaleData();
                         if( aScaleData.Categories.is())
                         {
-                            bHasDateCategories = aScaleData.AxisType == 
chart2::AxisType::DATE;
+                            *pbHasDateCategories = aScaleData.AxisType == 
chart2::AxisType::DATE;
                             xResult.set( aScaleData.Categories );
                             break;
                         }
@@ -299,7 +352,8 @@ static bool lcl_hasCategoryLabels( const Reference< 
chart2::XChartDocument >& xC
     //categories are always the first sequence
     Reference< chart2::XDiagram > xDiagram( xChartDoc->getFirstDiagram());
     bool bDateCategories;
-    Reference< chart2::data::XLabeledDataSequence > xCategories( 
lcl_getCategories( xDiagram, bDateCategories ) );
+    Reference< chart2::data::XLabeledDataSequence > xCategories(
+            lcl_getCategories( xDiagram, &bDateCategories ) );
     return xCategories.is();
 }
 
@@ -1264,7 +1318,7 @@ void ChartExport::exportChartSpace( const Reference< 
css::chart::XChartDocument
         pFS->startElement(FSNS(XML_cx, XML_chartData));
 
         exportExternalData(xChartDoc, true);
-        exportData(xChartDoc, true);
+        exportData_chartex(xChartDoc);
 
         pFS->endElement(FSNS(XML_cx, XML_chartData));
     } else {
@@ -1313,24 +1367,239 @@ void ChartExport::exportChartSpace( const Reference< 
css::chart::XChartDocument
     pFS->endElement( FSNS( nChartNS, XML_chartSpace ) );
 }
 
-void ChartExport::exportData( [[maybe_unused]] const Reference< 
css::chart::XChartDocument >& xChartDoc,
-        bool bIsChartex)
+void ChartExport::exportData_chartex( [[maybe_unused]] const Reference< 
css::chart::XChartDocument >& xChartDoc)
 {
-    if (bIsChartex) {
-        FSHelperPtr pFS = GetFS();
-
-        // Not sure if the data id is always 0. However, it seems it may need 
to
-        // agree with the id in exportSeries(). See DATA_ID_COMMENT
-        pFS->startElement(FSNS(XML_cx, XML_data), XML_id, "0");
-        // Just hard-coding this for now
-        pFS->startElement(FSNS(XML_cx, XML_numDim), XML_type, "val");
-        pFS->startElement(FSNS(XML_cx, XML_f));
-        pFS->writeEscaped("_xlchart.v2.0");    // I have no idea what this
-                                                // means or what it should be 
in
-                                                // general
-        pFS->endElement(FSNS(XML_cx, XML_f));
-        pFS->endElement(FSNS(XML_cx, XML_numDim));
-        pFS->endElement(FSNS(XML_cx, XML_data));
+    Reference< chart2::XCoordinateSystemContainer > xBCooSysCnt( mxNewDiagram, 
uno::UNO_QUERY );
+    if( ! xBCooSysCnt.is()) return;
+    const Sequence< Reference< chart2::XCoordinateSystem > >
+        aCooSysSeq( xBCooSysCnt->getCoordinateSystems());
+
+    if (!aCooSysSeq.hasElements()) return;
+
+    for( const auto& rCS : aCooSysSeq ) {
+        Reference< chart2::XChartTypeContainer > xCTCnt( rCS, uno::UNO_QUERY );
+        if( ! xCTCnt.is())
+            continue;
+        const Sequence< Reference< chart2::XChartType > > aCTSeq( 
xCTCnt->getChartTypes());
+
+        for( const auto& rCT : aCTSeq ) {
+            Reference< chart2::XDataSeriesContainer > xDSCnt( rCT, 
uno::UNO_QUERY );
+            if( ! xDSCnt.is())
+                return;
+            Reference< chart2::XChartType > xChartType( rCT, uno::UNO_QUERY );
+            if( ! xChartType.is())
+                continue;
+
+            OUString aLabelRole = 
xChartType->getRoleOfSequenceForSeriesLabel();
+
+            const std::vector<Sequence<Reference<chart2::XDataSeries> > > 
aSplitDataSeries = splitDataSeriesByAxis(xChartType);
+
+            for (const auto& splitDataSeries : aSplitDataSeries) {
+                sal_Int32 nSeriesIndex = 0;
+                for( const auto& rSeries : splitDataSeries )
+                {
+                    // export series
+                    Reference< chart2::data::XDataSource > xSource( rSeries, 
uno::UNO_QUERY );
+                    if( !xSource.is()) continue;
+
+                    Reference< chart2::XDataSeries > xDataSeries( xSource, 
uno::UNO_QUERY );
+                    Sequence< Reference< chart2::data::XLabeledDataSequence > 
> aSeqCnt(
+                        xSource->getDataSequences());
+
+                    // search for main sequence and create a series element
+                    sal_Int32 nMainSequenceIndex = -1;
+                    sal_Int32 nSeriesLength = 0;
+                    Reference< chart2::data::XDataSequence > xValueSeq;
+                    Reference< chart2::data::XDataSequence > xLabelSeq;
+                    sal_Int32 nSeqIdx=0;
+                    for( ; nSeqIdx<aSeqCnt.getLength(); ++nSeqIdx )
+                    {
+                        Reference< chart2::data::XDataSequence > 
xTempValueSeq( aSeqCnt[nSeqIdx]->getValues() );
+                        if( nMainSequenceIndex==-1 )
+                        {
+                            Reference< beans::XPropertySet > xSeqProp( 
xTempValueSeq, uno::UNO_QUERY );
+                            OUString aRole;
+                            if( xSeqProp.is())
+                                xSeqProp->getPropertyValue(u"Role"_ustr) >>= 
aRole;
+                            // "main" sequence
+                            if( aRole == aLabelRole )
+                            {
+                                xValueSeq.set( xTempValueSeq );
+                                xLabelSeq.set( aSeqCnt[nSeqIdx]->getLabel());
+                                nMainSequenceIndex = nSeqIdx;
+                            }
+                        }
+                        sal_Int32 nSequenceLength = (xTempValueSeq.is()? 
xTempValueSeq->getData().getLength() : sal_Int32(0));
+                        if( nSeriesLength < nSequenceLength )
+                            nSeriesLength = nSequenceLength;
+                    }
+                    FSHelperPtr pFS = GetFS();
+
+                    // The data id needs to agree with the id in 
exportSeries(). See DATA_ID_COMMENT
+                    pFS->startElement(FSNS(XML_cx, XML_data), XML_id, 
OUString::number(nSeriesIndex++));
+
+                    // .xlsx chartex files seem to have this magical 
"_xlchart.v2.0" string,
+                    // and no explicit data, while .docx and .pptx contain the 
literal data,
+                    // as well as a ../embeddings file (which LO doesn't seem 
to produce).
+                    // But there's probably a smarter way to determine which 
pathway to take
+                    // than based on document type.
+                    if (GetDocumentType() == DOCUMENT_XLSX) {
+                        // Just hard-coding this for now
+                        pFS->startElement(FSNS(XML_cx, XML_numDim), XML_type, 
"val");
+                        pFS->startElement(FSNS(XML_cx, XML_f));
+                        pFS->writeEscaped("_xlchart.v2.0");    // I have no 
idea what this
+                                                                // means or 
what it should be in
+                                                                // general
+                        pFS->endElement(FSNS(XML_cx, XML_f));
+                        pFS->endElement(FSNS(XML_cx, XML_numDim));
+                    } else {    // PPTX, DOCX
+                        OUString aCellRange = mxCategoriesValues.is() ? 
mxCategoriesValues->getSourceRangeRepresentation() : OUString();
+#undef OUTPUT_SPLIT_CATEGORIES  // do we need this or not? TODO
+#ifdef OUTPUT_SPLIT_CATEGORIES
+                        const Sequence< Sequence< OUString >> 
aFinalSplitSource = getSplitCategoriesList(aCellRange);
+#endif
+                        aCellRange = parseFormula( aCellRange );
+
+#ifdef OUTPUT_SPLIT_CATEGORIES
+                        if (aFinalSplitSource.getLength() > 1) {
+
+                            // export multi level category axis labels
+                            pFS->startElement(FSNS(XML_cx, XML_strDim), 
XML_type, "cat");
+
+                            pFS->startElement(FSNS(XML_cx, XML_f));
+                            pFS->writeEscaped(aCellRange);
+                            pFS->endElement(FSNS(XML_cx, XML_f));
+
+                            for (const auto& rSeq : aFinalSplitSource) {
+                                pFS->startElement(FSNS(XML_cx, XML_lvl),
+                                        XML_ptCount, 
OString::number(aFinalSplitSource[0].getLength()));
+
+                                for (sal_Int32 j = 0; j < rSeq.getLength(); 
j++) {
+                                    if(!rSeq[j].isEmpty()) {
+                                        pFS->startElement(FSNS(XML_cx, 
XML_pt), XML_idx, OString::number(j));
+                                        pFS->writeEscaped(rSeq[j]);
+                                        pFS->endElement(FSNS(XML_cx, XML_pt));
+                                    }
+                                }
+                                pFS->endElement(FSNS(XML_cx, XML_lvl));
+                            }
+
+                            pFS->endElement(FSNS(XML_cx, XML_strDim));
+                        }
+                        else
+#endif
+                        {
+                            // export single category axis labels
+                            // TODO: seems like this should consider 
mbHasCategoryLabels
+                            bool bWriteDateCategories = mbHasDateCategories;
+                            OUString aNumberFormatString;
+                            if (bWriteDateCategories)
+                            {
+                                Reference< css::chart::XAxisXSupplier > 
xAxisXSupp( mxDiagram, uno::UNO_QUERY );
+                                if( xAxisXSupp.is())
+                                {
+                                    Reference< XPropertySet > xAxisProp = 
xAxisXSupp->getXAxis();
+                                    if (GetProperty(xAxisProp, 
u"NumberFormat"_ustr))
+                                    {
+                                        sal_Int32 nKey = 0;
+                                        mAny >>= nKey;
+                                        aNumberFormatString = 
getNumberFormatCode(nKey);
+                                    }
+                                }
+                                if (aNumberFormatString.isEmpty()) 
bWriteDateCategories = false;
+                            }
+
+                            // === Output the categories
+                            if (bWriteDateCategories)
+                            {
+                                std::vector<double> aDateCategories = 
lcl_getAllValuesFromSequence(xValueSeq);
+                                const sal_Int32 ptCount = 
aDateCategories.size();
+
+                                pFS->startElement(FSNS(XML_cx, XML_numDim), 
XML_type, "x"); // is "x" right?
+                                // TODO: check this
+
+                                pFS->startElement(FSNS(XML_cx, XML_f));
+                                pFS->writeEscaped(aCellRange);
+                                pFS->endElement(FSNS(XML_cx, XML_f));
+
+                                pFS->startElement(FSNS(XML_cx, XML_lvl),
+                                        XML_ptCount, OString::number(ptCount),
+                                        XML_formatCode, aNumberFormatString);
+
+                                for (sal_Int32 i = 0; i < ptCount; i++) {
+                                    if (!std::isnan(aDateCategories[i])) {
+                                        pFS->startElement(FSNS(XML_cx, 
XML_pt), XML_idx, OString::number(i));
+                                        
pFS->write(OString::number(aDateCategories[i]));
+                                        pFS->endElement(FSNS(XML_cx, XML_pt));
+                                    }
+                                }
+
+                                pFS->endElement(FSNS(XML_cx, XML_lvl));
+                                pFS->endElement(FSNS(XML_cx, XML_numDim));
+                            }
+                            else
+                            {
+                                std::vector<OUString> aCategories;
+                                lcl_fillCategoriesIntoStringVector(xValueSeq, 
aCategories);
+                                const sal_Int32 ptCount = aCategories.size();
+
+                                // TODO: shouldn't have "cat" hard-coded here:
+                                // other options are colorStr, entityId
+                                pFS->startElement(FSNS(XML_cx, XML_strDim), 
XML_type, "cat");
+
+                                pFS->startElement(FSNS(XML_cx, XML_f));
+                                pFS->writeEscaped(aCellRange);
+                                pFS->endElement(FSNS(XML_cx, XML_f));
+
+                                pFS->startElement(FSNS(XML_cx, XML_lvl), 
XML_ptCount, OString::number(ptCount));
+
+                                for (sal_Int32 i = 0; i < ptCount; i++) {
+                                    pFS->startElement(FSNS(XML_cx, XML_pt), 
XML_idx, OString::number(i));
+                                    pFS->writeEscaped(aCategories[i]);
+                                    pFS->endElement(FSNS(XML_cx, XML_pt));
+                                }
+
+                                pFS->endElement(FSNS(XML_cx, XML_lvl));
+                                pFS->endElement(FSNS(XML_cx, XML_strDim));
+                            }
+
+                            // === Output the values
+                            pFS->startElement(FSNS(XML_cx, XML_numDim), 
XML_type, "val");
+
+                            aCellRange = xValueSeq.is() ? 
xValueSeq->getSourceRangeRepresentation() : OUString();
+                            aCellRange = parseFormula( aCellRange );
+                            // TODO: need to handle XML_multiLvlStrRef 
according to aCellRange
+
+                            pFS->startElement(FSNS(XML_cx, XML_f));
+                            pFS->writeEscaped( aCellRange );
+                            pFS->endElement( FSNS( XML_cx, XML_f ) );
+
+                            ::std::vector< double > aValues = 
lcl_getAllValuesFromSequence( xValueSeq );
+                            sal_Int32 ptCount = aValues.size();
+                            OUString sNumberFormatString(u"General"_ustr);
+                            const sal_Int32 nKey = xValueSeq.is() ? 
xValueSeq->getNumberFormatKeyByIndex(-1) : 0;
+                            if (nKey > 0) {
+                                sNumberFormatString = 
getNumberFormatCode(nKey);
+                            }
+                            pFS->startElement(FSNS(XML_cx, XML_lvl),
+                                    XML_ptCount, OString::number(ptCount),
+                                    XML_formatCode,  sNumberFormatString);
+
+                            for( sal_Int32 i = 0; i < ptCount; i++ ) {
+
+                                pFS->startElement(FSNS(XML_cx, XML_pt), 
XML_idx, OString::number(i));
+                                pFS->write(std::isnan(aValues[i]) ?  0 : 
aValues[i]);
+                                pFS->endElement(FSNS(XML_cx, XML_pt));
+                            }
+
+                            pFS->endElement(FSNS(XML_cx, XML_lvl));
+                            pFS->endElement(FSNS(XML_cx, XML_numDim));
+                        }
+                    }
+                    pFS->endElement(FSNS(XML_cx, XML_data));
+                }
+            }
+        }
     }
 }
 
@@ -2023,63 +2292,6 @@ void ChartExport::exportTitle( const Reference< XShape 
>& xShape, bool bIsCharte
     }
 }
 
-namespace {
-
-    std::vector<Sequence<Reference<chart2::XDataSeries> > > 
splitDataSeriesByAxis(const Reference< chart2::XChartType >& xChartType)
-    {
-        std::vector<Sequence<Reference<chart2::XDataSeries> > > aSplitSeries;
-        std::map<sal_Int32, size_t> aMapAxisToIndex;
-
-        Reference< chart2::XDataSeriesContainer > xDSCnt(xChartType, 
uno::UNO_QUERY);
-        if (xDSCnt.is())
-        {
-            sal_Int32 nAxisIndexOfFirstSeries = -1;
-            const Sequence< Reference< chart2::XDataSeries > > 
aSeriesSeq(xDSCnt->getDataSeries());
-            for (const uno::Reference<chart2::XDataSeries>& xSeries : 
aSeriesSeq)
-            {
-                Reference<beans::XPropertySet> xPropSet(xSeries, 
uno::UNO_QUERY);
-                if (!xPropSet.is())
-                    continue;
-
-                sal_Int32 nAxisIndex = -1;
-                uno::Any aAny = 
xPropSet->getPropertyValue(u"AttachedAxisIndex"_ustr);
-                aAny >>= nAxisIndex;
-                size_t nVectorPos = 0;
-                if (nAxisIndexOfFirstSeries == -1)
-                {
-                    nAxisIndexOfFirstSeries = nAxisIndex;
-                }
-
-                auto it = aMapAxisToIndex.find(nAxisIndex);
-                if (it == aMapAxisToIndex.end())
-                {
-                    aSplitSeries.emplace_back();
-                    nVectorPos = aSplitSeries.size() - 1;
-                    aMapAxisToIndex.insert(std::pair<sal_Int32, 
size_t>(nAxisIndex, nVectorPos));
-                }
-                else
-                {
-                    nVectorPos = it->second;
-                }
-
-                uno::Sequence<Reference<chart2::XDataSeries> >& rAxisSeriesSeq 
= aSplitSeries[nVectorPos];
-                sal_Int32 nLength = rAxisSeriesSeq.getLength();
-                rAxisSeriesSeq.realloc(nLength + 1);
-                rAxisSeriesSeq.getArray()[nLength] = xSeries;
-            }
-            // if the first series attached to secondary axis, then export 
those series first, which are attached to primary axis
-            // also the MS Office export every time in this order
-            if (aSplitSeries.size() > 1 && nAxisIndexOfFirstSeries == 1)
-            {
-                std::swap(aSplitSeries[0], aSplitSeries[1]);
-            }
-        }
-
-        return aSplitSeries;
-    }
-
-}
-
 void ChartExport::exportPlotArea(const Reference< css::chart::XChartDocument 
>& xChartDoc,
         bool bIsChartex)
 {
@@ -2635,8 +2847,9 @@ void ChartExport::exportAreaChart( const Reference< 
chart2::XChartType >& xChart
 
         exportGrouping();
         bool bPrimaryAxes = true;
-        exportSeries(xChartType, splitDataSeries, bPrimaryAxes, false);
+        exportSeries_chart(xChartType, splitDataSeries, bPrimaryAxes);
         createAxes(bPrimaryAxes, false);
+        //exportAxesId(bPrimaryAxes);
 
         pFS->endElement(FSNS(XML_c, nTypeId));
     }
@@ -2670,7 +2883,7 @@ void ChartExport::exportBarChart(const Reference< 
chart2::XChartType >& xChartTy
         exportVaryColors(xChartType);
 
         bool bPrimaryAxes = true;
-        exportSeries(xChartType, splitDataSeries, bPrimaryAxes, false);
+        exportSeries_chart(xChartType, splitDataSeries, bPrimaryAxes);
 
         Reference< XPropertySet > xTypeProp(xChartType, uno::UNO_QUERY);
 
@@ -2756,7 +2969,7 @@ void ChartExport::exportBubbleChart( const Reference< 
chart2::XChartType >& xCha
         exportVaryColors(xChartType);
 
         bool bPrimaryAxes = true;
-        exportSeries(xChartType, splitDataSeries, bPrimaryAxes, false);
+        exportSeries_chart(xChartType, splitDataSeries, bPrimaryAxes);
 
         createAxes(bPrimaryAxes, false);
 
@@ -2773,14 +2986,9 @@ void ChartExport::exportFunnelChart( const Reference< 
chart2::XChartType >& xCha
         if (!splitDataSeries.hasElements())
             continue;
 
-        pFS->startElement(FSNS(XML_cx, XML_series), XML_layoutId, "funnel");
-
         //exportVaryColors(xChartType);
 
-        bool bPrimaryAxes = false;
-        exportSeries(xChartType, splitDataSeries, bPrimaryAxes, true);
-
-        pFS->endElement(FSNS(XML_cx, XML_series));
+        exportSeries_chartex(xChartType, splitDataSeries, "funnel");
     }
 }
 
@@ -2878,7 +3086,7 @@ void ChartExport::exportLineChart( const Reference< 
chart2::XChartType >& xChart
         exportVaryColors(xChartType);
         // TODO: show marker symbol in series?
         bool bPrimaryAxes = true;
-        exportSeries(xChartType, splitDataSeries, bPrimaryAxes, false);
+        exportSeries_chart(xChartType, splitDataSeries, bPrimaryAxes);
 
         // show marker?
         sal_Int32 nSymbolType = css::chart::ChartSymbolType::NONE;
@@ -2968,8 +3176,9 @@ void ChartExport::exportScatterChartSeries( const 
Reference< chart2::XChartType
     // FIXME: should export xVal and yVal
     bool bPrimaryAxes = true;
     if (pSeries)
-        exportSeries(xChartType, *pSeries, bPrimaryAxes, false);
+        exportSeries_chart(xChartType, *pSeries, bPrimaryAxes);
     createAxes(bPrimaryAxes, false);
+    //exportAxesId(bPrimaryAxes);
 
     pFS->endElement( FSNS( XML_c, XML_scatterChart ) );
 }
@@ -3100,7 +3309,7 @@ void ChartExport::exportAllSeries(const 
Reference<chart2::XChartType>& xChartTyp
 
     // export dataseries for current chart-type
     Sequence< Reference< chart2::XDataSeries > > aSeriesSeq( 
xDSCnt->getDataSeries());
-    exportSeries(xChartType, aSeriesSeq, rPrimaryAxes, false);
+    exportSeries_chart(xChartType, aSeriesSeq, rPrimaryAxes);
 }
 
 void ChartExport::exportVaryColors(const Reference<chart2::XChartType>& 
xChartType)
@@ -3121,10 +3330,9 @@ void ChartExport::exportVaryColors(const 
Reference<chart2::XChartType>& xChartTy
     }
 }
 
-void ChartExport::exportSeries( const Reference<chart2::XChartType>& 
xChartType,
+void ChartExport::exportSeries_chart( const Reference<chart2::XChartType>& 
xChartType,
         const Sequence<Reference<chart2::XDataSeries> >& rSeriesSeq,
-        bool& rPrimaryAxes,
-        bool bIsChartex)
+        bool& rPrimaryAxes)
 {
     OUString aLabelRole = xChartType->getRoleOfSequenceForSeriesLabel();
     OUString aChartType( xChartType->getChartType());
@@ -3172,19 +3380,17 @@ void ChartExport::exportSeries( const 
Reference<chart2::XChartType>& xChartType,
                 // xLabelSeq contain those.  Otherwise both are empty
                 FSHelperPtr pFS = GetFS();
 
-                if (!bIsChartex) {
-                    pFS->startElement(FSNS(XML_c, XML_ser));
+                pFS->startElement(FSNS(XML_c, XML_ser));
 
-                    // TODO: idx and order
-                    pFS->singleElement( FSNS( XML_c, XML_idx ),
-                        XML_val, OString::number(mnSeriesCount) );
-                    pFS->singleElement( FSNS( XML_c, XML_order ),
-                        XML_val, OString::number(mnSeriesCount++) );
-                }
+                // TODO: idx and order
+                pFS->singleElement( FSNS( XML_c, XML_idx ),
+                    XML_val, OString::number(mnSeriesCount) );
+                pFS->singleElement( FSNS( XML_c, XML_order ),
+                    XML_val, OString::number(mnSeriesCount++) );
 
                 // export label
                 if( xLabelSeq.is() )
-                    exportSeriesText( xLabelSeq, bIsChartex );
+                    exportSeriesText( xLabelSeq, false );
 
                 Reference<XPropertySet> xPropSet(xDataSeries, UNO_QUERY_THROW);
                 if( GetProperty( xPropSet, u"AttachedAxisIndex"_ustr) )
@@ -3199,140 +3405,210 @@ void ChartExport::exportSeries( const 
Reference<chart2::XChartType>& xChartType,
                     rSeries, getModel() );
                 if( xOldPropSet.is() )
                 {
-                    exportShapeProps( xOldPropSet, bIsChartex );
+                    exportShapeProps( xOldPropSet, false );
                 }
 
-                if (!bIsChartex) {
-                    switch( eChartType )
+                switch( eChartType )
+                {
+                    case chart::TYPEID_BUBBLE:
+                    case chart::TYPEID_HORBAR:
+                    case chart::TYPEID_BAR:
                     {
-                        case chart::TYPEID_BUBBLE:
-                        case chart::TYPEID_HORBAR:
-                        case chart::TYPEID_BAR:
-                        {
-                            pFS->singleElement(FSNS(XML_c, 
XML_invertIfNegative), XML_val, "0");
-                        }
+                        pFS->singleElement(FSNS(XML_c, XML_invertIfNegative), 
XML_val, "0");
+                    }
+                    break;
+                    case chart::TYPEID_LINE:
+                    {
+                        exportMarker(xOldPropSet);
                         break;
-                        case chart::TYPEID_LINE:
-                        {
-                            exportMarker(xOldPropSet);
-                            break;
-                        }
-                        case chart::TYPEID_PIE:
-                        case chart::TYPEID_DOUGHNUT:
-                        {
-                            if( xOldPropSet.is() && GetProperty( xOldPropSet, 
u"SegmentOffset"_ustr) )
-                            {
-                                sal_Int32 nOffset = 0;
-                                mAny >>= nOffset;
-                                pFS->singleElement( FSNS( XML_c, XML_explosion 
),
-                                    XML_val, OString::number( nOffset ) );
-                            }
-                            break;
-                        }
-                        case chart::TYPEID_SCATTER:
-                        {
-                            exportMarker(xOldPropSet);
-                            break;
-                        }
-                        case chart::TYPEID_RADARLINE:
+                    }
+                    case chart::TYPEID_PIE:
+                    case chart::TYPEID_DOUGHNUT:
+                    {
+                        if( xOldPropSet.is() && GetProperty( xOldPropSet, 
u"SegmentOffset"_ustr) )
                         {
-                            exportMarker(xOldPropSet);
-                            break;
+                            sal_Int32 nOffset = 0;
+                            mAny >>= nOffset;
+                            pFS->singleElement( FSNS( XML_c, XML_explosion ),
+                                XML_val, OString::number( nOffset ) );
                         }
+                        break;
+                    }
+                    case chart::TYPEID_SCATTER:
+                    {
+                        exportMarker(xOldPropSet);
+                        break;
+                    }
+                    case chart::TYPEID_RADARLINE:
+                    {
+                        exportMarker(xOldPropSet);
+                        break;
                     }
-
-                    // export data points
-                    exportDataPoints( uno::Reference< beans::XPropertySet >( 
rSeries, uno::UNO_QUERY ), nSeriesLength, eChartType );
                 }
 
+                // export data points
+                exportDataPoints( uno::Reference< beans::XPropertySet >( 
rSeries, uno::UNO_QUERY ), nSeriesLength, eChartType );
+
                 DataLabelsRange aDLblsRange;
                 // export data labels
-                exportDataLabels(rSeries, nSeriesLength, eChartType, 
aDLblsRange, bIsChartex);
+                exportDataLabels(rSeries, nSeriesLength, eChartType, 
aDLblsRange, false);
 
-                if (!bIsChartex) {
-                    exportTrendlines( rSeries );
+                exportTrendlines( rSeries );
 
-                    if( eChartType != chart::TYPEID_PIE &&
-                            eChartType != chart::TYPEID_RADARLINE )
+                if( eChartType != chart::TYPEID_PIE &&
+                        eChartType != chart::TYPEID_RADARLINE )
+                {
+                    //export error bars here
+                    Reference< XPropertySet > xSeriesPropSet( xSource, 
uno::UNO_QUERY );
+                    Reference< XPropertySet > xErrorBarYProps;
+                    xSeriesPropSet->getPropertyValue(u"ErrorBarY"_ustr) >>= 
xErrorBarYProps;
+                    if(xErrorBarYProps.is())
+                        exportErrorBar(xErrorBarYProps, true);
+                    if (eChartType != chart::TYPEID_BAR &&
+                            eChartType != chart::TYPEID_HORBAR)
                     {
-                        //export error bars here
-                        Reference< XPropertySet > xSeriesPropSet( xSource, 
uno::UNO_QUERY );
-                        Reference< XPropertySet > xErrorBarYProps;
-                        xSeriesPropSet->getPropertyValue(u"ErrorBarY"_ustr) 
>>= xErrorBarYProps;
-                        if(xErrorBarYProps.is())
-                            exportErrorBar(xErrorBarYProps, true);
-                        if (eChartType != chart::TYPEID_BAR &&
-                                eChartType != chart::TYPEID_HORBAR)
-                        {
-                            Reference< XPropertySet > xErrorBarXProps;
-                            
xSeriesPropSet->getPropertyValue(u"ErrorBarX"_ustr) >>= xErrorBarXProps;
-                            if(xErrorBarXProps.is())
-                                exportErrorBar(xErrorBarXProps, false);
-                        }
+                        Reference< XPropertySet > xErrorBarXProps;
+                        xSeriesPropSet->getPropertyValue(u"ErrorBarX"_ustr) 
>>= xErrorBarXProps;
+                        if(xErrorBarXProps.is())
+                            exportErrorBar(xErrorBarXProps, false);
                     }
+                }
 
-                    // export categories
-                    if( eChartType != chart::TYPEID_SCATTER && eChartType != 
chart::TYPEID_BUBBLE && mxCategoriesValues.is() )
-                        exportSeriesCategory( mxCategoriesValues );
+                // export categories
+                if( eChartType != chart::TYPEID_SCATTER && eChartType != 
chart::TYPEID_BUBBLE && mxCategoriesValues.is() )
+                    exportSeriesCategory( mxCategoriesValues );
 
-                    if( (eChartType == chart::TYPEID_SCATTER)
-                        || (eChartType == chart::TYPEID_BUBBLE) )
+                if( (eChartType == chart::TYPEID_SCATTER)
+                    || (eChartType == chart::TYPEID_BUBBLE) )
+                {
+                    // export xVal
+                    Reference< chart2::data::XLabeledDataSequence > xSequence( 
lcl_getDataSequenceByRole( aSeqCnt, u"values-x"_ustr ) );
+                    if( xSequence.is() )
                     {
-                        // export xVal
-                        Reference< chart2::data::XLabeledDataSequence > 
xSequence( lcl_getDataSequenceByRole( aSeqCnt, u"values-x"_ustr ) );
-                        if( xSequence.is() )
-                        {
-                            Reference< chart2::data::XDataSequence > xValues( 
xSequence->getValues() );
-                            if( xValues.is() )
-                                exportSeriesValues( xValues, XML_xVal );
-                        }
-                        else if( mxCategoriesValues.is() )
-                            exportSeriesCategory( mxCategoriesValues, XML_xVal 
);
+                        Reference< chart2::data::XDataSequence > xValues( 
xSequence->getValues() );
+                        if( xValues.is() )
+                            exportSeriesValues( xValues, XML_xVal );
                     }
+                    else if( mxCategoriesValues.is() )
+                        exportSeriesCategory( mxCategoriesValues, XML_xVal );
+                }
 
-                    if( eChartType == chart::TYPEID_BUBBLE )
+                if( eChartType == chart::TYPEID_BUBBLE )
+                {
+                    // export yVal
+                    Reference< chart2::data::XLabeledDataSequence > xSequence( 
lcl_getDataSequenceByRole( aSeqCnt, u"values-y"_ustr ) );
+                    if( xSequence.is() )
                     {
-                        // export yVal
-                        Reference< chart2::data::XLabeledDataSequence > 
xSequence( lcl_getDataSequenceByRole( aSeqCnt, u"values-y"_ustr ) );
-                        if( xSequence.is() )
-                        {
-                            Reference< chart2::data::XDataSequence > xValues( 
xSequence->getValues() );
-                            if( xValues.is() )
-                                exportSeriesValues( xValues, XML_yVal );
-                        }
+                        Reference< chart2::data::XDataSequence > xValues( 
xSequence->getValues() );
+                        if( xValues.is() )
+                            exportSeriesValues( xValues, XML_yVal );
                     }
+                }
 
-                    // export values
-                    if( xValuesSeq.is() )
-                    {
-                        sal_Int32 nYValueType = XML_val;
-                        if( eChartType == chart::TYPEID_SCATTER )
-                            nYValueType = XML_yVal;
-                        else if( eChartType == chart::TYPEID_BUBBLE )
-                            nYValueType = XML_bubbleSize;
-                        exportSeriesValues( xValuesSeq, nYValueType );
-                    }
+                // export values
+                if( xValuesSeq.is() )
+                {
+                    sal_Int32 nYValueType = XML_val;
+                    if( eChartType == chart::TYPEID_SCATTER )
+                        nYValueType = XML_yVal;
+                    else if( eChartType == chart::TYPEID_BUBBLE )
+                        nYValueType = XML_bubbleSize;
+                    exportSeriesValues( xValuesSeq, nYValueType );
+                }
 
-                    if( eChartType == chart::TYPEID_SCATTER
-                            || eChartType == chart::TYPEID_LINE )
-                        exportSmooth();
+                if( eChartType == chart::TYPEID_SCATTER
+                        || eChartType == chart::TYPEID_LINE )
+                    exportSmooth();
 
-                    // tdf103988: "corrupted" files with Bubble chart opening 
in MSO
-                    if( eChartType == chart::TYPEID_BUBBLE )
-                        pFS->singleElement(FSNS(XML_c, XML_bubble3D), XML_val, 
"0");
+                // tdf103988: "corrupted" files with Bubble chart opening in 
MSO
+                if( eChartType == chart::TYPEID_BUBBLE )
+                    pFS->singleElement(FSNS(XML_c, XML_bubble3D), XML_val, 
"0");
 
-                    if (!aDLblsRange.empty())
-                        writeDataLabelsRange(pFS, GetFB(), aDLblsRange);
+                if (!aDLblsRange.empty())
+                    writeDataLabelsRange(pFS, GetFB(), aDLblsRange);
 
-                    pFS->endElement( FSNS( XML_c, XML_ser ) );
-                } else {
-                    // chartex
+                pFS->endElement( FSNS( XML_c, XML_ser ) );
+            }
+        }
+    }
+}
 
-                    // Align the data id here with that in exportData().
-                    // See DATA_ID_COMMENT
-                    pFS->singleElement(FSNS(XML_cx, XML_dataId), XML_val, "0");
+void ChartExport::exportSeries_chartex( const Reference<chart2::XChartType>& 
xChartType,
+        const Sequence<Reference<chart2::XDataSeries> >& rSeriesSeq,
+        const char* sTypeName)
+{
+    OUString aLabelRole = xChartType->getRoleOfSequenceForSeriesLabel();
+    OUString aChartType( xChartType->getChartType());
+    sal_Int32 eChartType = lcl_getChartType( aChartType );
+
+    sal_Int32 nSeriesCnt = 0;
+    for( const auto& rSeries : rSeriesSeq )
+    {
+        // export series
+        Reference< chart2::data::XDataSource > xSource( rSeries, 
uno::UNO_QUERY );
+        if( xSource.is())
+        {
+            FSHelperPtr pFS = GetFS();
+            pFS->startElement(FSNS(XML_cx, XML_series), XML_layoutId, 
sTypeName);
+
+            Reference< chart2::XDataSeries > xDataSeries( xSource, 
uno::UNO_QUERY );
+            Sequence< Reference< chart2::data::XLabeledDataSequence > > 
aSeqCnt(
+                xSource->getDataSequences());
+
+            // search for main sequence and create a series element
+            sal_Int32 nMainSequenceIndex = -1;
+            sal_Int32 nSeriesLength = 0;
+            Reference< chart2::data::XDataSequence > xLabelSeq;
+            sal_Int32 nSeqIdx=0;
+            for( ; nSeqIdx<aSeqCnt.getLength(); ++nSeqIdx )
+            {
+                Reference< chart2::data::XDataSequence > xTempValueSeq( 
aSeqCnt[nSeqIdx]->getValues() );
+                if( nMainSequenceIndex==-1 )
+                {
+                    Reference< beans::XPropertySet > xSeqProp( xTempValueSeq, 
uno::UNO_QUERY );
+                    OUString aRole;
+                    if( xSeqProp.is())
+                        xSeqProp->getPropertyValue(u"Role"_ustr) >>= aRole;
+                    // "main" sequence
+                    if( aRole == aLabelRole )
+                    {
+                        xLabelSeq.set( aSeqCnt[nSeqIdx]->getLabel());
+                        nMainSequenceIndex = nSeqIdx;
+                    }
                 }
+                sal_Int32 nSequenceLength = (xTempValueSeq.is()? 
xTempValueSeq->getData().getLength() : sal_Int32(0));
+                if( nSeriesLength < nSequenceLength )
+                    nSeriesLength = nSequenceLength;
+            }
+
+            // export label
+            if( xLabelSeq.is() )
+                exportSeriesText( xLabelSeq, true );
+
+            // export shape properties
+            Reference< XPropertySet > xOldPropSet = 
SchXMLSeriesHelper::createOldAPISeriesPropertySet(
+                rSeries, getModel() );
+            if( xOldPropSet.is() )
+            {
+                exportShapeProps( xOldPropSet, true );
             }
+
+            DataLabelsRange aDLblsRange;
+            // export data labels
+            exportDataLabels(rSeries, nSeriesLength, eChartType, aDLblsRange, 
true);
+
+            // dataId links to the correct data set in the <cx:chartData>. See
+            // DATA_ID_COMMENT
+            pFS->singleElement(FSNS(XML_cx, XML_dataId), XML_val,
+                    OString::number(nSeriesCnt++));
+
+            // layoutPr
+
+            // axisId
+
+            // extLst
+
+            pFS->endElement(FSNS(XML_cx, XML_series));
         }
     }
 }
@@ -3687,7 +3963,8 @@ void ChartExport::InitPlotArea( )
 
     if( mbHasCategoryLabels && mxNewDiagram.is())
     {
-        Reference< chart2::data::XLabeledDataSequence > xCategories( 
lcl_getCategories( mxNewDiagram, mbHasDateCategories ) );
+        Reference< chart2::data::XLabeledDataSequence > xCategories(
+                lcl_getCategories( mxNewDiagram, &mbHasDateCategories ) );
         if( xCategories.is() )
         {
             mxCategoriesValues.set( xCategories->getValues() );
diff --git a/test/source/xmltesttools.cxx b/test/source/xmltesttools.cxx
index 29ae85e1403e..123cb90dc5ec 100644
--- a/test/source/xmltesttools.cxx
+++ b/test/source/xmltesttools.cxx
@@ -88,6 +88,11 @@ void XmlTestTools::registerNamespaces(xmlXPathContextPtr& 
pXmlXpathCtx)
 }
 
 OUString XmlTestTools::getXPath(const xmlDocUniquePtr& pXmlDoc, const char* 
pXPath, const char* pAttribute)
+{
+    return getXPath(pXmlDoc, pXPath, 1, 0, pAttribute);
+}
+
+OUString XmlTestTools::getXPath(const xmlDocUniquePtr& pXmlDoc, const 
char*pXPath, int nNumNodes, int nPathIdx, const char* pAttribute)
 {
     CPPUNIT_ASSERT(pXmlDoc);
     xmlXPathObjectPtr pXmlObj = getXPathNode(pXmlDoc, pXPath);
@@ -96,10 +101,10 @@ OUString XmlTestTools::getXPath(const xmlDocUniquePtr& 
pXmlDoc, const char* pXPa
     xmlNodeSetPtr pXmlNodes = pXmlObj->nodesetval;
     CPPUNIT_ASSERT_MESSAGE(OString(docAndXPath + "' not found").getStr(), 
pXmlNodes);
     CPPUNIT_ASSERT_EQUAL_MESSAGE(OString(docAndXPath + "' number of nodes is 
incorrect").getStr(),
-                                 1, xmlXPathNodeSetGetLength(pXmlNodes));
+                                 nNumNodes, 
xmlXPathNodeSetGetLength(pXmlNodes));
     CPPUNIT_ASSERT(pAttribute);
     CPPUNIT_ASSERT(pAttribute[0]);
-    xmlNodePtr pXmlNode = pXmlNodes->nodeTab[0];
+    xmlNodePtr pXmlNode = pXmlNodes->nodeTab[nPathIdx];
     xmlChar * prop = xmlGetProp(pXmlNode, BAD_CAST(pAttribute));
     OString sAttAbsent = docAndXPath + "' no attribute '" + pAttribute + "' 
exist";
     CPPUNIT_ASSERT_MESSAGE(sAttAbsent.getStr(), prop);
@@ -169,6 +174,16 @@ void XmlTestTools::assertXPath(const xmlDocUniquePtr& 
pXmlDoc, const char* pXPat
                                  rExpectedValue, std::u16string_view(aValue));
 }
 
+// Verify the given path and attribute, where the expected number of nodes with
+// the given path is nNumNodes and the desired node index is nPathIdx.
+void XmlTestTools::assertXPath(const xmlDocUniquePtr& pXmlDoc, const char*
+        pXPath, int nNumNodes, int nPathIdx, const char* pAttribute, 
std::u16string_view rExpectedValue)
+{
+    OUString aValue = getXPath(pXmlDoc, pXPath, nNumNodes, nPathIdx, 
pAttribute);
+    CPPUNIT_ASSERT_EQUAL_MESSAGE(OString(OString::Concat("In <") + 
pXmlDoc->name + ">, attribute '" + pAttribute + "' of '" + pXPath + "' 
incorrect value.").getStr(),
+                                 rExpectedValue, std::u16string_view(aValue));
+}
+
 void XmlTestTools::assertXPathDoubleValue(const xmlDocUniquePtr& pXmlDoc, 
const char* pXPath, const char* pAttribute, double expectedValue, double delta)
 {
     OUString aValue = getXPath(pXmlDoc, pXPath, pAttribute);

Reply via email to