tdf#123206 Store custom label as chart:data-label

Use the chart:data-label element instead of using the
loext:custom-label-field attribute.

chart:data-label stores can be a child of chart:data-point and it may
contain a text:o element for holding one or more paragraphs of custom
label text.

This commit aims to export and import chart:data-label with paragraphs
put into different text:span elements. These span elements may hold a
text:style-name attribute in order to achieve formatted text.

This structure is already in the ODF format.

Change-Id: I0bea7ce1a16af9c47b33555e18545bdaae7e95ca
Reviewed-on: https://gerrit.libreoffice.org/c/core/+/85659
Tested-by: Jenkins
Reviewed-by: Tamás Bunth <btomi96@gmail.com>
diff --git a/include/xmloff/xmltoken.hxx b/include/xmloff/xmltoken.hxx
index 02bd970..1175958 100644
--- a/include/xmloff/xmltoken.hxx
+++ b/include/xmloff/xmltoken.hxx
@@ -551,6 +551,7 @@
        XML_DATA_BAR,
        XML_DATA_BAR_ENTRY,
        XML_DATA_CELL_RANGE_ADDRESS,
        XML_DATA_LABEL,
        XML_DATA_LABEL_NUMBER,
        XML_DATA_LABEL_SYMBOL,
        XML_DATA_LABEL_TEXT,
diff --git a/xmloff/inc/SchXMLImport.hxx b/xmloff/inc/SchXMLImport.hxx
index 51aec6d..2a1080b 100644
--- a/xmloff/inc/SchXMLImport.hxx
+++ b/xmloff/inc/SchXMLImport.hxx
@@ -73,6 +73,7 @@
enum SchXMLSeriesElemTokenMap
{
    XML_TOK_SERIES_DATA_POINT,
    XML_TOK_SERIES_DATA_LABEL,
    XML_TOK_SERIES_DOMAIN,
    XML_TOK_SERIES_MEAN_VALUE_LINE,
    XML_TOK_SERIES_REGRESSION_CURVE,
diff --git a/xmloff/source/chart/SchXMLExport.cxx b/xmloff/source/chart/SchXMLExport.cxx
index 25ec802..b3d18c7 100644
--- a/xmloff/source/chart/SchXMLExport.cxx
+++ b/xmloff/source/chart/SchXMLExport.cxx
@@ -113,11 +113,13 @@

namespace
{
    using CustomLabelSeq = Sequence<Reference<chart2::XDataPointCustomLabelField>>;

    struct SchXMLDataPointStruct
    {
        OUString   maStyleName;
        sal_Int32  mnRepeat;
        OUString   msCustomLabelText;
        CustomLabelSeq   mCustomLabelText;

        SchXMLDataPointStruct() : mnRepeat( 1 ) {}
    };
@@ -225,6 +227,8 @@
        const css::uno::Reference< css::chart2::XDiagram > & xDiagram,
        bool bExportContent );

    void exportCustomLabel(const CustomLabelSeq & xCustomLabel);

