diff options
Diffstat (limited to 'reportdesign/source/core/misc')
-rw-r--r-- | reportdesign/source/core/misc/conditionalexpression.cxx | 187 | ||||
-rw-r--r-- | reportdesign/source/core/misc/conditionupdater.cxx | 118 | ||||
-rw-r--r-- | reportdesign/source/core/misc/reportformula.cxx | 124 |
3 files changed, 429 insertions, 0 deletions
diff --git a/reportdesign/source/core/misc/conditionalexpression.cxx b/reportdesign/source/core/misc/conditionalexpression.cxx new file mode 100644 index 000000000..0fe6dbb40 --- /dev/null +++ b/reportdesign/source/core/misc/conditionalexpression.cxx @@ -0,0 +1,187 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <conditionalexpression.hxx> + +#include <o3tl/safeint.hxx> +#include <osl/diagnose.h> + + +namespace rptui +{ + + // = ConditionalExpression + + + ConditionalExpression::ConditionalExpression( const char* _pAsciiPattern ) + :m_sPattern( OUString::createFromAscii( _pAsciiPattern ) ) + { + } + + + OUString ConditionalExpression::assembleExpression( const OUString& _rFieldDataSource, const OUString& _rLHS, const OUString& _rRHS ) const + { + OUString sExpression( m_sPattern ); + + sal_Int32 nPatternIndex = sExpression.indexOf( '$' ); + while ( nPatternIndex > -1 ) + { + const OUString* pReplace = nullptr; + switch ( sExpression[ nPatternIndex + 1 ] ) + { + case '$': pReplace = &_rFieldDataSource; break; + case '1': pReplace = &_rLHS; break; + case '2': pReplace = &_rRHS; break; + default: break; + } + + if ( pReplace == nullptr ) + { + OSL_FAIL( "ConditionalExpression::assembleExpression: illegal pattern!" ); + break; + } + + sExpression = sExpression.replaceAt( nPatternIndex, 2, *pReplace ); + nPatternIndex = sExpression.indexOf( '$', nPatternIndex + pReplace->getLength() + 1 ); + } + return sExpression; + } + + + bool ConditionalExpression::matchExpression( const OUString& _rExpression, const OUString& _rFieldDataSource, OUString& _out_rLHS, OUString& _out_rRHS ) const + { + // if we had regular expression, the matching would be pretty easy ... + // just replace $1 and $2 in the pattern with (.*), and then get them with \1 resp. \2. + // Unfortunately, we don't have such a regexp engine ... + + // Okay, let's start with replacing all $$ in our pattern with the actual field data source + OUString sMatchExpression( m_sPattern ); + static const OUStringLiteral sFieldDataPattern( u"$$" ); + sal_Int32 nIndex( sMatchExpression.indexOf( sFieldDataPattern ) ); + while ( nIndex != -1 ) + { + sMatchExpression = sMatchExpression.replaceAt( nIndex, sFieldDataPattern.getLength(), _rFieldDataSource ); + nIndex = sMatchExpression.indexOf( sFieldDataPattern, nIndex + _rFieldDataSource.getLength() ); + } + + static const OUStringLiteral sLHSPattern( u"$1" ); + static const OUStringLiteral sRHSPattern( u"$2" ); + sal_Int32 nLHSIndex( sMatchExpression.indexOf( sLHSPattern ) ); + sal_Int32 nRHSIndex( sMatchExpression.indexOf( sRHSPattern ) ); + + // now we should have at most one occurrence of $1 and $2, resp. + OSL_ENSURE( sMatchExpression.indexOf( sLHSPattern, nLHSIndex + 1 ) == -1, + "ConditionalExpression::matchExpression: unsupported pattern (more than one LHS occurrence)!" ); + OSL_ENSURE( sMatchExpression.indexOf( sRHSPattern, nRHSIndex + 1 ) == -1, + "ConditionalExpression::matchExpression: unsupported pattern (more than one RHS occurrence)!" ); + // Also, an LHS must be present, and precede the RHS (if present) + OSL_ENSURE( ( nLHSIndex != -1 ) && ( ( nLHSIndex < nRHSIndex ) || ( nRHSIndex == -1 ) ), + "ConditionalExpression::matchExpression: no LHS, or an RHS preceding the LHS - this is not supported!" ); + + // up to the occurrence of the LHS (which must exist, see above), the two expressions + // must be identical + if ( _rExpression.getLength() < nLHSIndex ) + return false; + const std::u16string_view sExprPart1( _rExpression.subView( 0, nLHSIndex ) ); + const std::u16string_view sMatchExprPart1( sMatchExpression.subView( 0, nLHSIndex ) ); + if ( sExprPart1 != sMatchExprPart1 ) + // the left-most expression parts do not match + return false; + + // after the occurrence of the RHS (or the LHS, if there is no RHS), the two expressions + // must be identical, too + bool bHaveRHS( nRHSIndex != -1 ); + sal_Int32 nRightMostIndex( bHaveRHS ? nRHSIndex : nLHSIndex ); + const std::u16string_view sMatchExprPart3( sMatchExpression.subView( nRightMostIndex + 2 ) ); + if ( o3tl::make_unsigned(_rExpression.getLength()) < sMatchExprPart3.size() ) + // the expression is not even long enough to hold the right-most part of the match expression + return false; + const std::u16string_view sExprPart3( _rExpression.subView( _rExpression.getLength() - sMatchExprPart3.size() ) ); + if ( sExprPart3 != sMatchExprPart3 ) + // the right-most expression parts do not match + return false; + + // if we don't have an RHS, we're done + if ( !bHaveRHS ) + { + _out_rLHS = _rExpression.copy( sExprPart1.size(), _rExpression.getLength() - sExprPart1.size() - sExprPart3.size() ); + return true; + } + + // strip the match expression by its right-most and left-most part, and by the placeholders $1 and $2 + sal_Int32 nMatchExprPart2Start( nLHSIndex + sLHSPattern.getLength() ); + std::u16string_view sMatchExprPart2 = sMatchExpression.subView( + nMatchExprPart2Start, + sMatchExpression.getLength() - nMatchExprPart2Start - sMatchExprPart3.size() - 2 + ); + // strip the expression by its left-most and right-most part + const std::u16string_view sExpression( _rExpression.subView( + sExprPart1.size(), + _rExpression.getLength() - sExprPart1.size() - sExprPart3.size() + ) ); + + size_t nPart2Index = sExpression.find( sMatchExprPart2 ); + if ( nPart2Index == std::u16string_view::npos ) + // the "middle" part of the match expression does not exist in the expression at all + return false; + + OSL_ENSURE( sExpression.find( sMatchExprPart2, nPart2Index + 1 ) == std::u16string_view::npos, + "ConditionalExpression::matchExpression: ambiguous matching!" ); + // if this fires, then we're lost: The middle part exists two times in the expression, + // so we cannot reliably determine what's the LHS and what's the RHS. + // Well, the whole mechanism is flawed, anyway: + // Encoding the field content in the conditional expression will break as soon + // as somebody + // - assigns a Data Field to a control + // - creates a conditional format expression for the control + // - assigns another Data Field to the control + // - opens the Conditional Format Dialog, again + // Here, at the latest, you can see that we need another mechanism, anyway, which does not + // rely on those strange expression building/matching + + _out_rLHS = sExpression.substr( 0, nPart2Index ); + _out_rRHS = sExpression.substr( nPart2Index + sMatchExprPart2.size() ); + + return true; + } + + + // = ConditionalExpressionFactory + + + size_t ConditionalExpressionFactory::getKnownConditionalExpressions( ConditionalExpressions& _out_rCondExp ) + { + ConditionalExpressions().swap(_out_rCondExp); + + _out_rCondExp[ eBetween ] = std::make_shared<ConditionalExpression>( "AND( ( $$ ) >= ( $1 ); ( $$ ) <= ( $2 ) )" ); + _out_rCondExp[ eNotBetween ] = std::make_shared<ConditionalExpression>( "NOT( AND( ( $$ ) >= ( $1 ); ( $$ ) <= ( $2 ) ) )" ); + _out_rCondExp[ eEqualTo ] = std::make_shared<ConditionalExpression>( "( $$ ) = ( $1 )" ); + _out_rCondExp[ eNotEqualTo ] = std::make_shared<ConditionalExpression>( "( $$ ) <> ( $1 )" ); + _out_rCondExp[ eGreaterThan ] = std::make_shared<ConditionalExpression>( "( $$ ) > ( $1 )" ); + _out_rCondExp[ eLessThan ] = std::make_shared<ConditionalExpression>( "( $$ ) < ( $1 )" ); + _out_rCondExp[ eGreaterOrEqual ] = std::make_shared<ConditionalExpression>( "( $$ ) >= ( $1 )" ); + _out_rCondExp[ eLessOrEqual ] = std::make_shared<ConditionalExpression>( "( $$ ) <= ( $1 )" ); + + return _out_rCondExp.size(); + } + +} // namespace rptui + + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/reportdesign/source/core/misc/conditionupdater.cxx b/reportdesign/source/core/misc/conditionupdater.cxx new file mode 100644 index 000000000..428c3b3e7 --- /dev/null +++ b/reportdesign/source/core/misc/conditionupdater.cxx @@ -0,0 +1,118 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ +#include <conditionupdater.hxx> +#include <reportformula.hxx> + +#include <com/sun/star/report/XFormatCondition.hpp> + +#include <tools/diagnose_ex.h> + + +namespace rptui +{ + + + using ::com::sun::star::beans::PropertyChangeEvent; + using ::com::sun::star::uno::Reference; + using ::com::sun::star::report::XReportControlModel; + using ::com::sun::star::uno::UNO_QUERY; + using ::com::sun::star::report::XFormatCondition; + using ::com::sun::star::uno::UNO_QUERY_THROW; + using ::com::sun::star::uno::Exception; + + + //= ConditionUpdater + + + ConditionUpdater::ConditionUpdater() + { + } + + + ConditionUpdater::~ConditionUpdater() + { + } + + + void ConditionUpdater::notifyPropertyChange( const PropertyChangeEvent& _rEvent ) + { + impl_lateInit_nothrow(); + + Reference< XReportControlModel > xRptControlModel( _rEvent.Source, UNO_QUERY ); + if ( xRptControlModel.is() && _rEvent.PropertyName == "DataField" ) + { + OUString sOldDataSource, sNewDataSource; + OSL_VERIFY( _rEvent.OldValue >>= sOldDataSource ); + OSL_VERIFY( _rEvent.NewValue >>= sNewDataSource ); + impl_adjustFormatConditions_nothrow( xRptControlModel, sOldDataSource, sNewDataSource ); + } + } + + + void ConditionUpdater::impl_lateInit_nothrow() + { + if ( !m_aConditionalExpressions.empty() ) + return; + + ConditionalExpressionFactory::getKnownConditionalExpressions( m_aConditionalExpressions ); + } + + + void ConditionUpdater::impl_adjustFormatConditions_nothrow( const Reference< XReportControlModel >& _rxRptControlModel, + const OUString& _rOldDataSource, const OUString& _rNewDataSource ) + { + try + { + ReportFormula aOldContentFormula( _rOldDataSource ); + OUString sOldUnprefixed( aOldContentFormula.getBracketedFieldOrExpression() ); + ReportFormula aNewContentFormula( _rNewDataSource ); + OUString sNewUnprefixed( aNewContentFormula.getBracketedFieldOrExpression() ); + + sal_Int32 nCount( _rxRptControlModel->getCount() ); + Reference< XFormatCondition > xFormatCondition; + OUString sFormulaExpression, sLHS, sRHS; + for ( sal_Int32 i=0; i<nCount; ++i ) + { + xFormatCondition.set( _rxRptControlModel->getByIndex( i ), UNO_QUERY_THROW ); + sFormulaExpression = ReportFormula(xFormatCondition->getFormula()).getExpression(); + + for (const auto& rEntry : m_aConditionalExpressions) + { + if ( !rEntry.second->matchExpression( sFormulaExpression, sOldUnprefixed, sLHS, sRHS ) ) + continue; + + // the expression matches -> translate it to the new data source of the report control model + sFormulaExpression = rEntry.second->assembleExpression( sNewUnprefixed, sLHS, sRHS ); + ReportFormula aFormula(ReportFormula(ReportFormula::Expression, sFormulaExpression)); + xFormatCondition->setFormula(aFormula.getCompleteFormula()); + break; + } + } + } + catch( const Exception& ) + { + DBG_UNHANDLED_EXCEPTION("reportdesign"); + } + } + + +} // namespace rptui + + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/reportdesign/source/core/misc/reportformula.cxx b/reportdesign/source/core/misc/reportformula.cxx new file mode 100644 index 000000000..cd6ae4df5 --- /dev/null +++ b/reportdesign/source/core/misc/reportformula.cxx @@ -0,0 +1,124 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <reportformula.hxx> + +#include <rtl/ustrbuf.hxx> + + +namespace rptui +{ + namespace + { + + const char sExpressionPrefix[] = "rpt:"; + const char sFieldPrefix[] = "field:"; + } + + + //= ReportFormula + + + ReportFormula::ReportFormula( const OUString& _rFormula ) + :m_eType( Invalid ) + { + m_sCompleteFormula = _rFormula; + + // is it an ordinary expression? + if ( m_sCompleteFormula.startsWith( sExpressionPrefix, &m_sUndecoratedContent ) ) + { + m_eType = Expression; + return; + } + + /// does it refer to a field? + if ( m_sCompleteFormula.startsWith( sFieldPrefix ) ) + { + sal_Int32 nPrefixLen = strlen(sFieldPrefix); + if ( ( m_sCompleteFormula.getLength() >= nPrefixLen + 2 ) + && ( m_sCompleteFormula[ nPrefixLen ] == '[' ) + && ( m_sCompleteFormula[ m_sCompleteFormula.getLength() - 1 ] == ']' ) + ) + { + m_eType = Field; + m_sUndecoratedContent = m_sCompleteFormula.copy( nPrefixLen + 1, m_sCompleteFormula.getLength() - nPrefixLen - 2 ); + return; + } + } + + m_eType = Invalid; + } + + + ReportFormula::ReportFormula( const BindType _eType, const OUString& _rFieldOrExpression ) + :m_eType( _eType ) + { + switch ( m_eType ) + { + case Expression: + { + if ( _rFieldOrExpression.startsWith( sExpressionPrefix ) ) + m_sCompleteFormula = _rFieldOrExpression; + else + m_sCompleteFormula = sExpressionPrefix + _rFieldOrExpression; + } + break; + + case Field: + { + m_sCompleteFormula = sFieldPrefix + OUString::Concat(u"[") + _rFieldOrExpression + "]"; + } + break; + default: + OSL_FAIL( "ReportFormula::ReportFormula: illegal bind type!" ); + return; + } + + m_sUndecoratedContent = _rFieldOrExpression; + } + + ReportFormula::~ReportFormula() + { + } + + OUString ReportFormula::getBracketedFieldOrExpression() const + { + bool bIsField = ( getType() == Field ); + OUStringBuffer aFieldContent; + if ( bIsField ) + aFieldContent.append( "[" ); + aFieldContent.append( getUndecoratedContent() ); + if ( bIsField ) + aFieldContent.append( "]" ); + + return aFieldContent.makeStringAndClear(); + } + + bool ReportFormula::isValid() const { return getType() != Invalid; } + + OUString ReportFormula::getEqualUndecoratedContent() const + { + return "=" + getUndecoratedContent(); + } + + +} // namespace rptui + + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ |