From 8906275d40a1828db684e7d9c9bc4934a937bc6c Mon Sep 17 00:00:00 2001 From: Balazs Varga Date: Tue, 9 Jul 2019 13:30:16 +0200 Subject: [PATCH] tdf#126193 Chart OOXML: Export Multi-level category labels MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 Tested-by: László Németh --- chart2/qa/extras/chart2export.cxx | 38 +++ chart2/qa/extras/data/ods/multilevelcat.ods | Bin 0 -> 13559 bytes chart2/qa/extras/data/odt/multilevelcat.odt | Bin 0 -> 13942 bytes chart2/source/tools/InternalDataProvider.cxx | 1 + include/oox/export/chartexport.hxx | 3 + oox/source/export/chartexport.cxx | 270 ++++++++++++++++--- 6 files changed, 270 insertions(+), 42 deletions(-) create mode 100644 chart2/qa/extras/data/ods/multilevelcat.ods create mode 100644 chart2/qa/extras/data/odt/multilevelcat.odt 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 0000000000000000000000000000000000000000..76b140a879afc6fda47af7c25f0f478fa3c766fe GIT binary patch literal 13559 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 E4|-FVw*UYD literal 0 HcmV?d00001 diff --git a/chart2/qa/extras/data/odt/multilevelcat.odt b/chart2/qa/extras/data/odt/multilevelcat.odt new file mode 100644 index 0000000000000000000000000000000000000000..8148e1be1089d4fed9f5451d6cb63b3fa30d86c6 GIT binary patch literal 13942 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 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 #include #include +#include #include #include #include #include +#include +#include #include #include @@ -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 getPrimaryDataSeries(const Reference& 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 xSource(aSeriesSeq[nSeriesIdx], uno::UNO_QUERY); + if (xSource.is()) + return xSource; + } + + return Reference(); +} + +} + +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(elemLabel.getLength(), nLevelCount); + } + + if (nLevelCount > 1) + { + //we have complex categories + //sort the categories name + Sequence>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(); + 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>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& xChartTyp exportSeries(xChartType, aSeriesSeq, rPrimaryAxes); } -namespace { - -Reference getPrimaryDataSeries(const Reference& 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 xSource(aSeriesSeq[nSeriesIdx], uno::UNO_QUERY); - if (xSource.is()) - return xSource; - } - - return Reference(); -} - -} - void ChartExport::exportVaryColors(const Reference& 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 ) ); - - ::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++ ) + if(aFinalSplitSource.getLength() > 1) { - 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 multi level category axis labels + pFS->startElement(FSNS(XML_c, XML_multiLvlStrRef)); + + 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 + { + // 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