chart2/qa/extras/chart2export2.cxx              |   13 +----
 oox/inc/drawingml/chart/typegroupcontext.hxx    |    3 -
 oox/source/drawingml/chart/plotareacontext.cxx  |   50 +++++++++++++++++++++-
 oox/source/drawingml/chart/typegroupcontext.cxx |   54 ------------------------
 4 files changed, 55 insertions(+), 65 deletions(-)

New commits:
commit a39d4b4e9a59849404726bb59b1a21b7bd85a5a9
Author:     Kurt Nordback <[email protected]>
AuthorDate: Mon Jan 5 11:41:08 2026 -0700
Commit:     Tomaž Vajngerl <[email protected]>
CommitDate: Fri Jan 9 07:22:39 2026 +0100

    tdf#165742 Step 5.3: Fix pareto chart import
    
    Restructure chartex parsing architecture a bit, to fix handling of
    multiple series in a chart (as in pareto charts, as created by MSO).
    This is necessitated by the difference between chart and chartex
    structure: the former includes a 'type group' tag (e.g., barChart)
    which can contain multiple series; the latter has the chart type
    attached to the series tag as an attribute.
    
    Change-Id: I4bd20b189248888dc88939398bf47bd750f57c26
    Reviewed-on: https://gerrit.libreoffice.org/c/core/+/196580
    Tested-by: Jenkins
    Reviewed-by: Tomaž Vajngerl <[email protected]>

diff --git a/chart2/qa/extras/chart2export2.cxx 
b/chart2/qa/extras/chart2export2.cxx
index d425d6acbebd..3ee326e61b3b 100644
--- a/chart2/qa/extras/chart2export2.cxx
+++ b/chart2/qa/extras/chart2export2.cxx
@@ -196,17 +196,12 @@ CPPUNIT_TEST_FIXTURE(Chart2ExportTest2, 
testChartexTitleXLSX)
     pXmlDoc = parseExport(u"xl/charts/chartEx1.xml"_ustr);
     CPPUNIT_ASSERT(pXmlDoc);
 
-#if 0
-    // This is what it really should be, based on MSO output
-    assertXPath(pXmlDoc, 
"/cx:chartSpace/cx:chart/cx:plotArea/cx:plotAreaRegion/cx:series",
-            2, 0, "layoutId", u"clusteredColumn");
-    assertXPath(pXmlDoc, 
"/cx:chartSpace/cx:chart/cx:plotArea/cx:plotAreaRegion/cx:series",
-            2, 1, "layoutId", u"paretoLine");
-#else
-    // This is what LO currently produces
+    // A pareto chart from MSO really consists of two subcharts: a pareto line
+    // and a clustered column chart.
     assertXPath(pXmlDoc, 
"/cx:chartSpace/cx:chart/cx:plotArea/cx:plotAreaRegion/cx:series", 2, 0,
+                "layoutId", u"clusteredColumn");
+    assertXPath(pXmlDoc, 
"/cx:chartSpace/cx:chart/cx:plotArea/cx:plotAreaRegion/cx:series", 2, 1,
                 "layoutId", u"paretoLine");
-#endif
     assertXPathContent(pXmlDoc, 
"/cx:chartSpace/cx:chart/cx:title/cx:tx/cx:txData/cx:v",
                        u"ParetoLine");
     // ====
diff --git a/oox/inc/drawingml/chart/typegroupcontext.hxx 
b/oox/inc/drawingml/chart/typegroupcontext.hxx
index c64f18d74c72..07b45d5c50bd 100644
--- a/oox/inc/drawingml/chart/typegroupcontext.hxx
+++ b/oox/inc/drawingml/chart/typegroupcontext.hxx
@@ -162,9 +162,6 @@ public:
     explicit            ChartexTypeGroupContext( 
::oox::core::ContextHandler2Helper& rParent, TypeGroupModel& rModel );
     virtual             ~ChartexTypeGroupContext() override;
 
-    // Explicitly create a new series
-    void CreateSeries();
-
     virtual ::oox::core::ContextHandlerRef onCreateContext( sal_Int32 
nElement, const AttributeList& rAttribs ) override;
 };
 
diff --git a/oox/source/drawingml/chart/plotareacontext.cxx 
b/oox/source/drawingml/chart/plotareacontext.cxx
index 731c23368933..c3c3fc6af82f 100644
--- a/oox/source/drawingml/chart/plotareacontext.cxx
+++ b/oox/source/drawingml/chart/plotareacontext.cxx
@@ -170,7 +170,7 @@ ContextHandlerRef PlotAreaContext::onCreateContext( 
sal_Int32 nElement, [[maybe_
         case CX_TOKEN(plotArea) :
             switch (nElement) {
                 case CX_TOKEN(plotAreaRegion) :
-                    return new ChartexTypeGroupContext(*this, 
mrModel.maTypeGroups.create(nElement, false));
+                    return this;
                 case CX_TOKEN(axis) :
                     if (rAttribs.hasAttribute(XML_id)) {
                         sal_Int32 nId = rAttribs.getInteger(XML_id, -1);
@@ -186,6 +186,54 @@ ContextHandlerRef PlotAreaContext::onCreateContext( 
sal_Int32 nElement, [[maybe_
                     return nullptr;
             }
             break;
+        case CX_TOKEN(plotAreaRegion):
+            switch (nElement) {
+                case CX_TOKEN(series):
+                    if (rAttribs.hasAttribute(XML_layoutId)) {
+                        sal_Int32 nTypeId = 0;
+                        OUString sChartId = 
rAttribs.getStringDefaulted(XML_layoutId);
+                        assert(!sChartId.isEmpty());
+
+                        if (sChartId == "boxWhisker") {
+                            nTypeId = CX_TOKEN(boxWhisker);
+                        } else if (sChartId == "clusteredColumn") {
+                            nTypeId = CX_TOKEN(clusteredColumn);
+                        } else if (sChartId == "funnel") {
+                            nTypeId = CX_TOKEN(funnel);
+                        } else if (sChartId == "paretoLine") {
+                            nTypeId = CX_TOKEN(paretoLine);
+                        } else if (sChartId == "regionMap") {
+                            nTypeId = CX_TOKEN(regionMap);
+                        } else if (sChartId == "sunburst") {
+                            nTypeId = CX_TOKEN(sunburst);
+                        } else if (sChartId == "treemap") {
+                            nTypeId = CX_TOKEN(treemap);
+                        } else if (sChartId == "waterfall") {
+                            nTypeId = CX_TOKEN(waterfall);
+                        } else {
+                            assert(false);
+                        }
+
+                        // The chartex schema doesn't have the same structure 
as
+                        // chart. Specifically, there's not a chart type tag
+                        // (which corresponds to the "type group" used in oox)
+                        // plus enclosed series tags. Instead, in chartex, each
+                        // series has an attribute specifying the chart type. 
As
+                        // a result, we don't want to call the type group
+                        // context handler, but we still need to create a type
+                        // group in the model tree, since much of the existing
+                        // machinery depends on it.
+                        mrModel.maTypeGroups.create(nTypeId, false);
+                        std::shared_ptr<TypeGroupModel> aTGM =
+                            
mrModel.maTypeGroups.get(mrModel.maTypeGroups.size() - 1);
+                        return new ChartexSeriesContext(*this, 
aTGM->maSeries.create(false));
+                    }
+                    return nullptr;
+                case CX_TOKEN(plotSurface) :
+                    // TODO
+                    return nullptr;
+
+            }
     }
     return nullptr;
 }
diff --git a/oox/source/drawingml/chart/typegroupcontext.cxx 
b/oox/source/drawingml/chart/typegroupcontext.cxx
index f29b42ab42bf..4f2ea33652e9 100644
--- a/oox/source/drawingml/chart/typegroupcontext.cxx
+++ b/oox/source/drawingml/chart/typegroupcontext.cxx
@@ -405,61 +405,11 @@ ChartexTypeGroupContext::~ChartexTypeGroupContext()
 {
 }
 
-void ChartexTypeGroupContext::CreateSeries()
-{
-    mrModel.maSeries.create(false);
-}
-
 ContextHandlerRef ChartexTypeGroupContext::onCreateContext( [[maybe_unused]] 
sal_Int32 nElement,
         [[maybe_unused]] const AttributeList& rAttribs )
 {
-    if (isRootElement()) switch (nElement) {
-        case CX_TOKEN(plotSurface) :
-            // TODO
-            return nullptr;
-        case CX_TOKEN(series) :
-            if (rAttribs.hasAttribute(XML_layoutId)) {
-                // If this is the first series, then the type ID is currently
-                // set to <cx:plotAreaRegion>. If this is not the first series
-                // in a multi-series chart, it should be set to the previous
-                // chart type in the series (which *should* only be another
-                // chartex type, not a <c> type). In either case, set it
-                // to the specific chart type based on the layoutId attribute
-                assert(mrModel.mnTypeId == CX_TOKEN(plotAreaRegion) ||
-                        mrModel.mnTypeId == CX_TOKEN(boxWhisker) ||
-                        mrModel.mnTypeId == CX_TOKEN(clusteredColumn) ||
-                        mrModel.mnTypeId == CX_TOKEN(funnel) ||
-                        mrModel.mnTypeId == CX_TOKEN(paretoLine) ||
-                        mrModel.mnTypeId == CX_TOKEN(regionMap) ||
-                        mrModel.mnTypeId == CX_TOKEN(sunburst) ||
-                        mrModel.mnTypeId == CX_TOKEN(treemap) ||
-                        mrModel.mnTypeId == CX_TOKEN(waterfall));
-                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;
+    if (isRootElement()) {
+         return new ChartexSeriesContext(*this, 
mrModel.maSeries.create(false));
     }
 
     return nullptr;

Reply via email to