Resolves: tdf#162093 TableRef item specifier may occur standalone
[LibreOffice.git] / sc / source / core / tool / address.cxx
blob6b44de5f1b1afe5407e2220ea2e969c7abffa2a6
1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /*
3 * This file is part of the LibreOffice project.
5 * This Source Code Form is subject to the terms of the Mozilla Public
6 * License, v. 2.0. If a copy of the MPL was not distributed with this
7 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
9 * This file incorporates work covered by the following license notice:
11 * Licensed to the Apache Software Foundation (ASF) under one or more
12 * contributor license agreements. See the NOTICE file distributed
13 * with this work for additional information regarding copyright
14 * ownership. The ASF licenses this file to you under the Apache
15 * License, Version 2.0 (the "License"); you may not use this file
16 * except in compliance with the License. You may obtain a copy of
17 * the License at http://www.apache.org/licenses/LICENSE-2.0 .
20 #include <sal/config.h>
22 #include <string_view>
24 #include <address.hxx>
25 #include <global.hxx>
26 #include <compiler.hxx>
27 #include <document.hxx>
28 #include <docsh.hxx>
29 #include <externalrefmgr.hxx>
31 #include <osl/diagnose.h>
32 #include <o3tl/underlyingenumvalue.hxx>
33 #include <com/sun/star/frame/XModel.hpp>
34 #include <com/sun/star/sheet/ExternalLinkInfo.hpp>
35 #include <com/sun/star/sheet/ExternalLinkType.hpp>
36 #include <sfx2/objsh.hxx>
37 #include <tools/urlobj.hxx>
38 #include <sal/log.hxx>
39 #include <rtl/character.hxx>
40 #include <unotools/charclass.hxx>
42 using namespace css;
44 const ScAddress::Details ScAddress::detailsOOOa1( formula::FormulaGrammar::CONV_OOO, 0, 0 );
46 ScAddress::Details::Details ( const ScDocument& rDoc,
47 const ScAddress& rAddr ) :
48 eConv( rDoc.GetAddressConvention() ),
49 nRow( rAddr.Row() ),
50 nCol( rAddr.Col() )
53 namespace {
55 const sal_Unicode* parseQuotedNameWithBuffer( const sal_Unicode* pStart, const sal_Unicode* p, OUString& rName )
57 // The current character must be on the 2nd quote.
59 // Push all the characters up to the current, but skip the very first
60 // character which is the opening quote.
61 OUStringBuffer aBuf(std::u16string_view(pStart+1, p-pStart-1));
63 ++p; // Skip the 2nd quote.
64 sal_Unicode cPrev = 0;
65 for (; *p; ++p)
67 if (*p == '\'')
69 if (cPrev == '\'')
71 // double single-quote equals one single quote.
72 aBuf.append(*p);
73 cPrev = 0;
74 continue;
77 else if (cPrev == '\'')
79 // We are past the closing quote. We're done!
80 rName = aBuf.makeStringAndClear();
81 return p;
83 else
84 aBuf.append(*p);
85 cPrev = *p;
88 return pStart;
91 /**
92 * Parse from the opening single quote to the closing single quote. Inside
93 * the quotes, a single quote character is encoded by double single-quote
94 * characters.
96 * @param p pointer to the first character to begin parsing.
97 * @param rName (reference) parsed name within the quotes. If the name is
98 * empty, either the parsing failed or it's an empty quote.
100 * @return pointer to the character immediately after the closing single
101 * quote.
103 const sal_Unicode* parseQuotedName( const sal_Unicode* p, OUString& rName )
105 if (*p != '\'')
106 return p;
108 const sal_Unicode* pStart = p;
109 sal_Unicode cPrev = 0;
110 for (++p; *p; ++p)
112 if (*p == '\'')
114 if (cPrev == '\'')
116 // double single-quote equals one single quote.
117 return parseQuotedNameWithBuffer(pStart, p, rName);
120 else if (cPrev == '\'')
122 // We are past the closing quote. We're done! Skip the opening
123 // and closing quotes.
124 rName = OUString(pStart+1, p - pStart-2);
125 return p;
128 cPrev = *p;
131 rName.clear();
132 return pStart;
137 static sal_Int64 sal_Unicode_strtol ( const sal_Unicode* p, const sal_Unicode** pEnd )
139 sal_Int64 accum = 0;
140 bool is_neg = false;
142 if( *p == '-' )
144 is_neg = true;
145 p++;
147 else if( *p == '+' )
148 p++;
150 const sal_Int64 cutoff = is_neg ? std::numeric_limits<sal_Int64>::min() / 10
151 : -(std::numeric_limits<sal_Int64>::max() / 10);
152 const int cutlim = is_neg ? -(std::numeric_limits<sal_Int64>::min() % 10)
153 : std::numeric_limits<sal_Int64>::max() % 10;
155 while (rtl::isAsciiDigit( *p ))
157 int val = *p - '0';
158 if (accum < cutoff || (accum == cutoff && val > cutlim))
160 *pEnd = nullptr;
161 return 0;
163 accum = accum * 10 - val;
164 p++;
167 *pEnd = p;
168 return is_neg ? accum : -accum;
171 static const sal_Unicode* lcl_eatWhiteSpace( const sal_Unicode* p )
173 if ( p )
175 while( *p == ' ' )
176 ++p;
178 return p;
181 // Compare ignore case ASCII.
182 static bool lcl_isString( const sal_Unicode* p1, const OUString& rStr )
184 const size_t n = rStr.getLength();
185 if (!n)
186 return false;
187 const sal_Unicode* p2 = rStr.getStr();
188 for (size_t i=0; i<n; ++i)
190 if (!p1[i])
191 return false;
192 if (p1[i] != p2[i])
194 sal_Unicode c1 = p1[i];
195 if ('A' <= c1 && c1 <= 'Z')
196 c1 += 0x20;
197 if (c1 < 'a' || 'z' < c1)
198 return false; // not a letter
200 sal_Unicode c2 = p2[i];
201 if ('A' <= c2 && c2 <= 'Z')
202 c2 += 0x20;
203 if (c2 < 'a' || 'z' < c2)
204 return false; // not a letter to match
206 if (c1 != c2)
207 return false; // lower case doesn't match either
210 return true;
213 /** Determines the number of sheets an external reference spans and sets
214 rRange.aEnd.nTab accordingly. If a sheet is not found, the corresponding
215 bits in rFlags are cleared. pExtInfo is filled if it wasn't already. If in
216 cached order rStartTabName comes after rEndTabName, pExtInfo->maTabName
217 is set to rEndTabName.
218 @returns <FALSE/> if pExtInfo is already filled and rExternDocName does not
219 result in the identical file ID. Else <TRUE/>.
221 static bool lcl_ScRange_External_TabSpan(
222 ScRange & rRange,
223 ScRefFlags & rFlags,
224 ScAddress::ExternalInfo* pExtInfo,
225 const OUString & rExternDocName,
226 const OUString & rStartTabName,
227 const OUString & rEndTabName,
228 const ScDocument& rDoc )
230 if (rExternDocName.isEmpty())
231 return !pExtInfo || !pExtInfo->mbExternal;
233 ScExternalRefManager* pRefMgr = rDoc.GetExternalRefManager();
234 if (pRefMgr->isOwnDocument( rExternDocName))
236 // This is an internal document. Get the sheet positions from the
237 // ScDocument instance.
238 if (!rStartTabName.isEmpty())
240 SCTAB nTab;
241 if (rDoc.GetTable(rStartTabName, nTab))
242 rRange.aStart.SetTab(nTab);
245 if (!rEndTabName.isEmpty())
247 SCTAB nTab;
248 if (rDoc.GetTable(rEndTabName, nTab))
249 rRange.aEnd.SetTab(nTab);
251 return !pExtInfo || !pExtInfo->mbExternal;
254 sal_uInt16 nFileId = pRefMgr->getExternalFileId( rExternDocName);
256 if (pExtInfo)
258 if (pExtInfo->mbExternal)
260 if (pExtInfo->mnFileId != nFileId)
261 return false;
263 else
265 pExtInfo->mbExternal = true;
266 pExtInfo->maTabName = rStartTabName;
267 pExtInfo->mnFileId = nFileId;
271 if (rEndTabName.isEmpty() || rStartTabName == rEndTabName)
273 rRange.aEnd.SetTab( rRange.aStart.Tab());
274 return true;
277 SCTAB nSpan = pRefMgr->getCachedTabSpan( nFileId, rStartTabName, rEndTabName);
278 if (nSpan == -1)
279 rFlags &= ~ScRefFlags(ScRefFlags::TAB_VALID | ScRefFlags::TAB2_VALID);
280 else if (nSpan == 0)
281 rFlags &= ~ScRefFlags::TAB2_VALID;
282 else if (nSpan >= 1)
283 rRange.aEnd.SetTab( rRange.aStart.Tab() + nSpan - 1);
284 else // (nSpan < -1)
286 rRange.aEnd.SetTab( rRange.aStart.Tab() - nSpan - 1);
287 if (pExtInfo)
288 pExtInfo->maTabName = rEndTabName;
290 return true;
293 /** Returns NULL if the string should be a sheet name, but is invalid.
294 Returns a pointer to the first character after the sheet name, if there was
295 any, else pointer to start.
296 @param pMsoxlQuoteStop
297 Starting _within_ a quoted name, but still may be 3D; quoted name stops
298 at pMsoxlQuoteStop
300 static const sal_Unicode * lcl_XL_ParseSheetRef( const sal_Unicode* start,
301 OUString& rExternTabName,
302 bool bAllow3D,
303 const sal_Unicode* pMsoxlQuoteStop,
304 const OUString* pErrRef )
306 OUString aTabName;
307 const sal_Unicode *p = start;
309 // XL only seems to use single quotes for sheet names.
310 if (pMsoxlQuoteStop)
312 const sal_Unicode* pCurrentStart = p;
313 while (p < pMsoxlQuoteStop)
315 if (*p == '\'')
317 // We pre-analyzed the quoting, no checks needed here.
318 if (*++p == '\'')
320 aTabName += std::u16string_view( pCurrentStart,
321 sal::static_int_cast<sal_Int32>( p - pCurrentStart));
322 pCurrentStart = ++p;
325 else if (*p == ':')
327 break; // while
329 else
330 ++p;
332 if (pCurrentStart < p)
333 aTabName += std::u16string_view( pCurrentStart, sal::static_int_cast<sal_Int32>( p - pCurrentStart));
334 if (aTabName.isEmpty())
335 return nullptr;
336 if (p == pMsoxlQuoteStop)
337 ++p; // position on ! of ...'!...
338 if( *p != '!' && ( !bAllow3D || *p != ':' ) )
339 return (!bAllow3D && *p == ':') ? p : start;
341 else if( *p == '\'')
343 p = parseQuotedName(p, aTabName);
344 if (aTabName.isEmpty())
345 return nullptr;
347 else if (pErrRef && lcl_isString( p, *pErrRef) && p[pErrRef->getLength()] == '!')
349 p += pErrRef->getLength(); // position after "#REF!" on '!'
350 // XXX NOTE: caller has to check the name and that it wasn't quoted.
351 aTabName = *pErrRef;
353 else
355 bool only_digits = true;
358 * Valid: Normal!a1
359 * Valid: x.y!a1
360 * Invalid: .y!a1
362 * Some names starting with digits are actually valid, but
363 * unparse quoted. Things are quite tricky: most sheet names
364 * starting with a digit are ok, but not those starting with
365 * "[0-9]*\." or "[0-9]+[eE]".
367 * Valid: 42!a1
368 * Valid: 4x!a1
369 * Invalid: 1.!a1
370 * Invalid: 1e!a1
372 while( true )
374 const sal_Unicode uc = *p;
375 if( rtl::isAsciiAlpha( uc ) || uc == '_' )
377 if( only_digits && p != start &&
378 (uc == 'e' || uc == 'E' ) )
380 p = start;
381 break;
383 only_digits = false;
384 p++;
386 else if( rtl::isAsciiDigit( uc ))
388 p++;
390 else if( uc == '.' )
392 if( only_digits ) // Valid, except after only digits.
394 p = start;
395 break;
397 p++;
399 else if (uc > 127)
401 // non ASCII character is allowed.
402 ++p;
404 else
405 break;
408 if( *p != '!' && ( !bAllow3D || *p != ':' ) )
409 return (!bAllow3D && *p == ':') ? p : start;
411 aTabName += std::u16string_view( start, sal::static_int_cast<sal_Int32>( p - start ) );
414 rExternTabName = aTabName;
415 return p;
418 /** Tries to obtain the external document index and replace by actual document
419 name.
421 @param ppErrRet
422 Contains the default pointer the caller would return if this method
423 returns FALSE, may be replaced by NULL for type or data errors.
425 @returns FALSE only if the input name is numeric and not within the index
426 sequence, or the link type cannot be determined or data mismatch. Returns
427 TRUE in all other cases, also when there is no index sequence or the input
428 name is not numeric.
430 static bool lcl_XL_getExternalDoc( const sal_Unicode** ppErrRet, OUString& rExternDocName,
431 const uno::Sequence<sheet::ExternalLinkInfo>* pExternalLinks )
433 // 1-based, sequence starts with an empty element.
434 if (pExternalLinks && pExternalLinks->hasElements())
436 // A numeric "document name" is an index into the sequence.
437 if (CharClass::isAsciiNumeric( rExternDocName))
439 sal_Int32 i = rExternDocName.toInt32();
440 if (i < 0 || i >= pExternalLinks->getLength())
441 return false; // with default *ppErrRet
442 const sheet::ExternalLinkInfo & rInfo = (*pExternalLinks)[i];
443 switch (rInfo.Type)
445 case sheet::ExternalLinkType::DOCUMENT :
447 OUString aStr;
448 if (!(rInfo.Data >>= aStr))
450 SAL_INFO(
451 "sc.core",
452 "Data type mismatch for ExternalLinkInfo "
453 << i);
454 *ppErrRet = nullptr;
455 return false;
457 rExternDocName = aStr;
459 break;
460 case sheet::ExternalLinkType::SELF :
461 return false; // ???
462 case sheet::ExternalLinkType::SPECIAL :
463 // silently return nothing (do not assert), caller has to handle this
464 *ppErrRet = nullptr;
465 return false;
466 default:
467 SAL_INFO(
468 "sc.core",
469 "unhandled ExternalLinkType " << rInfo.Type
470 << " for index " << i);
471 *ppErrRet = nullptr;
472 return false;
476 return true;
479 const sal_Unicode* ScRange::Parse_XL_Header(
480 const sal_Unicode* p,
481 const ScDocument& rDoc,
482 OUString& rExternDocName,
483 OUString& rStartTabName,
484 OUString& rEndTabName,
485 ScRefFlags& nFlags,
486 bool bOnlyAcceptSingle,
487 const uno::Sequence<sheet::ExternalLinkInfo>* pExternalLinks,
488 const OUString* pErrRef )
490 const sal_Unicode* startTabs, *start = p;
491 ScRefFlags nSaveFlags = nFlags;
493 // Is this an external reference ?
494 rStartTabName.clear();
495 rEndTabName.clear();
496 rExternDocName.clear();
497 const sal_Unicode* pMsoxlQuoteStop = nullptr;
498 if (*p == '[')
500 ++p;
501 // Only single quotes are correct, and a double single quote escapes a
502 // single quote text inside the quoted text.
503 if (*p == '\'')
505 p = parseQuotedName(p, rExternDocName);
506 if (*p != ']' || rExternDocName.isEmpty())
508 rExternDocName.clear();
509 return start;
512 else
514 // non-quoted file name.
515 p = ScGlobal::UnicodeStrChr( start+1, ']' );
516 if( p == nullptr )
517 return start;
518 rExternDocName += std::u16string_view( start+1, sal::static_int_cast<sal_Int32>( p-(start+1) ) );
520 ++p;
522 const sal_Unicode* pErrRet = start;
523 if (!lcl_XL_getExternalDoc( &pErrRet, rExternDocName, pExternalLinks))
524 return pErrRet;
526 rExternDocName = ScGlobal::GetAbsDocName(rExternDocName, rDoc.GetDocumentShell());
528 else if (*p == '\'')
530 // Sickness in Excel's ODF msoxl namespace:
531 // 'E:\[EXTDATA8.XLS]Sheet1'!$A$7 or
532 // 'E:\[EXTDATA12B.XLSB]Sheet1:Sheet3'!$A$11
533 // But, 'Sheet1'!B3 would also be a valid!
534 // Excel does not allow [ and ] characters in sheet names though.
535 // But, more sickness comes with MOOXML as there may be
536 // '[1]Sheet 4'!$A$1 where [1] is the external doc's index.
537 p = parseQuotedName(p, rExternDocName);
538 if (*p != '!')
540 rExternDocName.clear();
541 return start;
543 if (!rExternDocName.isEmpty())
545 sal_Int32 nOpen = rExternDocName.indexOf( '[');
546 if (nOpen == -1)
547 rExternDocName.clear();
548 else
550 sal_Int32 nClose = rExternDocName.indexOf( ']', nOpen+1);
551 if (nClose == -1)
552 rExternDocName.clear();
553 else
555 rExternDocName = rExternDocName.copy(0, nClose);
556 rExternDocName = rExternDocName.replaceAt( nOpen, 1, u"");
557 pMsoxlQuoteStop = p - 1; // the ' quote char
558 // There may be embedded escaped quotes, just matching the
559 // doc name's length may not work.
560 for (p = start; *p != '['; ++p)
562 for ( ; *p != ']'; ++p)
564 ++p;
566 // Handle '[1]Sheet 4'!$A$1
567 if (nOpen == 0)
569 const sal_Unicode* pErrRet = start;
570 if (!lcl_XL_getExternalDoc( &pErrRet, rExternDocName, pExternalLinks))
571 return pErrRet;
576 if (rExternDocName.isEmpty())
577 p = start;
580 startTabs = p;
581 p = lcl_XL_ParseSheetRef( p, rStartTabName, !bOnlyAcceptSingle, pMsoxlQuoteStop, pErrRef);
582 if( nullptr == p )
583 return start; // invalid tab
584 if (bOnlyAcceptSingle && *p == ':')
585 return nullptr; // 3D
586 const sal_Unicode* startEndTabs = nullptr;
587 if( p != startTabs )
589 nFlags |= ScRefFlags::TAB_VALID | ScRefFlags::TAB_3D | ScRefFlags::TAB_ABS;
590 if( *p == ':' ) // 3d ref
592 startEndTabs = p + 1;
593 p = lcl_XL_ParseSheetRef( startEndTabs, rEndTabName, false, pMsoxlQuoteStop, pErrRef);
594 if( p == nullptr )
596 nFlags = nSaveFlags;
597 return start; // invalid tab
599 nFlags |= ScRefFlags::TAB2_VALID | ScRefFlags::TAB2_3D | ScRefFlags::TAB2_ABS;
601 else
603 // If only one sheet is given, the full reference is still valid,
604 // only the second 3D flag is not set.
605 nFlags |= ScRefFlags::TAB2_VALID | ScRefFlags::TAB2_ABS;
606 aEnd.SetTab( aStart.Tab() );
609 if( *p++ != '!' )
611 nFlags = nSaveFlags;
612 return start; // syntax error
614 else
615 p = lcl_eatWhiteSpace( p );
617 else
619 nFlags |= ScRefFlags::TAB_VALID | ScRefFlags::TAB2_VALID;
620 // Use the current tab, it needs to be passed in. : aEnd.SetTab( .. );
623 if (!rExternDocName.isEmpty())
625 ScExternalRefManager* pRefMgr = rDoc.GetExternalRefManager();
626 pRefMgr->convertToAbsName(rExternDocName);
628 else
630 // Internal reference.
631 if (rStartTabName.isEmpty())
633 nFlags = nSaveFlags;
634 return start;
637 SCTAB nTab;
638 if ((pErrRef && *startTabs != '\'' && rStartTabName == *pErrRef) || !rDoc.GetTable(rStartTabName, nTab))
640 // invalid table name.
641 nFlags &= ~ScRefFlags::TAB_VALID;
642 nTab = -1;
645 aStart.SetTab(nTab);
646 aEnd.SetTab(nTab);
648 if (!rEndTabName.isEmpty())
650 if ((pErrRef && startEndTabs && *startEndTabs != '\'' && rEndTabName == *pErrRef) ||
651 !rDoc.GetTable(rEndTabName, nTab))
653 // invalid table name.
654 nFlags &= ~ScRefFlags::TAB2_VALID;
655 nTab = -1;
658 aEnd.SetTab(nTab);
661 return p;
664 static const sal_Unicode* lcl_r1c1_get_col( const ScSheetLimits& rSheetLimits,
665 const sal_Unicode* p,
666 const ScAddress::Details& rDetails,
667 ScAddress* pAddr, ScRefFlags* nFlags )
669 const sal_Unicode *pEnd;
670 sal_Int64 n;
671 bool isRelative;
673 if( p[0] == '\0' )
674 return nullptr;
676 p++;
677 isRelative = *p == '[';
678 if( isRelative )
679 p++;
680 n = sal_Unicode_strtol( p, &pEnd );
681 if( nullptr == pEnd )
682 return nullptr;
684 if( p == pEnd ) // C is a relative ref with offset 0
686 if( isRelative )
687 return nullptr;
688 n = rDetails.nCol;
690 else if( isRelative )
692 if( *pEnd != ']' )
693 return nullptr;
694 n += rDetails.nCol;
695 pEnd++;
697 else
699 *nFlags |= ScRefFlags::COL_ABS;
700 n--;
703 if( n < 0 || n >= rSheetLimits.GetMaxColCount())
704 return nullptr;
705 pAddr->SetCol( static_cast<SCCOL>( n ) );
706 *nFlags |= ScRefFlags::COL_VALID;
708 return pEnd;
711 static const sal_Unicode* lcl_r1c1_get_row(
712 const ScSheetLimits& rSheetLimits,
713 const sal_Unicode* p,
714 const ScAddress::Details& rDetails,
715 ScAddress* pAddr, ScRefFlags* nFlags )
717 const sal_Unicode *pEnd;
718 bool isRelative;
720 if( p[0] == '\0' )
721 return nullptr;
723 p++;
724 isRelative = *p == '[';
725 if( isRelative )
726 p++;
727 sal_Int64 n = sal_Unicode_strtol( p, &pEnd );
728 if( nullptr == pEnd )
729 return nullptr;
731 if( p == pEnd ) // R is a relative ref with offset 0
733 if( isRelative )
734 return nullptr;
735 n = rDetails.nRow;
737 else if( isRelative )
739 if( *pEnd != ']' )
740 return nullptr;
741 n += rDetails.nRow;
742 pEnd++;
744 else
746 *nFlags |= ScRefFlags::ROW_ABS;
747 n--;
750 if( n < 0 || n >= rSheetLimits.GetMaxRowCount() )
751 return nullptr;
752 pAddr->SetRow( static_cast<SCROW>( n ) );
753 *nFlags |= ScRefFlags::ROW_VALID;
755 return pEnd;
758 static ScRefFlags lcl_ScRange_Parse_XL_R1C1( ScRange& r,
759 const sal_Unicode* p,
760 const ScDocument& rDoc,
761 const ScAddress::Details& rDetails,
762 bool bOnlyAcceptSingle,
763 ScAddress::ExternalInfo* pExtInfo,
764 sal_Int32* pSheetEndPos )
766 const sal_Unicode* const pStart = p;
767 if (pSheetEndPos)
768 *pSheetEndPos = 0;
769 const sal_Unicode* pTmp = nullptr;
770 OUString aExternDocName, aStartTabName, aEndTabName;
771 ScRefFlags nFlags = ScRefFlags::VALID | ScRefFlags::TAB_VALID;
772 // Keep in mind that nFlags2 gets left-shifted by 4 bits before being merged.
773 ScRefFlags nFlags2 = ScRefFlags::TAB_VALID;
775 p = r.Parse_XL_Header( p, rDoc, aExternDocName, aStartTabName,
776 aEndTabName, nFlags, bOnlyAcceptSingle );
778 ScRefFlags nBailOutFlags = ScRefFlags::ZERO;
779 if (pSheetEndPos && pStart < p && (nFlags & ScRefFlags::TAB_VALID) && (nFlags & ScRefFlags::TAB_3D))
781 *pSheetEndPos = p - pStart;
782 nBailOutFlags = ScRefFlags::TAB_VALID | ScRefFlags::TAB_3D;
785 if (!aExternDocName.isEmpty())
786 lcl_ScRange_External_TabSpan( r, nFlags, pExtInfo, aExternDocName,
787 aStartTabName, aEndTabName, rDoc);
789 if( nullptr == p )
790 return ScRefFlags::ZERO;
792 if( *p == 'R' || *p == 'r' )
794 if( nullptr == (p = lcl_r1c1_get_row( rDoc.GetSheetLimits(), p, rDetails, &r.aStart, &nFlags )) )
795 return nBailOutFlags;
797 if( *p != 'C' && *p != 'c' ) // full row R#
799 if( p[0] != ':' || (p[1] != 'R' && p[1] != 'r' ) ||
800 nullptr == (pTmp = lcl_r1c1_get_row( rDoc.GetSheetLimits(), p+1, rDetails, &r.aEnd, &nFlags2 )))
802 // Only the initial row number is given, or the second row
803 // number is invalid. Fallback to just the initial R
804 applyStartToEndFlags(nFlags);
805 r.aEnd.SetRow( r.aStart.Row() );
807 else // pTmp != nullptr
809 // Full row range successfully parsed.
810 applyStartToEndFlags(nFlags, nFlags2);
811 p = pTmp;
814 if (p[0] != 0)
816 // any trailing invalid character must invalidate the whole address.
817 nFlags &= ~ScRefFlags(ScRefFlags::VALID | ScRefFlags::COL_VALID | ScRefFlags::ROW_VALID | ScRefFlags::TAB_VALID |
818 ScRefFlags::COL2_VALID | ScRefFlags::ROW2_VALID | ScRefFlags::TAB2_VALID);
819 return nFlags;
822 nFlags |=
823 ScRefFlags::COL_VALID | ScRefFlags::COL2_VALID |
824 ScRefFlags::COL_ABS | ScRefFlags::COL2_ABS;
825 r.aStart.SetCol( 0 );
826 r.aEnd.SetCol( rDoc.MaxCol() );
828 return bOnlyAcceptSingle ? ScRefFlags::ZERO : nFlags;
830 else if( nullptr == (p = lcl_r1c1_get_col( rDoc.GetSheetLimits(), p, rDetails, &r.aStart, &nFlags )))
832 return ScRefFlags::ZERO;
835 if( p[0] != ':' ||
836 (p[1] != 'R' && p[1] != 'r') ||
837 nullptr == (pTmp = lcl_r1c1_get_row( rDoc.GetSheetLimits(), p+1, rDetails, &r.aEnd, &nFlags2 )) ||
838 (*pTmp != 'C' && *pTmp != 'c') ||
839 nullptr == (pTmp = lcl_r1c1_get_col( rDoc.GetSheetLimits(), pTmp, rDetails, &r.aEnd, &nFlags2 )))
841 // single cell reference
843 if (p[0] != 0)
845 // any trailing invalid character must invalidate the whole address.
846 nFlags &= ~ScRefFlags(ScRefFlags::VALID | ScRefFlags::COL_VALID | ScRefFlags::ROW_VALID | ScRefFlags::TAB_VALID);
847 return nFlags;
850 return bOnlyAcceptSingle ? nFlags : ScRefFlags::ZERO;
852 assert(pTmp);
853 p = pTmp;
855 // double reference
857 if (p[0] != 0)
859 // any trailing invalid character must invalidate the whole range.
860 nFlags &= ~ScRefFlags(ScRefFlags::VALID | ScRefFlags::COL_VALID | ScRefFlags::ROW_VALID | ScRefFlags::TAB_VALID |
861 ScRefFlags::COL2_VALID | ScRefFlags::ROW2_VALID | ScRefFlags::TAB2_VALID);
862 return nFlags;
865 applyStartToEndFlags(nFlags, nFlags2);
866 return bOnlyAcceptSingle ? ScRefFlags::ZERO : nFlags;
868 else if( *p == 'C' || *p == 'c' ) // full col C#
870 if( nullptr == (p = lcl_r1c1_get_col( rDoc.GetSheetLimits(), p, rDetails, &r.aStart, &nFlags )))
871 return nBailOutFlags;
873 if( p[0] != ':' || (p[1] != 'C' && p[1] != 'c') ||
874 nullptr == (pTmp = lcl_r1c1_get_col( rDoc.GetSheetLimits(), p+1, rDetails, &r.aEnd, &nFlags2 )))
875 { // Fallback to just the initial C
876 applyStartToEndFlags(nFlags);
877 r.aEnd.SetCol( r.aStart.Col() );
879 else // pTmp != nullptr
881 applyStartToEndFlags(nFlags, nFlags2);
882 p = pTmp;
885 if (p[0] != 0)
887 // any trailing invalid character must invalidate the whole address.
888 nFlags &= ~ScRefFlags(ScRefFlags::VALID | ScRefFlags::COL_VALID | ScRefFlags::ROW_VALID | ScRefFlags::TAB_VALID |
889 ScRefFlags::COL2_VALID | ScRefFlags::ROW2_VALID | ScRefFlags::TAB2_VALID);
890 return nFlags;
893 nFlags |=
894 ScRefFlags::ROW_VALID | ScRefFlags::ROW2_VALID |
895 ScRefFlags::ROW_ABS | ScRefFlags::ROW2_ABS;
896 r.aStart.SetRow( 0 );
897 r.aEnd.SetRow( rDoc.MaxRow() );
899 return bOnlyAcceptSingle ? ScRefFlags::ZERO : nFlags;
902 return nBailOutFlags;
905 static const sal_Unicode* lcl_a1_get_col( const ScDocument& rDoc,
906 const sal_Unicode* p,
907 ScAddress* pAddr,
908 ScRefFlags* nFlags,
909 const OUString* pErrRef )
911 if( *p == '$' )
913 *nFlags |= ScRefFlags::COL_ABS;
914 p++;
917 if (pErrRef && lcl_isString( p, *pErrRef))
919 p += pErrRef->getLength();
920 *nFlags &= ~ScRefFlags::COL_VALID;
921 pAddr->SetCol(-1);
922 return p;
925 if( !rtl::isAsciiAlpha( *p ) )
926 return nullptr;
928 sal_Int64 nCol = rtl::toAsciiUpperCase( *p++ ) - 'A';
929 const SCCOL nMaxCol = rDoc.MaxCol();
930 while (nCol <= nMaxCol && rtl::isAsciiAlpha(*p))
931 nCol = ((nCol + 1) * 26) + rtl::toAsciiUpperCase( *p++ ) - 'A';
932 if( nCol > nMaxCol || nCol < 0 || rtl::isAsciiAlpha( *p ) )
933 return nullptr;
935 *nFlags |= ScRefFlags::COL_VALID;
936 pAddr->SetCol( sal::static_int_cast<SCCOL>( nCol ));
938 return p;
941 static const sal_Unicode* lcl_a1_get_row( const ScDocument& rDoc,
942 const sal_Unicode* p,
943 ScAddress* pAddr,
944 ScRefFlags* nFlags,
945 const OUString* pErrRef )
947 const sal_Unicode *pEnd;
949 if( *p == '$' )
951 *nFlags |= ScRefFlags::ROW_ABS;
952 p++;
955 if (pErrRef && lcl_isString( p, *pErrRef))
957 p += pErrRef->getLength();
958 *nFlags &= ~ScRefFlags::ROW_VALID;
959 pAddr->SetRow(-1);
960 return p;
963 sal_Int64 n = sal_Unicode_strtol(p, &pEnd);
964 if (nullptr == pEnd || p == pEnd || n < 1)
965 return nullptr;
966 n -= 1;
967 if (n > rDoc.MaxRow())
968 return nullptr;
970 *nFlags |= ScRefFlags::ROW_VALID;
971 pAddr->SetRow( sal::static_int_cast<SCROW>(n) );
973 return pEnd;
976 /// B:B or 2:2, but not B:2 or 2:B or B2:B or B:B2 or ...
977 static bool isValidSingleton( ScRefFlags nFlags, ScRefFlags nFlags2 )
979 bool bCols = (nFlags & ScRefFlags::COL_VALID) && ((nFlags & ScRefFlags::COL2_VALID) || (nFlags2 & ScRefFlags::COL_VALID));
980 bool bRows = (nFlags & ScRefFlags::ROW_VALID) && ((nFlags & ScRefFlags::ROW2_VALID) || (nFlags2 & ScRefFlags::ROW_VALID));
981 return bCols != bRows;
984 static ScRefFlags lcl_ScRange_Parse_XL_A1( ScRange& r,
985 const sal_Unicode* p,
986 const ScDocument& rDoc,
987 bool bOnlyAcceptSingle,
988 ScAddress::ExternalInfo* pExtInfo,
989 const uno::Sequence<sheet::ExternalLinkInfo>* pExternalLinks,
990 sal_Int32* pSheetEndPos,
991 const OUString* pErrRef )
993 const sal_Unicode* const pStart = p;
994 if (pSheetEndPos)
995 *pSheetEndPos = 0;
996 const sal_Unicode* tmp1, *tmp2;
997 OUString aExternDocName, aStartTabName, aEndTabName; // for external link table
998 ScRefFlags nFlags = ScRefFlags::VALID | ScRefFlags::TAB_VALID, nFlags2 = ScRefFlags::TAB_VALID;
1000 p = r.Parse_XL_Header( p, rDoc, aExternDocName, aStartTabName,
1001 aEndTabName, nFlags, bOnlyAcceptSingle, pExternalLinks, pErrRef );
1003 ScRefFlags nBailOutFlags = ScRefFlags::ZERO;
1004 if (pSheetEndPos && pStart < p && (nFlags & ScRefFlags::TAB_VALID) && (nFlags & ScRefFlags::TAB_3D))
1006 *pSheetEndPos = p - pStart;
1007 nBailOutFlags = ScRefFlags::TAB_VALID | ScRefFlags::TAB_3D;
1010 if (!aExternDocName.isEmpty())
1011 lcl_ScRange_External_TabSpan( r, nFlags, pExtInfo, aExternDocName,
1012 aStartTabName, aEndTabName, rDoc);
1014 if( nullptr == p )
1015 return nBailOutFlags;
1017 tmp1 = lcl_a1_get_col( rDoc, p, &r.aStart, &nFlags, pErrRef);
1018 if( tmp1 == nullptr ) // Is it a row only reference 3:5
1020 if( bOnlyAcceptSingle ) // by definition full row refs are ranges
1021 return nBailOutFlags;
1023 tmp1 = lcl_a1_get_row( rDoc, p, &r.aStart, &nFlags, pErrRef);
1025 tmp1 = lcl_eatWhiteSpace( tmp1 );
1026 if( !tmp1 || *tmp1++ != ':' ) // Even a singleton requires ':' (eg 2:2)
1027 return nBailOutFlags;
1029 tmp1 = lcl_eatWhiteSpace( tmp1 );
1030 tmp2 = lcl_a1_get_row( rDoc, tmp1, &r.aEnd, &nFlags2, pErrRef);
1031 if( !tmp2 || *tmp2 != 0 ) // Must have fully parsed a singleton.
1032 return nBailOutFlags;
1034 r.aStart.SetCol( 0 ); r.aEnd.SetCol( rDoc.MaxCol() );
1035 nFlags |=
1036 ScRefFlags::COL_VALID | ScRefFlags::COL2_VALID |
1037 ScRefFlags::COL_ABS | ScRefFlags::COL2_ABS;
1038 applyStartToEndFlags(nFlags, nFlags2);
1039 return nFlags;
1042 tmp2 = lcl_a1_get_row( rDoc, tmp1, &r.aStart, &nFlags, pErrRef);
1043 if( tmp2 == nullptr ) // check for col only reference F:H
1045 if( bOnlyAcceptSingle ) // by definition full col refs are ranges
1046 return nBailOutFlags;
1048 tmp1 = lcl_eatWhiteSpace( tmp1 );
1049 if( *tmp1++ != ':' ) // Even a singleton requires ':' (eg F:F)
1050 return nBailOutFlags;
1052 tmp1 = lcl_eatWhiteSpace( tmp1 );
1053 tmp2 = lcl_a1_get_col( rDoc, tmp1, &r.aEnd, &nFlags2, pErrRef);
1054 if( !tmp2 || *tmp2 != 0 ) // Must have fully parsed a singleton.
1055 return nBailOutFlags;
1057 r.aStart.SetRow( 0 ); r.aEnd.SetRow( rDoc.MaxRow() );
1058 nFlags |=
1059 ScRefFlags::ROW_VALID | ScRefFlags::ROW2_VALID |
1060 ScRefFlags::ROW_ABS | ScRefFlags::ROW2_ABS;
1061 applyStartToEndFlags(nFlags, nFlags2);
1062 return nFlags;
1065 // prepare as if it's a singleton, in case we want to fall back */
1066 r.aEnd.SetCol( r.aStart.Col() );
1067 r.aEnd.SetRow( r.aStart.Row() ); // don't overwrite sheet number as parsed in Parse_XL_Header()
1069 if ( bOnlyAcceptSingle )
1071 if ( *tmp2 == 0 )
1072 return nFlags;
1073 else
1075 // any trailing invalid character must invalidate the address.
1076 nFlags &= ~ScRefFlags(ScRefFlags::VALID | ScRefFlags::COL_VALID | ScRefFlags::ROW_VALID | ScRefFlags::TAB_VALID);
1077 return nFlags;
1081 tmp2 = lcl_eatWhiteSpace( tmp2 );
1082 if( *tmp2 != ':' )
1084 // Sheet1:Sheet2!C4 is a valid range, without a second sheet it is
1085 // not. Any trailing invalid character invalidates the range.
1086 if (*tmp2 == 0 && (nFlags & ScRefFlags::TAB2_3D))
1088 if (nFlags & ScRefFlags::COL_ABS)
1089 nFlags |= ScRefFlags::COL2_ABS;
1090 if (nFlags & ScRefFlags::ROW_ABS)
1091 nFlags |= ScRefFlags::ROW2_ABS;
1093 else
1094 nFlags &= ~ScRefFlags(ScRefFlags::VALID |
1095 ScRefFlags::COL_VALID | ScRefFlags::ROW_VALID | ScRefFlags::TAB_VALID |
1096 ScRefFlags::COL2_VALID | ScRefFlags::ROW2_VALID | ScRefFlags::TAB2_VALID);
1097 return nFlags;
1100 p = lcl_eatWhiteSpace( tmp2+1 ); // after ':'
1101 tmp1 = lcl_a1_get_col( rDoc, p, &r.aEnd, &nFlags2, pErrRef);
1102 if( !tmp1 && aEndTabName.isEmpty() ) // Probably the aEndTabName was specified after the first range
1104 p = lcl_XL_ParseSheetRef( p, aEndTabName, false, nullptr, pErrRef);
1105 if( p )
1107 SCTAB nTab = 0;
1108 if( !aEndTabName.isEmpty() && rDoc.GetTable( aEndTabName, nTab ) )
1110 r.aEnd.SetTab( nTab );
1111 nFlags |= ScRefFlags::TAB2_VALID | ScRefFlags::TAB2_3D | ScRefFlags::TAB2_ABS;
1113 if (*p == '!' || *p == ':')
1114 p = lcl_eatWhiteSpace( p+1 );
1115 tmp1 = lcl_a1_get_col( rDoc, p, &r.aEnd, &nFlags2, pErrRef);
1118 if( !tmp1 ) // strange, but maybe valid singleton
1119 return isValidSingleton( nFlags, nFlags2) ? nFlags : (nFlags & ~ScRefFlags::VALID);
1121 tmp2 = lcl_a1_get_row( rDoc, tmp1, &r.aEnd, &nFlags2, pErrRef);
1122 if( !tmp2 ) // strange, but maybe valid singleton
1123 return isValidSingleton( nFlags, nFlags2) ? nFlags : (nFlags & ~ScRefFlags::VALID);
1125 if ( *tmp2 != 0 )
1127 // any trailing invalid character must invalidate the range.
1128 nFlags &= ~ScRefFlags(ScRefFlags::VALID | ScRefFlags::COL_VALID | ScRefFlags::ROW_VALID | ScRefFlags::TAB_VALID |
1129 ScRefFlags::COL2_VALID | ScRefFlags::ROW2_VALID | ScRefFlags::TAB2_VALID);
1130 return nFlags;
1133 applyStartToEndFlags(nFlags, nFlags2);
1134 return nFlags;
1138 @param p pointer to null-terminated sal_Unicode string
1139 @param rRawRes returns ScRefFlags::... flags without the final check for full
1140 validity that is applied to the return value, with which
1141 two addresses that form a column or row singleton range,
1142 e.g. A:A or 1:1, can be detected. Used in
1143 lcl_ScRange_Parse_OOo().
1144 @param pRange pointer to range where rAddr effectively is *pRange->aEnd,
1145 used in conjunction with pExtInfo to determine the tab span
1146 of a 3D reference.
1148 static ScRefFlags lcl_ScAddress_Parse_OOo( const sal_Unicode* p, const ScDocument& rDoc, ScAddress& rAddr,
1149 ScRefFlags& rRawRes,
1150 ScAddress::ExternalInfo* pExtInfo,
1151 ScRange* pRange,
1152 sal_Int32* pSheetEndPos,
1153 const OUString* pErrRef )
1155 const sal_Unicode* const pStart = p;
1156 if (pSheetEndPos)
1157 *pSheetEndPos = 0;
1158 ScRefFlags nRes = ScRefFlags::ZERO;
1159 rRawRes = ScRefFlags::ZERO;
1160 OUString aDocName; // the pure Document Name
1161 OUString aTab;
1162 bool bExtDoc = false;
1163 bool bExtDocInherited = false;
1165 // Lets see if this is a reference to something in an external file. A
1166 // document name is always quoted and has a trailing #.
1167 if (*p == '\'')
1169 OUString aTmp;
1170 p = parseQuotedName(p, aTmp);
1171 aDocName = aTmp;
1172 if (*p++ == SC_COMPILER_FILE_TAB_SEP)
1173 bExtDoc = true;
1174 else
1175 // This is not a document name. Perhaps a quoted relative table
1176 // name.
1177 p = pStart;
1179 else if (pExtInfo && pExtInfo->mbExternal)
1181 // This is an external reference.
1182 bExtDoc = bExtDocInherited = true;
1185 SCCOL nCol = 0;
1186 SCROW nRow = 0;
1187 SCTAB nTab = 0;
1188 ScRefFlags nBailOutFlags = ScRefFlags::ZERO;
1189 ScRefFlags nBits = ScRefFlags::TAB_VALID;
1190 const sal_Unicode* q;
1191 if ( ScGlobal::FindUnquoted( p, '.') )
1193 nRes |= ScRefFlags::TAB_3D;
1194 if ( bExtDoc )
1195 nRes |= ScRefFlags::TAB_ABS;
1196 if (*p == '$')
1198 nRes |= ScRefFlags::TAB_ABS;
1199 p++;
1202 if (pErrRef && lcl_isString( p, *pErrRef) && p[pErrRef->getLength()] == '.')
1204 // #REF! particle of an invalidated reference plus sheet separator.
1205 p += pErrRef->getLength() + 1;
1206 nRes &= ~ScRefFlags::TAB_VALID;
1207 nTab = -1;
1209 else
1211 if (*p == '\'')
1213 // Tokens that start at ' can have anything in them until a final
1214 // ' but '' marks an escaped '. We've earlier guaranteed that a
1215 // string containing '' will be surrounded by '.
1216 p = parseQuotedName(p, aTab);
1218 else
1220 OUStringBuffer aTabAcc;
1221 while (*p)
1223 if( *p == '.')
1224 break;
1226 if( *p == '\'' )
1228 p++; break;
1230 aTabAcc.append(*p);
1231 p++;
1233 aTab = aTabAcc.makeStringAndClear();
1235 if (*p != '.')
1236 nBits = ScRefFlags::ZERO;
1237 else
1239 ++p;
1240 if (!bExtDoc && !rDoc.GetTable( aTab, nTab ))
1241 nBits = ScRefFlags::ZERO;
1245 if (pSheetEndPos && (nBits & ScRefFlags::TAB_VALID))
1247 *pSheetEndPos = p - pStart;
1248 nBailOutFlags = ScRefFlags::TAB_VALID | ScRefFlags::TAB_3D;
1251 else
1253 if (bExtDoc && !bExtDocInherited)
1254 return nRes; // After a document a sheet must follow.
1255 nTab = rAddr.Tab();
1257 nRes |= nBits;
1259 q = p;
1260 if (*p)
1262 nBits = ScRefFlags::COL_VALID;
1263 if (*p == '$')
1265 nBits |= ScRefFlags::COL_ABS;
1266 p++;
1269 if (pErrRef && lcl_isString( p, *pErrRef))
1271 // #REF! particle of an invalidated reference.
1272 p += pErrRef->getLength();
1273 nBits &= ~ScRefFlags::COL_VALID;
1274 nCol = -1;
1276 else
1278 if (rtl::isAsciiAlpha( *p ))
1280 const SCCOL nMaxCol = rDoc.MaxCol();
1281 sal_Int64 n = rtl::toAsciiUpperCase( *p++ ) - 'A';
1282 while (n < nMaxCol && rtl::isAsciiAlpha(*p))
1283 n = ((n + 1) * 26) + rtl::toAsciiUpperCase( *p++ ) - 'A';
1284 if (n > nMaxCol || n < 0 || (*p && *p != '$' && !rtl::isAsciiDigit( *p ) &&
1285 (!pErrRef || !lcl_isString( p, *pErrRef))))
1286 nBits = ScRefFlags::ZERO;
1287 else
1288 nCol = sal::static_int_cast<SCCOL>( n );
1290 else
1291 nBits = ScRefFlags::ZERO;
1293 if( nBits == ScRefFlags::ZERO )
1294 p = q;
1296 nRes |= nBits;
1299 q = p;
1300 if (*p)
1302 nBits = ScRefFlags::ROW_VALID;
1303 if (*p == '$')
1305 nBits |= ScRefFlags::ROW_ABS;
1306 p++;
1309 if (pErrRef && lcl_isString( p, *pErrRef))
1311 // #REF! particle of an invalidated reference.
1312 p += pErrRef->getLength();
1313 // Clearing the ROW_VALID bit here is not possible because of the
1314 // check at the end whether only a valid column was detected in
1315 // which case all bits are cleared because it could be any other
1316 // name. Instead, set to an absolute invalid row value. This will
1317 // display a $#REF! instead of #REF! if the error value was
1318 // relative, but live with it.
1319 nBits |= ScRefFlags::ROW_ABS;
1320 nRow = -1;
1322 else
1324 if( !rtl::isAsciiDigit( *p ) )
1326 nBits = ScRefFlags::ZERO;
1327 nRow = -1;
1329 else
1331 sal_Int64 n = rtl_ustr_toInt32( p, 10 ) - 1;
1332 while (rtl::isAsciiDigit( *p ))
1333 p++;
1334 const SCROW nMaxRow = rDoc.MaxRow();
1335 if( n < 0 || n > nMaxRow )
1336 nBits = ScRefFlags::ZERO;
1337 nRow = sal::static_int_cast<SCROW>(n);
1339 if( nBits == ScRefFlags::ZERO )
1340 p = q;
1342 nRes |= nBits;
1345 rAddr.Set( nCol, nRow, nTab );
1347 if (!*p && bExtDoc)
1349 ScExternalRefManager* pRefMgr = rDoc.GetExternalRefManager();
1351 // Need document name if inherited.
1352 if (bExtDocInherited)
1354 // The FileId was created using the original file name, so
1355 // obtain that. Otherwise lcl_ScRange_External_TabSpan() would
1356 // retrieve a FileId for the real name and bail out if that
1357 // differed from pExtInfo->mnFileId, as is the case when
1358 // loading documents that refer external files relative to the
1359 // current own document but were saved from a different path
1360 // than loaded.
1361 const OUString* pFileName = pRefMgr->getExternalFileName( pExtInfo->mnFileId, true);
1362 if (pFileName)
1363 aDocName = *pFileName;
1364 else
1365 nRes = ScRefFlags::ZERO;
1367 pRefMgr->convertToAbsName(aDocName);
1369 if ((!pExtInfo || !pExtInfo->mbExternal) && pRefMgr->isOwnDocument(aDocName))
1371 if (!rDoc.GetTable( aTab, nTab ))
1372 nRes = ScRefFlags::ZERO;
1373 else
1375 rAddr.SetTab( nTab);
1376 nRes |= ScRefFlags::TAB_VALID;
1379 else
1381 if (!pExtInfo)
1382 nRes = ScRefFlags::ZERO;
1383 else
1385 if (!pExtInfo->mbExternal)
1387 sal_uInt16 nFileId = pRefMgr->getExternalFileId(aDocName);
1389 pExtInfo->mbExternal = true;
1390 pExtInfo->maTabName = aTab;
1391 pExtInfo->mnFileId = nFileId;
1393 if (pRefMgr->getSingleRefToken(nFileId, aTab,
1394 ScAddress(nCol, nRow, 0), nullptr,
1395 &nTab))
1397 rAddr.SetTab( nTab);
1398 nRes |= ScRefFlags::TAB_VALID;
1400 else
1401 nRes = ScRefFlags::ZERO;
1403 else
1405 // This is a call for the second part of the reference,
1406 // we must have the range to adapt tab span.
1407 if (!pRange)
1408 nRes = ScRefFlags::ZERO;
1409 else
1411 ScRefFlags nFlags = nRes | ScRefFlags::TAB2_VALID;
1412 if (!lcl_ScRange_External_TabSpan( *pRange, nFlags,
1413 pExtInfo, aDocName,
1414 pExtInfo->maTabName, aTab, rDoc))
1415 nRes &= ~ScRefFlags::TAB_VALID;
1416 else
1418 if (nFlags & ScRefFlags::TAB2_VALID)
1420 rAddr.SetTab( pRange->aEnd.Tab());
1421 nRes |= ScRefFlags::TAB_VALID;
1423 else
1424 nRes &= ~ScRefFlags::TAB_VALID;
1431 else if (bExtDoc && pExtInfo && !bExtDocInherited && !pExtInfo->mbExternal && pSheetEndPos)
1433 // Pass partial info up to caller, there may be an external name
1434 // following, and if after *pSheetEndPos it's external sheet-local.
1435 // For global names aTab is empty and *pSheetEndPos==0.
1436 pExtInfo->mbExternal = true;
1437 pExtInfo->maTabName = aTab;
1438 pExtInfo->mnFileId = rDoc.GetExternalRefManager()->getExternalFileId(aDocName);
1441 rRawRes |= nRes;
1443 if ( !(nRes & ScRefFlags::ROW_VALID) && (nRes & ScRefFlags::COL_VALID)
1444 && !( (nRes & ScRefFlags::TAB_3D) && (nRes & ScRefFlags::TAB_VALID)) )
1445 { // no Row, no Tab, but Col => DM (...), B (...) et al
1446 nRes = ScRefFlags::ZERO;
1448 if( !*p )
1450 ScRefFlags nMask = nRes & ( ScRefFlags::ROW_VALID | ScRefFlags::COL_VALID | ScRefFlags::TAB_VALID );
1451 if( nMask == ( ScRefFlags::ROW_VALID | ScRefFlags::COL_VALID | ScRefFlags::TAB_VALID ) )
1452 nRes |= ScRefFlags::VALID;
1454 else
1455 nRes = rRawRes = nBailOutFlags;
1456 return nRes;
1459 static ScRefFlags lcl_ScAddress_Parse ( const sal_Unicode* p, const ScDocument& rDoc, ScAddress& rAddr,
1460 const ScAddress::Details& rDetails,
1461 ScAddress::ExternalInfo* pExtInfo,
1462 const uno::Sequence<sheet::ExternalLinkInfo>* pExternalLinks,
1463 sal_Int32* pSheetEndPos,
1464 const OUString* pErrRef )
1466 if( !*p )
1467 return ScRefFlags::ZERO;
1469 switch (rDetails.eConv)
1471 case formula::FormulaGrammar::CONV_XL_A1:
1472 case formula::FormulaGrammar::CONV_XL_OOX:
1474 ScRange rRange = rAddr;
1475 ScRefFlags nFlags = lcl_ScRange_Parse_XL_A1(
1476 rRange, p, rDoc, true, pExtInfo,
1477 (rDetails.eConv == formula::FormulaGrammar::CONV_XL_OOX ? pExternalLinks : nullptr),
1478 pSheetEndPos, pErrRef);
1479 rAddr = rRange.aStart;
1480 return nFlags;
1482 case formula::FormulaGrammar::CONV_XL_R1C1:
1484 ScRange rRange = rAddr;
1485 ScRefFlags nFlags = lcl_ScRange_Parse_XL_R1C1( rRange, p, rDoc, rDetails, true, pExtInfo, pSheetEndPos);
1486 rAddr = rRange.aStart;
1487 return nFlags;
1489 default :
1490 case formula::FormulaGrammar::CONV_OOO:
1492 ScRefFlags nRawRes = ScRefFlags::ZERO;
1493 return lcl_ScAddress_Parse_OOo( p, rDoc, rAddr, nRawRes, pExtInfo, nullptr, pSheetEndPos, pErrRef);
1498 bool ConvertSingleRef( const ScDocument& rDoc, const OUString& rRefString,
1499 SCTAB nDefTab, ScRefAddress& rRefAddress,
1500 const ScAddress::Details& rDetails,
1501 ScAddress::ExternalInfo* pExtInfo /* = NULL */ )
1503 bool bRet = false;
1504 if (pExtInfo || (ScGlobal::FindUnquoted( rRefString, SC_COMPILER_FILE_TAB_SEP) == -1))
1506 ScAddress aAddr( 0, 0, nDefTab );
1507 ScRefFlags nRes = aAddr.Parse( rRefString, rDoc, rDetails, pExtInfo);
1508 if ( nRes & ScRefFlags::VALID )
1510 rRefAddress.Set( aAddr,
1511 ((nRes & ScRefFlags::COL_ABS) == ScRefFlags::ZERO),
1512 ((nRes & ScRefFlags::ROW_ABS) == ScRefFlags::ZERO),
1513 ((nRes & ScRefFlags::TAB_ABS) == ScRefFlags::ZERO));
1514 bRet = true;
1517 return bRet;
1520 bool ConvertDoubleRef( const ScDocument& rDoc, const OUString& rRefString, SCTAB nDefTab,
1521 ScRefAddress& rStartRefAddress, ScRefAddress& rEndRefAddress,
1522 const ScAddress::Details& rDetails,
1523 ScAddress::ExternalInfo* pExtInfo /* = NULL */ )
1525 bool bRet = false;
1526 if (pExtInfo || (ScGlobal::FindUnquoted( rRefString, SC_COMPILER_FILE_TAB_SEP) == -1))
1528 ScRange aRange( ScAddress( 0, 0, nDefTab));
1529 ScRefFlags nRes = aRange.Parse( rRefString, rDoc, rDetails, pExtInfo);
1530 if ( nRes & ScRefFlags::VALID )
1532 rStartRefAddress.Set( aRange.aStart,
1533 ((nRes & ScRefFlags::COL_ABS) == ScRefFlags::ZERO),
1534 ((nRes & ScRefFlags::ROW_ABS) == ScRefFlags::ZERO),
1535 ((nRes & ScRefFlags::TAB_ABS) == ScRefFlags::ZERO));
1536 rEndRefAddress.Set( aRange.aEnd,
1537 ((nRes & ScRefFlags::COL2_ABS) == ScRefFlags::ZERO),
1538 ((nRes & ScRefFlags::ROW2_ABS) == ScRefFlags::ZERO),
1539 ((nRes & ScRefFlags::TAB2_ABS) == ScRefFlags::ZERO));
1540 bRet = true;
1543 return bRet;
1546 ScRefFlags ScAddress::Parse( const OUString& r, const ScDocument& rDoc,
1547 const Details& rDetails,
1548 ExternalInfo* pExtInfo,
1549 const uno::Sequence<sheet::ExternalLinkInfo>* pExternalLinks,
1550 sal_Int32* pSheetEndPos,
1551 const OUString* pErrRef )
1553 return lcl_ScAddress_Parse( r.getStr(), rDoc, *this, rDetails, pExtInfo, pExternalLinks, pSheetEndPos, pErrRef);
1556 ScRange ScRange::Intersection( const ScRange& rOther ) const
1558 SCCOL nCol1 = std::max(aStart.Col(), rOther.aStart.Col());
1559 SCCOL nCol2 = std::min(aEnd.Col(), rOther.aEnd.Col());
1560 SCROW nRow1 = std::max(aStart.Row(), rOther.aStart.Row());
1561 SCROW nRow2 = std::min(aEnd.Row(), rOther.aEnd.Row());
1562 SCTAB nTab1 = std::max(aStart.Tab(), rOther.aStart.Tab());
1563 SCTAB nTab2 = std::min(aEnd.Tab(), rOther.aEnd.Tab());
1565 if (nCol1 > nCol2 || nRow1 > nRow2 || nTab1 > nTab2)
1566 return ScRange(ScAddress::INITIALIZE_INVALID);
1568 return ScRange(nCol1, nRow1, nTab1, nCol2, nRow2, nTab2);
1571 void ScRange::ExtendTo( const ScRange& rRange )
1573 OSL_ENSURE( rRange.IsValid(), "ScRange::ExtendTo - cannot extend to invalid range" );
1574 if( IsValid() )
1576 aStart.SetCol( std::min( aStart.Col(), rRange.aStart.Col() ) );
1577 aStart.SetRow( std::min( aStart.Row(), rRange.aStart.Row() ) );
1578 aStart.SetTab( std::min( aStart.Tab(), rRange.aStart.Tab() ) );
1579 aEnd.SetCol( std::max( aEnd.Col(), rRange.aEnd.Col() ) );
1580 aEnd.SetRow( std::max( aEnd.Row(), rRange.aEnd.Row() ) );
1581 aEnd.SetTab( std::max( aEnd.Tab(), rRange.aEnd.Tab() ) );
1583 else
1584 *this = rRange;
1587 static ScRefFlags lcl_ScRange_Parse_OOo( ScRange& rRange,
1588 const OUString& r,
1589 const ScDocument& rDoc,
1590 ScAddress::ExternalInfo* pExtInfo,
1591 const OUString* pErrRef )
1593 ScRefFlags nRes1 = ScRefFlags::ZERO, nRes2 = ScRefFlags::ZERO;
1594 sal_Int32 nPos = ScGlobal::FindUnquoted( r, ':');
1595 if (nPos != -1)
1597 OUStringBuffer aTmp(r);
1598 aTmp[nPos] = 0;
1599 const sal_Unicode* p = aTmp.getStr();
1600 ScRefFlags nRawRes1 = ScRefFlags::ZERO;
1601 nRes1 = lcl_ScAddress_Parse_OOo( p, rDoc, rRange.aStart, nRawRes1, pExtInfo, nullptr, nullptr, pErrRef);
1602 if ((nRes1 != ScRefFlags::ZERO) ||
1603 ((nRawRes1 & (ScRefFlags::COL_VALID | ScRefFlags::ROW_VALID)) &&
1604 (nRawRes1 & ScRefFlags::TAB_VALID)))
1606 rRange.aEnd = rRange.aStart; // sheet must be initialized identical to first sheet
1607 ScRefFlags nRawRes2 = ScRefFlags::ZERO;
1608 nRes2 = lcl_ScAddress_Parse_OOo( p + nPos+ 1, rDoc, rRange.aEnd, nRawRes2,
1609 pExtInfo, &rRange, nullptr, pErrRef);
1610 if (!((nRes1 & ScRefFlags::VALID) && (nRes2 & ScRefFlags::VALID)) &&
1611 // If not fully valid addresses, check if both have a valid
1612 // column or row, and both have valid (or omitted) sheet references.
1613 (nRawRes1 & (ScRefFlags::COL_VALID | ScRefFlags::ROW_VALID)) &&
1614 (nRawRes1 & ScRefFlags::TAB_VALID) &&
1615 (nRawRes2 & (ScRefFlags::COL_VALID | ScRefFlags::ROW_VALID)) &&
1616 (nRawRes2 & ScRefFlags::TAB_VALID) &&
1617 // Both must be column XOR row references, A:A or 1:1 but not A:1 or 1:A
1618 ((nRawRes1 & (ScRefFlags::COL_VALID | ScRefFlags::ROW_VALID)) ==
1619 (nRawRes2 & (ScRefFlags::COL_VALID | ScRefFlags::ROW_VALID))))
1621 nRes1 = nRawRes1 | ScRefFlags::VALID;
1622 nRes2 = nRawRes2 | ScRefFlags::VALID;
1623 if (nRawRes1 & ScRefFlags::COL_VALID)
1625 rRange.aStart.SetRow(0);
1626 rRange.aEnd.SetRow(rDoc.MaxRow());
1627 nRes1 |= ScRefFlags::ROW_VALID | ScRefFlags::ROW_ABS;
1628 nRes2 |= ScRefFlags::ROW_VALID | ScRefFlags::ROW_ABS;
1630 else
1632 rRange.aStart.SetCol(0);
1633 rRange.aEnd.SetCol( rDoc.MaxCol() );
1634 nRes1 |= ScRefFlags::COL_VALID | ScRefFlags::COL_ABS;
1635 nRes2 |= ScRefFlags::COL_VALID | ScRefFlags::COL_ABS;
1638 else if ((nRes1 & ScRefFlags::VALID) && (nRes2 & ScRefFlags::VALID))
1640 // Flag entire column/row references so they can be displayed
1641 // as such. If the sticky reference parts are not both
1642 // absolute or relative, assume that the user thought about
1643 // something we should not touch.
1644 if (rRange.aStart.Row() == 0 && rRange.aEnd.Row() == rDoc.MaxRow() &&
1645 ((nRes1 & ScRefFlags::ROW_ABS) == ScRefFlags::ZERO) &&
1646 ((nRes2 & ScRefFlags::ROW_ABS) == ScRefFlags::ZERO))
1648 nRes1 |= ScRefFlags::ROW_ABS;
1649 nRes2 |= ScRefFlags::ROW_ABS;
1651 else if (rRange.aStart.Col() == 0 && rRange.aEnd.Col() == rDoc.MaxCol() &&
1652 ((nRes1 & ScRefFlags::COL_ABS) == ScRefFlags::ZERO) && ((nRes2 & ScRefFlags::COL_ABS) == ScRefFlags::ZERO))
1654 nRes1 |= ScRefFlags::COL_ABS;
1655 nRes2 |= ScRefFlags::COL_ABS;
1658 if ((nRes1 & ScRefFlags::VALID) && (nRes2 & ScRefFlags::VALID))
1660 // PutInOrder / Justify
1661 ScRefFlags nMask, nBits1, nBits2;
1662 SCCOL nTempCol;
1663 if ( rRange.aEnd.Col() < (nTempCol = rRange.aStart.Col()) )
1665 rRange.aStart.SetCol(rRange.aEnd.Col()); rRange.aEnd.SetCol(nTempCol);
1666 nMask = (ScRefFlags::COL_VALID | ScRefFlags::COL_ABS);
1667 nBits1 = nRes1 & nMask;
1668 nBits2 = nRes2 & nMask;
1669 nRes1 = (nRes1 & ~nMask) | nBits2;
1670 nRes2 = (nRes2 & ~nMask) | nBits1;
1672 SCROW nTempRow;
1673 if ( rRange.aEnd.Row() < (nTempRow = rRange.aStart.Row()) )
1675 rRange.aStart.SetRow(rRange.aEnd.Row()); rRange.aEnd.SetRow(nTempRow);
1676 nMask = (ScRefFlags::ROW_VALID | ScRefFlags::ROW_ABS);
1677 nBits1 = nRes1 & nMask;
1678 nBits2 = nRes2 & nMask;
1679 nRes1 = (nRes1 & ~nMask) | nBits2;
1680 nRes2 = (nRes2 & ~nMask) | nBits1;
1682 SCTAB nTempTab;
1683 if ( rRange.aEnd.Tab() < (nTempTab = rRange.aStart.Tab()) )
1685 rRange.aStart.SetTab(rRange.aEnd.Tab()); rRange.aEnd.SetTab(nTempTab);
1686 nMask = (ScRefFlags::TAB_VALID | ScRefFlags::TAB_ABS | ScRefFlags::TAB_3D);
1687 nBits1 = nRes1 & nMask;
1688 nBits2 = nRes2 & nMask;
1689 nRes1 = (nRes1 & ~nMask) | nBits2;
1690 nRes2 = (nRes2 & ~nMask) | nBits1;
1692 if ( ((nRes1 & ( ScRefFlags::TAB_ABS | ScRefFlags::TAB_3D ))
1693 == ( ScRefFlags::TAB_ABS | ScRefFlags::TAB_3D ))
1694 && !(nRes2 & ScRefFlags::TAB_3D) )
1695 nRes2 |= ScRefFlags::TAB_ABS;
1697 else
1699 // Don't leave around valid half references.
1700 nRes1 = nRes2 = ScRefFlags::ZERO;
1704 applyStartToEndFlags(nRes1, nRes2 & ScRefFlags::BITS);
1705 nRes1 |= nRes2 & ScRefFlags::VALID;
1706 return nRes1;
1709 ScRefFlags ScRange::Parse( const OUString& rString, const ScDocument& rDoc,
1710 const ScAddress::Details& rDetails,
1711 ScAddress::ExternalInfo* pExtInfo,
1712 const uno::Sequence<sheet::ExternalLinkInfo>* pExternalLinks,
1713 const OUString* pErrRef )
1715 if (rString.isEmpty())
1716 return ScRefFlags::ZERO;
1718 switch (rDetails.eConv)
1720 case formula::FormulaGrammar::CONV_XL_A1:
1721 case formula::FormulaGrammar::CONV_XL_OOX:
1723 return lcl_ScRange_Parse_XL_A1( *this, rString.getStr(), rDoc, false, pExtInfo,
1724 (rDetails.eConv == formula::FormulaGrammar::CONV_XL_OOX ? pExternalLinks : nullptr),
1725 nullptr, pErrRef );
1728 case formula::FormulaGrammar::CONV_XL_R1C1:
1730 return lcl_ScRange_Parse_XL_R1C1( *this, rString.getStr(), rDoc, rDetails, false, pExtInfo, nullptr );
1733 default:
1734 case formula::FormulaGrammar::CONV_OOO:
1736 return lcl_ScRange_Parse_OOo( *this, rString, rDoc, pExtInfo, pErrRef );
1741 // Accept a full range, or an address
1742 ScRefFlags ScRange::ParseAny( const OUString& rString, const ScDocument& rDoc,
1743 const ScAddress::Details& rDetails )
1745 ScRefFlags nRet = Parse( rString, rDoc, rDetails );
1746 const ScRefFlags nValid = ScRefFlags::VALID | ScRefFlags::COL2_VALID | ScRefFlags::ROW2_VALID | ScRefFlags::TAB2_VALID;
1748 if ( (nRet & nValid) != nValid )
1750 ScAddress aAdr(aStart);//initialize with currentPos as fallback for table number
1751 nRet = aAdr.Parse( rString, rDoc, rDetails );
1752 if ( nRet & ScRefFlags::VALID )
1753 aStart = aEnd = aAdr;
1755 return nRet;
1758 // Parse only full row references
1759 ScRefFlags ScRange::ParseCols( const ScDocument& rDoc,
1760 const OUString& rStr,
1761 const ScAddress::Details& rDetails )
1763 if (rStr.isEmpty())
1764 return ScRefFlags::ZERO;
1766 const sal_Unicode* p = rStr.getStr();
1767 ScRefFlags nRes = ScRefFlags::ZERO;
1768 ScRefFlags ignored = ScRefFlags::ZERO;
1770 switch (rDetails.eConv)
1772 default :
1773 case formula::FormulaGrammar::CONV_OOO: // No full col refs in OOO yet, assume XL notation
1774 case formula::FormulaGrammar::CONV_XL_A1:
1775 case formula::FormulaGrammar::CONV_XL_OOX:
1776 if (nullptr != (p = lcl_a1_get_col( rDoc, p, &aStart, &ignored, nullptr) ) )
1778 if( p[0] == ':')
1780 if( nullptr != (p = lcl_a1_get_col( rDoc, p+1, &aEnd, &ignored, nullptr)))
1782 nRes = ScRefFlags::COL_VALID;
1785 else
1787 aEnd = aStart;
1788 nRes = ScRefFlags::COL_VALID;
1791 break;
1793 case formula::FormulaGrammar::CONV_XL_R1C1:
1794 if ((p[0] == 'C' || p[0] == 'c') &&
1795 nullptr != (p = lcl_r1c1_get_col( rDoc.GetSheetLimits(), p, rDetails, &aStart, &ignored )))
1797 if( p[0] == ':')
1799 if( (p[1] == 'C' || p[1] == 'c') &&
1800 nullptr != (p = lcl_r1c1_get_col( rDoc.GetSheetLimits(), p+1, rDetails, &aEnd, &ignored )))
1802 nRes = ScRefFlags::COL_VALID;
1805 else
1807 aEnd = aStart;
1808 nRes = ScRefFlags::COL_VALID;
1811 break;
1814 return (p != nullptr && *p == '\0') ? nRes : ScRefFlags::ZERO;
1817 // Parse only full row references
1818 void ScRange::ParseRows( const ScDocument& rDoc,
1819 const OUString& rStr,
1820 const ScAddress::Details& rDetails )
1822 if (rStr.isEmpty())
1823 return;
1825 const sal_Unicode* p = rStr.getStr();
1826 ScRefFlags ignored = ScRefFlags::ZERO;
1828 switch (rDetails.eConv)
1830 default :
1831 case formula::FormulaGrammar::CONV_OOO: // No full row refs in OOO yet, assume XL notation
1832 case formula::FormulaGrammar::CONV_XL_A1:
1833 case formula::FormulaGrammar::CONV_XL_OOX:
1834 if (nullptr != (p = lcl_a1_get_row( rDoc, p, &aStart, &ignored, nullptr) ) )
1836 if( p[0] == ':')
1838 lcl_a1_get_row( rDoc, p+1, &aEnd, &ignored, nullptr);
1840 else
1842 aEnd = aStart;
1845 break;
1847 case formula::FormulaGrammar::CONV_XL_R1C1:
1848 if ((p[0] == 'R' || p[0] == 'r') &&
1849 nullptr != (p = lcl_r1c1_get_row( rDoc.GetSheetLimits(), p, rDetails, &aStart, &ignored )))
1851 if( p[0] == ':')
1853 if( p[1] == 'R' || p[1] == 'r' )
1855 lcl_r1c1_get_row( rDoc.GetSheetLimits(), p+1, rDetails, &aEnd, &ignored );
1858 else
1860 aEnd = aStart;
1863 break;
1867 template<typename T > static void lcl_ScColToAlpha( T& rBuf, SCCOL nCol )
1869 if (nCol < 26*26)
1871 if (nCol < 26)
1872 rBuf.append( static_cast<char>( 'A' + nCol ));
1873 else
1875 rBuf.append( static_cast<char>( 'A' + nCol / 26 - 1 ));
1876 rBuf.append( static_cast<char>( 'A' + nCol % 26 ));
1879 else
1881 sal_Int32 nInsert = rBuf.getLength();
1882 while (nCol >= 26)
1884 SCCOL nC = nCol % 26;
1885 rBuf.insert(nInsert, static_cast<char> ( 'A' + nC ));
1886 nCol = sal::static_int_cast<SCCOL>( nCol - nC );
1887 nCol = nCol / 26 - 1;
1889 rBuf.insert(nInsert, static_cast<char> ( 'A' + nCol ));
1893 void ScColToAlpha( OUStringBuffer& rBuf, SCCOL nCol)
1895 lcl_ScColToAlpha(rBuf, nCol);
1898 template <typename T > static void lcl_a1_append_c ( T &rString, int nCol, bool bIsAbs )
1900 if( bIsAbs )
1901 rString.append("$");
1902 lcl_ScColToAlpha( rString, sal::static_int_cast<SCCOL>(nCol) );
1905 template <typename T > static void lcl_a1_append_r ( T &rString, sal_Int32 nRow, bool bIsAbs )
1907 if ( bIsAbs )
1908 rString.append("$");
1909 rString.append( nRow + 1 );
1912 template <typename T > static void lcl_r1c1_append_c ( T &rString, sal_Int32 nCol, bool bIsAbs,
1913 const ScAddress::Details& rDetails )
1915 rString.append("C");
1916 if (bIsAbs)
1918 rString.append( nCol + 1 );
1920 else
1922 nCol -= rDetails.nCol;
1923 if (nCol != 0) {
1924 rString.append("[").append(nCol).append("]");
1929 template <typename T > static void lcl_r1c1_append_r ( T &rString, sal_Int32 nRow, bool bIsAbs,
1930 const ScAddress::Details& rDetails )
1932 rString.append("R");
1933 if (bIsAbs)
1935 rString.append( nRow + 1 );
1937 else
1939 nRow -= rDetails.nRow;
1940 if (nRow != 0) {
1941 rString.append("[").append(nRow).append("]");
1946 static OUString getFileNameFromDoc( const ScDocument* pDoc )
1948 // TODO : er points at ScGlobal::GetAbsDocName()
1949 // as a better template. Look into it
1950 OUString sFileName;
1951 SfxObjectShell* pShell;
1953 if( nullptr != pDoc &&
1954 nullptr != (pShell = pDoc->GetDocumentShell() ) )
1956 uno::Reference< frame::XModel > xModel = pShell->GetModel();
1957 if( xModel.is() )
1959 if( !xModel->getURL().isEmpty() )
1961 INetURLObject aURL( xModel->getURL() );
1962 sFileName = aURL.GetLastName();
1964 else
1965 sFileName = pShell->GetTitle();
1968 return sFileName;
1972 static void lcl_string_append(OUStringBuffer &rString, std::u16string_view sString)
1974 rString.append(sString);
1977 static void lcl_string_append(OStringBuffer &rString, std::u16string_view sString)
1979 rString.append(OUStringToOString( sString, RTL_TEXTENCODING_UTF8 ));
1982 template<typename T > static void lcl_Format( T& r, SCTAB nTab, SCROW nRow, SCCOL nCol, ScRefFlags nFlags,
1983 const ScDocument* pDoc,
1984 const ScAddress::Details& rDetails)
1986 if( nFlags & ScRefFlags::VALID )
1987 nFlags |= ScRefFlags::ROW_VALID | ScRefFlags::COL_VALID | ScRefFlags::TAB_VALID;
1988 if( pDoc && (nFlags & ScRefFlags::TAB_VALID ) )
1990 if ( nTab < 0 || nTab >= pDoc->GetTableCount() )
1992 lcl_string_append(r, ScCompiler::GetNativeSymbol(ocErrRef));
1993 return;
1995 if( nFlags & ScRefFlags::TAB_3D )
1997 OUString aTabName, aDocName;
1998 pDoc->GetName(nTab, aTabName);
1999 assert( !aTabName.isEmpty() && "empty sheet name");
2000 // External Reference, same as in ScCompiler::MakeTabStr()
2001 if( aTabName[0] == '\'' )
2002 { // "'Doc'#Tab"
2003 sal_Int32 nPos = ScCompiler::GetDocTabPos( aTabName);
2004 if (nPos != -1)
2006 aDocName = aTabName.copy( 0, nPos + 1 );
2007 aTabName = aTabName.copy( nPos + 1 );
2010 else if( nFlags & ScRefFlags::FORCE_DOC )
2012 // VBA has an 'external' flag that forces the addition of the
2013 // tab name _and_ the doc name. The VBA code would be
2014 // needlessly complicated if it constructed an actual external
2015 // reference so we add this somewhat cheesy kludge to force the
2016 // addition of the document name even for non-external references
2017 aDocName = getFileNameFromDoc( pDoc );
2019 ScCompiler::CheckTabQuotes( aTabName, rDetails.eConv);
2021 switch( rDetails.eConv )
2023 default :
2024 case formula::FormulaGrammar::CONV_OOO:
2025 lcl_string_append(r, aDocName);
2026 if( nFlags & ScRefFlags::TAB_ABS )
2027 r.append("$");
2028 lcl_string_append(r, aTabName);
2029 r.append(".");
2030 break;
2032 case formula::FormulaGrammar::CONV_XL_OOX:
2033 if (!aTabName.isEmpty() && aTabName[0] == '\'')
2035 if (!aDocName.isEmpty())
2037 lcl_string_append(r.append("'["), aDocName);
2038 r.append("]");
2039 lcl_string_append(r, aTabName.subView(1));
2041 else
2043 lcl_string_append(r, aTabName);
2045 r.append("!");
2046 break;
2048 [[fallthrough]];
2049 case formula::FormulaGrammar::CONV_XL_A1:
2050 case formula::FormulaGrammar::CONV_XL_R1C1:
2051 if (!aDocName.isEmpty())
2053 lcl_string_append(r.append("["), aDocName);
2054 r.append("]");
2056 lcl_string_append(r, aTabName);
2057 r.append("!");
2058 break;
2062 switch( rDetails.eConv )
2064 default :
2065 case formula::FormulaGrammar::CONV_OOO:
2066 case formula::FormulaGrammar::CONV_XL_A1:
2067 case formula::FormulaGrammar::CONV_XL_OOX:
2068 if( nFlags & ScRefFlags::COL_VALID )
2069 lcl_a1_append_c ( r, nCol, (nFlags & ScRefFlags::COL_ABS) != ScRefFlags::ZERO );
2070 if( nFlags & ScRefFlags::ROW_VALID )
2071 lcl_a1_append_r ( r, nRow, (nFlags & ScRefFlags::ROW_ABS) != ScRefFlags::ZERO );
2072 break;
2074 case formula::FormulaGrammar::CONV_XL_R1C1:
2075 if( nFlags & ScRefFlags::ROW_VALID )
2076 lcl_r1c1_append_r ( r, nRow, (nFlags & ScRefFlags::ROW_ABS) != ScRefFlags::ZERO, rDetails );
2077 if( nFlags & ScRefFlags::COL_VALID )
2078 lcl_r1c1_append_c ( r, nCol, (nFlags & ScRefFlags::COL_ABS) != ScRefFlags::ZERO, rDetails );
2079 break;
2083 void ScAddress::Format( OStringBuffer& r, ScRefFlags nFlags,
2084 const ScDocument* pDoc,
2085 const Details& rDetails) const
2087 lcl_Format(r, nTab, nRow, nCol, nFlags, pDoc, rDetails);
2090 OUString ScAddress::Format(ScRefFlags nFlags, const ScDocument* pDoc,
2091 const Details& rDetails) const
2093 OUStringBuffer r;
2094 lcl_Format(r, nTab, nRow, nCol, nFlags, pDoc, rDetails);
2095 return r.makeStringAndClear();
2098 static void lcl_Split_DocTab( const ScDocument& rDoc, SCTAB nTab,
2099 const ScAddress::Details& rDetails,
2100 ScRefFlags nFlags,
2101 OUString& rTabName, OUString& rDocName )
2103 rDoc.GetName(nTab, rTabName);
2104 rDocName.clear();
2105 // External reference, same as in ScCompiler::MakeTabStr()
2106 if (!rTabName.isEmpty() && rTabName[0] == '\'')
2107 { // "'Doc'#Tab"
2108 sal_Int32 nPos = ScCompiler::GetDocTabPos( rTabName);
2109 if (nPos != -1)
2111 rDocName = rTabName.copy( 0, nPos + 1 );
2112 rTabName = rTabName.copy( nPos + 1 );
2115 else if( nFlags & ScRefFlags::FORCE_DOC )
2117 // VBA has an 'external' flag that forces the addition of the
2118 // tab name _and_ the doc name. The VBA code would be
2119 // needlessly complicated if it constructed an actual external
2120 // reference so we add this somewhat cheesy kludge to force the
2121 // addition of the document name even for non-external references
2122 rDocName = getFileNameFromDoc(&rDoc);
2124 ScCompiler::CheckTabQuotes( rTabName, rDetails.eConv);
2127 static void lcl_ScRange_Format_XL_Header( OUStringBuffer& rString, const ScRange& rRange,
2128 ScRefFlags nFlags, const ScDocument& rDoc,
2129 const ScAddress::Details& rDetails )
2131 if( !(nFlags & ScRefFlags::TAB_3D) )
2132 return;
2134 OUString aTabName, aDocName;
2135 lcl_Split_DocTab( rDoc, rRange.aStart.Tab(), rDetails, nFlags, aTabName, aDocName );
2136 switch (rDetails.eConv)
2138 case formula::FormulaGrammar::CONV_XL_OOX:
2139 if (!aTabName.isEmpty() && aTabName[0] == '\'')
2141 if (!aDocName.isEmpty())
2143 rString.append("'[" + aDocName + "]" + aTabName.subView(1));
2145 else
2147 rString.append(aTabName);
2149 break;
2151 [[fallthrough]];
2152 default:
2153 if (!aDocName.isEmpty())
2155 rString.append("[" + aDocName + "]");
2157 rString.append(aTabName);
2158 break;
2160 if( nFlags & ScRefFlags::TAB2_3D )
2162 lcl_Split_DocTab( rDoc, rRange.aEnd.Tab(), rDetails, nFlags, aTabName, aDocName );
2163 rString.append(":");
2164 rString.append(aTabName);
2166 rString.append("!");
2169 // helpers used in ScRange::Format
2170 static bool lcl_ColAbsFlagDiffer(const ScRefFlags nFlags)
2172 return static_cast<bool>(nFlags & ScRefFlags::COL_ABS) != static_cast<bool>(nFlags & ScRefFlags::COL2_ABS);
2174 static bool lcl_RowAbsFlagDiffer(const ScRefFlags nFlags)
2176 return static_cast<bool>(nFlags & ScRefFlags::ROW_ABS) != static_cast<bool>(nFlags & ScRefFlags::ROW2_ABS);
2179 OUString ScRange::Format( const ScDocument& rDoc, ScRefFlags nFlags,
2180 const ScAddress::Details& rDetails, bool bFullAddressNotation ) const
2182 if( !( nFlags & ScRefFlags::VALID ) )
2184 return ScCompiler::GetNativeSymbol(ocErrRef);
2187 OUStringBuffer r;
2188 switch( rDetails.eConv ) {
2189 default :
2190 case formula::FormulaGrammar::CONV_OOO: {
2191 bool bOneTab = (aStart.Tab() == aEnd.Tab());
2192 if ( !bOneTab )
2193 nFlags |= ScRefFlags::TAB_3D;
2194 r = aStart.Format(nFlags, &rDoc, rDetails);
2195 if( aStart != aEnd ||
2196 lcl_ColAbsFlagDiffer( nFlags ) ||
2197 lcl_RowAbsFlagDiffer( nFlags ))
2199 const ScDocument* pDoc = &rDoc;
2200 // move flags of end reference to start reference, mask with BITS to exclude FORCE_DOC flag
2201 nFlags = ScRefFlags::VALID | (ScRefFlags(o3tl::to_underlying(nFlags) >> 4) & ScRefFlags::BITS);
2202 if ( bOneTab )
2203 pDoc = nullptr;
2204 else
2205 nFlags |= ScRefFlags::TAB_3D;
2206 r.append(":" + aEnd.Format(nFlags, pDoc, rDetails));
2208 break;
2211 case formula::FormulaGrammar::CONV_XL_A1:
2212 case formula::FormulaGrammar::CONV_XL_OOX: {
2213 SCCOL nMaxCol = rDoc.MaxCol();
2214 SCROW nMaxRow = rDoc.MaxRow();
2216 lcl_ScRange_Format_XL_Header( r, *this, nFlags, rDoc, rDetails );
2217 if( aStart.Col() == 0 && aEnd.Col() >= nMaxCol && !bFullAddressNotation )
2219 // Full col refs always require 2 rows (2:2)
2220 lcl_a1_append_r( r, aStart.Row(), (nFlags & ScRefFlags::ROW_ABS) != ScRefFlags::ZERO );
2221 r.append(":");
2222 lcl_a1_append_r( r, aEnd.Row(), (nFlags & ScRefFlags::ROW2_ABS) != ScRefFlags::ZERO );
2224 else if( aStart.Row() == 0 && aEnd.Row() >= nMaxRow && !bFullAddressNotation )
2226 // Full row refs always require 2 cols (A:A)
2227 lcl_a1_append_c( r, aStart.Col(), (nFlags & ScRefFlags::COL_ABS) != ScRefFlags::ZERO );
2228 r.append(":");
2229 lcl_a1_append_c( r, aEnd.Col(), (nFlags & ScRefFlags::COL2_ABS) != ScRefFlags::ZERO );
2231 else
2233 lcl_a1_append_c ( r, aStart.Col(), (nFlags & ScRefFlags::COL_ABS) != ScRefFlags::ZERO );
2234 lcl_a1_append_r ( r, aStart.Row(), (nFlags & ScRefFlags::ROW_ABS) != ScRefFlags::ZERO );
2235 if( aStart.Col() != aEnd.Col() ||
2236 lcl_ColAbsFlagDiffer( nFlags ) ||
2237 aStart.Row() != aEnd.Row() ||
2238 lcl_RowAbsFlagDiffer( nFlags ) ) {
2239 r.append(":");
2240 lcl_a1_append_c ( r, aEnd.Col(), (nFlags & ScRefFlags::COL2_ABS) != ScRefFlags::ZERO );
2241 lcl_a1_append_r ( r, aEnd.Row(), (nFlags & ScRefFlags::ROW2_ABS) != ScRefFlags::ZERO );
2244 break;
2247 case formula::FormulaGrammar::CONV_XL_R1C1: {
2248 SCCOL nMaxCol = rDoc.MaxCol();
2249 SCROW nMaxRow = rDoc.MaxRow();
2251 lcl_ScRange_Format_XL_Header( r, *this, nFlags, rDoc, rDetails );
2252 if( aStart.Col() == 0 && aEnd.Col() >= nMaxCol && !bFullAddressNotation )
2254 lcl_r1c1_append_r( r, aStart.Row(), (nFlags & ScRefFlags::ROW_ABS) != ScRefFlags::ZERO, rDetails );
2255 if( aStart.Row() != aEnd.Row() ||
2256 lcl_RowAbsFlagDiffer( nFlags ) ) {
2257 r.append(":");
2258 lcl_r1c1_append_r( r, aEnd.Row(), (nFlags & ScRefFlags::ROW2_ABS) != ScRefFlags::ZERO, rDetails );
2261 else if( aStart.Row() == 0 && aEnd.Row() >= nMaxRow && !bFullAddressNotation )
2263 lcl_r1c1_append_c( r, aStart.Col(), (nFlags & ScRefFlags::COL_ABS) != ScRefFlags::ZERO, rDetails );
2264 if( aStart.Col() != aEnd.Col() ||
2265 lcl_ColAbsFlagDiffer( nFlags )) {
2266 r.append(":");
2267 lcl_r1c1_append_c( r, aEnd.Col(), (nFlags & ScRefFlags::COL2_ABS) != ScRefFlags::ZERO, rDetails );
2270 else
2272 lcl_r1c1_append_r( r, aStart.Row(), (nFlags & ScRefFlags::ROW_ABS) != ScRefFlags::ZERO, rDetails );
2273 lcl_r1c1_append_c( r, aStart.Col(), (nFlags & ScRefFlags::COL_ABS) != ScRefFlags::ZERO, rDetails );
2274 if( aStart.Col() != aEnd.Col() ||
2275 lcl_ColAbsFlagDiffer( nFlags ) ||
2276 aStart.Row() != aEnd.Row() ||
2277 lcl_RowAbsFlagDiffer( nFlags ) ) {
2278 r.append(":");
2279 lcl_r1c1_append_r( r, aEnd.Row(), (nFlags & ScRefFlags::ROW2_ABS) != ScRefFlags::ZERO, rDetails );
2280 lcl_r1c1_append_c( r, aEnd.Col(), (nFlags & ScRefFlags::COL2_ABS) != ScRefFlags::ZERO, rDetails );
2283 break;
2286 return r.makeStringAndClear();
2289 bool ScAddress::Move( SCCOL dx, SCROW dy, SCTAB dz, ScAddress& rErrorPos, const ScDocument& rDoc )
2291 SCTAB nMaxTab = rDoc.GetTableCount();
2292 SCCOL nMaxCol = rDoc.MaxCol();
2293 SCROW nMaxRow = rDoc.MaxRow();
2294 dx = Col() + dx;
2295 dy = Row() + dy;
2296 dz = Tab() + dz;
2297 bool bValid = true;
2298 rErrorPos.SetCol(dx);
2299 if( dx < 0 )
2301 dx = 0;
2302 bValid = false;
2304 else if( dx > nMaxCol )
2306 dx = nMaxCol;
2307 bValid =false;
2309 rErrorPos.SetRow(dy);
2310 if( dy < 0 )
2312 dy = 0;
2313 bValid = false;
2315 else if( dy > nMaxRow )
2317 dy = nMaxRow;
2318 bValid =false;
2320 rErrorPos.SetTab(dz);
2321 if( dz < 0 )
2323 dz = 0;
2324 bValid = false;
2326 else if( dz > nMaxTab )
2328 // Always set MAXTAB+1 so further checks without ScDocument detect invalid.
2329 rErrorPos.SetTab(MAXTAB+1);
2330 dz = nMaxTab;
2331 bValid =false;
2333 Set( dx, dy, dz );
2334 return bValid;
2337 bool ScRange::Move( SCCOL dx, SCROW dy, SCTAB dz, ScRange& rErrorRange, const ScDocument& rDoc )
2339 SCCOL nMaxCol = rDoc.MaxCol();
2340 SCROW nMaxRow = rDoc.MaxRow();
2341 if (dy && aStart.Row() == 0 && aEnd.Row() == nMaxRow)
2342 dy = 0; // Entire column not to be moved.
2343 if (dx && aStart.Col() == 0 && aEnd.Col() == nMaxCol)
2344 dx = 0; // Entire row not to be moved.
2345 bool b = aStart.Move( dx, dy, dz, rErrorRange.aStart, rDoc );
2346 b &= aEnd.Move( dx, dy, dz, rErrorRange.aEnd, rDoc );
2347 return b;
2350 bool ScRange::MoveSticky( const ScDocument& rDoc, SCCOL dx, SCROW dy, SCTAB dz, ScRange& rErrorRange )
2352 const SCCOL nMaxCol = rDoc.MaxCol();
2353 const SCROW nMaxRow = rDoc.MaxRow();
2354 bool bColRange = (aStart.Col() < aEnd.Col());
2355 bool bRowRange = (aStart.Row() < aEnd.Row());
2356 if (dy && aStart.Row() == 0 && aEnd.Row() == nMaxRow)
2357 dy = 0; // Entire column not to be moved.
2358 if (dx && aStart.Col() == 0 && aEnd.Col() == nMaxCol)
2359 dx = 0; // Entire row not to be moved.
2360 bool b1 = aStart.Move( dx, dy, dz, rErrorRange.aStart, rDoc );
2361 if (dx && bColRange && aEnd.Col() == nMaxCol)
2362 dx = 0; // End column sticky.
2363 if (dy && bRowRange && aEnd.Row() == nMaxRow)
2364 dy = 0; // End row sticky.
2365 SCTAB nOldTab = aEnd.Tab();
2366 bool b2 = aEnd.Move( dx, dy, dz, rErrorRange.aEnd, rDoc );
2367 if (!b2)
2369 // End column or row of a range may have become sticky.
2370 bColRange = (!dx || (bColRange && aEnd.Col() == nMaxCol));
2371 if (dx && bColRange)
2372 rErrorRange.aEnd.SetCol(nMaxCol);
2373 bRowRange = (!dy || (bRowRange && aEnd.Row() == nMaxRow));
2374 if (dy && bRowRange)
2375 rErrorRange.aEnd.SetRow(nMaxRow);
2376 b2 = bColRange && bRowRange && (aEnd.Tab() - nOldTab == dz);
2378 return b1 && b2;
2381 void ScRange::IncColIfNotLessThan(const ScDocument& rDoc, SCCOL nStartCol, SCCOL nOffset)
2383 SCCOL offset;
2384 if (aStart.Col() > nStartCol)
2386 offset = nOffset;
2387 if (nStartCol + nOffset > aStart.Col())
2388 offset = aStart.Col() - nStartCol;
2389 else if (nStartCol - nOffset > aStart.Col())
2390 offset = -1 * (aStart.Col() - nStartCol);
2392 aStart.IncCol(offset);
2393 if (aStart.Col() < 0)
2394 aStart.SetCol(0);
2395 else if(aStart.Col() > rDoc.MaxCol())
2396 aStart.SetCol(rDoc.MaxCol());
2398 if (aEnd.Col() > nStartCol)
2400 offset = nOffset;
2401 if (nStartCol + nOffset > aEnd.Col())
2402 offset = aEnd.Col() - nStartCol;
2403 else if (nStartCol - nOffset > aEnd.Col())
2404 offset = -1 * (aEnd.Col() - nStartCol);
2406 aEnd.IncCol(offset);
2407 if (aEnd.Col() < 0)
2408 aEnd.SetCol(0);
2409 else if(aEnd.Col() > rDoc.MaxCol())
2410 aEnd.SetCol(rDoc.MaxCol());
2414 void ScRange::IncRowIfNotLessThan(const ScDocument& rDoc, SCROW nStartRow, SCROW nOffset)
2416 SCROW offset;
2417 if (aStart.Row() > nStartRow)
2419 offset = nOffset;
2420 if (nStartRow + nOffset > aStart.Row())
2421 offset = aStart.Row() - nStartRow;
2422 else if (nStartRow - nOffset > aStart.Row())
2423 offset = -1 * (aStart.Row() - nStartRow);
2425 aStart.IncRow(offset);
2426 if (aStart.Row() < 0)
2427 aStart.SetRow(0);
2428 else if(aStart.Row() > rDoc.MaxRow())
2429 aStart.SetRow(rDoc.MaxRow());
2431 if (aEnd.Row() > nStartRow)
2433 offset = nOffset;
2434 if (nStartRow + nOffset > aEnd.Row())
2435 offset = aEnd.Row() - nStartRow;
2436 else if (nStartRow - nOffset > aEnd.Row())
2437 offset = -1 * (aEnd.Row() - nStartRow);
2439 aEnd.IncRow(offset);
2440 if (aEnd.Row() < 0)
2441 aEnd.SetRow(0);
2442 else if(aEnd.Row() > rDoc.MaxRow())
2443 aEnd.SetRow(rDoc.MaxRow());
2447 bool ScRange::IsEndColSticky( const ScDocument& rDoc ) const
2449 // Only in an actual column range, i.e. not if both columns are MAXCOL.
2450 return aEnd.Col() == rDoc.MaxCol() && aStart.Col() < aEnd.Col();
2453 bool ScRange::IsEndRowSticky( const ScDocument& rDoc ) const
2455 // Only in an actual row range, i.e. not if both rows are MAXROW.
2456 return aEnd.Row() == rDoc.MaxRow() && aStart.Row() < aEnd.Row();
2459 void ScRange::IncEndColSticky( const ScDocument& rDoc, SCCOL nDelta )
2461 SCCOL nCol = aEnd.Col();
2462 if (aStart.Col() >= nCol)
2464 // Less than two columns => not sticky.
2465 aEnd.IncCol( nDelta);
2466 return;
2469 const SCCOL nMaxCol = rDoc.MaxCol();
2470 if (nCol == nMaxCol)
2471 // already sticky
2472 return;
2474 if (nCol < nMaxCol)
2475 aEnd.SetCol( ::std::min( static_cast<SCCOL>(nCol + nDelta), nMaxCol));
2476 else
2477 aEnd.IncCol( nDelta); // was greater than nMaxCol, caller should know...
2480 void ScRange::IncEndRowSticky( const ScDocument& rDoc, SCROW nDelta )
2482 SCROW nRow = aEnd.Row();
2483 if (aStart.Row() >= nRow)
2485 // Less than two rows => not sticky.
2486 aEnd.IncRow( nDelta);
2487 return;
2490 if (nRow == rDoc.MaxRow())
2491 // already sticky
2492 return;
2494 if (nRow < rDoc.MaxRow())
2495 aEnd.SetRow( ::std::min( static_cast<SCROW>(nRow + nDelta), rDoc.MaxRow()));
2496 else
2497 aEnd.IncRow( nDelta); // was greater than rDoc.MaxRow(), caller should know...
2500 OUString ScAddress::GetColRowString() const
2502 OUStringBuffer aString;
2504 switch( detailsOOOa1.eConv )
2506 default :
2507 case formula::FormulaGrammar::CONV_OOO:
2508 case formula::FormulaGrammar::CONV_XL_A1:
2509 case formula::FormulaGrammar::CONV_XL_OOX:
2510 lcl_ScColToAlpha( aString, nCol);
2511 aString.append(nRow+1);
2512 break;
2514 case formula::FormulaGrammar::CONV_XL_R1C1:
2515 lcl_r1c1_append_r ( aString, nRow, false/*bAbsolute*/, detailsOOOa1 );
2516 lcl_r1c1_append_c ( aString, nCol, false/*bAbsolute*/, detailsOOOa1 );
2517 break;
2520 return aString.makeStringAndClear();
2523 OUString ScRefAddress::GetRefString( const ScDocument& rDoc, SCTAB nActTab,
2524 const ScAddress::Details& rDetails ) const
2526 if ( Tab()+1 > rDoc.GetTableCount() )
2527 return ScCompiler::GetNativeSymbol(ocErrRef);
2529 ScRefFlags nFlags = ScRefFlags::VALID;
2530 if ( nActTab != Tab() )
2532 nFlags |= ScRefFlags::TAB_3D;
2533 if ( !bRelTab )
2534 nFlags |= ScRefFlags::TAB_ABS;
2536 if ( !bRelCol )
2537 nFlags |= ScRefFlags::COL_ABS;
2538 if ( !bRelRow )
2539 nFlags |= ScRefFlags::ROW_ABS;
2541 return aAdr.Format(nFlags, &rDoc, rDetails);
2544 bool AlphaToCol(const ScDocument& rDoc, SCCOL& rCol, std::u16string_view rStr)
2546 SCCOL nResult = 0;
2547 sal_Int32 nStop = rStr.size();
2548 sal_Int32 nPos = 0;
2549 sal_Unicode c;
2550 const SCCOL nMaxCol = rDoc.MaxCol();
2551 while (nResult <= nMaxCol && nPos < nStop && (c = rStr[nPos]) != 0 &&
2552 rtl::isAsciiAlpha(c))
2554 if (nPos > 0)
2555 nResult = (nResult + 1) * 26;
2556 nResult += ScGlobal::ToUpperAlpha(c) - 'A';
2557 ++nPos;
2559 bool bOk = (rDoc.ValidCol(nResult) && nPos > 0);
2560 if (bOk)
2561 rCol = nResult;
2562 return bOk;
2565 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */