diff options
Diffstat (limited to 'sc/source/core/tool/compiler.cxx')
-rw-r--r-- | sc/source/core/tool/compiler.cxx | 5426 |
1 files changed, 5426 insertions, 0 deletions
diff --git a/sc/source/core/tool/compiler.cxx b/sc/source/core/tool/compiler.cxx new file mode 100644 index 000000000000..e4eab3fa510f --- /dev/null +++ b/sc/source/core/tool/compiler.cxx @@ -0,0 +1,5426 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/************************************************************************* + * + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * Copyright 2000, 2010 Oracle and/or its affiliates. + * + * OpenOffice.org - a multi-platform office productivity suite + * + * This file is part of OpenOffice.org. + * + * OpenOffice.org is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License version 3 + * only, as published by the Free Software Foundation. + * + * OpenOffice.org is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License version 3 for more details + * (a copy is included in the LICENSE file that accompanied this code). + * + * You should have received a copy of the GNU Lesser General Public License + * version 3 along with OpenOffice.org. If not, see + * <http://www.openoffice.org/license.html> + * for a copy of the LGPLv3 License. + * + ************************************************************************/ + +// MARKER(update_precomp.py): autogen include statement, do not remove +#include "precompiled_sc.hxx" + +// INCLUDE --------------------------------------------------------------- + +#include <sfx2/app.hxx> +#include <sfx2/objsh.hxx> +#include <basic/sbmeth.hxx> +#include <basic/sbstar.hxx> +#include <svl/zforlist.hxx> +#include <sal/macros.h> +#include <tools/rcid.h> +#include <tools/rc.hxx> +#include <tools/solar.h> +#include <unotools/charclass.hxx> +#include <com/sun/star/lang/Locale.hpp> +#include <com/sun/star/sheet/FormulaOpCodeMapEntry.hpp> +#include <com/sun/star/sheet/FormulaLanguage.hpp> +#include <com/sun/star/sheet/FormulaMapGroup.hpp> +#include <comphelper/processfactory.hxx> +#include <unotools/transliterationwrapper.hxx> +#include <tools/urlobj.hxx> +#include <rtl/math.hxx> +#include <ctype.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <math.h> +#include "compiler.hxx" +#include "rangenam.hxx" +#include "dbcolect.hxx" +#include "document.hxx" +#include "callform.hxx" +#include "addincol.hxx" +#include "refupdat.hxx" +#include "scresid.hxx" +#include "sc.hrc" +#include "globstr.hrc" +#include "cell.hxx" +#include "dociter.hxx" +#include "docoptio.hxx" +#include <formula/errorcodes.hxx> +#include "parclass.hxx" +#include "autonamecache.hxx" +#include "externalrefmgr.hxx" +#include "rangeutl.hxx" +#include "convuno.hxx" +#include "tokenuno.hxx" +#include "formulaparserpool.hxx" + +using namespace formula; +using namespace ::com::sun::star; +using rtl::OUString; +using ::std::vector; + +#if OSL_DEBUG_LEVEL > 1 +// For some unknown reason the identical dbg_dump utilities in +// tools/source/string/debugprint.cxx tend to crash when called from within +// gdb. Having them here also comes handy as libtl*.so doesn't have to be +// replaced. +const char* dbg_sc_dump( const ByteString & rStr ) +{ + static ByteString aStr; + aStr = rStr; + aStr.Append(static_cast<char>(0)); + return aStr.GetBuffer(); +} +const char* dbg_sc_dump( const UniString & rStr ) +{ + return dbg_sc_dump(ByteString(rStr, RTL_TEXTENCODING_UTF8)); +} +const char* dbg_sc_dump( const sal_Unicode * pBuf ) +{ + return dbg_sc_dump( UniString( pBuf)); +} +const char* dbg_sc_dump( const sal_Unicode c ) +{ + return dbg_sc_dump( UniString( c)); +} +#endif + +CharClass* ScCompiler::pCharClassEnglish = NULL; +const ScCompiler::Convention* ScCompiler::pConventions[ ] = { NULL, NULL, NULL, NULL, NULL, NULL }; + +enum ScanState +{ + ssGetChar, + ssGetBool, + ssGetValue, + ssGetString, + ssSkipString, + ssGetIdent, + ssGetReference, + ssSkipReference, + ssStop +}; + +static const sal_Char* pInternal[ 1 ] = { "TTT" }; + +using namespace ::com::sun::star::i18n; + +///////////////////////////////////////////////////////////////////////// + + + +class ScCompilerRecursionGuard +{ +private: + short& rRecursion; +public: + ScCompilerRecursionGuard( short& rRec ) + : rRecursion( rRec ) { ++rRecursion; } + ~ScCompilerRecursionGuard() { --rRecursion; } +}; + + +void ScCompiler::fillFromAddInMap( NonConstOpCodeMapPtr xMap,FormulaGrammar::Grammar _eGrammar ) const +{ + size_t nSymbolOffset; + switch( _eGrammar ) + { + case FormulaGrammar::GRAM_PODF: + nSymbolOffset = offsetof( AddInMap, pUpper); + break; + default: + case FormulaGrammar::GRAM_ODFF: + nSymbolOffset = offsetof( AddInMap, pODFF); + break; + case FormulaGrammar::GRAM_ENGLISH: + nSymbolOffset = offsetof( AddInMap, pEnglish); + break; + } + const AddInMap* pMap = GetAddInMap(); + const AddInMap* const pStop = pMap + GetAddInMapCount(); + for ( ; pMap < pStop; ++pMap) + { + char const * const * ppSymbol = + reinterpret_cast< char const * const * >( + reinterpret_cast< char const * >(pMap) + nSymbolOffset); + xMap->putExternal( String::CreateFromAscii( *ppSymbol), + String::CreateFromAscii( pMap->pOriginal)); + } +} + +void ScCompiler::fillFromAddInCollectionUpperName( NonConstOpCodeMapPtr xMap ) const +{ + ScUnoAddInCollection* pColl = ScGlobal::GetAddInCollection(); + long nCount = pColl->GetFuncCount(); + for (long i=0; i < nCount; ++i) + { + const ScUnoAddInFuncData* pFuncData = pColl->GetFuncData(i); + if (pFuncData) + xMap->putExternalSoftly( pFuncData->GetUpperName(), + pFuncData->GetOriginalName()); + } +} + +void ScCompiler::fillFromAddInCollectionEnglishName( NonConstOpCodeMapPtr xMap ) const +{ + ScUnoAddInCollection* pColl = ScGlobal::GetAddInCollection(); + long nCount = pColl->GetFuncCount(); + for (long i=0; i < nCount; ++i) + { + const ScUnoAddInFuncData* pFuncData = pColl->GetFuncData(i); + if (pFuncData) + { + String aName; + if (pFuncData->GetExcelName( LANGUAGE_ENGLISH_US, aName)) + xMap->putExternalSoftly( aName, pFuncData->GetOriginalName()); + else + xMap->putExternalSoftly( pFuncData->GetUpperName(), + pFuncData->GetOriginalName()); + } + } +} + +void ScCompiler::DeInit() +{ + if (pCharClassEnglish) + { + delete pCharClassEnglish; + pCharClassEnglish = NULL; + } +} + +bool ScCompiler::IsEnglishSymbol( const String& rName ) +{ + // function names are always case-insensitive + String aUpper( ScGlobal::pCharClass->upper( rName ) ); + + // 1. built-in function name + OpCode eOp = ScCompiler::GetEnglishOpCode( aUpper ); + if ( eOp != ocNone ) + { + return true; + } + // 2. old add in functions + sal_uInt16 nIndex; + if ( ScGlobal::GetFuncCollection()->SearchFunc( aUpper, nIndex ) ) + { + return true; + } + + // 3. new (uno) add in functions + String aIntName(ScGlobal::GetAddInCollection()->FindFunction( aUpper, false )); + if (aIntName.Len()) + { + return true; + } + return false; // no valid function name +} + +void ScCompiler::InitCharClassEnglish() +{ + ::com::sun::star::lang::Locale aLocale( + OUString( RTL_CONSTASCII_USTRINGPARAM( "en")), + OUString( RTL_CONSTASCII_USTRINGPARAM( "US")), + OUString()); + pCharClassEnglish = new CharClass( + ::comphelper::getProcessServiceFactory(), aLocale); +} + + +void ScCompiler::SetGrammar( const FormulaGrammar::Grammar eGrammar ) +{ + DBG_ASSERT( eGrammar != FormulaGrammar::GRAM_UNSPECIFIED, "ScCompiler::SetGrammar: don't pass FormulaGrammar::GRAM_UNSPECIFIED"); + if (eGrammar == GetGrammar()) + return; // nothing to be done + + if( eGrammar == FormulaGrammar::GRAM_EXTERNAL ) + { + meGrammar = eGrammar; + mxSymbols = GetOpCodeMap( ::com::sun::star::sheet::FormulaLanguage::NATIVE); + } + else + { + FormulaGrammar::Grammar eMyGrammar = eGrammar; + const sal_Int32 nFormulaLanguage = FormulaGrammar::extractFormulaLanguage( eMyGrammar); + OpCodeMapPtr xMap = GetOpCodeMap( nFormulaLanguage); + DBG_ASSERT( xMap, "ScCompiler::SetGrammar: unknown formula language"); + if (!xMap) + { + xMap = GetOpCodeMap( ::com::sun::star::sheet::FormulaLanguage::NATIVE); + eMyGrammar = xMap->getGrammar(); + } + + // Save old grammar for call to SetGrammarAndRefConvention(). + FormulaGrammar::Grammar eOldGrammar = GetGrammar(); + // This also sets the grammar associated with the map! + SetFormulaLanguage( xMap); + + // Override if necessary. + if (eMyGrammar != GetGrammar()) + SetGrammarAndRefConvention( eMyGrammar, eOldGrammar); + } +} + +ScCompiler::EncodeUrlMode ScCompiler::GetEncodeUrlMode() const +{ + return meEncodeUrlMode; +} + +void ScCompiler::SetFormulaLanguage( const ScCompiler::OpCodeMapPtr & xMap ) +{ + if (xMap.get()) + { + mxSymbols = xMap; + if (mxSymbols->isEnglish()) + { + if (!pCharClassEnglish) + InitCharClassEnglish(); + pCharClass = pCharClassEnglish; + } + else + pCharClass = ScGlobal::pCharClass; + SetGrammarAndRefConvention( mxSymbols->getGrammar(), GetGrammar()); + } +} + + +void ScCompiler::SetGrammarAndRefConvention( + const FormulaGrammar::Grammar eNewGrammar, const FormulaGrammar::Grammar eOldGrammar ) +{ + meGrammar = eNewGrammar; //! SetRefConvention needs the new grammar set! + FormulaGrammar::AddressConvention eConv = FormulaGrammar::extractRefConvention( meGrammar); + if (eConv == FormulaGrammar::CONV_UNSPECIFIED && eOldGrammar == FormulaGrammar::GRAM_UNSPECIFIED) + { + if (pDoc) + SetRefConvention( pDoc->GetAddressConvention()); + else + SetRefConvention( pConvOOO_A1); + } + else + SetRefConvention( eConv ); +} + +String ScCompiler::FindAddInFunction( const String& rUpperName, sal_Bool bLocalFirst ) const +{ + return ScGlobal::GetAddInCollection()->FindFunction(rUpperName, bLocalFirst); // bLocalFirst=sal_False for english +} + + +//----------------------------------------------------------------------------- + +ScCompiler::Convention::~Convention() +{ + delete [] mpCharTable; + mpCharTable = NULL; +} + +ScCompiler::Convention::Convention( FormulaGrammar::AddressConvention eConv ) + : + meConv( eConv ) +{ + int i; + sal_uLong *t= new sal_uLong [128]; + + ScCompiler::pConventions[ meConv ] = this; + mpCharTable = t; + + for (i = 0; i < 128; i++) + t[i] = SC_COMPILER_C_ILLEGAL; + +/* */ t[32] = SC_COMPILER_C_CHAR_DONTCARE | SC_COMPILER_C_WORD_SEP | SC_COMPILER_C_VALUE_SEP; +/* ! */ t[33] = SC_COMPILER_C_CHAR | SC_COMPILER_C_WORD_SEP | SC_COMPILER_C_VALUE_SEP; + if (FormulaGrammar::CONV_ODF == meConv) +/* ! */ t[33] |= SC_COMPILER_C_ODF_LABEL_OP; +/* " */ t[34] = SC_COMPILER_C_CHAR_STRING | SC_COMPILER_C_STRING_SEP; +/* # */ t[35] = SC_COMPILER_C_WORD_SEP; +/* $ */ t[36] = SC_COMPILER_C_CHAR_WORD | SC_COMPILER_C_WORD | SC_COMPILER_C_CHAR_IDENT | SC_COMPILER_C_IDENT; + if (FormulaGrammar::CONV_ODF == meConv) +/* $ */ t[36] |= SC_COMPILER_C_ODF_NAME_MARKER; +/* % */ t[37] = SC_COMPILER_C_VALUE; +/* & */ t[38] = SC_COMPILER_C_CHAR | SC_COMPILER_C_WORD_SEP | SC_COMPILER_C_VALUE_SEP; +/* ' */ t[39] = SC_COMPILER_C_NAME_SEP; +/* ( */ t[40] = SC_COMPILER_C_CHAR | SC_COMPILER_C_WORD_SEP | SC_COMPILER_C_VALUE_SEP; +/* ) */ t[41] = SC_COMPILER_C_CHAR | SC_COMPILER_C_WORD_SEP | SC_COMPILER_C_VALUE_SEP; +/* * */ t[42] = SC_COMPILER_C_CHAR | SC_COMPILER_C_WORD_SEP | SC_COMPILER_C_VALUE_SEP; +/* + */ t[43] = SC_COMPILER_C_CHAR | SC_COMPILER_C_WORD_SEP | SC_COMPILER_C_VALUE_EXP | SC_COMPILER_C_VALUE_SIGN; +/* , */ t[44] = SC_COMPILER_C_CHAR_VALUE | SC_COMPILER_C_VALUE; +/* - */ t[45] = SC_COMPILER_C_CHAR | SC_COMPILER_C_WORD_SEP | SC_COMPILER_C_VALUE_EXP | SC_COMPILER_C_VALUE_SIGN; +/* . */ t[46] = SC_COMPILER_C_WORD | SC_COMPILER_C_CHAR_VALUE | SC_COMPILER_C_VALUE | SC_COMPILER_C_IDENT | SC_COMPILER_C_NAME; +/* / */ t[47] = SC_COMPILER_C_CHAR | SC_COMPILER_C_WORD_SEP | SC_COMPILER_C_VALUE_SEP; + + for (i = 48; i < 58; i++) +/* 0-9 */ t[i] = SC_COMPILER_C_CHAR_VALUE | SC_COMPILER_C_WORD | SC_COMPILER_C_VALUE | SC_COMPILER_C_VALUE_EXP | SC_COMPILER_C_VALUE_VALUE | SC_COMPILER_C_IDENT | SC_COMPILER_C_NAME; + +/* : */ t[58] = SC_COMPILER_C_CHAR | SC_COMPILER_C_WORD; +/* ; */ t[59] = SC_COMPILER_C_CHAR | SC_COMPILER_C_WORD_SEP | SC_COMPILER_C_VALUE_SEP; +/* < */ t[60] = SC_COMPILER_C_CHAR_BOOL | SC_COMPILER_C_WORD_SEP | SC_COMPILER_C_VALUE_SEP; +/* = */ t[61] = SC_COMPILER_C_CHAR | SC_COMPILER_C_BOOL | SC_COMPILER_C_WORD_SEP | SC_COMPILER_C_VALUE_SEP; +/* > */ t[62] = SC_COMPILER_C_CHAR_BOOL | SC_COMPILER_C_BOOL | SC_COMPILER_C_WORD_SEP | SC_COMPILER_C_VALUE_SEP; +/* ? */ t[63] = SC_COMPILER_C_CHAR_WORD | SC_COMPILER_C_WORD | SC_COMPILER_C_NAME; +/* @ */ // FREE + + for (i = 65; i < 91; i++) +/* A-Z */ t[i] = SC_COMPILER_C_CHAR_WORD | SC_COMPILER_C_WORD | SC_COMPILER_C_CHAR_IDENT | SC_COMPILER_C_IDENT | SC_COMPILER_C_CHAR_NAME | SC_COMPILER_C_NAME; + + if (FormulaGrammar::CONV_ODF == meConv) + { +/* [ */ t[91] = SC_COMPILER_C_ODF_LBRACKET; +/* \ */ // FREE +/* ] */ t[93] = SC_COMPILER_C_ODF_RBRACKET; + } + else + { +/* [ */ // FREE +/* \ */ // FREE +/* ] */ // FREE + } +/* ^ */ t[94] = SC_COMPILER_C_CHAR | SC_COMPILER_C_WORD_SEP | SC_COMPILER_C_VALUE_SEP; +/* _ */ t[95] = SC_COMPILER_C_CHAR_WORD | SC_COMPILER_C_WORD | SC_COMPILER_C_CHAR_IDENT | SC_COMPILER_C_IDENT | SC_COMPILER_C_CHAR_NAME | SC_COMPILER_C_NAME; +/* ` */ // FREE + + for (i = 97; i < 123; i++) +/* a-z */ t[i] = SC_COMPILER_C_CHAR_WORD | SC_COMPILER_C_WORD | SC_COMPILER_C_CHAR_IDENT | SC_COMPILER_C_IDENT | SC_COMPILER_C_CHAR_NAME | SC_COMPILER_C_NAME; + +/* { */ t[123] = SC_COMPILER_C_CHAR | SC_COMPILER_C_WORD_SEP | SC_COMPILER_C_VALUE_SEP; // array open +/* | */ t[124] = SC_COMPILER_C_CHAR | SC_COMPILER_C_WORD_SEP | SC_COMPILER_C_VALUE_SEP; // array row sep (Should be OOo specific) +/* } */ t[125] = SC_COMPILER_C_CHAR | SC_COMPILER_C_WORD_SEP | SC_COMPILER_C_VALUE_SEP; // array close +/* ~ */ t[126] = SC_COMPILER_C_CHAR; // OOo specific +/* 127 */ // FREE + + if( FormulaGrammar::CONV_XL_A1 == meConv || FormulaGrammar::CONV_XL_R1C1 == meConv || FormulaGrammar::CONV_XL_OOX == meConv ) + { +/* */ t[32] |= SC_COMPILER_C_WORD; +/* ! */ t[33] |= SC_COMPILER_C_IDENT | SC_COMPILER_C_WORD; +/* " */ t[34] |= SC_COMPILER_C_WORD; +/* # */ t[35] &= (~SC_COMPILER_C_WORD_SEP); +/* # */ t[35] |= SC_COMPILER_C_WORD; +/* % */ t[37] |= SC_COMPILER_C_WORD; +/* ' */ t[39] |= SC_COMPILER_C_WORD; + +/* % */ t[37] |= SC_COMPILER_C_WORD; +/* & */ t[38] |= SC_COMPILER_C_WORD; +/* ' */ t[39] |= SC_COMPILER_C_WORD; +/* ( */ t[40] |= SC_COMPILER_C_WORD; +/* ) */ t[41] |= SC_COMPILER_C_WORD; +/* * */ t[42] |= SC_COMPILER_C_WORD; +/* + */ t[43] |= SC_COMPILER_C_WORD; +#if 0 /* this really needs to be locale specific. */ +/* , */ t[44] = SC_COMPILER_C_CHAR | SC_COMPILER_C_WORD_SEP | SC_COMPILER_C_VALUE_SEP; +#else +/* , */ t[44] |= SC_COMPILER_C_WORD; +#endif +/* - */ t[45] |= SC_COMPILER_C_WORD; + +/* ; */ t[59] |= SC_COMPILER_C_WORD; +/* < */ t[60] |= SC_COMPILER_C_WORD; +/* = */ t[61] |= SC_COMPILER_C_WORD; +/* > */ t[62] |= SC_COMPILER_C_WORD; +/* ? */ // question really is not permitted in sheet name +/* @ */ t[64] |= SC_COMPILER_C_WORD; +/* [ */ t[91] |= SC_COMPILER_C_WORD; +/* ] */ t[93] |= SC_COMPILER_C_WORD; +/* { */ t[123]|= SC_COMPILER_C_WORD; +/* | */ t[124]|= SC_COMPILER_C_WORD; +/* } */ t[125]|= SC_COMPILER_C_WORD; +/* ~ */ t[126]|= SC_COMPILER_C_WORD; + + if( FormulaGrammar::CONV_XL_R1C1 == meConv ) + { +/* [ */ t[91] |= SC_COMPILER_C_IDENT; +/* ] */ t[93] |= SC_COMPILER_C_IDENT; + } + if( FormulaGrammar::CONV_XL_OOX == meConv ) + { +/* [ */ t[91] |= SC_COMPILER_C_CHAR_IDENT; +/* ] */ t[93] |= SC_COMPILER_C_IDENT; + } + } +} + +//----------------------------------------------------------------------------- + +static bool lcl_isValidQuotedText( const String& rFormula, xub_StrLen nSrcPos, ParseResult& rRes ) +{ + // 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 ' + if (rFormula.GetChar(nSrcPos) == '\'') + { + xub_StrLen nPos = nSrcPos+1; + while (nPos < rFormula.Len()) + { + if (rFormula.GetChar(nPos) == '\'') + { + if ( (nPos+1 == rFormula.Len()) || (rFormula.GetChar(nPos+1) != '\'') ) + { + rRes.TokenType = KParseType::SINGLE_QUOTE_NAME; + rRes.EndPos = nPos+1; + return true; + } + ++nPos; + } + ++nPos; + } + } + + return false; +} + +static bool lcl_parseExternalName( + const String& rSymbol, + String& rFile, + String& rName, + const sal_Unicode cSep, + const ScDocument* pDoc = NULL, + const uno::Sequence< const sheet::ExternalLinkInfo > * pExternalLinks = NULL ) +{ + /* TODO: future versions will have to support sheet-local names too, thus + * return a possible sheet name as well. */ + const sal_Unicode* const pStart = rSymbol.GetBuffer(); + const sal_Unicode* p = pStart; + xub_StrLen nLen = rSymbol.Len(); + sal_Unicode cPrev = 0; + String aTmpFile, aTmpName; + xub_StrLen i = 0; + bool bInName = false; + if (cSep == '!') + { + // For XL use existing parser that resolves bracketed and quoted and + // indexed external document names. + ScRange aRange; + String aStartTabName, aEndTabName; + sal_uInt16 nFlags = 0; + p = aRange.Parse_XL_Header( p, pDoc, aTmpFile, aStartTabName, + aEndTabName, nFlags, true, pExternalLinks ); + if (!p || p == pStart) + return false; + i = xub_StrLen(p - pStart); + cPrev = *(p-1); + } + for ( ; i < nLen; ++i, ++p) + { + sal_Unicode c = *p; + if (i == 0) + { + if (c == '.' || c == cSep) + return false; + + if (c == '\'') + { + // Move to the next char and loop until the second single + // quote. + cPrev = c; + ++i; ++p; + for (xub_StrLen j = i; j < nLen; ++j, ++p) + { + c = *p; + if (c == '\'') + { + if (j == i) + { + // empty quote e.g. (=''!Name) + return false; + } + + if (cPrev == '\'') + { + // two consecutive quotes equal a single quote in + // the file name. + aTmpFile.Append(c); + cPrev = 'a'; + } + else + cPrev = c; + + continue; + } + + if (cPrev == '\'' && j != i) + { + // this is not a quote but the previous one is. This + // ends the parsing of the quoted segment. At this + // point, the current char must equal the separator + // char. + + i = j; + bInName = true; + aTmpName.Append(c); // Keep the separator as part of the name. + break; + } + aTmpFile.Append(c); + cPrev = c; + } + + if (!bInName) + { + // premature ending of the quoted segment. + return false; + } + + if (c != cSep) + { + // only the separator is allowed after the closing quote. + return false; + } + + cPrev = c; + continue; + } + } + + if (bInName) + { + if (c == cSep) + { + // A second separator ? Not a valid external name. + return false; + } + aTmpName.Append(c); + } + else + { + if (c == cSep) + { + bInName = true; + aTmpName.Append(c); // Keep the separator as part of the name. + } + else + { + do + { + if (CharClass::isAsciiAlphaNumeric(c)) + // allowed. + break; + + if (c > 128) + // non-ASCII character is allowed. + break; + + bool bValid = false; + switch (c) + { + case '_': + case '-': + case '.': + // these special characters are allowed. + bValid = true; + break; + } + if (bValid) + break; + + return false; + } + while (false); + aTmpFile.Append(c); + } + } + cPrev = c; + } + + if (!bInName) + { + // No name found - most likely the symbol has no '!'s. + return false; + } + + xub_StrLen nNameLen = aTmpName.Len(); + if (nNameLen < 2) + { + // Name must be at least 2-char long (separator plus name). + return false; + } + + if (aTmpName.GetChar(0) != cSep) + { + // 1st char of the name must equal the separator. + return false; + } + + sal_Unicode cLast = aTmpName.GetChar(nNameLen-1); + if (cLast == sal_Unicode('!')) + { + // Check against #REF!. + if (aTmpName.EqualsAscii("#REF!")) + return false; + } + + rFile = aTmpFile; + rName = aTmpName.Copy(1); // Skip the first char as it is always the separator. + return true; +} + +static String lcl_makeExternalNameStr( const String& rFile, const String& rName, + const sal_Unicode cSep, bool bODF ) +{ + String aFile( rFile), aName( rName), aEscQuote( RTL_CONSTASCII_USTRINGPARAM("''")); + aFile.SearchAndReplaceAllAscii( "'", aEscQuote); + if (bODF) + aName.SearchAndReplaceAllAscii( "'", aEscQuote); + rtl::OUStringBuffer aBuf( aFile.Len() + aName.Len() + 9); + if (bODF) + aBuf.append( sal_Unicode( '[')); + aBuf.append( sal_Unicode( '\'')); + aBuf.append( aFile); + aBuf.append( sal_Unicode( '\'')); + aBuf.append( cSep); + if (bODF) + aBuf.appendAscii( RTL_CONSTASCII_STRINGPARAM( "$$'")); + aBuf.append( aName); + if (bODF) + aBuf.appendAscii( RTL_CONSTASCII_STRINGPARAM( "']")); + return String( aBuf.makeStringAndClear()); +} + +static bool lcl_getLastTabName( OUString& rTabName2, const OUString& rTabName1, + const vector<OUString>& rTabNames, const ScComplexRefData& rRef ) +{ + SCsTAB nTabSpan = rRef.Ref2.nTab - rRef.Ref1.nTab; + if (nTabSpan > 0) + { + size_t nCount = rTabNames.size(); + vector<OUString>::const_iterator itrBeg = rTabNames.begin(), itrEnd = rTabNames.end(); + vector<OUString>::const_iterator itr = ::std::find(itrBeg, itrEnd, rTabName1); + if (itr == rTabNames.end()) + { + rTabName2 = ScGlobal::GetRscString(STR_NO_REF_TABLE); + return false; + } + + size_t nDist = ::std::distance(itrBeg, itr); + if (nDist + static_cast<size_t>(nTabSpan) >= nCount) + { + rTabName2 = ScGlobal::GetRscString(STR_NO_REF_TABLE); + return false; + } + + rTabName2 = rTabNames[nDist+nTabSpan]; + } + else + rTabName2 = rTabName1; + + return true; +} + +struct Convention_A1 : public ScCompiler::Convention +{ + Convention_A1( FormulaGrammar::AddressConvention eConv ) : ScCompiler::Convention( eConv ) { } + static void MakeColStr( rtl::OUStringBuffer& rBuffer, SCCOL nCol ); + static void MakeRowStr( rtl::OUStringBuffer& rBuffer, SCROW nRow ); + + ParseResult parseAnyToken( const String& rFormula, + xub_StrLen nSrcPos, + const CharClass* pCharClass) const + { + ParseResult aRet; + if ( lcl_isValidQuotedText(rFormula, nSrcPos, aRet) ) + return aRet; + + static const sal_Int32 nStartFlags = KParseTokens::ANY_LETTER_OR_NUMBER | + KParseTokens::ASC_UNDERSCORE | KParseTokens::ASC_DOLLAR; + static const sal_Int32 nContFlags = nStartFlags | KParseTokens::ASC_DOT; + // '?' allowed in range names because of Xcl :-/ + static const String aAddAllowed(String::CreateFromAscii("?#")); + return pCharClass->parseAnyToken( rFormula, + nSrcPos, nStartFlags, aAddAllowed, nContFlags, aAddAllowed ); + } + + virtual sal_uLong getCharTableFlags( sal_Unicode c, sal_Unicode /*cLast*/ ) const + { + return mpCharTable[static_cast<sal_uInt8>(c)]; + } +}; + +void Convention_A1::MakeColStr( rtl::OUStringBuffer& rBuffer, SCCOL nCol ) +{ + if ( !ValidCol( nCol) ) + rBuffer.append(ScGlobal::GetRscString(STR_NO_REF_TABLE)); + else + ::ScColToAlpha( rBuffer, nCol); +} + +void Convention_A1::MakeRowStr( rtl::OUStringBuffer& rBuffer, SCROW nRow ) +{ + if ( !ValidRow(nRow) ) + rBuffer.append(ScGlobal::GetRscString(STR_NO_REF_TABLE)); + else + rBuffer.append(sal_Int32(nRow + 1)); +} + +//----------------------------------------------------------------------------- + +struct ConventionOOO_A1 : public Convention_A1 +{ + ConventionOOO_A1() : Convention_A1 (FormulaGrammar::CONV_OOO) { } + ConventionOOO_A1( FormulaGrammar::AddressConvention eConv ) : Convention_A1 (eConv) { } + static String MakeTabStr( const ScCompiler& rComp, SCTAB nTab, String& aDoc ) + { + String aString; + if (!rComp.GetDoc()->GetName( nTab, aString )) + aString = ScGlobal::GetRscString(STR_NO_REF_TABLE); + else + { + if ( aString.GetChar(0) == '\'' ) + { // "'Doc'#Tab" + xub_StrLen nPos = ScGlobal::FindUnquoted( aString, SC_COMPILER_FILE_TAB_SEP); + if (nPos != STRING_NOTFOUND && nPos > 0 && aString.GetChar(nPos-1) == '\'') + { + aDoc = aString.Copy( 0, nPos + 1 ); + aString.Erase( 0, nPos + 1 ); + aDoc = INetURLObject::decode( aDoc, INET_HEX_ESCAPE, + INetURLObject::DECODE_UNAMBIGUOUS ); + } + else + aDoc.Erase(); + } + else + aDoc.Erase(); + ScCompiler::CheckTabQuotes( aString, FormulaGrammar::CONV_OOO ); + } + aString += '.'; + return aString; + } + + void MakeRefStrImpl( rtl::OUStringBuffer& rBuffer, + const ScCompiler& rComp, + const ScComplexRefData& rRef, + bool bSingleRef, + bool bODF ) const + { + if (bODF) + rBuffer.append(sal_Unicode('[')); + ScComplexRefData aRef( rRef ); + // In case absolute/relative positions weren't separately available: + // transform relative to absolute! + aRef.Ref1.CalcAbsIfRel( rComp.GetPos() ); + if( !bSingleRef ) + aRef.Ref2.CalcAbsIfRel( rComp.GetPos() ); + if( aRef.Ref1.IsFlag3D() ) + { + if (aRef.Ref1.IsTabDeleted()) + { + if (!aRef.Ref1.IsTabRel()) + rBuffer.append(sal_Unicode('$')); + rBuffer.append(ScGlobal::GetRscString(STR_NO_REF_TABLE)); + rBuffer.append(sal_Unicode('.')); + } + else + { + String aDoc; + String aRefStr( MakeTabStr( rComp, aRef.Ref1.nTab, aDoc ) ); + rBuffer.append(aDoc); + if (!aRef.Ref1.IsTabRel()) rBuffer.append(sal_Unicode('$')); + rBuffer.append(aRefStr); + } + } + else if (bODF) + rBuffer.append(sal_Unicode('.')); + if (!aRef.Ref1.IsColRel()) + rBuffer.append(sal_Unicode('$')); + if ( aRef.Ref1.IsColDeleted() ) + rBuffer.append(ScGlobal::GetRscString(STR_NO_REF_TABLE)); + else + MakeColStr(rBuffer, aRef.Ref1.nCol ); + if (!aRef.Ref1.IsRowRel()) + rBuffer.append(sal_Unicode('$')); + if ( aRef.Ref1.IsRowDeleted() ) + rBuffer.append(ScGlobal::GetRscString(STR_NO_REF_TABLE)); + else + MakeRowStr( rBuffer, aRef.Ref1.nRow ); + if (!bSingleRef) + { + rBuffer.append(sal_Unicode(':')); + if (aRef.Ref2.IsFlag3D() || aRef.Ref2.nTab != aRef.Ref1.nTab) + { + if (aRef.Ref2.IsTabDeleted()) + { + if (!aRef.Ref2.IsTabRel()) + rBuffer.append(sal_Unicode('$')); + rBuffer.append(ScGlobal::GetRscString(STR_NO_REF_TABLE)); + rBuffer.append(sal_Unicode('.')); + } + else + { + String aDoc; + String aRefStr( MakeTabStr( rComp, aRef.Ref2.nTab, aDoc ) ); + rBuffer.append(aDoc); + if (!aRef.Ref2.IsTabRel()) rBuffer.append(sal_Unicode('$')); + rBuffer.append(aRefStr); + } + } + else if (bODF) + rBuffer.append(sal_Unicode('.')); + if (!aRef.Ref2.IsColRel()) + rBuffer.append(sal_Unicode('$')); + if ( aRef.Ref2.IsColDeleted() ) + rBuffer.append(ScGlobal::GetRscString(STR_NO_REF_TABLE)); + else + MakeColStr( rBuffer, aRef.Ref2.nCol ); + if (!aRef.Ref2.IsRowRel()) + rBuffer.append(sal_Unicode('$')); + if ( aRef.Ref2.IsRowDeleted() ) + rBuffer.append(ScGlobal::GetRscString(STR_NO_REF_TABLE)); + else + MakeRowStr( rBuffer, aRef.Ref2.nRow ); + } + if (bODF) + rBuffer.append(sal_Unicode(']')); + } + + void MakeRefStr( rtl::OUStringBuffer& rBuffer, + const ScCompiler& rComp, + const ScComplexRefData& rRef, + sal_Bool bSingleRef ) const + { + MakeRefStrImpl( rBuffer, rComp, rRef, bSingleRef, false); + } + + virtual sal_Unicode getSpecialSymbol( SpecialSymbolType eSymType ) const + { + switch (eSymType) + { + case ScCompiler::Convention::ABS_SHEET_PREFIX: + return '$'; + case ScCompiler::Convention::SHEET_SEPARATOR: + return '.'; + } + + return sal_Unicode(0); + } + + virtual bool parseExternalName( const String& rSymbol, String& rFile, String& rName, + const ScDocument* pDoc, + const ::com::sun::star::uno::Sequence< + const ::com::sun::star::sheet::ExternalLinkInfo > * pExternalLinks ) const + { + return lcl_parseExternalName(rSymbol, rFile, rName, sal_Unicode('#'), pDoc, pExternalLinks); + } + + virtual String makeExternalNameStr( const String& rFile, const String& rName ) const + { + return lcl_makeExternalNameStr( rFile, rName, sal_Unicode('#'), false); + } + + bool makeExternalSingleRefStr( ::rtl::OUStringBuffer& rBuffer, sal_uInt16 nFileId, + const String& rTabName, const ScSingleRefData& rRef, + ScExternalRefManager* pRefMgr, bool bDisplayTabName, bool bEncodeUrl ) const + { + if (bDisplayTabName) + { + String aFile; + const String* p = pRefMgr->getExternalFileName(nFileId); + if (p) + { + if (bEncodeUrl) + aFile = *p; + else + aFile = INetURLObject::decode(*p, INET_HEX_ESCAPE, INetURLObject::DECODE_UNAMBIGUOUS); + } + aFile.SearchAndReplaceAllAscii("'", String::CreateFromAscii("''")); + + rBuffer.append(sal_Unicode('\'')); + rBuffer.append(aFile); + rBuffer.append(sal_Unicode('\'')); + rBuffer.append(sal_Unicode('#')); + + if (!rRef.IsTabRel()) + rBuffer.append(sal_Unicode('$')); + ScRangeStringConverter::AppendTableName(rBuffer, rTabName); + + rBuffer.append(sal_Unicode('.')); + } + + if (!rRef.IsColRel()) + rBuffer.append(sal_Unicode('$')); + MakeColStr( rBuffer, rRef.nCol); + if (!rRef.IsRowRel()) + rBuffer.append(sal_Unicode('$')); + MakeRowStr( rBuffer, rRef.nRow); + + return true; + } + + void makeExternalRefStrImpl( ::rtl::OUStringBuffer& rBuffer, const ScCompiler& rCompiler, + sal_uInt16 nFileId, const String& rTabName, const ScSingleRefData& rRef, + ScExternalRefManager* pRefMgr, bool bODF ) const + { + ScSingleRefData aRef(rRef); + aRef.CalcAbsIfRel(rCompiler.GetPos()); + + if (bODF) + rBuffer.append( sal_Unicode('[')); + + bool bEncodeUrl = true; + switch (rCompiler.GetEncodeUrlMode()) + { + case ScCompiler::ENCODE_BY_GRAMMAR: + bEncodeUrl = bODF; + break; + case ScCompiler::ENCODE_ALWAYS: + bEncodeUrl = true; + break; + case ScCompiler::ENCODE_NEVER: + bEncodeUrl = false; + break; + default: + ; + } + makeExternalSingleRefStr(rBuffer, nFileId, rTabName, aRef, pRefMgr, true, bEncodeUrl); + if (bODF) + rBuffer.append( sal_Unicode(']')); + } + + virtual void makeExternalRefStr( ::rtl::OUStringBuffer& rBuffer, const ScCompiler& rCompiler, + sal_uInt16 nFileId, const String& rTabName, const ScSingleRefData& rRef, + ScExternalRefManager* pRefMgr ) const + { + makeExternalRefStrImpl( rBuffer, rCompiler, nFileId, rTabName, rRef, pRefMgr, false); + } + + void makeExternalRefStrImpl( ::rtl::OUStringBuffer& rBuffer, const ScCompiler& rCompiler, + sal_uInt16 nFileId, const String& rTabName, const ScComplexRefData& rRef, + ScExternalRefManager* pRefMgr, bool bODF ) const + { + ScComplexRefData aRef(rRef); + aRef.CalcAbsIfRel(rCompiler.GetPos()); + + if (bODF) + rBuffer.append( sal_Unicode('[')); + // Ensure that there's always a closing bracket, no premature returns. + bool bEncodeUrl = true; + switch (rCompiler.GetEncodeUrlMode()) + { + case ScCompiler::ENCODE_BY_GRAMMAR: + bEncodeUrl = bODF; + break; + case ScCompiler::ENCODE_ALWAYS: + bEncodeUrl = true; + break; + case ScCompiler::ENCODE_NEVER: + bEncodeUrl = false; + break; + default: + ; + } + + do + { + if (!makeExternalSingleRefStr(rBuffer, nFileId, rTabName, aRef.Ref1, pRefMgr, true, bEncodeUrl)) + break; + + rBuffer.append(sal_Unicode(':')); + + OUString aLastTabName; + bool bDisplayTabName = (aRef.Ref1.nTab != aRef.Ref2.nTab); + if (bDisplayTabName) + { + // Get the name of the last table. + vector<OUString> aTabNames; + pRefMgr->getAllCachedTableNames(nFileId, aTabNames); + if (aTabNames.empty()) + { + OSL_TRACE( "ConventionOOO_A1::makeExternalRefStrImpl: no sheet names for document ID %s", nFileId); + } + + if (!lcl_getLastTabName(aLastTabName, rTabName, aTabNames, aRef)) + { + OSL_FAIL( "ConventionOOO_A1::makeExternalRefStrImpl: sheet name not found"); + // aLastTabName contains #REF!, proceed. + } + } + else if (bODF) + rBuffer.append( sal_Unicode('.')); // need at least the sheet separator in ODF + makeExternalSingleRefStr( rBuffer, nFileId, aLastTabName, + aRef.Ref2, pRefMgr, bDisplayTabName, bEncodeUrl); + } while (0); + if (bODF) + rBuffer.append( sal_Unicode(']')); + } + + virtual void makeExternalRefStr( ::rtl::OUStringBuffer& rBuffer, const ScCompiler& rCompiler, + sal_uInt16 nFileId, const String& rTabName, const ScComplexRefData& rRef, + ScExternalRefManager* pRefMgr ) const + { + makeExternalRefStrImpl( rBuffer, rCompiler, nFileId, rTabName, rRef, pRefMgr, false); + } +}; + + +static const ConventionOOO_A1 ConvOOO_A1; +const ScCompiler::Convention * const ScCompiler::pConvOOO_A1 = &ConvOOO_A1; + +//----------------------------------------------------------------------------- + +struct ConventionOOO_A1_ODF : public ConventionOOO_A1 +{ + ConventionOOO_A1_ODF() : ConventionOOO_A1 (FormulaGrammar::CONV_ODF) { } + void MakeRefStr( rtl::OUStringBuffer& rBuffer, + const ScCompiler& rComp, + const ScComplexRefData& rRef, + sal_Bool bSingleRef ) const + { + MakeRefStrImpl( rBuffer, rComp, rRef, bSingleRef, true); + } + + virtual String makeExternalNameStr( const String& rFile, const String& rName ) const + { + return lcl_makeExternalNameStr( rFile, rName, sal_Unicode('#'), true); + } + + virtual void makeExternalRefStr( ::rtl::OUStringBuffer& rBuffer, const ScCompiler& rCompiler, + sal_uInt16 nFileId, const String& rTabName, const ScSingleRefData& rRef, + ScExternalRefManager* pRefMgr ) const + { + makeExternalRefStrImpl( rBuffer, rCompiler, nFileId, rTabName, rRef, pRefMgr, true); + } + + virtual void makeExternalRefStr( ::rtl::OUStringBuffer& rBuffer, const ScCompiler& rCompiler, + sal_uInt16 nFileId, const String& rTabName, const ScComplexRefData& rRef, + ScExternalRefManager* pRefMgr ) const + { + makeExternalRefStrImpl( rBuffer, rCompiler, nFileId, rTabName, rRef, pRefMgr, true); + } +}; + +static const ConventionOOO_A1_ODF ConvOOO_A1_ODF; +const ScCompiler::Convention * const ScCompiler::pConvOOO_A1_ODF = &ConvOOO_A1_ODF; + +//----------------------------------------------------------------------------- + +struct ConventionXL +{ + static bool GetDocAndTab( const ScCompiler& rComp, + const ScSingleRefData& rRef, + String& rDocName, + String& rTabName ) + { + bool bHasDoc = false; + + rDocName.Erase(); + if (rRef.IsTabDeleted() || + !rComp.GetDoc()->GetName( rRef.nTab, rTabName )) + { + rTabName = ScGlobal::GetRscString( STR_NO_REF_TABLE ); + return false; + } + + // Cheesy hack to unparse the OOO style "'Doc'#Tab" + if ( rTabName.GetChar(0) == '\'' ) + { + xub_StrLen nPos = ScGlobal::FindUnquoted( rTabName, SC_COMPILER_FILE_TAB_SEP); + if (nPos != STRING_NOTFOUND && nPos > 0 && rTabName.GetChar(nPos-1) == '\'') + { + rDocName = rTabName.Copy( 0, nPos ); + // TODO : More research into how XL escapes the doc path + rDocName = INetURLObject::decode( rDocName, INET_HEX_ESCAPE, + INetURLObject::DECODE_UNAMBIGUOUS ); + rTabName.Erase( 0, nPos + 1 ); + bHasDoc = true; + } + } + + // XL uses the same sheet name quoting conventions in both modes + // it is safe to use A1 here + ScCompiler::CheckTabQuotes( rTabName, FormulaGrammar::CONV_XL_A1 ); + return bHasDoc; + } + + static void MakeDocStr( rtl::OUStringBuffer& rBuf, + const ScCompiler& rComp, + const ScComplexRefData& rRef, + bool bSingleRef ) + { + if( rRef.Ref1.IsFlag3D() ) + { + String aStartTabName, aStartDocName, aEndTabName, aEndDocName; + bool bStartHasDoc = false, bEndHasDoc = false; + + bStartHasDoc = GetDocAndTab( rComp, rRef.Ref1, + aStartDocName, aStartTabName); + + if( !bSingleRef && rRef.Ref2.IsFlag3D() ) + { + bEndHasDoc = GetDocAndTab( rComp, rRef.Ref2, + aEndDocName, aEndTabName); + } + else + bEndHasDoc = bStartHasDoc; + + if( bStartHasDoc ) + { + // A ref across multipled workbooks ? + if( !bEndHasDoc ) + return; + + rBuf.append( sal_Unicode( '[' ) ); + rBuf.append( aStartDocName ); + rBuf.append( sal_Unicode( ']' ) ); + } + + rBuf.append( aStartTabName ); + if( !bSingleRef && rRef.Ref2.IsFlag3D() && aStartTabName != aEndTabName ) + { + rBuf.append( sal_Unicode( ':' ) ); + rBuf.append( aEndTabName ); + } + + rBuf.append( sal_Unicode( '!' ) ); + } + } + + static sal_Unicode getSpecialSymbol( ScCompiler::Convention::SpecialSymbolType eSymType ) + { + switch (eSymType) + { + case ScCompiler::Convention::ABS_SHEET_PREFIX: + return sal_Unicode(0); + case ScCompiler::Convention::SHEET_SEPARATOR: + return '!'; + } + return sal_Unicode(0); + } + + static bool parseExternalName( const String& rSymbol, String& rFile, String& rName, + const ScDocument* pDoc, + const ::com::sun::star::uno::Sequence< + const ::com::sun::star::sheet::ExternalLinkInfo > * pExternalLinks ) + { + return lcl_parseExternalName( rSymbol, rFile, rName, sal_Unicode('!'), pDoc, pExternalLinks); + } + + static String makeExternalNameStr( const String& rFile, const String& rName ) + { + return lcl_makeExternalNameStr( rFile, rName, sal_Unicode('!'), false); + } + + static void makeExternalDocStr( ::rtl::OUStringBuffer& rBuffer, const String& rFullName, bool bEncodeUrl ) + { + // Format that is easier to deal with inside OOo, because we use file + // URL, and all characetrs are allowed. Check if it makes sense to do + // it the way Gnumeric does it. Gnumeric doesn't use the URL form + // and allows relative file path. + // + // ['file:///path/to/source/filename.xls'] + + rBuffer.append(sal_Unicode('[')); + rBuffer.append(sal_Unicode('\'')); + String aFullName; + if (bEncodeUrl) + aFullName = rFullName; + else + aFullName = INetURLObject::decode(rFullName, INET_HEX_ESCAPE, INetURLObject::DECODE_UNAMBIGUOUS); + + const sal_Unicode* pBuf = aFullName.GetBuffer(); + xub_StrLen nLen = aFullName.Len(); + for (xub_StrLen i = 0; i < nLen; ++i) + { + const sal_Unicode c = pBuf[i]; + if (c == sal_Unicode('\'')) + rBuffer.append(c); + rBuffer.append(c); + } + rBuffer.append(sal_Unicode('\'')); + rBuffer.append(sal_Unicode(']')); + } + + static void makeExternalTabNameRange( ::rtl::OUStringBuffer& rBuf, const OUString& rTabName, + const vector<OUString>& rTabNames, + const ScComplexRefData& rRef ) + { + OUString aLastTabName; + if (!lcl_getLastTabName(aLastTabName, rTabName, rTabNames, rRef)) + { + ScRangeStringConverter::AppendTableName(rBuf, aLastTabName); + return; + } + + ScRangeStringConverter::AppendTableName(rBuf, rTabName); + if (rTabName != aLastTabName) + { + rBuf.append(sal_Unicode(':')); + ScRangeStringConverter::AppendTableName(rBuf, rTabName); + } + } + + static void parseExternalDocName( const String& rFormula, xub_StrLen& rSrcPos ) + { + xub_StrLen nLen = rFormula.Len(); + const sal_Unicode* p = rFormula.GetBuffer(); + sal_Unicode cPrev = 0; + for (xub_StrLen i = rSrcPos; i < nLen; ++i) + { + sal_Unicode c = p[i]; + if (i == rSrcPos) + { + // first character must be '['. + if (c != '[') + return; + } + else if (i == rSrcPos + 1) + { + // second character must be a single quote. + if (c != '\'') + return; + } + else if (c == '\'') + { + if (cPrev == '\'') + // two successive single quote is treated as a single + // valid character. + c = 'a'; + } + else if (c == ']') + { + if (cPrev == '\'') + { + // valid source document path found. Increment the + // current position to skip the source path. + rSrcPos = i + 1; + if (rSrcPos >= nLen) + rSrcPos = nLen - 1; + return; + } + else + return; + } + else + { + // any other character + if (i > rSrcPos + 2 && cPrev == '\'') + // unless it's the 3rd character, a normal character + // following immediately a single quote is invalid. + return; + } + cPrev = c; + } + } +}; + +struct ConventionXL_A1 : public Convention_A1, public ConventionXL +{ + ConventionXL_A1() : Convention_A1( FormulaGrammar::CONV_XL_A1 ) { } + ConventionXL_A1( FormulaGrammar::AddressConvention eConv ) : Convention_A1( eConv ) { } + + void makeSingleCellStr( ::rtl::OUStringBuffer& rBuf, const ScSingleRefData& rRef ) const + { + if (!rRef.IsColRel()) + rBuf.append(sal_Unicode('$')); + MakeColStr(rBuf, rRef.nCol); + if (!rRef.IsRowRel()) + rBuf.append(sal_Unicode('$')); + MakeRowStr(rBuf, rRef.nRow); + } + + void MakeRefStr( rtl::OUStringBuffer& rBuf, + const ScCompiler& rComp, + const ScComplexRefData& rRef, + sal_Bool bSingleRef ) const + { + ScComplexRefData aRef( rRef ); + + // Play fast and loose with invalid refs. There is not much point in producing + // Foo!A1:#REF! versus #REF! at this point + aRef.Ref1.CalcAbsIfRel( rComp.GetPos() ); + + MakeDocStr( rBuf, rComp, aRef, bSingleRef ); + + if( aRef.Ref1.IsColDeleted() || aRef.Ref1.IsRowDeleted() ) + { + rBuf.append(ScGlobal::GetRscString(STR_NO_REF_TABLE)); + return; + } + + if( !bSingleRef ) + { + aRef.Ref2.CalcAbsIfRel( rComp.GetPos() ); + if( aRef.Ref2.IsColDeleted() || aRef.Ref2.IsRowDeleted() ) + { + rBuf.append(ScGlobal::GetRscString(STR_NO_REF_TABLE)); + return; + } + + if( aRef.Ref1.nCol == 0 && aRef.Ref2.nCol >= MAXCOL ) + { + if (!aRef.Ref1.IsRowRel()) + rBuf.append(sal_Unicode( '$' )); + MakeRowStr( rBuf, aRef.Ref1.nRow ); + rBuf.append(sal_Unicode( ':' )); + if (!aRef.Ref2.IsRowRel()) + rBuf.append(sal_Unicode( '$' )); + MakeRowStr( rBuf, aRef.Ref2.nRow ); + return; + } + + if( aRef.Ref1.nRow == 0 && aRef.Ref2.nRow >= MAXROW ) + { + if (!aRef.Ref1.IsColRel()) + rBuf.append(sal_Unicode( '$' )); + MakeColStr(rBuf, aRef.Ref1.nCol ); + rBuf.append(sal_Unicode( ':' )); + if (!aRef.Ref2.IsColRel()) + rBuf.append(sal_Unicode( '$' )); + MakeColStr(rBuf, aRef.Ref2.nCol ); + return; + } + } + + makeSingleCellStr(rBuf, aRef.Ref1); + if (!bSingleRef) + { + rBuf.append(sal_Unicode( ':' )); + makeSingleCellStr(rBuf, aRef.Ref2); + } + } + + virtual ParseResult parseAnyToken( const String& rFormula, + xub_StrLen nSrcPos, + const CharClass* pCharClass) const + { + ParseResult aRet; + if ( lcl_isValidQuotedText(rFormula, nSrcPos, aRet) ) + return aRet; + + static const sal_Int32 nStartFlags = KParseTokens::ANY_LETTER_OR_NUMBER | + KParseTokens::ASC_UNDERSCORE | KParseTokens::ASC_DOLLAR; + static const sal_Int32 nContFlags = nStartFlags | KParseTokens::ASC_DOT; + // '?' allowed in range names + static const String aAddAllowed = String::CreateFromAscii("?!"); + return pCharClass->parseAnyToken( rFormula, + nSrcPos, nStartFlags, aAddAllowed, nContFlags, aAddAllowed ); + } + + virtual sal_Unicode getSpecialSymbol( SpecialSymbolType eSymType ) const + { + return ConventionXL::getSpecialSymbol(eSymType); + } + + virtual bool parseExternalName( const String& rSymbol, String& rFile, String& rName, + const ScDocument* pDoc, + const ::com::sun::star::uno::Sequence< + const ::com::sun::star::sheet::ExternalLinkInfo > * pExternalLinks ) const + { + return ConventionXL::parseExternalName( rSymbol, rFile, rName, pDoc, pExternalLinks); + } + + virtual String makeExternalNameStr( const String& rFile, const String& rName ) const + { + return ConventionXL::makeExternalNameStr(rFile, rName); + } + + virtual void makeExternalRefStr( ::rtl::OUStringBuffer& rBuffer, const ScCompiler& rCompiler, + sal_uInt16 nFileId, const String& rTabName, const ScSingleRefData& rRef, + ScExternalRefManager* pRefMgr ) const + { + // ['file:///path/to/file/filename.xls']'Sheet Name'!$A$1 + // This is a little different from the format Excel uses, as Excel + // puts [] only around the file name. But we need to enclose the + // whole file path with [] because the file name can contain any + // characters. + + const String* pFullName = pRefMgr->getExternalFileName(nFileId); + if (!pFullName) + return; + + ScSingleRefData aRef(rRef); + aRef.CalcAbsIfRel(rCompiler.GetPos()); + + ConventionXL::makeExternalDocStr( + rBuffer, *pFullName, rCompiler.GetEncodeUrlMode() == ScCompiler::ENCODE_ALWAYS); + ScRangeStringConverter::AppendTableName(rBuffer, rTabName); + rBuffer.append(sal_Unicode('!')); + + makeSingleCellStr(rBuffer, aRef); + } + + virtual void makeExternalRefStr( ::rtl::OUStringBuffer& rBuffer, const ScCompiler& rCompiler, + sal_uInt16 nFileId, const String& rTabName, const ScComplexRefData& rRef, + ScExternalRefManager* pRefMgr ) const + { + const String* pFullName = pRefMgr->getExternalFileName(nFileId); + if (!pFullName) + return; + + vector<OUString> aTabNames; + pRefMgr->getAllCachedTableNames(nFileId, aTabNames); + if (aTabNames.empty()) + return; + + ScComplexRefData aRef(rRef); + aRef.CalcAbsIfRel(rCompiler.GetPos()); + + ConventionXL::makeExternalDocStr( + rBuffer, *pFullName, rCompiler.GetEncodeUrlMode() == ScCompiler::ENCODE_ALWAYS); + ConventionXL::makeExternalTabNameRange(rBuffer, rTabName, aTabNames, aRef); + rBuffer.append(sal_Unicode('!')); + + makeSingleCellStr(rBuffer, aRef.Ref1); + if (aRef.Ref1 != aRef.Ref2) + { + rBuffer.append(sal_Unicode(':')); + makeSingleCellStr(rBuffer, aRef.Ref2); + } + } +}; + +static const ConventionXL_A1 ConvXL_A1; +const ScCompiler::Convention * const ScCompiler::pConvXL_A1 = &ConvXL_A1; + + +struct ConventionXL_OOX : public ConventionXL_A1 +{ + ConventionXL_OOX() : ConventionXL_A1( FormulaGrammar::CONV_XL_OOX ) { } +}; + +static const ConventionXL_OOX ConvXL_OOX; +const ScCompiler::Convention * const ScCompiler::pConvXL_OOX = &ConvXL_OOX; + + +//----------------------------------------------------------------------------- + +static void +r1c1_add_col( rtl::OUStringBuffer &rBuf, const ScSingleRefData& rRef ) +{ + rBuf.append( sal_Unicode( 'C' ) ); + if( rRef.IsColRel() ) + { + if (rRef.nRelCol != 0) + { + rBuf.append( sal_Unicode( '[' ) ); + rBuf.append( String::CreateFromInt32( rRef.nRelCol ) ); + rBuf.append( sal_Unicode( ']' ) ); + } + } + else + rBuf.append( String::CreateFromInt32( rRef.nCol + 1 ) ); +} +static void +r1c1_add_row( rtl::OUStringBuffer &rBuf, const ScSingleRefData& rRef ) +{ + rBuf.append( sal_Unicode( 'R' ) ); + if( rRef.IsRowRel() ) + { + if (rRef.nRelRow != 0) + { + rBuf.append( sal_Unicode( '[' ) ); + rBuf.append( String::CreateFromInt32( rRef.nRelRow ) ); + rBuf.append( sal_Unicode( ']' ) ); + } + } + else + rBuf.append( String::CreateFromInt32( rRef.nRow + 1 ) ); +} + +struct ConventionXL_R1C1 : public ScCompiler::Convention, public ConventionXL +{ + ConventionXL_R1C1() : ScCompiler::Convention( FormulaGrammar::CONV_XL_R1C1 ) { } + void MakeRefStr( rtl::OUStringBuffer& rBuf, + const ScCompiler& rComp, + const ScComplexRefData& rRef, + sal_Bool bSingleRef ) const + { + ScComplexRefData aRef( rRef ); + + MakeDocStr( rBuf, rComp, aRef, bSingleRef ); + + // Play fast and loose with invalid refs. There is not much point in producing + // Foo!A1:#REF! versus #REF! at this point + aRef.Ref1.CalcAbsIfRel( rComp.GetPos() ); + if( aRef.Ref1.IsColDeleted() || aRef.Ref1.IsRowDeleted() ) + { + rBuf.append(ScGlobal::GetRscString(STR_NO_REF_TABLE)); + return; + } + + if( !bSingleRef ) + { + aRef.Ref2.CalcAbsIfRel( rComp.GetPos() ); + if( aRef.Ref2.IsColDeleted() || aRef.Ref2.IsRowDeleted() ) + { + rBuf.append(ScGlobal::GetRscString(STR_NO_REF_TABLE)); + return; + } + + if( aRef.Ref1.nCol == 0 && aRef.Ref2.nCol >= MAXCOL ) + { + r1c1_add_row( rBuf, rRef.Ref1 ); + if( rRef.Ref1.nRow != rRef.Ref2.nRow || + rRef.Ref1.IsRowRel() != rRef.Ref2.IsRowRel() ) { + rBuf.append (sal_Unicode ( ':' ) ); + r1c1_add_row( rBuf, rRef.Ref2 ); + } + return; + + } + + if( aRef.Ref1.nRow == 0 && aRef.Ref2.nRow >= MAXROW ) + { + r1c1_add_col( rBuf, rRef.Ref1 ); + if( rRef.Ref1.nCol != rRef.Ref2.nCol || + rRef.Ref1.IsColRel() != rRef.Ref2.IsColRel() ) + { + rBuf.append (sal_Unicode ( ':' ) ); + r1c1_add_col( rBuf, rRef.Ref2 ); + } + return; + } + } + + r1c1_add_row( rBuf, rRef.Ref1 ); + r1c1_add_col( rBuf, rRef.Ref1 ); + if (!bSingleRef) + { + rBuf.append (sal_Unicode ( ':' ) ); + r1c1_add_row( rBuf, rRef.Ref2 ); + r1c1_add_col( rBuf, rRef.Ref2 ); + } + } + + ParseResult parseAnyToken( const String& rFormula, + xub_StrLen nSrcPos, + const CharClass* pCharClass) const + { + ConventionXL::parseExternalDocName(rFormula, nSrcPos); + + ParseResult aRet; + if ( lcl_isValidQuotedText(rFormula, nSrcPos, aRet) ) + return aRet; + + static const sal_Int32 nStartFlags = KParseTokens::ANY_LETTER_OR_NUMBER | + KParseTokens::ASC_UNDERSCORE ; + static const sal_Int32 nContFlags = nStartFlags | KParseTokens::ASC_DOT; + // '?' allowed in range names + static const String aAddAllowed = String::CreateFromAscii( "?-[]!" ); + + return pCharClass->parseAnyToken( rFormula, + nSrcPos, nStartFlags, aAddAllowed, nContFlags, aAddAllowed ); + } + + virtual sal_Unicode getSpecialSymbol( SpecialSymbolType eSymType ) const + { + return ConventionXL::getSpecialSymbol(eSymType); + } + + virtual bool parseExternalName( const String& rSymbol, String& rFile, String& rName, + const ScDocument* pDoc, + const ::com::sun::star::uno::Sequence< + const ::com::sun::star::sheet::ExternalLinkInfo > * pExternalLinks ) const + { + return ConventionXL::parseExternalName( rSymbol, rFile, rName, pDoc, pExternalLinks); + } + + virtual String makeExternalNameStr( const String& rFile, const String& rName ) const + { + return ConventionXL::makeExternalNameStr(rFile, rName); + } + + virtual void makeExternalRefStr( ::rtl::OUStringBuffer& rBuffer, const ScCompiler& rCompiler, + sal_uInt16 nFileId, const String& rTabName, const ScSingleRefData& rRef, + ScExternalRefManager* pRefMgr ) const + { + // ['file:///path/to/file/filename.xls']'Sheet Name'!$A$1 + // This is a little different from the format Excel uses, as Excel + // puts [] only around the file name. But we need to enclose the + // whole file path with [] because the file name can contain any + // characters. + + const String* pFullName = pRefMgr->getExternalFileName(nFileId); + if (!pFullName) + return; + + ScSingleRefData aRef(rRef); + aRef.CalcAbsIfRel(rCompiler.GetPos()); + + ConventionXL::makeExternalDocStr( + rBuffer, *pFullName, rCompiler.GetEncodeUrlMode() == ScCompiler::ENCODE_ALWAYS); + ScRangeStringConverter::AppendTableName(rBuffer, rTabName); + rBuffer.append(sal_Unicode('!')); + + r1c1_add_row(rBuffer, aRef); + r1c1_add_col(rBuffer, aRef); + } + + virtual void makeExternalRefStr( ::rtl::OUStringBuffer& rBuffer, const ScCompiler& rCompiler, + sal_uInt16 nFileId, const String& rTabName, const ScComplexRefData& rRef, + ScExternalRefManager* pRefMgr ) const + { + const String* pFullName = pRefMgr->getExternalFileName(nFileId); + if (!pFullName) + return; + + vector<OUString> aTabNames; + pRefMgr->getAllCachedTableNames(nFileId, aTabNames); + if (aTabNames.empty()) + return; + + ScComplexRefData aRef(rRef); + aRef.CalcAbsIfRel(rCompiler.GetPos()); + + ConventionXL::makeExternalDocStr( + rBuffer, *pFullName, rCompiler.GetEncodeUrlMode() == ScCompiler::ENCODE_ALWAYS); + ConventionXL::makeExternalTabNameRange(rBuffer, rTabName, aTabNames, aRef); + rBuffer.append(sal_Unicode('!')); + + if (aRef.Ref2.IsColDeleted() || aRef.Ref2.IsRowDeleted()) + { + rBuffer.append(ScGlobal::GetRscString(STR_NO_REF_TABLE)); + return; + } + + if (aRef.Ref1.nCol == 0 && aRef.Ref2.nCol >= MAXCOL) + { + r1c1_add_row(rBuffer, rRef.Ref1); + if (rRef.Ref1.nRow != rRef.Ref2.nRow || rRef.Ref1.IsRowRel() != rRef.Ref2.IsRowRel()) + { + rBuffer.append (sal_Unicode(':')); + r1c1_add_row(rBuffer, rRef.Ref2); + } + return; + } + + if (aRef.Ref1.nRow == 0 && aRef.Ref2.nRow >= MAXROW) + { + r1c1_add_col(rBuffer, aRef.Ref1); + if (aRef.Ref1.nCol != aRef.Ref2.nCol || aRef.Ref1.IsColRel() != aRef.Ref2.IsColRel()) + { + rBuffer.append (sal_Unicode(':')); + r1c1_add_col(rBuffer, aRef.Ref2); + } + return; + } + + r1c1_add_row(rBuffer, aRef.Ref1); + r1c1_add_col(rBuffer, aRef.Ref1); + rBuffer.append (sal_Unicode (':')); + r1c1_add_row(rBuffer, aRef.Ref2); + r1c1_add_col(rBuffer, aRef.Ref2); + } + + virtual sal_uLong getCharTableFlags( sal_Unicode c, sal_Unicode cLast ) const + { + sal_uLong nFlags = mpCharTable[static_cast<sal_uInt8>(c)]; + if (c == '-' && cLast == '[') + // '-' can occur within a reference string only after '[' e.g. R[-1]C. + nFlags |= SC_COMPILER_C_IDENT; + return nFlags; + } +}; + +static const ConventionXL_R1C1 ConvXL_R1C1; +const ScCompiler::Convention * const ScCompiler::pConvXL_R1C1 = &ConvXL_R1C1; + +//----------------------------------------------------------------------------- +ScCompiler::ScCompiler( ScDocument* pDocument, const ScAddress& rPos,ScTokenArray& rArr) + : FormulaCompiler(rArr), + pDoc( pDocument ), + aPos( rPos ), + pCharClass( ScGlobal::pCharClass ), + mnPredetectedReference(0), + mnRangeOpPosInSymbol(-1), + pConv( pConvOOO_A1 ), + meEncodeUrlMode( ENCODE_BY_GRAMMAR ), + mbCloseBrackets( true ), + mbExtendedErrorDetection( false ), + mbRewind( false ) +{ + nMaxTab = pDoc ? pDoc->GetTableCount() - 1 : 0; +} + +ScCompiler::ScCompiler( ScDocument* pDocument, const ScAddress& rPos) + : + pDoc( pDocument ), + aPos( rPos ), + pCharClass( ScGlobal::pCharClass ), + mnPredetectedReference(0), + mnRangeOpPosInSymbol(-1), + pConv( pConvOOO_A1 ), + meEncodeUrlMode( ENCODE_BY_GRAMMAR ), + mbCloseBrackets( true ), + mbExtendedErrorDetection( false ), + mbRewind( false ) +{ + nMaxTab = pDoc ? pDoc->GetTableCount() - 1 : 0; +} + +void ScCompiler::CheckTabQuotes( String& rString, + const FormulaGrammar::AddressConvention eConv ) +{ + using namespace ::com::sun::star::i18n; + sal_Int32 nStartFlags = KParseTokens::ANY_LETTER_OR_NUMBER | KParseTokens::ASC_UNDERSCORE; + sal_Int32 nContFlags = nStartFlags; + ParseResult aRes = ScGlobal::pCharClass->parsePredefinedToken( + KParseType::IDENTNAME, rString, 0, nStartFlags, EMPTY_STRING, nContFlags, EMPTY_STRING); + bool bNeedsQuote = !((aRes.TokenType & KParseType::IDENTNAME) && aRes.EndPos == rString.Len()); + + switch ( eConv ) + { + default : + case FormulaGrammar::CONV_UNSPECIFIED : + break; + case FormulaGrammar::CONV_OOO : + case FormulaGrammar::CONV_XL_A1 : + case FormulaGrammar::CONV_XL_R1C1 : + case FormulaGrammar::CONV_XL_OOX : + if( bNeedsQuote ) + { + static const String one_quote = static_cast<sal_Unicode>( '\'' ); + static const String two_quote = String::CreateFromAscii( "''" ); + // escape embedded quotes + rString.SearchAndReplaceAll( one_quote, two_quote ); + } + break; + } + + if ( !bNeedsQuote && CharClass::isAsciiNumeric( rString ) ) + { + // Prevent any possible confusion resulting from pure numeric sheet names. + bNeedsQuote = true; + } + + if( bNeedsQuote ) + { + rString.Insert( '\'', 0 ); + rString += '\''; + } +} + +//--------------------------------------------------------------------------- + +void ScCompiler::SetRefConvention( FormulaGrammar::AddressConvention eConv ) +{ + switch ( eConv ) { + case FormulaGrammar::CONV_UNSPECIFIED : + break; + default : + case FormulaGrammar::CONV_OOO : SetRefConvention( pConvOOO_A1 ); break; + case FormulaGrammar::CONV_ODF : SetRefConvention( pConvOOO_A1_ODF ); break; + case FormulaGrammar::CONV_XL_A1 : SetRefConvention( pConvXL_A1 ); break; + case FormulaGrammar::CONV_XL_R1C1 : SetRefConvention( pConvXL_R1C1 ); break; + case FormulaGrammar::CONV_XL_OOX : SetRefConvention( pConvXL_OOX ); break; + } +} + +void ScCompiler::SetRefConvention( const ScCompiler::Convention *pConvP ) +{ + pConv = pConvP; + meGrammar = FormulaGrammar::mergeToGrammar( meGrammar, pConv->meConv); + DBG_ASSERT( FormulaGrammar::isSupported( meGrammar), + "ScCompiler::SetRefConvention: unsupported grammar resulting"); +} + +void ScCompiler::SetError(sal_uInt16 nError) +{ + if( !pArr->GetCodeError() ) + pArr->SetCodeError( nError); +} + + +sal_Unicode* lcl_UnicodeStrNCpy( sal_Unicode* pDst, const sal_Unicode* pSrc, xub_StrLen nMax ) +{ + const sal_Unicode* const pStop = pDst + nMax; + while ( *pSrc && pDst < pStop ) + { + *pDst++ = *pSrc++; + } + *pDst = 0; + return pDst; +} + + +//--------------------------------------------------------------------------- +// NextSymbol +//--------------------------------------------------------------------------- +// Zerlegt die Formel in einzelne Symbole fuer die weitere +// Verarbeitung (Turing-Maschine). +//--------------------------------------------------------------------------- +// Ausgangs Zustand = GetChar +//---------------+-------------------+-----------------------+--------------- +// Alter Zustand | gelesenes Zeichen | Aktion | Neuer Zustand +//---------------+-------------------+-----------------------+--------------- +// GetChar | ;()+-*/^=& | Symbol=Zeichen | Stop +// | <> | Symbol=Zeichen | GetBool +// | $ Buchstabe | Symbol=Zeichen | GetWord +// | Ziffer | Symbol=Zeichen | GetValue +// | " | Keine | GetString +// | Sonst | Keine | GetChar +//---------------+-------------------+-----------------------+--------------- +// GetBool | => | Symbol=Symbol+Zeichen | Stop +// | Sonst | Dec(CharPos) | Stop +//---------------+-------------------+-----------------------+--------------- +// GetWord | SepSymbol | Dec(CharPos) | Stop +// | ()+-*/^=<>&~ | | +// | Leerzeichen | Dec(CharPos) | Stop +// | $_:. | | +// | Buchstabe,Ziffer | Symbol=Symbol+Zeichen | GetWord +// | Sonst | Fehler | Stop +//---------------|-------------------+-----------------------+--------------- +// GetValue | ;()*/^=<>& | | +// | Leerzeichen | Dec(CharPos) | Stop +// | Ziffer E+-%,. | Symbol=Symbol+Zeichen | GetValue +// | Sonst | Fehler | Stop +//---------------+-------------------+-----------------------+--------------- +// GetString | " | Keine | Stop +// | Sonst | Symbol=Symbol+Zeichen | GetString +//---------------+-------------------+-----------------------+--------------- + +xub_StrLen ScCompiler::NextSymbol(bool bInArray) +{ + cSymbol[MAXSTRLEN-1] = 0; // Stopper + sal_Unicode* pSym = cSymbol; + const sal_Unicode* const pStart = aFormula.GetBuffer(); + const sal_Unicode* pSrc = pStart + nSrcPos; + bool bi18n = false; + sal_Unicode c = *pSrc; + sal_Unicode cLast = 0; + bool bQuote = false; + mnRangeOpPosInSymbol = -1; + ScanState eState = ssGetChar; + xub_StrLen nSpaces = 0; + sal_Unicode cSep = mxSymbols->getSymbol( ocSep).GetChar(0); + sal_Unicode cArrayColSep = mxSymbols->getSymbol( ocArrayColSep).GetChar(0); + sal_Unicode cArrayRowSep = mxSymbols->getSymbol( ocArrayRowSep).GetChar(0); + sal_Unicode cDecSep = (mxSymbols->isEnglish() ? '.' : + ScGlobal::pLocaleData->getNumDecimalSep().GetChar(0)); + + // special symbols specific to address convention used + sal_Unicode cSheetPrefix = pConv->getSpecialSymbol(ScCompiler::Convention::ABS_SHEET_PREFIX); + sal_Unicode cSheetSep = pConv->getSpecialSymbol(ScCompiler::Convention::SHEET_SEPARATOR); + + int nDecSeps = 0; + bool bAutoIntersection = false; + int nRefInName = 0; + mnPredetectedReference = 0; + // try to parse simple tokens before calling i18n parser + while ((c != 0) && (eState != ssStop) ) + { + pSrc++; + sal_uLong nMask = GetCharTableFlags( c, cLast ); + + // The parameter separator and the array column and row separators end + // things unconditionally if not in string or reference. + if (c == cSep || (bInArray && (c == cArrayColSep || c == cArrayRowSep))) + { + switch (eState) + { + // these are to be continued + case ssGetString: + case ssSkipString: + case ssGetReference: + case ssSkipReference: + break; + default: + if (eState == ssGetChar) + *pSym++ = c; + else + pSrc--; + eState = ssStop; + } + } +Label_MaskStateMachine: + switch (eState) + { + case ssGetChar : + { + // Order is important! + if( nMask & SC_COMPILER_C_ODF_LABEL_OP ) + { + // '!!' automatic intersection + if (GetCharTableFlags( pSrc[0], 0 ) & SC_COMPILER_C_ODF_LABEL_OP) + { + /* TODO: For now the UI "space operator" is used, this + * could be enhanced using a specialized OpCode to get + * rid of the space ambiguity, which would need some + * places to be adapted though. And we would still need + * to support the ambiguous space operator for UI + * purposes anyway. However, we then could check for + * invalid usage of '!!', which currently isn't + * possible. */ + if (!bAutoIntersection) + { + ++pSrc; + nSpaces += 2; // must match the character count + bAutoIntersection = true; + } + else + { + pSrc--; + eState = ssStop; + } + } + else + { + nMask &= ~SC_COMPILER_C_ODF_LABEL_OP; + goto Label_MaskStateMachine; + } + } + else if( nMask & SC_COMPILER_C_ODF_NAME_MARKER ) + { + // '$$' defined name marker + if (GetCharTableFlags( pSrc[0], 0 ) & SC_COMPILER_C_ODF_NAME_MARKER) + { + // both eaten, not added to pSym + ++pSrc; + } + else + { + nMask &= ~SC_COMPILER_C_ODF_NAME_MARKER; + goto Label_MaskStateMachine; + } + } + else if( nMask & SC_COMPILER_C_CHAR ) + { + *pSym++ = c; + eState = ssStop; + } + else if( nMask & SC_COMPILER_C_ODF_LBRACKET ) + { + // eaten, not added to pSym + eState = ssGetReference; + mnPredetectedReference = 1; + } + else if( nMask & SC_COMPILER_C_CHAR_BOOL ) + { + *pSym++ = c; + eState = ssGetBool; + } + else if( nMask & SC_COMPILER_C_CHAR_VALUE ) + { + *pSym++ = c; + eState = ssGetValue; + } + else if( nMask & SC_COMPILER_C_CHAR_STRING ) + { + *pSym++ = c; + eState = ssGetString; + } + else if( nMask & SC_COMPILER_C_CHAR_DONTCARE ) + { + nSpaces++; + } + else if( nMask & SC_COMPILER_C_CHAR_IDENT ) + { // try to get a simple ASCII identifier before calling + // i18n, to gain performance during import + *pSym++ = c; + eState = ssGetIdent; + } + else + { + bi18n = true; + eState = ssStop; + } + } + break; + case ssGetIdent: + { + if ( nMask & SC_COMPILER_C_IDENT ) + { // This catches also $Sheet1.A$1, for example. + if( pSym == &cSymbol[ MAXSTRLEN-1 ] ) + { + SetError(errStringOverflow); + eState = ssStop; + } + else + *pSym++ = c; + } + else if (c == ':' && mnRangeOpPosInSymbol < 0) + { + // One range operator may form Sheet1.A:A, which we need to + // pass as one entity to IsReference(). + mnRangeOpPosInSymbol = pSym - &cSymbol[0]; + if( pSym == &cSymbol[ MAXSTRLEN-1 ] ) + { + SetError(errStringOverflow); + eState = ssStop; + } + else + *pSym++ = c; + } + else if ( 128 <= c || '\'' == c ) + { // High values need reparsing with i18n, + // single quoted $'sheet' names too (otherwise we'd had to + // implement everything twice). + bi18n = true; + eState = ssStop; + } + else + { + pSrc--; + eState = ssStop; + } + } + break; + case ssGetBool : + { + if( nMask & SC_COMPILER_C_BOOL ) + { + *pSym++ = c; + eState = ssStop; + } + else + { + pSrc--; + eState = ssStop; + } + } + break; + case ssGetValue : + { + if( pSym == &cSymbol[ MAXSTRLEN-1 ] ) + { + SetError(errStringOverflow); + eState = ssStop; + } + else if (c == cDecSep) + { + if (++nDecSeps > 1) + { + // reparse with i18n, may be numeric sheet name as well + bi18n = true; + eState = ssStop; + } + else + *pSym++ = c; + } + else if( nMask & SC_COMPILER_C_VALUE ) + *pSym++ = c; + else if( nMask & SC_COMPILER_C_VALUE_SEP ) + { + pSrc--; + eState = ssStop; + } + else if (c == 'E' || c == 'e') + { + if (GetCharTableFlags( pSrc[0], 0 ) & SC_COMPILER_C_VALUE_EXP) + *pSym++ = c; + else + { + // reparse with i18n + bi18n = true; + eState = ssStop; + } + } + else if( nMask & SC_COMPILER_C_VALUE_SIGN ) + { + if (((cLast == 'E') || (cLast == 'e')) && + (GetCharTableFlags( pSrc[0], 0 ) & SC_COMPILER_C_VALUE_VALUE)) + { + *pSym++ = c; + } + else + { + pSrc--; + eState = ssStop; + } + } + else + { + // reparse with i18n + bi18n = true; + eState = ssStop; + } + } + break; + case ssGetString : + { + if( nMask & SC_COMPILER_C_STRING_SEP ) + { + if ( !bQuote ) + { + if ( *pSrc == '"' ) + bQuote = true; // "" => literal " + else + eState = ssStop; + } + else + bQuote = false; + } + if ( !bQuote ) + { + if( pSym == &cSymbol[ MAXSTRLEN-1 ] ) + { + SetError(errStringOverflow); + eState = ssSkipString; + } + else + *pSym++ = c; + } + } + break; + case ssSkipString: + if( nMask & SC_COMPILER_C_STRING_SEP ) + eState = ssStop; + break; + case ssGetReference: + if( pSym == &cSymbol[ MAXSTRLEN-1 ] ) + { + SetError( errStringOverflow); + eState = ssSkipReference; + } + // fall through and follow logic + case ssSkipReference: + // ODF reference: ['External'#$'Sheet'.A1:.B2] with dots being + // mandatory also if no sheet name. 'External'# is optional, + // sheet name is optional, quotes around sheet name are + // optional if no quote contained. + // 2nd usage: ['Sheet'.$$'DefinedName'] + // 3rd usage: ['External'#$$'DefinedName'] + // 4th usage: ['External'#$'Sheet'.$$'DefinedName'] + // Also for all these names quotes are optional if no quote + // contained. + { + + // nRefInName: 0 := not in sheet name yet. 'External' + // is parsed as if it was a sheet name and nRefInName + // is reset when # is encountered immediately after closing + // quote. Same with 'DefinedName', nRefInName is cleared + // when : is encountered. + + // Encountered leading $ before sheet name. + static const int kDollar = (1 << 1); + // Encountered ' opening quote, which may be after $ or + // not. + static const int kOpen = (1 << 2); + // Somewhere in name. + static const int kName = (1 << 3); + // Encountered ' in name, will be cleared if double or + // transformed to kClose if not, in which case kOpen is + // cleared. + static const int kQuote = (1 << 4); + // Past ' closing quote. + static const int kClose = (1 << 5); + // Encountered # file/sheet separator. + static const int kFileSep = (1 << 6); + // Past . sheet name separator. + static const int kPast = (1 << 7); + // Marked name $$ follows sheet name separator, detected + // while we're still on the separator. Will be cleared when + // entering the name. + static const int kMarkAhead = (1 << 8); + // In marked defined name. + static const int kDefName = (1 << 9); + + bool bAddToSymbol = true; + if ((nMask & SC_COMPILER_C_ODF_RBRACKET) && !(nRefInName & kOpen)) + { + DBG_ASSERT( nRefInName & (kPast | kDefName), + "ScCompiler::NextSymbol: reference: " + "closing bracket ']' without prior sheet name separator '.' violates ODF spec"); + // eaten, not added to pSym + bAddToSymbol = false; + eState = ssStop; + } + else if (cSheetSep == c && nRefInName == 0) + { + // eat it, no sheet name [.A1] + bAddToSymbol = false; + nRefInName |= kPast; + if ('$' == pSrc[0] && '$' == pSrc[1]) + nRefInName |= kMarkAhead; + } + else if (!(nRefInName & kPast) || (nRefInName & (kMarkAhead | kDefName))) + { + // Not in col/row yet. + + if (SC_COMPILER_FILE_TAB_SEP == c && (nRefInName & kFileSep)) + nRefInName = 0; + else if ('$' == c && '$' == pSrc[0] && !(nRefInName & kOpen)) + { + nRefInName &= ~kMarkAhead; + if (!(nRefInName & kDefName)) + { + // eaten, not added to pSym (2 chars) + bAddToSymbol = false; + ++pSrc; + nRefInName &= kPast; + nRefInName |= kDefName; + } + else + { + // ScAddress::Parse() will recognize this as + // invalid later. + if (eState != ssSkipReference) + { + *pSym++ = c; + *pSym++ = *pSrc++; + } + bAddToSymbol = false; + } + } + else if (cSheetPrefix == c && nRefInName == 0) + nRefInName |= kDollar; + else if ('\'' == c) + { + // TODO: The conventions' parseExternalName() + // should handle quoted names, but as long as they + // don't remove non-embedded quotes here. + if (!(nRefInName & kName)) + { + nRefInName |= (kOpen | kName); + bAddToSymbol = !(nRefInName & kDefName); + } + else if (!(nRefInName & kOpen)) + { + DBG_ERRORFILE("ScCompiler::NextSymbol: reference: " + "a ''' without the name being enclosed in '...' violates ODF spec"); + } + else if (nRefInName & kQuote) + { + // escaped embedded quote + nRefInName &= ~kQuote; + } + else + { + switch (pSrc[0]) + { + case '\'': + // escapes embedded quote + nRefInName |= kQuote; + break; + case SC_COMPILER_FILE_TAB_SEP: + // sheet name should follow + nRefInName |= kFileSep; + // fallthru + default: + // quote not followed by quote => close + nRefInName |= kClose; + nRefInName &= ~kOpen; + } + bAddToSymbol = !(nRefInName & kDefName); + } + } + else if (cSheetSep == c && !(nRefInName & kOpen)) + { + // unquoted sheet name separator + nRefInName |= kPast; + if ('$' == pSrc[0] && '$' == pSrc[1]) + nRefInName |= kMarkAhead; + } + else if (':' == c && !(nRefInName & kOpen)) + { + DBG_ERRORFILE("ScCompiler::NextSymbol: reference: " + "range operator ':' without prior sheet name separator '.' violates ODF spec"); + nRefInName = 0; + ++mnPredetectedReference; + } + else if (!(nRefInName & kName)) + { + // start unquoted name + nRefInName |= kName; + } + } + else if (':' == c) + { + // range operator + nRefInName = 0; + ++mnPredetectedReference; + } + if (bAddToSymbol && eState != ssSkipReference) + *pSym++ = c; // everything is part of reference + } + break; + case ssStop: + ; // nothing, prevent warning + break; + } + cLast = c; + c = *pSrc; + } + if ( bi18n ) + { + nSrcPos = sal::static_int_cast<xub_StrLen>( nSrcPos + nSpaces ); + String aSymbol; + mnRangeOpPosInSymbol = -1; + sal_uInt16 nErr = 0; + do + { + bi18n = false; + // special case (e.g. $'sheetname' in OOO A1) + if ( pStart[nSrcPos] == cSheetPrefix && pStart[nSrcPos+1] == '\'' ) + aSymbol += pStart[nSrcPos++]; + + ParseResult aRes = pConv->parseAnyToken( aFormula, nSrcPos, pCharClass ); + + if ( !aRes.TokenType ) + SetError( nErr = errIllegalChar ); // parsed chars as string + if ( aRes.EndPos <= nSrcPos ) + { // ?!? + SetError( nErr = errIllegalChar ); + nSrcPos = aFormula.Len(); + aSymbol.Erase(); + } + else + { + aSymbol.Append( pStart + nSrcPos, (xub_StrLen)aRes.EndPos - nSrcPos ); + nSrcPos = (xub_StrLen) aRes.EndPos; + c = pStart[nSrcPos]; + if ( aRes.TokenType & KParseType::SINGLE_QUOTE_NAME ) + { // special cases (e.g. 'sheetname'. or 'filename'# in OOO A1) + bi18n = (c == cSheetSep || c == SC_COMPILER_FILE_TAB_SEP); + } + // One range operator restarts parsing for second reference. + if (c == ':' && mnRangeOpPosInSymbol < 0) + { + mnRangeOpPosInSymbol = aSymbol.Len(); + bi18n = true; + } + if ( bi18n ) + aSymbol += pStart[nSrcPos++]; + } + } while ( bi18n && !nErr ); + xub_StrLen nLen = aSymbol.Len(); + if ( nLen >= MAXSTRLEN ) + { + SetError( errStringOverflow ); + nLen = MAXSTRLEN-1; + } + lcl_UnicodeStrNCpy( cSymbol, aSymbol.GetBuffer(), nLen ); + pSym = &cSymbol[nLen]; + } + else + { + nSrcPos = sal::static_int_cast<xub_StrLen>( pSrc - pStart ); + *pSym = 0; + } + if (mnRangeOpPosInSymbol >= 0 && mnRangeOpPosInSymbol == (pSym-1) - &cSymbol[0]) + { + // This is a trailing range operator, which is nonsense. Will be caught + // in next round. + mnRangeOpPosInSymbol = -1; + *--pSym = 0; + --nSrcPos; + } + if ( bAutoCorrect ) + aCorrectedSymbol = cSymbol; + if (bAutoIntersection && nSpaces > 1) + --nSpaces; // replace '!!' with only one space + return nSpaces; +} + +//--------------------------------------------------------------------------- +// Convert symbol to token +//--------------------------------------------------------------------------- + +sal_Bool ScCompiler::IsOpCode( const String& rName, bool bInArray ) +{ + OpCodeHashMap::const_iterator iLook( mxSymbols->getHashMap()->find( rName)); + sal_Bool bFound = (iLook != mxSymbols->getHashMap()->end()); + if (bFound) + { + ScRawToken aToken; + OpCode eOp = iLook->second; + if (bInArray) + { + if (rName.Equals(mxSymbols->getSymbol(ocArrayColSep))) + eOp = ocArrayColSep; + else if (rName.Equals(mxSymbols->getSymbol(ocArrayRowSep))) + eOp = ocArrayRowSep; + } + aToken.SetOpCode(eOp); + pRawToken = aToken.Clone(); + } + else if (mxSymbols->isODFF()) + { + // ODFF names that are not written in the current mapping but to be + // recognized. New names will be written in a future relase, then + // exchange (!) with the names in + // formula/source/core/resource/core_resource.src to be able to still + // read the old names as well. + struct FunctionName + { + const sal_Char* pName; + OpCode eOp; + }; + static const FunctionName aOdffAliases[] = { + // Renamed old names: + // XXX none yet. + // Renamed new names: + { "BINOM.DIST.RANGE", ocB }, // B -> BINOM.DIST.RANGE + { "LEGACY.TDIST", ocTDist }, // TDIST -> LEGACY.TDIST + { "ORG.OPENOFFICE.EASTERSUNDAY", ocEasterSunday } // EASTERSUNDAY -> ORG.OPENOFFICE.EASTERSUNDAY + }; + static const size_t nOdffAliases = SAL_N_ELEMENTS(aOdffAliases); + for (size_t i=0; i<nOdffAliases; ++i) + { + if (rName.EqualsIgnoreCaseAscii( aOdffAliases[i].pName)) + { + ScRawToken aToken; + aToken.SetOpCode( aOdffAliases[i].eOp); + pRawToken = aToken.Clone(); + bFound = sal_True; + break; // for + } + } + } + if (!bFound) + { + String aIntName; + if (mxSymbols->hasExternals()) + { + // If symbols are set by filters get mapping to exact name. + ExternalHashMap::const_iterator iExt( + mxSymbols->getExternalHashMap()->find( rName)); + if (iExt != mxSymbols->getExternalHashMap()->end()) + { + if (ScGlobal::GetAddInCollection()->GetFuncData( (*iExt).second)) + aIntName = (*iExt).second; + } + if (!aIntName.Len()) + { + // If that isn't found we might continue with rName lookup as a + // last resort by just falling through to FindFunction(), but + // it shouldn't happen if the map was setup correctly. Don't + // waste time and bail out. + return false; + } + } + if (!aIntName.Len()) + { + // Old (deprecated) addins first for legacy. + sal_uInt16 nIndex; + bFound = ScGlobal::GetFuncCollection()->SearchFunc( cSymbol, nIndex); + if (bFound) + { + ScRawToken aToken; + aToken.SetExternal( cSymbol ); + pRawToken = aToken.Clone(); + } + else + // bLocalFirst=sal_False for (English) upper full original name + // (service.function) + aIntName = ScGlobal::GetAddInCollection()->FindFunction( + rName, !mxSymbols->isEnglish()); + } + if (aIntName.Len()) + { + ScRawToken aToken; + aToken.SetExternal( aIntName.GetBuffer() ); // international name + pRawToken = aToken.Clone(); + bFound = sal_True; + } + } + OpCode eOp; + if (bFound && ((eOp = pRawToken->GetOpCode()) == ocSub || eOp == ocNegSub)) + { + bool bShouldBeNegSub = + (eLastOp == ocOpen || eLastOp == ocSep || eLastOp == ocNegSub || + (SC_OPCODE_START_BIN_OP <= eLastOp && eLastOp < SC_OPCODE_STOP_BIN_OP) || + eLastOp == ocArrayOpen || + eLastOp == ocArrayColSep || eLastOp == ocArrayRowSep); + if (bShouldBeNegSub && eOp == ocSub) + pRawToken->NewOpCode( ocNegSub ); + //! if ocNegSub had ForceArray we'd have to set it here + else if (!bShouldBeNegSub && eOp == ocNegSub) + pRawToken->NewOpCode( ocSub ); + } + return bFound; +} + +sal_Bool ScCompiler::IsOpCode2( const String& rName ) +{ + sal_Bool bFound = false; + sal_uInt16 i; + + for( i = ocInternalBegin; i <= ocInternalEnd && !bFound; i++ ) + bFound = rName.EqualsAscii( pInternal[ i-ocInternalBegin ] ); + + if (bFound) + { + ScRawToken aToken; + aToken.SetOpCode( (OpCode) --i ); + pRawToken = aToken.Clone(); + } + return bFound; +} + +sal_Bool ScCompiler::IsValue( const String& rSym ) +{ + double fVal; + sal_uInt32 nIndex = ( mxSymbols->isEnglish() ? + pDoc->GetFormatTable()->GetStandardIndex( LANGUAGE_ENGLISH_US ) : 0 ); + + if (pDoc->GetFormatTable()->IsNumberFormat( rSym, nIndex, fVal ) ) + { + sal_uInt16 nType = pDoc->GetFormatTable()->GetType(nIndex); + + // Don't accept 3:3 as time, it is a reference to entire row 3 instead. + // Dates should never be entered directly and automatically converted + // to serial, because the serial would be wrong if null-date changed. + // Usually it wouldn't be accepted anyway because the date separator + // clashed with other separators or operators. + if (nType & (NUMBERFORMAT_TIME | NUMBERFORMAT_DATE)) + return false; + + if (nType == NUMBERFORMAT_LOGICAL) + { + const sal_Unicode* p = aFormula.GetBuffer() + nSrcPos; + while( *p == ' ' ) + p++; + if (*p == '(') + return false; // Boolean function instead. + } + + if( nType == NUMBERFORMAT_TEXT ) + // HACK: number too big! + SetError( errIllegalArgument ); + ScRawToken aToken; + aToken.SetDouble( fVal ); + pRawToken = aToken.Clone(); + return sal_True; + } + else + return false; +} + +sal_Bool ScCompiler::IsString() +{ + register const sal_Unicode* p = cSymbol; + while ( *p ) + p++; + xub_StrLen nLen = sal::static_int_cast<xub_StrLen>( p - cSymbol - 1 ); + sal_Bool bQuote = ((cSymbol[0] == '"') && (cSymbol[nLen] == '"')); + if ((bQuote ? nLen-2 : nLen) > MAXSTRLEN-1) + { + SetError(errStringOverflow); + return false; + } + if ( bQuote ) + { + cSymbol[nLen] = '\0'; + ScRawToken aToken; + aToken.SetString( cSymbol+1 ); + pRawToken = aToken.Clone(); + return sal_True; + } + return false; +} + + +sal_Bool ScCompiler::IsPredetectedReference( const String& rName ) +{ + // Speedup documents with lots of broken references, e.g. sheet deleted. + xub_StrLen nPos = rName.SearchAscii( "#REF!"); + if (nPos != STRING_NOTFOUND) + { + /* TODO: this may be enhanced by reusing scan information from + * NextSymbol(), the positions of quotes and special characters found + * there for $'sheet'.A1:... could be stored in a vector. We don't + * fully rescan here whether found positions are within single quotes + * for performance reasons. This code does not check for possible + * occurrences of insane "valid" sheet names like + * 'haha.#REF!1fooledyou' and will generate an error on such. */ + if (nPos == 0) + return false; // #REF!.AB42 or #REF!42 or #REF!#REF! + sal_Unicode c = rName.GetChar(nPos-1); // before #REF! + if ('$' == c) + { + if (nPos == 1) + return false; // $#REF!.AB42 or $#REF!42 or $#REF!#REF! + c = rName.GetChar(nPos-2); // before $#REF! + } + sal_Unicode c2 = rName.GetChar(nPos+5); // after #REF! + switch (c) + { + case '.': + if ('$' == c2 || '#' == c2 || ('0' <= c2 && c2 <= '9')) + return false; // sheet.#REF!42 or sheet.#REF!#REF! + break; + case ':': + if (mnPredetectedReference > 1 && + ('.' == c2 || '$' == c2 || '#' == c2 || + ('0' <= c2 && c2 <= '9'))) + return false; // :#REF!.AB42 or :#REF!42 or :#REF!#REF! + break; + default: + if ((('A' <= c && c <= 'Z') || ('a' <= c && c <= 'z')) && + ((mnPredetectedReference > 1 && ':' == c2) || 0 == c2)) + return false; // AB#REF!: or AB#REF! + } + } + switch (mnPredetectedReference) + { + case 1: + return IsSingleReference( rName); + case 2: + return IsDoubleReference( rName); + } + return false; +} + + +sal_Bool ScCompiler::IsDoubleReference( const String& rName ) +{ + ScRange aRange( aPos, aPos ); + const ScAddress::Details aDetails( pConv->meConv, aPos ); + ScAddress::ExternalInfo aExtInfo; + sal_uInt16 nFlags = aRange.Parse( rName, pDoc, aDetails, &aExtInfo, &maExternalLinks ); + if( nFlags & SCA_VALID ) + { + ScRawToken aToken; + ScComplexRefData aRef; + aRef.InitRange( aRange ); + aRef.Ref1.SetColRel( (nFlags & SCA_COL_ABSOLUTE) == 0 ); + aRef.Ref1.SetRowRel( (nFlags & SCA_ROW_ABSOLUTE) == 0 ); + aRef.Ref1.SetTabRel( (nFlags & SCA_TAB_ABSOLUTE) == 0 ); + if ( !(nFlags & SCA_VALID_TAB) ) + aRef.Ref1.SetTabDeleted( sal_True ); // #REF! + aRef.Ref1.SetFlag3D( ( nFlags & SCA_TAB_3D ) != 0 ); + aRef.Ref2.SetColRel( (nFlags & SCA_COL2_ABSOLUTE) == 0 ); + aRef.Ref2.SetRowRel( (nFlags & SCA_ROW2_ABSOLUTE) == 0 ); + aRef.Ref2.SetTabRel( (nFlags & SCA_TAB2_ABSOLUTE) == 0 ); + if ( !(nFlags & SCA_VALID_TAB2) ) + aRef.Ref2.SetTabDeleted( sal_True ); // #REF! + aRef.Ref2.SetFlag3D( ( nFlags & SCA_TAB2_3D ) != 0 ); + aRef.CalcRelFromAbs( aPos ); + if (aExtInfo.mbExternal) + { + ScExternalRefManager* pRefMgr = pDoc->GetExternalRefManager(); + const String* pRealTab = pRefMgr->getRealTableName(aExtInfo.mnFileId, aExtInfo.maTabName); + aToken.SetExternalDoubleRef( + aExtInfo.mnFileId, pRealTab ? *pRealTab : aExtInfo.maTabName, aRef); + } + else + { + aToken.SetDoubleReference(aRef); + } + pRawToken = aToken.Clone(); + } + + return ( nFlags & SCA_VALID ) != 0; +} + + +sal_Bool ScCompiler::IsSingleReference( const String& rName ) +{ + ScAddress aAddr( aPos ); + const ScAddress::Details aDetails( pConv->meConv, aPos ); + ScAddress::ExternalInfo aExtInfo; + sal_uInt16 nFlags = aAddr.Parse( rName, pDoc, aDetails, &aExtInfo, &maExternalLinks ); + // Something must be valid in order to recognize Sheet1.blah or blah.a1 + // as a (wrong) reference. + if( nFlags & ( SCA_VALID_COL|SCA_VALID_ROW|SCA_VALID_TAB ) ) + { + ScRawToken aToken; + ScSingleRefData aRef; + aRef.InitAddress( aAddr ); + aRef.SetColRel( (nFlags & SCA_COL_ABSOLUTE) == 0 ); + aRef.SetRowRel( (nFlags & SCA_ROW_ABSOLUTE) == 0 ); + aRef.SetTabRel( (nFlags & SCA_TAB_ABSOLUTE) == 0 ); + aRef.SetFlag3D( ( nFlags & SCA_TAB_3D ) != 0 ); + // the reference is really invalid + if( !( nFlags & SCA_VALID ) ) + { + if( !( nFlags & SCA_VALID_COL ) ) + aRef.nCol = MAXCOL+1; + if( !( nFlags & SCA_VALID_ROW ) ) + aRef.nRow = MAXROW+1; + if( !( nFlags & SCA_VALID_TAB ) ) + aRef.nTab = MAXTAB+3; + nFlags |= SCA_VALID; + } + aRef.CalcRelFromAbs( aPos ); + + if (aExtInfo.mbExternal) + { + ScExternalRefManager* pRefMgr = pDoc->GetExternalRefManager(); + const String* pRealTab = pRefMgr->getRealTableName(aExtInfo.mnFileId, aExtInfo.maTabName); + aToken.SetExternalSingleRef( + aExtInfo.mnFileId, pRealTab ? *pRealTab : aExtInfo.maTabName, aRef); + } + else + aToken.SetSingleReference(aRef); + pRawToken = aToken.Clone(); + } + + return ( nFlags & SCA_VALID ) != 0; +} + + +sal_Bool ScCompiler::IsReference( const String& rName ) +{ + // Has to be called before IsValue + sal_Unicode ch1 = rName.GetChar(0); + sal_Unicode cDecSep = ( mxSymbols->isEnglish() ? '.' : + ScGlobal::pLocaleData->getNumDecimalSep().GetChar(0) ); + if ( ch1 == cDecSep ) + return false; + // Who was that imbecile introducing '.' as the sheet name separator!?! + if ( CharClass::isAsciiNumeric( ch1 ) ) + { + // Numerical sheet name is valid. + // But English 1.E2 or 1.E+2 is value 100, 1.E-2 is 0.01 + // Don't create a #REF! of values. But also do not bail out on + // something like 3:3, meaning entire row 3. + do + { + const xub_StrLen nPos = ScGlobal::FindUnquoted( rName, '.'); + if ( nPos == STRING_NOTFOUND ) + { + if (ScGlobal::FindUnquoted( rName, ':') != STRING_NOTFOUND) + break; // may be 3:3, continue as usual + return false; + } + sal_Unicode const * const pTabSep = rName.GetBuffer() + nPos; + sal_Unicode ch2 = pTabSep[1]; // maybe a column identifier + if ( !(ch2 == '$' || CharClass::isAsciiAlpha( ch2 )) ) + return false; + if ( cDecSep == '.' && (ch2 == 'E' || ch2 == 'e') // E + - digit + && (GetCharTableFlags( pTabSep[2], pTabSep[1] ) & SC_COMPILER_C_VALUE_EXP) ) + { // #91053# + // If it is an 1.E2 expression check if "1" is an existent sheet + // name. If so, a desired value 1.E2 would have to be entered as + // 1E2 or 1.0E2 or 1.E+2, sorry. Another possibility would be to + // require numerical sheet names always being entered quoted, which + // is not desirable (too many 1999, 2000, 2001 sheets in use). + // Furthermore, XML files created with versions prior to SRC640e + // wouldn't contain the quotes added by MakeTabStr()/CheckTabQuotes() + // and would produce wrong formulas if the conditions here are met. + // If you can live with these restrictions you may remove the + // check and return an unconditional FALSE. + String aTabName( rName.Copy( 0, nPos ) ); + SCTAB nTab; + if ( !pDoc->GetTable( aTabName, nTab ) ) + return false; + // If sheet "1" exists and the expression is 1.E+2 continue as + // usual, the ScRange/ScAddress parser will take care of it. + } + } while(0); + } + + if (IsSingleReference( rName)) + return true; + + // Though the range operator is handled explicitly, when encountering + // something like Sheet1.A:A we will have to treat it as one entity if it + // doesn't pass as single cell reference. + if (mnRangeOpPosInSymbol > 0) // ":foo" would be nonsense + { + if (IsDoubleReference( rName)) + return true; + // Now try with a symbol up to the range operator, rewind source + // position. + sal_Int32 nLen = mnRangeOpPosInSymbol; + while (cSymbol[++nLen]) + ; + cSymbol[mnRangeOpPosInSymbol] = 0; + nSrcPos -= static_cast<xub_StrLen>(nLen - mnRangeOpPosInSymbol); + mnRangeOpPosInSymbol = -1; + mbRewind = true; + return true; // end all checks + } + else + { + // Special treatment for the 'E:\[doc]Sheet1:Sheet3'!D5 Excel sickness, + // mnRangeOpPosInSymbol did not catch the range operator as it is + // within a quoted name. + switch (pConv->meConv) + { + case FormulaGrammar::CONV_XL_A1: + case FormulaGrammar::CONV_XL_R1C1: + case FormulaGrammar::CONV_XL_OOX: + if (rName.GetChar(0) == '\'' && IsDoubleReference( rName)) + return true; + break; + default: + ; // nothing + } + } + return false; +} + +sal_Bool ScCompiler::IsMacro( const String& rName ) +{ + String aName( rName); + StarBASIC* pObj = 0; + SfxObjectShell* pDocSh = pDoc->GetDocumentShell(); + + SfxApplication* pSfxApp = SFX_APP(); + + if( pDocSh )//XXX + pObj = pDocSh->GetBasic(); + else + pObj = pSfxApp->GetBasic(); + + // ODFF recommends to store user-defined functions prefixed with "USER.", + // use only unprefixed name if encountered. BASIC doesn't allow '.' in a + // function name so a function "USER.FOO" could not exist, and macro check + // is assigned the lowest priority in function name check. + if (FormulaGrammar::isODFF( GetGrammar()) && aName.EqualsIgnoreCaseAscii( "USER.", 0, 5)) + aName.Erase( 0, 5); + + SbxMethod* pMeth = (SbxMethod*) pObj->Find( aName, SbxCLASS_METHOD ); + if( !pMeth ) + { + return false; + } + // It really should be a BASIC function! + if( pMeth->GetType() == SbxVOID + || ( pMeth->IsFixed() && pMeth->GetType() == SbxEMPTY ) + || !pMeth->ISA(SbMethod) ) + { + return false; + } + ScRawToken aToken; + aToken.SetExternal( aName.GetBuffer() ); + aToken.eOp = ocMacro; + pRawToken = aToken.Clone(); + return sal_True; +} + +sal_Bool ScCompiler::IsNamedRange( const String& rUpperName ) +{ + // IsNamedRange is called only from NextNewToken, with an upper-case string + + // try local names first + bool bGlobal = false; + ScRangeName* pRangeName = pDoc->GetRangeName(aPos.Tab()); + const ScRangeData* pData = pRangeName->findByUpperName(rUpperName); + if (!pData) + { + pRangeName = pDoc->GetRangeName(); + if (pRangeName) + pData = pRangeName->findByUpperName(rUpperName); + if (pData) + bGlobal = true; + } + + if (pData) + { + ScRawToken aToken; + aToken.SetName(bGlobal, pData->GetIndex()); + pRawToken = aToken.Clone(); + return true; + } + else + return false; +} + +bool ScCompiler::IsExternalNamedRange( const String& rSymbol ) +{ + /* FIXME: This code currently (2008-12-02T15:41+0100 in CWS mooxlsc) + * correctly parses external named references in OOo, as required per RFE + * #i3740#, just that we can't store them in ODF yet. We will need an OASIS + * spec first. Until then don't pretend to support external names that + * wouldn't survive a save and reload cycle, return false instead. */ + + if (!pConv) + return false; + + String aFile, aName; + if (!pConv->parseExternalName( rSymbol, aFile, aName, pDoc, &maExternalLinks)) + return false; + + ScRawToken aToken; + if (aFile.Len() > MAXSTRLEN || aName.Len() > MAXSTRLEN) + return false; + + ScExternalRefManager* pRefMgr = pDoc->GetExternalRefManager(); + pRefMgr->convertToAbsName(aFile); + sal_uInt16 nFileId = pRefMgr->getExternalFileId(aFile); + if (!pRefMgr->getRangeNameTokens(nFileId, aName).get()) + // range name doesn't exist in the source document. + return false; + + const String* pRealName = pRefMgr->getRealRangeName(nFileId, aName); + aToken.SetExternalName(nFileId, pRealName ? *pRealName : aName); + pRawToken = aToken.Clone(); + return true; +} + +sal_Bool ScCompiler::IsDBRange( const String& rName ) +{ + sal_uInt16 n; + ScDBCollection* pDBColl = pDoc->GetDBCollection(); + if (pDBColl->SearchName( rName, n ) ) + { + ScDBData* pData = (*pDBColl)[n]; + ScRawToken aToken; + aToken.SetName(true, pData->GetIndex()); // DB range is always global. + aToken.eOp = ocDBArea; + pRawToken = aToken.Clone(); + return sal_True; + } + else + return false; +} + +sal_Bool ScCompiler::IsColRowName( const String& rName ) +{ + sal_Bool bInList = false; + sal_Bool bFound = false; + ScSingleRefData aRef; + String aName( rName ); + DeQuote( aName ); + SCTAB nThisTab = aPos.Tab(); + for ( short jThisTab = 1; jThisTab >= 0 && !bInList; jThisTab-- ) + { // first check ranges on this sheet, in case of duplicated names + for ( short jRow=0; jRow<2 && !bInList; jRow++ ) + { + ScRangePairList* pRL; + if ( !jRow ) + pRL = pDoc->GetColNameRanges(); + else + pRL = pDoc->GetRowNameRanges(); + for ( size_t iPair = 0, nPairs = pRL->size(); iPair < nPairs && !bInList; ++iPair ) + { + ScRangePair* pR = (*pRL)[iPair]; + const ScRange& rNameRange = pR->GetRange(0); + if ( jThisTab && !(rNameRange.aStart.Tab() <= nThisTab && + nThisTab <= rNameRange.aEnd.Tab()) ) + continue; // for + ScCellIterator aIter( pDoc, rNameRange ); + for ( ScBaseCell* pCell = aIter.GetFirst(); pCell && !bInList; + pCell = aIter.GetNext() ) + { + // Don't crash if cell (via CompileNameFormula) encounters + // a formula cell without code and + // HasStringData/Interpret/Compile is executed and all that + // recursive.. + // Furthermore, *this* cell won't be touched, since no RPN exists yet. + CellType eType = pCell->GetCellType(); + sal_Bool bOk = sal::static_int_cast<sal_Bool>( (eType == CELLTYPE_FORMULA ? + ((ScFormulaCell*)pCell)->GetCode()->GetCodeLen() > 0 + && ((ScFormulaCell*)pCell)->aPos != aPos // noIter + : sal_True ) ); + if ( bOk && pCell->HasStringData() ) + { + String aStr; + switch ( eType ) + { + case CELLTYPE_STRING: + ((ScStringCell*)pCell)->GetString( aStr ); + break; + case CELLTYPE_FORMULA: + ((ScFormulaCell*)pCell)->GetString( aStr ); + break; + case CELLTYPE_EDIT: + ((ScEditCell*)pCell)->GetString( aStr ); + break; + case CELLTYPE_NONE: + case CELLTYPE_VALUE: + case CELLTYPE_NOTE: + case CELLTYPE_SYMBOLS: +#if DBG_UTIL + case CELLTYPE_DESTROYED: +#endif + ; // nothing, prevent compiler warning + break; + } + if ( ScGlobal::GetpTransliteration()->isEqual( aStr, aName ) ) + { + aRef.InitFlags(); + aRef.nCol = aIter.GetCol(); + aRef.nRow = aIter.GetRow(); + aRef.nTab = aIter.GetTab(); + if ( !jRow ) + aRef.SetColRel( sal_True ); // ColName + else + aRef.SetRowRel( sal_True ); // RowName + aRef.CalcRelFromAbs( aPos ); + bInList = bFound = sal_True; + } + } + } + } + } + } + if ( !bInList && pDoc->GetDocOptions().IsLookUpColRowNames() ) + { // search in current sheet + long nDistance = 0, nMax = 0; + long nMyCol = (long) aPos.Col(); + long nMyRow = (long) aPos.Row(); + sal_Bool bTwo = false; + ScAddress aOne( 0, 0, aPos.Tab() ); + ScAddress aTwo( MAXCOL, MAXROW, aPos.Tab() ); + + ScAutoNameCache* pNameCache = pDoc->GetAutoNameCache(); + if ( pNameCache ) + { + // use GetNameOccurrences to collect all positions of aName on the sheet + // (only once), similar to the outer part of the loop in the "else" branch. + + const ScAutoNameAddresses& rAddresses = pNameCache->GetNameOccurrences( aName, aPos.Tab() ); + + // Loop through the found positions, similar to the inner part of the loop in the "else" branch. + // The order of addresses in the vector is the same as from ScCellIterator. + + ScAutoNameAddresses::const_iterator aEnd(rAddresses.end()); + for ( ScAutoNameAddresses::const_iterator aAdrIter(rAddresses.begin()); aAdrIter != aEnd; ++aAdrIter ) + { + ScAddress aAddress( *aAdrIter ); // cell address with an equal string + + if ( bFound ) + { // stop if everything else is further away + if ( nMax < (long)aAddress.Col() ) + break; // aIter + } + if ( aAddress != aPos ) + { + // same treatment as in isEqual case below + + SCCOL nCol = aAddress.Col(); + SCROW nRow = aAddress.Row(); + long nC = nMyCol - nCol; + long nR = nMyRow - nRow; + if ( bFound ) + { + long nD = nC * nC + nR * nR; + if ( nD < nDistance ) + { + if ( nC < 0 || nR < 0 ) + { // right or below + bTwo = sal_True; + aTwo.Set( nCol, nRow, aAddress.Tab() ); + nMax = Max( nMyCol + Abs( nC ), nMyRow + Abs( nR ) ); + nDistance = nD; + } + else if ( !(nRow < aOne.Row() && nMyRow >= (long)aOne.Row()) ) + { + // upper left, only if not further up than the + // current entry and nMyRow is below (CellIter + // runs column-wise) + bTwo = false; + aOne.Set( nCol, nRow, aAddress.Tab() ); + nMax = Max( nMyCol + nC, nMyRow + nR ); + nDistance = nD; + } + } + } + else + { + aOne.Set( nCol, nRow, aAddress.Tab() ); + nDistance = nC * nC + nR * nR; + nMax = Max( nMyCol + Abs( nC ), nMyRow + Abs( nR ) ); + } + bFound = sal_True; + } + } + } + else + { + ScCellIterator aIter( pDoc, ScRange( aOne, aTwo ) ); + for ( ScBaseCell* pCell = aIter.GetFirst(); pCell; pCell = aIter.GetNext() ) + { + if ( bFound ) + { // stop if everything else is further away + if ( nMax < (long)aIter.GetCol() ) + break; // aIter + } + CellType eType = pCell->GetCellType(); + sal_Bool bOk = sal::static_int_cast<sal_Bool>( (eType == CELLTYPE_FORMULA ? + ((ScFormulaCell*)pCell)->GetCode()->GetCodeLen() > 0 + && ((ScFormulaCell*)pCell)->aPos != aPos // noIter + : sal_True ) ); + if ( bOk && pCell->HasStringData() ) + { + String aStr; + switch ( eType ) + { + case CELLTYPE_STRING: + ((ScStringCell*)pCell)->GetString( aStr ); + break; + case CELLTYPE_FORMULA: + ((ScFormulaCell*)pCell)->GetString( aStr ); + break; + case CELLTYPE_EDIT: + ((ScEditCell*)pCell)->GetString( aStr ); + break; + case CELLTYPE_NONE: + case CELLTYPE_VALUE: + case CELLTYPE_NOTE: + case CELLTYPE_SYMBOLS: +#if DBG_UTIL + case CELLTYPE_DESTROYED: +#endif + ; // nothing, prevent compiler warning + break; + } + if ( ScGlobal::GetpTransliteration()->isEqual( aStr, aName ) ) + { + SCCOL nCol = aIter.GetCol(); + SCROW nRow = aIter.GetRow(); + long nC = nMyCol - nCol; + long nR = nMyRow - nRow; + if ( bFound ) + { + long nD = nC * nC + nR * nR; + if ( nD < nDistance ) + { + if ( nC < 0 || nR < 0 ) + { // right or below + bTwo = sal_True; + aTwo.Set( nCol, nRow, aIter.GetTab() ); + nMax = Max( nMyCol + Abs( nC ), nMyRow + Abs( nR ) ); + nDistance = nD; + } + else if ( !(nRow < aOne.Row() && nMyRow >= (long)aOne.Row()) ) + { + // upper left, only if not further up than the + // current entry and nMyRow is below (CellIter + // runs column-wise) + bTwo = false; + aOne.Set( nCol, nRow, aIter.GetTab() ); + nMax = Max( nMyCol + nC, nMyRow + nR ); + nDistance = nD; + } + } + } + else + { + aOne.Set( nCol, nRow, aIter.GetTab() ); + nDistance = nC * nC + nR * nR; + nMax = Max( nMyCol + Abs( nC ), nMyRow + Abs( nR ) ); + } + bFound = sal_True; + } + } + } + } + + if ( bFound ) + { + ScAddress aAdr; + if ( bTwo ) + { + if ( nMyCol >= (long)aOne.Col() && nMyRow >= (long)aOne.Row() ) + aAdr = aOne; // upper left takes precedence + else + { + if ( nMyCol < (long)aOne.Col() ) + { // two to the right + if ( nMyRow >= (long)aTwo.Row() ) + aAdr = aTwo; // directly right + else + aAdr = aOne; + } + else + { // two below or below and right, take the nearest + long nC1 = nMyCol - aOne.Col(); + long nR1 = nMyRow - aOne.Row(); + long nC2 = nMyCol - aTwo.Col(); + long nR2 = nMyRow - aTwo.Row(); + if ( nC1 * nC1 + nR1 * nR1 <= nC2 * nC2 + nR2 * nR2 ) + aAdr = aOne; + else + aAdr = aTwo; + } + } + } + else + aAdr = aOne; + aRef.InitAddress( aAdr ); + if ( (aRef.nRow != MAXROW && pDoc->HasStringData( + aRef.nCol, aRef.nRow + 1, aRef.nTab )) + || (aRef.nRow != 0 && pDoc->HasStringData( + aRef.nCol, aRef.nRow - 1, aRef.nTab )) ) + aRef.SetRowRel( sal_True ); // RowName + else + aRef.SetColRel( sal_True ); // ColName + aRef.CalcRelFromAbs( aPos ); + } + } + if ( bFound ) + { + ScRawToken aToken; + aToken.SetSingleReference( aRef ); + aToken.eOp = ocColRowName; + pRawToken = aToken.Clone(); + return sal_True; + } + else + return false; +} + +sal_Bool ScCompiler::IsBoolean( const String& rName ) +{ + OpCodeHashMap::const_iterator iLook( mxSymbols->getHashMap()->find( rName ) ); + if( iLook != mxSymbols->getHashMap()->end() && + ((*iLook).second == ocTrue || + (*iLook).second == ocFalse) ) + { + ScRawToken aToken; + aToken.SetOpCode( (*iLook).second ); + pRawToken = aToken.Clone(); + return sal_True; + } + else + return false; +} + +//--------------------------------------------------------------------------- + +void ScCompiler::AutoCorrectParsedSymbol() +{ + xub_StrLen nPos = aCorrectedSymbol.Len(); + if ( nPos ) + { + nPos--; + const sal_Unicode cQuote = '\"'; + const sal_Unicode cx = 'x'; + const sal_Unicode cX = 'X'; + sal_Unicode c1 = aCorrectedSymbol.GetChar( 0 ); + sal_Unicode c2 = aCorrectedSymbol.GetChar( nPos ); + sal_Unicode c2p = nPos > 0 ? aCorrectedSymbol.GetChar( nPos-1 ) : 0; + if ( c1 == cQuote && c2 != cQuote ) + { // "... + // What's not a word doesn't belong to it. + // Don't be pedantic: c < 128 should be sufficient here. + while ( nPos && ((aCorrectedSymbol.GetChar(nPos) < 128) && + ((GetCharTableFlags(aCorrectedSymbol.GetChar(nPos), aCorrectedSymbol.GetChar(nPos-1)) & + (SC_COMPILER_C_WORD | SC_COMPILER_C_CHAR_DONTCARE)) == 0)) ) + nPos--; + if ( nPos == MAXSTRLEN - 2 ) + aCorrectedSymbol.SetChar( nPos, cQuote ); // '"' the 255th character + else + aCorrectedSymbol.Insert( cQuote, nPos + 1 ); + bCorrected = sal_True; + } + else if ( c1 != cQuote && c2 == cQuote ) + { // ..." + aCorrectedSymbol.Insert( cQuote, 0 ); + bCorrected = sal_True; + } + else if ( nPos == 0 && (c1 == cx || c1 == cX) ) + { // x => * + aCorrectedSymbol = mxSymbols->getSymbol(ocMul); + bCorrected = sal_True; + } + else if ( (GetCharTableFlags( c1, 0 ) & SC_COMPILER_C_CHAR_VALUE) + && (GetCharTableFlags( c2, c2p ) & SC_COMPILER_C_CHAR_VALUE) ) + { + xub_StrLen nXcount; + if ( (nXcount = aCorrectedSymbol.GetTokenCount( cx )) > 1 ) + { // x => * + xub_StrLen nIndex = 0; + sal_Unicode c = mxSymbols->getSymbol(ocMul).GetChar(0); + while ( (nIndex = aCorrectedSymbol.SearchAndReplace( + cx, c, nIndex )) != STRING_NOTFOUND ) + nIndex++; + bCorrected = sal_True; + } + if ( (nXcount = aCorrectedSymbol.GetTokenCount( cX )) > 1 ) + { // X => * + xub_StrLen nIndex = 0; + sal_Unicode c = mxSymbols->getSymbol(ocMul).GetChar(0); + while ( (nIndex = aCorrectedSymbol.SearchAndReplace( + cX, c, nIndex )) != STRING_NOTFOUND ) + nIndex++; + bCorrected = sal_True; + } + } + else + { + String aSymbol( aCorrectedSymbol ); + String aDoc; + xub_StrLen nPosition; + if ( aSymbol.GetChar(0) == '\'' + && ((nPosition = aSymbol.SearchAscii( "'#" )) != STRING_NOTFOUND) ) + { // Split off 'Doc'#, may be d:\... or whatever + aDoc = aSymbol.Copy( 0, nPosition + 2 ); + aSymbol.Erase( 0, nPosition + 2 ); + } + xub_StrLen nRefs = aSymbol.GetTokenCount( ':' ); + sal_Bool bColons; + if ( nRefs > 2 ) + { // duplicated or too many ':'? B:2::C10 => B2:C10 + bColons = sal_True; + xub_StrLen nIndex = 0; + String aTmp1( aSymbol.GetToken( 0, ':', nIndex ) ); + xub_StrLen nLen1 = aTmp1.Len(); + String aSym, aTmp2; + sal_Bool bLastAlp, bNextNum; + bLastAlp = bNextNum = sal_True; + xub_StrLen nStrip = 0; + xub_StrLen nCount = nRefs; + for ( xub_StrLen j=1; j<nCount; j++ ) + { + aTmp2 = aSymbol.GetToken( 0, ':', nIndex ); + xub_StrLen nLen2 = aTmp2.Len(); + if ( nLen1 || nLen2 ) + { + if ( nLen1 ) + { + aSym += aTmp1; + bLastAlp = CharClass::isAsciiAlpha( aTmp1 ); + } + if ( nLen2 ) + { + bNextNum = CharClass::isAsciiNumeric( aTmp2 ); + if ( bLastAlp == bNextNum && nStrip < 1 ) + { + // Must be alternating number/string, only + // strip within a reference. + nRefs--; + nStrip++; + } + else + { + xub_StrLen nSymLen = aSym.Len(); + if ( nSymLen + && (aSym.GetChar( nSymLen - 1 ) != ':') ) + aSym += ':'; + nStrip = 0; + } + bLastAlp = !bNextNum; + } + else + { // :: + nRefs--; + if ( nLen1 ) + { // B10::C10 ? append ':' on next round + if ( !bLastAlp && !CharClass::isAsciiNumeric( aTmp1 ) ) + nStrip++; + } + bNextNum = !bLastAlp; + } + aTmp1 = aTmp2; + nLen1 = nLen2; + } + else + nRefs--; + } + aSymbol = aSym; + aSymbol += aTmp1; + } + else + bColons = false; + if ( nRefs && nRefs <= 2 ) + { // reference twisted? 4A => A4 etc. + String aTab[2], aRef[2]; + const ScAddress::Details aDetails( pConv->meConv, aPos ); + if ( nRefs == 2 ) + { + aRef[0] = aSymbol.GetToken( 0, ':' ); + aRef[1] = aSymbol.GetToken( 1, ':' ); + } + else + aRef[0] = aSymbol; + + sal_Bool bChanged = false; + sal_Bool bOk = sal_True; + sal_uInt16 nMask = SCA_VALID | SCA_VALID_COL | SCA_VALID_ROW; + for ( int j=0; j<nRefs; j++ ) + { + xub_StrLen nTmp = 0; + xub_StrLen nDotPos = STRING_NOTFOUND; + while ( (nTmp = aRef[j].Search( '.', nTmp )) != STRING_NOTFOUND ) + nDotPos = nTmp++; // the last one counts + if ( nDotPos != STRING_NOTFOUND ) + { + aTab[j] = aRef[j].Copy( 0, nDotPos + 1 ); // with '.' + aRef[j].Erase( 0, nDotPos + 1 ); + } + String aOld( aRef[j] ); + String aStr2; + const sal_Unicode* p = aRef[j].GetBuffer(); + while ( *p && CharClass::isAsciiNumeric( *p ) ) + aStr2 += *p++; + aRef[j] = String( p ); + aRef[j] += aStr2; + if ( bColons || aRef[j] != aOld ) + { + bChanged = sal_True; + ScAddress aAdr; + bOk &= ((aAdr.Parse( aRef[j], pDoc, aDetails ) & nMask) == nMask); + } + } + if ( bChanged && bOk ) + { + aCorrectedSymbol = aDoc; + aCorrectedSymbol += aTab[0]; + aCorrectedSymbol += aRef[0]; + if ( nRefs == 2 ) + { + aCorrectedSymbol += ':'; + aCorrectedSymbol += aTab[1]; + aCorrectedSymbol += aRef[1]; + } + bCorrected = sal_True; + } + } + } + } +} + +inline bool lcl_UpperAsciiOrI18n( String& rUpper, const String& rOrg, FormulaGrammar::Grammar eGrammar ) +{ + if (FormulaGrammar::isODFF( eGrammar )) + { + // ODFF has a defined set of English function names, avoid i18n + // overhead. + rUpper = rOrg; + rUpper.ToUpperAscii(); + return true; + } + else + { + rUpper = ScGlobal::pCharClass->upper( rOrg ); + return false; + } +} + +sal_Bool ScCompiler::NextNewToken( bool bInArray ) +{ + bool bAllowBooleans = bInArray; + xub_StrLen nSpaces = NextSymbol(bInArray); + + if (!cSymbol[0]) + return false; + + if( nSpaces ) + { + ScRawToken aToken; + aToken.SetOpCode( ocSpaces ); + aToken.sbyte.cByte = (sal_uInt8) ( nSpaces > 255 ? 255 : nSpaces ); + if( !static_cast<ScTokenArray*>(pArr)->AddRawToken( aToken ) ) + { + SetError(errCodeOverflow); + return false; + } + } + + // Short cut for references when reading ODF to speedup things. + if (mnPredetectedReference) + { + String aStr( cSymbol); + if (!IsPredetectedReference( aStr) && !IsExternalNamedRange( aStr)) + { + /* 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. */ + ScRawToken aToken; + aToken.SetString( aStr.GetBuffer() ); + aToken.NewOpCode( ocBad ); + pRawToken = aToken.Clone(); + } + return true; + } + + if ( (cSymbol[0] == '#' || cSymbol[0] == '$') && cSymbol[1] == 0 && + !bAutoCorrect ) + { // special case to speed up broken [$]#REF documents + /* FIXME: ISERROR(#REF!) would be valid and TRUE and the formula to + * be processed as usual. That would need some special treatment, + * also in NextSymbol() because of possible combinations of + * #REF!.#REF!#REF! parts. In case of reading ODF that is all + * handled by IsPredetectedReference(), this case here remains for + * manual/API input. */ + String aBad( aFormula.Copy( nSrcPos-1 ) ); + eLastOp = pArr->AddBad( aBad )->GetOpCode(); + return false; + } + + if( IsString() ) + return true; + + bool bMayBeFuncName; + bool bAsciiNonAlnum; // operators, separators, ... + if ( cSymbol[0] < 128 ) + { + bMayBeFuncName = CharClass::isAsciiAlpha( cSymbol[0] ); + bAsciiNonAlnum = !bMayBeFuncName && !CharClass::isAsciiDigit( cSymbol[0] ); + } + else + { + String aTmpStr( cSymbol[0] ); + bMayBeFuncName = ScGlobal::pCharClass->isLetter( aTmpStr, 0 ); + bAsciiNonAlnum = false; + } + if ( bMayBeFuncName ) + { + // a function name must be followed by a parenthesis + const sal_Unicode* p = aFormula.GetBuffer() + nSrcPos; + while( *p == ' ' ) + p++; + bMayBeFuncName = ( *p == '(' ); + } + + // Italian ARCTAN.2 resulted in #REF! => IsOpcode() before + // IsReference(). + + String aUpper; + + do + { + mbRewind = false; + const String aOrg( cSymbol ); + + if (bAsciiNonAlnum && IsOpCode( aOrg, bInArray )) + return true; + + aUpper.Erase(); + bool bAsciiUpper = false; + if (bMayBeFuncName) + { + bAsciiUpper = lcl_UpperAsciiOrI18n( aUpper, aOrg, meGrammar); + if (IsOpCode( aUpper, bInArray )) + return true; + } + + // Column 'DM' ("Deutsche Mark", German currency) couldn't be + // referred => IsReference() before IsValue(). + // Preserve case of file names in external references. + if (IsReference( aOrg )) + { + if (mbRewind) // Range operator, but no direct reference. + continue; // do; up to range operator. + return true; + } + + if (!aUpper.Len()) + bAsciiUpper = lcl_UpperAsciiOrI18n( aUpper, aOrg, meGrammar); + + // IsBoolean() before IsValue() to catch inline bools without the kludge + // for inline arrays. + if (bAllowBooleans && IsBoolean( aUpper )) + return true; + + if (IsValue( aUpper )) + return true; + + // User defined names and such do need i18n upper also in ODF. + if (bAsciiUpper) + aUpper = ScGlobal::pCharClass->upper( aOrg ); + + if (IsNamedRange( aUpper )) + return true; + // Preserve case of file names in external references. + if (IsExternalNamedRange( aOrg )) + return true; + if (IsDBRange( aUpper )) + return true; + if (IsColRowName( aUpper )) + return true; + if (bMayBeFuncName && IsMacro( aUpper )) + return true; + if (bMayBeFuncName && IsOpCode2( aUpper )) + return true; + + } while (mbRewind); + + if ( mbExtendedErrorDetection ) + { + // set an error and end compilation + SetError( errNoName ); + return false; + } + + // Provide single token information and continue. Do not set an error, that + // would prematurely end compilation. Simple unknown names are handled by + // the interpreter. + ScGlobal::pCharClass->toLower( aUpper ); + ScRawToken aToken; + aToken.SetString( aUpper.GetBuffer() ); + aToken.NewOpCode( ocBad ); + pRawToken = aToken.Clone(); + if ( bAutoCorrect ) + AutoCorrectParsedSymbol(); + return true; +} + +void ScCompiler::CreateStringFromXMLTokenArray( String& rFormula, String& rFormulaNmsp ) +{ + bool bExternal = GetGrammar() == FormulaGrammar::GRAM_EXTERNAL; + sal_uInt16 nExpectedCount = bExternal ? 2 : 1; + DBG_ASSERT( pArr->GetLen() == nExpectedCount, "ScCompiler::CreateStringFromXMLTokenArray - wrong number of tokens" ); + if( pArr->GetLen() == nExpectedCount ) + { + FormulaToken** ppTokens = pArr->GetArray(); + // string tokens expected, GetString() will assert if token type is wrong + rFormula = ppTokens[ 0 ]->GetString(); + if( bExternal ) + rFormulaNmsp = ppTokens[ 1 ]->GetString(); + } +} + +ScTokenArray* ScCompiler::CompileString( const String& rFormula ) +{ + OSL_ENSURE( meGrammar != FormulaGrammar::GRAM_EXTERNAL, "ScCompiler::CompileString - unexpected grammar GRAM_EXTERNAL" ); + if( meGrammar == FormulaGrammar::GRAM_EXTERNAL ) + SetGrammar( FormulaGrammar::GRAM_PODF ); + + ScTokenArray aArr; + pArr = &aArr; + aFormula = rFormula; + + aFormula.EraseLeadingChars(); + aFormula.EraseTrailingChars(); + nSrcPos = 0; + bCorrected = false; + if ( bAutoCorrect ) + { + aCorrectedFormula.Erase(); + aCorrectedSymbol.Erase(); + } + sal_uInt8 nForced = 0; // ==formula forces recalc even if cell is not visible + if( aFormula.GetChar(nSrcPos) == '=' ) + { + nSrcPos++; + nForced++; + if ( bAutoCorrect ) + aCorrectedFormula += '='; + } + if( aFormula.GetChar(nSrcPos) == '=' ) + { + nSrcPos++; + nForced++; + if ( bAutoCorrect ) + aCorrectedFormula += '='; + } + struct FunctionStack + { + OpCode eOp; + short nPar; + }; + // FunctionStack only used if PODF! + bool bPODF = FormulaGrammar::isPODF( meGrammar); + const size_t nAlloc = 512; + FunctionStack aFuncs[ nAlloc ]; + FunctionStack* pFunctionStack = (bPODF && rFormula.Len() > nAlloc ? + new FunctionStack[ rFormula.Len() ] : &aFuncs[0]); + pFunctionStack[0].eOp = ocNone; + pFunctionStack[0].nPar = 0; + size_t nFunction = 0; + short nBrackets = 0; + bool bInArray = false; + eLastOp = ocOpen; + while( NextNewToken( bInArray ) ) + { + const OpCode eOp = pRawToken->GetOpCode(); + switch (eOp) + { + case ocOpen: + { + ++nBrackets; + if (bPODF) + { + ++nFunction; + pFunctionStack[ nFunction ].eOp = eLastOp; + pFunctionStack[ nFunction ].nPar = 0; + } + } + break; + case ocClose: + { + if( !nBrackets ) + { + SetError( errPairExpected ); + if ( bAutoCorrect ) + { + bCorrected = sal_True; + aCorrectedSymbol.Erase(); + } + } + else + nBrackets--; + if (bPODF && nFunction) + --nFunction; + } + break; + case ocSep: + { + if (bPODF) + ++pFunctionStack[ nFunction ].nPar; + } + break; + case ocArrayOpen: + { + if( bInArray ) + SetError( errNestedArray ); + else + bInArray = true; + // Don't count following column separator as parameter separator. + if (bPODF) + { + ++nFunction; + pFunctionStack[ nFunction ].eOp = eOp; + pFunctionStack[ nFunction ].nPar = 0; + } + } + break; + case ocArrayClose: + { + if( bInArray ) + { + bInArray = false; + } + else + { + SetError( errPairExpected ); + if ( bAutoCorrect ) + { + bCorrected = sal_True; + aCorrectedSymbol.Erase(); + } + } + if (bPODF && nFunction) + --nFunction; + } + default: + break; + } + if( (eLastOp == ocSep || + eLastOp == ocArrayRowSep || + eLastOp == ocArrayColSep || + eLastOp == ocArrayOpen) && + (eOp == ocSep || + eOp == ocClose || + eOp == ocArrayRowSep || + eOp == ocArrayColSep || + eOp == ocArrayClose) ) + { + // FIXME: should we check for known functions with optional empty + // args so the correction dialog can do better? + if ( !static_cast<ScTokenArray*>(pArr)->Add( new FormulaMissingToken ) ) + { + SetError(errCodeOverflow); break; + } + } + if (bPODF) + { + /* TODO: for now this is the only PODF adapter. If there were more, + * factor this out. */ + // Insert ADDRESS() new empty parameter 4 if there is a 4th, now to be 5th. + if (eOp == ocSep && + pFunctionStack[ nFunction ].eOp == ocAddress && + pFunctionStack[ nFunction ].nPar == 3) + { + if (!static_cast<ScTokenArray*>(pArr)->Add( new FormulaToken( svSep,ocSep)) || + !static_cast<ScTokenArray*>(pArr)->Add( new FormulaDoubleToken( 1.0))) + { + SetError(errCodeOverflow); break; + } + ++pFunctionStack[ nFunction ].nPar; + } + } + FormulaToken* pNewToken = static_cast<ScTokenArray*>(pArr)->Add( pRawToken->CreateToken()); + if (!pNewToken) + { + SetError(errCodeOverflow); break; + } + else if (eLastOp == ocRange && pNewToken->GetOpCode() == ocPush && + pNewToken->GetType() == svSingleRef) + static_cast<ScTokenArray*>(pArr)->MergeRangeReference( aPos); + eLastOp = pRawToken->GetOpCode(); + if ( bAutoCorrect ) + aCorrectedFormula += aCorrectedSymbol; + } + if ( mbCloseBrackets ) + { + if( bInArray ) + { + FormulaByteToken aToken( ocArrayClose ); + if( !pArr->AddToken( aToken ) ) + { + SetError(errCodeOverflow); + } + else if ( bAutoCorrect ) + aCorrectedFormula += mxSymbols->getSymbol(ocArrayClose); + } + + FormulaByteToken aToken( ocClose ); + while( nBrackets-- ) + { + if( !pArr->AddToken( aToken ) ) + { + SetError(errCodeOverflow); break; + } + if ( bAutoCorrect ) + aCorrectedFormula += mxSymbols->getSymbol(ocClose); + } + } + if ( nForced >= 2 ) + pArr->SetRecalcModeForced(); + + if (pFunctionStack != &aFuncs[0]) + delete [] pFunctionStack; + + // remember pArr, in case a subsequent CompileTokenArray() is executed. + ScTokenArray* pNew = new ScTokenArray( aArr ); + pArr = pNew; + return pNew; +} + + +ScTokenArray* ScCompiler::CompileString( const String& rFormula, const String& rFormulaNmsp ) +{ + DBG_ASSERT( (GetGrammar() == FormulaGrammar::GRAM_EXTERNAL) || (rFormulaNmsp.Len() == 0), + "ScCompiler::CompileString - unexpected formula namespace for internal grammar" ); + if( GetGrammar() == FormulaGrammar::GRAM_EXTERNAL ) try + { + ScFormulaParserPool& rParserPool = pDoc->GetFormulaParserPool(); + uno::Reference< sheet::XFormulaParser > xParser( rParserPool.getFormulaParser( rFormulaNmsp ), uno::UNO_SET_THROW ); + table::CellAddress aReferencePos; + ScUnoConversion::FillApiAddress( aReferencePos, aPos ); + uno::Sequence< sheet::FormulaToken > aTokenSeq = xParser->parseFormula( rFormula, aReferencePos ); + ScTokenArray aTokenArray; + if( ScTokenConversion::ConvertToTokenArray( *pDoc, aTokenArray, aTokenSeq ) ) + { + // remember pArr, in case a subsequent CompileTokenArray() is executed. + ScTokenArray* pNew = new ScTokenArray( aTokenArray ); + pArr = pNew; + return pNew; + } + } + catch( uno::Exception& ) + { + } + // no success - fallback to some internal grammar and hope the best + return CompileString( rFormula ); +} + + +sal_Bool ScCompiler::HandleRange() +{ + ScRangeData* pRangeData = NULL; + + bool bGlobal = pToken->GetByte(); + if (bGlobal) + // global named range. + pRangeData = pDoc->GetRangeName()->findByIndex( pToken->GetIndex() ); + else + { + // sheet local named range. + ScRangeName* pRN = pDoc->GetRangeName(aPos.Tab()); + if (pRN) + pRangeData = pRN->findByIndex( pToken->GetIndex() ); + } + + if (pRangeData) + { + sal_uInt16 nErr = pRangeData->GetErrCode(); + if( nErr ) + SetError( errNoName ); + else if ( !bCompileForFAP ) + { + ScTokenArray* pNew; + // put named formula into parentheses. + // But only if there aren't any yet, parenthetical + // ocSep doesn't work, e.g. SUM((...;...)) + // or if not directly between ocSep/parenthesis, + // e.g. SUM(...;(...;...)) no, SUM(...;(...)*3) yes, + // in short: if it isn't a self-contained expression. + FormulaToken* p1 = pArr->PeekPrevNoSpaces(); + FormulaToken* p2 = pArr->PeekNextNoSpaces(); + OpCode eOp1 = (p1 ? p1->GetOpCode() : static_cast<OpCode>( ocSep ) ); + OpCode eOp2 = (p2 ? p2->GetOpCode() : static_cast<OpCode>( ocSep ) ); + sal_Bool bBorder1 = (eOp1 == ocSep || eOp1 == ocOpen); + sal_Bool bBorder2 = (eOp2 == ocSep || eOp2 == ocClose); + sal_Bool bAddPair = !(bBorder1 && bBorder2); + if ( bAddPair ) + { + pNew = new ScTokenArray(); + pNew->AddOpCode( ocClose ); + PushTokenArray( pNew, sal_True ); + pNew->Reset(); + } + pNew = pRangeData->GetCode()->Clone(); + PushTokenArray( pNew, sal_True ); + if( pRangeData->HasReferences() ) + { + SetRelNameReference(); + MoveRelWrap(pRangeData->GetMaxCol(), pRangeData->GetMaxRow()); + } + pNew->Reset(); + if ( bAddPair ) + { + pNew = new ScTokenArray(); + pNew->AddOpCode( ocOpen ); + PushTokenArray( pNew, sal_True ); + pNew->Reset(); + } + return GetToken(); + } + } + else + SetError(errNoName); + return sal_True; +} +// ----------------------------------------------------------------------------- +sal_Bool ScCompiler::HandleExternalReference(const FormulaToken& _aToken) +{ + // Handle external range names. + switch (_aToken.GetType()) + { + case svExternalSingleRef: + case svExternalDoubleRef: + pArr->IncrementRefs(); + break; + case svExternalName: + { + ScExternalRefManager* pRefMgr = pDoc->GetExternalRefManager(); + const String* pFile = pRefMgr->getExternalFileName(_aToken.GetIndex()); + if (!pFile) + { + SetError(errNoName); + return true; + } + + const String& rName = _aToken.GetString(); + ScExternalRefCache::TokenArrayRef xNew = pRefMgr->getRangeNameTokens( + _aToken.GetIndex(), rName, &aPos); + + if (!xNew) + { + SetError(errNoName); + return true; + } + + ScTokenArray* pNew = xNew->Clone(); + PushTokenArray( pNew, true); + if (pNew->GetNextReference() != NULL) + { + SetRelNameReference(); + MoveRelWrap(MAXCOL, MAXROW); + } + pNew->Reset(); + return GetToken(); + } + default: + OSL_FAIL("Wrong type for external reference!"); + return false; + } + return sal_True; +} + + +//--------------------------------------------------------------------------- + + +//--------------------------------------------------------------------------- +// Append token to RPN code +//--------------------------------------------------------------------------- + + +//----------------------------------------------------------------------------- + +//--------------------------------------------------------------------------- +// RPN creation by recursion +//--------------------------------------------------------------------------- + + + +//----------------------------------------------------------------------------- + +sal_Bool ScCompiler::HasModifiedRange() +{ + pArr->Reset(); + for ( FormulaToken* t = pArr->Next(); t; t = pArr->Next() ) + { + OpCode eOpCode = t->GetOpCode(); + if ( eOpCode == ocName ) + { + ScRangeData* pRangeData = pDoc->GetRangeName()->findByIndex(t->GetIndex()); + + if (pRangeData && pRangeData->IsModified()) + return sal_True; + } + else if ( eOpCode == ocDBArea ) + { + ScDBData* pDBData = pDoc->GetDBCollection()->FindIndex(t->GetIndex()); + + if (pDBData && pDBData->IsModified()) + return sal_True; + } + } + return false; +} + + +//--------------------------------------------------------------------------- + +template< typename T, typename S > +S lcl_adjval( S& n, T pos, T max, sal_Bool bRel ) +{ + max++; + if( bRel ) + n = sal::static_int_cast<S>( n + pos ); + if( n < 0 ) + n = sal::static_int_cast<S>( n + max ); + else if( n >= max ) + n = sal::static_int_cast<S>( n - max ); + if( bRel ) + n = sal::static_int_cast<S>( n - pos ); + return n; +} + +// reference of named range with relative references + +void ScCompiler::SetRelNameReference() +{ + pArr->Reset(); + for( ScToken* t = static_cast<ScToken*>(pArr->GetNextReference()); t; + t = static_cast<ScToken*>(pArr->GetNextReference()) ) + { + ScSingleRefData& rRef1 = t->GetSingleRef(); + if ( rRef1.IsColRel() || rRef1.IsRowRel() || rRef1.IsTabRel() ) + rRef1.SetRelName( sal_True ); + if ( t->GetType() == svDoubleRef ) + { + ScSingleRefData& rRef2 = t->GetDoubleRef().Ref2; + if ( rRef2.IsColRel() || rRef2.IsRowRel() || rRef2.IsTabRel() ) + rRef2.SetRelName( sal_True ); + } + } +} + +// Wrap-adjust relative references of a RangeName to current position, +// don't call for other token arrays! +void ScCompiler::MoveRelWrap( SCCOL nMaxCol, SCROW nMaxRow ) +{ + pArr->Reset(); + for( ScToken* t = static_cast<ScToken*>(pArr->GetNextReference()); t; + t = static_cast<ScToken*>(pArr->GetNextReference()) ) + { + if ( t->GetType() == svSingleRef || t->GetType() == svExternalSingleRef ) + ScRefUpdate::MoveRelWrap( pDoc, aPos, nMaxCol, nMaxRow, SingleDoubleRefModifier( t->GetSingleRef() ).Ref() ); + else + ScRefUpdate::MoveRelWrap( pDoc, aPos, nMaxCol, nMaxRow, t->GetDoubleRef() ); + } +} + +// Wrap-adjust relative references of a RangeName to current position, +// don't call for other token arrays! +void ScCompiler::MoveRelWrap( ScTokenArray& rArr, ScDocument* pDoc, const ScAddress& rPos, + SCCOL nMaxCol, SCROW nMaxRow ) +{ + rArr.Reset(); + for( ScToken* t = static_cast<ScToken*>(rArr.GetNextReference()); t; + t = static_cast<ScToken*>(rArr.GetNextReference()) ) + { + if ( t->GetType() == svSingleRef || t->GetType() == svExternalSingleRef ) + ScRefUpdate::MoveRelWrap( pDoc, rPos, nMaxCol, nMaxRow, SingleDoubleRefModifier( t->GetSingleRef() ).Ref() ); + else + ScRefUpdate::MoveRelWrap( pDoc, rPos, nMaxCol, nMaxRow, t->GetDoubleRef() ); + } +} + +ScRangeData* ScCompiler::UpdateReference(UpdateRefMode eUpdateRefMode, + const ScAddress& rOldPos, const ScRange& r, + SCsCOL nDx, SCsROW nDy, SCsTAB nDz, + bool& rChanged, bool& rRefSizeChanged ) +{ + rChanged = rRefSizeChanged = false; + if ( eUpdateRefMode == URM_COPY ) + { // Normally nothing has to be done here since RelRefs are used, also + // SharedFormulas don't need any special handling, except if they + // wrapped around sheet borders. + // But ColRowName tokens pointing to a ColRow header which was + // copied along with this formula need to be updated to point to the + // copied header instead of the old position's new intersection. + ScToken* t; + pArr->Reset(); + while( (t = static_cast<ScToken*>(pArr->GetNextColRowName())) != NULL ) + { + ScSingleRefData& rRef = t->GetSingleRef(); + rRef.CalcAbsIfRel( rOldPos ); + ScAddress aNewRef( rRef.nCol + nDx, rRef.nRow + nDy, rRef.nTab + nDz ); + if ( r.In( aNewRef ) ) + { // yes, this is URM_MOVE + if ( ScRefUpdate::Update( pDoc, URM_MOVE, aPos, + r, nDx, nDy, nDz, + SingleDoubleRefModifier( rRef ).Ref() ) + != UR_NOTHING + ) + rChanged = true; + } + } + // Check for SharedFormulas. + ScRangeData* pRangeData = NULL; + pArr->Reset(); + for( FormulaToken* j = pArr->GetNextName(); j && !pRangeData; + j = pArr->GetNextName() ) + { + if( j->GetOpCode() == ocName ) + { + ScRangeData* pName = pDoc->GetRangeName()->findByIndex( j->GetIndex() ); + if (pName && pName->HasType(RT_SHARED)) + pRangeData = pName; + } + } + // Check SharedFormulas for wraps. + if (pRangeData) + { + ScRangeData* pName = pRangeData; + pRangeData = NULL; + pArr->Reset(); + for( t = static_cast<ScToken*>(pArr->GetNextReferenceRPN()); t && !pRangeData; + t = static_cast<ScToken*>(pArr->GetNextReferenceRPN()) ) + { + sal_Bool bRelName = (t->GetType() == svSingleRef ? + t->GetSingleRef().IsRelName() : + (t->GetDoubleRef().Ref1.IsRelName() || + t->GetDoubleRef().Ref2.IsRelName())); + if (bRelName) + { + t->CalcAbsIfRel( rOldPos); + sal_Bool bValid = (t->GetType() == svSingleRef ? + t->GetSingleRef().Valid() : + t->GetDoubleRef().Valid()); + // If the reference isn't valid, copying the formula + // wrapped it. Replace SharedFormula. + if (!bValid) + { + pRangeData = pName; + rChanged = true; + } + } + } + } + return pRangeData; + } + else + { +/* + * Set SC_PRESERVE_SHARED_FORMULAS_IF_POSSIBLE to 1 if we wanted to preserve as + * many shared formulas as possible instead of replacing them with direct code. + * Note that this may produce shared formula usage Excel doesn't understand, + * which would have to be adapted for in the export filter. Advisable as a long + * term goal, since it could decrease memory footprint. + */ +#define SC_PRESERVE_SHARED_FORMULAS_IF_POSSIBLE 0 + ScRangeData* pRangeData = NULL; + ScToken* t; + pArr->Reset(); + while( (t = static_cast<ScToken*>(pArr->GetNextReferenceOrName())) != NULL ) + { + if( t->GetOpCode() == ocName ) + { + ScRangeData* pName = pDoc->GetRangeName()->findByIndex( t->GetIndex() ); + if (pName && pName->HasType(RT_SHAREDMOD)) + { + pRangeData = pName; // maybe need a replacement of shared with own code +#if ! SC_PRESERVE_SHARED_FORMULAS_IF_POSSIBLE + rChanged = true; +#endif + } + } + else if( t->GetType() != svIndex ) // it may be a DB area!!! + { + t->CalcAbsIfRel( rOldPos ); + switch (t->GetType()) + { + case svExternalSingleRef: + case svExternalDoubleRef: + // External references never change their positioning + // nor point to parts that will be removed or expanded. + // In fact, calling ScRefUpdate::Update() for URM_MOVE + // may have negative side effects. Simply adapt + // relative references to the new position. + t->CalcRelFromAbs( aPos); + break; + case svSingleRef: + { + if ( ScRefUpdate::Update( pDoc, eUpdateRefMode, + aPos, r, nDx, nDy, nDz, + SingleDoubleRefModifier( + t->GetSingleRef()).Ref()) + != UR_NOTHING) + rChanged = true; + } + break; + default: + { + ScComplexRefData& rRef = t->GetDoubleRef(); + SCCOL nCols = rRef.Ref2.nCol - rRef.Ref1.nCol; + SCROW nRows = rRef.Ref2.nRow - rRef.Ref1.nRow; + SCTAB nTabs = rRef.Ref2.nTab - rRef.Ref1.nTab; + if ( ScRefUpdate::Update( pDoc, eUpdateRefMode, + aPos, r, nDx, nDy, nDz, + t->GetDoubleRef()) != UR_NOTHING) + { + rChanged = true; + if (rRef.Ref2.nCol - rRef.Ref1.nCol != nCols || + rRef.Ref2.nRow - rRef.Ref1.nRow != nRows || + rRef.Ref2.nTab - rRef.Ref1.nTab != nTabs) + rRefSizeChanged = true; + } + } + } + } + } +#if SC_PRESERVE_SHARED_FORMULAS_IF_POSSIBLE + sal_Bool bEasyShared, bPosInRange; + if ( !pRangeData ) + bEasyShared = bPosInRange = false; + else + { + bEasyShared = sal_True; + bPosInRange = r.In( eUpdateRefMode == URM_MOVE ? aPos : rOldPos ); + } +#endif + pArr->Reset(); + while ( (t = static_cast<ScToken*>(pArr->GetNextReferenceRPN())) != NULL ) + { + if ( t->GetRef() != 1 ) + { +#if SC_PRESERVE_SHARED_FORMULAS_IF_POSSIBLE + bEasyShared = false; +#endif + } + else + { // if nRefCnt>1 it's already updated in token code + if ( t->GetType() == svSingleRef ) + { + ScSingleRefData& rRef = t->GetSingleRef(); + SingleDoubleRefModifier aMod( rRef ); + if ( rRef.IsRelName() ) + { + ScRefUpdate::MoveRelWrap( pDoc, aPos, MAXCOL, MAXROW, aMod.Ref() ); + rChanged = true; + } + else + { + aMod.Ref().CalcAbsIfRel( rOldPos ); + if ( ScRefUpdate::Update( pDoc, eUpdateRefMode, aPos, + r, nDx, nDy, nDz, aMod.Ref() ) + != UR_NOTHING + ) + rChanged = true; + } +#if SC_PRESERVE_SHARED_FORMULAS_IF_POSSIBLE + if ( bEasyShared ) + { + const ScSingleRefData& rSRD = aMod.Ref().Ref1; + ScAddress aRef( rSRD.nCol, rSRD.nRow, rSRD.nTab ); + if ( r.In( aRef ) != bPosInRange ) + bEasyShared = false; + } +#endif + } + else + { + ScComplexRefData& rRef = t->GetDoubleRef(); + SCCOL nCols = rRef.Ref2.nCol - rRef.Ref1.nCol; + SCROW nRows = rRef.Ref2.nRow - rRef.Ref1.nRow; + SCTAB nTabs = rRef.Ref2.nTab - rRef.Ref1.nTab; + if ( rRef.Ref1.IsRelName() || rRef.Ref2.IsRelName() ) + { + ScRefUpdate::MoveRelWrap( pDoc, aPos, MAXCOL, MAXROW, rRef ); + rChanged = true; + } + else + { + if ( ScRefUpdate::Update( pDoc, eUpdateRefMode, aPos, + r, nDx, nDy, nDz, rRef ) + != UR_NOTHING + ) + { + rChanged = true; + if (rRef.Ref2.nCol - rRef.Ref1.nCol != nCols || + rRef.Ref2.nRow - rRef.Ref1.nRow != nRows || + rRef.Ref2.nTab - rRef.Ref1.nTab != nTabs) + { + rRefSizeChanged = true; +#if SC_PRESERVE_SHARED_FORMULAS_IF_POSSIBLE + bEasyShared = false; +#endif + } + } + } +#if SC_PRESERVE_SHARED_FORMULAS_IF_POSSIBLE + if ( bEasyShared ) + { + ScRange aRef( rRef.Ref1.nCol, rRef.Ref1.nRow, + rRef.Ref1.nTab, rRef.Ref2.nCol, rRef.Ref2.nRow, + rRef.Ref2.nTab ); + if ( r.In( aRef ) != bPosInRange ) + bEasyShared = false; + } +#endif + } + } + } +#if SC_PRESERVE_SHARED_FORMULAS_IF_POSSIBLE + if ( pRangeData ) + { + if ( bEasyShared ) + pRangeData = 0; + else + rChanged = true; + } +#endif +#undef SC_PRESERVE_SHARED_FORMULAS_IF_POSSIBLE + return pRangeData; + } +} + +sal_Bool ScCompiler::UpdateNameReference(UpdateRefMode eUpdateRefMode, + const ScRange& r, + SCsCOL nDx, SCsROW nDy, SCsTAB nDz, + sal_Bool& rChanged, sal_Bool bSharedFormula) +{ + sal_Bool bRelRef = false; // set if relative reference + rChanged = false; + pArr->Reset(); + ScToken* t; + while ( (t = static_cast<ScToken*>(pArr->GetNextReference())) != NULL ) + { + SingleDoubleRefModifier aMod( *t ); + ScComplexRefData& rRef = aMod.Ref(); + bRelRef = rRef.Ref1.IsColRel() || rRef.Ref1.IsRowRel() || + rRef.Ref1.IsTabRel(); + if (!bRelRef && t->GetType() == svDoubleRef) + bRelRef = rRef.Ref2.IsColRel() || rRef.Ref2.IsRowRel() || + rRef.Ref2.IsTabRel(); + bool bUpdate = !rRef.Ref1.IsColRel() || !rRef.Ref1.IsRowRel() || + !rRef.Ref1.IsTabRel(); + if (!bUpdate && t->GetType() == svDoubleRef) + bUpdate = !rRef.Ref2.IsColRel() || !rRef.Ref2.IsRowRel() || + !rRef.Ref2.IsTabRel(); + if (!bSharedFormula) + { + // We cannot update names with sheet-relative references, they may + // be used on other sheets as well and the resulting reference + // would be wrong. This is a dilemma if col/row would need to be + // updated for the current usage. + // TODO: seems the only way out of this would be to not allow + // relative sheet references and have sheet-local names that can be + // copied along with sheets. + bUpdate = bUpdate && !rRef.Ref1.IsTabRel() && !rRef.Ref2.IsTabRel(); + } + if (bUpdate) + { + rRef.CalcAbsIfRel( aPos); + if (ScRefUpdate::Update( pDoc, eUpdateRefMode, aPos, r, + nDx, nDy, nDz, rRef, ScRefUpdate::ABSOLUTE) + != UR_NOTHING ) + rChanged = sal_True; + } + } + return bRelRef; +} + + +void ScCompiler::UpdateSharedFormulaReference( UpdateRefMode eUpdateRefMode, + const ScAddress& rOldPos, const ScRange& r, + SCsCOL nDx, SCsROW nDy, SCsTAB nDz ) +{ + if ( eUpdateRefMode == URM_COPY ) + return ; + else + { + ScToken* t; + pArr->Reset(); + while ( (t = static_cast<ScToken*>(pArr->GetNextReference())) != NULL ) + { + if( t->GetType() != svIndex ) // it may be a DB area!!! + { + t->CalcAbsIfRel( rOldPos ); + // Absolute references have been already adjusted in the named + // shared formula itself prior to breaking the shared formula + // and calling this function. Don't readjust them again. + SingleDoubleRefModifier aMod( *t ); + ScComplexRefData& rRef = aMod.Ref(); + ScComplexRefData aBkp = rRef; + ScRefUpdate::Update( pDoc, eUpdateRefMode, aPos, + r, nDx, nDy, nDz, rRef ); + // restore absolute parts + if ( !aBkp.Ref1.IsColRel() ) + { + rRef.Ref1.nCol = aBkp.Ref1.nCol; + rRef.Ref1.nRelCol = aBkp.Ref1.nRelCol; + rRef.Ref1.SetColDeleted( aBkp.Ref1.IsColDeleted() ); + } + if ( !aBkp.Ref1.IsRowRel() ) + { + rRef.Ref1.nRow = aBkp.Ref1.nRow; + rRef.Ref1.nRelRow = aBkp.Ref1.nRelRow; + rRef.Ref1.SetRowDeleted( aBkp.Ref1.IsRowDeleted() ); + } + if ( !aBkp.Ref1.IsTabRel() ) + { + rRef.Ref1.nTab = aBkp.Ref1.nTab; + rRef.Ref1.nRelTab = aBkp.Ref1.nRelTab; + rRef.Ref1.SetTabDeleted( aBkp.Ref1.IsTabDeleted() ); + } + if ( t->GetType() == svDoubleRef ) + { + if ( !aBkp.Ref2.IsColRel() ) + { + rRef.Ref2.nCol = aBkp.Ref2.nCol; + rRef.Ref2.nRelCol = aBkp.Ref2.nRelCol; + rRef.Ref2.SetColDeleted( aBkp.Ref2.IsColDeleted() ); + } + if ( !aBkp.Ref2.IsRowRel() ) + { + rRef.Ref2.nRow = aBkp.Ref2.nRow; + rRef.Ref2.nRelRow = aBkp.Ref2.nRelRow; + rRef.Ref2.SetRowDeleted( aBkp.Ref2.IsRowDeleted() ); + } + if ( !aBkp.Ref2.IsTabRel() ) + { + rRef.Ref2.nTab = aBkp.Ref2.nTab; + rRef.Ref2.nRelTab = aBkp.Ref2.nRelTab; + rRef.Ref2.SetTabDeleted( aBkp.Ref2.IsTabDeleted() ); + } + } + } + } + } +} + + +ScRangeData* ScCompiler::UpdateInsertTab( SCTAB nTable, sal_Bool bIsName ) +{ + ScRangeData* pRangeData = NULL; + SCTAB nPosTab = aPos.Tab(); // _after_ incremented! + SCTAB nOldPosTab = ((nPosTab > nTable) ? (nPosTab - 1) : nPosTab); + sal_Bool bIsRel = false; + ScToken* t; + pArr->Reset(); + if (bIsName) + t = static_cast<ScToken*>(pArr->GetNextReference()); + else + t = static_cast<ScToken*>(pArr->GetNextReferenceOrName()); + while( t ) + { + if( t->GetOpCode() == ocName ) + { + if (!bIsName) + { + ScRangeData* pName = pDoc->GetRangeName()->findByIndex(t->GetIndex()); + if (pName && pName->HasType(RT_SHAREDMOD)) + pRangeData = pName; + } + } + else if( t->GetType() != svIndex ) // it may be a DB area!!! + { + if ( !(bIsName && t->GetSingleRef().IsTabRel()) ) + { // of names only adjust absolute references + ScSingleRefData& rRef = t->GetSingleRef(); + if ( rRef.IsTabRel() ) + { + rRef.nTab = rRef.nRelTab + nOldPosTab; + if ( rRef.nTab < 0 ) + rRef.nTab = sal::static_int_cast<SCsTAB>( rRef.nTab + pDoc->GetTableCount() ); // was a wrap + } + if (nTable <= rRef.nTab) + ++rRef.nTab; + rRef.nRelTab = rRef.nTab - nPosTab; + } + else + bIsRel = sal_True; + if ( t->GetType() == svDoubleRef ) + { + if ( !(bIsName && t->GetDoubleRef().Ref2.IsTabRel()) ) + { // of names only adjust absolute references + ScSingleRefData& rRef = t->GetDoubleRef().Ref2; + if ( rRef.IsTabRel() ) + { + rRef.nTab = rRef.nRelTab + nOldPosTab; + if ( rRef.nTab < 0 ) + rRef.nTab = sal::static_int_cast<SCsTAB>( rRef.nTab + pDoc->GetTableCount() ); // was a wrap + } + if (nTable <= rRef.nTab) + ++rRef.nTab; + rRef.nRelTab = rRef.nTab - nPosTab; + } + else + bIsRel = sal_True; + } + if ( bIsName && bIsRel ) + pRangeData = (ScRangeData*) this; // not dereferenced in rangenam + } + if (bIsName) + t = static_cast<ScToken*>(pArr->GetNextReference()); + else + t = static_cast<ScToken*>(pArr->GetNextReferenceOrName()); + } + if ( !bIsName ) + { + pArr->Reset(); + while ( (t = static_cast<ScToken*>(pArr->GetNextReferenceRPN())) != NULL ) + { + if ( t->GetRef() == 1 ) + { + ScSingleRefData& rRef1 = t->GetSingleRef(); + if ( !(rRef1.IsRelName() && rRef1.IsTabRel()) ) + { // of names only adjust absolute references + if ( rRef1.IsTabRel() ) + { + rRef1.nTab = rRef1.nRelTab + nOldPosTab; + if ( rRef1.nTab < 0 ) + rRef1.nTab = sal::static_int_cast<SCsTAB>( rRef1.nTab + pDoc->GetTableCount() ); // was a wrap + } + if (nTable <= rRef1.nTab) + ++rRef1.nTab; + rRef1.nRelTab = rRef1.nTab - nPosTab; + } + if ( t->GetType() == svDoubleRef ) + { + ScSingleRefData& rRef2 = t->GetDoubleRef().Ref2; + if ( !(rRef2.IsRelName() && rRef2.IsTabRel()) ) + { // of names only adjust absolute references + if ( rRef2.IsTabRel() ) + { + rRef2.nTab = rRef2.nRelTab + nOldPosTab; + if ( rRef2.nTab < 0 ) + rRef2.nTab = sal::static_int_cast<SCsTAB>( rRef2.nTab + pDoc->GetTableCount() ); // was a wrap + } + if (nTable <= rRef2.nTab) + ++rRef2.nTab; + rRef2.nRelTab = rRef2.nTab - nPosTab; + } + } + } + } + } + return pRangeData; +} + +ScRangeData* ScCompiler::UpdateDeleteTab(SCTAB nTable, sal_Bool /* bIsMove */, sal_Bool bIsName, + sal_Bool& rChanged) +{ + ScRangeData* pRangeData = NULL; + SCTAB nTab, nTab2; + SCTAB nPosTab = aPos.Tab(); // _after_ decremented! + SCTAB nOldPosTab = ((nPosTab >= nTable) ? (nPosTab + 1) : nPosTab); + rChanged = false; + sal_Bool bIsRel = false; + ScToken* t; + pArr->Reset(); + if (bIsName) + t = static_cast<ScToken*>(pArr->GetNextReference()); + else + t = static_cast<ScToken*>(pArr->GetNextReferenceOrName()); + while( t ) + { + if( t->GetOpCode() == ocName ) + { + if (!bIsName) + { + ScRangeData* pName = pDoc->GetRangeName()->findByIndex(t->GetIndex()); + if (pName && pName->HasType(RT_SHAREDMOD)) + pRangeData = pName; + } + rChanged = sal_True; + } + else if( t->GetType() != svIndex ) // it may be a DB area!!! + { + if ( !(bIsName && t->GetSingleRef().IsTabRel()) ) + { // of names only adjust absolute references + ScSingleRefData& rRef = t->GetSingleRef(); + if ( rRef.IsTabRel() ) + nTab = rRef.nTab = rRef.nRelTab + nOldPosTab; + else + nTab = rRef.nTab; + if ( nTable < nTab ) + { + rRef.nTab = nTab - 1; + rChanged = sal_True; + } + else if ( nTable == nTab ) + { + if ( t->GetType() == svDoubleRef ) + { + ScSingleRefData& rRef2 = t->GetDoubleRef().Ref2; + if ( rRef2.IsTabRel() ) + nTab2 = rRef2.nRelTab + nOldPosTab; + else + nTab2 = rRef2.nTab; + if ( nTab == nTab2 + || (nTab+1) >= pDoc->GetTableCount() ) + { + rRef.nTab = MAXTAB+1; + rRef.SetTabDeleted( sal_True ); + } + // else: nTab later points to what's nTable+1 now + // => area shrunk + } + else + { + rRef.nTab = MAXTAB+1; + rRef.SetTabDeleted( sal_True ); + } + rChanged = sal_True; + } + rRef.nRelTab = rRef.nTab - nPosTab; + } + else + bIsRel = sal_True; + if ( t->GetType() == svDoubleRef ) + { + if ( !(bIsName && t->GetDoubleRef().Ref2.IsTabRel()) ) + { // of names only adjust absolute references + ScSingleRefData& rRef = t->GetDoubleRef().Ref2; + if ( rRef.IsTabRel() ) + nTab = rRef.nTab = rRef.nRelTab + nOldPosTab; + else + nTab = rRef.nTab; + if ( nTable < nTab ) + { + rRef.nTab = nTab - 1; + rChanged = sal_True; + } + else if ( nTable == nTab ) + { + if ( !t->GetDoubleRef().Ref1.IsTabDeleted() ) + rRef.nTab = nTab - 1; // shrink area + else + { + rRef.nTab = MAXTAB+1; + rRef.SetTabDeleted( sal_True ); + } + rChanged = sal_True; + } + rRef.nRelTab = rRef.nTab - nPosTab; + } + else + bIsRel = sal_True; + } + if ( bIsName && bIsRel ) + pRangeData = (ScRangeData*) this; // not dereferenced in rangenam + } + if (bIsName) + t = static_cast<ScToken*>(pArr->GetNextReference()); + else + t = static_cast<ScToken*>(pArr->GetNextReferenceOrName()); + } + if ( !bIsName ) + { + pArr->Reset(); + while ( (t = static_cast<ScToken*>(pArr->GetNextReferenceRPN())) != NULL ) + { + if ( t->GetRef() == 1 ) + { + ScSingleRefData& rRef1 = t->GetSingleRef(); + if ( !(rRef1.IsRelName() && rRef1.IsTabRel()) ) + { // of names only adjust absolute references + if ( rRef1.IsTabRel() ) + nTab = rRef1.nTab = rRef1.nRelTab + nOldPosTab; + else + nTab = rRef1.nTab; + if ( nTable < nTab ) + { + rRef1.nTab = nTab - 1; + rChanged = sal_True; + } + else if ( nTable == nTab ) + { + if ( t->GetType() == svDoubleRef ) + { + ScSingleRefData& rRef2 = t->GetDoubleRef().Ref2; + if ( rRef2.IsTabRel() ) + nTab2 = rRef2.nRelTab + nOldPosTab; + else + nTab2 = rRef2.nTab; + if ( nTab == nTab2 + || (nTab+1) >= pDoc->GetTableCount() ) + { + rRef1.nTab = MAXTAB+1; + rRef1.SetTabDeleted( sal_True ); + } + // else: nTab later points to what's nTable+1 now + // => area shrunk + } + else + { + rRef1.nTab = MAXTAB+1; + rRef1.SetTabDeleted( sal_True ); + } + rChanged = sal_True; + } + rRef1.nRelTab = rRef1.nTab - nPosTab; + } + if ( t->GetType() == svDoubleRef ) + { + ScSingleRefData& rRef2 = t->GetDoubleRef().Ref2; + if ( !(rRef2.IsRelName() && rRef2.IsTabRel()) ) + { // of names only adjust absolute references + if ( rRef2.IsTabRel() ) + nTab = rRef2.nTab = rRef2.nRelTab + nOldPosTab; + else + nTab = rRef2.nTab; + if ( nTable < nTab ) + { + rRef2.nTab = nTab - 1; + rChanged = sal_True; + } + else if ( nTable == nTab ) + { + if ( !rRef1.IsTabDeleted() ) + rRef2.nTab = nTab - 1; // shrink area + else + { + rRef2.nTab = MAXTAB+1; + rRef2.SetTabDeleted( sal_True ); + } + rChanged = sal_True; + } + rRef2.nRelTab = rRef2.nTab - nPosTab; + } + } + } + } + } + return pRangeData; +} + +// aPos.Tab() must be already adjusted! +ScRangeData* ScCompiler::UpdateMoveTab( SCTAB nOldTab, SCTAB nNewTab, + sal_Bool bIsName ) +{ + ScRangeData* pRangeData = NULL; + SCsTAB nTab; + + SCTAB nStart, nEnd; + short nDir; // direction in which others move + if ( nOldTab < nNewTab ) + { + nDir = -1; + nStart = nOldTab; + nEnd = nNewTab; + } + else + { + nDir = 1; + nStart = nNewTab; + nEnd = nOldTab; + } + SCTAB nPosTab = aPos.Tab(); // current sheet + SCTAB nOldPosTab; // previously it was this one + if ( nPosTab == nNewTab ) + nOldPosTab = nOldTab; // look, it's me! + else if ( nPosTab < nStart || nEnd < nPosTab ) + nOldPosTab = nPosTab; // wasn't moved + else + nOldPosTab = nPosTab - nDir; // moved by one + + sal_Bool bIsRel = false; + ScToken* t; + pArr->Reset(); + if (bIsName) + t = static_cast<ScToken*>(pArr->GetNextReference()); + else + t = static_cast<ScToken*>(pArr->GetNextReferenceOrName()); + while( t ) + { + if( t->GetOpCode() == ocName ) + { + if (!bIsName) + { + ScRangeData* pName = pDoc->GetRangeName()->findByIndex(t->GetIndex()); + if (pName && pName->HasType(RT_SHAREDMOD)) + pRangeData = pName; + } + } + else if( t->GetType() != svIndex ) // it may be a DB area!!! + { + ScSingleRefData& rRef1 = t->GetSingleRef(); + if ( !(bIsName && rRef1.IsTabRel()) ) + { // of names only adjust absolute references + if ( rRef1.IsTabRel() ) + nTab = rRef1.nRelTab + nOldPosTab; + else + nTab = rRef1.nTab; + if ( nTab == nOldTab ) + rRef1.nTab = nNewTab; + else if ( nStart <= nTab && nTab <= nEnd ) + rRef1.nTab = nTab + nDir; + rRef1.nRelTab = rRef1.nTab - nPosTab; + } + else + bIsRel = sal_True; + if ( t->GetType() == svDoubleRef ) + { + ScSingleRefData& rRef2 = t->GetDoubleRef().Ref2; + if ( !(bIsName && rRef2.IsTabRel()) ) + { // of names only adjust absolute references + if ( rRef2.IsTabRel() ) + nTab = rRef2.nRelTab + nOldPosTab; + else + nTab = rRef2.nTab; + if ( nTab == nOldTab ) + rRef2.nTab = nNewTab; + else if ( nStart <= nTab && nTab <= nEnd ) + rRef2.nTab = nTab + nDir; + rRef2.nRelTab = rRef2.nTab - nPosTab; + } + else + bIsRel = sal_True; + SCsTAB nTab1, nTab2; + if ( rRef1.IsTabRel() ) + nTab1 = rRef1.nRelTab + nPosTab; + else + nTab1 = rRef1.nTab; + if ( rRef2.IsTabRel() ) + nTab2 = rRef2.nRelTab + nPosTab; + else + nTab2 = rRef1.nTab; + if ( nTab2 < nTab1 ) + { // PutInOrder + rRef1.nTab = nTab2; + rRef2.nTab = nTab1; + rRef1.nRelTab = rRef1.nTab - nPosTab; + rRef2.nRelTab = rRef2.nTab - nPosTab; + } + } + if ( bIsName && bIsRel ) + pRangeData = (ScRangeData*) this; // not dereferenced in rangenam + } + if (bIsName) + t = static_cast<ScToken*>(pArr->GetNextReference()); + else + t = static_cast<ScToken*>(pArr->GetNextReferenceOrName()); + } + if ( !bIsName ) + { + SCsTAB nMaxTabMod = (SCsTAB) pDoc->GetTableCount(); + pArr->Reset(); + while ( (t = static_cast<ScToken*>(pArr->GetNextReferenceRPN())) != NULL ) + { + if ( t->GetRef() == 1 ) + { + ScSingleRefData& rRef1 = t->GetSingleRef(); + if ( rRef1.IsRelName() && rRef1.IsTabRel() ) + { // possibly wrap RelName, like lcl_MoveItWrap in refupdat.cxx + nTab = rRef1.nRelTab + nPosTab; + if ( nTab < 0 ) + nTab = sal::static_int_cast<SCsTAB>( nTab + nMaxTabMod ); + else if ( nTab > nMaxTab ) + nTab = sal::static_int_cast<SCsTAB>( nTab - nMaxTabMod ); + rRef1.nRelTab = nTab - nPosTab; + } + else + { + if ( rRef1.IsTabRel() ) + nTab = rRef1.nRelTab + nOldPosTab; + else + nTab = rRef1.nTab; + if ( nTab == nOldTab ) + rRef1.nTab = nNewTab; + else if ( nStart <= nTab && nTab <= nEnd ) + rRef1.nTab = nTab + nDir; + rRef1.nRelTab = rRef1.nTab - nPosTab; + } + if( t->GetType() == svDoubleRef ) + { + ScSingleRefData& rRef2 = t->GetDoubleRef().Ref2; + if ( rRef2.IsRelName() && rRef2.IsTabRel() ) + { // possibly wrap RelName, like lcl_MoveItWrap in refupdat.cxx + nTab = rRef2.nRelTab + nPosTab; + if ( nTab < 0 ) + nTab = sal::static_int_cast<SCsTAB>( nTab + nMaxTabMod ); + else if ( nTab > nMaxTab ) + nTab = sal::static_int_cast<SCsTAB>( nTab - nMaxTabMod ); + rRef2.nRelTab = nTab - nPosTab; + } + else + { + if ( rRef2.IsTabRel() ) + nTab = rRef2.nRelTab + nOldPosTab; + else + nTab = rRef2.nTab; + if ( nTab == nOldTab ) + rRef2.nTab = nNewTab; + else if ( nStart <= nTab && nTab <= nEnd ) + rRef2.nTab = nTab + nDir; + rRef2.nRelTab = rRef2.nTab - nPosTab; + } + SCsTAB nTab1, nTab2; + if ( rRef1.IsTabRel() ) + nTab1 = rRef1.nRelTab + nPosTab; + else + nTab1 = rRef1.nTab; + if ( rRef2.IsTabRel() ) + nTab2 = rRef2.nRelTab + nPosTab; + else + nTab2 = rRef1.nTab; + if ( nTab2 < nTab1 ) + { // PutInOrder + rRef1.nTab = nTab2; + rRef2.nTab = nTab1; + rRef1.nRelTab = rRef1.nTab - nPosTab; + rRef2.nRelTab = rRef2.nTab - nPosTab; + } + } + } + } + } + return pRangeData; +} + + +void ScCompiler::CreateStringFromExternal(rtl::OUStringBuffer& rBuffer, FormulaToken* pTokenP) +{ + FormulaToken* t = pTokenP; + ScExternalRefManager* pRefMgr = pDoc->GetExternalRefManager(); + switch (t->GetType()) + { + case svExternalName: + { + const String *pStr = pRefMgr->getExternalFileName(t->GetIndex()); + String aFileName = pStr ? *pStr : ScGlobal::GetRscString(STR_NO_NAME_REF); + rBuffer.append(pConv->makeExternalNameStr( aFileName, t->GetString())); + } + break; + case svExternalSingleRef: + pConv->makeExternalRefStr( + rBuffer, *this, t->GetIndex(), t->GetString(), static_cast<ScToken*>(t)->GetSingleRef(), pRefMgr); + break; + case svExternalDoubleRef: + pConv->makeExternalRefStr( + rBuffer, *this, t->GetIndex(), t->GetString(), static_cast<ScToken*>(t)->GetDoubleRef(), pRefMgr); + break; + default: + // warning, not error, otherwise we may end up with a never + // ending message box loop if this was the cursor cell to be redrawn. + DBG_WARNING("ScCompiler::CreateStringFromToken: unknown type of ocExternalRef"); + } +} + +void ScCompiler::CreateStringFromMatrix( rtl::OUStringBuffer& rBuffer, + FormulaToken* pTokenP) +{ + const ScMatrix* pMatrix = static_cast<ScToken*>(pTokenP)->GetMatrix(); + SCSIZE nC, nMaxC, nR, nMaxR; + + pMatrix->GetDimensions( nMaxC, nMaxR); + + rBuffer.append( mxSymbols->getSymbol(ocArrayOpen) ); + for( nR = 0 ; nR < nMaxR ; nR++) + { + if( nR > 0) + { + rBuffer.append( mxSymbols->getSymbol(ocArrayRowSep) ); + } + + for( nC = 0 ; nC < nMaxC ; nC++) + { + if( nC > 0) + { + rBuffer.append( mxSymbols->getSymbol(ocArrayColSep) ); + } + + if( pMatrix->IsValue( nC, nR ) ) + { + if (pMatrix->IsBoolean(nC, nR)) + AppendBoolean(rBuffer, pMatrix->GetDouble(nC, nR) != 0.0); + else + { + sal_uInt16 nErr = pMatrix->GetError(nC, nR); + if (nErr) + rBuffer.append(ScGlobal::GetErrorString(nErr)); + else + AppendDouble(rBuffer, pMatrix->GetDouble(nC, nR)); + } + } + else if( pMatrix->IsEmpty( nC, nR ) ) + ; + else if( pMatrix->IsString( nC, nR ) ) + AppendString( rBuffer, pMatrix->GetString( nC, nR ) ); + } + } + rBuffer.append( mxSymbols->getSymbol(ocArrayClose) ); +} + +void ScCompiler::CreateStringFromSingleRef(rtl::OUStringBuffer& rBuffer,FormulaToken* _pTokenP) +{ + const OpCode eOp = _pTokenP->GetOpCode(); + ScSingleRefData& rRef = static_cast<ScToken*>(_pTokenP)->GetSingleRef(); + ScComplexRefData aRef; + aRef.Ref1 = aRef.Ref2 = rRef; + if ( eOp == ocColRowName ) + { + rRef.CalcAbsIfRel( aPos ); + if ( pDoc->HasStringData( rRef.nCol, rRef.nRow, rRef.nTab ) ) + { + String aStr; + pDoc->GetString( rRef.nCol, rRef.nRow, rRef.nTab, aStr ); + EnQuote( aStr ); + rBuffer.append(aStr); + } + else + { + rBuffer.append(ScGlobal::GetRscString(STR_NO_NAME_REF)); + pConv->MakeRefStr (rBuffer, *this, aRef, sal_True ); + } + } + else + pConv->MakeRefStr( rBuffer, *this, aRef, sal_True ); +} +// ----------------------------------------------------------------------------- +void ScCompiler::CreateStringFromDoubleRef(rtl::OUStringBuffer& rBuffer,FormulaToken* _pTokenP) +{ + pConv->MakeRefStr( rBuffer, *this, static_cast<ScToken*>(_pTokenP)->GetDoubleRef(), false ); +} +// ----------------------------------------------------------------------------- +void ScCompiler::CreateStringFromIndex(rtl::OUStringBuffer& rBuffer,FormulaToken* _pTokenP) +{ + const OpCode eOp = _pTokenP->GetOpCode(); + rtl::OUStringBuffer aBuffer; + switch ( eOp ) + { + case ocName: + { + bool bGlobal = _pTokenP->GetByte(); + ScRangeData* pData = NULL; + if (bGlobal) + // global named range. + pData = pDoc->GetRangeName()->findByIndex(_pTokenP->GetIndex()); + else + { + // sheet local named range. + ScRangeName* pRN = pDoc->GetRangeName(aPos.Tab()); + if (pRN) + pData = pRN->findByIndex(_pTokenP->GetIndex()); + } + + if (pData) + { + if (pData->HasType(RT_SHARED)) + pData->UpdateSymbol( aBuffer, aPos, GetGrammar()); + else + aBuffer.append(pData->GetName()); + } + } + break; + case ocDBArea: + { + ScDBData* pDBData = pDoc->GetDBCollection()->FindIndex(_pTokenP->GetIndex()); + if (pDBData) + aBuffer.append(pDBData->GetName()); + } + break; + default: + ; // nothing + } + if ( aBuffer.getLength() ) + rBuffer.append(aBuffer); + else + rBuffer.append(ScGlobal::GetRscString(STR_NO_NAME_REF)); +} +// ----------------------------------------------------------------------------- +void ScCompiler::LocalizeString( String& rName ) +{ + ScGlobal::GetAddInCollection()->LocalizeString( rName ); +} +// ----------------------------------------------------------------------------- +sal_Bool ScCompiler::IsImportingXML() const +{ + return pDoc->IsImportingXML(); +} + +// Put quotes around string if non-alphanumeric characters are contained, +// quote characters contained within are escaped by '\\'. +sal_Bool ScCompiler::EnQuote( String& rStr ) +{ + sal_Int32 nType = ScGlobal::pCharClass->getStringType( rStr, 0, rStr.Len() ); + if ( !CharClass::isNumericType( nType ) + && CharClass::isAlphaNumericType( nType ) ) + return false; + + xub_StrLen nPos = 0; + while ( (nPos = rStr.Search( '\'', nPos)) != STRING_NOTFOUND ) + { + rStr.Insert( '\\', nPos ); + nPos += 2; + } + rStr.Insert( '\'', 0 ); + rStr += '\''; + return sal_True; +} + +sal_Unicode ScCompiler::GetNativeAddressSymbol( Convention::SpecialSymbolType eType ) const +{ + return pConv->getSpecialSymbol(eType); +} + +void ScCompiler::fillAddInToken(::std::vector< ::com::sun::star::sheet::FormulaOpCodeMapEntry >& _rVec,bool _bIsEnglish) const +{ + // All known AddIn functions. + sheet::FormulaOpCodeMapEntry aEntry; + aEntry.Token.OpCode = ocExternal; + + ScUnoAddInCollection* pColl = ScGlobal::GetAddInCollection(); + const long nCount = pColl->GetFuncCount(); + for (long i=0; i < nCount; ++i) + { + const ScUnoAddInFuncData* pFuncData = pColl->GetFuncData(i); + if (pFuncData) + { + if ( _bIsEnglish ) + { + String aName; + if (pFuncData->GetExcelName( LANGUAGE_ENGLISH_US, aName)) + aEntry.Name = aName; + else + aEntry.Name = pFuncData->GetUpperName(); + } + else + aEntry.Name = pFuncData->GetUpperLocal(); + aEntry.Token.Data <<= ::rtl::OUString( pFuncData->GetOriginalName()); + _rVec.push_back( aEntry); + } + } + // FIXME: what about those old non-UNO AddIns? +} +// ----------------------------------------------------------------------------- +sal_Bool ScCompiler::HandleSingleRef() +{ + ScSingleRefData& rRef = static_cast<ScToken*>(pToken.get())->GetSingleRef(); + rRef.CalcAbsIfRel( aPos ); + if ( !rRef.Valid() ) + { + SetError( errNoRef ); + return sal_True; + } + SCCOL nCol = rRef.nCol; + SCROW nRow = rRef.nRow; + SCTAB nTab = rRef.nTab; + ScAddress aLook( nCol, nRow, nTab ); + sal_Bool bColName = rRef.IsColRel(); + SCCOL nMyCol = aPos.Col(); + SCROW nMyRow = aPos.Row(); + sal_Bool bInList = false; + sal_Bool bValidName = false; + ScRangePairList* pRL = (bColName ? + pDoc->GetColNameRanges() : pDoc->GetRowNameRanges()); + ScRange aRange; + for ( size_t i = 0, nPairs = pRL->size(); i < nPairs; ++i ) + { + ScRangePair* pR = (*pRL)[i]; + if ( pR->GetRange(0).In( aLook ) ) + { + bInList = bValidName = sal_True; + aRange = pR->GetRange(1); + if ( bColName ) + { + aRange.aStart.SetCol( nCol ); + aRange.aEnd.SetCol( nCol ); + } + else + { + aRange.aStart.SetRow( nRow ); + aRange.aEnd.SetRow( nRow ); + } + break; // for + } + } + if ( !bInList && pDoc->GetDocOptions().IsLookUpColRowNames() ) + { // automagically or created by copying and NamePos isn't in list + sal_Bool bString = pDoc->HasStringData( nCol, nRow, nTab ); + if ( !bString && !pDoc->GetCell( aLook ) ) + bString = sal_True; // empty cell is ok + if ( bString ) + { //! coresponds with ScInterpreter::ScColRowNameAuto() + bValidName = sal_True; + if ( bColName ) + { // ColName + SCROW nStartRow = nRow + 1; + if ( nStartRow > MAXROW ) + nStartRow = MAXROW; + SCROW nMaxRow = MAXROW; + if ( nMyCol == nCol ) + { // formula cell in same column + if ( nMyRow == nStartRow ) + { // take remainder under name cell + nStartRow++; + if ( nStartRow > MAXROW ) + nStartRow = MAXROW; + } + else if ( nMyRow > nStartRow ) + { // from name cell down to formula cell + nMaxRow = nMyRow - 1; + } + } + for ( size_t i = 0, nPairs = pRL->size(); i < nPairs; ++i ) + { // next defined ColNameRange below limits row + ScRangePair* pR = (*pRL)[i]; + const ScRange& rRange = pR->GetRange(1); + if ( rRange.aStart.Col() <= nCol && nCol <= rRange.aEnd.Col() ) + { // identical column range + SCROW nTmp = rRange.aStart.Row(); + if ( nStartRow < nTmp && nTmp <= nMaxRow ) + nMaxRow = nTmp - 1; + } + } + aRange.aStart.Set( nCol, nStartRow, nTab ); + aRange.aEnd.Set( nCol, nMaxRow, nTab ); + } + else + { // RowName + SCCOL nStartCol = nCol + 1; + if ( nStartCol > MAXCOL ) + nStartCol = MAXCOL; + SCCOL nMaxCol = MAXCOL; + if ( nMyRow == nRow ) + { // formula cell in same row + if ( nMyCol == nStartCol ) + { // take remainder right from name cell + nStartCol++; + if ( nStartCol > MAXCOL ) + nStartCol = MAXCOL; + } + else if ( nMyCol > nStartCol ) + { // from name cell right to formula cell + nMaxCol = nMyCol - 1; + } + } + for ( size_t i = 0, nPairs = pRL->size(); i < nPairs; ++i ) + { // next defined RowNameRange to the right limits column + ScRangePair* pR = (*pRL)[i]; + const ScRange& rRange = pR->GetRange(1); + if ( rRange.aStart.Row() <= nRow && nRow <= rRange.aEnd.Row() ) + { // identical row range + SCCOL nTmp = rRange.aStart.Col(); + if ( nStartCol < nTmp && nTmp <= nMaxCol ) + nMaxCol = nTmp - 1; + } + } + aRange.aStart.Set( nStartCol, nRow, nTab ); + aRange.aEnd.Set( nMaxCol, nRow, nTab ); + } + } + } + if ( bValidName ) + { + // And now the magic to distinguish between a range and a single + // cell thereof, which is picked position-dependent of the formula + // cell. If a direct neighbor is a binary operator (ocAdd, ...) a + // SingleRef matching the column/row of the formula cell is + // generated. A ocColRowName or ocIntersect as a neighbor results + // in a range. Special case: if label is valid for a single cell, a + // position independent SingleRef is generated. + sal_Bool bSingle = (aRange.aStart == aRange.aEnd); + sal_Bool bFound; + if ( bSingle ) + bFound = sal_True; + else + { + FormulaToken* p1 = pArr->PeekPrevNoSpaces(); + FormulaToken* p2 = pArr->PeekNextNoSpaces(); + // begin/end of a formula => single + OpCode eOp1 = p1 ? p1->GetOpCode() : static_cast<OpCode>( ocAdd ); + OpCode eOp2 = p2 ? p2->GetOpCode() : static_cast<OpCode>( ocAdd ); + if ( eOp1 != ocColRowName && eOp1 != ocIntersect + && eOp2 != ocColRowName && eOp2 != ocIntersect ) + { + if ( (SC_OPCODE_START_BIN_OP <= eOp1 && eOp1 < SC_OPCODE_STOP_BIN_OP) || + (SC_OPCODE_START_BIN_OP <= eOp2 && eOp2 < SC_OPCODE_STOP_BIN_OP)) + bSingle = sal_True; + } + if ( bSingle ) + { // column and/or row must match range + if ( bColName ) + { + bFound = (aRange.aStart.Row() <= nMyRow + && nMyRow <= aRange.aEnd.Row()); + if ( bFound ) + aRange.aStart.SetRow( nMyRow ); + } + else + { + bFound = (aRange.aStart.Col() <= nMyCol + && nMyCol <= aRange.aEnd.Col()); + if ( bFound ) + aRange.aStart.SetCol( nMyCol ); + } + } + else + bFound = sal_True; + } + if ( !bFound ) + SetError(errNoRef); + else if ( !bCompileForFAP ) + { + ScTokenArray* pNew = new ScTokenArray(); + if ( bSingle ) + { + ScSingleRefData aRefData; + aRefData.InitAddress( aRange.aStart ); + if ( bColName ) + aRefData.SetColRel( sal_True ); + else + aRefData.SetRowRel( sal_True ); + aRefData.CalcRelFromAbs( aPos ); + pNew->AddSingleReference( aRefData ); + } + else + { + ScComplexRefData aRefData; + aRefData.InitRange( aRange ); + if ( bColName ) + { + aRefData.Ref1.SetColRel( sal_True ); + aRefData.Ref2.SetColRel( sal_True ); + } + else + { + aRefData.Ref1.SetRowRel( sal_True ); + aRefData.Ref2.SetRowRel( sal_True ); + } + aRefData.CalcRelFromAbs( aPos ); + if ( bInList ) + pNew->AddDoubleReference( aRefData ); + else + { // automagically + pNew->Add( new ScDoubleRefToken( aRefData, ocColRowNameAuto ) ); + } + } + PushTokenArray( pNew, sal_True ); + pNew->Reset(); + return GetToken(); + } + } + else + SetError(errNoName); + return sal_True; +} +// ----------------------------------------------------------------------------- +sal_Bool ScCompiler::HandleDbData() +{ + ScDBData* pDBData = pDoc->GetDBCollection()->FindIndex( pToken->GetIndex() ); + if ( !pDBData ) + SetError(errNoName); + else if ( !bCompileForFAP ) + { + ScComplexRefData aRefData; + aRefData.InitFlags(); + pDBData->GetArea( (SCTAB&) aRefData.Ref1.nTab, + (SCCOL&) aRefData.Ref1.nCol, + (SCROW&) aRefData.Ref1.nRow, + (SCCOL&) aRefData.Ref2.nCol, + (SCROW&) aRefData.Ref2.nRow); + aRefData.Ref2.nTab = aRefData.Ref1.nTab; + aRefData.CalcRelFromAbs( aPos ); + ScTokenArray* pNew = new ScTokenArray(); + pNew->AddDoubleReference( aRefData ); + PushTokenArray( pNew, sal_True ); + pNew->Reset(); + return GetToken(); + } + return sal_True; +} + +// ----------------------------------------------------------------------------- +FormulaTokenRef ScCompiler::ExtendRangeReference( FormulaToken & rTok1, FormulaToken & rTok2, bool bReuseDoubleRef ) +{ + return ScToken::ExtendRangeReference( rTok1, rTok2, aPos,bReuseDoubleRef ); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ |