chart2/qa/extras/chart2export.cxx | 38 +++ chart2/qa/extras/data/ods/multilevelcat.ods |binary chart2/qa/extras/data/odt/multilevelcat.odt |binary chart2/source/tools/InternalDataProvider.cxx | 1 include/oox/export/chartexport.hxx | 3 oox/source/export/chartexport.cxx | 268 ++++++++++++++++++++++----- 6 files changed, 269 insertions(+), 41 deletions(-)
New commits: commit 8906275d40a1828db684e7d9c9bc4934a937bc6c Author: Balazs Varga <balazs.varga...@gmail.com> AuthorDate: Tue Jul 9 13:30:16 2019 +0200 Commit: László Németh <nem...@numbertext.org> CommitDate: Mon Jul 15 11:38:13 2019 +0200 tdf#126193 Chart OOXML: Export Multi-level category labels Fix export of Multi-level category axis labels with the correct OOXML tags (as the OOXML Standard requested) in the correct order. Also fix tdf#126195: but only the export part of the whole fault, which combined (united) the text of the category axis labels at different levels. Change-Id: Iefcef00818a3bb2ee1671bf693335904be471722 Reviewed-on: https://gerrit.libreoffice.org/75299 Reviewed-by: László Németh <nem...@numbertext.org> Tested-by: László Németh <nem...@numbertext.org> diff --git a/chart2/qa/extras/chart2export.cxx b/chart2/qa/extras/chart2export.cxx index f00959bdbfeb..ab932ac2bc68 100644 --- a/chart2/qa/extras/chart2export.cxx +++ b/chart2/qa/extras/chart2export.cxx @@ -128,6 +128,8 @@ public: void testChartTitlePropertiesGradientFillPPTX(); void testChartTitlePropertiesBitmapFillPPTX(); void testxAxisLabelsRotation(); + void testMultipleCategoryAxisLablesXLSX(); + void testMultipleCategoryAxisLablesDOCX(); void testTdf116163(); void testTdf111824(); void testTdf119029(); @@ -226,6 +228,8 @@ public: CPPUNIT_TEST(testChartTitlePropertiesGradientFillPPTX); CPPUNIT_TEST(testChartTitlePropertiesBitmapFillPPTX); CPPUNIT_TEST(testxAxisLabelsRotation); + CPPUNIT_TEST(testMultipleCategoryAxisLablesXLSX); + CPPUNIT_TEST(testMultipleCategoryAxisLablesDOCX); CPPUNIT_TEST(testTdf116163); CPPUNIT_TEST(testTdf111824); CPPUNIT_TEST(testTdf119029); @@ -2092,6 +2096,40 @@ void Chart2ExportTest::testxAxisLabelsRotation() assertXPath(pXmlDoc1, "/c:chartSpace/c:chart/c:plotArea/c:catAx/c:txPr/a:bodyPr", "rot", "2700000"); } +void Chart2ExportTest::testMultipleCategoryAxisLablesXLSX() +{ + load("/chart2/qa/extras/data/ods/", "multilevelcat.ods"); + xmlDocPtr pXmlDoc = parseExport("xl/charts/chart", "Calc Office Open XML"); + CPPUNIT_ASSERT(pXmlDoc); + // check category axis labels number of first level + assertXPath(pXmlDoc, "/c:chartSpace/c:chart/c:plotArea/c:barChart/c:ser[1]/c:cat/c:multiLvlStrRef/c:multiLvlStrCache/c:ptCount", "val", "6"); + // check category axis labels text of first level + assertXPathContent(pXmlDoc, "/c:chartSpace/c:chart/c:plotArea/c:barChart/c:ser[1]/c:cat/c:multiLvlStrRef/c:multiLvlStrCache/c:lvl[1]/c:pt[1]/c:v", "Categoria 1"); + assertXPathContent(pXmlDoc, "/c:chartSpace/c:chart/c:plotArea/c:barChart/c:ser[1]/c:cat/c:multiLvlStrRef/c:multiLvlStrCache/c:lvl[1]/c:pt[6]/c:v", "Categoria 6"); + // check category axis labels text of second level + assertXPathContent(pXmlDoc, "/c:chartSpace/c:chart/c:plotArea/c:barChart/c:ser[1]/c:cat/c:multiLvlStrRef/c:multiLvlStrCache/c:lvl[2]/c:pt[1]/c:v", "2011"); + assertXPathContent(pXmlDoc, "/c:chartSpace/c:chart/c:plotArea/c:barChart/c:ser[1]/c:cat/c:multiLvlStrRef/c:multiLvlStrCache/c:lvl[2]/c:pt[3]/c:v", "2013"); + // check the 'noMultiLvlLbl' tag - ChartExport.cxx:2950 FIXME: seems not support, so check the default noMultiLvlLbl value. + assertXPath(pXmlDoc, "/c:chartSpace/c:chart/c:plotArea/c:catAx/c:noMultiLvlLbl", "val", "0"); +} + +void Chart2ExportTest::testMultipleCategoryAxisLablesDOCX() +{ + load("/chart2/qa/extras/data/odt/", "multilevelcat.odt"); + xmlDocPtr pXmlDoc = parseExport("word/charts/chart", "Office Open XML Text"); + CPPUNIT_ASSERT(pXmlDoc); + // check category axis labels number of first level + assertXPath(pXmlDoc, "/c:chartSpace/c:chart/c:plotArea/c:barChart/c:ser[1]/c:cat/c:multiLvlStrRef/c:multiLvlStrCache/c:ptCount", "val", "4"); + // check category axis labels text of first level + assertXPathContent(pXmlDoc, "/c:chartSpace/c:chart/c:plotArea/c:barChart/c:ser[1]/c:cat/c:multiLvlStrRef/c:multiLvlStrCache/c:lvl[1]/c:pt[1]/c:v", "Categoria 1"); + assertXPathContent(pXmlDoc, "/c:chartSpace/c:chart/c:plotArea/c:barChart/c:ser[1]/c:cat/c:multiLvlStrRef/c:multiLvlStrCache/c:lvl[1]/c:pt[4]/c:v", "Categoria 4"); + // check category axis labels text of second level + assertXPathContent(pXmlDoc, "/c:chartSpace/c:chart/c:plotArea/c:barChart/c:ser[1]/c:cat/c:multiLvlStrRef/c:multiLvlStrCache/c:lvl[2]/c:pt[1]/c:v", "2011"); + assertXPathContent(pXmlDoc, "/c:chartSpace/c:chart/c:plotArea/c:barChart/c:ser[1]/c:cat/c:multiLvlStrRef/c:multiLvlStrCache/c:lvl[2]/c:pt[2]/c:v", "2012"); + // check the 'noMultiLvlLbl' tag - ChartExport.cxx:2950 FIXME: seems not support, so check the default noMultiLvlLbl value. + assertXPath(pXmlDoc, "/c:chartSpace/c:chart/c:plotArea/c:catAx/c:noMultiLvlLbl", "val", "0"); +} + void Chart2ExportTest::testTdf116163() { load("/chart2/qa/extras/data/pptx/", "tdf116163.pptx"); diff --git a/chart2/qa/extras/data/ods/multilevelcat.ods b/chart2/qa/extras/data/ods/multilevelcat.ods new file mode 100644 index 000000000000..76b140a879af Binary files /dev/null and b/chart2/qa/extras/data/ods/multilevelcat.ods differ diff --git a/chart2/qa/extras/data/odt/multilevelcat.odt b/chart2/qa/extras/data/odt/multilevelcat.odt new file mode 100644 index 000000000000..8148e1be1089 Binary files /dev/null and b/chart2/qa/extras/data/odt/multilevelcat.odt differ diff --git a/chart2/source/tools/InternalDataProvider.cxx b/chart2/source/tools/InternalDataProvider.cxx index 86c163e02c4e..5bea86b2d7d3 100644 --- a/chart2/source/tools/InternalDataProvider.cxx +++ b/chart2/source/tools/InternalDataProvider.cxx @@ -899,6 +899,7 @@ Sequence< uno::Any > SAL_CALL InternalDataProvider::getDataByRangeRepresentation } else { + // Maybe this 'else' part and the functions is not necessary anymore. Sequence< OUString > aLabels = m_bDataInColumns ? getRowDescriptions() : getColumnDescriptions(); aResult.realloc( aLabels.getLength() ); transform( aLabels.begin(), aLabels.end(), diff --git a/include/oox/export/chartexport.hxx b/include/oox/export/chartexport.hxx index 4716099dd8df..584b448aed2c 100644 --- a/include/oox/export/chartexport.hxx +++ b/include/oox/export/chartexport.hxx @@ -51,6 +51,7 @@ namespace com { namespace sun { namespace star { namespace data { class XDataSequence; + class XLabeledDataSequence; } } namespace drawing { @@ -120,6 +121,8 @@ private: private: sal_Int32 getChartType(); + css::uno::Sequence< css::uno::Sequence< rtl::OUString > > getSplitCategoriesList(const OUString& rRange); + OUString parseFormula( const OUString& rRange ); void InitPlotArea(); diff --git a/oox/source/export/chartexport.cxx b/oox/source/export/chartexport.cxx index b6c764ddbd8c..5e0d67ba3d3c 100644 --- a/oox/source/export/chartexport.cxx +++ b/oox/source/export/chartexport.cxx @@ -68,10 +68,13 @@ #include <com/sun/star/chart2/data/XDataSink.hpp> #include <com/sun/star/chart2/data/XDataReceiver.hpp> #include <com/sun/star/chart2/data/XDataProvider.hpp> +#include <com/sun/star/chart2/XInternalDataProvider.hpp> #include <com/sun/star/chart2/data/XDatabaseDataProvider.hpp> #include <com/sun/star/chart2/data/XRangeXMLConversion.hpp> #include <com/sun/star/chart2/data/XTextualDataSequence.hpp> #include <com/sun/star/chart2/data/XNumericalDataSequence.hpp> +#include <com/sun/star/chart2/data/XLabeledDataSequence.hpp> +#include <com/sun/star/chart2/XAnyDescriptionAccess.hpp> #include <com/sun/star/beans/XPropertySet.hpp> #include <com/sun/star/drawing/XShape.hpp> @@ -262,7 +265,7 @@ static OUString lcl_flattenStringSequence( const Sequence< OUString > & rSequenc return aResult.makeStringAndClear(); } -static OUString lcl_getLabelString( const Reference< chart2::data::XDataSequence > & xLabelSeq ) +static Sequence< OUString > lcl_getLabelSequence( const Reference< chart2::data::XDataSequence > & xLabelSeq ) { Sequence< OUString > aLabels; @@ -279,7 +282,7 @@ static OUString lcl_getLabelString( const Reference< chart2::data::XDataSequence aAnies[i] >>= aLabels[i]; } - return lcl_flattenStringSequence( aLabels ); + return aLabels; } static void lcl_fillCategoriesIntoStringVector( @@ -396,6 +399,168 @@ sal_Int32 ChartExport::getChartType( ) return lcl_getChartType( sChartType ); } +namespace { + +uno::Sequence< beans::PropertyValue > createArguments( + const OUString & rRangeRepresentation, bool bUseColumns) +{ + css::chart::ChartDataRowSource eRowSource = css::chart::ChartDataRowSource_ROWS; + if (bUseColumns) + eRowSource = css::chart::ChartDataRowSource_COLUMNS; + + uno::Sequence< beans::PropertyValue > aArguments(4); + aArguments[0] = beans::PropertyValue("DataRowSource" + , -1, uno::Any(eRowSource) + , beans::PropertyState_DIRECT_VALUE); + aArguments[1] = beans::PropertyValue("FirstCellAsLabel" + , -1, uno::Any(false) + , beans::PropertyState_DIRECT_VALUE); + aArguments[2] = beans::PropertyValue("HasCategories" + , -1, uno::Any(false) + , beans::PropertyState_DIRECT_VALUE); + aArguments[3] = beans::PropertyValue("CellRangeRepresentation" + , -1, uno::Any(rRangeRepresentation) + , beans::PropertyState_DIRECT_VALUE); + + return aArguments; +} + +Reference<chart2::XDataSeries> getPrimaryDataSeries(const Reference<chart2::XChartType>& xChartType) +{ + Reference< chart2::XDataSeriesContainer > xDSCnt(xChartType, uno::UNO_QUERY_THROW); + + // export dataseries for current chart-type + Sequence< Reference< chart2::XDataSeries > > aSeriesSeq(xDSCnt->getDataSeries()); + for (sal_Int32 nSeriesIdx = 0; nSeriesIdx < aSeriesSeq.getLength(); ++nSeriesIdx) + { + Reference<chart2::XDataSeries> xSource(aSeriesSeq[nSeriesIdx], uno::UNO_QUERY); + if (xSource.is()) + return xSource; + } + + return Reference<chart2::XDataSeries>(); +} + +} + +Sequence< Sequence< OUString > > ChartExport::getSplitCategoriesList( const OUString& rRange ) +{ + Reference< chart2::XChartDocument > xChartDoc(getModel(), uno::UNO_QUERY); + OSL_ASSERT(xChartDoc.is()); + if (xChartDoc.is()) + { + Reference< chart2::data::XDataProvider > xDataProvider(xChartDoc->getDataProvider()); + OSL_ENSURE(xDataProvider.is(), "No DataProvider"); + if (xDataProvider.is()) + { + //detect whether the first series is a row or a column + bool bSeriesUsesColumns = true; + Reference< chart2::XDiagram > xDiagram(xChartDoc->getFirstDiagram()); + try + { + Reference< chart2::XCoordinateSystemContainer > xCooSysCnt(xDiagram, uno::UNO_QUERY_THROW); + Sequence< Reference< chart2::XCoordinateSystem > > aCooSysSeq(xCooSysCnt->getCoordinateSystems()); + for (sal_Int32 i = 0; i < aCooSysSeq.getLength(); ++i) + { + const Reference< chart2::XChartTypeContainer > xCTCnt(aCooSysSeq[i], uno::UNO_QUERY_THROW); + const Sequence< Reference< chart2::XChartType > > aChartTypeSeq(xCTCnt->getChartTypes()); + for (sal_Int32 j = 0; j < aChartTypeSeq.getLength(); ++j) + { + Reference< chart2::XDataSeries > xDataSeries = getPrimaryDataSeries(aChartTypeSeq[j]); + if (xDataSeries.is()) + { + uno::Reference< chart2::data::XDataSource > xSeriesSource(xDataSeries, uno::UNO_QUERY); + const uno::Sequence< beans::PropertyValue > rArguments = xDataProvider->detectArguments(xSeriesSource); + for (const beans::PropertyValue& rProperty : rArguments) + { + if (rProperty.Name == "DataRowSource") + { + css::chart::ChartDataRowSource eRowSource; + if (rProperty.Value >>= eRowSource) + { + bSeriesUsesColumns = (eRowSource == css::chart::ChartDataRowSource_COLUMNS); + break; + } + } + } + } + } + } + } + catch (const uno::Exception &) + { + DBG_UNHANDLED_EXCEPTION("chart2"); + } + // detect we have an inner data table or not + if (xChartDoc->hasInternalDataProvider() && rRange == "categories") + { + try + { + css::uno::Reference< css::chart2::XAnyDescriptionAccess > xDataAccess(xChartDoc->getDataProvider(), uno::UNO_QUERY); + const Sequence< Sequence< uno::Any > >aAnyCategories(bSeriesUsesColumns ? xDataAccess->getAnyRowDescriptions() : xDataAccess->getAnyColumnDescriptions()); + sal_Int32 nLevelCount = 1;//minimum is 1! + for (auto const& elemLabel : aAnyCategories) + { + nLevelCount = std::max<sal_Int32>(elemLabel.getLength(), nLevelCount); + } + + if (nLevelCount > 1) + { + //we have complex categories + //sort the categories name + Sequence<Sequence<OUString>>aFinalSplitSource(nLevelCount); + for (sal_Int32 i = 0; i < nLevelCount; i++) + { + sal_Int32 nElemLabel = 0; + aFinalSplitSource[nLevelCount - i - 1].realloc(aAnyCategories.getLength()); + for (auto const& elemLabel : aAnyCategories) + { + aFinalSplitSource[nLevelCount - i - 1][nElemLabel] = elemLabel[i].get<OUString>(); + nElemLabel++; + } + } + return aFinalSplitSource; + } + } + catch (const uno::Exception &) + { + DBG_UNHANDLED_EXCEPTION("oox"); + } + } + else + { + try + { + uno::Reference< chart2::data::XDataSource > xCategoriesSource(xDataProvider->createDataSource( + createArguments(rRange, bSeriesUsesColumns))); + + if (xCategoriesSource.is()) + { + Sequence< Reference< chart2::data::XLabeledDataSequence >> aCategories = xCategoriesSource->getDataSequences(); + if (aCategories.getLength() > 1) + { + //we have complex categories + //sort the categories name + Sequence<Sequence<OUString>>aFinalSplitSource(aCategories.getLength()); + for (sal_Int32 i = 0; i < aFinalSplitSource.getLength(); i++) { + const uno::Reference< chart2::data::XDataSequence > xCategories(aCategories[i]->getValues(), uno::UNO_QUERY); + aFinalSplitSource[aFinalSplitSource.getLength() - i - 1] = lcl_getLabelSequence(xCategories); + } + return aFinalSplitSource; + } + } + } + catch (const uno::Exception &) + { + DBG_UNHANDLED_EXCEPTION("oox"); + } + } + } + } + + return Sequence< Sequence< OUString>>(0); +} + OUString ChartExport::parseFormula( const OUString& rRange ) { OUString aResult; @@ -1803,26 +1968,6 @@ void ChartExport::exportAllSeries(const Reference<chart2::XChartType>& xChartTyp exportSeries(xChartType, aSeriesSeq, rPrimaryAxes); } -namespace { - -Reference<chart2::XDataSeries> getPrimaryDataSeries(const Reference<chart2::XChartType>& xChartType) -{ - Reference< chart2::XDataSeriesContainer > xDSCnt(xChartType, uno::UNO_QUERY_THROW); - - // export dataseries for current chart-type - Sequence< Reference< chart2::XDataSeries > > aSeriesSeq(xDSCnt->getDataSeries()); - for (sal_Int32 nSeriesIdx=0; nSeriesIdx < aSeriesSeq.getLength(); ++nSeriesIdx) - { - Reference<chart2::XDataSeries> xSource(aSeriesSeq[nSeriesIdx], uno::UNO_QUERY); - if (xSource.is()) - return xSource; - } - - return Reference<chart2::XDataSeries>(); -} - -} - void ChartExport::exportVaryColors(const Reference<chart2::XChartType>& xChartType) { FSHelperPtr pFS = GetFS(); @@ -2108,7 +2253,7 @@ void ChartExport::exportSeriesText( const Reference< chart2::data::XDataSequence pFS->writeEscaped( aCellRange ); pFS->endElement( FSNS( XML_c, XML_f ) ); - OUString aLabelString = lcl_getLabelString( xValueSeq ); + OUString aLabelString = lcl_flattenStringSequence(lcl_getLabelSequence(xValueSeq)); pFS->startElement(FSNS(XML_c, XML_strCache)); pFS->singleElement(FSNS(XML_c, XML_ptCount), XML_val, "1"); pFS->startElement(FSNS(XML_c, XML_pt), XML_idx, "0"); @@ -2127,30 +2272,68 @@ void ChartExport::exportSeriesCategory( const Reference< chart2::data::XDataSequ pFS->startElement(FSNS(XML_c, XML_cat)); OUString aCellRange = xValueSeq.is() ? xValueSeq->getSourceRangeRepresentation() : OUString(); + Sequence< Sequence< OUString >> aFinalSplitSource = getSplitCategoriesList(aCellRange); aCellRange = parseFormula( aCellRange ); - // TODO: need to handle XML_multiLvlStrRef according to aCellRange - pFS->startElement(FSNS(XML_c, XML_strRef)); - pFS->startElement(FSNS(XML_c, XML_f)); - pFS->writeEscaped( aCellRange ); - pFS->endElement( FSNS( XML_c, XML_f ) ); + if(aFinalSplitSource.getLength() > 1) + { + // export multi level category axis labels + pFS->startElement(FSNS(XML_c, XML_multiLvlStrRef)); - ::std::vector< OUString > aCategories; - lcl_fillCategoriesIntoStringVector( xValueSeq, aCategories ); - sal_Int32 ptCount = aCategories.size(); - pFS->startElement(FSNS(XML_c, XML_strCache)); - pFS->singleElement(FSNS(XML_c, XML_ptCount), XML_val, OString::number(ptCount)); - for( sal_Int32 i = 0; i < ptCount; i++ ) + pFS->startElement(FSNS(XML_c, XML_f)); + pFS->writeEscaped(aCellRange); + pFS->endElement(FSNS(XML_c, XML_f)); + + sal_Int32 ptCount = aFinalSplitSource.getLength(); + pFS->startElement(FSNS(XML_c, XML_multiLvlStrCache)); + pFS->singleElement(FSNS(XML_c, XML_ptCount), XML_val, OString::number(aFinalSplitSource[0].getLength())); + for(sal_Int32 i = 0; i < ptCount; i++) + { + pFS->startElement(FSNS(XML_c, XML_lvl)); + for(sal_Int32 j = 0; j < aFinalSplitSource[i].getLength(); j++) + { + if(!aFinalSplitSource[i][j].isEmpty()) + { + pFS->startElement(FSNS(XML_c, XML_pt), XML_idx, OString::number(j)); + pFS->startElement(FSNS(XML_c, XML_v)); + pFS->writeEscaped(aFinalSplitSource[i][j]); + pFS->endElement(FSNS(XML_c, XML_v)); + pFS->endElement(FSNS(XML_c, XML_pt)); + } + } + pFS->endElement(FSNS(XML_c, XML_lvl)); + } + + pFS->endElement(FSNS(XML_c, XML_multiLvlStrCache)); + pFS->endElement(FSNS(XML_c, XML_multiLvlStrRef)); + } + else { - pFS->startElement(FSNS(XML_c, XML_pt), XML_idx, OString::number(i)); - pFS->startElement(FSNS(XML_c, XML_v)); - pFS->writeEscaped( aCategories[i] ); - pFS->endElement( FSNS( XML_c, XML_v ) ); - pFS->endElement( FSNS( XML_c, XML_pt ) ); + // export single category axis labels + pFS->startElement(FSNS(XML_c, XML_strRef)); + + pFS->startElement(FSNS(XML_c, XML_f)); + pFS->writeEscaped(aCellRange); + pFS->endElement(FSNS(XML_c, XML_f)); + + ::std::vector< OUString > aCategories; + lcl_fillCategoriesIntoStringVector(xValueSeq, aCategories); + sal_Int32 ptCount = aCategories.size(); + pFS->startElement(FSNS(XML_c, XML_strCache)); + pFS->singleElement(FSNS(XML_c, XML_ptCount), XML_val, OString::number(ptCount)); + for (sal_Int32 i = 0; i < ptCount; i++) + { + pFS->startElement(FSNS(XML_c, XML_pt), XML_idx, OString::number(i)); + pFS->startElement(FSNS(XML_c, XML_v)); + pFS->writeEscaped(aCategories[i]); + pFS->endElement(FSNS(XML_c, XML_v)); + pFS->endElement(FSNS(XML_c, XML_pt)); + } + + pFS->endElement(FSNS(XML_c, XML_strCache)); + pFS->endElement(FSNS(XML_c, XML_strRef)); } - pFS->endElement( FSNS( XML_c, XML_strCache ) ); - pFS->endElement( FSNS( XML_c, XML_strRef ) ); pFS->endElement( FSNS( XML_c, XML_cat ) ); } @@ -2708,6 +2891,9 @@ void ChartExport::_exportAxis( // FIXME: seems not support? lblOffset pFS->singleElement(FSNS(XML_c, XML_lblOffset), XML_val, OString::number(100)); + + // FIXME: seems not support? noMultiLvlLbl + pFS->singleElement(FSNS(XML_c, XML_noMultiLvlLbl), XML_val, OString::number(0)); } // TODO: MSO does not support random axis cross position for _______________________________________________ Libreoffice-commits mailing list libreoffice-comm...@lists.freedesktop.org https://lists.freedesktop.org/mailman/listinfo/libreoffice-commits