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 <hossein@libreoffice.org>
This commit is contained in:
committed by
Hossein
parent
76aafa18b7
commit
e348d75261
@@ -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)
|
||||
|
||||
Binary file not shown.
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user