chart2/qa/extras/chart2export2.cxx                          |  125 ++++++++++-
 chart2/qa/extras/data/xlsx/tdf143942.xlsx                   |binary
 chart2/source/model/main/FormattedString.cxx                |   34 +++
 chart2/source/model/main/FormattedString.hxx                |    6 
 chart2/source/view/charttypes/VSeriesPlotter.cxx            |    9 
 include/oox/export/chartexport.hxx                          |   40 +++
 include/xmloff/xmltoken.hxx                                 |    2 
 offapi/com/sun/star/chart2/XDataPointCustomLabelField.idl   |   28 ++
 oox/inc/drawingml/chart/datasourcecontext.hxx               |    1 
 oox/inc/drawingml/chart/seriesmodel.hxx                     |   15 +
 oox/source/drawingml/chart/datasourcecontext.cxx            |   21 +
 oox/source/drawingml/chart/seriescontext.cxx                |   55 ++++
 oox/source/drawingml/chart/seriesconverter.cxx              |   30 ++
 oox/source/drawingml/chart/seriesmodel.cxx                  |    4 
 oox/source/export/chartexport.cxx                           |  133 +++++++++++-
 oox/source/token/tokens.txt                                 |    3 
 schema/libreoffice/OpenDocument-v1.3+libreoffice-schema.rng |   12 +
 xmloff/source/chart/SchXMLExport.cxx                        |   64 ++++-
 xmloff/source/chart/SchXMLPlotAreaContext.cxx               |   16 +
 xmloff/source/chart/SchXMLPlotAreaContext.hxx               |    4 
 xmloff/source/chart/SchXMLSeries2Context.cxx                |   21 +
 xmloff/source/chart/transporttypes.hxx                      |   20 +
 xmloff/source/core/xmltoken.cxx                             |    2 
 xmloff/source/token/tokens.txt                              |    2 
 24 files changed, 588 insertions(+), 59 deletions(-)

New commits:
commit 9d8324524bdcd1244cd6e9d93b063b981d47c9be
Author:     Dennis Francis <dennis.fran...@collabora.com>
AuthorDate: Wed Aug 25 20:51:56 2021 +0530
Commit:     Miklos Vajna <vmik...@collabora.com>
CommitDate: Wed Sep 1 09:15:03 2021 +0200

    tdf#143942: oox: import/export labels from <c15:datalabelsRange>
    
    When <c15:showDataLabelsRange> boolean flag is present, the imported
    label texts are added as the first text field in oox data label model.
    The cell-range associated is also preserved. The export part preserves
    the how labels were store originally in <c15:datalabelsRange>.
    
    However in order to make the custom labels reflect the contents of the
    cells in the associated cell-range, more work needs to be done. For this
    the labels present in <c15:datalabelsRange> needs to be made available
    as a data-sequence with a new "role" like "point-labels" in
    XInternalDataProvider implementation and and make the label renderer
    read this data source rather than consulting the custom label fields
    property which is static after import.
    
    Change-Id: Ibc7045fa5ea209d463680c96efb49a06662d2500
    Reviewed-on: https://gerrit.libreoffice.org/c/core/+/121313
    Tested-by: Jenkins
    Reviewed-by: Miklos Vajna <vmik...@collabora.com>

diff --git a/chart2/qa/extras/chart2export2.cxx 
b/chart2/qa/extras/chart2export2.cxx
index adde208d620c..3a1afef91bd6 100644
--- a/chart2/qa/extras/chart2export2.cxx
+++ b/chart2/qa/extras/chart2export2.cxx
@@ -98,6 +98,7 @@ public:
     void testCustomShapeText();
     void testuserShapesXLSX();
     void testNameRangeXLSX();
+    void testTdf143942();
 
     CPPUNIT_TEST_SUITE(Chart2ExportTest2);
     CPPUNIT_TEST(testSetSeriesToSecondaryAxisXLSX);
@@ -159,6 +160,7 @@ public:
     CPPUNIT_TEST(testCustomShapeText);
     CPPUNIT_TEST(testuserShapesXLSX);
     CPPUNIT_TEST(testNameRangeXLSX);
+    CPPUNIT_TEST(testTdf143942);
     CPPUNIT_TEST_SUITE_END();
 };
 
@@ -1334,22 +1336,60 @@ void Chart2ExportTest2::testTdf138204()
         
"/c:chartSpace/c:chart/c:plotArea/c:barChart/c:ser[1]/c:dLbls/c:dLbl/c:tx/c:rich/a:p/a:fld",
         "type", "CELLRANGE");
 
+    assertXPath(
+        pXmlDoc,
+        
"/c:chartSpace/c:chart/c:plotArea/c:barChart/c:ser[2]/c:dLbls/c:dLbl/c:tx/c:rich/a:p/a:fld",
+        "type", "CELLRANGE");
+
     Reference<chart2::XChartDocument> xChartDoc = getChartDocFromSheet(0, 
mxComponent);
     CPPUNIT_ASSERT(xChartDoc.is());
 
-    uno::Reference<chart2::XDataSeries> 
xDataSeries(getDataSeriesFromDoc(xChartDoc, 1));
-    CPPUNIT_ASSERT(xDataSeries.is());
+    struct CustomLabelsTestData
+    {
+        sal_Int32 nSeriesIdx;
+        sal_Int32 nNumFields;
+        // First field attributes.
+        chart2::DataPointCustomLabelFieldType eFieldType;
+        OUString aCellRange;
+        OUString aString;
+    };
+
+    const CustomLabelsTestData aTestEntries[2] = {
+        {
+            // series id of c:ser[1] is 0.
+            0, // nSeriesIdx
+            1, // nNumFields
+            
chart2::DataPointCustomLabelFieldType::DataPointCustomLabelFieldType_CELLRANGE,
+            "Munka1!$F$9", // aCellRange
+            "67,5%", // aString
+        },
+        {
+
+            // series id of c:ser[2] is 1.
+            1, // nSeriesIdx
+            1, // nNumFields
+            
chart2::DataPointCustomLabelFieldType::DataPointCustomLabelFieldType_CELLRANGE,
+            "Munka1!$G$9", // aCellRange
+            "32,3%", // aString
+        },
+    };
+
+    for (const auto& aTestEntry : aTestEntries)
+    {
+        uno::Reference<chart2::XDataSeries> xDataSeries(
+            getDataSeriesFromDoc(xChartDoc, aTestEntry.nSeriesIdx));
+        CPPUNIT_ASSERT(xDataSeries.is());
 
-    uno::Reference<beans::XPropertySet> xPropertySet;
-    uno::Sequence<uno::Reference<chart2::XDataPointCustomLabelField>> aFields;
-    xPropertySet.set(xDataSeries->getDataPointByIndex(0), uno::UNO_SET_THROW);
-    xPropertySet->getPropertyValue("CustomLabelFields") >>= aFields;
-    CPPUNIT_ASSERT_EQUAL(static_cast<sal_Int32>(1), aFields.getLength());
+        uno::Reference<beans::XPropertySet> xPropertySet;
+        uno::Sequence<uno::Reference<chart2::XDataPointCustomLabelField>> 
aFields;
+        xPropertySet.set(xDataSeries->getDataPointByIndex(0), 
uno::UNO_SET_THROW);
+        xPropertySet->getPropertyValue("CustomLabelFields") >>= aFields;
+        CPPUNIT_ASSERT_EQUAL(aTestEntry.nNumFields, aFields.getLength());
 
-    CPPUNIT_ASSERT_EQUAL(
-        
chart2::DataPointCustomLabelFieldType::DataPointCustomLabelFieldType_CELLRANGE,
-        aFields[0]->getFieldType());
-    //CPPUNIT_ASSERT_EQUAL(OUString("67.5%"), aFields[0]->getString()); TODO: 
Not implemented yet
+        CPPUNIT_ASSERT_EQUAL(aTestEntry.eFieldType, 
aFields[0]->getFieldType());
+        CPPUNIT_ASSERT_EQUAL(aTestEntry.aCellRange, 
aFields[0]->getCellRange());
+        CPPUNIT_ASSERT_EQUAL(aTestEntry.aString, aFields[0]->getString());
+    }
 }
 
 void Chart2ExportTest2::testTdf138181()
@@ -1437,6 +1477,69 @@ void Chart2ExportTest2::testNameRangeXLSX()
                        "[0]!series1");
 }
 