    void exportRegressionCurve(
        const css::uno::Reference<css::chart2::XDataSeries>& xSeries,
        const css::awt::Size& rPageSize,
@@ -273,31 +277,26 @@
namespace
{

OUString lcl_getCustomLabelField(sal_Int32 nDataPointIndex,
CustomLabelSeq lcl_getCustomLabelField(sal_Int32 nDataPointIndex,
        const uno::Reference< chart2::XDataSeries >& rSeries)
{
    if( !rSeries.is() )
        return OUString{};
        return CustomLabelSeq();

    const SvtSaveOptions::ODFDefaultVersion nCurrentODFVersion( SvtSaveOptions().GetODFDefaultVersion() );
    if( nCurrentODFVersion <= SvtSaveOptions::ODFVER_012 )//do not export to ODF 1.2 or older
        return OUString{};
        return CustomLabelSeq();

    // export custom label text
    if(Reference<beans::XPropertySet> xLabels = rSeries->getDataPointByIndex(nDataPointIndex); xLabels.is())
    {
        if(Any aAny = xLabels->getPropertyValue("CustomLabelFields"); aAny.hasValue())
        {
            Sequence<uno::Reference<chart2::XDataPointCustomLabelField>> aCustomLabels;
            aAny >>= aCustomLabels;
            OUString sLabel;
            // TODO export formatted string instead of simple characters
            for(auto& aLabel : aCustomLabels)
                sLabel += aLabel->getString();
            return sLabel;
            return aCustomLabels;
        }
    }
    return OUString{};
    return CustomLabelSeq();
}

class lcl_MatchesRole
@@ -3261,7 +3260,7 @@
                        SchXMLDataPointStruct aPoint;
                        aPoint.maStyleName = maAutoStyleNameQueue.front();
                        if(bExportNumFmt)
                            aPoint.msCustomLabelText = lcl_getCustomLabelField(nElement, xSeries);
                            aPoint.mCustomLabelText = lcl_getCustomLabelField(nElement, xSeries);
                        maAutoStyleNameQueue.pop();
                        aDataPointVector.push_back( aPoint );
                    }
@@ -3289,7 +3288,7 @@
            {
                SchXMLDataPointStruct aPoint;
                aPoint.mnRepeat = nCurrIndex - nLastIndex - 1;
                aPoint.msCustomLabelText = lcl_getCustomLabelField(nCurrIndex, xSeries);
                aPoint.mCustomLabelText = lcl_getCustomLabelField(nCurrIndex, xSeries);
                aDataPointVector.push_back( aPoint );
            }

@@ -3322,7 +3321,7 @@
                        SAL_WARN_IF( maAutoStyleNameQueue.empty(), "xmloff.chart", "Autostyle queue empty!" );
                        SchXMLDataPointStruct aPoint;
                        aPoint.maStyleName = maAutoStyleNameQueue.front();
                        aPoint.msCustomLabelText = lcl_getCustomLabelField(nCurrIndex, xSeries);
                        aPoint.mCustomLabelText = lcl_getCustomLabelField(nCurrIndex, xSeries);
                        maAutoStyleNameQueue.pop();

                        aDataPointVector.push_back( aPoint );
@@ -3338,7 +3337,7 @@

            // if we get here the property states are empty
            SchXMLDataPointStruct aPoint;
            aPoint.msCustomLabelText = lcl_getCustomLabelField(nCurrIndex, xSeries);
            aPoint.mCustomLabelText = lcl_getCustomLabelField(nCurrIndex, xSeries);
            aDataPointVector.push_back( aPoint );

            nLastIndex = nCurrIndex;
@@ -3368,13 +3367,10 @@
    {
        aPoint = rPoint;

        if( aPoint.maStyleName == aLastPoint.maStyleName && aPoint.msCustomLabelText.isEmpty() )
        if( aPoint.maStyleName == aLastPoint.maStyleName && aLastPoint.mCustomLabelText.getLength() < 1 )
            aPoint.mnRepeat += aLastPoint.mnRepeat;
        else if( aLastPoint.mnRepeat > 0 )
        {
            // export custom label text
            if(!aLastPoint.msCustomLabelText.isEmpty())
                mrExport.AddAttribute( XML_NAMESPACE_LO_EXT, XML_CUSTOM_LABEL_FIELD, aLastPoint.msCustomLabelText);
            // write last element
            if( !aLastPoint.maStyleName.isEmpty() )
                mrExport.AddAttribute( XML_NAMESPACE_CHART, XML_STYLE_NAME, aLastPoint.maStyleName );
@@ -3384,16 +3380,13 @@
                                    OUString::number( ( aLastPoint.mnRepeat ) ));

            SvXMLElementExport aPointElem( mrExport, XML_NAMESPACE_CHART, XML_DATA_POINT, true, true );
            exportCustomLabel(aLastPoint.mCustomLabelText);
        }
        aLastPoint = aPoint;
    }
    // write last element if it hasn't been written in last iteration
    if( aPoint.maStyleName == aLastPoint.maStyleName )
    {
        // export custom label text
        if(!aLastPoint.msCustomLabelText.isEmpty())
            mrExport.AddAttribute( XML_NAMESPACE_LO_EXT, XML_CUSTOM_LABEL_FIELD, aLastPoint.msCustomLabelText);

        if( !aLastPoint.maStyleName.isEmpty() )
            mrExport.AddAttribute( XML_NAMESPACE_CHART, XML_STYLE_NAME, aLastPoint.maStyleName );

@@ -3402,6 +3395,22 @@
                                OUString::number( ( aLastPoint.mnRepeat ) ));

        SvXMLElementExport aPointElem( mrExport, XML_NAMESPACE_CHART, XML_DATA_POINT, true, true );
        exportCustomLabel(aLastPoint.mCustomLabelText);
    }
}

