summaryrefslogtreecommitdiffstats
path: root/sc/source/core/tool/reftokenhelper.cxx
diff options
context:
space:
mode:
Diffstat (limited to 'sc/source/core/tool/reftokenhelper.cxx')
-rw-r--r--sc/source/core/tool/reftokenhelper.cxx486
1 files changed, 486 insertions, 0 deletions
diff --git a/sc/source/core/tool/reftokenhelper.cxx b/sc/source/core/tool/reftokenhelper.cxx
new file mode 100644
index 000000000..a4992485e
--- /dev/null
+++ b/sc/source/core/tool/reftokenhelper.cxx
@@ -0,0 +1,486 @@
+/* -*- 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 <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>
+
+#include <memory>
+
+using namespace formula;
+
+using ::std::vector;
+
+void ScRefTokenHelper::compileRangeRepresentation(
+ vector<ScTokenRef>& rRefTokens, const OUString& rRangeStr, ScDocument& rDoc,
+ const sal_Unicode cSep, FormulaGrammar::Grammar eGrammar, bool bOnly3DRef)
+{
+ // #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);
+ if (nOffset < 0)
+ break;
+
+ ScCompiler aCompiler(rDoc, ScAddress(0,0,0), eGrammar);
+ std::unique_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;
+ break;
+ }
+
+ const FormulaToken* p = pArray->FirstToken();
+ if (!p)
+ {
+ bFailure = true;
+ break;
+ }
+
+ switch (p->GetType())
+ {
+ case svSingleRef:
+ {
+ const ScSingleRefData& rRef = *p->GetSingleRef();
+ if (!rRef.Valid(rDoc))
+ bFailure = true;
+ else if (bOnly3DRef && !rRef.IsFlag3D())
+ bFailure = true;
+ }
+ break;
+ case svDoubleRef:
+ {
+ const ScComplexRefData& rRef = *p->GetDoubleRef();
+ if (!rRef.Valid(rDoc))
+ bFailure = true;
+ else if (bOnly3DRef && !rRef.Ref1.IsFlag3D())
+ bFailure = true;
+ }
+ break;
+ case svExternalSingleRef:
+ {
+ if (!p->GetSingleRef()->ValidExternal(rDoc))
+ bFailure = true;
+ }
+ break;
+ case svExternalDoubleRef:
+ {
+ if (!p->GetDoubleRef()->ValidExternal(rDoc))
+ bFailure = true;
+ }
+ break;
+ case svString:
+ if (p->GetString().isEmpty())
+ bFailure = true;
+ break;
+ case svIndex:
+ {
+ if (p->GetOpCode() == ocName)
+ {
+ ScRangeData* pNameRange = rDoc.FindRangeNameBySheetAndIndex(p->GetSheet(), p->GetIndex());
+ if (!pNameRange->HasReferences())
+ bFailure = true;
+ }
+ }
+ break;
+ default:
+ bFailure = true;
+ break;
+ }
+ if (!bFailure)
+ rRefTokens.emplace_back(p->Clone());
+
+ }
+ if (bFailure)
+ rRefTokens.clear();
+}
+
+bool ScRefTokenHelper::getRangeFromToken(
+ const ScDocument* pDoc,
+ ScRange& rRange, const ScTokenRef& pToken, const ScAddress& rPos, 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();
+ rRange.aStart = rRefData.toAbs(*pDoc, rPos);
+ rRange.aEnd = rRange.aStart;
+ return true;
+ }
+ case svDoubleRef:
+ case svExternalDoubleRef:
+ {
+ if ((eType == svExternalDoubleRef && !bExternal) ||
+ (eType == svDoubleRef && bExternal))
+ return false;
+
+ const ScComplexRefData& rRefData = *pToken->GetDoubleRef();
+ rRange = rRefData.toAbs(*pDoc, rPos);
+ return true;
+ }
+ case svIndex:
+ {
+ if (pToken->GetOpCode() == ocName)
+ {
+ ScRangeData* pNameRange = pDoc->FindRangeNameBySheetAndIndex(pToken->GetSheet(), pToken->GetIndex());
+ if (pNameRange->IsReference(rRange, rPos))
+ return true;
+ }
+ return false;
+ }
+ default:
+ ; // do nothing
+ }
+ return false;
+}
+
+void ScRefTokenHelper::getRangeListFromTokens(
+ const ScDocument* pDoc, ScRangeList& rRangeList, const vector<ScTokenRef>& rTokens, const ScAddress& rPos)
+{
+ for (const auto& rToken : rTokens)
+ {
+ ScRange aRange;
+ getRangeFromToken(pDoc, aRange, rToken, rPos);
+ rRangeList.push_back(aRange);
+ }
+}
+
+void ScRefTokenHelper::getTokenFromRange(const ScDocument* pDoc, ScTokenRef& pToken, const ScRange& rRange)
+{
+ ScComplexRefData aData;
+ aData.InitRange(rRange);
+ aData.Ref1.SetFlag3D(true);
+
+ // Display sheet name on 2nd reference only when the 1st and 2nd refs are on
+ // different sheets.
+ aData.Ref2.SetFlag3D(rRange.aStart.Tab() != rRange.aEnd.Tab());
+
+ pToken.reset(new ScDoubleRefToken(pDoc->GetSheetLimits(), aData));
+}
+
+void ScRefTokenHelper::getTokensFromRangeList(const ScDocument* pDoc, vector<ScTokenRef>& pTokens, const ScRangeList& rRanges)
+{
+ vector<ScTokenRef> aTokens;
+ size_t nCount = rRanges.size();
+ aTokens.reserve(nCount);
+ for (size_t i = 0; i < nCount; ++i)
+ {
+ const ScRange & rRange = rRanges[i];
+ ScTokenRef pToken;
+ ScRefTokenHelper::getTokenFromRange(pDoc, pToken, rRange);
+ aTokens.push_back(pToken);
+ }
+ pTokens.swap(aTokens);
+}
+
+bool ScRefTokenHelper::isRef(const ScTokenRef& pToken)
+{
+ switch (pToken->GetType())
+ {
+ case svSingleRef:
+ case svDoubleRef:
+ case svExternalSingleRef:
+ case svExternalDoubleRef:
+ return true;
+ default:
+ ;
+ }
+ return false;
+}
+
+bool ScRefTokenHelper::isExternalRef(const ScTokenRef& pToken)
+{
+ switch (pToken->GetType())
+ {
+ case svExternalSingleRef:
+ case svExternalDoubleRef:
+ return true;
+ default:
+ ;
+ }
+ return false;
+}
+
+bool ScRefTokenHelper::intersects(
+ const ScDocument* pDoc,
+ const vector<ScTokenRef>& rTokens, const ScTokenRef& pToken, const ScAddress& rPos)
+{
+ if (!isRef(pToken))
+ return false;
+
+ bool bExternal = isExternalRef(pToken);
+ sal_uInt16 nFileId = bExternal ? pToken->GetIndex() : 0;
+
+ ScRange aRange;
+ getRangeFromToken(pDoc, aRange, pToken, rPos, bExternal);
+
+ for (const ScTokenRef& p : rTokens)
+ {
+ if (!isRef(p))
+ continue;
+
+ if (bExternal != isExternalRef(p))
+ continue;
+
+ ScRange aRange2;
+ getRangeFromToken(pDoc, aRange2, p, rPos, 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() (const ScDocument* pDoc, vector<ScTokenRef>& rTokens, const ScTokenRef& pToken, const ScAddress& rPos)
+ {
+ join(pDoc, rTokens, pToken, rPos);
+ }
+
+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 = std::min(nMin1, nMin2);
+ T nMax = std::max(nMax1, nMax2);
+
+ rNewMin = nMin;
+ rNewMax = nMax;
+
+ return true;
+ }
+
+ void join(const ScDocument* pDoc, vector<ScTokenRef>& rTokens, const ScTokenRef& pToken, const ScAddress& rPos)
+ {
+ // 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;
+ svl::SharedString aTabName = bExternal ? pToken->GetString() : svl::SharedString::getEmptyString();
+
+ bool bJoined = false;
+ for (ScTokenRef& pOldToken : rTokens)
+ {
+ 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;
+
+ ScRange aOld = aOldData.toAbs(*pDoc, rPos), aNew = aData.toAbs(*pDoc, rPos);
+
+ if (aNew.aStart.Tab() != aOld.aStart.Tab() || aNew.aEnd.Tab() != aOld.aEnd.Tab())
+ // Sheet ranges differ.
+ continue;
+
+ if (aOld.Contains(aNew))
+ // This new range is part of an existing range. Skip it.
+ return;
+
+ bool bSameRows = (aNew.aStart.Row() == aOld.aStart.Row()) && (aNew.aEnd.Row() == aOld.aEnd.Row());
+ bool bSameCols = (aNew.aStart.Col() == aOld.aStart.Col()) && (aNew.aEnd.Col() == aOld.aEnd.Col());
+ ScComplexRefData aNewData = aOldData;
+ bool bJoinRanges = false;
+ if (bSameRows)
+ {
+ SCCOL nNewMin, nNewMax;
+ bJoinRanges = overlaps(
+ aNew.aStart.Col(), aNew.aEnd.Col(), aOld.aStart.Col(), aOld.aEnd.Col(),
+ nNewMin, nNewMax);
+
+ if (bJoinRanges)
+ {
+ aNew.aStart.SetCol(nNewMin);
+ aNew.aEnd.SetCol(nNewMax);
+ aNewData.SetRange(pDoc->GetSheetLimits(), aNew, rPos);
+ }
+ }
+ else if (bSameCols)
+ {
+ SCROW nNewMin, nNewMax;
+ bJoinRanges = overlaps(
+ aNew.aStart.Row(), aNew.aEnd.Row(), aOld.aStart.Row(), aOld.aEnd.Row(),
+ nNewMin, nNewMax);
+
+ if (bJoinRanges)
+ {
+ aNew.aStart.SetRow(nNewMin);
+ aNew.aEnd.SetRow(nNewMax);
+ aNewData.SetRange(pDoc->GetSheetLimits(), aNew, rPos);
+ }
+ }
+
+ if (bJoinRanges)
+ {
+ if (bExternal)
+ pOldToken.reset(new ScExternalDoubleRefToken(nFileId, aTabName, aNewData));
+ else
+ pOldToken.reset(new ScDoubleRefToken(pDoc->GetSheetLimits(), 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.
+ ScTokenRef p = rTokens.back();
+ rTokens.pop_back();
+ join(pDoc, rTokens, p, rPos);
+ }
+ else
+ rTokens.push_back(pToken);
+ }
+};
+
+}
+
+void ScRefTokenHelper::join(const ScDocument* pDoc, vector<ScTokenRef>& rTokens, const ScTokenRef& pToken, const ScAddress& rPos)
+{
+ JoinRefTokenRanges join;
+ join(pDoc, rTokens, pToken, rPos);
+}
+
+bool ScRefTokenHelper::getDoubleRefDataFromToken(ScComplexRefData& rData, const ScTokenRef& 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;
+}
+
+ScTokenRef ScRefTokenHelper::createRefToken(const ScDocument& rDoc, const ScAddress& rAddr)
+{
+ ScSingleRefData aRefData;
+ aRefData.InitAddress(rAddr);
+ ScTokenRef pRef(new ScSingleRefToken(rDoc.GetSheetLimits(), aRefData));
+ return pRef;
+}
+
+ScTokenRef ScRefTokenHelper::createRefToken(const ScDocument& rDoc, const ScRange& rRange)
+{
+ ScComplexRefData aRefData;
+ aRefData.InitRange(rRange);
+ ScTokenRef pRef(new ScDoubleRefToken(rDoc.GetSheetLimits(), aRefData));
+ return pRef;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */