blob: 5e67187526d2b3cb03a35e84771bdf98ec6abcbb [file] [log] [blame]
/**************************************************************
*
* 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
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*
*************************************************************/
// MARKER(update_precomp.py): autogen include statement, do not remove
#include "precompiled_sc.hxx"
#include "reftokenhelper.hxx"
#include "document.hxx"
#include "rangeutl.hxx"
#include "compiler.hxx"
#include "tokenarray.hxx"
#include "rtl/ustring.hxx"
#include "formula/grammar.hxx"
#include "formula/token.hxx"
using namespace formula;
using ::std::vector;
using ::std::auto_ptr;
using ::rtl::OUString;
void ScRefTokenHelper::compileRangeRepresentation(
vector<ScSharedTokenRef>& rRefTokens, const OUString& rRangeStr, ScDocument* pDoc, FormulaGrammar::Grammar eGrammar)
{
const sal_Unicode cSep = GetScCompilerNativeSymbol(ocSep).GetChar(0);
const sal_Unicode cQuote = '\'';
// #i107275# ignore parentheses
OUString aRangeStr = rRangeStr;
while( (aRangeStr.getLength() >= 2) && (aRangeStr[ 0 ] == '(') && (aRangeStr[ aRangeStr.getLength() - 1 ] == ')') )
aRangeStr = aRangeStr.copy( 1, aRangeStr.getLength() - 2 );
bool bFailure = false;
sal_Int32 nOffset = 0;
while (nOffset >= 0 && !bFailure)
{
OUString aToken;
ScRangeStringConverter::GetTokenByOffset(aToken, aRangeStr, nOffset, cSep, cQuote);
if (nOffset < 0)
break;
ScCompiler aCompiler(pDoc, ScAddress(0,0,0));
aCompiler.SetGrammar(eGrammar);
auto_ptr<ScTokenArray> pArray(aCompiler.CompileString(aToken));
// There MUST be exactly one reference per range token and nothing
// else, and it MUST be a valid reference, not some #REF!
sal_uInt16 nLen = pArray->GetLen();
if (!nLen)
continue; // Should a missing range really be allowed?
if (nLen != 1)
bFailure = true;
else
{
pArray->Reset();
const FormulaToken* p = pArray->GetNextReference();
if (!p)
bFailure = true;
else
{
const ScToken* pT = static_cast<const ScToken*>(p);
switch (pT->GetType())
{
case svSingleRef:
if (!pT->GetSingleRef().Valid())
bFailure = true;
break;
case svDoubleRef:
if (!pT->GetDoubleRef().Valid())
bFailure = true;
break;
case svExternalSingleRef:
if (!pT->GetSingleRef().ValidExternal())
bFailure = true;
break;
case svExternalDoubleRef:
if (!pT->GetDoubleRef().ValidExternal())
bFailure = true;
break;
default:
;
}
if (!bFailure)
rRefTokens.push_back(
ScSharedTokenRef(static_cast<ScToken*>(p->Clone())));
}
}
#if 0
switch (p->GetType())
{
case svSingleRef:
fprintf(stdout, "ScChart2DataProvider::compileRangeRepresentation: single ref\n");
break;
case svDoubleRef:
fprintf(stdout, "ScChart2DataProvider::compileRangeRepresentation: double ref\n");
break;
case svExternalSingleRef:
fprintf(stdout, "ScChart2DataProvider::compileRangeRepresentation: external single ref\n");
break;
case svExternalDoubleRef:
fprintf(stdout, "ScChart2DataProvider::compileRangeRepresentation: external double ref\n");
break;
default:
;
}
#endif
}
if (bFailure)
rRefTokens.clear();
}
bool ScRefTokenHelper::getRangeFromToken(ScRange& rRange, const ScSharedTokenRef& pToken, bool bExternal)
{
StackVar eType = pToken->GetType();
switch (pToken->GetType())
{
case svSingleRef:
case svExternalSingleRef:
{
if ((eType == svExternalSingleRef && !bExternal) ||
(eType == svSingleRef && bExternal))
return false;
const ScSingleRefData& rRefData = pToken->GetSingleRef();
if(!rRefData.Valid())
{
OSL_ENSURE(false, "RefData out of range, correct before usage (!)");
}
rRange.aStart.SetCol(rRefData.nCol);
rRange.aStart.SetRow(rRefData.nRow);
rRange.aStart.SetTab(rRefData.nTab);
rRange.aEnd = rRange.aStart;
return true;
}
case svDoubleRef:
case svExternalDoubleRef:
{
if ((eType == svExternalDoubleRef && !bExternal) ||
(eType == svDoubleRef && bExternal))
return false;
const ScComplexRefData& rRefData = pToken->GetDoubleRef();
if(!rRefData.Valid())
{
OSL_ENSURE(false, "RefData out of range, correct before usage (!)");
}
rRange.aStart.SetCol(rRefData.Ref1.nCol);
rRange.aStart.SetRow(rRefData.Ref1.nRow);
rRange.aStart.SetTab(rRefData.Ref1.nTab);
rRange.aEnd.SetCol(rRefData.Ref2.nCol);
rRange.aEnd.SetRow(rRefData.Ref2.nRow);
rRange.aEnd.SetTab(rRefData.Ref2.nTab);
return true;
}
default:
; // do nothing
}
return false;
}
void ScRefTokenHelper::getRangeListFromTokens(ScRangeList& rRangeList, const vector<ScSharedTokenRef>& rTokens)
{
vector<ScSharedTokenRef>::const_iterator itr = rTokens.begin(), itrEnd = rTokens.end();
for (; itr != itrEnd; ++itr)
{
ScRange aRange;
getRangeFromToken(aRange, *itr);
rRangeList.Append(aRange);
}
}
void ScRefTokenHelper::getTokenFromRange(ScSharedTokenRef& pToken, const ScRange& rRange)
{
ScComplexRefData aData;
aData.InitFlags();
aData.Ref1.nCol = rRange.aStart.Col();
aData.Ref1.nRow = rRange.aStart.Row();
aData.Ref1.nTab = rRange.aStart.Tab();
aData.Ref1.SetColRel(false);
aData.Ref1.SetRowRel(false);
aData.Ref1.SetTabRel(false);
aData.Ref1.SetFlag3D(true);
aData.Ref2.nCol = rRange.aEnd.Col();
aData.Ref2.nRow = rRange.aEnd.Row();
aData.Ref2.nTab = rRange.aEnd.Tab();
aData.Ref2.SetColRel(false);
aData.Ref2.SetRowRel(false);
aData.Ref2.SetTabRel(false);
// Display sheet name on 2nd reference only when the 1st and 2nd refs are on
// different sheets.
aData.Ref2.SetFlag3D(aData.Ref1.nTab != aData.Ref2.nTab);
pToken.reset(new ScDoubleRefToken(aData));
}
void ScRefTokenHelper::getTokensFromRangeList(vector<ScSharedTokenRef>& pTokens, const ScRangeList& rRanges)
{
vector<ScSharedTokenRef> aTokens;
sal_uInt32 nCount = rRanges.Count();
aTokens.reserve(nCount);
for (sal_uInt32 i = 0; i < nCount; ++i)
{
ScRange* pRange = static_cast<ScRange*>(rRanges.GetObject(i));
if (!pRange)
// failed.
return;
ScSharedTokenRef pToken;
ScRefTokenHelper::getTokenFromRange(pToken,* pRange);
aTokens.push_back(pToken);
}
pTokens.swap(aTokens);
}
bool ScRefTokenHelper::isRef(const ScSharedTokenRef& pToken)
{
switch (pToken->GetType())
{
case svSingleRef:
case svDoubleRef:
case svExternalSingleRef:
case svExternalDoubleRef:
return true;
default:
;
}
return false;
}
bool ScRefTokenHelper::isExternalRef(const ScSharedTokenRef& pToken)
{
switch (pToken->GetType())
{
case svExternalSingleRef:
case svExternalDoubleRef:
return true;
default:
;
}
return false;
}
bool ScRefTokenHelper::intersects(const vector<ScSharedTokenRef>& rTokens, const ScSharedTokenRef& pToken)
{
if (!isRef(pToken))
return false;
bool bExternal = isExternalRef(pToken);
sal_uInt16 nFileId = bExternal ? pToken->GetIndex() : 0;
ScRange aRange;
getRangeFromToken(aRange, pToken, bExternal);
vector<ScSharedTokenRef>::const_iterator itr = rTokens.begin(), itrEnd = rTokens.end();
for (; itr != itrEnd; ++itr)
{
const ScSharedTokenRef& p = *itr;
if (!isRef(p))
continue;
if (bExternal != isExternalRef(p))
continue;
ScRange aRange2;
getRangeFromToken(aRange2, p, bExternal);
if (bExternal && nFileId != p->GetIndex())
// different external file
continue;
if (aRange.Intersects(aRange2))
return true;
}
return false;
}
namespace {
class JoinRefTokenRanges
{
public:
/**
* Insert a new reference token into the existing list of reference tokens,
* but in that process, try to join as many adjacent ranges as possible.
*
* @param rTokens existing list of reference tokens
* @param rToken new token
*/
void operator() (vector<ScSharedTokenRef>& rTokens, const ScSharedTokenRef& pToken)
{
join(rTokens, pToken);
}
private:
/**
* Check two 1-dimensional ranges to see if they overlap each other.
*
* @param nMin1 min value of range 1
* @param nMax1 max value of range 1
* @param nMin2 min value of range 2
* @param nMax2 max value of range 2
* @param rNewMin min value of new range in case they overlap
* @param rNewMax max value of new range in case they overlap
*/
template<typename T>
static bool overlaps(T nMin1, T nMax1, T nMin2, T nMax2, T& rNewMin, T& rNewMax)
{
bool bDisjoint1 = (nMin1 > nMax2) && (nMin1 - nMax2 > 1);
bool bDisjoint2 = (nMin2 > nMax1) && (nMin2 - nMax1 > 1);
if (bDisjoint1 || bDisjoint2)
// These two ranges cannot be joined. Move on.
return false;
T nMin = nMin1 < nMin2 ? nMin1 : nMin2;
T nMax = nMax1 > nMax2 ? nMax1 : nMax2;
rNewMin = nMin;
rNewMax = nMax;
return true;
}
bool isContained(const ScComplexRefData& aOldData, const ScComplexRefData& aData) const
{
// Check for containment.
bool bRowsContained = (aOldData.Ref1.nRow <= aData.Ref1.nRow) && (aData.Ref2.nRow <= aOldData.Ref2.nRow);
bool bColsContained = (aOldData.Ref1.nCol <= aData.Ref1.nCol) && (aData.Ref2.nCol <= aOldData.Ref2.nCol);
return (bRowsContained && bColsContained);
}
void join(vector<ScSharedTokenRef>& rTokens, const ScSharedTokenRef& pToken)
{
// Normalize the token to a double reference.
ScComplexRefData aData;
if (!ScRefTokenHelper::getDoubleRefDataFromToken(aData, pToken))
return;
// Get the information of the new token.
bool bExternal = ScRefTokenHelper::isExternalRef(pToken);
sal_uInt16 nFileId = bExternal ? pToken->GetIndex() : 0;
String aTabName = bExternal ? pToken->GetString() : String();
bool bJoined = false;
vector<ScSharedTokenRef>::iterator itr = rTokens.begin(), itrEnd = rTokens.end();
for (; itr != itrEnd; ++itr)
{
ScSharedTokenRef& pOldToken = *itr;
if (!ScRefTokenHelper::isRef(pOldToken))
// A non-ref token should not have been added here in the first
// place!
continue;
if (bExternal != ScRefTokenHelper::isExternalRef(pOldToken))
// External and internal refs don't mix.
continue;
if (bExternal)
{
if (nFileId != pOldToken->GetIndex())
// Different external files.
continue;
if (aTabName != pOldToken->GetString())
// Different table names.
continue;
}
ScComplexRefData aOldData;
if (!ScRefTokenHelper::getDoubleRefDataFromToken(aOldData, pOldToken))
continue;
if (aData.Ref1.nTab != aOldData.Ref1.nTab || aData.Ref2.nTab != aOldData.Ref2.nTab)
// Sheet ranges differ.
continue;
if (isContained(aOldData, aData))
// This new range is part of an existing range. Skip it.
return;
bool bSameRows = (aData.Ref1.nRow == aOldData.Ref1.nRow) && (aData.Ref2.nRow == aOldData.Ref2.nRow);
bool bSameCols = (aData.Ref1.nCol == aOldData.Ref1.nCol) && (aData.Ref2.nCol == aOldData.Ref2.nCol);
ScComplexRefData aNewData = aOldData;
bool bJoinRanges = false;
if (bSameRows)
{
bJoinRanges = overlaps(
aData.Ref1.nCol, aData.Ref2.nCol, aOldData.Ref1.nCol, aOldData.Ref2.nCol,
aNewData.Ref1.nCol, aNewData.Ref2.nCol);
}
else if (bSameCols)
{
bJoinRanges = overlaps(
aData.Ref1.nRow, aData.Ref2.nRow, aOldData.Ref1.nRow, aOldData.Ref2.nRow,
aNewData.Ref1.nRow, aNewData.Ref2.nRow);
}
if (bJoinRanges)
{
if (bExternal)
pOldToken.reset(new ScExternalDoubleRefToken(nFileId, aTabName, aNewData));
else
pOldToken.reset(new ScDoubleRefToken(aNewData));
bJoined = true;
break;
}
}
if (bJoined)
{
if (rTokens.size() == 1)
// There is only one left. No need to do more joining.
return;
// Pop the last token from the list, and keep joining recursively.
ScSharedTokenRef p = rTokens.back();
rTokens.pop_back();
join(rTokens, p);
}
else
rTokens.push_back(pToken);
}
};
}
void ScRefTokenHelper::join(vector<ScSharedTokenRef>& rTokens, const ScSharedTokenRef& pToken)
{
JoinRefTokenRanges join;
join(rTokens, pToken);
}
bool ScRefTokenHelper::getDoubleRefDataFromToken(ScComplexRefData& rData, const ScSharedTokenRef& pToken)
{
switch (pToken->GetType())
{
case svSingleRef:
case svExternalSingleRef:
{
const ScSingleRefData& r = pToken->GetSingleRef();
rData.Ref1 = r;
rData.Ref1.SetFlag3D(true);
rData.Ref2 = r;
rData.Ref2.SetFlag3D(false); // Don't display sheet name on second reference.
}
break;
case svDoubleRef:
case svExternalDoubleRef:
rData = pToken->GetDoubleRef();
break;
default:
// Not a reference token. Bail out.
return false;
}
return true;
}