tdf#64086 tdf#143623 tdf#66250 XLSX: fix named ranges in charts

tdf#64086: fix broken XLSX import of named ranges in charts

Do not create inner data table in Calc for charts, if the
data source contains named ranges. Use the named ranges
(local sheet names and global names) to find the data
source of charts, so it will be updated correctly if we
modify the values in cells.

Second part:

tdf#143623: chart OOXML, offapi: export the named ranges in charts

with the proper special (non-ooxml-standard) Excel syntax.
We need to add "[0]" characters before a global named range
for proper refreshing of chart data. Also we have to add the
sheet name to the local named ranges even if it's on the same
sheet, but only in case of charts. Because of this, add property
RefConventionChartOOXML to com::sun::star::sheet::FormulaParser,
which specifies that use special OOXML chart syntax in case of
OOXML reference convention, when parsing a formula string.

Third part:

tdf#66250 ODF chart: export the reference of named ranges

in case of charts, if the named range contains a valid reference.

Note: maybe export the name of named ranges would be a nicer
solution in the future.

Follow-up of commit 3c76651298
"Related: tdf#64086 Add FormulaGrammar::isRefConventionOOXML()".

Thanks to Eike Rathke for his help.

Change-Id: I10d8563fb436092e833682f331c25b0c0829ef86
Reviewed-on: https://gerrit.libreoffice.org/c/core/+/118862
Tested-by: László Németh <nemeth@numbertext.org>
Reviewed-by: László Németh <nemeth@numbertext.org>
This commit is contained in:
Balazs Varga
2021-08-09 19:32:36 +02:00
committed by László Németh
parent e8d8dad6bb
commit 233286df08
14 changed files with 192 additions and 15 deletions
+17
View File
@@ -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();
@@ -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.
<p>Special syntax like: [0]!GlobalNamedRange, LocalSheet!LocalNamedRange</p>
@since LibreOffice 7.3
*/
[property] boolean RefConventionChartOOXML;
};
+2
View File
@@ -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 ) );
}
+1
View File
@@ -411,6 +411,7 @@ RangeYMaximum
RangeYMinimum
ReadOnly
RefAngle
RefConventionChartOOXML
RefR
RefX
RefY
+3
View File
@@ -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<sal_uInt16> maExternalFiles;
std::vector<OUString> 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 );
+1
View File
@@ -62,6 +62,7 @@ private:
bool mbEnglish;
bool mbIgnoreSpaces;
bool mbCompileFAP;
bool mbRefConventionChartOOXML;
void SetCompilerFlags( ScCompiler& rCompiler ) const;
+1
View File
@@ -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"
+31
View File
@@ -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:
Binary file not shown.
+24 -3
View File
@@ -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());
}
}
+20
View File
@@ -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
}
+69 -10
View File
@@ -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<ScTokenRef>& rTokens );
void initRangeAnalyzer( const ScDocument* pDoc, const vector<ScTokenRef>& 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<ScTokenRef>& rTokens )
void RangeAnalyzer::initRangeAnalyzer( const ScDocument* pDoc, const vector<ScTokenRef>& rTokens )
{
mnRowCount=0;
mnColumnCount=0;
@@ -1675,6 +1687,28 @@ void RangeAnalyzer::initRangeAnalyzer( const vector<ScTokenRef>& 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<SCCOL>(mnColumnCount, static_cast<SCCOL>(abs(aRange.aEnd.Col() - aRange.aStart.Col()) + 1));
mnRowCount = std::max<SCROW>(mnRowCount, static_cast<SCROW>(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:
+13 -2
View File
@@ -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<sheet::FormulaToken> 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;