diff --git a/chart2/qa/extras/chart2export2.cxx b/chart2/qa/extras/chart2export2.cxx index e05d984f75cf..adde208d620c 100644 --- a/chart2/qa/extras/chart2export2.cxx +++ b/chart2/qa/extras/chart2export2.cxx @@ -97,6 +97,7 @@ public: void testTdf138181(); void testCustomShapeText(); void testuserShapesXLSX(); + void testNameRangeXLSX(); CPPUNIT_TEST_SUITE(Chart2ExportTest2); CPPUNIT_TEST(testSetSeriesToSecondaryAxisXLSX); @@ -157,6 +158,7 @@ public: CPPUNIT_TEST(testTdf138181); CPPUNIT_TEST(testCustomShapeText); CPPUNIT_TEST(testuserShapesXLSX); + CPPUNIT_TEST(testNameRangeXLSX); CPPUNIT_TEST_SUITE_END(); }; @@ -1420,6 +1422,21 @@ void Chart2ExportTest2::testuserShapesXLSX() CPPUNIT_ASSERT(!xRange->getString().isEmpty()); } +void Chart2ExportTest2::testNameRangeXLSX() +{ + load(u"/chart2/qa/extras/data/xlsx/", "chart_with_name_range.xlsx"); + xmlDocUniquePtr pXmlDoc = parseExport("xl/charts/chart", "Calc Office Open XML"); + CPPUNIT_ASSERT(pXmlDoc); + // test the syntax of local range name on the the local sheet. + assertXPathContent(pXmlDoc, + "/c:chartSpace/c:chart/c:plotArea/c:barChart/c:ser/c:cat/c:strRef/c:f", + "Sheet1!local_name_range"); + // test the syntax of a global range name. + assertXPathContent(pXmlDoc, + "/c:chartSpace/c:chart/c:plotArea/c:barChart/c:ser/c:val/c:numRef/c:f", + "[0]!series1"); +} + CPPUNIT_TEST_SUITE_REGISTRATION(Chart2ExportTest2); CPPUNIT_PLUGIN_IMPLEMENT(); diff --git a/chart2/qa/extras/data/xlsx/chart_with_name_range.xlsx b/chart2/qa/extras/data/xlsx/chart_with_name_range.xlsx new file mode 100644 index 000000000000..2f2b814011ff Binary files /dev/null and b/chart2/qa/extras/data/xlsx/chart_with_name_range.xlsx differ diff --git a/offapi/com/sun/star/sheet/FormulaParser.idl b/offapi/com/sun/star/sheet/FormulaParser.idl index 2c1145ec1369..fb9085130341 100644 --- a/offapi/com/sun/star/sheet/FormulaParser.idl +++ b/offapi/com/sun/star/sheet/FormulaParser.idl @@ -81,6 +81,16 @@ service FormulaParser */ [property] sequence< ExternalLinkInfo > ExternalLinks; + + /** specifies that use special ooxml chart syntax in case of OOXML reference + convention, when parsing a formula string. + +

Special syntax like: [0]!GlobalNamedRange, LocalSheet!LocalNamedRange

+ + @since LibreOffice 7.3 + */ + [property] boolean RefConventionChartOOXML; + }; diff --git a/oox/source/export/chartexport.cxx b/oox/source/export/chartexport.cxx index 2a0d335ca86b..dfebc8bf061a 100644 --- a/oox/source/export/chartexport.cxx +++ b/oox/source/export/chartexport.cxx @@ -710,6 +710,8 @@ OUString ChartExport::parseFormula( const OUString& rRange ) if( xParserProps.is() ) { xParserProps->setPropertyValue("FormulaConvention", uno::makeAny(css::sheet::AddressConvention::XL_OOX) ); + // For referencing named ranges correctly with special excel chart syntax. + xParserProps->setPropertyValue("RefConventionChartOOXML", uno::makeAny(true) ); } aResult = xParser->printFormula( aTokens, CellAddress( 0, 0, 0 ) ); } diff --git a/oox/source/token/properties.txt b/oox/source/token/properties.txt index b35a5bc3cd4a..808df85533ff 100644 --- a/oox/source/token/properties.txt +++ b/oox/source/token/properties.txt @@ -411,6 +411,7 @@ RangeYMaximum RangeYMinimum ReadOnly RefAngle +RefConventionChartOOXML RefR RefX RefY diff --git a/sc/inc/compiler.hxx b/sc/inc/compiler.hxx index 17e258dc3805..8ae65a4eb8b3 100644 --- a/sc/inc/compiler.hxx +++ b/sc/inc/compiler.hxx @@ -297,6 +297,8 @@ private: ExtendedErrorDetection meExtendedErrorDetection; bool mbCloseBrackets; // whether to close open brackets automatically, default TRUE bool mbRewind; // whether symbol is to be rewound to some step during lexical analysis + bool mbRefConventionChartOOXML; // whether to use special ooxml chart syntax in case of OOXML reference convention, + // when parsing a formula string. [0]!GlobalNamedRange, LocalSheet!LocalNamedRange std::vector maExternalFiles; std::vector maTabNames; /// sheet names mangled for the current grammar for output @@ -426,6 +428,7 @@ public: */ void SetAutoCorrection( bool bVal ); void SetCloseBrackets( bool bVal ) { mbCloseBrackets = bVal; } + void SetRefConventionChartOOXML( bool bVal ) { mbRefConventionChartOOXML = bVal; } void SetRefConvention( const Convention *pConvP ); void SetRefConvention( const formula::FormulaGrammar::AddressConvention eConv ); diff --git a/sc/inc/tokenuno.hxx b/sc/inc/tokenuno.hxx index cded5065f6b2..d025ba0cf44d 100644 --- a/sc/inc/tokenuno.hxx +++ b/sc/inc/tokenuno.hxx @@ -62,6 +62,7 @@ private: bool mbEnglish; bool mbIgnoreSpaces; bool mbCompileFAP; + bool mbRefConventionChartOOXML; void SetCompilerFlags( ScCompiler& rCompiler ) const; diff --git a/sc/inc/unonames.hxx b/sc/inc/unonames.hxx index c4e7a234f1a3..56936063f74c 100644 --- a/sc/inc/unonames.hxx +++ b/sc/inc/unonames.hxx @@ -674,6 +674,7 @@ #define SC_UNO_OPCODEMAP "OpCodeMap" #define SC_UNO_EXTERNALLINKS "ExternalLinks" #define SC_UNO_COMPILEFAP "CompileFAP" +#define SC_UNO_REF_CONV_CHARTOOXML "RefConventionChartOOXML" // Chart2 #define SC_UNONAME_ROLE "Role" diff --git a/sc/qa/uitest/chart/tdf64086.py b/sc/qa/uitest/chart/tdf64086.py new file mode 100644 index 000000000000..238a936a9caa --- /dev/null +++ b/sc/qa/uitest/chart/tdf64086.py @@ -0,0 +1,31 @@ +# -*- tab-width: 4; indent-tabs-mode: nil; py-indent-offset: 4 -*- +# +# 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/. +# + +from uitest.framework import UITestCase +from uitest.uihelper.common import get_url_for_data_file + +class NamedRangesChart(UITestCase): + + def test_chart_series_ranges_with_named_ranges_as_datasource(self): + with self.ui_test.load_file(get_url_for_data_file("tdf64086.xlsx")) as calc_doc: + + xChart = calc_doc.Sheets[0].Charts[0] + xDataSeries = xChart.getEmbeddedObject().getFirstDiagram().CoordinateSystems[0].ChartTypes[0].DataSeries + + self.assertEqual(3, len(xDataSeries)) + + test_range1 = calc_doc.Sheets[0].NamedRanges.getByName("Local_sheet_name") + self.assertEqual(xDataSeries[0].DataSequences[0].Values.SourceRangeRepresentation, test_range1.Name) + + test_range2 = calc_doc.NamedRanges.getByName("global_name") + self.assertEqual(xDataSeries[1].DataSequences[0].Values.SourceRangeRepresentation, test_range2.Name) + + test_range3 = calc_doc.Sheets[1].NamedRanges.getByName("other_sheet_name") + test_range3_Name = calc_doc.Sheets[1].Name + '.' + test_range3.Name + self.assertEqual(xDataSeries[2].DataSequences[0].Values.SourceRangeRepresentation, test_range3_Name) + +# vim: set shiftwidth=4 softtabstop=4 expandtab: diff --git a/sc/qa/uitest/data/tdf64086.xlsx b/sc/qa/uitest/data/tdf64086.xlsx new file mode 100644 index 000000000000..4cbd5ce8dbd1 Binary files /dev/null and b/sc/qa/uitest/data/tdf64086.xlsx differ diff --git a/sc/source/core/tool/compiler.cxx b/sc/source/core/tool/compiler.cxx index 83eb2f4ab7db..d6ae770e5c91 100644 --- a/sc/source/core/tool/compiler.cxx +++ b/sc/source/core/tool/compiler.cxx @@ -1842,6 +1842,7 @@ ScCompiler::ScCompiler( sc::CompileFormulaContext& rCxt, const ScAddress& rPos, meExtendedErrorDetection(EXTENDED_ERROR_DETECTION_NONE), mbCloseBrackets(true), mbRewind(false), + mbRefConventionChartOOXML(false), maTabNames(rCxt.getTabNames()) { SetGrammar(rCxt.getGrammar()); @@ -1865,7 +1866,8 @@ ScCompiler::ScCompiler( ScDocument& rDocument, const ScAddress& rPos, ScTokenArr pConv( GetRefConvention( FormulaGrammar::CONV_OOO ) ), meExtendedErrorDetection( EXTENDED_ERROR_DETECTION_NONE ), mbCloseBrackets( true ), - mbRewind( false ) + mbRewind( false ), + mbRefConventionChartOOXML( false ) { SetGrammar( (eGrammar == formula::FormulaGrammar::GRAM_UNSPECIFIED) ? rDocument.GetGrammar() : @@ -1889,6 +1891,7 @@ ScCompiler::ScCompiler( sc::CompileFormulaContext& rCxt, const ScAddress& rPos, meExtendedErrorDetection(EXTENDED_ERROR_DETECTION_NONE), mbCloseBrackets(true), mbRewind(false), + mbRefConventionChartOOXML(false), maTabNames(rCxt.getTabNames()) { SetGrammar(rCxt.getGrammar()); @@ -1912,7 +1915,8 @@ ScCompiler::ScCompiler( ScDocument& rDocument, const ScAddress& rPos, pConv( GetRefConvention( FormulaGrammar::CONV_OOO ) ), meExtendedErrorDetection( EXTENDED_ERROR_DETECTION_NONE ), mbCloseBrackets( true ), - mbRewind( false ) + mbRewind( false ), + mbRefConventionChartOOXML( false ) { SetGrammar( (eGrammar == formula::FormulaGrammar::GRAM_UNSPECIFIED) ? rDocument.GetGrammar() : @@ -2244,6 +2248,18 @@ Label_MaskStateMachine: if (c == '[' && FormulaGrammar::isExcelSyntax( meGrammar) && eLastOp != ocDBArea && maTableRefs.empty()) { + // [0]!Global_Range_Name, is a special case in OOXML + // syntax, where the '0' is referencing to self and we + // do not need it, so we should skip it, in order to + // later it will be more recognisable for IsNamedRange. + if (FormulaGrammar::isRefConventionOOXML(meGrammar) && + pSrc[0] == '0' && pSrc[1] == ']' && pSrc[2] == '!') + { + pSrc += 3; + c = *pSrc; + continue; + } + nMask &= ~ScCharFlags::Char; goto Label_MaskStateMachine; } @@ -5313,7 +5329,7 @@ void ScCompiler::CreateStringFromIndex( OUStringBuffer& rBuffer, const FormulaTo if (pData) { SCTAB nTab = _pTokenP->GetSheet(); - if (nTab >= 0 && nTab != aPos.Tab()) + if (nTab >= 0 && (nTab != aPos.Tab() || mbRefConventionChartOOXML)) { // Sheet-local on other sheet. OUString aName; @@ -5326,6 +5342,11 @@ void ScCompiler::CreateStringFromIndex( OUStringBuffer& rBuffer, const FormulaTo aBuffer.append(ScCompiler::GetNativeSymbol(ocErrName)); aBuffer.append( pConv->getSpecialSymbol( ScCompiler::Convention::SHEET_SEPARATOR)); } + else if (mbRefConventionChartOOXML) + { + aBuffer.append("[0]"); + aBuffer.append(pConv->getSpecialSymbol(ScCompiler::Convention::SHEET_SEPARATOR)); + } aBuffer.append(pData->GetName()); } } diff --git a/sc/source/core/tool/reftokenhelper.cxx b/sc/source/core/tool/reftokenhelper.cxx index 58d9353222dc..7d49a60f3e97 100644 --- a/sc/source/core/tool/reftokenhelper.cxx +++ b/sc/source/core/tool/reftokenhelper.cxx @@ -108,6 +108,16 @@ void ScRefTokenHelper::compileRangeRepresentation( if (p->GetString().isEmpty()) bFailure = true; break; + case svIndex: + { + if (p->GetOpCode() == ocName) + { + ScRangeData* pNameRange = rDoc.FindRangeNameBySheetAndIndex(p->GetSheet(), p->GetIndex()); + if (!pNameRange->HasReferences()) + bFailure = true; + } + } + break; default: bFailure = true; break; @@ -150,6 +160,16 @@ bool ScRefTokenHelper::getRangeFromToken( rRange = rRefData.toAbs(*pDoc, rPos); return true; } + case svIndex: + { + if (pToken->GetOpCode() == ocName) + { + ScRangeData* pNameRange = pDoc->FindRangeNameBySheetAndIndex(pToken->GetSheet(), pToken->GetIndex()); + if (pNameRange->IsReference(rRange, rPos)) + return true; + } + return false; + } default: ; // do nothing } diff --git a/sc/source/ui/unoobj/chart2uno.cxx b/sc/source/ui/unoobj/chart2uno.cxx index 2f0461b39a5d..3d1b05256578 100644 --- a/sc/source/ui/unoobj/chart2uno.cxx +++ b/sc/source/ui/unoobj/chart2uno.cxx @@ -874,9 +874,21 @@ public: ScTokenRef aStart, aEnd; bool bValidToken = splitRangeToken(*mpDoc, rToken, aStart, aEnd); + // Check there is a valid reference in named range + if (!bValidToken && rToken->GetType() == svIndex && rToken->GetOpCode() == ocName) + { + ScRangeData* pNameRange = mpDoc->FindRangeNameBySheetAndIndex(rToken->GetSheet(), rToken->GetIndex()); + if (pNameRange->HasReferences()) + { + const ScTokenRef aTempToken = pNameRange->GetCode()->FirstToken(); + bValidToken = splitRangeToken(*mpDoc, aTempToken, aStart, aEnd); + } + } + OSL_ENSURE(bValidToken, "invalid token"); if (!bValidToken) return; + ScCompiler aCompiler(*mpDoc, ScAddress(0,0,0), FormulaGrammar::GRAM_ENGLISH); { OUString aStr; @@ -1593,7 +1605,7 @@ class RangeAnalyzer { public: RangeAnalyzer(); - void initRangeAnalyzer( const vector& rTokens ); + void initRangeAnalyzer( const ScDocument* pDoc, const vector& rTokens ); void analyzeRange( sal_Int32& rnDataInRows, sal_Int32& rnDataInCols, bool& rbRowSourceAmbiguous ) const; bool inSameSingleRow( const RangeAnalyzer& rOther ); @@ -1621,7 +1633,7 @@ RangeAnalyzer::RangeAnalyzer() { } -void RangeAnalyzer::initRangeAnalyzer( const vector& rTokens ) +void RangeAnalyzer::initRangeAnalyzer( const ScDocument* pDoc, const vector& rTokens ) { mnRowCount=0; mnColumnCount=0; @@ -1675,6 +1687,28 @@ void RangeAnalyzer::initRangeAnalyzer( const vector& rTokens ) mbAmbiguous=true; } } + else if (eVar == svIndex && aRefToken->GetOpCode() == ocName) + { + ScRangeData* pNameRange = pDoc->FindRangeNameBySheetAndIndex(aRefToken->GetSheet(), aRefToken->GetIndex()); + ScRange aRange; + if (pNameRange->IsReference(aRange)) + { + mnColumnCount = std::max(mnColumnCount, static_cast(abs(aRange.aEnd.Col() - aRange.aStart.Col()) + 1)); + mnRowCount = std::max(mnRowCount, static_cast(abs(aRange.aEnd.Row() - aRange.aStart.Row()) + 1)); + if (mnStartColumn == -1) + { + mnStartColumn = aRange.aStart.Col(); + mnStartRow = aRange.aStart.Row(); + } + else + { + if (mnStartColumn != aRange.aStart.Col() && mnStartRow != aRange.aStart.Row()) + mbAmbiguous = true; + } + } + else + mbAmbiguous = true; + } else mbAmbiguous=true; } @@ -1777,10 +1811,22 @@ uno::Sequence< beans::PropertyValue > SAL_CALL ScChart2DataProvider::detectArgum const sal_Unicode cSep = ScCompiler::GetNativeSymbolChar(ocSep); ScRefTokenHelper::compileRangeRepresentation( aTokens, xLabel->getSourceRangeRepresentation(), *m_pDocument, cSep, m_pDocument->GetGrammar(), true); - aLabel.initRangeAnalyzer(aTokens); + aLabel.initRangeAnalyzer(m_pDocument, aTokens); for (const auto& rxToken : aTokens) { - ScRefTokenHelper::join(m_pDocument, aAllTokens, rxToken, ScAddress()); + if (rxToken->GetType() == svIndex && rxToken->GetOpCode() == ocName) + { + ScRangeData* pNameRange = m_pDocument->FindRangeNameBySheetAndIndex(rxToken->GetSheet(), rxToken->GetIndex()); + if (pNameRange->HasReferences()) + { + const ScTokenRef aTempToken = pNameRange->GetCode()->FirstToken(); + ScRefTokenHelper::join(m_pDocument, aAllTokens, aTempToken, ScAddress()); + } + else + ScRefTokenHelper::join(m_pDocument, aAllTokens, rxToken, ScAddress()); + } + else + ScRefTokenHelper::join(m_pDocument, aAllTokens, rxToken, ScAddress()); if(!bThisIsCategories) ScRefTokenHelper::join(m_pDocument, aAllSeriesLabelTokens, rxToken, ScAddress()); } @@ -1795,10 +1841,22 @@ uno::Sequence< beans::PropertyValue > SAL_CALL ScChart2DataProvider::detectArgum const sal_Unicode cSep = ScCompiler::GetNativeSymbolChar(ocSep); ScRefTokenHelper::compileRangeRepresentation( aTokens, xValues->getSourceRangeRepresentation(), *m_pDocument, cSep, m_pDocument->GetGrammar(), true); - aValues.initRangeAnalyzer(aTokens); + aValues.initRangeAnalyzer(m_pDocument, aTokens); for (const auto& rxToken : aTokens) { - ScRefTokenHelper::join(m_pDocument, aAllTokens, rxToken, ScAddress()); + if (rxToken->GetType() == svIndex && rxToken->GetOpCode() == ocName) + { + ScRangeData* pNameRange = m_pDocument->FindRangeNameBySheetAndIndex(rxToken->GetSheet(), rxToken->GetIndex()); + if (pNameRange->HasReferences()) + { + const ScTokenRef aTempToken = pNameRange->GetCode()->FirstToken(); + ScRefTokenHelper::join(m_pDocument, aAllTokens, aTempToken, ScAddress()); + } + else + ScRefTokenHelper::join(m_pDocument, aAllTokens, rxToken, ScAddress()); + } + else + ScRefTokenHelper::join(m_pDocument, aAllTokens, rxToken, ScAddress()); if(bThisIsCategories) ScRefTokenHelper::join(m_pDocument, aAllCategoriesValuesTokens, rxToken, ScAddress()); } @@ -1885,13 +1943,13 @@ uno::Sequence< beans::PropertyValue > SAL_CALL ScChart2DataProvider::detectArgum RangeAnalyzer aTop,aLeft; if( eRowSource==chart::ChartDataRowSource_COLUMNS ) { - aTop.initRangeAnalyzer(aAllSeriesLabelTokens); - aLeft.initRangeAnalyzer(aAllCategoriesValuesTokens); + aTop.initRangeAnalyzer(m_pDocument, aAllSeriesLabelTokens); + aLeft.initRangeAnalyzer(m_pDocument, aAllCategoriesValuesTokens); } else { - aTop.initRangeAnalyzer(aAllCategoriesValuesTokens); - aLeft.initRangeAnalyzer(aAllSeriesLabelTokens); + aTop.initRangeAnalyzer(m_pDocument, aAllCategoriesValuesTokens); + aLeft.initRangeAnalyzer(m_pDocument, aAllSeriesLabelTokens); } lcl_addUpperLeftCornerIfMissing(m_pDocument, aAllTokens, aTop.getRowCount(), aLeft.getColumnCount());//e.g. #i91212# } @@ -2127,6 +2185,7 @@ ScChart2DataProvider::createDataSequenceByFormulaTokens( } } break; + case svIndex: case svString: case svSingleRef: case svDoubleRef: diff --git a/sc/source/ui/unoobj/tokenuno.cxx b/sc/source/ui/unoobj/tokenuno.cxx index b07a04e12b04..96f882d282ea 100644 --- a/sc/source/ui/unoobj/tokenuno.cxx +++ b/sc/source/ui/unoobj/tokenuno.cxx @@ -78,7 +78,8 @@ ScFormulaParserObj::ScFormulaParserObj(ScDocShell* pDocSh) : mnConv( sheet::AddressConvention::UNSPECIFIED ), mbEnglish( false ), mbIgnoreSpaces( true ), - mbCompileFAP( false ) + mbCompileFAP( false ), + mbRefConventionChartOOXML( false ) { mpDocShell->GetDocument().AddUnoObject(*this); } @@ -122,7 +123,8 @@ void ScFormulaParserObj::SetCompilerFlags( ScCompiler& rCompiler ) const rCompiler.EnableJumpCommandReorder(!mbCompileFAP); rCompiler.EnableStopOnError(!mbCompileFAP); - rCompiler.SetExternalLinks( maExternalLinks); + rCompiler.SetExternalLinks(maExternalLinks); + rCompiler.SetRefConventionChartOOXML(mbRefConventionChartOOXML); } uno::Sequence SAL_CALL ScFormulaParserObj::parseFormula( @@ -243,6 +245,11 @@ void SAL_CALL ScFormulaParserObj::setPropertyValue( if (!(aValue >>= maExternalLinks)) throw lang::IllegalArgumentException(); } + else if ( aPropertyName == SC_UNO_REF_CONV_CHARTOOXML ) + { + if (!(aValue >>= mbRefConventionChartOOXML)) + throw lang::IllegalArgumentException(); + } else throw beans::UnknownPropertyException(aPropertyName); } @@ -275,6 +282,10 @@ uno::Any SAL_CALL ScFormulaParserObj::getPropertyValue( const OUString& aPropert { aRet <<= maExternalLinks; } + else if ( aPropertyName == SC_UNO_REF_CONV_CHARTOOXML ) + { + aRet <<= mbRefConventionChartOOXML; + } else throw beans::UnknownPropertyException(aPropertyName); return aRet;