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 ) );
         }
     }

Reply via email to