2603 lines
89 KiB
C++
2603 lines
89 KiB
C++
/* -*- 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 <sal/config.h>
|
|
|
|
#include <string_view>
|
|
|
|
#include <address.hxx>
|
|
#include <global.hxx>
|
|
#include <compiler.hxx>
|
|
#include <document.hxx>
|
|
#include <docsh.hxx>
|
|
#include <externalrefmgr.hxx>
|
|
|
|
#include <osl/diagnose.h>
|
|
#include <o3tl/underlyingenumvalue.hxx>
|
|
#include <com/sun/star/frame/XModel.hpp>
|
|
#include <com/sun/star/sheet/ExternalLinkInfo.hpp>
|
|
#include <com/sun/star/sheet/ExternalLinkType.hpp>
|
|
#include <sfx2/objsh.hxx>
|
|
#include <tools/urlobj.hxx>
|
|
#include <sal/log.hxx>
|
|
#include <rtl/character.hxx>
|
|
#include <unotools/charclass.hxx>
|
|
|
|
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;
|
|
bool is_neg = false;
|
|
|
|
if( *p == '-' )
|
|
{
|
|
is_neg = true;
|
|
p++;
|
|
}
|
|
else if( *p == '+' )
|
|
p++;
|
|
|
|
const sal_Int64 cutoff = is_neg ? std::numeric_limits<sal_Int64>::min() / 10
|
|
: -(std::numeric_limits<sal_Int64>::max() / 10);
|
|
const int cutlim = is_neg ? -(std::numeric_limits<sal_Int64>::min() % 10)
|
|
: std::numeric_limits<sal_Int64>::max() % 10;
|
|
|
|
while (rtl::isAsciiDigit( *p ))
|
|
{
|
|
int val = *p - '0';
|
|
if (accum < cutoff || (accum == cutoff && val > cutlim))
|
|
{
|
|
*pEnd = nullptr;
|
|
return 0;
|
|
}
|
|
accum = accum * 10 - val;
|
|
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; i<n; ++i)
|
|
{
|
|
if (!p1[i])
|
|
return false;
|
|
if (p1[i] != p2[i])
|
|
{
|
|
sal_Unicode c1 = p1[i];
|
|
if ('A' <= c1 && c1 <= 'Z')
|
|
c1 += 0x20;
|
|
if (c1 < 'a' || 'z' < c1)
|
|
return false; // not a letter
|
|
|
|
sal_Unicode c2 = p2[i];
|
|
if ('A' <= c2 && c2 <= 'Z')
|
|
c2 += 0x20;
|
|
if (c2 < 'a' || 'z' < c2)
|
|
return false; // not a letter to match
|
|
|
|
if (c1 != c2)
|
|
return false; // lower case doesn't match either
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
/** Determines the number of sheets an external reference spans and sets
|
|
rRange.aEnd.nTab accordingly. If a sheet is not found, the corresponding
|
|
bits in rFlags are cleared. pExtInfo is filled if it wasn't already. If in
|
|
cached order rStartTabName comes after rEndTabName, pExtInfo->maTabName
|
|
is set to rEndTabName.
|
|
@returns <FALSE/> if pExtInfo is already filled and rExternDocName does not
|
|
result in the identical file ID. Else <TRUE/>.
|
|
*/
|
|
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<sal_Int32>( p - pCurrentStart));
|
|
pCurrentStart = ++p;
|
|
}
|
|
}
|
|
else if (*p == ':')
|
|
{
|
|
break; // while
|
|
}
|
|
else
|
|
++p;
|
|
}
|
|
if (pCurrentStart < p)
|
|
aTabName += std::u16string_view( pCurrentStart, sal::static_int_cast<sal_Int32>( p - pCurrentStart));
|
|
if (aTabName.isEmpty())
|
|
return nullptr;
|
|
if (p == pMsoxlQuoteStop && *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<sal_Int32>( 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<sheet::ExternalLinkInfo>* 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<sheet::ExternalLinkInfo>* 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;
|
|
const sal_Unicode* pQuoted3DStop = 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<sal_Int32>( 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!
|
|
// Then again, 'Sheet1':'Sheet2'!C4 would be logical and Calc wrote
|
|
// that to OOXML but Excel instead does 'Sheet1:Sheet2'!C4 which is
|
|
// the worse you can get.
|
|
// Excel does not allow [ and ] 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 == ':')
|
|
{
|
|
// The incorrect 'Sheet1':'Sheet2' case. Just fall through.
|
|
}
|
|
else if (*p != '!')
|
|
{
|
|
rExternDocName.clear();
|
|
return start;
|
|
}
|
|
if (!rExternDocName.isEmpty())
|
|
{
|
|
sal_Int32 nOpen = rExternDocName.indexOf( '[');
|
|
if (nOpen == -1)
|
|
{
|
|
rExternDocName.clear();
|
|
// Look for 'Sheet1:Sheet2'!
|
|
if (*p == '!')
|
|
{
|
|
const sal_Unicode* pQ = start + 1;
|
|
do
|
|
{
|
|
if (*pQ == ':')
|
|
{
|
|
pMsoxlQuoteStop = pQ;
|
|
pQuoted3DStop = p - 1;
|
|
break;
|
|
}
|
|
}
|
|
while (++pQ < p);
|
|
}
|
|
}
|
|
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 = (pQuoted3DStop ? start + 1 : 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,
|
|
(pQuoted3DStop ? pQuoted3DStop : 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<SCCOL>( 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;
|
|
|
|
if (n < 0 || n >= rSheetLimits.GetMaxRowCount())
|
|
return nullptr;
|
|
}
|
|
else if( isRelative )
|
|
{
|
|
if( *pEnd != ']' )
|
|
return nullptr;
|
|
n += rDetails.nRow;
|
|
pEnd++;
|
|
|
|
if (n < 0 || n >= rSheetLimits.GetMaxRowCount())
|
|
return nullptr;
|
|
}
|
|
else
|
|
{
|
|
*nFlags |= ScRefFlags::ROW_ABS;
|
|
|
|
if (n <= 0)
|
|
return nullptr;
|
|
|
|
n--;
|
|
|
|
if (n >= rSheetLimits.GetMaxRowCount())
|
|
return nullptr;
|
|
}
|
|
|
|
pAddr->SetRow( static_cast<SCROW>( 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<SCCOL>( 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);
|
|
if (nullptr == pEnd || p == pEnd || n < 1)
|
|
return nullptr;
|
|
n -= 1;
|
|
if (n > rDoc.MaxRow())
|
|
return nullptr;
|
|
|
|
*nFlags |= ScRefFlags::ROW_VALID;
|
|
pAddr->SetRow( sal::static_int_cast<SCROW>(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<sheet::ExternalLinkInfo>* 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;
|
|
|
|
// Let's 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<SCCOL>( 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<SCROW>(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<sheet::ExternalLinkInfo>* 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<sheet::ExternalLinkInfo>* 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<sheet::ExternalLinkInfo>* 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<typename T > static void lcl_ScColToAlpha( T& rBuf, SCCOL nCol )
|
|
{
|
|
if (nCol < 26*26)
|
|
{
|
|
if (nCol < 26)
|
|
rBuf.append( static_cast<char>( 'A' + nCol ));
|
|
else
|
|
{
|
|
rBuf.append( static_cast<char>( 'A' + nCol / 26 - 1 ));
|
|
rBuf.append( static_cast<char>( 'A' + nCol % 26 ));
|
|
}
|
|
}
|
|
else
|
|
{
|
|
sal_Int32 nInsert = rBuf.getLength();
|
|
while (nCol >= 26)
|
|
{
|
|
SCCOL nC = nCol % 26;
|
|
rBuf.insert(nInsert, static_cast<char> ( 'A' + nC ));
|
|
nCol = sal::static_int_cast<SCCOL>( nCol - nC );
|
|
nCol = nCol / 26 - 1;
|
|
}
|
|
rBuf.insert(nInsert, static_cast<char> ( 'A' + nCol ));
|
|
}
|
|
}
|
|
|
|
void ScColToAlpha( OUStringBuffer& rBuf, SCCOL nCol)
|
|
{
|
|
lcl_ScColToAlpha(rBuf, nCol);
|
|
}
|
|
|
|
template <typename T > static void lcl_a1_append_c ( T &rString, int nCol, bool bIsAbs )
|
|
{
|
|
if( bIsAbs )
|
|
rString.append("$");
|
|
lcl_ScColToAlpha( rString, sal::static_int_cast<SCCOL>(nCol) );
|
|
}
|
|
|
|
template <typename T > static void lcl_a1_append_r ( T &rString, sal_Int32 nRow, bool bIsAbs )
|
|
{
|
|
if ( bIsAbs )
|
|
rString.append("$");
|
|
rString.append( nRow + 1 );
|
|
}
|
|
|
|
template <typename T > 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 <typename T > 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<typename T > 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;
|
|
|
|
sal_Int32 nQuotePos = rString.getLength();
|
|
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 + "]");
|
|
nQuotePos = rString.getLength();
|
|
}
|
|
rString.append(aTabName);
|
|
break;
|
|
}
|
|
if( nFlags & ScRefFlags::TAB2_3D )
|
|
{
|
|
lcl_Split_DocTab( rDoc, rRange.aEnd.Tab(), rDetails, nFlags, aTabName, aDocName );
|
|
ScCompiler::FormExcelSheetRange( rString, nQuotePos, aTabName);
|
|
}
|
|
rString.append("!");
|
|
}
|
|
|
|
// helpers used in ScRange::Format
|
|
static bool lcl_ColAbsFlagDiffer(const ScRefFlags nFlags)
|
|
{
|
|
return static_cast<bool>(nFlags & ScRefFlags::COL_ABS) != static_cast<bool>(nFlags & ScRefFlags::COL2_ABS);
|
|
}
|
|
static bool lcl_RowAbsFlagDiffer(const ScRefFlags nFlags)
|
|
{
|
|
return static_cast<bool>(nFlags & ScRefFlags::ROW_ABS) != static_cast<bool>(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;
|
|
r.append(":" + aEnd.Format(nFlags, pDoc, rDetails));
|
|
}
|
|
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)
|
|
{
|
|
SCCOL offset;
|
|
if (aStart.Col() > nStartCol)
|
|
{
|
|
offset = nOffset;
|
|
if (nStartCol + nOffset > aStart.Col())
|
|
offset = aStart.Col() - nStartCol;
|
|
else if (nStartCol - nOffset > aStart.Col())
|
|
offset = -1 * (aStart.Col() - nStartCol);
|
|
|
|
aStart.IncCol(offset);
|
|
if (aStart.Col() < 0)
|
|
aStart.SetCol(0);
|
|
else if(aStart.Col() > rDoc.MaxCol())
|
|
aStart.SetCol(rDoc.MaxCol());
|
|
}
|
|
if (aEnd.Col() > nStartCol)
|
|
{
|
|
offset = nOffset;
|
|
if (nStartCol + nOffset > aEnd.Col())
|
|
offset = aEnd.Col() - nStartCol;
|
|
else if (nStartCol - nOffset > aEnd.Col())
|
|
offset = -1 * (aEnd.Col() - nStartCol);
|
|
|
|
aEnd.IncCol(offset);
|
|
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)
|
|
{
|
|
SCROW offset;
|
|
if (aStart.Row() > nStartRow)
|
|
{
|
|
offset = nOffset;
|
|
if (nStartRow + nOffset > aStart.Row())
|
|
offset = aStart.Row() - nStartRow;
|
|
else if (nStartRow - nOffset > aStart.Row())
|
|
offset = -1 * (aStart.Row() - nStartRow);
|
|
|
|
aStart.IncRow(offset);
|
|
if (aStart.Row() < 0)
|
|
aStart.SetRow(0);
|
|
else if(aStart.Row() > rDoc.MaxRow())
|
|
aStart.SetRow(rDoc.MaxRow());
|
|
}
|
|
if (aEnd.Row() > nStartRow)
|
|
{
|
|
offset = nOffset;
|
|
if (nStartRow + nOffset > aEnd.Row())
|
|
offset = aEnd.Row() - nStartRow;
|
|
else if (nStartRow - nOffset > aEnd.Row())
|
|
offset = -1 * (aEnd.Row() - nStartRow);
|
|
|
|
aEnd.IncRow(offset);
|
|
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<SCCOL>(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<SCROW>(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, std::u16string_view rStr)
|
|
{
|
|
SCCOL nResult = 0;
|
|
sal_Int32 nStop = rStr.size();
|
|
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: */
|