chart2/qa/extras/chart2export2.cxx | 15 chart2/qa/extras/data/xlsx/funnel1.xlsx |binary include/oox/export/chartexport.hxx | 16 oox/source/drawingml/chart/plotareacontext.cxx | 67 -- oox/source/drawingml/chart/seriescontext.cxx | 55 +- oox/source/drawingml/chart/typegroupcontext.cxx | 36 + oox/source/export/chartexport.cxx | 611 +++++++++++++++--------- oox/source/token/tokens.txt | 7 test/source/xmltesttools.cxx | 2 9 files changed, 496 insertions(+), 313 deletions(-)
New commits: commit ae939db6ed131b2488ca9ff81b53610838bff178 Author: Kurt Nordback <kurt.nordb...@collabora.com> AuthorDate: Mon Jun 9 08:40:13 2025 -0600 Commit: Tomaž Vajngerl <qui...@gmail.com> CommitDate: Thu Jun 19 09:51:14 2025 +0200 tdf#165742 Step 4.4: Establish a narrow export path for chartex This is a subtask of tdf#165742: Chartex charts are lost on input from OOXML and re-export. Fix chartex parsing so as to properly handle series. This code considers the <cx:plotAreaRegion> tag to be equivalent to the chart 'type group' tags (<c:barChart>, <c:areaChart>, etc.). Therefore it allows use of the type group mechanisms in chartex, even though there is not strictly a tag for type group. This also includes a basic CI test for the ability to round-trip (OOXML -> LO -> OOXML) simple chartex content. Change-Id: Ibe96a239aa5329897b8e0652ce7388a7c398dda2 Reviewed-on: https://gerrit.libreoffice.org/c/core/+/186298 Tested-by: Jenkins Reviewed-by: Tomaž Vajngerl <qui...@gmail.com> diff --git a/chart2/qa/extras/chart2export2.cxx b/chart2/qa/extras/chart2export2.cxx index 31d936829eaa..e7f4bf9bee64 100644 --- a/chart2/qa/extras/chart2export2.cxx +++ b/chart2/qa/extras/chart2export2.cxx @@ -153,6 +153,21 @@ CPPUNIT_TEST_FIXTURE(Chart2ExportTest2, testCrossBetweenODS) u"between"); } +CPPUNIT_TEST_FIXTURE(Chart2ExportTest2, testChartexTitleXLSX) +{ + loadFromFile(u"xlsx/funnel1.xlsx"); + save(u"Calc Office Open XML"_ustr); + xmlDocUniquePtr pXmlDoc = parseExport(u"xl/charts/chartEx1.xml"_ustr); + CPPUNIT_ASSERT(pXmlDoc); + + assertXPath(pXmlDoc, "/cx:chartSpace/cx:chart/cx:plotArea/cx:plotAreaRegion/cx:series", + "layoutId", u"funnel"); + assertXPath(pXmlDoc, + "/cx:chartSpace/cx:chart/cx:plotArea/cx:plotAreaRegion/cx:series/cx:spPr/" + "a:solidFill/a:srgbClr", + "val", u"c55a11"); +} + CPPUNIT_TEST_FIXTURE(Chart2ExportTest2, testAxisTitleRotationXLSX) { loadFromFile(u"xlsx/axis_title_rotation.xlsx"); diff --git a/chart2/qa/extras/data/xlsx/funnel1.xlsx b/chart2/qa/extras/data/xlsx/funnel1.xlsx new file mode 100644 index 000000000000..7ae3e6c32a59 Binary files /dev/null and b/chart2/qa/extras/data/xlsx/funnel1.xlsx differ diff --git a/include/oox/export/chartexport.hxx b/include/oox/export/chartexport.hxx index 3359a44f7516..2d745d295986 100644 --- a/include/oox/export/chartexport.hxx +++ b/include/oox/export/chartexport.hxx @@ -229,7 +229,7 @@ private: css::chart2::XDataSeries > > & aSeriesSeq, bool& rPrimaryAxes ); void exportSeriesText( - const css::uno::Reference< css::chart2::data::XDataSequence >& xValueSeq ); + const css::uno::Reference< css::chart2::data::XDataSequence >& xValueSeq, bool bIsChartex ); void exportSeriesCategory( const css::uno::Reference< css::chart2::data::XDataSequence >& xValueSeq, sal_Int32 nValueType = XML_cat ); void exportSeriesValues( @@ -256,16 +256,22 @@ private: void exportAxes( bool bIsChartex ); void exportAxis(const AxisIdPair& rAxisIdPair, bool bIsChartex); - void _exportAxis( + void exportOneAxis_chart( const css::uno::Reference< css::beans::XPropertySet >& xAxisProp, const css::uno::Reference< css::drawing::XShape >& xAxisTitle, const css::uno::Reference< css::beans::XPropertySet >& xMajorGrid, const css::uno::Reference< css::beans::XPropertySet >& xMinorGrid, sal_Int32 nAxisType, const char* sAxisPos, - const AxisIdPair& rAxisIdPair, - bool bIsChartex); - void exportAxesId(bool bPrimaryAxes, bool bCheckCombinedAxes = false); + const AxisIdPair& rAxisIdPair); + void exportOneAxis_chartex( + const css::uno::Reference< css::beans::XPropertySet >& xAxisProp, + const css::uno::Reference< css::drawing::XShape >& xAxisTitle, + const css::uno::Reference< css::beans::XPropertySet >& xMajorGrid, + const css::uno::Reference< css::beans::XPropertySet >& xMinorGrid, + sal_Int32 nAxisType, + const AxisIdPair& rAxisIdPair); + void createAxes(bool bPrimaryAxes, bool bCheckCombinedAxes); void exportView3D(); bool isDeep3dChart(); // Determine if (at least one) chart exported is from the 2014 chartex diff --git a/oox/source/drawingml/chart/plotareacontext.cxx b/oox/source/drawingml/chart/plotareacontext.cxx index 0b958d1410ea..98b0b31a04fb 100644 --- a/oox/source/drawingml/chart/plotareacontext.cxx +++ b/oox/source/drawingml/chart/plotareacontext.cxx @@ -116,7 +116,7 @@ PlotAreaContext::~PlotAreaContext() { } -ContextHandlerRef PlotAreaContext::onCreateContext( sal_Int32 nElement, const AttributeList& rAttribs) +ContextHandlerRef PlotAreaContext::onCreateContext( sal_Int32 nElement, [[maybe_unused]]const AttributeList& rAttribs) { bool bMSO2007Doc = getFilter().isMSO2007Document(); switch( getCurrentElement() ) @@ -170,7 +170,7 @@ ContextHandlerRef PlotAreaContext::onCreateContext( sal_Int32 nElement, const At case CX_TOKEN(plotArea) : switch (nElement) { case CX_TOKEN(plotAreaRegion) : - return this; + return new ChartexTypeGroupContext(*this, mrModel.maTypeGroups.create(nElement, false)); case CX_TOKEN(axis) : // TODO return nullptr; @@ -181,69 +181,6 @@ ContextHandlerRef PlotAreaContext::onCreateContext( sal_Int32 nElement, const At return nullptr; } break; - case CX_TOKEN(plotAreaRegion) : - switch (nElement) { - case CX_TOKEN(plotSurface) : - // TODO - return nullptr; - case CX_TOKEN(series) : - if (rAttribs.hasAttribute(XML_layoutId)) { - sal_Int32 nChartType = 0; - OUString sChartId = rAttribs.getStringDefaulted(XML_layoutId); - assert(!sChartId.isEmpty()); - - if (sChartId == "boxWhisker") { - nChartType = CX_TOKEN(boxWhisker); - } else if (sChartId == "clusteredColumn") { - nChartType = CX_TOKEN(clusteredColumn); - } else if (sChartId == "funnel") { - nChartType = CX_TOKEN(funnel); - } else if (sChartId == "paretoLine") { - nChartType = CX_TOKEN(paretoLine); - } else if (sChartId == "regionMap") { - nChartType = CX_TOKEN(regionMap); - } else if (sChartId == "sunburst") { - nChartType = CX_TOKEN(sunburst); - } else if (sChartId == "treemap") { - nChartType = CX_TOKEN(treemap); - } else if (sChartId == "waterfall") { - nChartType = CX_TOKEN(waterfall); - } - assert(nChartType != 0); - - // This is a little awkward. The existing parsing - // structures are set up for the ECMA-376 charts, which - // are structured in the XML as - // ... - // <c:plotArea> - // <c:barChart> (or whatever) - // <c:series ... /> - // <c:barChart/> - // <c:plotArea/> - // - // By contrast, chartex is like this: - // ... - // <cx:plotArea> - // <cx:plotAreaRegion> - // <cx:series layoutId="funnel" ... /> (or other chart type) - // <cx:plotAreaRegion/> - // <cx:plotArea/> - // - // The best way I've figured out to bridge this - // difference is via the explicit CreateSeries() call - // below, since the structure wants a TypeGroup but - // we're already in the series handling. There may well - // be a better solution. - rtl::Reference<ChartexTypeGroupContext> rTGCtx = new ChartexTypeGroupContext( *this, - mrModel.maTypeGroups.create( nChartType, false ) ); - rTGCtx->CreateSeries(); - - return rTGCtx; - } - break; - - } - break; } return nullptr; } diff --git a/oox/source/drawingml/chart/seriescontext.cxx b/oox/source/drawingml/chart/seriescontext.cxx index 939b1a76071a..4b3d18624c8c 100644 --- a/oox/source/drawingml/chart/seriescontext.cxx +++ b/oox/source/drawingml/chart/seriescontext.cxx @@ -410,6 +410,7 @@ ContextHandlerRef SeriesContextBase::onCreateContext( sal_Int32 nElement, const switch( getCurrentElement() ) { case C_TOKEN( ser ): + case CX_TOKEN( series ): switch( nElement ) { case C_TOKEN( idx ): @@ -419,10 +420,13 @@ ContextHandlerRef SeriesContextBase::onCreateContext( sal_Int32 nElement, const mrModel.mnOrder = rAttribs.getInteger( XML_val, -1 ); return nullptr; case C_TOKEN( spPr ): + case CX_TOKEN( spPr ): return new ShapePropertiesContext( *this, mrModel.mxShapeProp.create() ); case C_TOKEN( tx ): + case CX_TOKEN( tx ): return new TextContext( *this, mrModel.mxText.create() ); case C_TOKEN( extLst ): + case CX_TOKEN( extLst ): return this; } break; @@ -442,9 +446,11 @@ ContextHandlerRef SeriesContextBase::onCreateContext( sal_Int32 nElement, const break; case C_TOKEN( extLst ): + case CX_TOKEN( extLst ): switch( nElement ) { case C_TOKEN( ext ): + case CX_TOKEN( ext ): if (mrModel.maSources.has( SeriesModel::DATALABELS )) break; @@ -768,32 +774,29 @@ ContextHandlerRef ChartexSeriesContext::onCreateContext( sal_Int32 nElement, con { switch( getCurrentElement() ) { - case CX_TOKEN( tx ): - return new TextContext( *this, mrModel.mxText.create() ); - case CX_TOKEN( spPr ): - return new ShapePropertiesContext( *this, mrModel.mxShapeProp.create() ); - case CX_TOKEN( valueColors ): - // TODO - return nullptr; - case CX_TOKEN( valueColorPositions ): - // TODO - return nullptr; - case CX_TOKEN( dataPt ): - return new DataPointContext( *this, mrModel.maPoints.create(false) ); - case CX_TOKEN( dataLabels ): - return new DataLabelsContext( *this, mrModel.mxLabels.create(false) ); - case CX_TOKEN( dataId ): - // TODO - return nullptr; - case CX_TOKEN( layoutPr ): - // This looks complicated. TODO - return nullptr; - case CX_TOKEN( axisId ): - // TODO - return nullptr; - case CX_TOKEN( extLst ): - // TODO - return nullptr; + case CX_TOKEN( series ): + switch( nElement ) + { + case CX_TOKEN( valueColors ): + // TODO + return nullptr; + case CX_TOKEN( valueColorPositions ): + // TODO + return nullptr; + case CX_TOKEN( dataPt ): + return new DataPointContext( *this, mrModel.maPoints.create(false) ); + case CX_TOKEN( dataLabels ): + return new DataLabelsContext( *this, mrModel.mxLabels.create(false) ); + case CX_TOKEN( dataId ): + // TODO + return nullptr; + case CX_TOKEN( layoutPr ): + // This looks complicated. TODO + return nullptr; + case CX_TOKEN( axisId ): + // TODO + return nullptr; + } } return SeriesContextBase::onCreateContext( nElement, rAttribs ); } diff --git a/oox/source/drawingml/chart/typegroupcontext.cxx b/oox/source/drawingml/chart/typegroupcontext.cxx index 29d2bdb46637..779409983374 100644 --- a/oox/source/drawingml/chart/typegroupcontext.cxx +++ b/oox/source/drawingml/chart/typegroupcontext.cxx @@ -414,12 +414,40 @@ ContextHandlerRef ChartexTypeGroupContext::onCreateContext( [[maybe_unused]] sal [[maybe_unused]] const AttributeList& rAttribs ) { if (isRootElement()) switch (nElement) { - case CX_TOKEN(dataLabels): - // TODO - return nullptr; - case CX_TOKEN(dataId): + case CX_TOKEN(plotSurface) : // TODO return nullptr; + case CX_TOKEN(series) : + if (rAttribs.hasAttribute(XML_layoutId)) { + // The type ID is currently set to <cx:plotAreaRegion>. Set it + // to the specific chart type based on the layoutId attribute + assert(mrModel.mnTypeId == CX_TOKEN(plotAreaRegion)); + OUString sChartId = rAttribs.getStringDefaulted(XML_layoutId); + assert(!sChartId.isEmpty()); + + if (sChartId == "boxWhisker") { + mrModel.mnTypeId = CX_TOKEN(boxWhisker); + } else if (sChartId == "clusteredColumn") { + mrModel.mnTypeId = CX_TOKEN(clusteredColumn); + } else if (sChartId == "funnel") { + mrModel.mnTypeId = CX_TOKEN(funnel); + } else if (sChartId == "paretoLine") { + mrModel.mnTypeId = CX_TOKEN(paretoLine); + } else if (sChartId == "regionMap") { + mrModel.mnTypeId = CX_TOKEN(regionMap); + } else if (sChartId == "sunburst") { + mrModel.mnTypeId = CX_TOKEN(sunburst); + } else if (sChartId == "treemap") { + mrModel.mnTypeId = CX_TOKEN(treemap); + } else if (sChartId == "waterfall") { + mrModel.mnTypeId = CX_TOKEN(waterfall); + } else { + assert(false); + } + + return new ChartexSeriesContext(*this, mrModel.maSeries.create(false)); + } + break; } return nullptr; diff --git a/oox/source/export/chartexport.cxx b/oox/source/export/chartexport.cxx index 1c3bdfb7388b..d99e6e3180e1 100644 --- a/oox/source/export/chartexport.cxx +++ b/oox/source/export/chartexport.cxx @@ -451,6 +451,20 @@ static OUString lcl_flattenStringSequence( const Sequence< OUString > & rSequenc return aResult.makeStringAndClear(); } +static void lcl_writeChartexString(FSHelperPtr pFS, std::u16string_view sOut) +{ + pFS->startElement(FSNS(XML_cx, XML_tx)); + // cell range doesn't seem to be supported in chartex? + // TODO: also handle <cx:rich> + pFS->startElement(FSNS(XML_cx, XML_txData)); + // TODO: also handle <cx:f> <cx:v> + pFS->startElement(FSNS(XML_cx, XML_v)); + pFS->writeEscaped(sOut); + pFS->endElement( FSNS( XML_cx, XML_v ) ); + pFS->endElement( FSNS( XML_cx, XML_txData ) ); + pFS->endElement( FSNS( XML_cx, XML_tx ) ); +} + static Sequence< OUString > lcl_getLabelSequence( const Reference< chart2::data::XDataSequence > & xLabelSeq ) { Sequence< OUString > aLabels; @@ -1269,7 +1283,6 @@ void ChartExport::exportChartSpace( const Reference< css::chart::XChartDocument // TODO: printSettings // TODO: style // TODO: text properties - // TODO: shape properties Reference< XPropertySet > xPropSet = xChartDoc->getArea(); if( xPropSet.is() ) exportShapeProps( xPropSet, bIsChartex ); @@ -1850,16 +1863,7 @@ void ChartExport::exportTitle( const Reference< XShape >& xShape, bool bIsCharte if (bIsChartex) { pFS->startElement(FSNS(XML_cx, XML_title)); - pFS->startElement(FSNS(XML_cx, XML_tx)); - pFS->startElement(FSNS(XML_cx, XML_txData)); - pFS->startElement(FSNS(XML_cx, XML_v)); - - // TODO: this is probably not right? - pFS->writeEscaped( xFormattedTitle[0]->getString() ); - - pFS->endElement(FSNS(XML_cx, XML_v)); - pFS->endElement(FSNS(XML_cx, XML_txData)); - pFS->endElement(FSNS(XML_cx, XML_tx)); + lcl_writeChartexString(pFS, xFormattedTitle[0]->getString()); } else { pFS->startElement(FSNS(XML_c, XML_title)); pFS->startElement(FSNS(XML_c, XML_tx)); @@ -1946,7 +1950,8 @@ void ChartExport::exportTitle( const Reference< XShape >& xShape, bool bIsCharte if (aManualLayout.hasValue()) { if (bIsChartex) { - // TODO + // TODO. Chartex doesn't have a manualLayout tag, but does have + // "pos" and "align" attributes. Not sure how these correspond. } else { pFS->startElement(FSNS(XML_c, XML_layout)); pFS->startElement(FSNS(XML_c, XML_manualLayout)); @@ -2105,7 +2110,7 @@ void ChartExport::exportPlotArea(const Reference< css::chart::XChartDocument >& pFS->singleElement(FSNS(XML_c, XML_barDir), XML_val, "col"); pFS->singleElement(FSNS(XML_c, XML_grouping), XML_val, "clustered"); pFS->singleElement(FSNS(XML_c, XML_varyColors), XML_val, "0"); - exportAxesId(true); + createAxes(true, false); pFS->endElement(FSNS(XML_c, XML_barChart)); } @@ -2234,10 +2239,11 @@ void ChartExport::exportPlotArea(const Reference< css::chart::XChartDocument >& } //Axis Data - exportAxes( bIsChartex ); + exportAxes(bIsChartex); - if (!bIsChartex) { // not supported in chartex? + if (!bIsChartex) { // Data Table + // not supported in chartex? exportDataTable(); } @@ -2615,7 +2621,7 @@ void ChartExport::exportAreaChart( const Reference< chart2::XChartType >& xChart exportGrouping(); bool bPrimaryAxes = true; exportSeries(xChartType, splitDataSeries, bPrimaryAxes, false); - exportAxesId(bPrimaryAxes); + createAxes(bPrimaryAxes, false); pFS->endElement(FSNS(XML_c, nTypeId)); } @@ -2715,7 +2721,7 @@ void ChartExport::exportBarChart(const Reference< chart2::XChartType >& xChartTy } } - exportAxesId(bPrimaryAxes); + createAxes(bPrimaryAxes, false); pFS->endElement(FSNS(XML_c, nTypeId)); } @@ -2737,7 +2743,7 @@ void ChartExport::exportBubbleChart( const Reference< chart2::XChartType >& xCha bool bPrimaryAxes = true; exportSeries(xChartType, splitDataSeries, bPrimaryAxes, false); - exportAxesId(bPrimaryAxes); + createAxes(bPrimaryAxes, false); pFS->endElement(FSNS(XML_c, XML_bubbleChart)); } @@ -2759,9 +2765,6 @@ void ChartExport::exportFunnelChart( const Reference< chart2::XChartType >& xCha bool bPrimaryAxes = false; exportSeries(xChartType, splitDataSeries, bPrimaryAxes, true); - // TODO: in chartex, axis is an element of cx:plotArea - //exportAxesId(bPrimaryAxes); - pFS->endElement(FSNS(XML_cx, XML_series)); } } @@ -2876,7 +2879,7 @@ void ChartExport::exportLineChart( const Reference< chart2::XChartType >& xChart pFS->singleElement(FSNS(XML_c, XML_marker), XML_val, marker); } - exportAxesId(bPrimaryAxes, true); + createAxes(bPrimaryAxes, true); pFS->endElement( FSNS( XML_c, nTypeId ) ); } @@ -2921,7 +2924,7 @@ void ChartExport::exportRadarChart( const Reference< chart2::XChartType >& xChar exportVaryColors(xChartType); bool bPrimaryAxes = true; exportAllSeries(xChartType, bPrimaryAxes); - exportAxesId(bPrimaryAxes); + createAxes(bPrimaryAxes, false); pFS->endElement( FSNS( XML_c, XML_radarChart ) ); } @@ -2951,7 +2954,7 @@ void ChartExport::exportScatterChartSeries( const Reference< chart2::XChartType bool bPrimaryAxes = true; if (pSeries) exportSeries(xChartType, *pSeries, bPrimaryAxes, false); - exportAxesId(bPrimaryAxes); + createAxes(bPrimaryAxes, false); pFS->endElement( FSNS( XML_c, XML_scatterChart ) ); } @@ -2994,7 +2997,7 @@ void ChartExport::exportStockChart( const Reference< chart2::XChartType >& xChar exportUpDownBars(xChartType); } - exportAxesId(bPrimaryAxes); + createAxes(bPrimaryAxes, false); pFS->endElement(FSNS(XML_c, XML_stockChart)); } @@ -3069,7 +3072,7 @@ void ChartExport::exportSurfaceChart( const Reference< chart2::XChartType >& xCh exportVaryColors(xChartType); bool bPrimaryAxes = true; exportAllSeries(xChartType, bPrimaryAxes); - exportAxesId(bPrimaryAxes); + createAxes(bPrimaryAxes, false); pFS->endElement( FSNS( XML_c, nTypeId ) ); } @@ -3152,167 +3155,167 @@ void ChartExport::exportSeries( const Reference<chart2::XChartType>& xChartType, // have found the main sequence, then xValuesSeq and // xLabelSeq contain those. Otherwise both are empty - { - FSHelperPtr pFS = GetFS(); + FSHelperPtr pFS = GetFS(); - if (!bIsChartex) { - pFS->startElement(FSNS(XML_c, XML_ser)); + if (!bIsChartex) { + pFS->startElement(FSNS(XML_c, XML_ser)); - // TODO: idx and order - pFS->singleElement( FSNS( XML_c, XML_idx ), - XML_val, OString::number(mnSeriesCount) ); - pFS->singleElement( FSNS( XML_c, XML_order ), - XML_val, OString::number(mnSeriesCount++) ); + // TODO: idx and order + pFS->singleElement( FSNS( XML_c, XML_idx ), + XML_val, OString::number(mnSeriesCount) ); + pFS->singleElement( FSNS( XML_c, XML_order ), + XML_val, OString::number(mnSeriesCount++) ); + } - // export label - if( xLabelSeq.is() ) - exportSeriesText( xLabelSeq ); + // export label + if( xLabelSeq.is() ) + exportSeriesText( xLabelSeq, bIsChartex ); - Reference<XPropertySet> xPropSet(xDataSeries, UNO_QUERY_THROW); - if( GetProperty( xPropSet, u"AttachedAxisIndex"_ustr) ) + Reference<XPropertySet> xPropSet(xDataSeries, UNO_QUERY_THROW); + if( GetProperty( xPropSet, u"AttachedAxisIndex"_ustr) ) + { + sal_Int32 nLocalAttachedAxis = 0; + mAny >>= nLocalAttachedAxis; + rPrimaryAxes = isPrimaryAxes(nLocalAttachedAxis); + } + + // export shape properties + Reference< XPropertySet > xOldPropSet = SchXMLSeriesHelper::createOldAPISeriesPropertySet( + rSeries, getModel() ); + if( xOldPropSet.is() ) + { + exportShapeProps( xOldPropSet, bIsChartex ); + } + + if (!bIsChartex) { + switch( eChartType ) + { + case chart::TYPEID_BUBBLE: + case chart::TYPEID_HORBAR: + case chart::TYPEID_BAR: { - sal_Int32 nLocalAttachedAxis = 0; - mAny >>= nLocalAttachedAxis; - rPrimaryAxes = isPrimaryAxes(nLocalAttachedAxis); + pFS->singleElement(FSNS(XML_c, XML_invertIfNegative), XML_val, "0"); } - - // export shape properties - Reference< XPropertySet > xOldPropSet = SchXMLSeriesHelper::createOldAPISeriesPropertySet( - rSeries, getModel() ); - if( xOldPropSet.is() ) + break; + case chart::TYPEID_LINE: { - exportShapeProps( xOldPropSet, false ); + exportMarker(xOldPropSet); + break; } - - switch( eChartType ) + case chart::TYPEID_PIE: + case chart::TYPEID_DOUGHNUT: { - case chart::TYPEID_BUBBLE: - case chart::TYPEID_HORBAR: - case chart::TYPEID_BAR: + if( xOldPropSet.is() && GetProperty( xOldPropSet, u"SegmentOffset"_ustr) ) { - pFS->singleElement(FSNS(XML_c, XML_invertIfNegative), XML_val, "0"); + sal_Int32 nOffset = 0; + mAny >>= nOffset; + pFS->singleElement( FSNS( XML_c, XML_explosion ), + XML_val, OString::number( nOffset ) ); } break; - case chart::TYPEID_LINE: - { - exportMarker(xOldPropSet); - break; - } - case chart::TYPEID_PIE: - case chart::TYPEID_DOUGHNUT: - { - if( xOldPropSet.is() && GetProperty( xOldPropSet, u"SegmentOffset"_ustr) ) - { - sal_Int32 nOffset = 0; - mAny >>= nOffset; - pFS->singleElement( FSNS( XML_c, XML_explosion ), - XML_val, OString::number( nOffset ) ); - } - break; - } - case chart::TYPEID_SCATTER: - { - exportMarker(xOldPropSet); - break; - } - case chart::TYPEID_RADARLINE: - { - exportMarker(xOldPropSet); - break; - } } - - // export data points - exportDataPoints( uno::Reference< beans::XPropertySet >( rSeries, uno::UNO_QUERY ), nSeriesLength, eChartType ); + case chart::TYPEID_SCATTER: + { + exportMarker(xOldPropSet); + break; + } + case chart::TYPEID_RADARLINE: + { + exportMarker(xOldPropSet); + break; + } } - DataLabelsRange aDLblsRange; - // export data labels - exportDataLabels(rSeries, nSeriesLength, eChartType, aDLblsRange, bIsChartex); + // export data points + exportDataPoints( uno::Reference< beans::XPropertySet >( rSeries, uno::UNO_QUERY ), nSeriesLength, eChartType ); + } + + DataLabelsRange aDLblsRange; + // export data labels + exportDataLabels(rSeries, nSeriesLength, eChartType, aDLblsRange, bIsChartex); - if (!bIsChartex) { - exportTrendlines( rSeries ); + if (!bIsChartex) { + exportTrendlines( rSeries ); - if( eChartType != chart::TYPEID_PIE && - eChartType != chart::TYPEID_RADARLINE ) + if( eChartType != chart::TYPEID_PIE && + eChartType != chart::TYPEID_RADARLINE ) + { + //export error bars here + Reference< XPropertySet > xSeriesPropSet( xSource, uno::UNO_QUERY ); + Reference< XPropertySet > xErrorBarYProps; + xSeriesPropSet->getPropertyValue(u"ErrorBarY"_ustr) >>= xErrorBarYProps; + if(xErrorBarYProps.is()) + exportErrorBar(xErrorBarYProps, true); + if (eChartType != chart::TYPEID_BAR && + eChartType != chart::TYPEID_HORBAR) { - //export error bars here - Reference< XPropertySet > xSeriesPropSet( xSource, uno::UNO_QUERY ); - Reference< XPropertySet > xErrorBarYProps; - xSeriesPropSet->getPropertyValue(u"ErrorBarY"_ustr) >>= xErrorBarYProps; - if(xErrorBarYProps.is()) - exportErrorBar(xErrorBarYProps, true); - if (eChartType != chart::TYPEID_BAR && - eChartType != chart::TYPEID_HORBAR) - { - Reference< XPropertySet > xErrorBarXProps; - xSeriesPropSet->getPropertyValue(u"ErrorBarX"_ustr) >>= xErrorBarXProps; - if(xErrorBarXProps.is()) - exportErrorBar(xErrorBarXProps, false); - } + Reference< XPropertySet > xErrorBarXProps; + xSeriesPropSet->getPropertyValue(u"ErrorBarX"_ustr) >>= xErrorBarXProps; + if(xErrorBarXProps.is()) + exportErrorBar(xErrorBarXProps, false); } + } - // export categories - if( eChartType != chart::TYPEID_SCATTER && eChartType != chart::TYPEID_BUBBLE && mxCategoriesValues.is() ) - exportSeriesCategory( mxCategoriesValues ); + // export categories + if( eChartType != chart::TYPEID_SCATTER && eChartType != chart::TYPEID_BUBBLE && mxCategoriesValues.is() ) + exportSeriesCategory( mxCategoriesValues ); - if( (eChartType == chart::TYPEID_SCATTER) - || (eChartType == chart::TYPEID_BUBBLE) ) + if( (eChartType == chart::TYPEID_SCATTER) + || (eChartType == chart::TYPEID_BUBBLE) ) + { + // export xVal + Reference< chart2::data::XLabeledDataSequence > xSequence( lcl_getDataSequenceByRole( aSeqCnt, u"values-x"_ustr ) ); + if( xSequence.is() ) { - // export xVal - Reference< chart2::data::XLabeledDataSequence > xSequence( lcl_getDataSequenceByRole( aSeqCnt, u"values-x"_ustr ) ); - if( xSequence.is() ) - { - Reference< chart2::data::XDataSequence > xValues( xSequence->getValues() ); - if( xValues.is() ) - exportSeriesValues( xValues, XML_xVal ); - } - else if( mxCategoriesValues.is() ) - exportSeriesCategory( mxCategoriesValues, XML_xVal ); + Reference< chart2::data::XDataSequence > xValues( xSequence->getValues() ); + if( xValues.is() ) + exportSeriesValues( xValues, XML_xVal ); } + else if( mxCategoriesValues.is() ) + exportSeriesCategory( mxCategoriesValues, XML_xVal ); + } - if( eChartType == chart::TYPEID_BUBBLE ) + if( eChartType == chart::TYPEID_BUBBLE ) + { + // export yVal + Reference< chart2::data::XLabeledDataSequence > xSequence( lcl_getDataSequenceByRole( aSeqCnt, u"values-y"_ustr ) ); + if( xSequence.is() ) { - // export yVal - Reference< chart2::data::XLabeledDataSequence > xSequence( lcl_getDataSequenceByRole( aSeqCnt, u"values-y"_ustr ) ); - if( xSequence.is() ) - { - Reference< chart2::data::XDataSequence > xValues( xSequence->getValues() ); - if( xValues.is() ) - exportSeriesValues( xValues, XML_yVal ); - } + Reference< chart2::data::XDataSequence > xValues( xSequence->getValues() ); + if( xValues.is() ) + exportSeriesValues( xValues, XML_yVal ); } + } - // export values - if( xValuesSeq.is() ) - { - sal_Int32 nYValueType = XML_val; - if( eChartType == chart::TYPEID_SCATTER ) - nYValueType = XML_yVal; - else if( eChartType == chart::TYPEID_BUBBLE ) - nYValueType = XML_bubbleSize; - exportSeriesValues( xValuesSeq, nYValueType ); - } + // export values + if( xValuesSeq.is() ) + { + sal_Int32 nYValueType = XML_val; + if( eChartType == chart::TYPEID_SCATTER ) + nYValueType = XML_yVal; + else if( eChartType == chart::TYPEID_BUBBLE ) + nYValueType = XML_bubbleSize; + exportSeriesValues( xValuesSeq, nYValueType ); + } - if( eChartType == chart::TYPEID_SCATTER - || eChartType == chart::TYPEID_LINE ) - exportSmooth(); + if( eChartType == chart::TYPEID_SCATTER + || eChartType == chart::TYPEID_LINE ) + exportSmooth(); - // tdf103988: "corrupted" files with Bubble chart opening in MSO - if( eChartType == chart::TYPEID_BUBBLE ) - pFS->singleElement(FSNS(XML_c, XML_bubble3D), XML_val, "0"); + // tdf103988: "corrupted" files with Bubble chart opening in MSO + if( eChartType == chart::TYPEID_BUBBLE ) + pFS->singleElement(FSNS(XML_c, XML_bubble3D), XML_val, "0"); - if (!aDLblsRange.empty()) - writeDataLabelsRange(pFS, GetFB(), aDLblsRange); + if (!aDLblsRange.empty()) + writeDataLabelsRange(pFS, GetFB(), aDLblsRange); - pFS->endElement( FSNS( XML_c, XML_ser ) ); - } else { - // chartex + pFS->endElement( FSNS( XML_c, XML_ser ) ); + } else { + // chartex - // Align the data id here with that in exportData(). - // See DATA_ID_COMMENT - pFS->singleElement(FSNS(XML_cx, XML_dataId), XML_val, "0"); - } + // Align the data id here with that in exportData(). + // See DATA_ID_COMMENT + pFS->singleElement(FSNS(XML_cx, XML_dataId), XML_val, "0"); } } } @@ -3358,7 +3361,7 @@ void ChartExport::exportCandleStickSeries( // export label if( xLabelSeq.is() ) - exportSeriesText( xLabelSeq ); + exportSeriesText( xLabelSeq, false ); // TODO:export shape properties @@ -3378,30 +3381,37 @@ void ChartExport::exportCandleStickSeries( } } -void ChartExport::exportSeriesText( const Reference< chart2::data::XDataSequence > & xValueSeq ) +void ChartExport::exportSeriesText( const Reference< chart2::data::XDataSequence > & xValueSeq, + bool bIsChartex) { FSHelperPtr pFS = GetFS(); - pFS->startElement(FSNS(XML_c, XML_tx)); - OUString aCellRange = xValueSeq->getSourceRangeRepresentation(); - aCellRange = parseFormula( aCellRange ); - pFS->startElement(FSNS(XML_c, XML_strRef)); + OUString aLabelString = lcl_flattenStringSequence(lcl_getLabelSequence(xValueSeq)); - pFS->startElement(FSNS(XML_c, XML_f)); - pFS->writeEscaped( aCellRange ); - pFS->endElement( FSNS( XML_c, XML_f ) ); + if (bIsChartex) { + lcl_writeChartexString(pFS, aLabelString); + } else { + pFS->startElement(FSNS(XML_c, XML_tx)); - 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"); - pFS->startElement(FSNS(XML_c, XML_v)); - pFS->writeEscaped( aLabelString ); - 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_tx ) ); + OUString aCellRange = xValueSeq->getSourceRangeRepresentation(); + aCellRange = parseFormula( 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 ) ); + + 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"); + pFS->startElement(FSNS(XML_c, XML_v)); + pFS->writeEscaped( aLabelString ); + 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_tx ) ); + } } void ChartExport::exportSeriesCategory( const Reference< chart2::data::XDataSequence > & xValueSeq, sal_Int32 nValueType ) @@ -3851,23 +3861,39 @@ void ChartExport::exportAxis(const AxisIdPair& rAxisIdPair, bool bIsChartex) } } - _exportAxis(xAxisProp, xAxisTitle, xMajorGrid, xMinorGrid, nAxisType, - sAxPos, rAxisIdPair, bIsChartex); + if (bIsChartex) { + exportOneAxis_chartex(xAxisProp, xAxisTitle, xMajorGrid, xMinorGrid, nAxisType, + rAxisIdPair); + } else { + exportOneAxis_chart(xAxisProp, xAxisTitle, xMajorGrid, xMinorGrid, nAxisType, + sAxPos, rAxisIdPair); + } +} + +static const char *getTickMarkLocStr(sal_Int32 nValue) +{ + const bool bInner = nValue & css::chart::ChartAxisMarks::INNER; + const bool bOuter = nValue & css::chart::ChartAxisMarks::OUTER; + if( bInner && bOuter ) { + return "cross"; + } else if( bInner ) { + return "in"; + } else if( bOuter ) { + return "out"; + } else { + return "none"; + } } -void ChartExport::_exportAxis( +void ChartExport::exportOneAxis_chart( const Reference< XPropertySet >& xAxisProp, const Reference< drawing::XShape >& xAxisTitle, const Reference< XPropertySet >& xMajorGrid, const Reference< XPropertySet >& xMinorGrid, sal_Int32 nAxisType, const char* sAxisPos, - const AxisIdPair& rAxisIdPair, - bool bIsChartex) + const AxisIdPair& rAxisIdPair) { - // TODO for chartex - if (bIsChartex) return; - FSHelperPtr pFS = GetFS(); pFS->startElement(FSNS(XML_c, nAxisType)); pFS->singleElement(FSNS(XML_c, XML_axId), XML_val, OString::number(rAxisIdPair.nAxisId)); @@ -3936,7 +3962,7 @@ void ChartExport::_exportAxis( if( xMajorGrid.is()) { pFS->startElement(FSNS(XML_c, XML_majorGridlines)); - exportShapeProps( xMajorGrid, bIsChartex ); + exportShapeProps( xMajorGrid, false ); pFS->endElement( FSNS( XML_c, XML_majorGridlines ) ); } @@ -3944,13 +3970,13 @@ void ChartExport::_exportAxis( if( xMinorGrid.is()) { pFS->startElement(FSNS(XML_c, XML_minorGridlines)); - exportShapeProps( xMinorGrid, bIsChartex ); + exportShapeProps( xMinorGrid, false ); pFS->endElement( FSNS( XML_c, XML_minorGridlines ) ); } // title if( xAxisTitle.is() ) - exportTitle( xAxisTitle, bIsChartex ); + exportTitle( xAxisTitle, false ); bool bLinkedNumFmt = true; if (GetProperty(xAxisProp, u"LinkNumberFormatToSource"_ustr)) @@ -3973,35 +3999,15 @@ void ChartExport::_exportAxis( if(GetProperty( xAxisProp, u"Marks"_ustr ) ) { mAny >>= nValue; - bool bInner = nValue & css::chart::ChartAxisMarks::INNER; - bool bOuter = nValue & css::chart::ChartAxisMarks::OUTER; - const char* majorTickMark = nullptr; - if( bInner && bOuter ) - majorTickMark = "cross"; - else if( bInner ) - majorTickMark = "in"; - else if( bOuter ) - majorTickMark = "out"; - else - majorTickMark = "none"; - pFS->singleElement(FSNS(XML_c, XML_majorTickMark), XML_val, majorTickMark); + pFS->singleElement(FSNS(XML_c, XML_majorTickMark), XML_val, + getTickMarkLocStr(nValue)); } // minorTickMark if(GetProperty( xAxisProp, u"HelpMarks"_ustr ) ) { mAny >>= nValue; - bool bInner = nValue & css::chart::ChartAxisMarks::INNER; - bool bOuter = nValue & css::chart::ChartAxisMarks::OUTER; - const char* minorTickMark = nullptr; - if( bInner && bOuter ) - minorTickMark = "cross"; - else if( bInner ) - minorTickMark = "in"; - else if( bOuter ) - minorTickMark = "out"; - else - minorTickMark = "none"; - pFS->singleElement(FSNS(XML_c, XML_minorTickMark), XML_val, minorTickMark); + pFS->singleElement(FSNS(XML_c, XML_minorTickMark), XML_val, + getTickMarkLocStr(nValue)); } // tickLblPos const char* sTickLblPos = nullptr; @@ -4036,9 +4042,9 @@ void ChartExport::_exportAxis( pFS->singleElement(FSNS(XML_c, XML_tickLblPos), XML_val, sTickLblPos); // shape properties - exportShapeProps( xAxisProp, bIsChartex ); + exportShapeProps( xAxisProp, false ); - exportTextProps(xAxisProp, bIsChartex); + exportTextProps(xAxisProp, false); pFS->singleElement(FSNS(XML_c, XML_crossAx), XML_val, OString::number(rAxisIdPair.nCrossAx)); @@ -4199,6 +4205,183 @@ void ChartExport::_exportAxis( pFS->endElement( FSNS( XML_c, nAxisType ) ); } +void ChartExport::exportOneAxis_chartex( + const Reference< XPropertySet >& xAxisProp, + const Reference< drawing::XShape >& xAxisTitle, + const Reference< XPropertySet >& xMajorGrid, + const Reference< XPropertySet >& xMinorGrid, + sal_Int32 nAxisType, + const AxisIdPair& rAxisIdPair) +{ + FSHelperPtr pFS = GetFS(); + pFS->startElement(FSNS(XML_cx, XML_axis), XML_id, OString::number(rAxisIdPair.nAxisId)); + + // The following is in the 2010 chart code above: + // bool bVisible = true; + // if( xAxisProp.is() ) + // { + // xAxisProp->getPropertyValue(u"Visible"_ustr) >>= bVisible; + // } + // // only export each axis only once non-deleted + // auto aItInsertedPair = maExportedAxis.insert(rAxisIdPair.nAxisType); + // bool bDeleted = !aItInsertedPair.second; + // + // pFS->singleElement(FSNS(XML_c, XML_delete), XML_val, !bDeleted && bVisible ? "0" : "1"); + // + // Is chartex attribute "hidden" the same as !bVisible? And what to do if + // the axis is deleted, per above? + + // ==== catScaling/valScaling + switch (nAxisType) { + case XML_catAx: + pFS->singleElement(FSNS(XML_cx, XML_catScaling) /* TODO: handle gapWidth */); + break; + case XML_valAx: + { + bool bAutoMax = false; + double dMax = 0; // Make VS happy + bool bMaxSpecified = false; + if(GetProperty( xAxisProp, u"AutoMax"_ustr ) ) + mAny >>= bAutoMax; + + if( !bAutoMax && (GetProperty( xAxisProp, u"Max"_ustr ) ) ) + { + mAny >>= dMax; + bMaxSpecified = true; + } + + bool bAutoMin = false; + double dMin = 0; // Make VS happy + bool bMinSpecified = false; + if(GetProperty( xAxisProp, u"AutoMin"_ustr ) ) + mAny >>= bAutoMin; + + if( !bAutoMin && (GetProperty( xAxisProp, u"Min"_ustr ) ) ) + { + mAny >>= dMin; + bMinSpecified = true; + } + + // TODO: handle majorUnit/minorUnit in the following + if (bMaxSpecified && bMinSpecified) { + pFS->singleElement(FSNS(XML_cx, XML_valScaling), + XML_max, OString::number(dMax), + XML_min, OString::number(dMin)); + } else if (!bMaxSpecified && bMinSpecified) { + pFS->singleElement(FSNS(XML_cx, XML_valScaling), + XML_min, OString::number(dMin)); + } else if (bMaxSpecified && !bMinSpecified) { + pFS->singleElement(FSNS(XML_cx, XML_valScaling), + XML_max, OString::number(dMax)); + } else { + pFS->singleElement(FSNS(XML_cx, XML_valScaling)); + } + + } + break; + default: + // shouldn't happen + assert(false); + } + + // ==== title + if( xAxisTitle.is() ) { + exportTitle( xAxisTitle, true ); + } + + // ==== units + if (GetProperty( xAxisProp, u"DisplayUnits"_ustr ) ) + { + bool bDisplayUnits = false; + mAny >>= bDisplayUnits; + if (bDisplayUnits) + { + if (GetProperty( xAxisProp, u"BuiltInUnit"_ustr )) + { + OUString aVal; + mAny >>= aVal; + if(!aVal.isEmpty()) + { + pFS->startElement(FSNS(XML_cx, XML_units)); + + pFS->startElement(FSNS(XML_cx, XML_unitsLabel)); + + lcl_writeChartexString(pFS, aVal); + + pFS->endElement(FSNS(XML_cx, XML_unitsLabel)); + + pFS->endElement( FSNS( XML_cx, XML_units ) ); + } + } + } + } + + // ==== majorGridlines + if( xMajorGrid.is()) + { + pFS->startElement(FSNS(XML_cx, XML_majorGridlines)); + exportShapeProps( xMajorGrid, true ); + pFS->endElement( FSNS( XML_cx, XML_majorGridlines ) ); + } + + // ==== minorGridlines + if( xMinorGrid.is()) + { + pFS->startElement(FSNS(XML_cx, XML_minorGridlines)); + exportShapeProps( xMinorGrid, true ); + pFS->endElement( FSNS( XML_cx, XML_minorGridlines ) ); + } + + // ==== majorTickMarks + if (GetProperty( xAxisProp, u"Marks"_ustr ) ) + { + sal_Int32 nValue = 0; + mAny >>= nValue; + pFS->singleElement(FSNS(XML_cx, XML_majorTickMarks), XML_type, + getTickMarkLocStr(nValue)); + } + + // ==== minorTickMarks + if (GetProperty( xAxisProp, u"HelpMarks"_ustr ) ) + { + sal_Int32 nValue = 0; + mAny >>= nValue; + pFS->singleElement(FSNS(XML_cx, XML_minorTickMarks), XML_type, + getTickMarkLocStr(nValue)); + } + + // ==== tickLabels consists of nothing but an extLst so I don't know how to + // handle it + + // ==== numFmt + bool bLinkedNumFmt = true; + if (GetProperty(xAxisProp, u"LinkNumberFormatToSource"_ustr)) + mAny >>= bLinkedNumFmt; + + OUString aNumberFormatString(u"General"_ustr); + if (GetProperty(xAxisProp, u"NumberFormat"_ustr)) + { + sal_Int32 nKey = 0; + mAny >>= nKey; + aNumberFormatString = getNumberFormatCode(nKey); + } + + // We're always outputting this, which presumably isn't necessary, but it's + // not clear what the defaults are for determining if an explicit element is + // needed + pFS->singleElement(FSNS(XML_cx, XML_numFmt), + XML_formatCode, aNumberFormatString, + XML_sourceLinked, bLinkedNumFmt ? "1" : "0"); + + // ==== spPr + exportShapeProps( xAxisProp, true ); + + // ==== txPr + exportTextProps(xAxisProp, true); + + pFS->endElement( FSNS( XML_cx, XML_axis ) ); +} + namespace { struct LabelPlacementParam @@ -4826,7 +5009,8 @@ void ChartExport::exportDataPoints( } } -void ChartExport::exportAxesId(bool bPrimaryAxes, bool bCheckCombinedAxes) +// Generalized axis output +void ChartExport::createAxes(bool bPrimaryAxes, bool bCheckCombinedAxes) { sal_Int32 nAxisIdx, nAxisIdy; bool bPrimaryAxisExists = false; @@ -4852,6 +5036,7 @@ void ChartExport::exportAxesId(bool bPrimaryAxes, bool bCheckCombinedAxes) maAxes.emplace_back( eXAxis, nAxisIdx, nAxisIdy ); maAxes.emplace_back( eYAxis, nAxisIdy, nAxisIdx ); } + // Export IDs FSHelperPtr pFS = GetFS(); pFS->singleElement(FSNS(XML_c, XML_axId), XML_val, OString::number(nAxisIdx)); pFS->singleElement(FSNS(XML_c, XML_axId), XML_val, OString::number(nAxisIdy)); diff --git a/oox/source/token/tokens.txt b/oox/source/token/tokens.txt index 2f0bfd869703..87d642f57f3a 100644 --- a/oox/source/token/tokens.txt +++ b/oox/source/token/tokens.txt @@ -1111,6 +1111,7 @@ cardinalText caseSensitive cat catAx +catScaling catLst catalog category @@ -3325,6 +3326,7 @@ majorFont majorGridlines majorHAnsi majorTickMark +majorTickMarks majorTimeUnit majorUnit man @@ -3479,6 +3481,7 @@ minorFont minorGridlines minorHAnsi minorTickMark +minorTickMarks minorTimeUnit minorUnit mintCream @@ -5403,6 +5406,7 @@ threePt thresh through thruBlk +tickLabels tickLblPos tickLblSkip tickMarkSkip @@ -5590,6 +5594,8 @@ uniqueName uniqueParent uniqueTag uniqueValues +units +unitsLabel unknown unknownRelationship unlocked @@ -5690,6 +5696,7 @@ vSpace vacatedStyle val valAx +valScaling value valueAxis valueBetween diff --git a/test/source/xmltesttools.cxx b/test/source/xmltesttools.cxx index be751eaa71dd..29ae85e1403e 100644 --- a/test/source/xmltesttools.cxx +++ b/test/source/xmltesttools.cxx @@ -453,6 +453,8 @@ void XmlTestTools::registerOOXMLNamespaces(xmlXPathContextPtr& pXmlXpathCtx) BAD_CAST("http://schemas.microsoft.com/office/drawing/2010/main")); xmlXPathRegisterNs(pXmlXpathCtx, BAD_CAST("c"), BAD_CAST("http://schemas.openxmlformats.org/drawingml/2006/chart")); + xmlXPathRegisterNs(pXmlXpathCtx, BAD_CAST("cx"), + BAD_CAST("http://schemas.microsoft.com/office/drawing/2014/chartex")); xmlXPathRegisterNs(pXmlXpathCtx, BAD_CAST("x"), BAD_CAST("http://schemas.openxmlformats.org/spreadsheetml/2006/main")); xmlXPathRegisterNs(pXmlXpathCtx, BAD_CAST("r"),