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;
