/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ /* * This file is part of the LibreOffice project. * * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. * * This file incorporates work covered by the following license notice: * * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed * with this work for additional information regarding copyright * ownership. The ASF licenses this file to you under the Apache * License, Version 2.0 (the "License"); you may not use this file * except in compliance with the License. You may obtain a copy of * the License at http://www.apache.org/licenses/LICENSE-2.0 . */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include using namespace css; const ScAddress::Details ScAddress::detailsOOOa1( formula::FormulaGrammar::CONV_OOO, 0, 0 ); ScAddress::Details::Details ( const ScDocument& rDoc, const ScAddress& rAddr ) : eConv( rDoc.GetAddressConvention() ), nRow( rAddr.Row() ), nCol( rAddr.Col() ) {} namespace { const sal_Unicode* parseQuotedNameWithBuffer( const sal_Unicode* pStart, const sal_Unicode* p, OUString& rName ) { // The current character must be on the 2nd quote. // Push all the characters up to the current, but skip the very first // character which is the opening quote. OUStringBuffer aBuf(std::u16string_view(pStart+1, p-pStart-1)); ++p; // Skip the 2nd quote. sal_Unicode cPrev = 0; for (; *p; ++p) { if (*p == '\'') { if (cPrev == '\'') { // double single-quote equals one single quote. aBuf.append(*p); cPrev = 0; continue; } } else if (cPrev == '\'') { // We are past the closing quote. We're done! rName = aBuf.makeStringAndClear(); return p; } else aBuf.append(*p); cPrev = *p; } return pStart; } /** * Parse from the opening single quote to the closing single quote. Inside * the quotes, a single quote character is encoded by double single-quote * characters. * * @param p pointer to the first character to begin parsing. * @param rName (reference) parsed name within the quotes. If the name is * empty, either the parsing failed or it's an empty quote. * * @return pointer to the character immediately after the closing single * quote. */ const sal_Unicode* parseQuotedName( const sal_Unicode* p, OUString& rName ) { if (*p != '\'') return p; const sal_Unicode* pStart = p; sal_Unicode cPrev = 0; for (++p; *p; ++p) { if (*p == '\'') { if (cPrev == '\'') { // double single-quote equals one single quote. return parseQuotedNameWithBuffer(pStart, p, rName); } } else if (cPrev == '\'') { // We are past the closing quote. We're done! Skip the opening // and closing quotes. rName = OUString(pStart+1, p - pStart-2); return p; } cPrev = *p; } rName.clear(); return pStart; } } static sal_Int64 sal_Unicode_strtol ( const sal_Unicode* p, const sal_Unicode** pEnd ) { sal_Int64 accum = 0, prev = 0; bool is_neg = false; if( *p == '-' ) { is_neg = true; p++; } else if( *p == '+' ) p++; while (rtl::isAsciiDigit( *p )) { accum = accum * 10 + *p - '0'; if( accum < prev ) { *pEnd = nullptr; return 0; } prev = accum; p++; } *pEnd = p; return is_neg ? -accum : accum; } static const sal_Unicode* lcl_eatWhiteSpace( const sal_Unicode* p ) { if ( p ) { while( *p == ' ' ) ++p; } return p; } // Compare ignore case ASCII. static bool lcl_isString( const sal_Unicode* p1, const OUString& rStr ) { const size_t n = rStr.getLength(); if (!n) return false; const sal_Unicode* p2 = rStr.getStr(); for (size_t i=0; imaTabName is set to rEndTabName. @returns if pExtInfo is already filled and rExternDocName does not result in the identical file ID. Else . */ static bool lcl_ScRange_External_TabSpan( ScRange & rRange, ScRefFlags & rFlags, ScAddress::ExternalInfo* pExtInfo, const OUString & rExternDocName, const OUString & rStartTabName, const OUString & rEndTabName, const ScDocument& rDoc ) { if (rExternDocName.isEmpty()) return !pExtInfo || !pExtInfo->mbExternal; ScExternalRefManager* pRefMgr = rDoc.GetExternalRefManager(); if (pRefMgr->isOwnDocument( rExternDocName)) { // This is an internal document. Get the sheet positions from the // ScDocument instance. if (!rStartTabName.isEmpty()) { SCTAB nTab; if (rDoc.GetTable(rStartTabName, nTab)) rRange.aStart.SetTab(nTab); } if (!rEndTabName.isEmpty()) { SCTAB nTab; if (rDoc.GetTable(rEndTabName, nTab)) rRange.aEnd.SetTab(nTab); } return !pExtInfo || !pExtInfo->mbExternal; } sal_uInt16 nFileId = pRefMgr->getExternalFileId( rExternDocName); if (pExtInfo) { if (pExtInfo->mbExternal) { if (pExtInfo->mnFileId != nFileId) return false; } else { pExtInfo->mbExternal = true; pExtInfo->maTabName = rStartTabName; pExtInfo->mnFileId = nFileId; } } if (rEndTabName.isEmpty() || rStartTabName == rEndTabName) { rRange.aEnd.SetTab( rRange.aStart.Tab()); return true; } SCTAB nSpan = pRefMgr->getCachedTabSpan( nFileId, rStartTabName, rEndTabName); if (nSpan == -1) rFlags &= ~ScRefFlags(ScRefFlags::TAB_VALID | ScRefFlags::TAB2_VALID); else if (nSpan == 0) rFlags &= ~ScRefFlags::TAB2_VALID; else if (nSpan >= 1) rRange.aEnd.SetTab( rRange.aStart.Tab() + nSpan - 1); else // (nSpan < -1) { rRange.aEnd.SetTab( rRange.aStart.Tab() - nSpan - 1); if (pExtInfo) pExtInfo->maTabName = rEndTabName; } return true; } /** Returns NULL if the string should be a sheet name, but is invalid. Returns a pointer to the first character after the sheet name, if there was any, else pointer to start. @param pMsoxlQuoteStop Starting _within_ a quoted name, but still may be 3D; quoted name stops at pMsoxlQuoteStop */ static const sal_Unicode * lcl_XL_ParseSheetRef( const sal_Unicode* start, OUString& rExternTabName, bool bAllow3D, const sal_Unicode* pMsoxlQuoteStop, const OUString* pErrRef ) { OUString aTabName; const sal_Unicode *p = start; // XL only seems to use single quotes for sheet names. if (pMsoxlQuoteStop) { const sal_Unicode* pCurrentStart = p; while (p < pMsoxlQuoteStop) { if (*p == '\'') { // We pre-analyzed the quoting, no checks needed here. if (*++p == '\'') { aTabName += std::u16string_view( pCurrentStart, sal::static_int_cast( p - pCurrentStart)); pCurrentStart = ++p; } } else if (*p == ':') { break; // while } else ++p; } if (pCurrentStart < p) aTabName += std::u16string_view( pCurrentStart, sal::static_int_cast( p - pCurrentStart)); if (aTabName.isEmpty()) return nullptr; if (p == pMsoxlQuoteStop) ++p; // position on ! of ...'!... if( *p != '!' && ( !bAllow3D || *p != ':' ) ) return (!bAllow3D && *p == ':') ? p : start; } else if( *p == '\'') { p = parseQuotedName(p, aTabName); if (aTabName.isEmpty()) return nullptr; } else if (pErrRef && lcl_isString( p, *pErrRef) && p[pErrRef->getLength()] == '!') { p += pErrRef->getLength(); // position after "#REF!" on '!' // XXX NOTE: caller has to check the name and that it wasn't quoted. aTabName = *pErrRef; } else { bool only_digits = true; /* * Valid: Normal!a1 * Valid: x.y!a1 * Invalid: .y!a1 * * Some names starting with digits are actually valid, but * unparse quoted. Things are quite tricky: most sheet names * starting with a digit are ok, but not those starting with * "[0-9]*\." or "[0-9]+[eE]". * * Valid: 42!a1 * Valid: 4x!a1 * Invalid: 1.!a1 * Invalid: 1e!a1 */ while( true ) { const sal_Unicode uc = *p; if( rtl::isAsciiAlpha( uc ) || uc == '_' ) { if( only_digits && p != start && (uc == 'e' || uc == 'E' ) ) { p = start; break; } only_digits = false; p++; } else if( rtl::isAsciiDigit( uc )) { p++; } else if( uc == '.' ) { if( only_digits ) // Valid, except after only digits. { p = start; break; } p++; } else if (uc > 127) { // non ASCII character is allowed. ++p; } else break; } if( *p != '!' && ( !bAllow3D || *p != ':' ) ) return (!bAllow3D && *p == ':') ? p : start; aTabName += std::u16string_view( start, sal::static_int_cast( p - start ) ); } rExternTabName = aTabName; return p; } /** Tries to obtain the external document index and replace by actual document name. @param ppErrRet Contains the default pointer the caller would return if this method returns FALSE, may be replaced by NULL for type or data errors. @returns FALSE only if the input name is numeric and not within the index sequence, or the link type cannot be determined or data mismatch. Returns TRUE in all other cases, also when there is no index sequence or the input name is not numeric. */ static bool lcl_XL_getExternalDoc( const sal_Unicode** ppErrRet, OUString& rExternDocName, const uno::Sequence* pExternalLinks ) { // 1-based, sequence starts with an empty element. if (pExternalLinks && pExternalLinks->hasElements()) { // A numeric "document name" is an index into the sequence. if (CharClass::isAsciiNumeric( rExternDocName)) { sal_Int32 i = rExternDocName.toInt32(); if (i < 0 || i >= pExternalLinks->getLength()) return false; // with default *ppErrRet const sheet::ExternalLinkInfo & rInfo = (*pExternalLinks)[i]; switch (rInfo.Type) { case sheet::ExternalLinkType::DOCUMENT : { OUString aStr; if (!(rInfo.Data >>= aStr)) { SAL_INFO( "sc.core", "Data type mismatch for ExternalLinkInfo " << i); *ppErrRet = nullptr; return false; } rExternDocName = aStr; } break; case sheet::ExternalLinkType::SELF : return false; // ??? case sheet::ExternalLinkType::SPECIAL : // silently return nothing (do not assert), caller has to handle this *ppErrRet = nullptr; return false; default: SAL_INFO( "sc.core", "unhandled ExternalLinkType " << rInfo.Type << " for index " << i); *ppErrRet = nullptr; return false; } } } return true; } const sal_Unicode* ScRange::Parse_XL_Header( const sal_Unicode* p, const ScDocument& rDoc, OUString& rExternDocName, OUString& rStartTabName, OUString& rEndTabName, ScRefFlags& nFlags, bool bOnlyAcceptSingle, const uno::Sequence* pExternalLinks, const OUString* pErrRef ) { const sal_Unicode* startTabs, *start = p; ScRefFlags nSaveFlags = nFlags; // Is this an external reference ? rStartTabName.clear(); rEndTabName.clear(); rExternDocName.clear(); const sal_Unicode* pMsoxlQuoteStop = nullptr; if (*p == '[') { ++p; // Only single quotes are correct, and a double single quote escapes a // single quote text inside the quoted text. if (*p == '\'') { p = parseQuotedName(p, rExternDocName); if (*p != ']' || rExternDocName.isEmpty()) { rExternDocName.clear(); return start; } } else { // non-quoted file name. p = ScGlobal::UnicodeStrChr( start+1, ']' ); if( p == nullptr ) return start; rExternDocName += std::u16string_view( start+1, sal::static_int_cast( p-(start+1) ) ); } ++p; const sal_Unicode* pErrRet = start; if (!lcl_XL_getExternalDoc( &pErrRet, rExternDocName, pExternalLinks)) return pErrRet; rExternDocName = ScGlobal::GetAbsDocName(rExternDocName, rDoc.GetDocumentShell()); } else if (*p == '\'') { // Sickness in Excel's ODF msoxl namespace: // 'E:\[EXTDATA8.XLS]Sheet1'!$A$7 or // 'E:\[EXTDATA12B.XLSB]Sheet1:Sheet3'!$A$11 // But, 'Sheet1'!B3 would also be a valid! // Excel does not allow [ and ] characters in sheet names though. // But, more sickness comes with MOOXML as there may be // '[1]Sheet 4'!$A$1 where [1] is the external doc's index. p = parseQuotedName(p, rExternDocName); if (*p != '!') { rExternDocName.clear(); return start; } if (!rExternDocName.isEmpty()) { sal_Int32 nOpen = rExternDocName.indexOf( '['); if (nOpen == -1) rExternDocName.clear(); else { sal_Int32 nClose = rExternDocName.indexOf( ']', nOpen+1); if (nClose == -1) rExternDocName.clear(); else { rExternDocName = rExternDocName.copy(0, nClose); rExternDocName = rExternDocName.replaceAt( nOpen, 1, u""); pMsoxlQuoteStop = p - 1; // the ' quote char // There may be embedded escaped quotes, just matching the // doc name's length may not work. for (p = start; *p != '['; ++p) ; for ( ; *p != ']'; ++p) ; ++p; // Handle '[1]Sheet 4'!$A$1 if (nOpen == 0) { const sal_Unicode* pErrRet = start; if (!lcl_XL_getExternalDoc( &pErrRet, rExternDocName, pExternalLinks)) return pErrRet; } } } } if (rExternDocName.isEmpty()) p = start; } startTabs = p; p = lcl_XL_ParseSheetRef( p, rStartTabName, !bOnlyAcceptSingle, pMsoxlQuoteStop, pErrRef); if( nullptr == p ) return start; // invalid tab if (bOnlyAcceptSingle && *p == ':') return nullptr; // 3D const sal_Unicode* startEndTabs = nullptr; if( p != startTabs ) { nFlags |= ScRefFlags::TAB_VALID | ScRefFlags::TAB_3D | ScRefFlags::TAB_ABS; if( *p == ':' ) // 3d ref { startEndTabs = p + 1; p = lcl_XL_ParseSheetRef( startEndTabs, rEndTabName, false, pMsoxlQuoteStop, pErrRef); if( p == nullptr ) { nFlags = nSaveFlags; return start; // invalid tab } nFlags |= ScRefFlags::TAB2_VALID | ScRefFlags::TAB2_3D | ScRefFlags::TAB2_ABS; } else { // If only one sheet is given, the full reference is still valid, // only the second 3D flag is not set. nFlags |= ScRefFlags::TAB2_VALID | ScRefFlags::TAB2_ABS; aEnd.SetTab( aStart.Tab() ); } if( *p++ != '!' ) { nFlags = nSaveFlags; return start; // syntax error } else p = lcl_eatWhiteSpace( p ); } else { nFlags |= ScRefFlags::TAB_VALID | ScRefFlags::TAB2_VALID; // Use the current tab, it needs to be passed in. : aEnd.SetTab( .. ); } if (!rExternDocName.isEmpty()) { ScExternalRefManager* pRefMgr = rDoc.GetExternalRefManager(); pRefMgr->convertToAbsName(rExternDocName); } else { // Internal reference. if (rStartTabName.isEmpty()) { nFlags = nSaveFlags; return start; } SCTAB nTab; if ((pErrRef && *startTabs != '\'' && rStartTabName == *pErrRef) || !rDoc.GetTable(rStartTabName, nTab)) { // invalid table name. nFlags &= ~ScRefFlags::TAB_VALID; nTab = -1; } aStart.SetTab(nTab); aEnd.SetTab(nTab); if (!rEndTabName.isEmpty()) { if ((pErrRef && startEndTabs && *startEndTabs != '\'' && rEndTabName == *pErrRef) || !rDoc.GetTable(rEndTabName, nTab)) { // invalid table name. nFlags &= ~ScRefFlags::TAB2_VALID; nTab = -1; } aEnd.SetTab(nTab); } } return p; } static const sal_Unicode* lcl_r1c1_get_col( const ScSheetLimits& rSheetLimits, const sal_Unicode* p, const ScAddress::Details& rDetails, ScAddress* pAddr, ScRefFlags* nFlags ) { const sal_Unicode *pEnd; sal_Int64 n; bool isRelative; if( p[0] == '\0' ) return nullptr; p++; isRelative = *p == '['; if( isRelative ) p++; n = sal_Unicode_strtol( p, &pEnd ); if( nullptr == pEnd ) return nullptr; if( p == pEnd ) // C is a relative ref with offset 0 { if( isRelative ) return nullptr; n = rDetails.nCol; } else if( isRelative ) { if( *pEnd != ']' ) return nullptr; n += rDetails.nCol; pEnd++; } else { *nFlags |= ScRefFlags::COL_ABS; n--; } if( n < 0 || n >= rSheetLimits.GetMaxColCount()) return nullptr; pAddr->SetCol( static_cast( n ) ); *nFlags |= ScRefFlags::COL_VALID; return pEnd; } static const sal_Unicode* lcl_r1c1_get_row( const ScSheetLimits& rSheetLimits, const sal_Unicode* p, const ScAddress::Details& rDetails, ScAddress* pAddr, ScRefFlags* nFlags ) { const sal_Unicode *pEnd; bool isRelative; if( p[0] == '\0' ) return nullptr; p++; isRelative = *p == '['; if( isRelative ) p++; sal_Int64 n = sal_Unicode_strtol( p, &pEnd ); if( nullptr == pEnd ) return nullptr; if( p == pEnd ) // R is a relative ref with offset 0 { if( isRelative ) return nullptr; n = rDetails.nRow; } else if( isRelative ) { if( *pEnd != ']' ) return nullptr; n += rDetails.nRow; pEnd++; } else { *nFlags |= ScRefFlags::ROW_ABS; n--; } if( n < 0 || n >= rSheetLimits.GetMaxRowCount() ) return nullptr; pAddr->SetRow( static_cast( n ) ); *nFlags |= ScRefFlags::ROW_VALID; return pEnd; } static ScRefFlags lcl_ScRange_Parse_XL_R1C1( ScRange& r, const sal_Unicode* p, const ScDocument& rDoc, const ScAddress::Details& rDetails, bool bOnlyAcceptSingle, ScAddress::ExternalInfo* pExtInfo, sal_Int32* pSheetEndPos ) { const sal_Unicode* const pStart = p; if (pSheetEndPos) *pSheetEndPos = 0; const sal_Unicode* pTmp = nullptr; OUString aExternDocName, aStartTabName, aEndTabName; ScRefFlags nFlags = ScRefFlags::VALID | ScRefFlags::TAB_VALID; // Keep in mind that nFlags2 gets left-shifted by 4 bits before being merged. ScRefFlags nFlags2 = ScRefFlags::TAB_VALID; p = r.Parse_XL_Header( p, rDoc, aExternDocName, aStartTabName, aEndTabName, nFlags, bOnlyAcceptSingle ); ScRefFlags nBailOutFlags = ScRefFlags::ZERO; if (pSheetEndPos && pStart < p && (nFlags & ScRefFlags::TAB_VALID) && (nFlags & ScRefFlags::TAB_3D)) { *pSheetEndPos = p - pStart; nBailOutFlags = ScRefFlags::TAB_VALID | ScRefFlags::TAB_3D; } if (!aExternDocName.isEmpty()) lcl_ScRange_External_TabSpan( r, nFlags, pExtInfo, aExternDocName, aStartTabName, aEndTabName, rDoc); if( nullptr == p ) return ScRefFlags::ZERO; if( *p == 'R' || *p == 'r' ) { if( nullptr == (p = lcl_r1c1_get_row( rDoc.GetSheetLimits(), p, rDetails, &r.aStart, &nFlags )) ) return nBailOutFlags; if( *p != 'C' && *p != 'c' ) // full row R# { if( p[0] != ':' || (p[1] != 'R' && p[1] != 'r' ) || nullptr == (pTmp = lcl_r1c1_get_row( rDoc.GetSheetLimits(), p+1, rDetails, &r.aEnd, &nFlags2 ))) { // Only the initial row number is given, or the second row // number is invalid. Fallback to just the initial R applyStartToEndFlags(nFlags); r.aEnd.SetRow( r.aStart.Row() ); } else // pTmp != nullptr { // Full row range successfully parsed. applyStartToEndFlags(nFlags, nFlags2); p = pTmp; } if (p[0] != 0) { // any trailing invalid character must invalidate the whole address. nFlags &= ~ScRefFlags(ScRefFlags::VALID | ScRefFlags::COL_VALID | ScRefFlags::ROW_VALID | ScRefFlags::TAB_VALID | ScRefFlags::COL2_VALID | ScRefFlags::ROW2_VALID | ScRefFlags::TAB2_VALID); return nFlags; } nFlags |= ScRefFlags::COL_VALID | ScRefFlags::COL2_VALID | ScRefFlags::COL_ABS | ScRefFlags::COL2_ABS; r.aStart.SetCol( 0 ); r.aEnd.SetCol( rDoc.MaxCol() ); return bOnlyAcceptSingle ? ScRefFlags::ZERO : nFlags; } else if( nullptr == (p = lcl_r1c1_get_col( rDoc.GetSheetLimits(), p, rDetails, &r.aStart, &nFlags ))) { return ScRefFlags::ZERO; } if( p[0] != ':' || (p[1] != 'R' && p[1] != 'r') || nullptr == (pTmp = lcl_r1c1_get_row( rDoc.GetSheetLimits(), p+1, rDetails, &r.aEnd, &nFlags2 )) || (*pTmp != 'C' && *pTmp != 'c') || nullptr == (pTmp = lcl_r1c1_get_col( rDoc.GetSheetLimits(), pTmp, rDetails, &r.aEnd, &nFlags2 ))) { // single cell reference if (p[0] != 0) { // any trailing invalid character must invalidate the whole address. nFlags &= ~ScRefFlags(ScRefFlags::VALID | ScRefFlags::COL_VALID | ScRefFlags::ROW_VALID | ScRefFlags::TAB_VALID); return nFlags; } return bOnlyAcceptSingle ? nFlags : ScRefFlags::ZERO; } assert(pTmp); p = pTmp; // double reference if (p[0] != 0) { // any trailing invalid character must invalidate the whole range. nFlags &= ~ScRefFlags(ScRefFlags::VALID | ScRefFlags::COL_VALID | ScRefFlags::ROW_VALID | ScRefFlags::TAB_VALID | ScRefFlags::COL2_VALID | ScRefFlags::ROW2_VALID | ScRefFlags::TAB2_VALID); return nFlags; } applyStartToEndFlags(nFlags, nFlags2); return bOnlyAcceptSingle ? ScRefFlags::ZERO : nFlags; } else if( *p == 'C' || *p == 'c' ) // full col C# { if( nullptr == (p = lcl_r1c1_get_col( rDoc.GetSheetLimits(), p, rDetails, &r.aStart, &nFlags ))) return nBailOutFlags; if( p[0] != ':' || (p[1] != 'C' && p[1] != 'c') || nullptr == (pTmp = lcl_r1c1_get_col( rDoc.GetSheetLimits(), p+1, rDetails, &r.aEnd, &nFlags2 ))) { // Fallback to just the initial C applyStartToEndFlags(nFlags); r.aEnd.SetCol( r.aStart.Col() ); } else // pTmp != nullptr { applyStartToEndFlags(nFlags, nFlags2); p = pTmp; } if (p[0] != 0) { // any trailing invalid character must invalidate the whole address. nFlags &= ~ScRefFlags(ScRefFlags::VALID | ScRefFlags::COL_VALID | ScRefFlags::ROW_VALID | ScRefFlags::TAB_VALID | ScRefFlags::COL2_VALID | ScRefFlags::ROW2_VALID | ScRefFlags::TAB2_VALID); return nFlags; } nFlags |= ScRefFlags::ROW_VALID | ScRefFlags::ROW2_VALID | ScRefFlags::ROW_ABS | ScRefFlags::ROW2_ABS; r.aStart.SetRow( 0 ); r.aEnd.SetRow( rDoc.MaxRow() ); return bOnlyAcceptSingle ? ScRefFlags::ZERO : nFlags; } return nBailOutFlags; } static const sal_Unicode* lcl_a1_get_col( const ScDocument& rDoc, const sal_Unicode* p, ScAddress* pAddr, ScRefFlags* nFlags, const OUString* pErrRef ) { if( *p == '$' ) { *nFlags |= ScRefFlags::COL_ABS; p++; } if (pErrRef && lcl_isString( p, *pErrRef)) { p += pErrRef->getLength(); *nFlags &= ~ScRefFlags::COL_VALID; pAddr->SetCol(-1); return p; } if( !rtl::isAsciiAlpha( *p ) ) return nullptr; sal_Int64 nCol = rtl::toAsciiUpperCase( *p++ ) - 'A'; const SCCOL nMaxCol = rDoc.MaxCol(); while (nCol <= nMaxCol && rtl::isAsciiAlpha(*p)) nCol = ((nCol + 1) * 26) + rtl::toAsciiUpperCase( *p++ ) - 'A'; if( nCol > nMaxCol || nCol < 0 || rtl::isAsciiAlpha( *p ) ) return nullptr; *nFlags |= ScRefFlags::COL_VALID; pAddr->SetCol( sal::static_int_cast( nCol )); return p; } static const sal_Unicode* lcl_a1_get_row( const ScDocument& rDoc, const sal_Unicode* p, ScAddress* pAddr, ScRefFlags* nFlags, const OUString* pErrRef ) { const sal_Unicode *pEnd; if( *p == '$' ) { *nFlags |= ScRefFlags::ROW_ABS; p++; } if (pErrRef && lcl_isString( p, *pErrRef)) { p += pErrRef->getLength(); *nFlags &= ~ScRefFlags::ROW_VALID; pAddr->SetRow(-1); return p; } sal_Int64 n = sal_Unicode_strtol( p, &pEnd ) - 1; if( nullptr == pEnd || p == pEnd || n < 0 || n > rDoc.MaxRow() ) return nullptr; *nFlags |= ScRefFlags::ROW_VALID; pAddr->SetRow( sal::static_int_cast(n) ); return pEnd; } /// B:B or 2:2, but not B:2 or 2:B or B2:B or B:B2 or ... static bool isValidSingleton( ScRefFlags nFlags, ScRefFlags nFlags2 ) { bool bCols = (nFlags & ScRefFlags::COL_VALID) && ((nFlags & ScRefFlags::COL2_VALID) || (nFlags2 & ScRefFlags::COL_VALID)); bool bRows = (nFlags & ScRefFlags::ROW_VALID) && ((nFlags & ScRefFlags::ROW2_VALID) || (nFlags2 & ScRefFlags::ROW_VALID)); return bCols != bRows; } static ScRefFlags lcl_ScRange_Parse_XL_A1( ScRange& r, const sal_Unicode* p, const ScDocument& rDoc, bool bOnlyAcceptSingle, ScAddress::ExternalInfo* pExtInfo, const uno::Sequence* pExternalLinks, sal_Int32* pSheetEndPos, const OUString* pErrRef ) { const sal_Unicode* const pStart = p; if (pSheetEndPos) *pSheetEndPos = 0; const sal_Unicode* tmp1, *tmp2; OUString aExternDocName, aStartTabName, aEndTabName; // for external link table ScRefFlags nFlags = ScRefFlags::VALID | ScRefFlags::TAB_VALID, nFlags2 = ScRefFlags::TAB_VALID; p = r.Parse_XL_Header( p, rDoc, aExternDocName, aStartTabName, aEndTabName, nFlags, bOnlyAcceptSingle, pExternalLinks, pErrRef ); ScRefFlags nBailOutFlags = ScRefFlags::ZERO; if (pSheetEndPos && pStart < p && (nFlags & ScRefFlags::TAB_VALID) && (nFlags & ScRefFlags::TAB_3D)) { *pSheetEndPos = p - pStart; nBailOutFlags = ScRefFlags::TAB_VALID | ScRefFlags::TAB_3D; } if (!aExternDocName.isEmpty()) lcl_ScRange_External_TabSpan( r, nFlags, pExtInfo, aExternDocName, aStartTabName, aEndTabName, rDoc); if( nullptr == p ) return nBailOutFlags; tmp1 = lcl_a1_get_col( rDoc, p, &r.aStart, &nFlags, pErrRef); if( tmp1 == nullptr ) // Is it a row only reference 3:5 { if( bOnlyAcceptSingle ) // by definition full row refs are ranges return nBailOutFlags; tmp1 = lcl_a1_get_row( rDoc, p, &r.aStart, &nFlags, pErrRef); tmp1 = lcl_eatWhiteSpace( tmp1 ); if( !tmp1 || *tmp1++ != ':' ) // Even a singleton requires ':' (eg 2:2) return nBailOutFlags; tmp1 = lcl_eatWhiteSpace( tmp1 ); tmp2 = lcl_a1_get_row( rDoc, tmp1, &r.aEnd, &nFlags2, pErrRef); if( !tmp2 || *tmp2 != 0 ) // Must have fully parsed a singleton. return nBailOutFlags; r.aStart.SetCol( 0 ); r.aEnd.SetCol( rDoc.MaxCol() ); nFlags |= ScRefFlags::COL_VALID | ScRefFlags::COL2_VALID | ScRefFlags::COL_ABS | ScRefFlags::COL2_ABS; applyStartToEndFlags(nFlags, nFlags2); return nFlags; } tmp2 = lcl_a1_get_row( rDoc, tmp1, &r.aStart, &nFlags, pErrRef); if( tmp2 == nullptr ) // check for col only reference F:H { if( bOnlyAcceptSingle ) // by definition full col refs are ranges return nBailOutFlags; tmp1 = lcl_eatWhiteSpace( tmp1 ); if( *tmp1++ != ':' ) // Even a singleton requires ':' (eg F:F) return nBailOutFlags; tmp1 = lcl_eatWhiteSpace( tmp1 ); tmp2 = lcl_a1_get_col( rDoc, tmp1, &r.aEnd, &nFlags2, pErrRef); if( !tmp2 || *tmp2 != 0 ) // Must have fully parsed a singleton. return nBailOutFlags; r.aStart.SetRow( 0 ); r.aEnd.SetRow( rDoc.MaxRow() ); nFlags |= ScRefFlags::ROW_VALID | ScRefFlags::ROW2_VALID | ScRefFlags::ROW_ABS | ScRefFlags::ROW2_ABS; applyStartToEndFlags(nFlags, nFlags2); return nFlags; } // prepare as if it's a singleton, in case we want to fall back */ r.aEnd.SetCol( r.aStart.Col() ); r.aEnd.SetRow( r.aStart.Row() ); // don't overwrite sheet number as parsed in Parse_XL_Header() if ( bOnlyAcceptSingle ) { if ( *tmp2 == 0 ) return nFlags; else { // any trailing invalid character must invalidate the address. nFlags &= ~ScRefFlags(ScRefFlags::VALID | ScRefFlags::COL_VALID | ScRefFlags::ROW_VALID | ScRefFlags::TAB_VALID); return nFlags; } } tmp2 = lcl_eatWhiteSpace( tmp2 ); if( *tmp2 != ':' ) { // Sheet1:Sheet2!C4 is a valid range, without a second sheet it is // not. Any trailing invalid character invalidates the range. if (*tmp2 == 0 && (nFlags & ScRefFlags::TAB2_3D)) { if (nFlags & ScRefFlags::COL_ABS) nFlags |= ScRefFlags::COL2_ABS; if (nFlags & ScRefFlags::ROW_ABS) nFlags |= ScRefFlags::ROW2_ABS; } else nFlags &= ~ScRefFlags(ScRefFlags::VALID | ScRefFlags::COL_VALID | ScRefFlags::ROW_VALID | ScRefFlags::TAB_VALID | ScRefFlags::COL2_VALID | ScRefFlags::ROW2_VALID | ScRefFlags::TAB2_VALID); return nFlags; } p = lcl_eatWhiteSpace( tmp2+1 ); // after ':' tmp1 = lcl_a1_get_col( rDoc, p, &r.aEnd, &nFlags2, pErrRef); if( !tmp1 && aEndTabName.isEmpty() ) // Probably the aEndTabName was specified after the first range { p = lcl_XL_ParseSheetRef( p, aEndTabName, false, nullptr, pErrRef); if( p ) { SCTAB nTab = 0; if( !aEndTabName.isEmpty() && rDoc.GetTable( aEndTabName, nTab ) ) { r.aEnd.SetTab( nTab ); nFlags |= ScRefFlags::TAB2_VALID | ScRefFlags::TAB2_3D | ScRefFlags::TAB2_ABS; } if (*p == '!' || *p == ':') p = lcl_eatWhiteSpace( p+1 ); tmp1 = lcl_a1_get_col( rDoc, p, &r.aEnd, &nFlags2, pErrRef); } } if( !tmp1 ) // strange, but maybe valid singleton return isValidSingleton( nFlags, nFlags2) ? nFlags : (nFlags & ~ScRefFlags::VALID); tmp2 = lcl_a1_get_row( rDoc, tmp1, &r.aEnd, &nFlags2, pErrRef); if( !tmp2 ) // strange, but maybe valid singleton return isValidSingleton( nFlags, nFlags2) ? nFlags : (nFlags & ~ScRefFlags::VALID); if ( *tmp2 != 0 ) { // any trailing invalid character must invalidate the range. nFlags &= ~ScRefFlags(ScRefFlags::VALID | ScRefFlags::COL_VALID | ScRefFlags::ROW_VALID | ScRefFlags::TAB_VALID | ScRefFlags::COL2_VALID | ScRefFlags::ROW2_VALID | ScRefFlags::TAB2_VALID); return nFlags; } applyStartToEndFlags(nFlags, nFlags2); return nFlags; } /** @param p pointer to null-terminated sal_Unicode string @param rRawRes returns ScRefFlags::... flags without the final check for full validity that is applied to the return value, with which two addresses that form a column or row singleton range, e.g. A:A or 1:1, can be detected. Used in lcl_ScRange_Parse_OOo(). @param pRange pointer to range where rAddr effectively is *pRange->aEnd, used in conjunction with pExtInfo to determine the tab span of a 3D reference. */ static ScRefFlags lcl_ScAddress_Parse_OOo( const sal_Unicode* p, const ScDocument& rDoc, ScAddress& rAddr, ScRefFlags& rRawRes, ScAddress::ExternalInfo* pExtInfo, ScRange* pRange, sal_Int32* pSheetEndPos, const OUString* pErrRef ) { const sal_Unicode* const pStart = p; if (pSheetEndPos) *pSheetEndPos = 0; ScRefFlags nRes = ScRefFlags::ZERO; rRawRes = ScRefFlags::ZERO; OUString aDocName; // the pure Document Name OUString aTab; bool bExtDoc = false; bool bExtDocInherited = false; // Lets see if this is a reference to something in an external file. A // document name is always quoted and has a trailing #. if (*p == '\'') { OUString aTmp; p = parseQuotedName(p, aTmp); aDocName = aTmp; if (*p++ == SC_COMPILER_FILE_TAB_SEP) bExtDoc = true; else // This is not a document name. Perhaps a quoted relative table // name. p = pStart; } else if (pExtInfo && pExtInfo->mbExternal) { // This is an external reference. bExtDoc = bExtDocInherited = true; } SCCOL nCol = 0; SCROW nRow = 0; SCTAB nTab = 0; ScRefFlags nBailOutFlags = ScRefFlags::ZERO; ScRefFlags nBits = ScRefFlags::TAB_VALID; const sal_Unicode* q; if ( ScGlobal::FindUnquoted( p, '.') ) { nRes |= ScRefFlags::TAB_3D; if ( bExtDoc ) nRes |= ScRefFlags::TAB_ABS; if (*p == '$') { nRes |= ScRefFlags::TAB_ABS; p++; } if (pErrRef && lcl_isString( p, *pErrRef) && p[pErrRef->getLength()] == '.') { // #REF! particle of an invalidated reference plus sheet separator. p += pErrRef->getLength() + 1; nRes &= ~ScRefFlags::TAB_VALID; nTab = -1; } else { if (*p == '\'') { // Tokens that start at ' can have anything in them until a final // ' but '' marks an escaped '. We've earlier guaranteed that a // string containing '' will be surrounded by '. p = parseQuotedName(p, aTab); } else { OUStringBuffer aTabAcc; while (*p) { if( *p == '.') break; if( *p == '\'' ) { p++; break; } aTabAcc.append(*p); p++; } aTab = aTabAcc.makeStringAndClear(); } if (*p != '.') nBits = ScRefFlags::ZERO; else { ++p; if (!bExtDoc && !rDoc.GetTable( aTab, nTab )) nBits = ScRefFlags::ZERO; } } if (pSheetEndPos && (nBits & ScRefFlags::TAB_VALID)) { *pSheetEndPos = p - pStart; nBailOutFlags = ScRefFlags::TAB_VALID | ScRefFlags::TAB_3D; } } else { if (bExtDoc && !bExtDocInherited) return nRes; // After a document a sheet must follow. nTab = rAddr.Tab(); } nRes |= nBits; q = p; if (*p) { nBits = ScRefFlags::COL_VALID; if (*p == '$') { nBits |= ScRefFlags::COL_ABS; p++; } if (pErrRef && lcl_isString( p, *pErrRef)) { // #REF! particle of an invalidated reference. p += pErrRef->getLength(); nBits &= ~ScRefFlags::COL_VALID; nCol = -1; } else { if (rtl::isAsciiAlpha( *p )) { const SCCOL nMaxCol = rDoc.MaxCol(); sal_Int64 n = rtl::toAsciiUpperCase( *p++ ) - 'A'; while (n < nMaxCol && rtl::isAsciiAlpha(*p)) n = ((n + 1) * 26) + rtl::toAsciiUpperCase( *p++ ) - 'A'; if (n > nMaxCol || n < 0 || (*p && *p != '$' && !rtl::isAsciiDigit( *p ) && (!pErrRef || !lcl_isString( p, *pErrRef)))) nBits = ScRefFlags::ZERO; else nCol = sal::static_int_cast( n ); } else nBits = ScRefFlags::ZERO; if( nBits == ScRefFlags::ZERO ) p = q; } nRes |= nBits; } q = p; if (*p) { nBits = ScRefFlags::ROW_VALID; if (*p == '$') { nBits |= ScRefFlags::ROW_ABS; p++; } if (pErrRef && lcl_isString( p, *pErrRef)) { // #REF! particle of an invalidated reference. p += pErrRef->getLength(); // Clearing the ROW_VALID bit here is not possible because of the // check at the end whether only a valid column was detected in // which case all bits are cleared because it could be any other // name. Instead, set to an absolute invalid row value. This will // display a $#REF! instead of #REF! if the error value was // relative, but live with it. nBits |= ScRefFlags::ROW_ABS; nRow = -1; } else { if( !rtl::isAsciiDigit( *p ) ) { nBits = ScRefFlags::ZERO; nRow = -1; } else { sal_Int64 n = rtl_ustr_toInt32( p, 10 ) - 1; while (rtl::isAsciiDigit( *p )) p++; const SCROW nMaxRow = rDoc.MaxRow(); if( n < 0 || n > nMaxRow ) nBits = ScRefFlags::ZERO; nRow = sal::static_int_cast(n); } if( nBits == ScRefFlags::ZERO ) p = q; } nRes |= nBits; } rAddr.Set( nCol, nRow, nTab ); if (!*p && bExtDoc) { ScExternalRefManager* pRefMgr = rDoc.GetExternalRefManager(); // Need document name if inherited. if (bExtDocInherited) { // The FileId was created using the original file name, so // obtain that. Otherwise lcl_ScRange_External_TabSpan() would // retrieve a FileId for the real name and bail out if that // differed from pExtInfo->mnFileId, as is the case when // loading documents that refer external files relative to the // current own document but were saved from a different path // than loaded. const OUString* pFileName = pRefMgr->getExternalFileName( pExtInfo->mnFileId, true); if (pFileName) aDocName = *pFileName; else nRes = ScRefFlags::ZERO; } pRefMgr->convertToAbsName(aDocName); if ((!pExtInfo || !pExtInfo->mbExternal) && pRefMgr->isOwnDocument(aDocName)) { if (!rDoc.GetTable( aTab, nTab )) nRes = ScRefFlags::ZERO; else { rAddr.SetTab( nTab); nRes |= ScRefFlags::TAB_VALID; } } else { if (!pExtInfo) nRes = ScRefFlags::ZERO; else { if (!pExtInfo->mbExternal) { sal_uInt16 nFileId = pRefMgr->getExternalFileId(aDocName); pExtInfo->mbExternal = true; pExtInfo->maTabName = aTab; pExtInfo->mnFileId = nFileId; if (pRefMgr->getSingleRefToken(nFileId, aTab, ScAddress(nCol, nRow, 0), nullptr, &nTab)) { rAddr.SetTab( nTab); nRes |= ScRefFlags::TAB_VALID; } else nRes = ScRefFlags::ZERO; } else { // This is a call for the second part of the reference, // we must have the range to adapt tab span. if (!pRange) nRes = ScRefFlags::ZERO; else { ScRefFlags nFlags = nRes | ScRefFlags::TAB2_VALID; if (!lcl_ScRange_External_TabSpan( *pRange, nFlags, pExtInfo, aDocName, pExtInfo->maTabName, aTab, rDoc)) nRes &= ~ScRefFlags::TAB_VALID; else { if (nFlags & ScRefFlags::TAB2_VALID) { rAddr.SetTab( pRange->aEnd.Tab()); nRes |= ScRefFlags::TAB_VALID; } else nRes &= ~ScRefFlags::TAB_VALID; } } } } } } else if (bExtDoc && pExtInfo && !bExtDocInherited && !pExtInfo->mbExternal && pSheetEndPos) { // Pass partial info up to caller, there may be an external name // following, and if after *pSheetEndPos it's external sheet-local. // For global names aTab is empty and *pSheetEndPos==0. pExtInfo->mbExternal = true; pExtInfo->maTabName = aTab; pExtInfo->mnFileId = rDoc.GetExternalRefManager()->getExternalFileId(aDocName); } rRawRes |= nRes; if ( !(nRes & ScRefFlags::ROW_VALID) && (nRes & ScRefFlags::COL_VALID) && !( (nRes & ScRefFlags::TAB_3D) && (nRes & ScRefFlags::TAB_VALID)) ) { // no Row, no Tab, but Col => DM (...), B (...) et al nRes = ScRefFlags::ZERO; } if( !*p ) { ScRefFlags nMask = nRes & ( ScRefFlags::ROW_VALID | ScRefFlags::COL_VALID | ScRefFlags::TAB_VALID ); if( nMask == ( ScRefFlags::ROW_VALID | ScRefFlags::COL_VALID | ScRefFlags::TAB_VALID ) ) nRes |= ScRefFlags::VALID; } else nRes = rRawRes = nBailOutFlags; return nRes; } static ScRefFlags lcl_ScAddress_Parse ( const sal_Unicode* p, const ScDocument& rDoc, ScAddress& rAddr, const ScAddress::Details& rDetails, ScAddress::ExternalInfo* pExtInfo, const uno::Sequence* pExternalLinks, sal_Int32* pSheetEndPos, const OUString* pErrRef ) { if( !*p ) return ScRefFlags::ZERO; switch (rDetails.eConv) { case formula::FormulaGrammar::CONV_XL_A1: case formula::FormulaGrammar::CONV_XL_OOX: { ScRange rRange = rAddr; ScRefFlags nFlags = lcl_ScRange_Parse_XL_A1( rRange, p, rDoc, true, pExtInfo, (rDetails.eConv == formula::FormulaGrammar::CONV_XL_OOX ? pExternalLinks : nullptr), pSheetEndPos, pErrRef); rAddr = rRange.aStart; return nFlags; } case formula::FormulaGrammar::CONV_XL_R1C1: { ScRange rRange = rAddr; ScRefFlags nFlags = lcl_ScRange_Parse_XL_R1C1( rRange, p, rDoc, rDetails, true, pExtInfo, pSheetEndPos); rAddr = rRange.aStart; return nFlags; } default : case formula::FormulaGrammar::CONV_OOO: { ScRefFlags nRawRes = ScRefFlags::ZERO; return lcl_ScAddress_Parse_OOo( p, rDoc, rAddr, nRawRes, pExtInfo, nullptr, pSheetEndPos, pErrRef); } } } bool ConvertSingleRef( const ScDocument& rDoc, const OUString& rRefString, SCTAB nDefTab, ScRefAddress& rRefAddress, const ScAddress::Details& rDetails, ScAddress::ExternalInfo* pExtInfo /* = NULL */ ) { bool bRet = false; if (pExtInfo || (ScGlobal::FindUnquoted( rRefString, SC_COMPILER_FILE_TAB_SEP) == -1)) { ScAddress aAddr( 0, 0, nDefTab ); ScRefFlags nRes = aAddr.Parse( rRefString, rDoc, rDetails, pExtInfo); if ( nRes & ScRefFlags::VALID ) { rRefAddress.Set( aAddr, ((nRes & ScRefFlags::COL_ABS) == ScRefFlags::ZERO), ((nRes & ScRefFlags::ROW_ABS) == ScRefFlags::ZERO), ((nRes & ScRefFlags::TAB_ABS) == ScRefFlags::ZERO)); bRet = true; } } return bRet; } bool ConvertDoubleRef( const ScDocument& rDoc, const OUString& rRefString, SCTAB nDefTab, ScRefAddress& rStartRefAddress, ScRefAddress& rEndRefAddress, const ScAddress::Details& rDetails, ScAddress::ExternalInfo* pExtInfo /* = NULL */ ) { bool bRet = false; if (pExtInfo || (ScGlobal::FindUnquoted( rRefString, SC_COMPILER_FILE_TAB_SEP) == -1)) { ScRange aRange( ScAddress( 0, 0, nDefTab)); ScRefFlags nRes = aRange.Parse( rRefString, rDoc, rDetails, pExtInfo); if ( nRes & ScRefFlags::VALID ) { rStartRefAddress.Set( aRange.aStart, ((nRes & ScRefFlags::COL_ABS) == ScRefFlags::ZERO), ((nRes & ScRefFlags::ROW_ABS) == ScRefFlags::ZERO), ((nRes & ScRefFlags::TAB_ABS) == ScRefFlags::ZERO)); rEndRefAddress.Set( aRange.aEnd, ((nRes & ScRefFlags::COL2_ABS) == ScRefFlags::ZERO), ((nRes & ScRefFlags::ROW2_ABS) == ScRefFlags::ZERO), ((nRes & ScRefFlags::TAB2_ABS) == ScRefFlags::ZERO)); bRet = true; } } return bRet; } ScRefFlags ScAddress::Parse( const OUString& r, const ScDocument& rDoc, const Details& rDetails, ExternalInfo* pExtInfo, const uno::Sequence* pExternalLinks, sal_Int32* pSheetEndPos, const OUString* pErrRef ) { return lcl_ScAddress_Parse( r.getStr(), rDoc, *this, rDetails, pExtInfo, pExternalLinks, pSheetEndPos, pErrRef); } ScRange ScRange::Intersection( const ScRange& rOther ) const { SCCOL nCol1 = std::max(aStart.Col(), rOther.aStart.Col()); SCCOL nCol2 = std::min(aEnd.Col(), rOther.aEnd.Col()); SCROW nRow1 = std::max(aStart.Row(), rOther.aStart.Row()); SCROW nRow2 = std::min(aEnd.Row(), rOther.aEnd.Row()); SCTAB nTab1 = std::max(aStart.Tab(), rOther.aStart.Tab()); SCTAB nTab2 = std::min(aEnd.Tab(), rOther.aEnd.Tab()); if (nCol1 > nCol2 || nRow1 > nRow2 || nTab1 > nTab2) return ScRange(ScAddress::INITIALIZE_INVALID); return ScRange(nCol1, nRow1, nTab1, nCol2, nRow2, nTab2); } void ScRange::ExtendTo( const ScRange& rRange ) { OSL_ENSURE( rRange.IsValid(), "ScRange::ExtendTo - cannot extend to invalid range" ); if( IsValid() ) { aStart.SetCol( std::min( aStart.Col(), rRange.aStart.Col() ) ); aStart.SetRow( std::min( aStart.Row(), rRange.aStart.Row() ) ); aStart.SetTab( std::min( aStart.Tab(), rRange.aStart.Tab() ) ); aEnd.SetCol( std::max( aEnd.Col(), rRange.aEnd.Col() ) ); aEnd.SetRow( std::max( aEnd.Row(), rRange.aEnd.Row() ) ); aEnd.SetTab( std::max( aEnd.Tab(), rRange.aEnd.Tab() ) ); } else *this = rRange; } static ScRefFlags lcl_ScRange_Parse_OOo( ScRange& rRange, const OUString& r, const ScDocument& rDoc, ScAddress::ExternalInfo* pExtInfo, const OUString* pErrRef ) { ScRefFlags nRes1 = ScRefFlags::ZERO, nRes2 = ScRefFlags::ZERO; sal_Int32 nPos = ScGlobal::FindUnquoted( r, ':'); if (nPos != -1) { OUStringBuffer aTmp(r); aTmp[nPos] = 0; const sal_Unicode* p = aTmp.getStr(); ScRefFlags nRawRes1 = ScRefFlags::ZERO; nRes1 = lcl_ScAddress_Parse_OOo( p, rDoc, rRange.aStart, nRawRes1, pExtInfo, nullptr, nullptr, pErrRef); if ((nRes1 != ScRefFlags::ZERO) || ((nRawRes1 & (ScRefFlags::COL_VALID | ScRefFlags::ROW_VALID)) && (nRawRes1 & ScRefFlags::TAB_VALID))) { rRange.aEnd = rRange.aStart; // sheet must be initialized identical to first sheet ScRefFlags nRawRes2 = ScRefFlags::ZERO; nRes2 = lcl_ScAddress_Parse_OOo( p + nPos+ 1, rDoc, rRange.aEnd, nRawRes2, pExtInfo, &rRange, nullptr, pErrRef); if (!((nRes1 & ScRefFlags::VALID) && (nRes2 & ScRefFlags::VALID)) && // If not fully valid addresses, check if both have a valid // column or row, and both have valid (or omitted) sheet references. (nRawRes1 & (ScRefFlags::COL_VALID | ScRefFlags::ROW_VALID)) && (nRawRes1 & ScRefFlags::TAB_VALID) && (nRawRes2 & (ScRefFlags::COL_VALID | ScRefFlags::ROW_VALID)) && (nRawRes2 & ScRefFlags::TAB_VALID) && // Both must be column XOR row references, A:A or 1:1 but not A:1 or 1:A ((nRawRes1 & (ScRefFlags::COL_VALID | ScRefFlags::ROW_VALID)) == (nRawRes2 & (ScRefFlags::COL_VALID | ScRefFlags::ROW_VALID)))) { nRes1 = nRawRes1 | ScRefFlags::VALID; nRes2 = nRawRes2 | ScRefFlags::VALID; if (nRawRes1 & ScRefFlags::COL_VALID) { rRange.aStart.SetRow(0); rRange.aEnd.SetRow(rDoc.MaxRow()); nRes1 |= ScRefFlags::ROW_VALID | ScRefFlags::ROW_ABS; nRes2 |= ScRefFlags::ROW_VALID | ScRefFlags::ROW_ABS; } else { rRange.aStart.SetCol(0); rRange.aEnd.SetCol( rDoc.MaxCol() ); nRes1 |= ScRefFlags::COL_VALID | ScRefFlags::COL_ABS; nRes2 |= ScRefFlags::COL_VALID | ScRefFlags::COL_ABS; } } else if ((nRes1 & ScRefFlags::VALID) && (nRes2 & ScRefFlags::VALID)) { // Flag entire column/row references so they can be displayed // as such. If the sticky reference parts are not both // absolute or relative, assume that the user thought about // something we should not touch. if (rRange.aStart.Row() == 0 && rRange.aEnd.Row() == rDoc.MaxRow() && ((nRes1 & ScRefFlags::ROW_ABS) == ScRefFlags::ZERO) && ((nRes2 & ScRefFlags::ROW_ABS) == ScRefFlags::ZERO)) { nRes1 |= ScRefFlags::ROW_ABS; nRes2 |= ScRefFlags::ROW_ABS; } else if (rRange.aStart.Col() == 0 && rRange.aEnd.Col() == rDoc.MaxCol() && ((nRes1 & ScRefFlags::COL_ABS) == ScRefFlags::ZERO) && ((nRes2 & ScRefFlags::COL_ABS) == ScRefFlags::ZERO)) { nRes1 |= ScRefFlags::COL_ABS; nRes2 |= ScRefFlags::COL_ABS; } } if ((nRes1 & ScRefFlags::VALID) && (nRes2 & ScRefFlags::VALID)) { // PutInOrder / Justify ScRefFlags nMask, nBits1, nBits2; SCCOL nTempCol; if ( rRange.aEnd.Col() < (nTempCol = rRange.aStart.Col()) ) { rRange.aStart.SetCol(rRange.aEnd.Col()); rRange.aEnd.SetCol(nTempCol); nMask = (ScRefFlags::COL_VALID | ScRefFlags::COL_ABS); nBits1 = nRes1 & nMask; nBits2 = nRes2 & nMask; nRes1 = (nRes1 & ~nMask) | nBits2; nRes2 = (nRes2 & ~nMask) | nBits1; } SCROW nTempRow; if ( rRange.aEnd.Row() < (nTempRow = rRange.aStart.Row()) ) { rRange.aStart.SetRow(rRange.aEnd.Row()); rRange.aEnd.SetRow(nTempRow); nMask = (ScRefFlags::ROW_VALID | ScRefFlags::ROW_ABS); nBits1 = nRes1 & nMask; nBits2 = nRes2 & nMask; nRes1 = (nRes1 & ~nMask) | nBits2; nRes2 = (nRes2 & ~nMask) | nBits1; } SCTAB nTempTab; if ( rRange.aEnd.Tab() < (nTempTab = rRange.aStart.Tab()) ) { rRange.aStart.SetTab(rRange.aEnd.Tab()); rRange.aEnd.SetTab(nTempTab); nMask = (ScRefFlags::TAB_VALID | ScRefFlags::TAB_ABS | ScRefFlags::TAB_3D); nBits1 = nRes1 & nMask; nBits2 = nRes2 & nMask; nRes1 = (nRes1 & ~nMask) | nBits2; nRes2 = (nRes2 & ~nMask) | nBits1; } if ( ((nRes1 & ( ScRefFlags::TAB_ABS | ScRefFlags::TAB_3D )) == ( ScRefFlags::TAB_ABS | ScRefFlags::TAB_3D )) && !(nRes2 & ScRefFlags::TAB_3D) ) nRes2 |= ScRefFlags::TAB_ABS; } else { // Don't leave around valid half references. nRes1 = nRes2 = ScRefFlags::ZERO; } } } applyStartToEndFlags(nRes1, nRes2 & ScRefFlags::BITS); nRes1 |= nRes2 & ScRefFlags::VALID; return nRes1; } ScRefFlags ScRange::Parse( const OUString& rString, const ScDocument& rDoc, const ScAddress::Details& rDetails, ScAddress::ExternalInfo* pExtInfo, const uno::Sequence* pExternalLinks, const OUString* pErrRef ) { if (rString.isEmpty()) return ScRefFlags::ZERO; switch (rDetails.eConv) { case formula::FormulaGrammar::CONV_XL_A1: case formula::FormulaGrammar::CONV_XL_OOX: { return lcl_ScRange_Parse_XL_A1( *this, rString.getStr(), rDoc, false, pExtInfo, (rDetails.eConv == formula::FormulaGrammar::CONV_XL_OOX ? pExternalLinks : nullptr), nullptr, pErrRef ); } case formula::FormulaGrammar::CONV_XL_R1C1: { return lcl_ScRange_Parse_XL_R1C1( *this, rString.getStr(), rDoc, rDetails, false, pExtInfo, nullptr ); } default: case formula::FormulaGrammar::CONV_OOO: { return lcl_ScRange_Parse_OOo( *this, rString, rDoc, pExtInfo, pErrRef ); } } } // Accept a full range, or an address ScRefFlags ScRange::ParseAny( const OUString& rString, const ScDocument& rDoc, const ScAddress::Details& rDetails ) { ScRefFlags nRet = Parse( rString, rDoc, rDetails ); const ScRefFlags nValid = ScRefFlags::VALID | ScRefFlags::COL2_VALID | ScRefFlags::ROW2_VALID | ScRefFlags::TAB2_VALID; if ( (nRet & nValid) != nValid ) { ScAddress aAdr(aStart);//initialize with currentPos as fallback for table number nRet = aAdr.Parse( rString, rDoc, rDetails ); if ( nRet & ScRefFlags::VALID ) aStart = aEnd = aAdr; } return nRet; } // Parse only full row references ScRefFlags ScRange::ParseCols( const ScDocument& rDoc, const OUString& rStr, const ScAddress::Details& rDetails ) { if (rStr.isEmpty()) return ScRefFlags::ZERO; const sal_Unicode* p = rStr.getStr(); ScRefFlags nRes = ScRefFlags::ZERO; ScRefFlags ignored = ScRefFlags::ZERO; switch (rDetails.eConv) { default : case formula::FormulaGrammar::CONV_OOO: // No full col refs in OOO yet, assume XL notation case formula::FormulaGrammar::CONV_XL_A1: case formula::FormulaGrammar::CONV_XL_OOX: if (nullptr != (p = lcl_a1_get_col( rDoc, p, &aStart, &ignored, nullptr) ) ) { if( p[0] == ':') { if( nullptr != (p = lcl_a1_get_col( rDoc, p+1, &aEnd, &ignored, nullptr))) { nRes = ScRefFlags::COL_VALID; } } else { aEnd = aStart; nRes = ScRefFlags::COL_VALID; } } break; case formula::FormulaGrammar::CONV_XL_R1C1: if ((p[0] == 'C' || p[0] == 'c') && nullptr != (p = lcl_r1c1_get_col( rDoc.GetSheetLimits(), p, rDetails, &aStart, &ignored ))) { if( p[0] == ':') { if( (p[1] == 'C' || p[1] == 'c') && nullptr != (p = lcl_r1c1_get_col( rDoc.GetSheetLimits(), p+1, rDetails, &aEnd, &ignored ))) { nRes = ScRefFlags::COL_VALID; } } else { aEnd = aStart; nRes = ScRefFlags::COL_VALID; } } break; } return (p != nullptr && *p == '\0') ? nRes : ScRefFlags::ZERO; } // Parse only full row references void ScRange::ParseRows( const ScDocument& rDoc, const OUString& rStr, const ScAddress::Details& rDetails ) { if (rStr.isEmpty()) return; const sal_Unicode* p = rStr.getStr(); ScRefFlags ignored = ScRefFlags::ZERO; switch (rDetails.eConv) { default : case formula::FormulaGrammar::CONV_OOO: // No full row refs in OOO yet, assume XL notation case formula::FormulaGrammar::CONV_XL_A1: case formula::FormulaGrammar::CONV_XL_OOX: if (nullptr != (p = lcl_a1_get_row( rDoc, p, &aStart, &ignored, nullptr) ) ) { if( p[0] == ':') { lcl_a1_get_row( rDoc, p+1, &aEnd, &ignored, nullptr); } else { aEnd = aStart; } } break; case formula::FormulaGrammar::CONV_XL_R1C1: if ((p[0] == 'R' || p[0] == 'r') && nullptr != (p = lcl_r1c1_get_row( rDoc.GetSheetLimits(), p, rDetails, &aStart, &ignored ))) { if( p[0] == ':') { if( p[1] == 'R' || p[1] == 'r' ) { lcl_r1c1_get_row( rDoc.GetSheetLimits(), p+1, rDetails, &aEnd, &ignored ); } } else { aEnd = aStart; } } break; } } template static void lcl_ScColToAlpha( T& rBuf, SCCOL nCol ) { if (nCol < 26*26) { if (nCol < 26) rBuf.append( static_cast( 'A' + nCol )); else { rBuf.append( static_cast( 'A' + nCol / 26 - 1 )); rBuf.append( static_cast( 'A' + nCol % 26 )); } } else { sal_Int32 nInsert = rBuf.getLength(); while (nCol >= 26) { SCCOL nC = nCol % 26; rBuf.insert(nInsert, static_cast ( 'A' + nC )); nCol = sal::static_int_cast( nCol - nC ); nCol = nCol / 26 - 1; } rBuf.insert(nInsert, static_cast ( 'A' + nCol )); } } void ScColToAlpha( OUStringBuffer& rBuf, SCCOL nCol) { lcl_ScColToAlpha(rBuf, nCol); } template static void lcl_a1_append_c ( T &rString, int nCol, bool bIsAbs ) { if( bIsAbs ) rString.append("$"); lcl_ScColToAlpha( rString, sal::static_int_cast(nCol) ); } template static void lcl_a1_append_r ( T &rString, sal_Int32 nRow, bool bIsAbs ) { if ( bIsAbs ) rString.append("$"); rString.append( nRow + 1 ); } template static void lcl_r1c1_append_c ( T &rString, sal_Int32 nCol, bool bIsAbs, const ScAddress::Details& rDetails ) { rString.append("C"); if (bIsAbs) { rString.append( nCol + 1 ); } else { nCol -= rDetails.nCol; if (nCol != 0) { rString.append("[").append(nCol).append("]"); } } } template static void lcl_r1c1_append_r ( T &rString, sal_Int32 nRow, bool bIsAbs, const ScAddress::Details& rDetails ) { rString.append("R"); if (bIsAbs) { rString.append( nRow + 1 ); } else { nRow -= rDetails.nRow; if (nRow != 0) { rString.append("[").append(nRow).append("]"); } } } static OUString getFileNameFromDoc( const ScDocument* pDoc ) { // TODO : er points at ScGlobal::GetAbsDocName() // as a better template. Look into it OUString sFileName; SfxObjectShell* pShell; if( nullptr != pDoc && nullptr != (pShell = pDoc->GetDocumentShell() ) ) { uno::Reference< frame::XModel > xModel = pShell->GetModel(); if( xModel.is() ) { if( !xModel->getURL().isEmpty() ) { INetURLObject aURL( xModel->getURL() ); sFileName = aURL.GetLastName(); } else sFileName = pShell->GetTitle(); } } return sFileName; } static void lcl_string_append(OUStringBuffer &rString, std::u16string_view sString) { rString.append(sString); } static void lcl_string_append(OStringBuffer &rString, std::u16string_view sString) { rString.append(OUStringToOString( sString, RTL_TEXTENCODING_UTF8 )); } template static void lcl_Format( T& r, SCTAB nTab, SCROW nRow, SCCOL nCol, ScRefFlags nFlags, const ScDocument* pDoc, const ScAddress::Details& rDetails) { if( nFlags & ScRefFlags::VALID ) nFlags |= ScRefFlags::ROW_VALID | ScRefFlags::COL_VALID | ScRefFlags::TAB_VALID; if( pDoc && (nFlags & ScRefFlags::TAB_VALID ) ) { if ( nTab < 0 || nTab >= pDoc->GetTableCount() ) { lcl_string_append(r, ScCompiler::GetNativeSymbol(ocErrRef)); return; } if( nFlags & ScRefFlags::TAB_3D ) { OUString aTabName, aDocName; pDoc->GetName(nTab, aTabName); assert( !aTabName.isEmpty() && "empty sheet name"); // External Reference, same as in ScCompiler::MakeTabStr() if( aTabName[0] == '\'' ) { // "'Doc'#Tab" sal_Int32 nPos = ScCompiler::GetDocTabPos( aTabName); if (nPos != -1) { aDocName = aTabName.copy( 0, nPos + 1 ); aTabName = aTabName.copy( nPos + 1 ); } } else if( nFlags & ScRefFlags::FORCE_DOC ) { // VBA has an 'external' flag that forces the addition of the // tab name _and_ the doc name. The VBA code would be // needlessly complicated if it constructed an actual external // reference so we add this somewhat cheesy kludge to force the // addition of the document name even for non-external references aDocName = getFileNameFromDoc( pDoc ); } ScCompiler::CheckTabQuotes( aTabName, rDetails.eConv); switch( rDetails.eConv ) { default : case formula::FormulaGrammar::CONV_OOO: lcl_string_append(r, aDocName); if( nFlags & ScRefFlags::TAB_ABS ) r.append("$"); lcl_string_append(r, aTabName); r.append("."); break; case formula::FormulaGrammar::CONV_XL_OOX: if (!aTabName.isEmpty() && aTabName[0] == '\'') { if (!aDocName.isEmpty()) { lcl_string_append(r.append("'["), aDocName); r.append("]"); lcl_string_append(r, aTabName.subView(1)); } else { lcl_string_append(r, aTabName); } r.append("!"); break; } [[fallthrough]]; case formula::FormulaGrammar::CONV_XL_A1: case formula::FormulaGrammar::CONV_XL_R1C1: if (!aDocName.isEmpty()) { lcl_string_append(r.append("["), aDocName); r.append("]"); } lcl_string_append(r, aTabName); r.append("!"); break; } } } switch( rDetails.eConv ) { default : case formula::FormulaGrammar::CONV_OOO: case formula::FormulaGrammar::CONV_XL_A1: case formula::FormulaGrammar::CONV_XL_OOX: if( nFlags & ScRefFlags::COL_VALID ) lcl_a1_append_c ( r, nCol, (nFlags & ScRefFlags::COL_ABS) != ScRefFlags::ZERO ); if( nFlags & ScRefFlags::ROW_VALID ) lcl_a1_append_r ( r, nRow, (nFlags & ScRefFlags::ROW_ABS) != ScRefFlags::ZERO ); break; case formula::FormulaGrammar::CONV_XL_R1C1: if( nFlags & ScRefFlags::ROW_VALID ) lcl_r1c1_append_r ( r, nRow, (nFlags & ScRefFlags::ROW_ABS) != ScRefFlags::ZERO, rDetails ); if( nFlags & ScRefFlags::COL_VALID ) lcl_r1c1_append_c ( r, nCol, (nFlags & ScRefFlags::COL_ABS) != ScRefFlags::ZERO, rDetails ); break; } } void ScAddress::Format( OStringBuffer& r, ScRefFlags nFlags, const ScDocument* pDoc, const Details& rDetails) const { lcl_Format(r, nTab, nRow, nCol, nFlags, pDoc, rDetails); } OUString ScAddress::Format(ScRefFlags nFlags, const ScDocument* pDoc, const Details& rDetails) const { OUStringBuffer r; lcl_Format(r, nTab, nRow, nCol, nFlags, pDoc, rDetails); return r.makeStringAndClear(); } static void lcl_Split_DocTab( const ScDocument& rDoc, SCTAB nTab, const ScAddress::Details& rDetails, ScRefFlags nFlags, OUString& rTabName, OUString& rDocName ) { rDoc.GetName(nTab, rTabName); rDocName.clear(); // External reference, same as in ScCompiler::MakeTabStr() if (!rTabName.isEmpty() && rTabName[0] == '\'') { // "'Doc'#Tab" sal_Int32 nPos = ScCompiler::GetDocTabPos( rTabName); if (nPos != -1) { rDocName = rTabName.copy( 0, nPos + 1 ); rTabName = rTabName.copy( nPos + 1 ); } } else if( nFlags & ScRefFlags::FORCE_DOC ) { // VBA has an 'external' flag that forces the addition of the // tab name _and_ the doc name. The VBA code would be // needlessly complicated if it constructed an actual external // reference so we add this somewhat cheesy kludge to force the // addition of the document name even for non-external references rDocName = getFileNameFromDoc(&rDoc); } ScCompiler::CheckTabQuotes( rTabName, rDetails.eConv); } static void lcl_ScRange_Format_XL_Header( OUStringBuffer& rString, const ScRange& rRange, ScRefFlags nFlags, const ScDocument& rDoc, const ScAddress::Details& rDetails ) { if( !(nFlags & ScRefFlags::TAB_3D) ) return; OUString aTabName, aDocName; lcl_Split_DocTab( rDoc, rRange.aStart.Tab(), rDetails, nFlags, aTabName, aDocName ); switch (rDetails.eConv) { case formula::FormulaGrammar::CONV_XL_OOX: if (!aTabName.isEmpty() && aTabName[0] == '\'') { if (!aDocName.isEmpty()) { rString.append("'[" + aDocName + "]" + aTabName.subView(1)); } else { rString.append(aTabName); } break; } [[fallthrough]]; default: if (!aDocName.isEmpty()) { rString.append("[" + aDocName + "]"); } rString.append(aTabName); break; } if( nFlags & ScRefFlags::TAB2_3D ) { lcl_Split_DocTab( rDoc, rRange.aEnd.Tab(), rDetails, nFlags, aTabName, aDocName ); rString.append(":"); rString.append(aTabName); } rString.append("!"); } // helpers used in ScRange::Format static bool lcl_ColAbsFlagDiffer(const ScRefFlags nFlags) { return static_cast(nFlags & ScRefFlags::COL_ABS) != static_cast(nFlags & ScRefFlags::COL2_ABS); } static bool lcl_RowAbsFlagDiffer(const ScRefFlags nFlags) { return static_cast(nFlags & ScRefFlags::ROW_ABS) != static_cast(nFlags & ScRefFlags::ROW2_ABS); } OUString ScRange::Format( const ScDocument& rDoc, ScRefFlags nFlags, const ScAddress::Details& rDetails, bool bFullAddressNotation ) const { if( !( nFlags & ScRefFlags::VALID ) ) { return ScCompiler::GetNativeSymbol(ocErrRef); } OUStringBuffer r; switch( rDetails.eConv ) { default : case formula::FormulaGrammar::CONV_OOO: { bool bOneTab = (aStart.Tab() == aEnd.Tab()); if ( !bOneTab ) nFlags |= ScRefFlags::TAB_3D; r = aStart.Format(nFlags, &rDoc, rDetails); if( aStart != aEnd || lcl_ColAbsFlagDiffer( nFlags ) || lcl_RowAbsFlagDiffer( nFlags )) { const ScDocument* pDoc = &rDoc; // move flags of end reference to start reference, mask with BITS to exclude FORCE_DOC flag nFlags = ScRefFlags::VALID | (ScRefFlags(o3tl::to_underlying(nFlags) >> 4) & ScRefFlags::BITS); if ( bOneTab ) pDoc = nullptr; else nFlags |= ScRefFlags::TAB_3D; OUString aName(aEnd.Format(nFlags, pDoc, rDetails)); r.append(":"); r.append(aName); } break; } case formula::FormulaGrammar::CONV_XL_A1: case formula::FormulaGrammar::CONV_XL_OOX: { SCCOL nMaxCol = rDoc.MaxCol(); SCROW nMaxRow = rDoc.MaxRow(); lcl_ScRange_Format_XL_Header( r, *this, nFlags, rDoc, rDetails ); if( aStart.Col() == 0 && aEnd.Col() >= nMaxCol && !bFullAddressNotation ) { // Full col refs always require 2 rows (2:2) lcl_a1_append_r( r, aStart.Row(), (nFlags & ScRefFlags::ROW_ABS) != ScRefFlags::ZERO ); r.append(":"); lcl_a1_append_r( r, aEnd.Row(), (nFlags & ScRefFlags::ROW2_ABS) != ScRefFlags::ZERO ); } else if( aStart.Row() == 0 && aEnd.Row() >= nMaxRow && !bFullAddressNotation ) { // Full row refs always require 2 cols (A:A) lcl_a1_append_c( r, aStart.Col(), (nFlags & ScRefFlags::COL_ABS) != ScRefFlags::ZERO ); r.append(":"); lcl_a1_append_c( r, aEnd.Col(), (nFlags & ScRefFlags::COL2_ABS) != ScRefFlags::ZERO ); } else { lcl_a1_append_c ( r, aStart.Col(), (nFlags & ScRefFlags::COL_ABS) != ScRefFlags::ZERO ); lcl_a1_append_r ( r, aStart.Row(), (nFlags & ScRefFlags::ROW_ABS) != ScRefFlags::ZERO ); if( aStart.Col() != aEnd.Col() || lcl_ColAbsFlagDiffer( nFlags ) || aStart.Row() != aEnd.Row() || lcl_RowAbsFlagDiffer( nFlags ) ) { r.append(":"); lcl_a1_append_c ( r, aEnd.Col(), (nFlags & ScRefFlags::COL2_ABS) != ScRefFlags::ZERO ); lcl_a1_append_r ( r, aEnd.Row(), (nFlags & ScRefFlags::ROW2_ABS) != ScRefFlags::ZERO ); } } break; } case formula::FormulaGrammar::CONV_XL_R1C1: { SCCOL nMaxCol = rDoc.MaxCol(); SCROW nMaxRow = rDoc.MaxRow(); lcl_ScRange_Format_XL_Header( r, *this, nFlags, rDoc, rDetails ); if( aStart.Col() == 0 && aEnd.Col() >= nMaxCol && !bFullAddressNotation ) { lcl_r1c1_append_r( r, aStart.Row(), (nFlags & ScRefFlags::ROW_ABS) != ScRefFlags::ZERO, rDetails ); if( aStart.Row() != aEnd.Row() || lcl_RowAbsFlagDiffer( nFlags ) ) { r.append(":"); lcl_r1c1_append_r( r, aEnd.Row(), (nFlags & ScRefFlags::ROW2_ABS) != ScRefFlags::ZERO, rDetails ); } } else if( aStart.Row() == 0 && aEnd.Row() >= nMaxRow && !bFullAddressNotation ) { lcl_r1c1_append_c( r, aStart.Col(), (nFlags & ScRefFlags::COL_ABS) != ScRefFlags::ZERO, rDetails ); if( aStart.Col() != aEnd.Col() || lcl_ColAbsFlagDiffer( nFlags )) { r.append(":"); lcl_r1c1_append_c( r, aEnd.Col(), (nFlags & ScRefFlags::COL2_ABS) != ScRefFlags::ZERO, rDetails ); } } else { lcl_r1c1_append_r( r, aStart.Row(), (nFlags & ScRefFlags::ROW_ABS) != ScRefFlags::ZERO, rDetails ); lcl_r1c1_append_c( r, aStart.Col(), (nFlags & ScRefFlags::COL_ABS) != ScRefFlags::ZERO, rDetails ); if( aStart.Col() != aEnd.Col() || lcl_ColAbsFlagDiffer( nFlags ) || aStart.Row() != aEnd.Row() || lcl_RowAbsFlagDiffer( nFlags ) ) { r.append(":"); lcl_r1c1_append_r( r, aEnd.Row(), (nFlags & ScRefFlags::ROW2_ABS) != ScRefFlags::ZERO, rDetails ); lcl_r1c1_append_c( r, aEnd.Col(), (nFlags & ScRefFlags::COL2_ABS) != ScRefFlags::ZERO, rDetails ); } } break; } } return r.makeStringAndClear(); } bool ScAddress::Move( SCCOL dx, SCROW dy, SCTAB dz, ScAddress& rErrorPos, const ScDocument& rDoc ) { SCTAB nMaxTab = rDoc.GetTableCount(); SCCOL nMaxCol = rDoc.MaxCol(); SCROW nMaxRow = rDoc.MaxRow(); dx = Col() + dx; dy = Row() + dy; dz = Tab() + dz; bool bValid = true; rErrorPos.SetCol(dx); if( dx < 0 ) { dx = 0; bValid = false; } else if( dx > nMaxCol ) { dx = nMaxCol; bValid =false; } rErrorPos.SetRow(dy); if( dy < 0 ) { dy = 0; bValid = false; } else if( dy > nMaxRow ) { dy = nMaxRow; bValid =false; } rErrorPos.SetTab(dz); if( dz < 0 ) { dz = 0; bValid = false; } else if( dz > nMaxTab ) { // Always set MAXTAB+1 so further checks without ScDocument detect invalid. rErrorPos.SetTab(MAXTAB+1); dz = nMaxTab; bValid =false; } Set( dx, dy, dz ); return bValid; } bool ScRange::Move( SCCOL dx, SCROW dy, SCTAB dz, ScRange& rErrorRange, const ScDocument& rDoc ) { SCCOL nMaxCol = rDoc.MaxCol(); SCROW nMaxRow = rDoc.MaxRow(); if (dy && aStart.Row() == 0 && aEnd.Row() == nMaxRow) dy = 0; // Entire column not to be moved. if (dx && aStart.Col() == 0 && aEnd.Col() == nMaxCol) dx = 0; // Entire row not to be moved. bool b = aStart.Move( dx, dy, dz, rErrorRange.aStart, rDoc ); b &= aEnd.Move( dx, dy, dz, rErrorRange.aEnd, rDoc ); return b; } bool ScRange::MoveSticky( const ScDocument& rDoc, SCCOL dx, SCROW dy, SCTAB dz, ScRange& rErrorRange ) { const SCCOL nMaxCol = rDoc.MaxCol(); const SCROW nMaxRow = rDoc.MaxRow(); bool bColRange = (aStart.Col() < aEnd.Col()); bool bRowRange = (aStart.Row() < aEnd.Row()); if (dy && aStart.Row() == 0 && aEnd.Row() == nMaxRow) dy = 0; // Entire column not to be moved. if (dx && aStart.Col() == 0 && aEnd.Col() == nMaxCol) dx = 0; // Entire row not to be moved. bool b1 = aStart.Move( dx, dy, dz, rErrorRange.aStart, rDoc ); if (dx && bColRange && aEnd.Col() == nMaxCol) dx = 0; // End column sticky. if (dy && bRowRange && aEnd.Row() == nMaxRow) dy = 0; // End row sticky. SCTAB nOldTab = aEnd.Tab(); bool b2 = aEnd.Move( dx, dy, dz, rErrorRange.aEnd, rDoc ); if (!b2) { // End column or row of a range may have become sticky. bColRange = (!dx || (bColRange && aEnd.Col() == nMaxCol)); if (dx && bColRange) rErrorRange.aEnd.SetCol(nMaxCol); bRowRange = (!dy || (bRowRange && aEnd.Row() == nMaxRow)); if (dy && bRowRange) rErrorRange.aEnd.SetRow(nMaxRow); b2 = bColRange && bRowRange && (aEnd.Tab() - nOldTab == dz); } return b1 && b2; } void ScRange::IncColIfNotLessThan(const ScDocument& rDoc, SCCOL nStartCol, SCCOL nOffset) { if (aStart.Col() >= nStartCol) { aStart.IncCol(nOffset); if (aStart.Col() < 0) aStart.SetCol(0); else if(aStart.Col() > rDoc.MaxCol()) aStart.SetCol(rDoc.MaxCol()); } if (aEnd.Col() >= nStartCol) { aEnd.IncCol(nOffset); if (aEnd.Col() < 0) aEnd.SetCol(0); else if(aEnd.Col() > rDoc.MaxCol()) aEnd.SetCol(rDoc.MaxCol()); } } void ScRange::IncRowIfNotLessThan(const ScDocument& rDoc, SCROW nStartRow, SCROW nOffset) { if (aStart.Row() >= nStartRow) { aStart.IncRow(nOffset); if (aStart.Row() < 0) aStart.SetRow(0); else if(aStart.Row() > rDoc.MaxRow()) aStart.SetRow(rDoc.MaxRow()); } if (aEnd.Row() >= nStartRow) { aEnd.IncRow(nOffset); if (aEnd.Row() < 0) aEnd.SetRow(0); else if(aEnd.Row() > rDoc.MaxRow()) aEnd.SetRow(rDoc.MaxRow()); } } bool ScRange::IsEndColSticky( const ScDocument& rDoc ) const { // Only in an actual column range, i.e. not if both columns are MAXCOL. return aEnd.Col() == rDoc.MaxCol() && aStart.Col() < aEnd.Col(); } bool ScRange::IsEndRowSticky( const ScDocument& rDoc ) const { // Only in an actual row range, i.e. not if both rows are MAXROW. return aEnd.Row() == rDoc.MaxRow() && aStart.Row() < aEnd.Row(); } void ScRange::IncEndColSticky( const ScDocument& rDoc, SCCOL nDelta ) { SCCOL nCol = aEnd.Col(); if (aStart.Col() >= nCol) { // Less than two columns => not sticky. aEnd.IncCol( nDelta); return; } const SCCOL nMaxCol = rDoc.MaxCol(); if (nCol == nMaxCol) // already sticky return; if (nCol < nMaxCol) aEnd.SetCol( ::std::min( static_cast(nCol + nDelta), nMaxCol)); else aEnd.IncCol( nDelta); // was greater than nMaxCol, caller should know... } void ScRange::IncEndRowSticky( const ScDocument& rDoc, SCROW nDelta ) { SCROW nRow = aEnd.Row(); if (aStart.Row() >= nRow) { // Less than two rows => not sticky. aEnd.IncRow( nDelta); return; } if (nRow == rDoc.MaxRow()) // already sticky return; if (nRow < rDoc.MaxRow()) aEnd.SetRow( ::std::min( static_cast(nRow + nDelta), rDoc.MaxRow())); else aEnd.IncRow( nDelta); // was greater than rDoc.MaxRow(), caller should know... } OUString ScAddress::GetColRowString() const { OUStringBuffer aString; switch( detailsOOOa1.eConv ) { default : case formula::FormulaGrammar::CONV_OOO: case formula::FormulaGrammar::CONV_XL_A1: case formula::FormulaGrammar::CONV_XL_OOX: lcl_ScColToAlpha( aString, nCol); aString.append(nRow+1); break; case formula::FormulaGrammar::CONV_XL_R1C1: lcl_r1c1_append_r ( aString, nRow, false/*bAbsolute*/, detailsOOOa1 ); lcl_r1c1_append_c ( aString, nCol, false/*bAbsolute*/, detailsOOOa1 ); break; } return aString.makeStringAndClear(); } OUString ScRefAddress::GetRefString( const ScDocument& rDoc, SCTAB nActTab, const ScAddress::Details& rDetails ) const { if ( Tab()+1 > rDoc.GetTableCount() ) return ScCompiler::GetNativeSymbol(ocErrRef); ScRefFlags nFlags = ScRefFlags::VALID; if ( nActTab != Tab() ) { nFlags |= ScRefFlags::TAB_3D; if ( !bRelTab ) nFlags |= ScRefFlags::TAB_ABS; } if ( !bRelCol ) nFlags |= ScRefFlags::COL_ABS; if ( !bRelRow ) nFlags |= ScRefFlags::ROW_ABS; return aAdr.Format(nFlags, &rDoc, rDetails); } bool AlphaToCol(const ScDocument& rDoc, SCCOL& rCol, const OUString& rStr) { SCCOL nResult = 0; sal_Int32 nStop = rStr.getLength(); sal_Int32 nPos = 0; sal_Unicode c; const SCCOL nMaxCol = rDoc.MaxCol(); while (nResult <= nMaxCol && nPos < nStop && (c = rStr[nPos]) != 0 && rtl::isAsciiAlpha(c)) { if (nPos > 0) nResult = (nResult + 1) * 26; nResult += ScGlobal::ToUpperAlpha(c) - 'A'; ++nPos; } bool bOk = (rDoc.ValidCol(nResult) && nPos > 0); if (bOk) rCol = nResult; return bOk; } /* vim:set shiftwidth=4 softtabstop=4 expandtab: */