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 <vmiklos@collabora.com>
diff --git a/chart2/qa/extras/chart2export2.cxx b/chart2/qa/extras/chart2export2.cxx
index adde208..3a1afef 100644
--- a/chart2/qa/extras/chart2export2.cxx
+++ b/chart2/qa/extras/chart2export2.cxx
@@ -98,6 +98,7 @@
    void testCustomShapeText();
    void testuserShapesXLSX();
    void testNameRangeXLSX();
    void testTdf143942();

    CPPUNIT_TEST_SUITE(Chart2ExportTest2);
    CPPUNIT_TEST(testSetSeriesToSecondaryAxisXLSX);
@@ -159,6 +160,7 @@
    CPPUNIT_TEST(testCustomShapeText);
    CPPUNIT_TEST(testuserShapesXLSX);
    CPPUNIT_TEST(testNameRangeXLSX);
    CPPUNIT_TEST(testTdf143942);
    CPPUNIT_TEST_SUITE_END();
};

@@ -1334,22 +1336,60 @@
        "/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;
    };

    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());
    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
        },
        {

    CPPUNIT_ASSERT_EQUAL(
        chart2::DataPointCustomLabelFieldType::DataPointCustomLabelFieldType_CELLRANGE,
        aFields[0]->getFieldType());
    //CPPUNIT_ASSERT_EQUAL(OUString("67.5%"), aFields[0]->getString()); TODO: Not implemented yet
            // 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(aTestEntry.nNumFields, aFields.getLength());

        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 @@
                       "[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 0000000..33ff669
--- /dev/null
+++ b/chart2/qa/extras/data/xlsx/tdf143942.xlsx
Binary files differ
diff --git a/chart2/source/model/main/FormattedString.cxx b/chart2/source/model/main/FormattedString.cxx
index 5433f36..7827292 100644
--- a/chart2/source/model/main/FormattedString.cxx
+++ b/chart2/source/model/main/FormattedString.cxx
@@ -98,6 +98,7 @@
FormattedString::FormattedString() :
        ::property::OPropertySet( m_aMutex ),
    m_aType(chart2::DataPointCustomLabelFieldType::DataPointCustomLabelFieldType_TEXT),
    m_bDataLabelsRange(false),
    m_xModifyEventForwarder( ModifyListenerHelper::createModifyEventForwarder())
{}

@@ -107,6 +108,7 @@
    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 @@

}

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 a7f9e36..bd7415f 100644
--- a/chart2/source/model/main/FormattedString.hxx
+++ b/chart2/source/model/main/FormattedString.hxx
@@ -87,6 +87,10 @@
        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 @@
    // ____ 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 b0a591d..b5bfc8d8 100644
--- a/chart2/source/view/charttypes/VSeriesPlotter.cxx
+++ b/chart2/source/view/charttypes/VSeriesPlotter.cxx
@@ -540,9 +540,16 @@
                        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();
                        break;
diff --git a/include/oox/export/chartexport.hxx b/include/oox/export/chartexport.hxx
index 5dbf8c2..c4440ae 100644
--- a/include/oox/export/chartexport.hxx
+++ b/include/oox/export/chartexport.hxx
@@ -91,6 +91,43 @@
    {}
};

/**
 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 @@
    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 4458bad..a7a3fc1 100644
--- a/include/xmloff/xmltoken.hxx
+++ b/include/xmloff/xmltoken.hxx
@@ -576,10 +576,12 @@
        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 a6a1b01..8291e90 100644
--- a/offapi/com/sun/star/chart2/XDataPointCustomLabelField.idl
+++ b/offapi/com/sun/star/chart2/XDataPointCustomLabelField.idl
@@ -29,6 +29,34 @@

    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 8ce8469..ec64c47 100644
--- a/oox/inc/drawingml/chart/datasourcecontext.hxx
+++ b/oox/inc/drawingml/chart/datasourcecontext.hxx
@@ -69,6 +69,7 @@

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 c4fb557..460293a 100644
--- a/oox/inc/drawingml/chart/seriesmodel.hxx
+++ b/oox/inc/drawingml/chart/seriesmodel.hxx
@@ -41,12 +41,18 @@
    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 @@

    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 @@

    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 @@
    {
        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 eac776e..1b64089 100644
--- a/oox/source/drawingml/chart/datasourcecontext.cxx
+++ b/oox/source/drawingml/chart/datasourcecontext.cxx
@@ -166,6 +166,7 @@
StringSequenceContext::StringSequenceContext( ContextHandler2Helper& rParent, DataSequenceModel& rModel )
    : DataSequenceContextBase( rParent, rModel )
    , mnPtIndex(-1)
    , mbReadC15(false)
{
}

@@ -186,6 +187,16 @@
            }
        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 @@

        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 @@
        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 @@
    {
        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 3f92818..5afc32c 100644
--- a/oox/source/drawingml/chart/seriescontext.cxx
+++ b/oox/source/drawingml/chart/seriescontext.cxx
@@ -100,15 +100,37 @@

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 @@
    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 @@
                    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 @@
                    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 2c3341a..3f03e1e 100644
--- a/oox/source/drawingml/chart/seriesconverter.cxx
+++ b/oox/source/drawingml/chart/seriesconverter.cxx
@@ -319,9 +319,25 @@
            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 @@
                    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 563e0e4..4ad9b25 100644
--- a/oox/source/drawingml/chart/seriesmodel.cxx
+++ b/oox/source/drawingml/chart/seriesmodel.cxx
@@ -31,8 +31,9 @@
{
}

DataLabelModel::DataLabelModel(bool bMSO2007Doc) :
DataLabelModel::DataLabelModel(const DataLabelsModel& rParent, bool bMSO2007Doc) :
    DataLabelModelBase(bMSO2007Doc),
    mrParent( rParent ),
    mnIndex( -1 )
{
}
@@ -43,6 +44,7 @@

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 dc122a4..2e13b40 100644
--- a/oox/source/export/chartexport.cxx
+++ b/oox/source/export/chartexport.cxx
@@ -485,6 +485,46 @@
    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 @@
    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 @@
                    // 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 @@
                    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 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 @@
    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 @@
        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 @@
            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 @@
            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 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 @@
    pChartExport->exportTextProps(xPropSet);

    if (aCustomLabelFields.hasElements())
        writeCustomLabel(pFS, pChartExport, aCustomLabelFields);
        writeCustomLabel(pFS, pChartExport, aCustomLabelFields, nLabelIndex, rDLblsRange);

    if (rLabelParam.mbExport)
    {
@@ -3707,12 +3808,26 @@
        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 @@
        }

        // 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 997b3e8..9a10000 100644
--- a/oox/source/token/tokens.txt
+++ b/oox/source/token/tokens.txt
@@ -1657,6 +1657,7 @@
dataValidations
database
databaseField
datalabelsRange
datastoreItem
date
date1904
@@ -1872,6 +1873,7 @@
dkUpDiag
dkVert
dkViolet
dlblRangeCache
dllVersion
dm
dn
@@ -4646,6 +4648,7 @@
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 8c6d296..a0f8711 100644
--- a/schema/libreoffice/OpenDocument-v1.3+libreoffice-schema.rng
+++ b/schema/libreoffice/OpenDocument-v1.3+libreoffice-schema.rng
@@ -2719,4 +2719,16 @@
    </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 4d1596b..65510e0 100644
--- a/xmloff/source/chart/SchXMLExport.cxx
+++ b/xmloff/source/chart/SchXMLExport.cxx
@@ -114,7 +114,27 @@

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 @@

        // 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 @@

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 @@
                            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 @@
                            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 @@
        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::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 563798b..a66b05d 100644
--- a/xmloff/source/chart/SchXMLPlotAreaContext.cxx
+++ b/xmloff/source/chart/SchXMLPlotAreaContext.cxx
@@ -617,7 +617,7 @@
}

SchXMLDataLabelContext::SchXMLDataLabelContext(SvXMLImport& rImport,
                                               ::std::vector<OUString>& rLabels,
                                               CustomLabelsInfo& rLabels,
                                               DataRowPointStyle& rDataLabelStyle)
    : SvXMLImportContext(rImport)
    , mrLabels(rLabels)
@@ -630,7 +630,7 @@
    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 @@
            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 @@
                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::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 f0c2c29..f80ee6f4 100644
--- a/xmloff/source/chart/SchXMLPlotAreaContext.hxx
+++ b/xmloff/source/chart/SchXMLPlotAreaContext.hxx
@@ -164,11 +164,11 @@
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 5446168..1d91af1 100644
--- a/xmloff/source/chart/SchXMLSeries2Context.cxx
+++ b/xmloff/source/chart/SchXMLSeries2Context.cxx
@@ -1214,16 +1214,28 @@
                }

                // 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 @@
                            }
                        }
                    }

                    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 7393a7b..7053d20 100644
--- a/xmloff/source/chart/transporttypes.hxx
+++ b/xmloff/source/chart/transporttypes.hxx
@@ -145,6 +145,24 @@
    {}
};

/**
 * 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 @@
    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 d4b996a..e3a4592 100644
--- a/xmloff/source/core/xmltoken.cxx
+++ b/xmloff/source/core/xmltoken.cxx
@@ -581,10 +581,12 @@
        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 9823df1..3fde556 100644
--- a/xmloff/source/token/tokens.txt
+++ b/xmloff/source/token/tokens.txt
@@ -493,10 +493,12 @@
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