chart2/qa/extras/chart2geometry.cxx | 56 +++ chart2/qa/extras/data/odt/tdf135366_data_label_export.odt |binary chart2/qa/extras/data/pptx/tdf135366_CustomLabelText.pptx |binary xmloff/source/chart/SchXMLExport.cxx | 210 +++++++++++--- 4 files changed, 235 insertions(+), 31 deletions(-)
New commits: commit 6d7a72ab1c044f7e1f30f7c4133dafdb214dfcbf Author: Regina Henschel <rb.hensc...@t-online.de> AuthorDate: Thu Sep 10 14:42:55 2020 +0200 Commit: Regina Henschel <rb.hensc...@t-online.de> CommitDate: Sat Sep 19 14:27:24 2020 +0200 tdf#135366 Save line and fill of data labels to ODF LibreOffice has line and fill properties of data labels in charts as loext attributes in the style of the <chart:series> or <chart:data-point> element. For ODF there has to be a <chart:data-label> element with line and fill properties in its style. This patch adds the needed <chart:data-label> elements and their associated <style:style> elements. The element <chart:data-lable> exists in ODF since version 1.2. The solution requires no extended namespace. The check is adapted in lcl_getCustomLabelField. Import was already done in commit 87d1ebeb11a00301745ee3c3c03fffb7033ab59d Change-Id: I829dae5433e8257c775aa4f08e511d514df4e936 Reviewed-on: https://gerrit.libreoffice.org/c/core/+/102381 Tested-by: Jenkins Reviewed-by: Regina Henschel <rb.hensc...@t-online.de> diff --git a/chart2/qa/extras/chart2geometry.cxx b/chart2/qa/extras/chart2geometry.cxx index b3f537c372e0..be6d842d7780 100644 --- a/chart2/qa/extras/chart2geometry.cxx +++ b/chart2/qa/extras/chart2geometry.cxx @@ -21,6 +21,7 @@ #include <com/sun/star/packages/zip/ZipFileAccess.hpp> #include <unotools/ucbstreamhelper.hxx> +#include <unotools/saveopt.hxx> #include <libxml/xpathInternals.h> @@ -52,6 +53,8 @@ public: void testTdf128345Legend_CS_TG_axial_import(); void testTdf135366LabelOnSeries(); void testTdf135366LabelOnPoint(); + void testTdf135366LabelExport(); + void testTdf135366_CustomLabelText(); CPPUNIT_TEST_SUITE(Chart2GeometryTest); CPPUNIT_TEST(testTdf135184RoundLineCap); @@ -66,6 +69,8 @@ public: CPPUNIT_TEST(testTdf128345Legend_CS_TG_axial_import); CPPUNIT_TEST(testTdf135366LabelOnSeries); CPPUNIT_TEST(testTdf135366LabelOnPoint); + CPPUNIT_TEST(testTdf135366LabelExport); + CPPUNIT_TEST(testTdf135366_CustomLabelText); CPPUNIT_TEST_SUITE_END(); @@ -502,6 +507,57 @@ void Chart2GeometryTest::testTdf135366LabelOnPoint() nFillColor); } +void Chart2GeometryTest::testTdf135366LabelExport() +{ + // Error was, that line and fill properties were not exported as + // graphic-properties of a <chart:data-label> element, but only + // as loext chart-properties of the <chart:data-point> element. + load("/chart2/qa/extras/data/odt/", "tdf135366_data_label_export.odt"); + xmlDocUniquePtr pXmlDoc = parseExport("Object 1/content.xml", "writer8"); + CPPUNIT_ASSERT(pXmlDoc); + + // Find label style + const OString sLabelPath( + "//office:document-content/office:body/office:chart/chart:chart/chart:plot-area" + "/chart:series/chart:data-point[1]/chart:data-label/@chart:style-name"); + const OUString sOULabelStyleName = getXPathContent(pXmlDoc, sLabelPath); + + // Verify content of graphic properties of label style + const OString sStylePath( + "//office:document-content/office:automatic-styles/style:style[@style:name='" + + OU2O(sOULabelStyleName) + "']/style:graphic-properties"); + assertXPath(pXmlDoc, sStylePath, 1); + assertXPath(pXmlDoc, sStylePath + "[@draw:fill='solid']"); + assertXPath(pXmlDoc, sStylePath + "[@draw:fill-color='#5050a0']"); + assertXPath(pXmlDoc, sStylePath + "[@draw:stroke='solid']"); + assertXPath(pXmlDoc, sStylePath + "[@svg:stroke-width='0.254cm']"); + assertXPath(pXmlDoc, sStylePath + "[@svg:stroke-color='#00ffff']"); +} + +void Chart2GeometryTest::testTdf135366_CustomLabelText() +{ + // Error was, that custom text in a data label was only exported in ODF extended, + // although the used <chart:data-label> element exists since ODF 1.2. + SvtSaveOptions aSaveOpt; + const SvtSaveOptions::ODFDefaultVersion nCurrentODFVersion(aSaveOpt.GetODFDefaultVersion()); + aSaveOpt.SetODFDefaultVersion(SvtSaveOptions::ODFVER_012); + load("/chart2/qa/extras/data/pptx/", "tdf135366_CustomLabelText.pptx"); + xmlDocUniquePtr pXmlDoc = parseExport("Object 1/content.xml", "impress8"); + CPPUNIT_ASSERT(pXmlDoc); + + // Find custom text. As of version 7.0 it is in a <text:span> element. + const OString sCustomTextPath( + "//office:document-content/office:body/office:chart/chart:chart/chart:plot-area" + "/chart:series/chart:data-point[2]/chart:data-label/text:p/text:span"); + assertXPath(pXmlDoc, sCustomTextPath, 1); + + // Verify text content + const OUString sOUTextContent = getXPathContent(pXmlDoc, sCustomTextPath); + CPPUNIT_ASSERT_EQUAL(OUString("Custom"), sOUTextContent); + + aSaveOpt.SetODFDefaultVersion(nCurrentODFVersion); +} + CPPUNIT_TEST_SUITE_REGISTRATION(Chart2GeometryTest); CPPUNIT_PLUGIN_IMPLEMENT(); diff --git a/chart2/qa/extras/data/odt/tdf135366_data_label_export.odt b/chart2/qa/extras/data/odt/tdf135366_data_label_export.odt new file mode 100644 index 000000000000..85759f2adeca Binary files /dev/null and b/chart2/qa/extras/data/odt/tdf135366_data_label_export.odt differ diff --git a/chart2/qa/extras/data/pptx/tdf135366_CustomLabelText.pptx b/chart2/qa/extras/data/pptx/tdf135366_CustomLabelText.pptx new file mode 100644 index 000000000000..58d73fcd26c1 Binary files /dev/null and b/chart2/qa/extras/data/pptx/tdf135366_CustomLabelText.pptx differ diff --git a/xmloff/source/chart/SchXMLExport.cxx b/xmloff/source/chart/SchXMLExport.cxx old mode 100644 new mode 100755 index a2c18e494d19..4911d5180d03 --- a/xmloff/source/chart/SchXMLExport.cxx +++ b/xmloff/source/chart/SchXMLExport.cxx @@ -117,8 +117,12 @@ namespace { OUString maStyleName; sal_Int32 mnRepeat; - CustomLabelSeq mCustomLabelText; - chart2::RelativePosition mCustomLabelPos; + chart2::RelativePosition mCustomLabelPos; // loext:custom-label-pos-x and -y + + // 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> + OUString msDataLabelStyleName; // chart:style-name attribute in <chart:data-label> SchXMLDataPointStruct() : mnRepeat( 1 ) {} }; @@ -226,8 +230,8 @@ public: const css::uno::Reference< css::chart2::XDiagram > & xDiagram, bool bExportContent ); - void exportCustomLabel(const CustomLabelSeq & xCustomLabel); - void exportCustomLabelPosition(const chart2::RelativePosition & xCustomLabelPosition); + void exportCustomLabel(const SchXMLDataPointStruct& rPoint); + void exportCustomLabelPosition(const chart2::RelativePosition& xCustomLabelPosition); void exportRegressionCurve( const css::uno::Reference<css::chart2::XDataSeries>& xSeries, @@ -276,15 +280,17 @@ public: namespace { - CustomLabelSeq lcl_getCustomLabelField(sal_Int32 nDataPointIndex, - const uno::Reference< chart2::XDataSeries >& rSeries) + const uno::Reference< chart2::XDataSeries >& rSeries) { - if( !rSeries.is() ) + if (!rSeries.is()) return CustomLabelSeq(); - const SvtSaveOptions::ODFSaneDefaultVersion nCurrentODFVersion(SvtSaveOptions().GetODFSaneDefaultVersion()); - if ((nCurrentODFVersion & SvtSaveOptions::ODFSVER_EXTENDED) == 0) // do not export to ODF 1.3 or older + // 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( + SvtSaveOptions().GetODFSaneDefaultVersion()); + if (nCurrentODFVersion < SvtSaveOptions::ODFSVER_012) return CustomLabelSeq(); if(Reference<beans::XPropertySet> xLabels = rSeries->getDataPointByIndex(nDataPointIndex); xLabels.is()) @@ -2535,7 +2541,64 @@ namespace //no doubles and no texts return false; } + +// ODF has the line and fill properties in a <style:style> element, which is referenced by the +// <chart:data-label> element. But LibreOffice has them as special label properties of the series +// or point respectively. The following method generates ODF from internal API name. +void lcl_createDataLabelProperties( + std::vector<XMLPropertyState>& rDataLabelPropertyStates, + const Reference<beans::XPropertySet>& xPropSet, + const rtl::Reference<XMLChartExportPropertyMapper>& xExpPropMapper) +{ + if (!xExpPropMapper.is() || !xPropSet.is()) + return; + + const uno::Reference<beans::XPropertySetInfo> xInfo(xPropSet->getPropertySetInfo()); + const uno::Reference<beans::XPropertyState> xPropState(xPropSet, uno::UNO_QUERY); + const rtl::Reference<XMLPropertySetMapper>& rPropertySetMapper( + xExpPropMapper->getPropertySetMapper()); + if (!xInfo.is() || !xPropState.is() || !rPropertySetMapper.is()) + return; + + struct API2ODFMapItem + { + OUString sAPIName; + sal_uInt16 nNameSpace; // from include/xmloff/xmlnamespace.hxx + OUString sLocalName; + API2ODFMapItem(const OUString& sAPI, const sal_uInt16 nNS, const OUString& sLocal) + : sAPIName(sAPI) + , nNameSpace(nNS) + , sLocalName(sLocal) + { + } + }; + + const API2ODFMapItem aLabelFoo2ODFArray[] + = { API2ODFMapItem("LabelBorderStyle", XML_NAMESPACE_DRAW, "stroke"), + API2ODFMapItem("LabelBorderWidth", XML_NAMESPACE_SVG, "stroke-width"), + API2ODFMapItem("LabelBorderColor", XML_NAMESPACE_SVG, "stroke-color"), + API2ODFMapItem("LabelBorderDashName", XML_NAMESPACE_DRAW, "stroke-dash"), + API2ODFMapItem("LabelBorderTransparency", XML_NAMESPACE_SVG, "stroke-opacity"), + API2ODFMapItem("LabelFillStyle", XML_NAMESPACE_DRAW, "fill"), + API2ODFMapItem("LabelFillBackground", XML_NAMESPACE_DRAW, "fill-hatch-solid"), + API2ODFMapItem("LabelFillHatchName", XML_NAMESPACE_DRAW, "fill-hatch-name"), + API2ODFMapItem("LabelFillColor", XML_NAMESPACE_DRAW, "fill-color") }; + + for (const auto& rIt : aLabelFoo2ODFArray) + { + if (!xInfo->hasPropertyByName(rIt.sAPIName) + || xPropState->getPropertyState(rIt.sAPIName) != beans::PropertyState_DIRECT_VALUE) + continue; + sal_Int32 nTargetIndex + = rPropertySetMapper->GetEntryIndex(rIt.nNameSpace, rIt.sLocalName, 0); + if (nTargetIndex < 0) + continue; + XMLPropertyState aDataLabelStateItem(nTargetIndex, + xPropSet->getPropertyValue(rIt.sAPIName)); + rDataLabelPropertyStates.emplace_back(aDataLabelStateItem); + } } +} // anonymous namespace void SchXMLExportHelper_Impl::exportSeries( const Reference< chart2::XDiagram > & xNewDiagram, @@ -2552,6 +2615,7 @@ void SchXMLExportHelper_Impl::exportSeries( OUString aFirstYDomainRange; std::vector< XMLPropertyState > aPropertyStates; + std::vector< XMLPropertyState > aDataLabelPropertyStates; const Sequence< Reference< chart2::XCoordinateSystem > > aCooSysSeq( xBCooSysCnt->getCoordinateSystems()); @@ -2862,6 +2926,37 @@ void SchXMLExportHelper_Impl::exportSeries( nSeriesLength, xNewDiagram, bExportContent ); const SvtSaveOptions::ODFSaneDefaultVersion nCurrentODFVersion(SvtSaveOptions().GetODFSaneDefaultVersion()); + + // create <chart:data-label> child element if needed. + if (xPropSet.is() && mxExpPropMapper.is()) + { + // Generate style for <chart:data-label> child element + if (nCurrentODFVersion >= SvtSaveOptions::ODFSVER_012) + { + lcl_createDataLabelProperties(aDataLabelPropertyStates, xPropSet, + mxExpPropMapper); + } + } + if (bExportContent) + { + if (!aDataLabelPropertyStates.empty()) + { + // write style name + AddAutoStyleAttribute(aDataLabelPropertyStates); + // Further content does currently not exist for a <chart:data-label> + // element as child of a <chart:series>. + SvXMLElementExport(mrExport, XML_NAMESPACE_CHART, XML_DATA_LABEL, true, + true); + } + } + else + { + // add the style for the to be <chart:data-label> too + if (!aDataLabelPropertyStates.empty()) + CollectAutoStyle(aDataLabelPropertyStates); + } + aDataLabelPropertyStates.clear(); + if (bExportContent && nCurrentODFVersion & SvtSaveOptions::ODFSVER_EXTENDED) // do not export to ODF 1.3 or older { Sequence< OUString > aSupportedMappings = rChartType->getSupportedPropertyRoles(); @@ -2873,6 +2968,7 @@ void SchXMLExportHelper_Impl::exportSeries( } } aPropertyStates.clear(); + aDataLabelPropertyStates.clear(); } } } @@ -3235,6 +3331,7 @@ void SchXMLExportHelper_Impl::exportDataPoints( uno::Reference< chart2::XDataSeries > xSeries( xSeriesProperties, uno::UNO_QUERY ); std::vector< XMLPropertyState > aPropertyStates; + std::vector<XMLPropertyState> aDataLabelPropertyStates; bool bVaryColorsByPoint = false; Sequence< sal_Int32 > aDataPointSeq; @@ -3272,6 +3369,7 @@ void SchXMLExportHelper_Impl::exportDataPoints( for( nElement = 0; nElement < nSeriesLength; ++nElement ) { aPropertyStates.clear(); + aDataLabelPropertyStates.clear(); uno::Reference< beans::XPropertySet > xPropSet; bool bExportNumFmt = false; if( aAttrPointSet.find( nElement ) != aEndIt ) @@ -3303,25 +3401,46 @@ void SchXMLExportHelper_Impl::exportDataPoints( lcl_exportNumberFormat( "PercentageNumberFormat", xPropSet, mrExport ); } - aPropertyStates = mxExpPropMapper->Filter( xPropSet ); - if( !aPropertyStates.empty() ) + // Generate style for <chart:data-label> child element + if (nCurrentODFVersion >= SvtSaveOptions::ODFSVER_012) { - if( bExportContent ) + lcl_createDataLabelProperties(aDataLabelPropertyStates, xPropSet, + mxExpPropMapper); + } + + aPropertyStates = mxExpPropMapper->Filter(xPropSet); + if (!aPropertyStates.empty() || !aDataLabelPropertyStates.empty()) + { + if (bExportContent) { // write data-point with style - SAL_WARN_IF( maAutoStyleNameQueue.empty(), "xmloff.chart", "Autostyle queue empty!" ); - SchXMLDataPointStruct aPoint; - aPoint.maStyleName = maAutoStyleNameQueue.front(); + if (!aPropertyStates.empty()) + { + SAL_WARN_IF(maAutoStyleNameQueue.empty(), "xmloff.chart", + "Autostyle queue empty!"); + aPoint.maStyleName = maAutoStyleNameQueue.front(); + maAutoStyleNameQueue.pop(); + } + if (!aDataLabelPropertyStates.empty()) + { + SAL_WARN_IF(maAutoStyleNameQueue.empty(), "xmloff.chart", + "Autostyle queue empty!"); + aPoint.msDataLabelStyleName = maAutoStyleNameQueue.front(); + maAutoStyleNameQueue.pop(); + } if(bExportNumFmt) aPoint.mCustomLabelText = lcl_getCustomLabelField(nElement, xSeries); aPoint.mCustomLabelPos = lcl_getCustomLabelPosition(nElement, xSeries); - maAutoStyleNameQueue.pop(); + aDataPointVector.push_back( aPoint ); } else { - CollectAutoStyle( aPropertyStates ); + if (!aPropertyStates.empty()) + CollectAutoStyle(aPropertyStates); + if (!aDataLabelPropertyStates.empty()) + CollectAutoStyle(aDataLabelPropertyStates); } } } @@ -3333,6 +3452,7 @@ void SchXMLExportHelper_Impl::exportDataPoints( for( sal_Int32 nCurrIndex : aDataPointSeq ) { aPropertyStates.clear(); + aDataLabelPropertyStates.clear(); //assuming sorted indices in pPoints if( nCurrIndex<0 || nCurrIndex>=nSeriesLength ) @@ -3366,25 +3486,47 @@ void SchXMLExportHelper_Impl::exportDataPoints( lcl_exportNumberFormat( "PercentageNumberFormat", xPropSet, mrExport ); } + // Generate style for <chart:data-label> child element + if (nCurrentODFVersion >= SvtSaveOptions::ODFSVER_012) + { + lcl_createDataLabelProperties(aDataLabelPropertyStates, xPropSet, + mxExpPropMapper); + } + aPropertyStates = mxExpPropMapper->Filter( xPropSet ); - if( !aPropertyStates.empty() ) + + if (!aPropertyStates.empty() || !aDataLabelPropertyStates.empty()) { if( bExportContent ) { // write data-point with style - SAL_WARN_IF( maAutoStyleNameQueue.empty(), "xmloff.chart", "Autostyle queue empty!" ); SchXMLDataPointStruct aPoint; - aPoint.maStyleName = maAutoStyleNameQueue.front(); + if (!aPropertyStates.empty()) + { + SAL_WARN_IF(maAutoStyleNameQueue.empty(), "xmloff.chart", + "Autostyle queue empty!"); + aPoint.maStyleName = maAutoStyleNameQueue.front(); + maAutoStyleNameQueue.pop(); + } aPoint.mCustomLabelText = lcl_getCustomLabelField(nCurrIndex, xSeries); aPoint.mCustomLabelPos = lcl_getCustomLabelPosition(nCurrIndex, xSeries); - maAutoStyleNameQueue.pop(); + if (!aDataLabelPropertyStates.empty()) + { + SAL_WARN_IF(maAutoStyleNameQueue.empty(), "xmloff.chart", + "Autostyle queue empty!"); + aPoint.msDataLabelStyleName = maAutoStyleNameQueue.front(); + maAutoStyleNameQueue.pop(); + } aDataPointVector.push_back( aPoint ); nLastIndex = nCurrIndex; } else { - CollectAutoStyle( aPropertyStates ); + if (!aPropertyStates.empty()) + CollectAutoStyle(aPropertyStates); + if (!aDataLabelPropertyStates.empty()) + CollectAutoStyle(aDataLabelPropertyStates); } continue; } @@ -3421,8 +3563,11 @@ void SchXMLExportHelper_Impl::exportDataPoints( { aPoint = rPoint; - if( aPoint.maStyleName == aLastPoint.maStyleName && aLastPoint.mCustomLabelText.getLength() < 1 && - aLastPoint.mCustomLabelPos.Primary == 0.0 && aLastPoint.mCustomLabelPos.Secondary == 0.0 ) + if (aPoint.maStyleName == aLastPoint.maStyleName + && aLastPoint.mCustomLabelText.getLength() < 1 + && aLastPoint.mCustomLabelPos.Primary == 0.0 + && aLastPoint.mCustomLabelPos.Secondary == 0.0 + && aPoint.msDataLabelStyleName == aLastPoint.msDataLabelStyleName) aPoint.mnRepeat += aLastPoint.mnRepeat; else if( aLastPoint.mnRepeat > 0 ) { @@ -3443,9 +3588,9 @@ void SchXMLExportHelper_Impl::exportDataPoints( } } nIndex++; - exportCustomLabelPosition(aLastPoint.mCustomLabelPos); + exportCustomLabelPosition(aLastPoint.mCustomLabelPos); // adds attributes SvXMLElementExport aPointElem( mrExport, XML_NAMESPACE_CHART, XML_DATA_POINT, true, true ); - exportCustomLabel(aLastPoint.mCustomLabelText); + exportCustomLabel(aLastPoint); } aLastPoint = aPoint; } @@ -3469,19 +3614,22 @@ void SchXMLExportHelper_Impl::exportDataPoints( } } - exportCustomLabelPosition(aLastPoint.mCustomLabelPos); + exportCustomLabelPosition(aLastPoint.mCustomLabelPos); // adds attributes SvXMLElementExport aPointElem( mrExport, XML_NAMESPACE_CHART, XML_DATA_POINT, true, true ); - exportCustomLabel(aLastPoint.mCustomLabelText); + exportCustomLabel(aLastPoint); } -void SchXMLExportHelper_Impl::exportCustomLabel( const CustomLabelSeq & xCustomLabel ) +void SchXMLExportHelper_Impl::exportCustomLabel(const SchXMLDataPointStruct& rPoint) { - if( xCustomLabel.getLength() < 1 ) + if (rPoint.mCustomLabelText.getLength() < 1 && rPoint.msDataLabelStyleName.isEmpty()) return; // nothing to export + if (!rPoint.msDataLabelStyleName.isEmpty()) + mrExport.AddAttribute(XML_NAMESPACE_CHART, XML_STYLE_NAME, rPoint.msDataLabelStyleName); + // 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 : xCustomLabel ) + for (const Reference<chart2::XDataPointCustomLabelField>& label : rPoint.mCustomLabelText) { // TODO add style SvXMLElementExport aSpan( mrExport, XML_NAMESPACE_TEXT, XML_SPAN, true, false); _______________________________________________ Libreoffice-commits mailing list libreoffice-comm...@lists.freedesktop.org https://lists.freedesktop.org/mailman/listinfo/libreoffice-commits