diff options
author | Eike Rathke <erack@redhat.com> | 2016-05-13 19:16:26 +0200 |
---|---|---|
committer | Eike Rathke <erack@redhat.com> | 2016-05-13 20:08:45 +0200 |
commit | bc1c92ef41e5f70eee7799d301b54985999482bb (patch) | |
tree | 6f8327093844bc3b286e3e0e3a4554907747e897 | |
parent | 5b78551dcf54158adffe3236a45946942af5f354 (diff) |
recognize #REF! particles of invalidated references, tdf#86575 follow-up
... so they result in an invalid reference again producing a #REF! error
instead of a bad string producing #NAME? error.
This way we can handle the invalid #REF!.A1 and similar references that
were wrongly written to ODFF between 2013 and 2016 until 5.1.4
As a benefit, this is now also the case in UI, e.g. when recompiling
changed names with already invalidated references.
Change-Id: I117d709f594b7c37d899528a51220c1855b7817d
-rw-r--r-- | sc/inc/address.hxx | 6 | ||||
-rw-r--r-- | sc/inc/compiler.hxx | 9 | ||||
-rw-r--r-- | sc/source/core/tool/address.cxx | 198 | ||||
-rw-r--r-- | sc/source/core/tool/compiler.cxx | 142 |
4 files changed, 264 insertions, 91 deletions
diff --git a/sc/inc/address.hxx b/sc/inc/address.hxx index 218f3462132c..f02f064b7a38 100644 --- a/sc/inc/address.hxx +++ b/sc/inc/address.hxx @@ -320,7 +320,8 @@ public: const Details& rDetails = detailsOOOa1, ExternalInfo* pExtInfo = nullptr, const css::uno::Sequence<css::sheet::ExternalLinkInfo>* pExternalLinks = nullptr, - sal_Int32* pSheetEndPos = nullptr ); + sal_Int32* pSheetEndPos = nullptr, + const OUString* pErrRef = nullptr ); SC_DLLPUBLIC void Format( OStringBuffer& r, ScRefFlags nFlags = ScRefFlags::ZERO, const ScDocument* pDocument = nullptr, @@ -504,7 +505,8 @@ public: SC_DLLPUBLIC ScRefFlags Parse( const OUString&, ScDocument* = nullptr, const ScAddress::Details& rDetails = ScAddress::detailsOOOa1, ScAddress::ExternalInfo* pExtInfo = nullptr, - const css::uno::Sequence<css::sheet::ExternalLinkInfo>* pExternalLinks = nullptr ); + const css::uno::Sequence<css::sheet::ExternalLinkInfo>* pExternalLinks = nullptr, + const OUString* pErrRef = nullptr ); SC_DLLPUBLIC ScRefFlags ParseAny( const OUString&, ScDocument* = nullptr, const ScAddress::Details& rDetails = ScAddress::detailsOOOa1 ); diff --git a/sc/inc/compiler.hxx b/sc/inc/compiler.hxx index d8f5e3e3cede..abc41e5a4407 100644 --- a/sc/inc/compiler.hxx +++ b/sc/inc/compiler.hxx @@ -312,10 +312,11 @@ private: bool IsOpCode( const OUString&, bool bInArray ); bool IsOpCode2( const OUString& ); bool IsString(); - bool IsReference( const OUString& ); - bool IsSingleReference( const OUString& ); - bool IsPredetectedReference(const OUString&); - bool IsDoubleReference( const OUString& ); + bool IsReference( const OUString& rSymbol, const OUString* pErrRef = nullptr ); + bool IsSingleReference( const OUString& rSymbol, const OUString* pErrRef = nullptr ); + bool IsDoubleReference( const OUString& rSymbol, const OUString* pErrRef = nullptr ); + bool IsPredetectedReference( const OUString& rSymbol ); + bool IsPredetectedErrRefReference( const OUString& rName, const OUString* pErrRef ); bool IsMacro( const OUString& ); bool IsNamedRange( const OUString& ); bool IsExternalNamedRange( const OUString& rSymbol, bool& rbInvalidExternalNameRange ); diff --git a/sc/source/core/tool/address.cxx b/sc/source/core/tool/address.cxx index c016102f2649..e0d4b0c78fe4 100644 --- a/sc/source/core/tool/address.cxx +++ b/sc/source/core/tool/address.cxx @@ -1056,6 +1056,38 @@ static ScRefFlags lcl_ScRange_Parse_XL_A1( ScRange& r, return nFlags; } +// Compare ignore case ASCII. +static bool lcl_isString( const sal_Unicode* p1, const OUString& rStr ) +{ + const size_t n = rStr.getLength(); + if (!n) + return false; + const sal_Unicode* p2 = rStr.getStr(); + for (size_t i=0; i<n; ++i) + { + if (!p1[i]) + return false; + if (p1[i] != p2[i]) + { + sal_Unicode c1 = p1[i]; + if ('A' <= c1 && c1 <= 'Z') + c1 += 0x20; + if (c1 < 'a' || 'z' < c1) + return false; // not a letter + + sal_Unicode c2 = p2[i]; + if ('A' <= c2 && c2 <= 'Z') + c2 += 0x20; + if (c2 < 'a' || 'z' < c2) + return false; // not a letter to match + + if (c1 != c2) + return false; // lower case doesn't match either + } + } + return true; +} + /** @param p pointer to null-terminated sal_Unicode string @param rRawRes returns ScRefFlags::... flags without the final check for full @@ -1071,7 +1103,8 @@ static ScRefFlags lcl_ScAddress_Parse_OOo( const sal_Unicode* p, ScDocument* pDo ScRefFlags& rRawRes, ScAddress::ExternalInfo* pExtInfo, ScRange* pRange, - sal_Int32* pSheetEndPos ) + sal_Int32* pSheetEndPos, + const OUString* pErrRef ) { const sal_Unicode* const pStart = p; if (pSheetEndPos) @@ -1121,54 +1154,64 @@ static ScRefFlags lcl_ScAddress_Parse_OOo( const sal_Unicode* p, ScDocument* pDo p++; } - if (*p == '\'') + if (pErrRef && lcl_isString( p, *pErrRef) && p[pErrRef->getLength()] == '.') { - // Tokens that start at ' can have anything in them until a final - // ' but '' marks an escaped '. We've earlier guaranteed that a - // string containing '' will be surrounded by '. - p = parseQuotedName(p, aTab); + // #REF! particle of an invalidated reference plus sheet separator. + p += pErrRef->getLength() + 1; + nRes &= ~ScRefFlags::TAB_VALID; + nTab = -1; } else { - OUStringBuffer aTabAcc; - while (*p) + if (*p == '\'') { - if( *p == '.') - break; + // Tokens that start at ' can have anything in them until a final + // ' but '' marks an escaped '. We've earlier guaranteed that a + // string containing '' will be surrounded by '. + p = parseQuotedName(p, aTab); + } + else + { + OUStringBuffer aTabAcc; + while (*p) + { + if( *p == '.') + break; + + if( *p == '\'' ) + { + p++; break; + } + aTabAcc.append(*p); + p++; + } + aTab = aTabAcc.makeStringAndClear(); + } + if( *p++ != '.' ) + nBits = ScRefFlags::ZERO; - if( *p == '\'' ) + if (!bExtDoc && (!pDoc || !pDoc->GetTable( aTab, nTab ))) + { + // Specified table name is not found in this document. Assume this is an external document. + aDocName = aTab; + sal_Int32 n = aDocName.lastIndexOf('.'); + if (n > 0) { - p++; break; + // Extension found. Strip it. + aTab = aTab.replaceAt(n, 1, ""); + bExtDoc = true; } - aTabAcc.append(*p); - p++; + else + // No extension found. This is probably not an external document. + nBits = ScRefFlags::ZERO; } - aTab = aTabAcc.makeStringAndClear(); } - if( *p++ != '.' ) - nBits = ScRefFlags::ZERO; if (pSheetEndPos && (nBits & ScRefFlags::TAB_VALID)) { *pSheetEndPos = p - pStart; nBailOutFlags = ScRefFlags::TAB_VALID | ScRefFlags::TAB_3D; } - - if (!bExtDoc && (!pDoc || !pDoc->GetTable( aTab, nTab ))) - { - // Specified table name is not found in this document. Assume this is an external document. - aDocName = aTab; - sal_Int32 n = aDocName.lastIndexOf('.'); - if (n > 0) - { - // Extension found. Strip it. - aTab = aTab.replaceAt(n, 1, ""); - bExtDoc = true; - } - else - // No extension found. This is probably not an external document. - nBits = ScRefFlags::ZERO; - } } else { @@ -1188,20 +1231,31 @@ static ScRefFlags lcl_ScAddress_Parse_OOo( const sal_Unicode* p, ScDocument* pDo p++; } - if (rtl::isAsciiAlpha( *p )) + if (pErrRef && lcl_isString( p, *pErrRef)) { - nCol = sal::static_int_cast<SCCOL>( rtl::toAsciiUpperCase( *p++ ) - 'A' ); - while (nCol < MAXCOL && rtl::isAsciiAlpha(*p)) - nCol = sal::static_int_cast<SCCOL>( ((nCol + 1) * 26) + rtl::toAsciiUpperCase( *p++ ) - 'A' ); + // #REF! particle of an invalidated reference. + p += pErrRef->getLength(); + nBits &= ~ScRefFlags::COL_VALID; + nCol = -1; } else - nBits = ScRefFlags::ZERO; + { + if (rtl::isAsciiAlpha( *p )) + { + nCol = sal::static_int_cast<SCCOL>( rtl::toAsciiUpperCase( *p++ ) - 'A' ); + while (nCol < MAXCOL && rtl::isAsciiAlpha(*p)) + nCol = sal::static_int_cast<SCCOL>( ((nCol + 1) * 26) + rtl::toAsciiUpperCase( *p++ ) - 'A' ); + } + else + nBits = ScRefFlags::ZERO; - if (nCol > MAXCOL || (*p && *p != '$' && !rtl::isAsciiDigit( *p ))) - nBits = ScRefFlags::ZERO; + if (nCol > MAXCOL || (*p && *p != '$' && !rtl::isAsciiDigit( *p ) && + (!pErrRef || !lcl_isString( p, *pErrRef)))) + nBits = ScRefFlags::ZERO; + if( nBits == ScRefFlags::ZERO ) + p = q; + } nRes |= nBits; - if( nBits == ScRefFlags::ZERO ) - p = q; } q = p; @@ -1213,23 +1267,40 @@ static ScRefFlags lcl_ScAddress_Parse_OOo( const sal_Unicode* p, ScDocument* pDo nBits |= ScRefFlags::ROW_ABS; p++; } - if( !rtl::isAsciiDigit( *p ) ) + + if (pErrRef && lcl_isString( p, *pErrRef)) { - nBits = ScRefFlags::ZERO; - nRow = SCROW(-1); + // #REF! particle of an invalidated reference. + p += pErrRef->getLength(); + // Clearing the ROW_VALID bit here is not possible because of the + // check at the end whether only a valid column was detected in + // which case all bits are cleared because it could be any other + // name. Instead, set to an absolute invalid row value. This will + // display a $#REF! instead of #REF! if the error value was + // relative, but live with it. + nBits |= ScRefFlags::ROW_ABS; + nRow = -1; } else { - long n = rtl_ustr_toInt32( p, 10 ) - 1; - while (rtl::isAsciiDigit( *p )) - p++; - if( n < 0 || n > MAXROW ) + if( !rtl::isAsciiDigit( *p ) ) + { nBits = ScRefFlags::ZERO; - nRow = static_cast<SCROW>(n); + nRow = SCROW(-1); + } + else + { + long n = rtl_ustr_toInt32( p, 10 ) - 1; + while (rtl::isAsciiDigit( *p )) + p++; + if( n < 0 || n > MAXROW ) + nBits = ScRefFlags::ZERO; + nRow = static_cast<SCROW>(n); + } + if( nBits == ScRefFlags::ZERO ) + p = q; } nRes |= nBits; - if( nBits == ScRefFlags::ZERO ) - p = q; } rAddr.Set( nCol, nRow, nTab ); @@ -1346,7 +1417,8 @@ static ScRefFlags lcl_ScAddress_Parse ( const sal_Unicode* p, ScDocument* pDoc, const ScAddress::Details& rDetails, ScAddress::ExternalInfo* pExtInfo = nullptr, const uno::Sequence<sheet::ExternalLinkInfo>* pExternalLinks = nullptr, - sal_Int32* pSheetEndPos = nullptr ) + sal_Int32* pSheetEndPos = nullptr, + const OUString* pErrRef = nullptr ) { if( !*p ) return ScRefFlags::ZERO; @@ -1375,7 +1447,7 @@ static ScRefFlags lcl_ScAddress_Parse ( const sal_Unicode* p, ScDocument* pDoc, case formula::FormulaGrammar::CONV_OOO: { ScRefFlags nRawRes = ScRefFlags::ZERO; - return lcl_ScAddress_Parse_OOo( p, pDoc, rAddr, nRawRes, pExtInfo, nullptr, pSheetEndPos); + return lcl_ScAddress_Parse_OOo( p, pDoc, rAddr, nRawRes, pExtInfo, nullptr, pSheetEndPos, pErrRef); } } } @@ -1432,9 +1504,10 @@ ScRefFlags ScAddress::Parse( const OUString& r, ScDocument* pDoc, const Details& rDetails, ExternalInfo* pExtInfo, const uno::Sequence<sheet::ExternalLinkInfo>* pExternalLinks, - sal_Int32* pSheetEndPos ) + sal_Int32* pSheetEndPos, + const OUString* pErrRef ) { - return lcl_ScAddress_Parse( r.getStr(), pDoc, *this, rDetails, pExtInfo, pExternalLinks, pSheetEndPos ); + return lcl_ScAddress_Parse( r.getStr(), pDoc, *this, rDetails, pExtInfo, pExternalLinks, pSheetEndPos, pErrRef); } bool ScRange::Intersects( const ScRange& rRange ) const @@ -1502,7 +1575,8 @@ void ScRange::ExtendTo( const ScRange& rRange ) static ScRefFlags lcl_ScRange_Parse_OOo( ScRange& rRange, const OUString& r, ScDocument* pDoc, - ScAddress::ExternalInfo* pExtInfo = nullptr ) + ScAddress::ExternalInfo* pExtInfo, + const OUString* pErrRef ) { ScRefFlags nRes1 = ScRefFlags::ZERO, nRes2 = ScRefFlags::ZERO; sal_Int32 nPos = ScGlobal::FindUnquoted( r, ':'); @@ -1512,14 +1586,15 @@ static ScRefFlags lcl_ScRange_Parse_OOo( ScRange& rRange, aTmp[nPos] = 0; const sal_Unicode* p = aTmp.getStr(); ScRefFlags nRawRes1 = ScRefFlags::ZERO; - nRes1 = lcl_ScAddress_Parse_OOo( p, pDoc, rRange.aStart, nRawRes1, pExtInfo, nullptr, nullptr); + nRes1 = lcl_ScAddress_Parse_OOo( p, pDoc, rRange.aStart, nRawRes1, pExtInfo, nullptr, nullptr, pErrRef); if ((nRes1 != ScRefFlags::ZERO) || ((nRawRes1 & (ScRefFlags::COL_VALID | ScRefFlags::ROW_VALID)) && (nRawRes1 & ScRefFlags::TAB_VALID))) { rRange.aEnd = rRange.aStart; // sheet must be initialized identical to first sheet ScRefFlags nRawRes2 = ScRefFlags::ZERO; - nRes2 = lcl_ScAddress_Parse_OOo( p + nPos+ 1, pDoc, rRange.aEnd, nRawRes2, pExtInfo, &rRange, nullptr); + nRes2 = lcl_ScAddress_Parse_OOo( p + nPos+ 1, pDoc, rRange.aEnd, nRawRes2, + pExtInfo, &rRange, nullptr, pErrRef); if (!((nRes1 & ScRefFlags::VALID) && (nRes2 & ScRefFlags::VALID)) && // If not fully valid addresses, check if both have a valid // column or row, and both have valid (or omitted) sheet references. @@ -1622,7 +1697,8 @@ static ScRefFlags lcl_ScRange_Parse_OOo( ScRange& rRange, ScRefFlags ScRange::Parse( const OUString& rString, ScDocument* pDoc, const ScAddress::Details& rDetails, ScAddress::ExternalInfo* pExtInfo, - const uno::Sequence<sheet::ExternalLinkInfo>* pExternalLinks ) + const uno::Sequence<sheet::ExternalLinkInfo>* pExternalLinks, + const OUString* pErrRef ) { if (rString.isEmpty()) return ScRefFlags::ZERO; @@ -1644,7 +1720,7 @@ ScRefFlags ScRange::Parse( const OUString& rString, ScDocument* pDoc, default: case formula::FormulaGrammar::CONV_OOO: { - return lcl_ScRange_Parse_OOo( *this, rString, pDoc, pExtInfo ); + return lcl_ScRange_Parse_OOo( *this, rString, pDoc, pExtInfo, pErrRef ); } } } diff --git a/sc/source/core/tool/compiler.cxx b/sc/source/core/tool/compiler.cxx index 7340de6f8a37..cc2ddfbc2dbd 100644 --- a/sc/source/core/tool/compiler.cxx +++ b/sc/source/core/tool/compiler.cxx @@ -644,7 +644,7 @@ static bool lcl_parseExternalName( if (aTmpName[nNameLen-1] == '!') { // Check against #REF!. - if (aTmpName == "#REF!") + if (aTmpName.equalsIgnoreAsciiCase("#REF!")) return false; } @@ -1920,6 +1920,28 @@ static sal_Unicode* lcl_UnicodeStrNCpy( sal_Unicode* pDst, const sal_Unicode* pS return pDst; } +// p1 MUST contain at least n characters, or terminate with NIL. +// p2 MUST pass upper case letters, if any. +// n MUST not be greater than length of p2 +static bool lcl_isUnicodeIgnoreAscii( const sal_Unicode* p1, const char* p2, size_t n ) +{ + for (size_t i=0; i<n; ++i) + { + if (!p1[i]) + return false; + if (p1[i] != p2[i]) + { + if (p1[i] < 'a' || 'z' < p1[i]) + return false; // not a lower case letter + if (p2[i] < 'A' || 'Z' < p2[i]) + return false; // not a letter to match + if (p1[i] != p2[i] + 0x20) + return false; // lower case doesn't match either + } + } + return true; +} + // NextSymbol // Parses the formula into separate symbols for further processing. @@ -2142,6 +2164,33 @@ Label_MaskStateMachine: else *pSym++ = c; } + else if (c == '#' && lcl_isUnicodeIgnoreAscii( pSrc, "REF!", 4)) + { + // Completely ugly means to catch broken + // [$]#REF!.[$]#REF![$]#REF! (one or multiple parts) + // references that were written in ODF named ranges + // (without embracing [] hence no predetected reference) + // and to OOXML and handle them as one symbol. + // Also catches these in UI, so we can process them + // further. + int i = 0; + for ( ; i<5; ++i) + { + if( pSym == &cSymbol[ MAXSTRLEN-1 ] ) + { + SetError(errStringOverflow); + eState = ssStop; + break; // for + } + else + { + *pSym++ = c; + c = *pSrc++; + } + } + if (i == 5) + c = *((--pSrc)-1); // position last/next character correctly + } else if (c == ':' && mnRangeOpPosInSymbol < 0) { // One range operator may form Sheet1.A:A, which we need to @@ -2282,8 +2331,18 @@ Label_MaskStateMachine: * as opcode symbols will be recognized and others result * in ocBad, so the result is actually conformant. */ bool bAdd = true; - if ('!' == c || '?' == c) + if ('?' == c) eState = ssStop; + else if ('!' == c) + { + // Check if this is #REF! that starts an invalid reference. + // Note we have an implicit '!' here at the end. + if (pSym - &cSymbol[0] == 4 && lcl_isUnicodeIgnoreAscii( cSymbol, "#REF", 4) && + ((GetCharTableFlags( *pSrc, c) & SC_COMPILER_C_IDENT) != 0)) + eState = ssGetIdent; + else + eState = ssStop; + } else if ('/' == c) { if (!bErrorConstantHadSlash) @@ -2811,10 +2870,27 @@ bool ScCompiler::IsString() return false; } -bool ScCompiler::IsPredetectedReference(const OUString& rName) +bool ScCompiler::IsPredetectedErrRefReference( const OUString& rName, const OUString* pErrRef ) +{ + switch (mnPredetectedReference) + { + case 1: + return IsSingleReference( rName, pErrRef); + case 2: + return IsDoubleReference( rName, pErrRef); + default: + return false; + } +} + +bool ScCompiler::IsPredetectedReference( const OUString& rName ) { // Speedup documents with lots of broken references, e.g. sheet deleted. - sal_Int32 nPos = rName.indexOf("#REF!"); + // It could also be a broken invalidated reference that contains #REF! + // (but is not equal to), which we wrote prior to ODFF and also to ODFF + // between 2013 and 2016 until 5.1.4 + const OUString aErrRef("#REF!"); // not localized in ODFF + sal_Int32 nPos = rName.indexOf( aErrRef); if (nPos != -1) { /* TODO: this may be enhanced by reusing scan information from @@ -2830,13 +2906,17 @@ bool ScCompiler::IsPredetectedReference(const OUString& rName) // so pass it on. if (rName.getLength() == 5) return IsErrorConstant( rName); - return false; // #REF!.AB42 or #REF!42 or #REF!#REF! + // #REF!.AB42 or #REF!42 or #REF!#REF! + return IsPredetectedErrRefReference( rName, &aErrRef); } sal_Unicode c = rName[nPos-1]; // before #REF! if ('$' == c) { if (nPos == 1) - return false; // $#REF!.AB42 or $#REF!42 or $#REF!#REF! + { + // $#REF!.AB42 or $#REF!42 or $#REF!#REF! + return IsPredetectedErrRefReference( rName, &aErrRef); + } c = rName[nPos-2]; // before $#REF! } sal_Unicode c2 = nPos+5 < rName.getLength() ? rName[nPos+5] : 0; // after #REF! @@ -2844,18 +2924,27 @@ bool ScCompiler::IsPredetectedReference(const OUString& rName) { case '.': if ('$' == c2 || '#' == c2 || ('0' <= c2 && c2 <= '9')) - return false; // sheet.#REF!42 or sheet.#REF!#REF! + { + // sheet.#REF!42 or sheet.#REF!#REF! + return IsPredetectedErrRefReference( rName, &aErrRef); + } break; case ':': if (mnPredetectedReference > 1 && ('.' == c2 || '$' == c2 || '#' == c2 || ('0' <= c2 && c2 <= '9'))) - return false; // :#REF!.AB42 or :#REF!42 or :#REF!#REF! + { + // :#REF!.AB42 or :#REF!42 or :#REF!#REF! + return IsPredetectedErrRefReference( rName, &aErrRef); + } break; default: if (rtl::isAsciiAlpha(c) && ((mnPredetectedReference > 1 && ':' == c2) || 0 == c2)) - return false; // AB#REF!: or AB#REF! + { + // AB#REF!: or AB#REF! + return IsPredetectedErrRefReference( rName, &aErrRef); + } } } switch (mnPredetectedReference) @@ -2868,12 +2957,12 @@ bool ScCompiler::IsPredetectedReference(const OUString& rName) return false; } -bool ScCompiler::IsDoubleReference( const OUString& rName ) +bool ScCompiler::IsDoubleReference( const OUString& rName, const OUString* pErrRef ) { ScRange aRange( aPos, aPos ); const ScAddress::Details aDetails( pConv->meConv, aPos ); ScAddress::ExternalInfo aExtInfo; - ScRefFlags nFlags = aRange.Parse( rName, pDoc, aDetails, &aExtInfo, &maExternalLinks ); + ScRefFlags nFlags = aRange.Parse( rName, pDoc, aDetails, &aExtInfo, &maExternalLinks, pErrRef ); if( nFlags & ScRefFlags::VALID ) { ScComplexRefData aRef; @@ -2908,14 +2997,15 @@ bool ScCompiler::IsDoubleReference( const OUString& rName ) return ( nFlags & ScRefFlags::VALID ) != ScRefFlags::ZERO; } -bool ScCompiler::IsSingleReference( const OUString& rName ) +bool ScCompiler::IsSingleReference( const OUString& rName, const OUString* pErrRef ) { mnCurrentSheetEndPos = 0; mnCurrentSheetTab = -1; ScAddress aAddr( aPos ); const ScAddress::Details aDetails( pConv->meConv, aPos ); ScAddress::ExternalInfo aExtInfo; - ScRefFlags nFlags = aAddr.Parse( rName, pDoc, aDetails, &aExtInfo, &maExternalLinks, &mnCurrentSheetEndPos); + ScRefFlags nFlags = aAddr.Parse( rName, pDoc, aDetails, + &aExtInfo, &maExternalLinks, &mnCurrentSheetEndPos, pErrRef); // Something must be valid in order to recognize Sheet1.blah or blah.a1 // as a (wrong) reference. if( nFlags & ( ScRefFlags::COL_VALID|ScRefFlags::ROW_VALID|ScRefFlags::TAB_VALID ) ) @@ -2972,7 +3062,7 @@ bool ScCompiler::IsSingleReference( const OUString& rName ) return ( nFlags & ScRefFlags::VALID ) != ScRefFlags::ZERO; } -bool ScCompiler::IsReference( const OUString& rName ) +bool ScCompiler::IsReference( const OUString& rName, const OUString* pErrRef ) { // Has to be called before IsValue sal_Unicode ch1 = rName[0]; @@ -3023,7 +3113,7 @@ bool ScCompiler::IsReference( const OUString& rName ) } while(false); } - if (IsSingleReference( rName)) + if (IsSingleReference( rName, pErrRef)) return true; // Though the range operator is handled explicitly, when encountering @@ -3031,7 +3121,7 @@ bool ScCompiler::IsReference( const OUString& rName ) // doesn't pass as single cell reference. if (mnRangeOpPosInSymbol > 0) // ":foo" would be nonsense { - if (IsDoubleReference( rName)) + if (IsDoubleReference( rName, pErrRef)) return true; // Now try with a symbol up to the range operator, rewind source // position. @@ -3058,7 +3148,7 @@ bool ScCompiler::IsReference( const OUString& rName ) SAL_FALLTHROUGH; case FormulaGrammar::CONV_XL_R1C1: // C2 or C[1] are valid entire column references. - if (IsDoubleReference( rName)) + if (IsDoubleReference( rName, pErrRef)) return true; break; default: @@ -3887,12 +3977,6 @@ bool ScCompiler::NextNewToken( bool bInArray ) bool bInvalidExternalNameRange; if (!IsPredetectedReference( aStr) && !IsExternalNamedRange( aStr, bInvalidExternalNameRange )) { - /* TODO: it would be nice to generate a #REF! error here, which - * would need an ocBad token with additional error value. - * FormulaErrorToken wouldn't do because we want to preserve the - * original string containing partial valid address - * information if not ODFF (in that case it was already handled). - * */ svl::SharedString aSS = pDoc->GetSharedStringPool().intern(aStr); maRawToken.SetString(aSS.getData(), aSS.getDataIgnoreCase()); maRawToken.NewOpCode( ocBad ); @@ -3986,10 +4070,13 @@ bool ScCompiler::NextNewToken( bool bInArray ) return true; } - // This can be only an error constant, if any. + // This can be either an error constant ... if (IsErrorConstant( aUpper)) return true; + // ... or some invalidated reference starting with #REF! + // which is handled after the do loop. + break; // do; create ocBad token or set error. } if (IsOpCode( aUpper, bInArray )) @@ -4066,6 +4153,13 @@ bool ScCompiler::NextNewToken( bool bInArray ) } while (mbRewind); + // Last chance: it could be a broken invalidated reference that contains + // #REF! (but is not equal to), which we also wrote to ODFF between 2013 + // and 2016 until 5.1.4 + OUString aErrRef( mxSymbols->getSymbol( ocErrRef)); + if (aUpper.indexOf( aErrRef) >= 0 && IsReference( aUpper, &aErrRef)) + return true; + if ( meExtendedErrorDetection != EXTENDED_ERROR_DETECTION_NONE ) { // set an error |