+void Chart2ExportTest2::testTdf143942()
+{
+    load(u"/chart2/qa/extras/data/xlsx/", "tdf143942.xlsx");
+    xmlDocUniquePtr pXmlDoc = parseExport("xl/charts/chart", "Calc Office Open 
XML");
+    CPPUNIT_ASSERT(pXmlDoc);
+
+    constexpr size_t nLabels = 4;
+    OUString aCellRange = "Sheet1!$A$2:$A$5";
+    OUString aLabels[nLabels] = {
+        "Test1",
+        "Test2",
+        "Tes3",
+        "Test4",
+    };
+
+    assertXPath(pXmlDoc, 
"/c:chartSpace/c:chart/c:plotArea/c:scatterChart/c:ser[1]/c:extLst/c:ext",
+                "uri", "{02D57815-91ED-43cb-92C2-25804820EDAC}");
+    assertXPath(pXmlDoc,
+                
"/c:chartSpace/c:chart/c:plotArea/c:scatterChart/c:ser[1]/c:extLst/c:ext/"
+                "c15:datalabelsRange/c15:dlblRangeCache/c:ptCount",
+                "val", "4");
+    assertXPathContent(pXmlDoc,
+                       
"/c:chartSpace/c:chart/c:plotArea/c:scatterChart/c:ser[1]/c:extLst/c:ext/"
+                       "c15:datalabelsRange/c15:f",
+                       aCellRange);
+    for (size_t i = 0; i < nLabels; ++i)
+    {
+        assertXPath(pXmlDoc,
+                    
"/c:chartSpace/c:chart/c:plotArea/c:scatterChart/c:ser[1]/c:dLbls/c:dLbl["
+                        + OString::number(i + 1) + "]/c:tx/c:rich/a:p/a:fld",
+                    "type", "CELLRANGE");
+        assertXPath(pXmlDoc,
+                    
"/c:chartSpace/c:chart/c:plotArea/c:scatterChart/c:ser[1]/c:dLbls/c:dLbl["
+                        + OString::number(i + 1) + 
"]/c:extLst/c:ext/c15:showDataLabelsRange",
+                    "val", "1");
+        // Check if the actual label is stored under c15:datalabelsRange
+        assertXPathContent(pXmlDoc,
+                           
"/c:chartSpace/c:chart/c:plotArea/c:scatterChart/c:ser[1]/c:extLst/"
+                           "c:ext/c15:datalabelsRange/c15:dlblRangeCache/c:pt["
+                               + OString::number(i + 1) + "]/c:v",
+                           aLabels[i]);
+    }
+
+    Reference<chart2::XChartDocument> xChartDoc = getChartDocFromSheet(0, 
mxComponent);
+    CPPUNIT_ASSERT(xChartDoc.is());
+    uno::Reference<chart2::XDataSeries> 
xDataSeries(getDataSeriesFromDoc(xChartDoc, 0));
+    CPPUNIT_ASSERT(xDataSeries.is());
+
+    uno::Reference<beans::XPropertySet> xPropertySet;
+    uno::Sequence<uno::Reference<chart2::XDataPointCustomLabelField>> aFields;
+    for (size_t i = 0; i < nLabels; ++i)
+    {
+        xPropertySet.set(xDataSeries->getDataPointByIndex(i), 
uno::UNO_SET_THROW);
+        xPropertySet->getPropertyValue("CustomLabelFields") >>= aFields;
+        CPPUNIT_ASSERT_EQUAL(static_cast<sal_Int32>(1), aFields.getLength());
+        CPPUNIT_ASSERT_EQUAL(
+            
chart2::DataPointCustomLabelFieldType::DataPointCustomLabelFieldType_CELLRANGE,
+            aFields[0]->getFieldType());
+        CPPUNIT_ASSERT_EQUAL(aCellRange, aFields[0]->getCellRange());
+        CPPUNIT_ASSERT_EQUAL(aLabels[i], aFields[0]->getString());
+    }
+}
+
 CPPUNIT_TEST_SUITE_REGISTRATION(Chart2ExportTest2);
 
 CPPUNIT_PLUGIN_IMPLEMENT();
diff --git a/chart2/qa/extras/data/xlsx/tdf143942.xlsx 
b/chart2/qa/extras/data/xlsx/tdf143942.xlsx
new file mode 100644
index 000000000000..33ff6696b7e6
Binary files /dev/null and b/chart2/qa/extras/data/xlsx/tdf143942.xlsx differ
diff --git a/chart2/source/model/main/FormattedString.cxx 
b/chart2/source/model/main/FormattedString.cxx
index 5433f3683d78..782729294789 100644
--- a/chart2/source/model/main/FormattedString.cxx
+++ b/chart2/source/model/main/FormattedString.cxx
@@ -98,6 +98,7 @@ namespace chart
 FormattedString::FormattedString() :
         ::property::OPropertySet( m_aMutex ),
     
m_aType(chart2::DataPointCustomLabelFieldType::DataPointCustomLabelFieldType_TEXT),
+    m_bDataLabelsRange(false),
     m_xModifyEventForwarder( 
ModifyListenerHelper::createModifyEventForwarder())
 {}
 
@@ -107,6 +108,7 @@ FormattedString::FormattedString( const FormattedString & 
rOther ) :
     m_aString( rOther.m_aString ),
     m_aType(rOther.m_aType),
     m_aGuid(rOther.m_aGuid),
+    m_bDataLabelsRange(rOther.m_bDataLabelsRange),
     m_xModifyEventForwarder( 
ModifyListenerHelper::createModifyEventForwarder())
 {}
 
@@ -172,6 +174,38 @@ void SAL_CALL FormattedString::setGuid( const OUString& 
guid )
 
 }
 
+sal_Bool SAL_CALL FormattedString::getDataLabelsRange()
+{
+    MutexGuard aGuard( m_aMutex);
+    return m_bDataLabelsRange;
+}
+
+void SAL_CALL FormattedString::setDataLabelsRange( sal_Bool dataLabelsRange )
+{
+    {
+        MutexGuard aGuard( m_aMutex);
+        m_bDataLabelsRange = dataLabelsRange;
+    }
+    //don't keep the mutex locked while calling out
+    fireModifyEvent();
+}
+
+OUString SAL_CALL FormattedString::getCellRange()
+{
+    MutexGuard aGuard( m_aMutex);
+    return m_aCellRange;
+}
+
+void SAL_CALL FormattedString::setCellRange( const OUString& cellRange )
+{
+    {
+        MutexGuard aGuard( m_aMutex);
+        m_aCellRange = cellRange;
+    }
+    //don't keep the mutex locked while calling out
+    fireModifyEvent();
+}
+
 // ____ XModifyBroadcaster ____
 void SAL_CALL FormattedString::addModifyListener( const uno::Reference< 
util::XModifyListener >& aListener )
 {
diff --git a/chart2/source/model/main/FormattedString.hxx 
b/chart2/source/model/main/FormattedString.hxx
index a7f9e36bb134..bd7415fb921a 100644
--- a/chart2/source/model/main/FormattedString.hxx
+++ b/chart2/source/model/main/FormattedString.hxx
@@ -87,6 +87,10 @@ private:
         setFieldType( const css::chart2::DataPointCustomLabelFieldType 
FieldType ) override;
     virtual OUString SAL_CALL getGuid() override;
     void SAL_CALL setGuid( const OUString& guid ) override;
+    virtual sal_Bool SAL_CALL getDataLabelsRange() override;
+    virtual void SAL_CALL setDataLabelsRange( sal_Bool dataLabelsRange ) 
override;
+    virtual OUString SAL_CALL getCellRange() override;
+    virtual void SAL_CALL setCellRange( const OUString& cellRange ) override;
 
     // ____ OPropertySet ____
     virtual css::uno::Any GetDefaultValue( sal_Int32 nHandle ) const override;
@@ -127,6 +131,8 @@ private:
     // ____ XDataPointCustomLabelField ____
     css::chart2::DataPointCustomLabelFieldType m_aType;
     OUString m_aGuid;
+    OUString m_aCellRange;
+    bool m_bDataLabelsRange;
 
     css::uno::Reference< css::util::XModifyListener > m_xModifyEventForwarder;
 };
diff --git a/chart2/source/view/charttypes/VSeriesPlotter.cxx 
b/chart2/source/view/charttypes/VSeriesPlotter.cxx
index b0a591d3c065..b5bfc8d871b0 100644
--- a/chart2/source/view/charttypes/VSeriesPlotter.cxx
+++ b/chart2/source/view/charttypes/VSeriesPlotter.cxx
@@ -540,8 +540,15 @@ uno::Reference< drawing::XShape > 
VSeriesPlotter::createDataLabel( const uno::Re
                         aTextList[i] = getLabelTextForValue(rDataSeries, 
nPointIndex, fValue, true);
                         break;
                     }
-                    case DataPointCustomLabelFieldType_CELLREF:
                     case DataPointCustomLabelFieldType_CELLRANGE:
+                    {
+                        if (aCustomLabels[i]->getDataLabelsRange())
+                            aTextList[i] = aCustomLabels[i]->getString();
+                        else
+                            aTextList[i] = OUString();
+                        break;
+                    }
+                    case DataPointCustomLabelFieldType_CELLREF:
                     {
                         // TODO: for now doesn't show placeholder
                         aTextList[i] = OUString();
diff --git a/include/oox/export/chartexport.hxx 
b/include/oox/export/chartexport.hxx
index 5dbf8c20ac0b..c4440ae08419 100644
--- a/include/oox/export/chartexport.hxx
+++ b/include/oox/export/chartexport.hxx
@@ -91,6 +91,43 @@ struct AxisIdPair{
     {}
 };
 
+/**
+ A helper container class to collect the chart data point labels and the 
address
+ of the cell[range] from which the labels are sourced if that is the case. This
+ is then used to write the label texts under the extension tag 
<c15:datalabelsRange>.
+
+ @since LibreOffice 7.3.0
+ */
+class DataLabelsRange
+{
+public:
+
+    /// type of the internal container that stores the indexed label text.
+    typedef std::map<sal_Int32, OUString> LabelsRangeMap;
+
+    /// Returns whether the container is empty or not.
+    bool empty() const;
+    /// Returns the count of labels stored.
+    size_t count() const;
+    /// Indicates whether the container has a label with index specified by 
nIndex.
+    bool hasLabel(sal_Int32 nIndex) const;
+    /// Returns the address of the cell[range] from which label contents are 
sourced.
+    OUString getRange() const;
+
+    /// Sets the address of the cell[range] from which label contents are 
sourced.
+    void setRange(const OUString& rRange);
+    /// Adds a new indexed label text.
+    void setLabel(sal_Int32 nIndex, const OUString& rText);
+
+    LabelsRangeMap::const_iterator begin() const;
+    LabelsRangeMap::const_iterator end() const;
+
+private:
+    OUString         maRange;
+    LabelsRangeMap   maLabels;
+};
+
+
 class OOX_DLLPUBLIC ChartExport final : public DrawingML {
 
 public:
@@ -184,7 +221,8 @@ private:
     void exportDataPoints(
         const css::uno::Reference< css::beans::XPropertySet >& 
xSeriesProperties,
         sal_Int32 nSeriesLength, sal_Int32 eChartType );
-    void exportDataLabels( const 
css::uno::Reference<css::chart2::XDataSeries>& xSeries, sal_Int32 
nSeriesLength, sal_Int32 eChartType );
+    void exportDataLabels( const 
css::uno::Reference<css::chart2::XDataSeries>& xSeries, sal_Int32 nSeriesLength,
+        sal_Int32 eChartType, DataLabelsRange& rDLblsRange );
     void exportGrouping( bool isBar = false );
     void exportTrendlines( const css::uno::Reference< css::chart2::XDataSeries 
>& xSeries );
     void exportMarker( const css::uno::Reference< css::beans::XPropertySet >& 
xPropSet );
diff --git a/include/xmloff/xmltoken.hxx b/include/xmloff/xmltoken.hxx
index 4458badec53d..a7a3fc17a4f6 100644
--- a/include/xmloff/xmltoken.hxx
+++ b/include/xmloff/xmltoken.hxx
@@ -576,10 +576,12 @@ namespace xmloff::token {
         XML_DATA_BAR_ENTRY,
         XML_DATA_CELL_RANGE_ADDRESS,
         XML_DATA_LABEL,
+        XML_DATA_LABEL_GUID,
         XML_DATA_LABEL_NUMBER,
         XML_DATA_LABEL_SYMBOL,
         XML_DATA_LABEL_TEXT,
         XML_DATA_LABEL_SERIES,
+        XML_DATA_LABELS_CELL_RANGE,
         XML_DATA_PILOT_SOURCE,
         XML_DATA_PILOT_FIELD,
         XML_DATA_PILOT_GRAND_TOTAL,
diff --git a/offapi/com/sun/star/chart2/XDataPointCustomLabelField.idl 
b/offapi/com/sun/star/chart2/XDataPointCustomLabelField.idl
index a6a1b0151c94..8291e9001c05 100644
--- a/offapi/com/sun/star/chart2/XDataPointCustomLabelField.idl
+++ b/offapi/com/sun/star/chart2/XDataPointCustomLabelField.idl
@@ -29,6 +29,34 @@ interface XDataPointCustomLabelField : XFormattedString2
 
     void setGuid( [in] string guid );
 
+    /**
+        Indicates whether the label field's content is sourced from a 
cell[range] or not.
+
+        @since LibreOffice 7.3
+    */
+    boolean getDataLabelsRange();
+
+    /**
+        Sets whether the label field's content is sourced from a cell[range] 
or not.
+
+        @since LibreOffice 7.3
+    */
+    void setDataLabelsRange( [in] boolean dataLabelsRange );
+
+    /**
+        Returns the address of the cell[range] from which the content of this 
field is sourced.
+
+        @since LibreOffice 7.3
+    */
+    string getCellRange();
+
+    /**
+        Sets the address of the cell[range] from which the content of this 
field is sourced.
+
+        @since LibreOffice 7.3
+    */
+    void setCellRange( [in] string cellRange );
+
 };
 
 
diff --git a/oox/inc/drawingml/chart/datasourcecontext.hxx 
b/oox/inc/drawingml/chart/datasourcecontext.hxx
index 8ce8469cc3cb..ec64c4768180 100644
--- a/oox/inc/drawingml/chart/datasourcecontext.hxx
+++ b/oox/inc/drawingml/chart/datasourcecontext.hxx
@@ -69,6 +69,7 @@ public:
 
 private:
     sal_Int32           mnPtIndex;          /// Current data point index.
+    bool                mbReadC15;          /// Allow reading extension tags 
data under c15 namespace.
 };
 
 
diff --git a/oox/inc/drawingml/chart/seriesmodel.hxx 
b/oox/inc/drawingml/chart/seriesmodel.hxx
index c4fb557d282d..460293a5feee 100644
--- a/oox/inc/drawingml/chart/seriesmodel.hxx
+++ b/oox/inc/drawingml/chart/seriesmodel.hxx
@@ -41,12 +41,18 @@ struct DataLabelModelBase
     OptValue< bool >    mobShowPercent;     /// True = show percentual value 
in pie/doughnut charts.
     OptValue< bool >    mobShowSerName;     /// True = show series name.
     OptValue< bool >    mobShowVal;         /// True = show data point value.
+
+    /// True = the value from the <c15:datalabelsRange> corresponding to the
+    /// index of this label is used as the label text.
+    OptValue< bool >    mobShowDataLabelsRange;
     bool                mbDeleted;          /// True = data label(s) deleted.
 
     explicit            DataLabelModelBase(bool bMSO2007Doc);
                         ~DataLabelModelBase();
 };
 
+struct DataLabelsModel;
+
 struct DataLabelModel : public DataLabelModelBase
 {
     typedef ModelRef< LayoutModel > LayoutRef;
@@ -54,9 +60,10 @@ struct DataLabelModel : public DataLabelModelBase
 
     LayoutRef           mxLayout;           /// Layout/position of the data 
point label frame.
     TextRef             mxText;             /// Manual or linked text for this 
data point label.
+    const DataLabelsModel&    mrParent;     /// Reference to the labels 
container.
     sal_Int32           mnIndex;            /// Data point index for this data 
label.
 
-    explicit            DataLabelModel(bool bMSO2007Doc);
+    explicit            DataLabelModel(const DataLabelsModel& rParent, bool 
bMSO2007Doc);
                         ~DataLabelModel();
 };
 
@@ -67,6 +74,9 @@ struct DataLabelsModel : public DataLabelModelBase
 
     DataLabelVector     maPointLabels;      /// Settings for individual data 
point labels.
     ShapeRef            mxLeaderLines;      /// Formatting of connector lines 
between data points and labels.
+
+    /// Labels source (owned by SeriesModel's DataSourceMap)
+    const DataSourceModel*  mpLabelsSource;
     bool                mbShowLeaderLines;  /// True = show connector lines 
between data points and labels.
 
     explicit            DataLabelsModel(bool bMSO2007Doc);
@@ -171,7 +181,8 @@ struct SeriesModel
     {
         CATEGORIES,         /// Data point categories.
         VALUES,             /// Data point values.
-        POINTS              /// Data point size (e.g. bubble size in bubble 
charts).
+        POINTS,             /// Data point size (e.g. bubble size in bubble 
charts).
+        DATALABELS,         /// Data point labels.
     };
 
     typedef ModelMap< SourceType, DataSourceModel > DataSourceMap;
