334 lines
10 KiB
C++
334 lines
10 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 <o3tl/underlyingenumvalue.hxx>
|
|
|
|
#include <reffind.hxx>
|
|
#include <global.hxx>
|
|
#include <compiler.hxx>
|
|
#include <document.hxx>
|
|
#include <utility>
|
|
|
|
namespace {
|
|
|
|
// Include colon; addresses in range reference are handled individually.
|
|
const sal_Unicode pDelimiters[] = {
|
|
'=','(',')','+','-','*','/','^','&',' ','{','}','<','>',':', 0
|
|
};
|
|
|
|
bool IsText( sal_Unicode c )
|
|
{
|
|
bool bFound = ScGlobal::UnicodeStrChr( pDelimiters, c );
|
|
if (bFound)
|
|
// This is one of delimiters, therefore not text.
|
|
return false;
|
|
|
|
// argument separator is configurable.
|
|
const sal_Unicode sep = ScCompiler::GetNativeSymbolChar(ocSep);
|
|
return c != sep;
|
|
}
|
|
|
|
bool IsText( bool& bQuote, sal_Unicode c )
|
|
{
|
|
if (c == '\'')
|
|
{
|
|
bQuote = !bQuote;
|
|
return true;
|
|
}
|
|
if (bQuote)
|
|
return true;
|
|
|
|
return IsText(c);
|
|
}
|
|
|
|
/**
|
|
* Find first character position that is considered text. A character is
|
|
* considered a text when it's within the ascii range and when it's not a
|
|
* delimiter.
|
|
*/
|
|
sal_Int32 FindStartPos(const sal_Unicode* p, sal_Int32 nStartPos, sal_Int32 nEndPos)
|
|
{
|
|
while (nStartPos <= nEndPos && !IsText(p[nStartPos]))
|
|
++nStartPos;
|
|
|
|
return nStartPos;
|
|
}
|
|
|
|
sal_Int32 FindEndPosA1(const sal_Unicode* p, sal_Int32 nStartPos, sal_Int32 nEndPos)
|
|
{
|
|
bool bQuote = false;
|
|
sal_Int32 nNewEnd = nStartPos;
|
|
while (nNewEnd <= nEndPos && IsText(bQuote, p[nNewEnd]))
|
|
++nNewEnd;
|
|
|
|
return nNewEnd;
|
|
}
|
|
|
|
sal_Int32 FindEndPosR1C1(const sal_Unicode* p, sal_Int32 nStartPos, sal_Int32 nEndPos)
|
|
{
|
|
sal_Int32 nNewEnd = nStartPos;
|
|
p = &p[nStartPos];
|
|
for (; nNewEnd <= nEndPos; ++p, ++nNewEnd)
|
|
{
|
|
if (*p == '\'')
|
|
{
|
|
// Skip until the closing quote.
|
|
for (++p, ++nNewEnd; nNewEnd <= nEndPos; ++p, ++nNewEnd)
|
|
if (*p == '\'')
|
|
break;
|
|
if (nNewEnd > nEndPos)
|
|
break;
|
|
}
|
|
else if (*p == '[')
|
|
{
|
|
// Skip until the closing bracket.
|
|
for (++p, ++nNewEnd; nNewEnd <= nEndPos; ++p, ++nNewEnd)
|
|
if (*p == ']')
|
|
break;
|
|
if (nNewEnd > nEndPos)
|
|
break;
|
|
}
|
|
else if (!IsText(*p))
|
|
break;
|
|
}
|
|
|
|
return nNewEnd;
|
|
}
|
|
|
|
/**
|
|
* Find last character position that is considered text, from the specified
|
|
* start position.
|
|
*/
|
|
sal_Int32 FindEndPos(const sal_Unicode* p, sal_Int32 nStartPos, sal_Int32 nEndPos,
|
|
formula::FormulaGrammar::AddressConvention eConv)
|
|
{
|
|
switch (eConv)
|
|
{
|
|
case formula::FormulaGrammar::CONV_XL_R1C1:
|
|
return FindEndPosR1C1(p, nStartPos, nEndPos);
|
|
case formula::FormulaGrammar::CONV_OOO:
|
|
case formula::FormulaGrammar::CONV_XL_A1:
|
|
default:
|
|
return FindEndPosA1(p, nStartPos, nEndPos);
|
|
}
|
|
}
|
|
|
|
void ExpandToTextA1(const sal_Unicode* p, sal_Int32 nLen, sal_Int32& rStartPos, sal_Int32& rEndPos)
|
|
{
|
|
bool bQuote = false; // skip quoted text
|
|
while (rStartPos > 0 && IsText(bQuote, p[rStartPos - 1]) )
|
|
--rStartPos;
|
|
if (rEndPos)
|
|
--rEndPos;
|
|
while (rEndPos+1 < nLen && IsText(p[rEndPos + 1]) )
|
|
++rEndPos;
|
|
}
|
|
|
|
void ExpandToTextR1C1(const sal_Unicode* p, sal_Int32 nLen, sal_Int32& rStartPos, sal_Int32& rEndPos)
|
|
{
|
|
// move back the start position to the first text character.
|
|
if (rStartPos > 0)
|
|
{
|
|
for (--rStartPos; rStartPos > 0; --rStartPos)
|
|
{
|
|
sal_Unicode c = p[rStartPos];
|
|
if (c == '\'')
|
|
{
|
|
// Skip until the opening quote.
|
|
for (--rStartPos; rStartPos > 0; --rStartPos)
|
|
{
|
|
c = p[rStartPos];
|
|
if (c == '\'')
|
|
break;
|
|
}
|
|
if (rStartPos == 0)
|
|
break;
|
|
}
|
|
else if (c == ']')
|
|
{
|
|
// Skip until the opening bracket.
|
|
for (--rStartPos; rStartPos > 0; --rStartPos)
|
|
{
|
|
c = p[rStartPos];
|
|
if (c == '[')
|
|
break;
|
|
}
|
|
if (rStartPos == 0)
|
|
break;
|
|
}
|
|
else if (!IsText(c))
|
|
{
|
|
++rStartPos;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
// move forward the end position to the last text character.
|
|
rEndPos = FindEndPosR1C1(p, rEndPos, nLen-1);
|
|
}
|
|
|
|
void ExpandToText(const sal_Unicode* p, sal_Int32 nLen, sal_Int32& rStartPos, sal_Int32& rEndPos,
|
|
formula::FormulaGrammar::AddressConvention eConv)
|
|
{
|
|
switch (eConv)
|
|
{
|
|
case formula::FormulaGrammar::CONV_XL_R1C1:
|
|
ExpandToTextR1C1(p, nLen, rStartPos, rEndPos);
|
|
break;
|
|
case formula::FormulaGrammar::CONV_OOO:
|
|
case formula::FormulaGrammar::CONV_XL_A1:
|
|
default:
|
|
ExpandToTextA1(p, nLen, rStartPos, rEndPos);
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
ScRefFinder::ScRefFinder(
|
|
OUString aFormula, const ScAddress& rPos,
|
|
ScDocument& rDoc, formula::FormulaGrammar::AddressConvention eConvP) :
|
|
maFormula(std::move(aFormula)),
|
|
meConv(eConvP),
|
|
mrDoc(rDoc),
|
|
maPos(rPos),
|
|
mnFound(0),
|
|
mnSelStart(0),
|
|
mnSelEnd(0)
|
|
{
|
|
}
|
|
|
|
ScRefFinder::~ScRefFinder()
|
|
{
|
|
}
|
|
|
|
static ScRefFlags lcl_NextFlags( ScRefFlags nOld )
|
|
{
|
|
const ScRefFlags Mask_ABS = ScRefFlags::COL_ABS | ScRefFlags::ROW_ABS | ScRefFlags::TAB_ABS;
|
|
ScRefFlags nNew = nOld & Mask_ABS;
|
|
nNew = ScRefFlags( o3tl::to_underlying(nNew) - 1 ) & Mask_ABS; // weiterzaehlen
|
|
|
|
if (!(nOld & ScRefFlags::TAB_3D))
|
|
nNew &= ~ScRefFlags::TAB_ABS; // not 3D -> never absolute!
|
|
|
|
return (nOld & ~Mask_ABS) | nNew;
|
|
}
|
|
|
|
void ScRefFinder::ToggleRel( sal_Int32 nStartPos, sal_Int32 nEndPos )
|
|
{
|
|
sal_Int32 nLen = maFormula.getLength();
|
|
if (nLen <= 0)
|
|
return;
|
|
const sal_Unicode* pSource = maFormula.getStr(); // for quick access
|
|
|
|
// expand selection, and instead of selection start- and end-index
|
|
|
|
if ( nEndPos < nStartPos )
|
|
::std::swap(nEndPos, nStartPos);
|
|
|
|
ExpandToText(pSource, nLen, nStartPos, nEndPos, meConv);
|
|
|
|
OUStringBuffer aResult;
|
|
OUString aExpr;
|
|
OUString aSep;
|
|
ScAddress aAddr;
|
|
mnFound = 0;
|
|
|
|
sal_Int32 nLoopStart = nStartPos;
|
|
while ( nLoopStart <= nEndPos )
|
|
{
|
|
// Determine the start and end positions of a text segment. Note that
|
|
// the end position returned from FindEndPos may be one position after
|
|
// the last character position in case of the last segment.
|
|
sal_Int32 nEStart = FindStartPos(pSource, nLoopStart, nEndPos);
|
|
sal_Int32 nEEnd = FindEndPos(pSource, nEStart, nEndPos, meConv);
|
|
|
|
aSep = maFormula.copy(nLoopStart, nEStart-nLoopStart);
|
|
if (nEEnd < maFormula.getLength())
|
|
aExpr = maFormula.copy(nEStart, nEEnd-nEStart);
|
|
else
|
|
aExpr = maFormula.copy(nEStart);
|
|
|
|
// Check the validity of the expression, and toggle the relative flag.
|
|
ScAddress::Details aDetails(meConv, maPos.Row(), maPos.Col());
|
|
ScAddress::ExternalInfo aExtInfo;
|
|
ScRefFlags nResult = aAddr.Parse(aExpr, mrDoc, aDetails, &aExtInfo);
|
|
if ( nResult & ScRefFlags::VALID )
|
|
{
|
|
ScRefFlags nFlags;
|
|
if( aExtInfo.mbExternal )
|
|
{ // retain external doc name and tab name before toggle relative flag
|
|
sal_Int32 nSep;
|
|
switch(meConv)
|
|
{
|
|
case formula::FormulaGrammar::CONV_XL_A1 :
|
|
case formula::FormulaGrammar::CONV_XL_OOX :
|
|
case formula::FormulaGrammar::CONV_XL_R1C1 :
|
|
nSep = aExpr.lastIndexOf('!');
|
|
break;
|
|
case formula::FormulaGrammar::CONV_OOO :
|
|
default:
|
|
nSep = aExpr.lastIndexOf('.');
|
|
break;
|
|
}
|
|
if (nSep >= 0)
|
|
{
|
|
OUString aRef = aExpr.copy(nSep+1);
|
|
std::u16string_view aExtDocNameTabName = aExpr.subView(0, nSep+1);
|
|
nResult = aAddr.Parse(aRef, mrDoc, aDetails);
|
|
aAddr.SetTab(0); // force to first tab to avoid error on checking
|
|
nFlags = lcl_NextFlags( nResult );
|
|
aExpr = aExtDocNameTabName + aAddr.Format(nFlags, &mrDoc, aDetails);
|
|
}
|
|
else
|
|
{
|
|
assert(!"Invalid syntax according to address convention.");
|
|
}
|
|
}
|
|
else
|
|
{
|
|
nFlags = lcl_NextFlags( nResult );
|
|
aExpr = aAddr.Format(nFlags, &mrDoc, aDetails);
|
|
}
|
|
|
|
sal_Int32 nAbsStart = nStartPos+aResult.getLength()+aSep.getLength();
|
|
|
|
if (!mnFound) // first reference ?
|
|
mnSelStart = nAbsStart;
|
|
mnSelEnd = nAbsStart + aExpr.getLength(); // selection, no indices
|
|
++mnFound;
|
|
}
|
|
|
|
// assemble
|
|
|
|
aResult.append(aSep + aExpr);
|
|
|
|
nLoopStart = nEEnd;
|
|
}
|
|
|
|
OUString aTotal = maFormula.subView(0, nStartPos) + aResult;
|
|
if (nEndPos < maFormula.getLength()-1)
|
|
aTotal += maFormula.subView(nEndPos+1);
|
|
|
|
maFormula = aTotal;
|
|
}
|
|
|
|
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
|