void SchXMLExportHelper_Impl::exportCustomLabel( const CustomLabelSeq & xCustomLabel )
{
    if( xCustomLabel.getLength() < 1 )
        return; // nothing to export

    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 : xCustomLabel )
    {
        // TODO add style
        SvXMLElementExport aSpan( mrExport, XML_NAMESPACE_TEXT, XML_SPAN, true, false);
        mrExport.GetDocHandler()->characters(label->getString());
    }
}

diff --git a/xmloff/source/chart/SchXMLImport.cxx b/xmloff/source/chart/SchXMLImport.cxx
index 322a97e..d0cd1f9 100644
--- a/xmloff/source/chart/SchXMLImport.cxx
+++ b/xmloff/source/chart/SchXMLImport.cxx
@@ -221,6 +221,7 @@
    { XML_NAMESPACE_CHART,  XML_REGRESSION_CURVE, XML_TOK_SERIES_REGRESSION_CURVE },
    { XML_NAMESPACE_CHART,  XML_ERROR_INDICATOR,  XML_TOK_SERIES_ERROR_INDICATOR  },
    { XML_NAMESPACE_LO_EXT, XML_PROPERTY_MAPPING, XML_TOK_SERIES_PROPERTY_MAPPING },
    { XML_NAMESPACE_CHART, XML_DATA_LABEL,       XML_TOK_SERIES_DATA_LABEL       },
    XML_TOKEN_MAP_END
};

diff --git a/xmloff/source/chart/SchXMLPlotAreaContext.cxx b/xmloff/source/chart/SchXMLPlotAreaContext.cxx
index 33bb1e0..9767efd 100644
--- a/xmloff/source/chart/SchXMLPlotAreaContext.cxx
+++ b/xmloff/source/chart/SchXMLPlotAreaContext.cxx
@@ -587,17 +587,87 @@
    SchXMLAxisContext::CorrectAxisPositions( uno::Reference< chart2::XChartDocument >( mrImportHelper.GetChartDocument(), uno::UNO_QUERY ), maChartTypeServiceName, GetImport().GetODFVersion(), m_bAxisPositionAttributeImported );
}

SchXMLDataPointContext::SchXMLDataPointContext(  SvXMLImport& rImport, const OUString& rLocalName,
SchXMLDataLabelSpanContext::SchXMLDataLabelSpanContext( SvXMLImport& rImport, const OUString& rLocalName, ::std::vector<OUString>& rLabels):
    SvXMLImportContext( rImport, XML_NAMESPACE_TEXT, rLocalName),
    mrLabels(rLabels)
{
}

void SchXMLDataLabelSpanContext::Characters(const OUString& sChars)
{
    mrLabels.push_back(sChars);
}

SchXMLDataLabelParaContext::SchXMLDataLabelParaContext( SvXMLImport& rImport, const OUString& rLocalName, ::std::vector<OUString>& rLabels):
    SvXMLImportContext( rImport, XML_NAMESPACE_TEXT, rLocalName),
    mrLabels(rLabels)
{
}

SvXMLImportContextRef SchXMLDataLabelParaContext::CreateChildContext(
    sal_uInt16 nPrefix,
    const OUString& rLocalName,
    const uno::Reference< xml::sax::XAttributeList >& /*xAttrList*/ )
{
    SvXMLImportContextRef xContext;
    if ( IsXMLToken( rLocalName, XML_SPAN ) && nPrefix == XML_NAMESPACE_TEXT )
        xContext = new SchXMLDataLabelSpanContext(GetImport(), rLocalName, mrLabels);
    return xContext;
}

SchXMLDataLabelContext::SchXMLDataLabelContext( SvXMLImport& rImport, const OUString& rLocalName, ::std::vector<OUString>& rLabels):
    SvXMLImportContext( rImport, XML_NAMESPACE_CHART, rLocalName),
    mrLabels(rLabels)
{
}

SvXMLImportContextRef SchXMLDataLabelContext::CreateChildContext(
    sal_uInt16 nPrefix,
    const OUString& rLocalName,
    const uno::Reference< xml::sax::XAttributeList >& xAttrList )
{
    SvXMLImportContextRef xContext;
    if ( IsXMLToken( rLocalName, XML_P ) && nPrefix == XML_NAMESPACE_TEXT )
        xContext = new SchXMLDataLabelParaContext(GetImport(), rLocalName, mrLabels);

    if (!xContext)
        xContext = SvXMLImportContext::CreateChildContext( nPrefix, rLocalName, xAttrList );

    return xContext;
}


SchXMLDataPointContext::SchXMLDataPointContext(  SchXMLImportHelper& rImportHelper,
                                                 SvXMLImport& rImport, const OUString& rLocalName,
                                                 ::std::vector< DataRowPointStyle >& rStyleVector,
                                                 const css::uno::Reference< css::chart2::XDataSeries >& xSeries,
                                                 sal_Int32& rIndex,
                                                 bool bSymbolSizeForSeriesIsMissingInFile ) :
        SvXMLImportContext( rImport, XML_NAMESPACE_CHART, rLocalName ),
        mrImportHelper( rImportHelper ),
        mrStyleVector( rStyleVector ),
        m_xSeries( xSeries ),
        mrIndex( rIndex ),
        mbSymbolSizeForSeriesIsMissingInFile( bSymbolSizeForSeriesIsMissingInFile )
        mDataPoint(DataRowPointStyle::DATA_POINT, xSeries, rIndex, 1, OUString{})
{
    mDataPoint.mbSymbolSizeForSeriesIsMissingInFile = bSymbolSizeForSeriesIsMissingInFile;
}

SvXMLImportContextRef SchXMLDataPointContext::CreateChildContext(
    sal_uInt16 nPrefix,
    const OUString& rLocalName,
    const uno::Reference< xml::sax::XAttributeList >& /*xAttrList*/ )
{
    SvXMLImportContext* pContext = nullptr;
    const SvXMLTokenMap& rTokenMap = mrImportHelper.GetSeriesElemTokenMap();

    switch( rTokenMap.Get( nPrefix, rLocalName ))
    {
        case XML_TOK_SERIES_DATA_LABEL:
            mbHasLabelParagraph = true;
            pContext = new SchXMLDataLabelContext( GetImport(), rLocalName, mDataPoint.mCustomLabels);
            break;
    }
    return pContext;
}

SchXMLDataPointContext::~SchXMLDataPointContext()
@@ -620,31 +690,37 @@
        if( nPrefix == XML_NAMESPACE_CHART )
        {
            if( IsXMLToken( aLocalName, XML_STYLE_NAME ) )
            {
                sAutoStyleName = xAttrList->getValueByIndex( i );
                mDataPoint.msStyleName = sAutoStyleName;
            }
            else if( IsXMLToken( aLocalName, XML_REPEATED ) )
            {
                nRepeat = xAttrList->getValueByIndex( i ).toInt32();
                mDataPoint.m_nPointRepeat = nRepeat;
            }
        }
        else if( nPrefix == XML_NAMESPACE_LO_EXT)
        {
            if( IsXMLToken( aLocalName, XML_CUSTOM_LABEL_FIELD))
            if( IsXMLToken( aLocalName, XML_CUSTOM_LABEL_FIELD) && !mbHasLabelParagraph)
            {
                sCustomLabelField = xAttrList->getValueByIndex( i );
                mDataPoint.mCustomLabels.push_back(sCustomLabelField);
            }
        }
    }

    if( !sAutoStyleName.isEmpty())
    {
        DataRowPointStyle aStyle(
            DataRowPointStyle::DATA_POINT,
            m_xSeries, mrIndex, nRepeat, sAutoStyleName );
        aStyle.mbSymbolSizeForSeriesIsMissingInFile = mbSymbolSizeForSeriesIsMissingInFile;
        aStyle.msCustomLabelField = sCustomLabelField;
        mrStyleVector.push_back( aStyle );
    }
    mrIndex += nRepeat;
}

