reportdesign/qa/unit/ReportDesignBasicTests.cxx | 57 ++++++++++ reportdesign/qa/unit/data/ReportBuilder_grouping_same_field.odb |binary reportdesign/source/filter/xml/xmlExport.cxx | 4 reportdesign/source/filter/xml/xmlGroup.cxx | 31 ++++- 4 files changed, 88 insertions(+), 4 deletions(-)
New commits: commit e348d752613dc30e33a48e26865ac195a4b4991b Author: Vissarion Fisikopoulos <[email protected]> AuthorDate: Wed Nov 12 15:42:27 2025 +0200 Commit: Hossein <[email protected]> CommitDate: Mon Nov 24 22:10:29 2025 +0100 tdf#62248 fix multiple groupings on same field issue in reportbuilder In a reportbuilder report, when grouping on the same field multiple times (with different interval/prefix characters setting), the two groupings interact and in the end it does not work right; both grouping levels behave like one of them. That's because the report function created by Report Builder for each of the groupings share the same name. Choose unique function name in ORptExport::exportGroupsExpressionAsFunction and sync OXMLGroup::OXMLGroup to parse that name. Keep supporting legacy names for backwards compatibility. Change-Id: Ic91e51743a407225d50b14f85d02c58ca0a243c0 Reviewed-on: https://gerrit.libreoffice.org/c/core/+/193993 Tested-by: Jenkins Reviewed-by: Hossein <[email protected]> diff --git a/reportdesign/qa/unit/ReportDesignBasicTests.cxx b/reportdesign/qa/unit/ReportDesignBasicTests.cxx index 03489b24501b..875aca028be9 100644 --- a/reportdesign/qa/unit/ReportDesignBasicTests.cxx +++ b/reportdesign/qa/unit/ReportDesignBasicTests.cxx @@ -21,6 +21,7 @@ #include <com/sun/star/sdb/application/XDatabaseDocumentUI.hpp> #include <comphelper/namedvaluecollection.hxx> +#include <com/sun/star/text/XTextDocument.hpp> using namespace ::com::sun::star; using namespace ::com::sun::star::uno; @@ -58,6 +59,62 @@ CPPUNIT_TEST_FIXTURE(RptBasicTest, roundTripTest) testLoadingAndSaving(u"calc8"_ustr, aReportNames[1], xComponentLoader, xActiveConnection); } +CPPUNIT_TEST_FIXTURE(RptBasicTest, multiGroupingSameFieldIntervals) +{ + // ODB fixture must contain a table and a report that groups on the same field twice + // with different settings (e.g., INTERVAL 5 and INTERVAL 10). The report should be + // prepared to reproduce the original failure (Column not found: INT_count_...). + loadURLCopy(u"ReportBuilder_grouping_same_field.odb"); + + Reference<frame::XModel> xModel(mxComponent, UNO_QUERY_THROW); + Reference<frame::XController> xController(xModel->getCurrentController()); + Reference<sdb::application::XDatabaseDocumentUI> xUI(xController, UNO_QUERY_THROW); + + xUI->connect(); + Reference<XConnection> xActiveConnection = xUI->getActiveConnection(); + + Reference<XReportDocumentsSupplier> xSupp(xModel, UNO_QUERY_THROW); + Reference<container::XNameAccess> xNameAccess = xSupp->getReportDocuments(); + const Sequence<OUString> aReportNames(xNameAccess->getElementNames()); + CPPUNIT_ASSERT(aReportNames.getLength() > 0); + + Reference<frame::XComponentLoader> xComponentLoader(xNameAccess, UNO_QUERY_THROW); + + // Execute all reports via writer export. This will fail the test if any report + // throws ReportExecutionException like "Column not found: INT_count_Number_*". + for (const OUString& rName : aReportNames) + testLoadingAndSaving(u"writer8"_ustr, rName, xComponentLoader, xActiveConnection); + + // Additionally, load the textual outputs of all reports and compare their lengths. + // They should be identical. + if (aReportNames.getLength() >= 2) + { + ::comphelper::NamedValueCollection aLoadArgs; + aLoadArgs.put(u"ActiveConnection"_ustr, xActiveConnection); + + Reference<lang::XComponent> xComp1 = xComponentLoader->loadComponentFromURL( + aReportNames[0], u"_blank"_ustr, 0, aLoadArgs.getPropertyValues()); + Reference<lang::XComponent> xComp2 = xComponentLoader->loadComponentFromURL( + aReportNames[1], u"_blank"_ustr, 0, aLoadArgs.getPropertyValues()); + Reference<text::XTextDocument> xTextDoc1(xComp1, UNO_QUERY); + Reference<text::XTextDocument> xTextDoc2(xComp2, UNO_QUERY); + + if (xTextDoc1.is() && xTextDoc2.is()) + { + sal_Int32 nLen1 = xTextDoc1->getText()->getString().getLength(); + sal_Int32 nLen2 = xTextDoc2->getText()->getString().getLength(); + CPPUNIT_ASSERT_EQUAL_MESSAGE("Report textual output length differs", nLen1, nLen2); + } + + Reference<util::XCloseable> xClose1(xComp1, UNO_QUERY); + if (xClose1.is()) + xClose1->close(true); + Reference<util::XCloseable> xClose2(xComp2, UNO_QUERY); + if (xClose2.is()) + xClose2->close(true); + } +} + void RptBasicTest::testLoadingAndSaving(const OUString& rFilterName, const OUString& rReportName, Reference<frame::XComponentLoader>& xComponentLoader, Reference<XConnection>& xActiveConnection) diff --git a/reportdesign/qa/unit/data/ReportBuilder_grouping_same_field.odb b/reportdesign/qa/unit/data/ReportBuilder_grouping_same_field.odb new file mode 100644 index 000000000000..bba06e5aa285 Binary files /dev/null and b/reportdesign/qa/unit/data/ReportBuilder_grouping_same_field.odb differ diff --git a/reportdesign/source/filter/xml/xmlExport.cxx b/reportdesign/source/filter/xml/xmlExport.cxx index 455b46648769..7d09758dbcbf 100644 --- a/reportdesign/source/filter/xml/xmlExport.cxx +++ b/reportdesign/source/filter/xml/xmlExport.cxx @@ -1415,7 +1415,9 @@ void ORptExport::exportGroupsExpressionAsFunction(const Reference< XGroups>& _xG sFunction = "INT"; uno::Reference< XFunction> xCountFunction = xFunctions->createFunction(); xCountFunction->setInitialFormula(beans::Optional< OUString>(true,u"rpt:0"_ustr)); - OUString sCountName = sFunction + "_count_" + sExpression; + OUString sCountName = sFunction + "_count_" + + OUString::number(xGroup->getGroupInterval()) + + "_" + sExpression; xCountFunction->setName(sCountName); xCountFunction->setFormula( "rpt:[" + sCountName + "] + 1" ); exportFunction(xCountFunction); diff --git a/reportdesign/source/filter/xml/xmlGroup.cxx b/reportdesign/source/filter/xml/xmlGroup.cxx index b25a31fa8089..a3799c19905e 100644 --- a/reportdesign/source/filter/xml/xmlGroup.cxx +++ b/reportdesign/source/filter/xml/xmlGroup.cxx @@ -73,6 +73,14 @@ OXMLGroup::OXMLGroup( ORptFilter& _rImport case XML_ELEMENT(REPORT, XML_SORT_ASCENDING): m_xGroup->setSortAscending(IsXMLToken(aIter, XML_TRUE)); break; + case XML_ELEMENT(REPORT, XML_SORT_EXPRESSION): + { + // Use the explicit sort field as the group's expression + OUString sExpr = aIter.toString(); + if (sExpr.indexOf("INT_count") == -1)// for backward compatibility + m_xGroup->setExpression(sExpr); + } + break; case XML_ELEMENT(REPORT, XML_GROUP_EXPRESSION): { OUString sValue = aIter.toString(); @@ -138,7 +146,21 @@ OXMLGroup::OXMLGroup( ORptFilter& _rImport { nGroupOn = report::GroupOn::INTERVAL; _rImport.removeFunction(sExpression); - sExpression = sExpression.copy(std::string_view("INT_count_").size()); + // sExpression can be either: + // - "INT_count_<digits>_<Field>" (new) + // - "INT_count_<Field>" (legacy) + const OUString aPrefix = u"INT_count_"_ustr; + const sal_Int32 nStart = aPrefix.getLength(); + const sal_Int32 nEnd = sExpression.indexOf(u'_', nStart); + const sal_Int32 nDigitsOfInterval = (nEnd >= 0 && nEnd > nStart) + ? (nEnd - nStart) : 0; + + // Cut after prefix; if digits found, also skip the following '_' + sal_Int32 nCut = nStart; + if (nDigitsOfInterval > 0 && nEnd >= 0) + nCut = nEnd + 1; + if (nCut <= sExpression.getLength()) + sExpression = sExpression.copy(nCut); OUString sInterval = sCompleteFormula.getToken(1,'/'); sInterval = sInterval.getToken(0,')'); m_xGroup->setGroupInterval(sInterval.toInt32()); @@ -146,10 +168,13 @@ OXMLGroup::OXMLGroup( ORptFilter& _rImport m_xGroup->setGroupOn(nGroupOn); + // Drop only the reference name; keep functions for export _rImport.removeFunction(sValue); - sValue = sExpression; + + // Only set expression from fallback if not already provided via sort-expression + if (m_xGroup->getExpression().isEmpty()) + m_xGroup->setExpression(sExpression); } - m_xGroup->setExpression(sValue); } } break;