diff --git a/oox/source/drawingml/chart/datasourcecontext.cxx 
b/oox/source/drawingml/chart/datasourcecontext.cxx
index eac776e107d9..1b64089d4281 100644
--- a/oox/source/drawingml/chart/datasourcecontext.cxx
+++ b/oox/source/drawingml/chart/datasourcecontext.cxx
@@ -166,6 +166,7 @@ SvNumberFormatter* 
DoubleSequenceContext::getNumberFormatter()
 StringSequenceContext::StringSequenceContext( ContextHandler2Helper& rParent, 
DataSequenceModel& rModel )
     : DataSequenceContextBase( rParent, rModel )
     , mnPtIndex(-1)
+    , mbReadC15(false)
 {
 }
 
@@ -186,6 +187,16 @@ ContextHandlerRef StringSequenceContext::onCreateContext( 
sal_Int32 nElement, co
             }
         break;
 
+        case C15_TOKEN( datalabelsRange ):
+            mbReadC15 = true;
+            switch( nElement )
+            {
+                case C15_TOKEN( f ):
+                case C15_TOKEN( dlblRangeCache ):
+                    return this;
+            }
+        break;
+
         case C_TOKEN( strRef ):
             switch( nElement )
             {
@@ -197,6 +208,10 @@ ContextHandlerRef StringSequenceContext::onCreateContext( 
sal_Int32 nElement, co
 
         case C_TOKEN( strCache ):
         case C_TOKEN( strLit ):
+        case C15_TOKEN( dlblRangeCache ):
+            if (nElement == C15_TOKEN( dlblRangeCache ) && !mbReadC15)
+                break;
+
             switch( nElement )
             {
                 case C_TOKEN( ptCount ):
@@ -248,6 +263,10 @@ void StringSequenceContext::onCharacters( const OUString& 
rChars )
         case C_TOKEN( f ):
             mrModel.maFormula = rChars;
         break;
+        case C15_TOKEN( f ):
+            if (mbReadC15)
+                mrModel.maFormula = rChars;
+        break;
         case C_TOKEN( v ):
             if( mnPtIndex >= 0 )
                 mrModel.maData[ (mrModel.mnLevelCount-1) * 
mrModel.mnPointCount + mnPtIndex ] <<= rChars;
@@ -270,11 +289,13 @@ ContextHandlerRef DataSourceContext::onCreateContext( 
sal_Int32 nElement, const
     {
         case C_TOKEN( cat ):
         case C_TOKEN( xVal ):
+        case C_TOKEN( ext ):
             switch( nElement )
             {
                 case C_TOKEN( multiLvlStrRef ):
                 case C_TOKEN( strLit ):
                 case C_TOKEN( strRef ):
+                case C15_TOKEN( datalabelsRange ):
                     OSL_ENSURE( !mrModel.mxDataSeq, 
"DataSourceContext::onCreateContext - multiple data sequences" );
                     return new StringSequenceContext( *this, 
mrModel.mxDataSeq.create() );
 
diff --git a/oox/source/drawingml/chart/seriescontext.cxx 
b/oox/source/drawingml/chart/seriescontext.cxx
index 3f92818c5544..5afc32c1497d 100644
--- a/oox/source/drawingml/chart/seriescontext.cxx
+++ b/oox/source/drawingml/chart/seriescontext.cxx
@@ -100,15 +100,37 @@ DataLabelContext::~DataLabelContext()
 
 ContextHandlerRef DataLabelContext::onCreateContext( sal_Int32 nElement, const 
AttributeList& rAttribs )
 {
-    if( isRootElement() ) switch( nElement )
+    if( isRootElement() )
     {
-        case C_TOKEN( idx ):
-            mrModel.mnIndex = rAttribs.getInteger( XML_val, -1 );
-            return nullptr;
-        case C_TOKEN( layout ):
-            return new LayoutContext( *this, mrModel.mxLayout.create() );
-        case C_TOKEN( tx ):
-            return new TextContext( *this, mrModel.mxText.create() );
+        switch( nElement )
+        {
+            case C_TOKEN( idx ):
+                mrModel.mnIndex = rAttribs.getInteger( XML_val, -1 );
+                return nullptr;
+            case C_TOKEN( layout ):
+                return new LayoutContext( *this, mrModel.mxLayout.create() );
+            case C_TOKEN( tx ):
+                return new TextContext( *this, mrModel.mxText.create() );
+            case C_TOKEN( extLst ):
+                return this;
+        }
+    }
+    else
+    {
+        switch( getCurrentElement() )
+        {
+            case C_TOKEN( extLst ):
+                if ( nElement == C_TOKEN( ext ) )
+                    return this;
+            break;
+            case C_TOKEN( ext ):
+                if ( nElement == C15_TOKEN( showDataLabelsRange ) )
+                {
+                    mrModel.mobShowDataLabelsRange = rAttribs.getBool( XML_val 
);
+                    return nullptr;
+                }
+            break;
+        }
     }
     bool bMSO2007 = getFilter().isMSO2007Document();
     return lclDataLabelSharedCreateContext( *this, nElement, rAttribs, 
mrModel, bMSO2007 );
@@ -135,7 +157,7 @@ ContextHandlerRef DataLabelsContext::onCreateContext( 
sal_Int32 nElement, const
     if( isRootElement() ) switch( nElement )
     {
         case C_TOKEN( dLbl ):
-            return new DataLabelContext( *this, 
mrModel.maPointLabels.create(bMSO2007Doc) );
+            return new DataLabelContext( *this, 
mrModel.maPointLabels.create(mrModel, bMSO2007Doc) );
         case C_TOKEN( leaderLines ):
             return new ShapePrWrapperContext( *this, 
mrModel.mxLeaderLines.create() );
         case C_TOKEN( showLeaderLines ):
@@ -390,6 +412,8 @@ ContextHandlerRef SeriesContextBase::onCreateContext( 
sal_Int32 nElement, const
                     return new ShapePropertiesContext( *this, 
mrModel.mxShapeProp.create() );
                 case C_TOKEN( tx ):
                     return new TextContext( *this, mrModel.mxText.create() );
+                case C_TOKEN( extLst ):
+                    return this;
             }
         break;
 
@@ -406,6 +430,19 @@ ContextHandlerRef SeriesContextBase::onCreateContext( 
sal_Int32 nElement, const
                     return nullptr;
             }
         break;
+
+        case C_TOKEN( extLst ):
+            switch( nElement )
+            {
+                case C_TOKEN( ext ):
+                    if (mrModel.maSources.has( SeriesModel::DATALABELS ))
+                        break;
+
+                    DataSourceModel& rLabelsSource = mrModel.maSources.create( 
SeriesModel::DATALABELS );
+                    if (mrModel.mxLabels.is())
+                        mrModel.mxLabels->mpLabelsSource = &rLabelsSource;
+                    return new DataSourceContext( *this, rLabelsSource );
+            }
     }
     return nullptr;
 }
diff --git a/oox/source/drawingml/chart/seriesconverter.cxx 
b/oox/source/drawingml/chart/seriesconverter.cxx
index 2c3341a30e1a..3f03e1e5c2a6 100644
--- a/oox/source/drawingml/chart/seriesconverter.cxx
+++ b/oox/source/drawingml/chart/seriesconverter.cxx
@@ -319,9 +319,25 @@ void DataLabelConverter::convertFromModel( const 
Reference< XDataSeries >& rxDat
             if( nParagraphs > 1 )
                 nSequenceSize += nParagraphs - 1;
 
+            OptValue< OUString > oaLabelText;
+            OptValue< OUString > oaCellRange;
+            if (mrModel.mobShowDataLabelsRange.get(false))
+            {
+                const DataSourceModel* pLabelSource = 
mrModel.mrParent.mpLabelsSource;
+                if (pLabelSource && pLabelSource->mxDataSeq.is())
+                {
+                    oaCellRange = pLabelSource->mxDataSeq->maFormula;
+                    const auto& rLabelMap = pLabelSource->mxDataSeq->maData;
+                    const auto& rKV = rLabelMap.find(mrModel.mnIndex);
+                    if (rKV != rLabelMap.end())
+                        rKV->second >>= oaLabelText.use();
+                }
+            }
+
             aSequence.realloc( nSequenceSize );
 
             int nPos = 0;
+
             for( auto& pParagraph : rParagraphs )
             {
                 for( auto& pRun : pParagraph->getRuns() )
@@ -336,8 +352,18 @@ void DataLabelConverter::convertFromModel( const 
Reference< XDataSeries >& rxDat
                     TextField* pField = nullptr;
                     if( ( pField = dynamic_cast< TextField* >( pRun.get() ) ) )
                     {
-                        xCustomLabel->setString( pField->getText() );
-                        xCustomLabel->setFieldType( 
lcl_ConvertFieldNameToFieldEnum( pField->getType() ) );
+                        DataPointCustomLabelFieldType eType = 
lcl_ConvertFieldNameToFieldEnum( pField->getType() );
+
+                        if (eType == 
DataPointCustomLabelFieldType::DataPointCustomLabelFieldType_CELLRANGE && 
oaCellRange.has())
+                        {
+                            xCustomLabel->setCellRange( oaCellRange.get() );
+                            xCustomLabel->setString( oaLabelText.get() );
+                            xCustomLabel->setDataLabelsRange( true );
+                        }
+                        else
+                            xCustomLabel->setString( pField->getText() );
+
+                        xCustomLabel->setFieldType( eType );
                         xCustomLabel->setGuid( pField->getUuid() );
                     }
                     else if( pRun )
diff --git a/oox/source/drawingml/chart/seriesmodel.cxx 
b/oox/source/drawingml/chart/seriesmodel.cxx
index 563e0e48e3bf..4ad9b2593af4 100644
--- a/oox/source/drawingml/chart/seriesmodel.cxx
+++ b/oox/source/drawingml/chart/seriesmodel.cxx
@@ -31,8 +31,9 @@ DataLabelModelBase::~DataLabelModelBase()
 {
 }
 
-DataLabelModel::DataLabelModel(bool bMSO2007Doc) :
+DataLabelModel::DataLabelModel(const DataLabelsModel& rParent, bool 
bMSO2007Doc) :
     DataLabelModelBase(bMSO2007Doc),
+    mrParent( rParent ),
     mnIndex( -1 )
 {
 }
@@ -43,6 +44,7 @@ DataLabelModel::~DataLabelModel()
 
 DataLabelsModel::DataLabelsModel(bool bMSO2007Doc) :
     DataLabelModelBase(bMSO2007Doc),
+    mpLabelsSource( nullptr ),
     mbShowLeaderLines( !bMSO2007Doc )
 {
 }
diff --git a/oox/source/export/chartexport.cxx 
b/oox/source/export/chartexport.cxx
index dc122a4e97da..2e13b40288bb 100644
--- a/oox/source/export/chartexport.cxx
+++ b/oox/source/export/chartexport.cxx
@@ -485,6 +485,46 @@ static sal_Int32 
lcl_getAlphaFromTransparenceGradient(const awt::Gradient& rGrad
     return (255 - nRed) * oox::drawingml::MAX_PERCENT / 255;
 }
 
+bool DataLabelsRange::empty() const
+{
+    return maLabels.empty();
+}
+
+size_t DataLabelsRange::count() const
+{
+    return maLabels.size();
+}
+
+bool DataLabelsRange::hasLabel(sal_Int32 nIndex) const
+{
+    return maLabels.find(nIndex) != maLabels.end();
+}
+
+OUString DataLabelsRange::getRange() const
+{
+    return maRange;
+}
+
+void DataLabelsRange::setRange(const OUString& rRange)
+{
+    maRange = rRange;
+}
+
+void DataLabelsRange::setLabel(sal_Int32 nIndex, const OUString& rText)
+{
+    maLabels.emplace(nIndex, rText);
+}
+
+DataLabelsRange::LabelsRangeMap::const_iterator DataLabelsRange::begin() const
+{
+    return maLabels.begin();
+}
+
+DataLabelsRange::LabelsRangeMap::const_iterator DataLabelsRange::end() const
+{
+    return maLabels.end();
+}
+
 ChartExport::ChartExport( sal_Int32 nXmlNamespace, FSHelperPtr pFS, Reference< 
frame::XModel > const & xModel, XmlFilterBase* pFB, DocumentType eDocumentType )
     : DrawingML( std::move(pFS), pFB, eDocumentType )
     , mnXmlNamespace( nXmlNamespace )
@@ -2183,6 +2223,43 @@ void ChartExport::exportDoughnutChart( const Reference< 
chart2::XChartType >& xC
     pFS->endElement( FSNS( XML_c, XML_doughnutChart ) );
 }
 
+namespace {
+
+void writeDataLabelsRange(FSHelperPtr& pFS, XmlFilterBase* pFB, 
DataLabelsRange& rDLblsRange)
+{
+    if (rDLblsRange.empty())
+        return;
+
+    pFS->startElement(FSNS(XML_c, XML_extLst));
+    pFS->startElement(FSNS(XML_c, XML_ext), XML_uri, 
"{02D57815-91ED-43cb-92C2-25804820EDAC}", FSNS(XML_xmlns, XML_c15), 
pFB->getNamespaceURL(OOX_NS(c15)));
+    pFS->startElement(FSNS(XML_c15, XML_datalabelsRange));
+
+    // Write cell range.
+    pFS->startElement(FSNS(XML_c15, XML_f));
+    pFS->writeEscaped(rDLblsRange.getRange());
+    pFS->endElement(FSNS(XML_c15, XML_f));
+
+    // Write all labels.
+    pFS->startElement(FSNS(XML_c15, XML_dlblRangeCache));
+    pFS->singleElement(FSNS(XML_c, XML_ptCount), XML_val, 
OString::number(rDLblsRange.count()));
+    for (const auto& rLabelKV: rDLblsRange)
+    {
+        pFS->startElement(FSNS(XML_c, XML_pt), XML_idx, 
OString::number(rLabelKV.first));
+        pFS->startElement(FSNS(XML_c, XML_v));
+        pFS->writeEscaped(rLabelKV.second);
+        pFS->endElement(FSNS( XML_c, XML_v ));
+        pFS->endElement(FSNS(XML_c, XML_pt));
+    }
+
+    pFS->endElement(FSNS(XML_c15, XML_dlblRangeCache));
+
+    pFS->endElement(FSNS(XML_c15, XML_datalabelsRange));
+    pFS->endElement(FSNS(XML_c, XML_ext));
+    pFS->endElement(FSNS(XML_c, XML_extLst));
+}
+
+}
+
 void ChartExport::exportLineChart( const Reference< chart2::XChartType >& 
xChartType )
 {
     FSHelperPtr pFS = GetFS();
@@ -2570,8 +2647,9 @@ void ChartExport::exportSeries( const 
Reference<chart2::XChartType>& xChartType,
                     // export data points
                     exportDataPoints( uno::Reference< beans::XPropertySet >( 
rSeries, uno::UNO_QUERY ), nSeriesLength, eChartType );
 
+                    DataLabelsRange aDLblsRange;
                     // export data labels
-                    exportDataLabels(rSeries, nSeriesLength, eChartType);
+                    exportDataLabels(rSeries, nSeriesLength, eChartType, 
aDLblsRange);
 
                     exportTrendlines( rSeries );
 
@@ -2644,6 +2722,9 @@ void ChartExport::exportSeries( const 
Reference<chart2::XChartType>& xChartType,
                     if( eChartType == chart::TYPEID_BUBBLE )
                         pFS->singleElement(FSNS(XML_c, XML_bubble3D), XML_val, 
"0");
 
+                    if (!aDLblsRange.empty())
+                        writeDataLabelsRange(pFS, GetFB(), aDLblsRange);
+
                     pFS->endElement( FSNS( XML_c, XML_ser ) );
                 }
             }
@@ -3560,7 +3641,8 @@ void writeRunProperties( ChartExport* pChartExport, 
Reference<XPropertySet> cons
 }
 
 void writeCustomLabel( const FSHelperPtr& pFS, ChartExport* pChartExport,
-                       const 
Sequence<Reference<chart2::XDataPointCustomLabelField>>& rCustomLabelFields )
+                       const 
Sequence<Reference<chart2::XDataPointCustomLabelField>>& rCustomLabelFields,
+                       sal_Int32 nLabelIndex, DataLabelsRange& rDLblsRange )
 {
     pFS->startElement(FSNS(XML_c, XML_tx));
     pFS->startElement(FSNS(XML_c, XML_rich));
@@ -3569,6 +3651,7 @@ void writeCustomLabel( const FSHelperPtr& pFS, 
ChartExport* pChartExport,
     pFS->singleElement(FSNS(XML_a, XML_bodyPr));
 
     OUString sFieldType;
+    OUString sContent;
     pFS->startElement(FSNS(XML_a, XML_p));
 
     for (auto& rField : rCustomLabelFields)
@@ -3576,8 +3659,25 @@ void writeCustomLabel( const FSHelperPtr& pFS, 
ChartExport* pChartExport,
         Reference<XPropertySet> xPropertySet(rField, UNO_QUERY);
         chart2::DataPointCustomLabelFieldType aType = rField->getFieldType();
         sFieldType.clear();
+        sContent.clear();
         bool bNewParagraph = false;
 
+        if (aType == chart2::DataPointCustomLabelFieldType_CELLRANGE &&
+            rField->getDataLabelsRange())
+        {
+            if (rDLblsRange.getRange().isEmpty())
+                rDLblsRange.setRange(rField->getCellRange());
+
+            if (!rDLblsRange.hasLabel(nLabelIndex))
+                rDLblsRange.setLabel(nLabelIndex, rField->getString());
+
+            sContent = "[CELLRANGE]";
+        }
+        else
+        {
+            sContent = rField->getString();
+        }
+
         if (aType == chart2::DataPointCustomLabelFieldType_NEWLINE)
             bNewParagraph = true;
         else if (aType != chart2::DataPointCustomLabelFieldType_TEXT)
@@ -3597,7 +3697,7 @@ void writeCustomLabel( const FSHelperPtr& pFS, 
ChartExport* pChartExport,
             writeRunProperties(pChartExport, xPropertySet);
 
             pFS->startElement(FSNS(XML_a, XML_t));
-            pFS->writeEscaped(rField->getString());
+            pFS->writeEscaped(sContent);
             pFS->endElement(FSNS(XML_a, XML_t));
 
             pFS->endElement(FSNS(XML_a, XML_r));
@@ -3610,7 +3710,7 @@ void writeCustomLabel( const FSHelperPtr& pFS, 
ChartExport* pChartExport,
             writeRunProperties(pChartExport, xPropertySet);
 
             pFS->startElement(FSNS(XML_a, XML_t));
-            pFS->writeEscaped(rField->getString());
+            pFS->writeEscaped(sContent);
             pFS->endElement(FSNS(XML_a, XML_t));
 
             pFS->endElement(FSNS(XML_a, XML_fld));
@@ -3623,7 +3723,8 @@ void writeCustomLabel( const FSHelperPtr& pFS, 
ChartExport* pChartExport,
 }
 
 void writeLabelProperties( const FSHelperPtr& pFS, ChartExport* pChartExport,
-    const uno::Reference<beans::XPropertySet>& xPropSet, const 
LabelPlacementParam& rLabelParam )
+    const uno::Reference<beans::XPropertySet>& xPropSet, const 
LabelPlacementParam& rLabelParam,
+    sal_Int32 nLabelIndex, DataLabelsRange& rDLblsRange )
 {
     if (!xPropSet.is())
         return;
@@ -3678,7 +3779,7 @@ void writeLabelProperties( const FSHelperPtr& pFS, 
ChartExport* pChartExport,
     pChartExport->exportTextProps(xPropSet);
 
     if (aCustomLabelFields.hasElements())
-        writeCustomLabel(pFS, pChartExport, aCustomLabelFields);
+        writeCustomLabel(pFS, pChartExport, aCustomLabelFields, nLabelIndex, 
rDLblsRange);
 
     if (rLabelParam.mbExport)
     {
@@ -3707,12 +3808,26 @@ void writeLabelProperties( const FSHelperPtr& pFS, 
ChartExport* pChartExport,
         pFS->writeEscaped( nLabelSeparator );
         pFS->endElement( FSNS( XML_c, XML_separator ) );
     }
+
+    if (rDLblsRange.hasLabel(nLabelIndex))
+    {
+        pFS->startElement(FSNS(XML_c, XML_extLst));
+        pFS->startElement(FSNS(XML_c, XML_ext), XML_uri,
+            "{CE6537A1-D6FC-4f65-9D91-7224C49458BB}", FSNS(XML_xmlns, XML_c15),
+            pChartExport->GetFB()->getNamespaceURL(OOX_NS(c15)));
+
+        pFS->singleElement(FSNS(XML_c15, XML_showDataLabelsRange), XML_val, 
"1");
+
+        pFS->endElement(FSNS(XML_c, XML_ext));
+        pFS->endElement(FSNS(XML_c, XML_extLst));
+    }
 }
 
 }
 
 void ChartExport::exportDataLabels(
-    const uno::Reference<chart2::XDataSeries> & xSeries, sal_Int32 
nSeriesLength, sal_Int32 eChartType )
+    const uno::Reference<chart2::XDataSeries> & xSeries, sal_Int32 
nSeriesLength, sal_Int32 eChartType,
+    DataLabelsRange& rDLblsRange)
 {
     if (!xSeries.is() || nSeriesLength <= 0)
         return;
@@ -3843,12 +3958,12 @@ void ChartExport::exportDataLabels(
         }
 
         // Individual label property that overwrites the baseline.
-        writeLabelProperties(pFS, this, xLabelPropSet, aParam);
+        writeLabelProperties(pFS, this, xLabelPropSet, aParam, nIdx, 
rDLblsRange);
         pFS->endElement(FSNS(XML_c, XML_dLbl));
     }
 
     // Baseline label properties for all labels.
-    writeLabelProperties(pFS, this, xPropSet, aParam);
+    writeLabelProperties(pFS, this, xPropSet, aParam, -1, rDLblsRange);
 
     bool bShowLeaderLines = false;
     xPropSet->getPropertyValue("ShowCustomLeaderLines") >>= bShowLeaderLines;
diff --git a/oox/source/token/tokens.txt b/oox/source/token/tokens.txt
index 997b3e8b3b25..9a10000c1fc8 100644
--- a/oox/source/token/tokens.txt
+++ b/oox/source/token/tokens.txt
@@ -1657,6 +1657,7 @@ dataValidation
 dataValidations
 database
 databaseField
+datalabelsRange
 datastoreItem
 date
 date1904
@@ -1872,6 +1873,7 @@ dkTurquoise
 dkUpDiag
 dkVert
 dkViolet
+dlblRangeCache
 dllVersion
 dm
 dn
@@ -4646,6 +4648,7 @@ showComments
 showDLblsOverMax
 showDataAs
 showDataDropDown
+showDataLabelsRange
 showDataTips
 showDrill
 showDropDown
diff --git a/schema/libreoffice/OpenDocument-v1.3+libreoffice-schema.rng 
b/schema/libreoffice/OpenDocument-v1.3+libreoffice-schema.rng
index 8c6d29611caa..a0f8711d092b 100644
--- a/schema/libreoffice/OpenDocument-v1.3+libreoffice-schema.rng
+++ b/schema/libreoffice/OpenDocument-v1.3+libreoffice-schema.rng
@@ -2719,4 +2719,16 @@ 
xmlns:loext="urn:org:documentfoundation:names:experimental:office:xmlns:loext:1.
     </rng:element>
   </rng:define>
 
+  <!-- TODO no proposal -->
+  <rng:define name="chart-data-label-attlist" combine="interleave">
+    <rng:optional>
+      <rng:attribute name="loext:data-label-guid">
+        <rng:ref name="string"/>
+      </rng:attribute>
+      <rng:attribute name="loext:data-labels-cell-range">
+        <rng:ref name="cellRangeAddress"/>
+      </rng:attribute>
+    </rng:optional>
+  </rng:define>
+
 </rng:grammar>
diff --git a/xmloff/source/chart/SchXMLExport.cxx 
b/xmloff/source/chart/SchXMLExport.cxx
index 4d1596b04aa5..65510e036ac2 100644
--- a/xmloff/source/chart/SchXMLExport.cxx
+++ b/xmloff/source/chart/SchXMLExport.cxx
@@ -114,7 +114,27 @@ using ::std::vector;
 
 namespace
 {
-    using CustomLabelSeq = 
Sequence<Reference<chart2::XDataPointCustomLabelField>>;
+    /**
+     * Used to store a data point custom-label's fields and also the helper 
members that
+     * indicates whether this label's contents are sourced from a cell[range] 
and
+     * the address of the cell[range] with guid of the CELLRANGE field.
+     */
+    struct CustomLabelData
+    {
+        CustomLabelData():
+            mbDataLabelsRange( false )
+        {
+        }
+
+        /// Label fields.
+        Sequence<Reference<chart2::XDataPointCustomLabelField>> maFields;
+        /// Are label's contents sourced from a cell[range] ?
+        bool mbDataLabelsRange;
+        /// cell[range] from which label's contents are sourced.
+        OUString maRange;
+        /// GUID of the CELLRANGE field.
+        OUString maGuid;
+    };
 
     struct SchXMLDataPointStruct
     {
@@ -124,7 +144,7 @@ namespace
 
         // There is no internal equivalent for <chart:data-label>. It will be 
generated on the fly
         // on export. All about data label is hold in the data point.
-        CustomLabelSeq   mCustomLabelText; // <text:p> child element in 
<chart:data-label>
+        CustomLabelData   mCustomLabel; // <text:p> child element in 
<chart:data-label>
         OUString msDataLabelStyleName; // chart:style-name attribute in 
<chart:data-label>
 
         SchXMLDataPointStruct() : mnRepeat( 1 ) {}
@@ -283,30 +303,43 @@ public:
 
 namespace
 {
-CustomLabelSeq lcl_getCustomLabelField(SvXMLExport const& rExport,
+CustomLabelData lcl_getCustomLabelField(SvXMLExport const& rExport,
                                        sal_Int32 nDataPointIndex,
                                        const uno::Reference< 
chart2::XDataSeries >& rSeries)
 {
     if (!rSeries.is())
-        return CustomLabelSeq();
+        return CustomLabelData();
 
     // Custom data label text will be written to the <text:p> child element of 
a
     // <chart:data-label> element. That exists only since ODF 1.2.
     const SvtSaveOptions::ODFSaneDefaultVersion nCurrentODFVersion(
         rExport.getSaneDefaultVersion());
     if (nCurrentODFVersion < SvtSaveOptions::ODFSVER_012)
-        return CustomLabelSeq();
+        return CustomLabelData();
 
     if(Reference<beans::XPropertySet> xLabels = 
rSeries->getDataPointByIndex(nDataPointIndex); xLabels.is())
     {
         if(Any aAny = xLabels->getPropertyValue("CustomLabelFields"); 
aAny.hasValue())
         {
+            CustomLabelData aData;
             Sequence<uno::Reference<chart2::XDataPointCustomLabelField>> 
aCustomLabels;
             aAny >>= aCustomLabels;
-            return aCustomLabels;
+            for (const auto& rField: std::as_const(aCustomLabels))
+            {
+                if (rField->getFieldType() == 
chart2::DataPointCustomLabelFieldType_CELLRANGE)
+                {
+                    if (rField->getDataLabelsRange())
+                        aData.mbDataLabelsRange = true;
+                    aData.maRange = rField->getCellRange();
+                    aData.maGuid = rField->getGuid();
+                }
+            }
+
+            aData.maFields = aCustomLabels;
+            return aData;
         }
     }
-    return CustomLabelSeq();
+    return CustomLabelData();
 }
 
 css::chart2::RelativePosition lcl_getCustomLabelPosition(
@@ -3463,7 +3496,7 @@ void SchXMLExportHelper_Impl::exportDataPoints(
                             maAutoStyleNameQueue.pop();
                         }
                         if(bExportNumFmt)
-                            aPoint.mCustomLabelText = 
lcl_getCustomLabelField(mrExport, nElement, xSeries);
+                            aPoint.mCustomLabel = 
lcl_getCustomLabelField(mrExport, nElement, xSeries);
                         aPoint.mCustomLabelPos = 
lcl_getCustomLabelPosition(mrExport, nElement, xSeries);
 
                         aDataPointVector.push_back( aPoint );
@@ -3542,7 +3575,7 @@ void SchXMLExportHelper_Impl::exportDataPoints(
                             aPoint.maStyleName = maAutoStyleNameQueue.front();
                             maAutoStyleNameQueue.pop();
                         }
-                        aPoint.mCustomLabelText = 
lcl_getCustomLabelField(mrExport, nCurrIndex, xSeries);
+                        aPoint.mCustomLabel = 
lcl_getCustomLabelField(mrExport, nCurrIndex, xSeries);
                         aPoint.mCustomLabelPos = 
lcl_getCustomLabelPosition(mrExport, nCurrIndex, xSeries);
                         if (!aDataLabelPropertyStates.empty())
                         {
@@ -3598,7 +3631,7 @@ void SchXMLExportHelper_Impl::exportDataPoints(
         aPoint = rPoint;
 
         if (aPoint.maStyleName == aLastPoint.maStyleName
-            && aLastPoint.mCustomLabelText.getLength() < 1
+            && aLastPoint.mCustomLabel.maFields.getLength() < 1
             && aLastPoint.mCustomLabelPos.Primary == 0.0
             && aLastPoint.mCustomLabelPos.Secondary == 0.0
             && aPoint.msDataLabelStyleName == aLastPoint.msDataLabelStyleName)
@@ -3655,15 +3688,22 @@ void SchXMLExportHelper_Impl::exportDataPoints(
 
 void SchXMLExportHelper_Impl::exportCustomLabel(const SchXMLDataPointStruct& 
rPoint)
 {
-    if (rPoint.mCustomLabelText.getLength() < 1 && 
rPoint.msDataLabelStyleName.isEmpty())
+    if (rPoint.mCustomLabel.maFields.getLength() < 1 && 
rPoint.msDataLabelStyleName.isEmpty())
         return; // nothing to export
 
     if (!rPoint.msDataLabelStyleName.isEmpty())
         mrExport.AddAttribute(XML_NAMESPACE_CHART, XML_STYLE_NAME, 
rPoint.msDataLabelStyleName);
+
+    if (rPoint.mCustomLabel.mbDataLabelsRange)
+    {
+        mrExport.AddAttribute(XML_NAMESPACE_LO_EXT, 
XML_DATA_LABELS_CELL_RANGE, rPoint.mCustomLabel.maRange);
+        mrExport.AddAttribute(XML_NAMESPACE_LO_EXT, XML_DATA_LABEL_GUID, 
rPoint.mCustomLabel.maGuid);
+    }
     // TODO svg:x and svg:y for <chart:data-label>
     SvXMLElementExport aLabelElem( mrExport, XML_NAMESPACE_CHART, 
XML_DATA_LABEL, true, true);
     SvXMLElementExport aPara( mrExport, XML_NAMESPACE_TEXT, XML_P, true, false 
);
-    for (const Reference<chart2::XDataPointCustomLabelField>& label : 
rPoint.mCustomLabelText)
+
+    for (const Reference<chart2::XDataPointCustomLabelField>& label : 
rPoint.mCustomLabel.maFields)
     {
         // TODO add style
         SvXMLElementExport aSpan( mrExport, XML_NAMESPACE_TEXT, XML_SPAN, 
true, false);
diff --git a/xmloff/source/chart/SchXMLPlotAreaContext.cxx 
b/xmloff/source/chart/SchXMLPlotAreaContext.cxx
index 563798b7c599..a66b05d0661b 100644
--- a/xmloff/source/chart/SchXMLPlotAreaContext.cxx
+++ b/xmloff/source/chart/SchXMLPlotAreaContext.cxx
@@ -617,7 +617,7 @@ css::uno::Reference< css::xml::sax::XFastContextHandler > 
SchXMLDataLabelParaCon
 }
 
 SchXMLDataLabelContext::SchXMLDataLabelContext(SvXMLImport& rImport,
-                                               ::std::vector<OUString>& 
rLabels,
+                                               CustomLabelsInfo& rLabels,
                                                DataRowPointStyle& 
rDataLabelStyle)
     : SvXMLImportContext(rImport)
     , mrLabels(rLabels)
@@ -630,7 +630,7 @@ css::uno::Reference< css::xml::sax::XFastContextHandler > 
SchXMLDataLabelContext
     const css::uno::Reference< css::xml::sax::XFastAttributeList >&  )
 {
     if ( nElement == XML_ELEMENT(TEXT, XML_P) )
-        return new SchXMLDataLabelParaContext(GetImport(), mrLabels);
+        return new SchXMLDataLabelParaContext(GetImport(), mrLabels.mLabels);
     else
         XMLOFF_WARN_UNKNOWN_ELEMENT("xmloff", nElement);
     return nullptr;
@@ -663,6 +663,14 @@ void SchXMLDataLabelContext::startFastElement(
             case XML_ELEMENT(CHART, XML_STYLE_NAME):
                 mrDataLabelStyle.msStyleName = aIter.toString();
                 break;
+            case XML_ELEMENT(LO_EXT, XML_DATA_LABEL_GUID):
+                mrLabels.msLabelGuid = aIter.toString();
+                mrLabels.mbDataLabelsRange = true;
+                break;
+            case XML_ELEMENT(LO_EXT, XML_DATA_LABELS_CELL_RANGE):
+                mrLabels.msLabelsCellRange = aIter.toString();
+                mrLabels.mbDataLabelsRange = true;
+                break;
             default:
                 XMLOFF_WARN_UNKNOWN("xmloff", aIter);
         }
@@ -737,7 +745,7 @@ void SchXMLDataPointContext::startFastElement (sal_Int32 
/*Element*/,
                 if (!mbHasLabelParagraph)
                 {
                     sCustomLabelField = aIter.toString();
-                    mDataPoint.mCustomLabels.push_back(sCustomLabelField);
+                    
mDataPoint.mCustomLabels.mLabels.push_back(sCustomLabelField);
                 }
                 break;
             case XML_ELEMENT(LO_EXT, XML_HIDE_LEGEND):
@@ -778,7 +786,7 @@ void SchXMLDataPointContext::startFastElement (sal_Int32 
/*Element*/,
 
 void SchXMLDataPointContext::endFastElement(sal_Int32 )
 {
-    if(!mDataPoint.msStyleName.isEmpty() || mDataPoint.mCustomLabels.size() > 
0)
+    if(!mDataPoint.msStyleName.isEmpty() || 
mDataPoint.mCustomLabels.mLabels.size() > 0)
     {
         mrStyleVector.push_back(mDataPoint);
     }
diff --git a/xmloff/source/chart/SchXMLPlotAreaContext.hxx 
b/xmloff/source/chart/SchXMLPlotAreaContext.hxx
index f0c2c2982a46..f80ee6f4840a 100644
--- a/xmloff/source/chart/SchXMLPlotAreaContext.hxx
+++ b/xmloff/source/chart/SchXMLPlotAreaContext.hxx
@@ -164,11 +164,11 @@ public:
 class SchXMLDataLabelContext: public SvXMLImportContext
 {
 private:
-    ::std::vector<OUString>& mrLabels;
+    CustomLabelsInfo& mrLabels;
     DataRowPointStyle& mrDataLabelStyle;
 public:
     SchXMLDataLabelContext(SvXMLImport& rImport,
-                            ::std::vector<OUString>& rLabels, 
DataRowPointStyle& rDataLabel);
+                            CustomLabelsInfo& rLabels, DataRowPointStyle& 
rDataLabel);
 
     virtual void SAL_CALL startFastElement(
         sal_Int32 nElement,
diff --git a/xmloff/source/chart/SchXMLSeries2Context.cxx 
b/xmloff/source/chart/SchXMLSeries2Context.cxx
index 544616846338..1d91af151317 100644
--- a/xmloff/source/chart/SchXMLSeries2Context.cxx
+++ b/xmloff/source/chart/SchXMLSeries2Context.cxx
@@ -1214,16 +1214,28 @@ void SchXMLSeries2Context::setStylesToDataPoints( 
SeriesDefaultsAndStyles& rSeri
                 }
 
                 // Custom labels might be passed as property
-                if(auto nLabelCount = seriesStyle.mCustomLabels.size(); 
nLabelCount > 0)
+                if(const size_t nLabelCount = 
seriesStyle.mCustomLabels.mLabels.size(); nLabelCount > 0)
                 {
+                    auto& rCustomLabels = seriesStyle.mCustomLabels;
+
                     Sequence< Reference<chart2::XDataPointCustomLabelField>> 
xLabels(nLabelCount);
                     Reference< uno::XComponentContext > xContext( 
comphelper::getProcessComponentContext() );
-                    for( auto j = 0; j< xLabels.getLength(); ++j )
+                    for( size_t j = 0; j < nLabelCount; ++j )
                     {
                         Reference< chart2::XDataPointCustomLabelField > 
xCustomLabel = chart2::DataPointCustomLabelField::create(xContext);
                         xLabels[j] = xCustomLabel;
-                        xCustomLabel->setString(seriesStyle.mCustomLabels[j]);
-                        
xCustomLabel->setFieldType(chart2::DataPointCustomLabelFieldType::DataPointCustomLabelFieldType_TEXT);
+                        xCustomLabel->setString(rCustomLabels.mLabels[j]);
+                        if ( j == 0 && rCustomLabels.mbDataLabelsRange)
+                        {
+                            
xCustomLabel->setFieldType(chart2::DataPointCustomLabelFieldType::DataPointCustomLabelFieldType_CELLRANGE);
+                            xCustomLabel->setGuid(rCustomLabels.msLabelGuid);
+                            
xCustomLabel->setCellRange(rCustomLabels.msLabelsCellRange);
+                            xCustomLabel->setDataLabelsRange(true);
+                        }
+                        else
+                        {
+                            
xCustomLabel->setFieldType(chart2::DataPointCustomLabelFieldType::DataPointCustomLabelFieldType_TEXT);
+                        }
 
                         // Restore character properties on the text span 
manually, till
                         // SchXMLExportHelper_Impl::exportCustomLabel() does 
not write the style.
@@ -1245,6 +1257,7 @@ void SchXMLSeries2Context::setStylesToDataPoints( 
SeriesDefaultsAndStyles& rSeri
                             }
                         }
                     }
+
                     xPointProp->setPropertyValue("CustomLabelFields", 
uno::Any(xLabels));
                     xPointProp->setPropertyValue("DataCaption", 
uno::Any(chart::ChartDataCaption::CUSTOM));
                 }
diff --git a/xmloff/source/chart/transporttypes.hxx 
b/xmloff/source/chart/transporttypes.hxx
index 7393a7b34848..7053d20c84c0 100644
--- a/xmloff/source/chart/transporttypes.hxx
+++ b/xmloff/source/chart/transporttypes.hxx
@@ -145,6 +145,24 @@ struct RegressionStyle
     {}
 };
 
+/**
+ * Used to store text content of a data point custom-label's fields and also
+ * the helper members that indicates whether this label's contents are sourced
+ * from a cell[range] and the address of the cell[range] with GUID of
+ * the CELLRANGE field.
+ */
+struct CustomLabelsInfo
+{
+    /// Text content of each field.
+    ::std::vector<OUString> mLabels;
+    /// Are label's contents sourced from a cell[range] ?
+    bool mbDataLabelsRange = false;
+    /// GUID of the CELLRANGE field.
+    OUString msLabelGuid;
+    /// cell[range] from which label's contents are sourced.
+    OUString msLabelsCellRange;
+};
+
 struct DataRowPointStyle
 {
     enum StyleType
@@ -170,7 +188,7 @@ struct DataRowPointStyle
     sal_Int32 m_nPointRepeat;
     OUString msStyleName;
     OUString msStyleNameOfParent; // e.g. target of line and fill styles of 
data-labels
-    ::std::vector<OUString> mCustomLabels;
+    CustomLabelsInfo mCustomLabels;
     double mCustomLabelPos[2] = { 0.0, 0.0 };
     // for svg:x and svg:y attribute (in core unit), of element 
<chart:data-label>
     std::optional<sal_Int32> mo_nLabelAbsolutePosX;
diff --git a/xmloff/source/core/xmltoken.cxx b/xmloff/source/core/xmltoken.cxx
index d4b996a4d9dd..e3a45924091e 100644
--- a/xmloff/source/core/xmltoken.cxx
+++ b/xmloff/source/core/xmltoken.cxx
@@ -581,10 +581,12 @@ namespace xmloff::token {
         TOKEN( "data-bar-entry",                  XML_DATA_BAR_ENTRY ),
         TOKEN( "data-cell-range-address",         XML_DATA_CELL_RANGE_ADDRESS 
),
         TOKEN( "data-label",                      XML_DATA_LABEL ),
+        TOKEN( "data-label-guid",                 XML_DATA_LABEL_GUID ),
         TOKEN( "data-label-number",               XML_DATA_LABEL_NUMBER ),
         TOKEN( "data-label-symbol",               XML_DATA_LABEL_SYMBOL ),
         TOKEN( "data-label-text",                 XML_DATA_LABEL_TEXT ),
         TOKEN( "data-label-series",               XML_DATA_LABEL_SERIES ),
+        TOKEN( "data-labels-cell-range",          XML_DATA_LABELS_CELL_RANGE ),
         TOKEN( "data-pilot-source",               XML_DATA_PILOT_SOURCE ),
         TOKEN( "data-pilot-field",                XML_DATA_PILOT_FIELD ),
         TOKEN( "data-pilot-grand-total",          XML_DATA_PILOT_GRAND_TOTAL ),
diff --git a/xmloff/source/token/tokens.txt b/xmloff/source/token/tokens.txt
index 9823df1e82e7..3fde556acfee 100644
--- a/xmloff/source/token/tokens.txt
+++ b/xmloff/source/token/tokens.txt
@@ -493,10 +493,12 @@ data-bar
 data-bar-entry
 data-cell-range-address
 data-label
+data-label-guid
 data-label-number
 data-label-symbol
 data-label-text
 data-label-series
+data-labels-cell-range
 data-pilot-source
 data-pilot-field
 data-pilot-grand-total

Reply via email to