void SchXMLDataPointContext::EndElement()
{
    if( !mDataPoint.msStyleName.isEmpty() || mDataPoint.mCustomLabels.size() > 0)
    {
        mrStyleVector.push_back( mDataPoint );
    }
}

SchXMLPositionAttributesHelper::SchXMLPositionAttributesHelper( SvXMLImport& rImporter )
    : m_rImport( rImporter )
    , m_aPosition(0,0)
diff --git a/xmloff/source/chart/SchXMLPlotAreaContext.hxx b/xmloff/source/chart/SchXMLPlotAreaContext.hxx
index 53fbd49..748aca4 100644
--- a/xmloff/source/chart/SchXMLPlotAreaContext.hxx
+++ b/xmloff/source/chart/SchXMLPlotAreaContext.hxx
@@ -137,16 +137,51 @@
    css::awt::Size const maChartSize;
};

class SchXMLDataLabelSpanContext: public SvXMLImportContext
{
private:
    ::std::vector<OUString>& mrLabels;
public:
    SchXMLDataLabelSpanContext( SvXMLImport& rImport, const OUString& rLocalName, ::std::vector<OUString>& rLabels);
    virtual void Characters( const OUString& rChars ) override;
};

class SchXMLDataLabelParaContext: public SvXMLImportContext
{
private:
    ::std::vector<OUString>& mrLabels;
public:
    SchXMLDataLabelParaContext( SvXMLImport& rImport, const OUString& rLocalName, ::std::vector<OUString>& rLabels);
    virtual SvXMLImportContextRef CreateChildContext(
        sal_uInt16 nPrefix,
        const OUString& rLocalName,
        const css::uno::Reference< css::xml::sax::XAttributeList >& xAttrList ) override;
};

class SchXMLDataLabelContext: public SvXMLImportContext
{
private:
    ::std::vector<OUString>& mrLabels;
public:
    SchXMLDataLabelContext( SvXMLImport& rImport, const OUString& rLocalName, ::std::vector<OUString>& rLabels);
    virtual SvXMLImportContextRef CreateChildContext(
        sal_uInt16 nPrefix,
        const OUString& rLocalName,
        const css::uno::Reference< css::xml::sax::XAttributeList >& xAttrList ) override;
};

