chart2/qa/extras/chart2export2.cxx | 2 oox/inc/drawingml/chart/axiscontext.hxx | 11 +++ oox/inc/drawingml/chart/titlecontext.hxx | 4 - oox/source/drawingml/chart/axiscontext.cxx | 69 ++++++++++++++++++++++ oox/source/drawingml/chart/chartcontextbase.cxx | 6 + oox/source/drawingml/chart/chartspacefragment.cxx | 8 ++ oox/source/drawingml/chart/plotareacontext.cxx | 9 ++ oox/source/drawingml/chart/titlecontext.cxx | 20 ++++-- oox/source/drawingml/chart/titleconverter.cxx | 37 +++++++++++ oox/source/export/chartexport.cxx | 7 +- 10 files changed, 160 insertions(+), 13 deletions(-)
New commits: commit b5f3a27fef394c9b27e515f2efa226719dd9a517 Author: Kurt Nordback <kurt.nordb...@collabora.com> AuthorDate: Mon Jun 16 10:09:19 2025 -0600 Commit: Xisco Fauli <xiscofa...@libreoffice.org> CommitDate: Tue Jul 1 09:08:56 2025 +0200 tdf#165742 Step 4.5: 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. Additional support for title and axis import and export in chartex, plus one small added regression test. Change-Id: I96ae551623dfbbdc3792e0b882a875c1a6293a25 Reviewed-on: https://gerrit.libreoffice.org/c/core/+/186578 Reviewed-by: Tomaž Vajngerl <qui...@gmail.com> Tested-by: Jenkins (cherry picked from commit 41dcb22e68ac9bf9ef2b2de28d8578344f8b9583) Reviewed-on: https://gerrit.libreoffice.org/c/core/+/187061 Reviewed-by: Xisco Fauli <xiscofa...@libreoffice.org> diff --git a/chart2/qa/extras/chart2export2.cxx b/chart2/qa/extras/chart2export2.cxx index e7f4bf9bee64..f369661c5441 100644 --- a/chart2/qa/extras/chart2export2.cxx +++ b/chart2/qa/extras/chart2export2.cxx @@ -166,6 +166,8 @@ CPPUNIT_TEST_FIXTURE(Chart2ExportTest2, testChartexTitleXLSX) "/cx:chartSpace/cx:chart/cx:plotArea/cx:plotAreaRegion/cx:series/cx:spPr/" "a:solidFill/a:srgbClr", "val", u"c55a11"); + assertXPathContent(pXmlDoc, "/cx:chartSpace/cx:chart/cx:title/cx:tx/cx:txData/cx:v", + u"Funnel chart!"); } CPPUNIT_TEST_FIXTURE(Chart2ExportTest2, testAxisTitleRotationXLSX) diff --git a/oox/inc/drawingml/chart/axiscontext.hxx b/oox/inc/drawingml/chart/axiscontext.hxx index ad240a4a76ab..5cbb2651e5fc 100644 --- a/oox/inc/drawingml/chart/axiscontext.hxx +++ b/oox/inc/drawingml/chart/axiscontext.hxx @@ -101,6 +101,17 @@ public: virtual ::oox::core::ContextHandlerRef onCreateContext( sal_Int32 nElement, const AttributeList& rAttribs ) override; }; +/** Handler for a chartex axis context (<cx:axis> element). + */ +class CxAxisContext final : public AxisContextBase +{ +public: + explicit CxAxisContext( ::oox::core::ContextHandler2Helper& rParent, AxisModel& rModel, sal_Int32 nId ); + virtual ~CxAxisContext() override; + + virtual ::oox::core::ContextHandlerRef onCreateContext( sal_Int32 nElement, const AttributeList& rAttribs ) override; +}; + } // namespace oox::drawingml::chart diff --git a/oox/inc/drawingml/chart/titlecontext.hxx b/oox/inc/drawingml/chart/titlecontext.hxx index cb7383a7eb19..df33b32b58fc 100644 --- a/oox/inc/drawingml/chart/titlecontext.hxx +++ b/oox/inc/drawingml/chart/titlecontext.hxx @@ -20,6 +20,7 @@ #ifndef INCLUDED_OOX_DRAWINGML_CHART_TITLECONTEXT_HXX #define INCLUDED_OOX_DRAWINGML_CHART_TITLECONTEXT_HXX +#include <oox/token/tokens.hxx> #include <drawingml/chart/chartcontextbase.hxx> namespace oox::drawingml::chart { @@ -73,7 +74,8 @@ struct LegendModel; class LegendContext final : public ContextBase< LegendModel > { public: - explicit LegendContext( ::oox::core::ContextHandler2Helper& rParent, LegendModel& rModel ); + explicit LegendContext( ::oox::core::ContextHandler2Helper& rParent, + LegendModel& rModel, bool bOverlay = false, sal_Int32 nPos = XML_r); virtual ~LegendContext() override; virtual ::oox::core::ContextHandlerRef onCreateContext( sal_Int32 nElement, const AttributeList& rAttribs ) override; diff --git a/oox/source/drawingml/chart/axiscontext.cxx b/oox/source/drawingml/chart/axiscontext.cxx index 8f1defe40bea..cbab186f1ed9 100644 --- a/oox/source/drawingml/chart/axiscontext.cxx +++ b/oox/source/drawingml/chart/axiscontext.cxx @@ -293,6 +293,75 @@ ContextHandlerRef ValAxisContext::onCreateContext( sal_Int32 nElement, const Att return AxisContextBase::onCreateContext( nElement, rAttribs ); } +CxAxisContext::CxAxisContext( ContextHandler2Helper& rParent, AxisModel& rModel, + sal_Int32 nId) : + AxisContextBase( rParent, rModel ) +{ + mrModel.mnAxisId = nId; +} + +CxAxisContext::~CxAxisContext() +{ +} + +ContextHandlerRef CxAxisContext::onCreateContext( sal_Int32 nElement, const AttributeList& rAttribs ) +{ + if( isRootElement() ) switch( nElement ) + { + case CX_TOKEN(catScaling) : + // TODO: figure out how to get gapWidth attribute to the right place + return nullptr; + case CX_TOKEN(valScaling) : + if (rAttribs.hasAttribute(XML_max)) { + mrModel.mofMax = rAttribs.getDouble(XML_max); + } + if (rAttribs.hasAttribute(XML_min)) { + mrModel.mofMin = rAttribs.getDouble(XML_min); + } + /* TODO: need to implement AttributeList method + if (rAttribs.hasAttribute(XML_majorUnit)) { + mrModel.mofMajorUnit = rAttribs.getValAxisUnit(XML_majorUnit); + } + if (rAttribs.hasAttribute(XML_minorUnit)) { + mrModel.mofMinorUnit = rAttribs.getValAxisUnit(XML_minorUnit); + } + */ + return nullptr; + case CX_TOKEN(title): + { + bool bVerticalDefault = mrModel.mnAxisPos == XML_l || mrModel.mnAxisPos == XML_r; + sal_Int32 nDefaultRotation = bVerticalDefault ? -5400000 : 0; + return new TitleContext( *this, mrModel.mxTitle.create(nDefaultRotation) ); + } + case CX_TOKEN(units) : + // TODO + return nullptr; + case CX_TOKEN(majorGridlines): + return new ShapePrWrapperContext( *this, mrModel.mxMajorGridLines.create() ); + case CX_TOKEN(minorGridlines): + return new ShapePrWrapperContext( *this, mrModel.mxMinorGridLines.create() ); + case CX_TOKEN(majorTickMarks): + mrModel.mnMajorTickMark = rAttribs.getToken( XML_type, XML_cross ); + return nullptr; + case CX_TOKEN(minorTickMarks): + mrModel.mnMinorTickMark = rAttribs.getToken( XML_type, XML_cross ); + return nullptr; + case CX_TOKEN(tickLabels) : + // TODO (contents is only an extLst) + return nullptr; + case CX_TOKEN(numFmt): + mrModel.maNumberFormat.setAttributes( rAttribs ); + return nullptr; + case CX_TOKEN(spPr): + return new ShapePropertiesContext( *this, mrModel.mxShapeProp.create() ); + case CX_TOKEN(txPr): + return new TextBodyContext( *this, mrModel.mxTextProp.create() ); + default: + assert(false); + } + return nullptr; +} + } // namespace oox::drawingml::chart /* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/oox/source/drawingml/chart/chartcontextbase.cxx b/oox/source/drawingml/chart/chartcontextbase.cxx index 283c8e03ad55..5582f7600d20 100644 --- a/oox/source/drawingml/chart/chartcontextbase.cxx +++ b/oox/source/drawingml/chart/chartcontextbase.cxx @@ -41,7 +41,11 @@ ShapePrWrapperContext::~ShapePrWrapperContext() ContextHandlerRef ShapePrWrapperContext::onCreateContext( sal_Int32 nElement, const AttributeList& ) { - return (isRootElement() && (nElement == C_TOKEN( spPr ))) ? new ShapePropertiesContext( *this, mrModel ) : nullptr; + if (isRootElement() && (nElement == C_TOKEN(spPr) || nElement == CX_TOKEN(spPr))) { + return new ShapePropertiesContext( *this, mrModel ); + } else { + return nullptr; + } } LayoutContext::LayoutContext( ContextHandler2Helper& rParent, LayoutModel& rModel ) : diff --git a/oox/source/drawingml/chart/chartspacefragment.cxx b/oox/source/drawingml/chart/chartspacefragment.cxx index 1361ddf31d7a..88ee3c5bdf41 100644 --- a/oox/source/drawingml/chart/chartspacefragment.cxx +++ b/oox/source/drawingml/chart/chartspacefragment.cxx @@ -174,7 +174,13 @@ ContextHandlerRef ChartSpaceFragment::onCreateContext( sal_Int32 nElement, const case CX_TOKEN(plotArea): return new PlotAreaContext( *this, mrModel.mxPlotArea.create() ); case CX_TOKEN(legend): - return new LegendContext( *this, mrModel.mxLegend.create() ); + { + const bool bOverlay = rAttribs.getBool(XML_overlay, false); + const sal_Int32 nPos = rAttribs.getToken(XML_pos, XML_t); + + return new LegendContext( *this, mrModel.mxLegend.create(), + bOverlay, nPos); + } case CX_TOKEN(extLst): // TODO return nullptr; diff --git a/oox/source/drawingml/chart/plotareacontext.cxx b/oox/source/drawingml/chart/plotareacontext.cxx index 98b0b31a04fb..00a6a47bde3b 100644 --- a/oox/source/drawingml/chart/plotareacontext.cxx +++ b/oox/source/drawingml/chart/plotareacontext.cxx @@ -172,8 +172,13 @@ ContextHandlerRef PlotAreaContext::onCreateContext( sal_Int32 nElement, [[maybe_ case CX_TOKEN(plotAreaRegion) : return new ChartexTypeGroupContext(*this, mrModel.maTypeGroups.create(nElement, false)); case CX_TOKEN(axis) : - // TODO - return nullptr; + if (rAttribs.hasAttribute(XML_id)) { + sal_Int32 nId = rAttribs.getInteger(XML_id, -1); + // TODO: also handle attribute "hidden" + return new CxAxisContext(*this, mrModel.maAxes.create(nElement, false), nId); + } else { + return nullptr; + } case CX_TOKEN(spPr) : return new ShapePropertiesContext( *this, mrModel.mxShapeProp.getOrCreate() ); case CX_TOKEN(extLst) : diff --git a/oox/source/drawingml/chart/titlecontext.cxx b/oox/source/drawingml/chart/titlecontext.cxx index 77e56ac36ada..1470aa1b44e7 100644 --- a/oox/source/drawingml/chart/titlecontext.cxx +++ b/oox/source/drawingml/chart/titlecontext.cxx @@ -25,7 +25,6 @@ #include <drawingml/chart/titlemodel.hxx> #include <oox/helper/attributelist.hxx> #include <oox/token/namespaces.hxx> -#include <oox/token/tokens.hxx> #include <osl/diagnose.h> @@ -47,7 +46,7 @@ TextContext::~TextContext() ContextHandlerRef TextContext::onCreateContext( sal_Int32 nElement, const AttributeList& ) { // this context handler is used for <c:tx> and embedded <c:v> elements - if( isCurrentElement( C_TOKEN( tx ) ) ) switch( nElement ) + if( isCurrentElement( C_TOKEN( tx ) ) || isCurrentElement(CX_TOKEN(tx)) ) switch( nElement ) { case C_TOKEN( rich ): case CX_TOKEN( rich ): @@ -94,7 +93,6 @@ TitleContext::~TitleContext() ContextHandlerRef TitleContext::onCreateContext( sal_Int32 nElement, const AttributeList& rAttribs ) { - // this context handler is used for <c:title> only switch( nElement ) { case C_TOKEN( layout ): @@ -105,12 +103,15 @@ ContextHandlerRef TitleContext::onCreateContext( sal_Int32 nElement, const Attri 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( txPr ): + case CX_TOKEN( txPr ): return new TextBodyContext( *this, mrModel.mxTextProp.create() ); } return nullptr; @@ -141,9 +142,16 @@ ContextHandlerRef LegendEntryContext::onCreateContext( sal_Int32 nElement, const return nullptr; } -LegendContext::LegendContext( ContextHandler2Helper& rParent, LegendModel& rModel ) : +LegendContext::LegendContext( ContextHandler2Helper& rParent, + LegendModel& rModel, + bool bOverlay /* = false */, + sal_Int32 nPos /* = XML_r */) : ContextBase< LegendModel >( rParent, rModel ) { + // These can't be in the initializer list because they're members of + // ContextBase<LegendModel> + mrModel.mbOverlay = bOverlay; + mrModel.mnPosition = nPos; } LegendContext::~LegendContext() @@ -152,7 +160,6 @@ LegendContext::~LegendContext() ContextHandlerRef LegendContext::onCreateContext( sal_Int32 nElement, const AttributeList& rAttribs ) { - // this context handler is used for <c:legend> only switch( nElement ) { case C_TOKEN( layout ): @@ -166,13 +173,16 @@ ContextHandlerRef LegendContext::onCreateContext( sal_Int32 nElement, const Attr return new LegendEntryContext( *this, mrModel.maLegendEntries.create() ); case C_TOKEN( overlay ): + // For cx, overlay is an attribute of <cx:legend> mrModel.mbOverlay = rAttribs.getBool( XML_val, true ); return nullptr; case C_TOKEN( spPr ): + case CX_TOKEN( spPr ): return new ShapePropertiesContext( *this, mrModel.mxShapeProp.create() ); case C_TOKEN( txPr ): + case CX_TOKEN( txPr ): return new TextBodyContext( *this, mrModel.mxTextProp.create() ); } return nullptr; diff --git a/oox/source/drawingml/chart/titleconverter.cxx b/oox/source/drawingml/chart/titleconverter.cxx index 0ee8affc7984..f26cc75a966e 100644 --- a/oox/source/drawingml/chart/titleconverter.cxx +++ b/oox/source/drawingml/chart/titleconverter.cxx @@ -75,6 +75,7 @@ Sequence< Reference< XFormattedString > > TextConverter::createStringSequence( { OSL_ENSURE( !mrModel.mxDataSeq || !mrModel.mxTextBody, "TextConverter::createStringSequence - linked string and rich text found" ); ::std::vector< Reference< XFormattedString > > aStringVec; + bool bTextFound = false; if( mrModel.mxTextBody.is() ) { // rich-formatted text objects can be created, but currently Chart2 is not able to show them @@ -101,10 +102,44 @@ Sequence< Reference< XFormattedString > > TextConverter::createStringSequence( aRunProps = rParaProps; aRunProps.assignUsed( rTextRun.getTextCharacterProperties() ); getFormatter().convertTextFormatting( aPropSet, aRunProps, eObjType ); + + bTextFound = true; } } } - else + else if (rxTextProp.is() && !rxTextProp->getParagraphs().empty()) { + // <c:txPr> or <cx:txPr> can contain <a:p>. Which seems odd, but handle + // it here. + const TextParagraphVector& rTextParas = rxTextProp->getParagraphs(); + for( TextParagraphVector::const_iterator aPIt = rTextParas.begin(), aPEnd = rTextParas.end(); aPIt != aPEnd; ++aPIt ) + { + const TextParagraph& rTextPara = **aPIt; + const TextCharacterProperties& rParaProps = rTextPara.getProperties().getTextCharacterProperties(); + for( TextRunVector::const_iterator aRIt = rTextPara.getRuns().begin(), aREnd = rTextPara.getRuns().end(); aRIt != aREnd; ++aRIt ) + { + const TextRun& rTextRun = **aRIt; + bool bAddNewLine = ((aRIt + 1 == aREnd) && (aPIt + 1 != aPEnd)) || rTextRun.isLineBreak(); + Reference< XFormattedString > xFmtStr = appendFormattedString( aStringVec, rTextRun.getText(), bAddNewLine ); + PropertySet aPropSet( xFmtStr ); + TextCharacterProperties aRunProps; + if (rParaProps.mbHasEmptyParaProperties && rxTextProp->hasParagraphProperties()) + { + const TextParagraphVector rDefTextParas = rxTextProp->getParagraphs(); + TextParagraphVector::const_iterator aDefPIt = rDefTextParas.begin(); + const TextParagraph& rDefTextPara = **aDefPIt; + aRunProps = rDefTextPara.getProperties().getTextCharacterProperties(); + } + else + aRunProps = rParaProps; + aRunProps.assignUsed( rTextRun.getTextCharacterProperties() ); + getFormatter().convertTextFormatting( aPropSet, aRunProps, eObjType ); + + bTextFound = true; + } + } + } + + if (!bTextFound) { OUString aString; // try to create string from linked data diff --git a/oox/source/export/chartexport.cxx b/oox/source/export/chartexport.cxx index d99e6e3180e1..036e62e49e5a 100644 --- a/oox/source/export/chartexport.cxx +++ b/oox/source/export/chartexport.cxx @@ -1574,8 +1574,9 @@ void ChartExport::exportChart( const Reference< css::chart::XChartDocument >& xC // plot area exportPlotArea( xChartDoc, bIsChartex ); // legend - if( bHasLegend ) + if( bHasLegend ) { exportLegend( xChartDoc, bIsChartex ); + } if (!bIsChartex) { uno::Reference<beans::XPropertySet> xDiagramPropSet(xChartDoc->getDiagram(), uno::UNO_QUERY); @@ -1815,7 +1816,9 @@ void ChartExport::exportLegend( const Reference< css::chart::XChartDocument >& x // draw-chart:txPr text properties exportTextProps( xProp, bIsChartex ); - if (!bIsChartex) { + if (bIsChartex) { + pFS->endElement( FSNS( XML_cx, XML_legend ) ); + } else { pFS->endElement( FSNS( XML_c, XML_legend ) ); } }