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 #include +#include 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 xModel(mxComponent, UNO_QUERY_THROW); + Reference xController(xModel->getCurrentController()); + Reference xUI(xController, UNO_QUERY_THROW); + + xUI->connect(); + Reference xActiveConnection = xUI->getActiveConnection(); + + Reference xSupp(xModel, UNO_QUERY_THROW); + Reference xNameAccess = xSupp->getReportDocuments(); + const Sequence aReportNames(xNameAccess->getElementNames()); + CPPUNIT_ASSERT(aReportNames.getLength() > 0); + + Reference 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 xComp1 = xComponentLoader->loadComponentFromURL( + aReportNames[0], u"_blank"_ustr, 0, aLoadArgs.getPropertyValues()); + Reference xComp2 = xComponentLoader->loadComponentFromURL( + aReportNames[1], u"_blank"_ustr, 0, aLoadArgs.getPropertyValues()); + Reference xTextDoc1(xComp1, UNO_QUERY); + Reference 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 xClose1(xComp1, UNO_QUERY); + if (xClose1.is()) + xClose1->close(true); + Reference xClose2(xComp2, UNO_QUERY); + if (xClose2.is()) + xClose2->close(true); + } +} + void RptBasicTest::testLoadingAndSaving(const OUString& rFilterName, const OUString& rReportName, Reference& xComponentLoader, Reference& 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__" (new) + // - "INT_count_" (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;