class SchXMLDataPointContext : public SvXMLImportContext
{
private:
    SchXMLImportHelper& mrImportHelper;
    ::std::vector< DataRowPointStyle >& mrStyleVector;
    css::uno::Reference< css::chart2::XDataSeries > m_xSeries;
    bool mbHasLabelParagraph = false;
    sal_Int32& mrIndex;
    bool const mbSymbolSizeForSeriesIsMissingInFile;
    DataRowPointStyle mDataPoint;

public:
    SchXMLDataPointContext(  SvXMLImport& rImport, const OUString& rLocalName,
    SchXMLDataPointContext(  SchXMLImportHelper& rImportHelper,
                             SvXMLImport& rImport, const OUString& rLocalName,
                             ::std::vector< DataRowPointStyle >& rStyleVector,
                             const css::uno::Reference< css::chart2::XDataSeries >& xSeries,
                             sal_Int32& rIndex,
@@ -154,6 +189,12 @@
    virtual ~SchXMLDataPointContext() override;

    virtual void StartElement( const css::uno::Reference< css::xml::sax::XAttributeList >& xAttrList ) override;

    virtual SvXMLImportContextRef CreateChildContext(
        sal_uInt16 nPrefix,
        const OUString& rLocalName,
        const css::uno::Reference< css::xml::sax::XAttributeList >& xAttrList ) override;
    virtual void EndElement() override;
};

class SchXMLCoordinateRegionContext : public SvXMLImportContext
diff --git a/xmloff/source/chart/SchXMLSeries2Context.cxx b/xmloff/source/chart/SchXMLSeries2Context.cxx
index 66c473d..86c67f8 100644
--- a/xmloff/source/chart/SchXMLSeries2Context.cxx
+++ b/xmloff/source/chart/SchXMLSeries2Context.cxx
@@ -688,7 +688,7 @@
            break;

        case XML_TOK_SERIES_DATA_POINT:
            pContext = new SchXMLDataPointContext( GetImport(), rLocalName,
            pContext = new SchXMLDataPointContext( mrImportHelper, GetImport(), rLocalName,
                                                   mrStyleVector, m_xSeries, mnDataPointIndex, mbSymbolSizeIsMissingInFile );
            break;
        case XML_TOK_SERIES_PROPERTY_MAPPING:
@@ -1090,14 +1090,18 @@
                        lcl_resetSymbolSizeForPointsIfNecessary( xPointProp, rImport, pPropStyleContext, pStylesCtxt );
                }

                if(!seriesStyle.msCustomLabelField.isEmpty())
                // Custom labels might be passed as property
                if(auto nLabelCount = seriesStyle.mCustomLabels.size(); nLabelCount > 0)
                {
                    Sequence< Reference<chart2::XDataPointCustomLabelField>> xLabels(1);
                    Sequence< Reference<chart2::XDataPointCustomLabelField>> xLabels(nLabelCount);
                    Reference< uno::XComponentContext > xContext( comphelper::getProcessComponentContext() );
                    Reference< chart2::XDataPointCustomLabelField > xCustomLabel = chart2::DataPointCustomLabelField::create(xContext);
                    xLabels[0] = xCustomLabel;
                    xCustomLabel->setString(seriesStyle.msCustomLabelField);
                    xCustomLabel->setFieldType(chart2::DataPointCustomLabelFieldType::DataPointCustomLabelFieldType_TEXT);
                    for( auto j = 0; j< xLabels.getLength(); ++j )
                    {
                        Reference< chart2::XDataPointCustomLabelField > xCustomLabel = chart2::DataPointCustomLabelField::create(xContext);
                        xLabels[j] = xCustomLabel;
                        xCustomLabel->setString(seriesStyle.mCustomLabels[j]);
                        xCustomLabel->setFieldType(chart2::DataPointCustomLabelFieldType::DataPointCustomLabelFieldType_TEXT);
                    }
                    xPointProp->setPropertyValue("CustomLabelFields", uno::Any(xLabels));
                }
            }
diff --git a/xmloff/source/chart/transporttypes.hxx b/xmloff/source/chart/transporttypes.hxx
index 6481cfb..04fbe70 100644
--- a/xmloff/source/chart/transporttypes.hxx
+++ b/xmloff/source/chart/transporttypes.hxx
@@ -145,6 +145,10 @@
    {}
};

struct CustomLabelField {
    std::vector<OUString> sRuns;
};

struct DataRowPointStyle
{
    enum StyleType
@@ -167,8 +171,9 @@
    sal_Int32 m_nPointIndex;
    sal_Int32 m_nPointRepeat;
    OUString msStyleName;
    ::std::vector<OUString> mCustomLabels;
    OUString msSeriesStyleNameForDonuts;
    OUString msCustomLabelField;

    sal_Int32 mnAttachedAxis;
    bool mbSymbolSizeForSeriesIsMissingInFile;

diff --git a/xmloff/source/core/xmltoken.cxx b/xmloff/source/core/xmltoken.cxx
index 62ddf00..6efad44 100644
--- a/xmloff/source/core/xmltoken.cxx
+++ b/xmloff/source/core/xmltoken.cxx
@@ -552,6 +552,7 @@
        TOKEN( "data-bar",                        XML_DATA_BAR ),
        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-number",               XML_DATA_LABEL_NUMBER ),
        TOKEN( "data-label-symbol",               XML_DATA_LABEL_SYMBOL ),
        TOKEN( "data-label-text",                 XML_DATA_LABEL_TEXT ),
diff --git a/xmloff/source/token/tokens.txt b/xmloff/source/token/tokens.txt
index 539cdb5..921ff2b 100644
--- a/xmloff/source/token/tokens.txt
+++ b/xmloff/source/token/tokens.txt
@@ -475,6 +475,7 @@
data-bar
data-bar-entry
data-cell-range-address
data-label
data-label-number
data-label-symbol
data-label-text