Stop leaking all ScPostIt instances.
[LibreOffice.git] / sc / source / core / tool / address.cxx
blobbda4a5a1d9329f698f074aaf8a3001674ce47763
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 "address.hxx"
21 #include "global.hxx"
22 #include "compiler.hxx"
23 #include "document.hxx"
24 #include "externalrefmgr.hxx"
26 #include "globstr.hrc"
27 #include <sal/alloca.h>
29 #include <com/sun/star/frame/XModel.hpp>
30 #include <com/sun/star/beans/XPropertySet.hpp>
31 #include <com/sun/star/sheet/ExternalLinkInfo.hpp>
32 #include <com/sun/star/sheet/ExternalLinkType.hpp>
33 #include <comphelper/string.hxx>
34 #include <sfx2/objsh.hxx>
35 #include <tools/string.hxx>
36 #include <tools/urlobj.hxx>
38 using namespace ::com::sun::star;
40 const ScAddress::Details ScAddress::detailsOOOa1( formula::FormulaGrammar::CONV_OOO, 0, 0 );
42 ScAddress::Details::Details ( const ScDocument* pDoc,
43 const ScAddress & rAddr ) :
44 eConv( pDoc->GetAddressConvention() ),
45 nRow( rAddr.Row() ),
46 nCol( rAddr.Col() )
50 namespace {
52 const sal_Unicode* parseQuotedNameWithBuffer( const sal_Unicode* pStart, const sal_Unicode* p, OUString& rName )
54 // The current character must be on the 2nd quote.
56 // Push all the characters up to the current, but skip the very first
57 // character which is the opening quote.
58 OUStringBuffer aBuf(OUString(pStart+1, p-pStart-1));
60 ++p; // Skip the 2nd quote.
61 sal_Unicode cPrev = 0;
62 for (; *p; ++p)
64 if (*p == '\'')
66 if (cPrev == '\'')
68 // double single-quote equals one single quote.
69 aBuf.append(*p);
70 cPrev = 0;
71 continue;
74 else if (cPrev == '\'')
76 // We are past the closing quote. We're done!
77 rName = aBuf.makeStringAndClear();
78 return p;
80 else
81 aBuf.append(*p);
82 cPrev = *p;
85 return pStart;
88 /**
89 * Parse from the opening single quote to the closing single quote. Inside
90 * the quotes, a single quote character is encoded by double single-quote
91 * characters.
93 * @param p pointer to the first character to begin parsing.
94 * @param rName (reference) parsed name within the quotes. If the name is
95 * empty, either the parsing failed or it's an empty quote.
97 * @return pointer to the character immediately after the closing single
98 * quote.
100 const sal_Unicode* parseQuotedName( const sal_Unicode* p, OUString& rName )
102 if (*p != '\'')
103 return p;
105 const sal_Unicode* pStart = p;
106 sal_Unicode cPrev = 0;
107 for (++p; *p; ++p)
109 if (*p == '\'')
111 if (cPrev == '\'')
113 // double single-quote equals one single quote.
114 return parseQuotedNameWithBuffer(pStart, p, rName);
117 else if (cPrev == '\'')
119 // We are past the closing quote. We're done! Skip the opening
120 // and closing quotes.
121 rName = OUString(pStart+1, p - pStart-2);
122 return p;
125 cPrev = *p;
128 rName = "";
129 return pStart;
134 static long int
135 sal_Unicode_strtol ( const sal_Unicode* p,
136 const sal_Unicode** pEnd )
138 long int accum = 0, prev = 0;
139 bool is_neg = false;
141 if( *p == '-' )
143 is_neg = true;
144 p++;
146 else if( *p == '+' )
147 p++;
149 while (rtl::isAsciiDigit( *p ))
151 accum = accum * 10 + *p - '0';
152 if( accum < prev )
154 *pEnd = NULL;
155 return 0;
157 prev = accum;
158 p++;
161 *pEnd = p;
162 return is_neg ? -accum : accum;
165 static const sal_Unicode* lcl_eatWhiteSpace( const sal_Unicode* p )
167 if ( p )
169 while( *p == ' ' )
170 ++p;
172 return p;
175 /** Determines the number of sheets an external reference spans and sets
176 rRange.aEnd.nTab accordingly. If a sheet is not found, the corresponding
177 bits in rFlags are cleared. pExtInfo is filled if it wasn't already. If in
178 cached order rStartTabName comes after rEndTabName, pExtInfo->maTabName
179 is set to rEndTabName.
180 @returns <FALSE/> if pExtInfo is already filled and rExternDocName does not
181 result in the identical file ID. Else <TRUE/>.
183 static bool lcl_ScRange_External_TabSpan(
184 ScRange & rRange,
185 sal_uInt16 & rFlags,
186 ScAddress::ExternalInfo* pExtInfo,
187 const OUString & rExternDocName,
188 const OUString & rStartTabName,
189 const OUString & rEndTabName,
190 ScDocument* pDoc )
192 if (rExternDocName.isEmpty())
193 return !pExtInfo || !pExtInfo->mbExternal;
195 ScExternalRefManager* pRefMgr = pDoc->GetExternalRefManager();
196 if (pRefMgr->isOwnDocument( rExternDocName))
198 // This is an internal document. Get the sheet positions from the
199 // ScDocument instance.
200 if (!rStartTabName.isEmpty())
202 SCTAB nTab;
203 if (pDoc->GetTable(rStartTabName, nTab))
204 rRange.aStart.SetTab(nTab);
207 if (!rEndTabName.isEmpty())
209 SCTAB nTab;
210 if (pDoc->GetTable(rEndTabName, nTab))
211 rRange.aEnd.SetTab(nTab);
213 return !pExtInfo || !pExtInfo->mbExternal;
216 sal_uInt16 nFileId = pRefMgr->getExternalFileId( rExternDocName);
218 if (pExtInfo)
220 if (pExtInfo->mbExternal)
222 if (pExtInfo->mnFileId != nFileId)
223 return false;
225 else
227 pExtInfo->mbExternal = true;
228 pExtInfo->maTabName = rStartTabName;
229 pExtInfo->mnFileId = nFileId;
233 if (rEndTabName.isEmpty() || rStartTabName == rEndTabName)
235 rRange.aEnd.SetTab( rRange.aStart.Tab());
236 return true;
239 SCsTAB nSpan = pRefMgr->getCachedTabSpan( nFileId, rStartTabName, rEndTabName);
240 if (nSpan == -1)
241 rFlags &= ~(SCA_VALID_TAB | SCA_VALID_TAB2);
242 else if (nSpan == 0)
243 rFlags &= ~SCA_VALID_TAB2;
244 else if (nSpan >= 1)
245 rRange.aEnd.SetTab( rRange.aStart.Tab() + nSpan - 1);
246 else // (nSpan < -1)
248 rRange.aEnd.SetTab( rRange.aStart.Tab() - nSpan - 1);
249 if (pExtInfo)
250 pExtInfo->maTabName = rEndTabName;
252 return true;
255 /** Returns NULL if the string should be a sheet name, but is invalid.
256 Returns a pointer to the first character after the sheet name, if there was
257 any, else pointer to start.
258 @param pMsoxlQuoteStop
259 Starting _within_ a quoted name, but still may be 3D; quoted name stops
260 at pMsoxlQuoteStop
262 static const sal_Unicode *
263 lcl_XL_ParseSheetRef( const sal_Unicode* start,
264 OUString& rExternTabName,
265 bool allow_3d,
266 const sal_Unicode* pMsoxlQuoteStop )
268 OUString aTabName;
269 const sal_Unicode *p = start;
271 // XL only seems to use single quotes for sheet names.
272 if (pMsoxlQuoteStop)
274 const sal_Unicode* pCurrentStart = p;
275 while (p < pMsoxlQuoteStop)
277 if (*p == '\'')
279 // We pre-analyzed the quoting, no checks needed here.
280 if (*++p == '\'')
282 aTabName += OUString( pCurrentStart,
283 sal::static_int_cast<xub_StrLen>( p - pCurrentStart));
284 pCurrentStart = ++p;
287 else if (*p == ':')
289 break; // while
291 else
292 ++p;
294 if (pCurrentStart < p)
295 aTabName += OUString( pCurrentStart, sal::static_int_cast<xub_StrLen>( p - pCurrentStart));
296 if (aTabName.isEmpty())
297 return NULL;
298 if (p == pMsoxlQuoteStop)
299 ++p; // position on ! of ...'!...
300 if( *p != '!' && ( !allow_3d || *p != ':' ) )
301 return (!allow_3d && *p == ':') ? p : start;
303 else if( *p == '\'')
305 p = parseQuotedName(p, aTabName);
306 if (aTabName.isEmpty())
307 return NULL;
309 else
311 bool only_digits = true;
314 * Valid: Normal!a1
315 * Valid: x.y!a1
316 * Invalid: .y!a1
318 * Some names starting with digits are actually valid, but
319 * unparse quoted. Things are quite tricky: most sheet names
320 * starting with a digit are ok, but not those starting with
321 * "[0-9]*\." or "[0-9]+[eE]".
323 * Valid: 42!a1
324 * Valid: 4x!a1
325 * Invalid: 1.!a1
326 * Invalid: 1e!a1
328 while( 1 )
330 const sal_Unicode uc = *p;
331 if( rtl::isAsciiAlpha( uc ) || uc == '_' )
333 if( only_digits && p != start &&
334 (uc == 'e' || uc == 'E' ) )
336 p = start;
337 break;
339 only_digits = false;
340 p++;
342 else if( rtl::isAsciiDigit( uc ))
344 p++;
346 else if( uc == '.' )
348 if( only_digits ) // Valid, except after only digits.
350 p = start;
351 break;
353 p++;
355 else if (uc > 127)
357 // non ASCII character is allowed.
358 ++p;
360 else
361 break;
364 if( *p != '!' && ( !allow_3d || *p != ':' ) )
365 return (!allow_3d && *p == ':') ? p : start;
367 aTabName += OUString( start, sal::static_int_cast<xub_StrLen>( p - start ) );
370 rExternTabName = aTabName;
371 return p;
374 /** Tries to obtain the external document index and replace by actual document
375 name.
377 @param ppErrRet
378 Contains the default pointer the caller would return if this method
379 returns FALSE, may be replaced by NULL for type or data errors.
381 @returns FALSE only if the input name is numeric and not within the index
382 sequence, or the link type cannot be determined or data mismatch. Returns
383 TRUE in all other cases, also when there is no index sequence or the input
384 name is not numeric.
386 static bool lcl_XL_getExternalDoc( const sal_Unicode** ppErrRet, OUString& rExternDocName,
387 const uno::Sequence< const sheet::ExternalLinkInfo > * pExternalLinks)
389 // 1-based, sequence starts with an empty element.
390 if (pExternalLinks && pExternalLinks->hasElements())
392 // A numeric "document name" is an index into the sequence.
393 if (CharClass::isAsciiNumeric( rExternDocName))
395 sal_Int32 i = rExternDocName.toInt32();
396 if (i < 0 || i >= pExternalLinks->getLength())
397 return false; // with default *ppErrRet
398 const sheet::ExternalLinkInfo & rInfo = (*pExternalLinks)[i];
399 switch (rInfo.Type)
401 case sheet::ExternalLinkType::DOCUMENT :
403 OUString aStr;
404 if (!(rInfo.Data >>= aStr))
406 OSL_TRACE( "ScRange::Parse_XL_Header: Data type mismatch for ExternalLinkInfo %d", i);
407 *ppErrRet = NULL;
408 return false;
410 rExternDocName = aStr;
412 break;
413 case sheet::ExternalLinkType::SELF :
414 return false; // ???
415 case sheet::ExternalLinkType::SPECIAL :
416 // silently return nothing (do not assert), caller has to handle this
417 *ppErrRet = NULL;
418 return false;
419 default:
420 OSL_TRACE( "ScRange::Parse_XL_Header: unhandled ExternalLinkType %d for index %d",
421 rInfo.Type, i);
422 *ppErrRet = NULL;
423 return false;
427 return true;
430 const sal_Unicode* ScRange::Parse_XL_Header(
431 const sal_Unicode* p,
432 const ScDocument* pDoc,
433 OUString& rExternDocName,
434 OUString& rStartTabName,
435 OUString& rEndTabName,
436 sal_uInt16& nFlags,
437 bool bOnlyAcceptSingle,
438 const uno::Sequence< const sheet::ExternalLinkInfo > * pExternalLinks )
440 const sal_Unicode* startTabs, *start = p;
441 sal_uInt16 nSaveFlags = nFlags;
443 // Is this an external reference ?
444 rStartTabName = "";
445 rEndTabName = "";
446 rExternDocName = "";
447 const sal_Unicode* pMsoxlQuoteStop = NULL;
448 if (*p == '[')
450 ++p;
451 // Only single quotes are correct, and a double single quote escapes a
452 // single quote text inside the quoted text.
453 if (*p == '\'')
455 p = parseQuotedName(p, rExternDocName);
456 if (!*p || *p != ']' || rExternDocName.isEmpty())
458 rExternDocName = "";
459 return start;
462 else
464 // non-quoted file name.
465 p = ScGlobal::UnicodeStrChr( start+1, ']' );
466 if( p == NULL )
467 return start;
468 rExternDocName += OUString( start+1, sal::static_int_cast<xub_StrLen>( p-(start+1) ) );
470 ++p;
472 const sal_Unicode* pErrRet = start;
473 if (!lcl_XL_getExternalDoc( &pErrRet, rExternDocName, pExternalLinks))
474 return pErrRet;
476 rExternDocName = ScGlobal::GetAbsDocName(rExternDocName, pDoc->GetDocumentShell());
478 else if (*p == '\'')
480 // Sickness in Excel's ODF msoxl namespace:
481 // 'E:\[EXTDATA8.XLS]Sheet1'!$A$7 or
482 // 'E:\[EXTDATA12B.XLSB]Sheet1:Sheet3'!$A$11
483 // But, 'Sheet1'!B3 would also be a valid!
484 // Excel does not allow [ and ] characters in sheet names though.
485 // But, more sickness comes with MOOXML as there may be
486 // '[1]Sheet 4'!$A$1 where [1] is the external doc's index.
487 p = parseQuotedName(p, rExternDocName);
488 if (!*p || *p != '!')
490 rExternDocName = "";
491 return start;
493 if (!rExternDocName.isEmpty())
495 sal_Int32 nOpen = rExternDocName.indexOf( '[');
496 if (nOpen == -1)
497 rExternDocName = "";
498 else
500 sal_Int32 nClose = rExternDocName.indexOf( ']', nOpen+1);
501 if (nClose == -1)
502 rExternDocName = "";
503 else
505 rExternDocName = rExternDocName.copy(0, nClose);
506 rExternDocName = rExternDocName.replaceAt( nOpen, 1, "");
507 pMsoxlQuoteStop = p - 1; // the ' quote char
508 // There may be embedded escaped quotes, just matching the
509 // doc name's length may not work.
510 for (p = start; *p != '['; ++p)
512 for ( ; *p != ']'; ++p)
514 ++p;
516 // Handle '[1]Sheet 4'!$A$1
517 if (nOpen == 0)
519 const sal_Unicode* pErrRet = start;
520 if (!lcl_XL_getExternalDoc( &pErrRet, rExternDocName, pExternalLinks))
521 return pErrRet;
526 if (rExternDocName.isEmpty())
527 p = start;
530 startTabs = p;
531 p = lcl_XL_ParseSheetRef( p, rStartTabName, !bOnlyAcceptSingle, pMsoxlQuoteStop);
532 if( NULL == p )
533 return start; // invalid tab
534 if (bOnlyAcceptSingle && *p == ':')
535 return NULL; // 3D
536 if( p != startTabs )
538 nFlags |= SCA_VALID_TAB | SCA_TAB_3D | SCA_TAB_ABSOLUTE;
539 if( *p == ':' ) // 3d ref
541 p = lcl_XL_ParseSheetRef( p+1, rEndTabName, false, pMsoxlQuoteStop);
542 if( p == NULL )
544 nFlags = nSaveFlags;
545 return start; // invalid tab
547 nFlags |= SCA_VALID_TAB2 | SCA_TAB2_3D | SCA_TAB2_ABSOLUTE;
549 else
551 // If only one sheet is given, the full reference is still valid,
552 // only the second 3D flag is not set.
553 nFlags |= SCA_VALID_TAB2 | SCA_TAB2_ABSOLUTE;
554 aEnd.SetTab( aStart.Tab() );
557 if( *p++ != '!' )
559 nFlags = nSaveFlags;
560 return start; // syntax error
562 else
563 p = lcl_eatWhiteSpace( p );
565 else
567 nFlags |= SCA_VALID_TAB | SCA_VALID_TAB2;
568 // Use the current tab, it needs to be passed in. : aEnd.SetTab( .. );
571 if (!rExternDocName.isEmpty())
573 ScExternalRefManager* pRefMgr = pDoc->GetExternalRefManager();
574 pRefMgr->convertToAbsName(rExternDocName);
576 else
578 // Internal reference.
579 if (rStartTabName.isEmpty())
581 nFlags = nSaveFlags;
582 return start;
585 SCTAB nTab;
586 if (!pDoc->GetTable(rStartTabName, nTab))
588 // invalid table name.
589 nFlags &= ~SCA_VALID_TAB;
590 nTab = -1;
593 aStart.SetTab(nTab);
594 aEnd.SetTab(nTab);
596 if (!rEndTabName.isEmpty())
598 if (!pDoc->GetTable(rEndTabName, nTab))
600 // invalid table name.
601 nFlags &= ~SCA_VALID_TAB2;
602 nTab = -1;
605 aEnd.SetTab(nTab);
608 return p;
611 static const sal_Unicode*
612 lcl_r1c1_get_col( const sal_Unicode* p,
613 const ScAddress::Details& rDetails,
614 ScAddress* pAddr, sal_uInt16* nFlags )
616 const sal_Unicode *pEnd;
617 long int n;
618 bool isRelative;
620 if( p[0] == '\0' )
621 return NULL;
623 p++;
624 if( (isRelative = (*p == '[') ) != false )
625 p++;
626 n = sal_Unicode_strtol( p, &pEnd );
627 if( NULL == pEnd )
628 return NULL;
630 if( p == pEnd ) // C is a relative ref with offset 0
632 if( isRelative )
633 return NULL;
634 n = rDetails.nCol;
636 else if( isRelative )
638 if( *pEnd != ']' )
639 return NULL;
640 n += rDetails.nCol;
641 pEnd++;
643 else
645 *nFlags |= SCA_COL_ABSOLUTE;
646 n--;
649 if( n < 0 || n >= MAXCOLCOUNT )
650 return NULL;
651 pAddr->SetCol( static_cast<SCCOL>( n ) );
652 *nFlags |= SCA_VALID_COL;
654 return pEnd;
656 static inline const sal_Unicode*
657 lcl_r1c1_get_row( const sal_Unicode* p,
658 const ScAddress::Details& rDetails,
659 ScAddress* pAddr, sal_uInt16* nFlags )
661 const sal_Unicode *pEnd;
662 long int n;
663 bool isRelative;
665 if( p[0] == '\0' )
666 return NULL;
668 p++;
669 if( (isRelative = (*p == '[') ) != false )
670 p++;
671 n = sal_Unicode_strtol( p, &pEnd );
672 if( NULL == pEnd )
673 return NULL;
675 if( p == pEnd ) // R is a relative ref with offset 0
677 if( isRelative )
678 return NULL;
679 n = rDetails.nRow;
681 else if( isRelative )
683 if( *pEnd != ']' )
684 return NULL;
685 n += rDetails.nRow;
686 pEnd++;
688 else
690 *nFlags |= SCA_ROW_ABSOLUTE;
691 n--;
694 if( n < 0 || n >= MAXROWCOUNT )
695 return NULL;
696 pAddr->SetRow( static_cast<SCROW>( n ) );
697 *nFlags |= SCA_VALID_ROW;
699 return pEnd;
702 static sal_uInt16
703 lcl_ScRange_Parse_XL_R1C1( ScRange& r,
704 const sal_Unicode* p,
705 ScDocument* pDoc,
706 const ScAddress::Details& rDetails,
707 bool bOnlyAcceptSingle,
708 ScAddress::ExternalInfo* pExtInfo )
710 const sal_Unicode* pTmp = NULL;
711 OUString aExternDocName, aStartTabName, aEndTabName;
712 sal_uInt16 nFlags = SCA_VALID | SCA_VALID_TAB;
713 // Keep in mind that nFlags2 gets left-shifted by 4 bits before being merged.
714 sal_uInt16 nFlags2 = SCA_VALID_TAB;
716 p = r.Parse_XL_Header( p, pDoc, aExternDocName, aStartTabName,
717 aEndTabName, nFlags, bOnlyAcceptSingle, NULL );
719 if (!aExternDocName.isEmpty())
720 lcl_ScRange_External_TabSpan( r, nFlags, pExtInfo, aExternDocName,
721 aStartTabName, aEndTabName, pDoc);
723 if( NULL == p )
724 return 0;
726 if( *p == 'R' || *p == 'r' )
728 if( NULL == (p = lcl_r1c1_get_row( p, rDetails, &r.aStart, &nFlags )) )
729 goto failed;
731 if( *p != 'C' && *p != 'c' ) // full row R#
733 if( p[0] != ':' || (p[1] != 'R' && p[1] != 'r' ) ||
734 NULL == (pTmp = lcl_r1c1_get_row( p+1, rDetails, &r.aEnd, &nFlags2 )))
736 // Only the initial row number is given, or the second row
737 // number is invalid. Fallback to just the initial R
738 nFlags |= (nFlags << 4);
739 r.aEnd.SetRow( r.aStart.Row() );
741 else
743 // Full row range successfully parsed.
744 nFlags |= (nFlags2 << 4);
745 p = pTmp;
748 if (p && p[0] != 0)
750 // any trailing invalid character must invalidate the whole address.
751 nFlags &= ~(SCA_VALID | SCA_VALID_COL | SCA_VALID_ROW | SCA_VALID_TAB |
752 SCA_VALID_COL2 | SCA_VALID_ROW2 | SCA_VALID_TAB2);
753 return nFlags;
756 nFlags |=
757 SCA_VALID_COL | SCA_VALID_COL2 |
758 SCA_COL_ABSOLUTE | SCA_COL2_ABSOLUTE;
759 r.aStart.SetCol( 0 );
760 r.aEnd.SetCol( MAXCOL );
762 return bOnlyAcceptSingle ? 0 : nFlags;
764 else if( NULL == (p = lcl_r1c1_get_col( p, rDetails, &r.aStart, &nFlags )))
765 goto failed;
767 if( p[0] != ':' ||
768 (p[1] != 'R' && p[1] != 'r') ||
769 NULL == (pTmp = lcl_r1c1_get_row( p+1, rDetails, &r.aEnd, &nFlags2 )) ||
770 (*pTmp != 'C' && *pTmp != 'c') ||
771 NULL == (pTmp = lcl_r1c1_get_col( pTmp, rDetails, &r.aEnd, &nFlags2 )))
773 // single cell reference
775 if (p && p[0] != 0)
777 // any trailing invalid character must invalidate the whole address.
778 nFlags &= ~(SCA_VALID | SCA_VALID_COL | SCA_VALID_ROW | SCA_VALID_TAB);
779 return nFlags;
782 return bOnlyAcceptSingle ? nFlags : 0;
784 p = pTmp;
786 // double reference
788 if (p && p[0] != 0)
790 // any trailing invalid character must invalidate the whole range.
791 nFlags &= ~(SCA_VALID | SCA_VALID_COL | SCA_VALID_ROW | SCA_VALID_TAB |
792 SCA_VALID_COL2 | SCA_VALID_ROW2 | SCA_VALID_TAB2);
793 return nFlags;
796 nFlags |= (nFlags2 << 4);
797 return bOnlyAcceptSingle ? 0 : nFlags;
799 else if( *p == 'C' || *p == 'c' ) // full col C#
801 if( NULL == (p = lcl_r1c1_get_col( p, rDetails, &r.aStart, &nFlags )))
802 goto failed;
804 if( p[0] != ':' || (p[1] != 'C' && p[1] != 'c') ||
805 NULL == (pTmp = lcl_r1c1_get_col( p+1, rDetails, &r.aEnd, &nFlags2 )))
806 { // Fallback to just the initial C
807 nFlags |= (nFlags << 4);
808 r.aEnd.SetCol( r.aStart.Col() );
810 else
812 nFlags |= (nFlags2 << 4);
813 p = pTmp;
816 if (p && p[0] != 0)
818 // any trailing invalid character must invalidate the whole address.
819 nFlags &= ~(SCA_VALID | SCA_VALID_COL | SCA_VALID_ROW | SCA_VALID_TAB |
820 SCA_VALID_COL2 | SCA_VALID_ROW2 | SCA_VALID_TAB2);
821 return nFlags;
824 nFlags |=
825 SCA_VALID_ROW | SCA_VALID_ROW2 |
826 SCA_ROW_ABSOLUTE | SCA_ROW2_ABSOLUTE;
827 r.aStart.SetRow( 0 );
828 r.aEnd.SetRow( MAXROW );
830 return bOnlyAcceptSingle ? 0 : nFlags;
833 failed :
834 return 0;
837 static inline const sal_Unicode*
838 lcl_a1_get_col( const sal_Unicode* p, ScAddress* pAddr, sal_uInt16* nFlags )
840 SCCOL nCol;
842 if( *p == '$' )
843 *nFlags |= SCA_COL_ABSOLUTE, p++;
845 if( !rtl::isAsciiAlpha( *p ) )
846 return NULL;
848 nCol = sal::static_int_cast<SCCOL>( toupper( char(*p++) ) - 'A' );
849 while (nCol <= MAXCOL && rtl::isAsciiAlpha(*p))
850 nCol = sal::static_int_cast<SCCOL>( ((nCol + 1) * 26) + toupper( char(*p++) ) - 'A' );
851 if( nCol > MAXCOL || rtl::isAsciiAlpha( *p ) )
852 return NULL;
854 *nFlags |= SCA_VALID_COL;
855 pAddr->SetCol( nCol );
857 return p;
860 static inline const sal_Unicode*
861 lcl_a1_get_row( const sal_Unicode* p, ScAddress* pAddr, sal_uInt16* nFlags )
863 const sal_Unicode *pEnd;
864 long int n;
866 if( *p == '$' )
867 *nFlags |= SCA_ROW_ABSOLUTE, p++;
869 n = sal_Unicode_strtol( p, &pEnd ) - 1;
870 if( NULL == pEnd || p == pEnd || n < 0 || n > MAXROW )
871 return NULL;
873 *nFlags |= SCA_VALID_ROW;
874 pAddr->SetRow( static_cast<SCROW>(n) );
876 return pEnd;
879 static sal_uInt16
880 lcl_ScRange_Parse_XL_A1( ScRange& r,
881 const sal_Unicode* p,
882 ScDocument* pDoc,
883 bool bOnlyAcceptSingle,
884 ScAddress::ExternalInfo* pExtInfo,
885 const uno::Sequence< const sheet::ExternalLinkInfo > * pExternalLinks )
887 const sal_Unicode* tmp1, *tmp2;
888 OUString aExternDocName, aStartTabName, aEndTabName; // for external link table
889 sal_uInt16 nFlags = SCA_VALID | SCA_VALID_TAB, nFlags2 = SCA_VALID_TAB;
891 p = r.Parse_XL_Header( p, pDoc, aExternDocName, aStartTabName,
892 aEndTabName, nFlags, bOnlyAcceptSingle, pExternalLinks );
894 if (!aExternDocName.isEmpty())
895 lcl_ScRange_External_TabSpan( r, nFlags, pExtInfo, aExternDocName,
896 aStartTabName, aEndTabName, pDoc);
898 if( NULL == p )
899 return 0;
901 tmp1 = lcl_a1_get_col( p, &r.aStart, &nFlags );
902 if( tmp1 == NULL ) // Is it a row only reference 3:5
904 if( bOnlyAcceptSingle ) // by definition full row refs are ranges
905 return 0;
907 tmp1 = lcl_a1_get_row( p, &r.aStart, &nFlags );
909 tmp1 = lcl_eatWhiteSpace( tmp1 );
910 if( !tmp1 || *tmp1++ != ':' ) // Even a singleton requires ':' (eg 2:2)
911 return 0;
913 tmp1 = lcl_eatWhiteSpace( tmp1 );
914 tmp2 = lcl_a1_get_row( tmp1, &r.aEnd, &nFlags2 );
915 if( !tmp2 )
916 return 0;
918 r.aStart.SetCol( 0 ); r.aEnd.SetCol( MAXCOL );
919 nFlags |=
920 SCA_VALID_COL | SCA_VALID_COL2 |
921 SCA_COL_ABSOLUTE | SCA_COL2_ABSOLUTE;
922 nFlags |= (nFlags2 << 4);
923 return nFlags;
926 tmp2 = lcl_a1_get_row( tmp1, &r.aStart, &nFlags );
927 if( tmp2 == NULL ) // check for col only reference F:H
929 if( bOnlyAcceptSingle ) // by definition full col refs are ranges
930 return 0;
932 tmp1 = lcl_eatWhiteSpace( tmp1 );
933 if( *tmp1++ != ':' ) // Even a singleton requires ':' (eg F:F)
934 return 0;
936 tmp1 = lcl_eatWhiteSpace( tmp1 );
937 tmp2 = lcl_a1_get_col( tmp1, &r.aEnd, &nFlags2 );
938 if( !tmp2 )
939 return 0;
941 r.aStart.SetRow( 0 ); r.aEnd.SetRow( MAXROW );
942 nFlags |=
943 SCA_VALID_ROW | SCA_VALID_ROW2 |
944 SCA_ROW_ABSOLUTE | SCA_ROW2_ABSOLUTE;
945 nFlags |= (nFlags2 << 4);
946 return nFlags;
949 // prepare as if it's a singleton, in case we want to fall back */
950 r.aEnd.SetCol( r.aStart.Col() );
951 r.aEnd.SetRow( r.aStart.Row() ); // don't overwrite sheet number as parsed in Parse_XL_Header()
953 if ( bOnlyAcceptSingle )
955 if ( *tmp2 == 0 )
956 return nFlags;
957 else
959 // any trailing invalid character must invalidate the address.
960 nFlags &= ~(SCA_VALID | SCA_VALID_COL | SCA_VALID_ROW | SCA_VALID_TAB);
961 return nFlags;
965 tmp2 = lcl_eatWhiteSpace( tmp2 );
966 if( *tmp2 != ':' )
968 // Sheet1:Sheet2!C4 is a valid range, without a second sheet it is
969 // not. Any trailing invalid character invalidates the range.
970 if (*tmp2 == 0 && (nFlags & SCA_TAB2_3D))
972 if (nFlags & SCA_COL_ABSOLUTE)
973 nFlags |= SCA_COL2_ABSOLUTE;
974 if (nFlags & SCA_ROW_ABSOLUTE)
975 nFlags |= SCA_ROW2_ABSOLUTE;
977 else
978 nFlags &= ~(SCA_VALID |
979 SCA_VALID_COL | SCA_VALID_ROW | SCA_VALID_TAB |
980 SCA_VALID_COL2 | SCA_VALID_ROW2 | SCA_VALID_TAB2);
981 return nFlags;
984 p = tmp2;
985 p = lcl_eatWhiteSpace( p+1 );
986 tmp1 = lcl_a1_get_col( p, &r.aEnd, &nFlags2 );
987 if( !tmp1 && aEndTabName.isEmpty() ) // Probably the aEndTabName was specified after the first range
989 p = lcl_XL_ParseSheetRef( p, aEndTabName, false, NULL );
990 if( p )
992 SCTAB nTab = 0;
993 if( !aEndTabName.isEmpty() && pDoc->GetTable( aEndTabName, nTab ) )
995 r.aEnd.SetTab( nTab );
996 nFlags |= SCA_VALID_TAB2 | SCA_TAB2_3D | SCA_TAB2_ABSOLUTE;
998 p = lcl_eatWhiteSpace( p+1 );
999 tmp1 = lcl_a1_get_col( p, &r.aEnd, &nFlags2 );
1002 if( !tmp1 ) // strange, but valid singleton
1003 return nFlags;
1005 tmp2 = lcl_a1_get_row( tmp1, &r.aEnd, &nFlags2 );
1006 if( !tmp2 ) // strange, but valid singleton
1007 return nFlags;
1009 if ( *tmp2 != 0 )
1011 // any trailing invalid character must invalidate the range.
1012 nFlags &= ~(SCA_VALID | SCA_VALID_COL | SCA_VALID_ROW | SCA_VALID_TAB |
1013 SCA_VALID_COL2 | SCA_VALID_ROW2 | SCA_VALID_TAB2);
1014 return nFlags;
1017 nFlags |= (nFlags2 << 4);
1018 return nFlags;
1022 @param pRange pointer to range where rAddr effectively is *pRange->aEnd,
1023 used in conjunction with pExtInfo to determine the tab span
1024 of a 3D reference.
1026 static sal_uInt16
1027 lcl_ScAddress_Parse_OOo( const sal_Unicode* p, ScDocument* pDoc, ScAddress& rAddr,
1028 ScAddress::ExternalInfo* pExtInfo = NULL, ScRange* pRange = NULL )
1030 sal_uInt16 nRes = 0;
1031 OUString aDocName; // der pure Dokumentenname
1032 OUString aTab;
1033 bool bExtDoc = false;
1034 bool bExtDocInherited = false;
1035 const ScAddress aCurPos(rAddr);
1037 // Lets see if this is a reference to something in an external file. A
1038 // document name is always quoted and has a trailing #.
1039 if (*p == '\'')
1041 const sal_Unicode* pStart = p;
1042 OUString aTmp;
1043 p = parseQuotedName(p, aTmp);
1044 aDocName = aTmp;
1045 if (*p++ == SC_COMPILER_FILE_TAB_SEP)
1046 bExtDoc = true;
1047 else
1048 // This is not a document name. Perhaps a quoted relative table
1049 // name.
1050 p = pStart;
1052 else if (pExtInfo && pExtInfo->mbExternal)
1054 // This is an external reference.
1055 bExtDoc = bExtDocInherited = true;
1058 SCCOL nCol = 0;
1059 SCROW nRow = 0;
1060 SCTAB nTab = 0;
1061 sal_uInt16 nBits = SCA_VALID_TAB;
1062 const sal_Unicode* q;
1063 if ( ScGlobal::FindUnquoted( p, '.') )
1065 nRes |= SCA_TAB_3D;
1066 if ( bExtDoc )
1067 nRes |= SCA_TAB_ABSOLUTE;
1068 if (*p == '$')
1069 nRes |= SCA_TAB_ABSOLUTE, p++;
1071 if (*p == '\'')
1073 // Tokens that start at ' can have anything in them until a final
1074 // ' but '' marks an escaped '. We've earlier guaranteed that a
1075 // string containing '' will be surrounded by '.
1076 p = parseQuotedName(p, aTab);
1078 else
1080 while (*p)
1082 if( *p == '.')
1083 break;
1085 if( *p == '\'' )
1087 p++; break;
1089 aTab += OUString(*p);
1090 p++;
1093 if( *p++ != '.' )
1094 nBits = 0;
1096 if (!bExtDoc && (!pDoc || !pDoc->GetTable( aTab, nTab )))
1098 // Specified table name is not found in this document. Assume this is an external document.
1099 aDocName = aTab;
1100 sal_Int32 n = aDocName.lastIndexOf('.');
1101 if (n != -1 && n > 0)
1103 // Extension found. Strip it.
1104 aTab = aTab.replaceAt(n, 1, "");
1105 bExtDoc = true;
1107 else
1108 // No extension found. This is probably not an external document.
1109 nBits = 0;
1112 else
1114 if (bExtDoc && !bExtDocInherited)
1115 return nRes; // After a document a sheet must follow.
1116 nTab = rAddr.Tab();
1118 nRes |= nBits;
1120 q = p;
1121 if (*p)
1123 nBits = SCA_VALID_COL;
1124 if (*p == '$')
1125 nBits |= SCA_COL_ABSOLUTE, p++;
1127 if (rtl::isAsciiAlpha( *p ))
1129 nCol = sal::static_int_cast<SCCOL>( toupper( char(*p++) ) - 'A' );
1130 while (nCol < MAXCOL && rtl::isAsciiAlpha(*p))
1131 nCol = sal::static_int_cast<SCCOL>( ((nCol + 1) * 26) + toupper( char(*p++) ) - 'A' );
1133 else
1134 nBits = 0;
1136 if( nCol > MAXCOL || rtl::isAsciiAlpha( *p ) )
1137 nBits = 0;
1138 nRes |= nBits;
1139 if( !nBits )
1140 p = q;
1143 q = p;
1144 if (*p)
1146 nBits = SCA_VALID_ROW;
1147 if (*p == '$')
1148 nBits |= SCA_ROW_ABSOLUTE, p++;
1149 if( !rtl::isAsciiDigit( *p ) )
1151 nBits = 0;
1152 nRow = SCROW(-1);
1154 else
1156 OUString aTmp( p );
1157 long n = aTmp.toInt32() - 1;
1158 while (rtl::isAsciiDigit( *p ))
1159 p++;
1160 if( n < 0 || n > MAXROW )
1161 nBits = 0;
1162 nRow = static_cast<SCROW>(n);
1164 nRes |= nBits;
1165 if( !nBits )
1166 p = q;
1169 rAddr.Set( nCol, nRow, nTab );
1171 if (!*p && bExtDoc)
1173 if (!pDoc)
1174 nRes = 0;
1175 else
1177 ScExternalRefManager* pRefMgr = pDoc->GetExternalRefManager();
1179 // Need document name if inherited.
1180 if (bExtDocInherited)
1182 const OUString* pFileName = pRefMgr->getExternalFileName( pExtInfo->mnFileId);
1183 if (pFileName)
1184 aDocName = *pFileName;
1185 else
1186 nRes = 0;
1188 pRefMgr->convertToAbsName(aDocName);
1190 if ((!pExtInfo || !pExtInfo->mbExternal) && pRefMgr->isOwnDocument(aDocName))
1192 if (!pDoc->GetTable( aTab, nTab ))
1193 nRes = 0;
1194 else
1196 rAddr.SetTab( nTab);
1197 nRes |= SCA_VALID_TAB;
1200 else
1202 if (!pExtInfo)
1203 nRes = 0;
1204 else
1206 if (!pExtInfo->mbExternal)
1208 sal_uInt16 nFileId = pRefMgr->getExternalFileId(aDocName);
1210 pExtInfo->mbExternal = true;
1211 pExtInfo->maTabName = aTab;
1212 pExtInfo->mnFileId = nFileId;
1214 if (pRefMgr->getSingleRefToken(nFileId, aTab,
1215 ScAddress(nCol, nRow, 0), NULL,
1216 &nTab).get())
1218 rAddr.SetTab( nTab);
1219 nRes |= SCA_VALID_TAB;
1221 else
1222 nRes = 0;
1224 else
1226 // This is a call for the second part of the reference,
1227 // we must have the range to adapt tab span.
1228 if (!pRange)
1229 nRes = 0;
1230 else
1232 sal_uInt16 nFlags = nRes | SCA_VALID_TAB2;
1233 if (!lcl_ScRange_External_TabSpan( *pRange, nFlags,
1234 pExtInfo, aDocName,
1235 pExtInfo->maTabName, aTab, pDoc))
1236 nRes &= ~SCA_VALID_TAB;
1237 else
1239 if (nFlags & SCA_VALID_TAB2)
1241 rAddr.SetTab( pRange->aEnd.Tab());
1242 nRes |= SCA_VALID_TAB;
1244 else
1245 nRes &= ~SCA_VALID_TAB;
1254 if ( !(nRes & SCA_VALID_ROW) && (nRes & SCA_VALID_COL)
1255 && !( (nRes & SCA_TAB_3D) && (nRes & SCA_VALID_TAB)) )
1256 { // no Row, no Tab, but Col => DM (...), B (...) et al
1257 nRes = 0;
1259 if( !*p )
1261 sal_uInt16 nMask = nRes & ( SCA_VALID_ROW | SCA_VALID_COL | SCA_VALID_TAB );
1262 if( nMask == ( SCA_VALID_ROW | SCA_VALID_COL | SCA_VALID_TAB ) )
1263 nRes |= SCA_VALID;
1265 else
1266 nRes = 0;
1267 return nRes;
1270 static sal_uInt16
1271 lcl_ScAddress_Parse ( const sal_Unicode* p, ScDocument* pDoc, ScAddress& rAddr,
1272 const ScAddress::Details& rDetails,
1273 ScAddress::ExternalInfo* pExtInfo = NULL,
1274 const uno::Sequence< const sheet::ExternalLinkInfo > * pExternalLinks = NULL )
1276 if( !*p )
1277 return 0;
1279 switch (rDetails.eConv)
1281 default :
1282 case formula::FormulaGrammar::CONV_OOO:
1284 return lcl_ScAddress_Parse_OOo( p, pDoc, rAddr, pExtInfo, NULL );
1287 case formula::FormulaGrammar::CONV_XL_A1:
1288 case formula::FormulaGrammar::CONV_XL_OOX:
1290 ScRange r = rAddr;
1291 sal_uInt16 nFlags = lcl_ScRange_Parse_XL_A1( r, p, pDoc, true, pExtInfo,
1292 (rDetails.eConv == formula::FormulaGrammar::CONV_XL_OOX ? pExternalLinks : NULL) );
1293 rAddr = r.aStart;
1294 return nFlags;
1296 case formula::FormulaGrammar::CONV_XL_R1C1:
1298 ScRange r = rAddr;
1299 sal_uInt16 nFlags = lcl_ScRange_Parse_XL_R1C1( r, p, pDoc, rDetails, true, pExtInfo );
1300 rAddr = r.aStart;
1301 return nFlags;
1306 bool ConvertSingleRef( ScDocument* pDoc, const OUString& rRefString,
1307 SCTAB nDefTab, ScRefAddress& rRefAddress,
1308 const ScAddress::Details& rDetails,
1309 ScAddress::ExternalInfo* pExtInfo /* = NULL */ )
1311 bool bRet = false;
1312 if (pExtInfo || (ScGlobal::FindUnquoted( rRefString, SC_COMPILER_FILE_TAB_SEP) == -1))
1314 ScAddress aAddr( 0, 0, nDefTab );
1315 sal_uInt16 nRes = aAddr.Parse( rRefString, pDoc, rDetails, pExtInfo);
1316 if ( nRes & SCA_VALID )
1318 rRefAddress.Set( aAddr,
1319 ((nRes & SCA_COL_ABSOLUTE) == 0),
1320 ((nRes & SCA_ROW_ABSOLUTE) == 0),
1321 ((nRes & SCA_TAB_ABSOLUTE) == 0));
1322 bRet = true;
1325 return bRet;
1328 bool ConvertDoubleRef( ScDocument* pDoc, const OUString& rRefString, SCTAB nDefTab,
1329 ScRefAddress& rStartRefAddress, ScRefAddress& rEndRefAddress,
1330 const ScAddress::Details& rDetails,
1331 ScAddress::ExternalInfo* pExtInfo /* = NULL */ )
1333 bool bRet = false;
1334 if (pExtInfo || (ScGlobal::FindUnquoted( rRefString, SC_COMPILER_FILE_TAB_SEP) == -1))
1336 ScRange aRange( ScAddress( 0, 0, nDefTab));
1337 sal_uInt16 nRes = aRange.Parse( rRefString, pDoc, rDetails, pExtInfo);
1338 if ( nRes & SCA_VALID )
1340 rStartRefAddress.Set( aRange.aStart,
1341 ((nRes & SCA_COL_ABSOLUTE) == 0),
1342 ((nRes & SCA_ROW_ABSOLUTE) == 0),
1343 ((nRes & SCA_TAB_ABSOLUTE) == 0));
1344 rEndRefAddress.Set( aRange.aEnd,
1345 ((nRes & SCA_COL2_ABSOLUTE) == 0),
1346 ((nRes & SCA_ROW2_ABSOLUTE) == 0),
1347 ((nRes & SCA_TAB2_ABSOLUTE) == 0));
1348 bRet = true;
1351 return bRet;
1354 sal_uInt16 ScAddress::Parse( const OUString& r, ScDocument* pDoc,
1355 const Details& rDetails,
1356 ExternalInfo* pExtInfo,
1357 const uno::Sequence< const sheet::ExternalLinkInfo > * pExternalLinks )
1359 return lcl_ScAddress_Parse( r.getStr(), pDoc, *this, rDetails, pExtInfo, pExternalLinks );
1362 bool ScRange::Intersects( const ScRange& r ) const
1364 return !(
1365 std::min( aEnd.Col(), r.aEnd.Col() ) < std::max( aStart.Col(), r.aStart.Col() )
1366 || std::min( aEnd.Row(), r.aEnd.Row() ) < std::max( aStart.Row(), r.aStart.Row() )
1367 || std::min( aEnd.Tab(), r.aEnd.Tab() ) < std::max( aStart.Tab(), r.aStart.Tab() )
1371 void ScRange::PutInOrder()
1373 SCCOL nCol1 = aStart.Col(), nCol2 = aEnd.Col();
1374 SCROW nRow1 = aStart.Row(), nRow2 = aEnd.Row();
1375 SCTAB nTab1 = aStart.Tab(), nTab2 = aEnd.Tab();
1377 ::PutInOrder(nCol1, nCol2);
1378 ::PutInOrder(nRow1, nRow2);
1379 ::PutInOrder(nTab1, nTab2);
1381 aStart.SetCol(nCol1);
1382 aStart.SetRow(nRow1);
1383 aStart.SetTab(nTab1);
1385 aEnd.SetCol(nCol2);
1386 aEnd.SetRow(nRow2);
1387 aEnd.SetTab(nTab2);
1390 void ScRange::Justify()
1392 SCCOL nTempCol;
1393 if ( aEnd.Col() < (nTempCol = aStart.Col()) )
1395 aStart.SetCol(aEnd.Col()); aEnd.SetCol(nTempCol);
1397 SCROW nTempRow;
1398 if ( aEnd.Row() < (nTempRow = aStart.Row()) )
1400 aStart.SetRow(aEnd.Row()); aEnd.SetRow(nTempRow);
1402 SCTAB nTempTab;
1403 if ( aEnd.Tab() < (nTempTab = aStart.Tab()) )
1405 aStart.SetTab(aEnd.Tab()); aEnd.SetTab(nTempTab);
1409 void ScRange::ExtendTo( const ScRange& rRange )
1411 OSL_ENSURE( rRange.IsValid(), "ScRange::ExtendTo - cannot extend to invalid range" );
1412 if( IsValid() )
1414 aStart.SetCol( ::std::min( aStart.Col(), rRange.aStart.Col() ) );
1415 aStart.SetRow( ::std::min( aStart.Row(), rRange.aStart.Row() ) );
1416 aStart.SetTab( ::std::min( aStart.Tab(), rRange.aStart.Tab() ) );
1417 aEnd.SetCol( ::std::max( aEnd.Col(), rRange.aEnd.Col() ) );
1418 aEnd.SetRow( ::std::max( aEnd.Row(), rRange.aEnd.Row() ) );
1419 aEnd.SetTab( ::std::max( aEnd.Tab(), rRange.aEnd.Tab() ) );
1421 else
1422 *this = rRange;
1425 static sal_uInt16
1426 lcl_ScRange_Parse_OOo( ScRange &aRange, const OUString& r, ScDocument* pDoc, ScAddress::ExternalInfo* pExtInfo = NULL )
1428 sal_uInt16 nRes1 = 0, nRes2 = 0;
1429 sal_Int32 nPos = ScGlobal::FindUnquoted( r, ':');
1430 if (nPos != -1)
1432 OUStringBuffer aTmp(r);
1433 aTmp[nPos] = 0;
1434 const sal_Unicode* p = aTmp.getStr();
1435 if( (nRes1 = lcl_ScAddress_Parse_OOo( p, pDoc, aRange.aStart, pExtInfo, NULL ) ) != 0 )
1437 aRange.aEnd = aRange.aStart; // sheet must be initialized identical to first sheet
1438 if ( (nRes2 = lcl_ScAddress_Parse_OOo( p + nPos+ 1, pDoc, aRange.aEnd, pExtInfo, &aRange ) ) != 0 )
1440 // PutInOrder / Justify
1441 sal_uInt16 nMask, nBits1, nBits2;
1442 SCCOL nTempCol;
1443 if ( aRange.aEnd.Col() < (nTempCol = aRange.aStart.Col()) )
1445 aRange.aStart.SetCol(aRange.aEnd.Col()); aRange.aEnd.SetCol(nTempCol);
1446 nMask = (SCA_VALID_COL | SCA_COL_ABSOLUTE);
1447 nBits1 = nRes1 & nMask;
1448 nBits2 = nRes2 & nMask;
1449 nRes1 = (nRes1 & ~nMask) | nBits2;
1450 nRes2 = (nRes2 & ~nMask) | nBits1;
1452 SCROW nTempRow;
1453 if ( aRange.aEnd.Row() < (nTempRow = aRange.aStart.Row()) )
1455 aRange.aStart.SetRow(aRange.aEnd.Row()); aRange.aEnd.SetRow(nTempRow);
1456 nMask = (SCA_VALID_ROW | SCA_ROW_ABSOLUTE);
1457 nBits1 = nRes1 & nMask;
1458 nBits2 = nRes2 & nMask;
1459 nRes1 = (nRes1 & ~nMask) | nBits2;
1460 nRes2 = (nRes2 & ~nMask) | nBits1;
1462 SCTAB nTempTab;
1463 if ( aRange.aEnd.Tab() < (nTempTab = aRange.aStart.Tab()) )
1465 aRange.aStart.SetTab(aRange.aEnd.Tab()); aRange.aEnd.SetTab(nTempTab);
1466 nMask = (SCA_VALID_TAB | SCA_TAB_ABSOLUTE | SCA_TAB_3D);
1467 nBits1 = nRes1 & nMask;
1468 nBits2 = nRes2 & nMask;
1469 nRes1 = (nRes1 & ~nMask) | nBits2;
1470 nRes2 = (nRes2 & ~nMask) | nBits1;
1472 if ( ((nRes1 & ( SCA_TAB_ABSOLUTE | SCA_TAB_3D ))
1473 == ( SCA_TAB_ABSOLUTE | SCA_TAB_3D ))
1474 && !(nRes2 & SCA_TAB_3D) )
1475 nRes2 |= SCA_TAB_ABSOLUTE;
1477 else
1478 nRes1 = 0; // keine Tokens aus halben Sachen
1481 nRes1 = ( ( nRes1 | nRes2 ) & SCA_VALID )
1482 | nRes1
1483 | ( ( nRes2 & SCA_BITS ) << 4 );
1484 return nRes1;
1487 sal_uInt16 ScRange::Parse( const OUString& r, ScDocument* pDoc,
1488 const ScAddress::Details& rDetails,
1489 ScAddress::ExternalInfo* pExtInfo,
1490 const uno::Sequence< const sheet::ExternalLinkInfo > * pExternalLinks )
1492 if (r.isEmpty())
1493 return 0;
1495 switch (rDetails.eConv)
1497 default :
1498 case formula::FormulaGrammar::CONV_OOO:
1499 return lcl_ScRange_Parse_OOo( *this, r, pDoc, pExtInfo );
1501 case formula::FormulaGrammar::CONV_XL_A1:
1502 case formula::FormulaGrammar::CONV_XL_OOX:
1503 return lcl_ScRange_Parse_XL_A1( *this, r.getStr(), pDoc, false, pExtInfo,
1504 (rDetails.eConv == formula::FormulaGrammar::CONV_XL_OOX ? pExternalLinks : NULL) );
1506 case formula::FormulaGrammar::CONV_XL_R1C1:
1507 return lcl_ScRange_Parse_XL_R1C1( *this, r.getStr(), pDoc, rDetails, false, pExtInfo );
1511 // Accept a full range, or an address
1512 sal_uInt16 ScRange::ParseAny( const OUString& r, ScDocument* pDoc,
1513 const ScAddress::Details& rDetails )
1515 sal_uInt16 nRet = Parse( r, pDoc, rDetails );
1516 const sal_uInt16 nValid = SCA_VALID | SCA_VALID_COL2 | SCA_VALID_ROW2 |
1517 SCA_VALID_TAB2;
1519 if ( (nRet & nValid) != nValid )
1521 ScAddress aAdr(aStart);//initialize with currentPos as fallback for table number
1522 nRet = aAdr.Parse( r, pDoc, rDetails );
1523 if ( nRet & SCA_VALID )
1524 aStart = aEnd = aAdr;
1526 return nRet;
1529 // Parse only full row references
1530 sal_uInt16 ScRange::ParseCols( const OUString& rStr, ScDocument* pDoc,
1531 const ScAddress::Details& rDetails )
1533 if (rStr.isEmpty())
1534 return 0;
1536 const sal_Unicode* p = rStr.getStr();
1537 sal_uInt16 nRes = 0, ignored = 0;
1539 (void)pDoc; // make compiler shutup we may need this later
1541 switch (rDetails.eConv)
1543 default :
1544 case formula::FormulaGrammar::CONV_OOO: // No full col refs in OOO yet, assume XL notation
1545 case formula::FormulaGrammar::CONV_XL_A1:
1546 case formula::FormulaGrammar::CONV_XL_OOX:
1547 if (NULL != (p = lcl_a1_get_col( p, &aStart, &ignored ) ) )
1549 if( p[0] == ':')
1551 if( NULL != (p = lcl_a1_get_col( p+1, &aEnd, &ignored )))
1553 nRes = SCA_VALID_COL;
1556 else
1558 aEnd = aStart;
1559 nRes = SCA_VALID_COL;
1562 break;
1564 case formula::FormulaGrammar::CONV_XL_R1C1:
1565 if ((p[0] == 'C' || p[0] != 'c') &&
1566 NULL != (p = lcl_r1c1_get_col( p, rDetails, &aStart, &ignored )))
1568 if( p[0] == ':')
1570 if( (p[1] == 'C' || p[1] == 'c') &&
1571 NULL != (p = lcl_r1c1_get_col( p+1, rDetails, &aEnd, &ignored )))
1573 nRes = SCA_VALID_COL;
1576 else
1578 aEnd = aStart;
1579 nRes = SCA_VALID_COL;
1582 break;
1585 return (p != NULL && *p == '\0') ? nRes : 0;
1588 // Parse only full row references
1589 sal_uInt16 ScRange::ParseRows( const OUString& rStr, ScDocument* pDoc,
1590 const ScAddress::Details& rDetails )
1592 if (rStr.isEmpty())
1593 return 0;
1595 const sal_Unicode* p = rStr.getStr();
1596 sal_uInt16 nRes = 0, ignored = 0;
1598 (void)pDoc; // make compiler shutup we may need this later
1600 switch (rDetails.eConv)
1602 default :
1603 case formula::FormulaGrammar::CONV_OOO: // No full row refs in OOO yet, assume XL notation
1604 case formula::FormulaGrammar::CONV_XL_A1:
1605 case formula::FormulaGrammar::CONV_XL_OOX:
1606 if (NULL != (p = lcl_a1_get_row( p, &aStart, &ignored ) ) )
1608 if( p[0] == ':')
1610 if( NULL != (p = lcl_a1_get_row( p+1, &aEnd, &ignored )))
1612 nRes = SCA_VALID_COL;
1615 else
1617 aEnd = aStart;
1618 nRes = SCA_VALID_COL;
1621 break;
1623 case formula::FormulaGrammar::CONV_XL_R1C1:
1624 if ((p[0] == 'R' || p[0] != 'r') &&
1625 NULL != (p = lcl_r1c1_get_row( p, rDetails, &aStart, &ignored )))
1627 if( p[0] == ':')
1629 if( (p[1] == 'R' || p[1] == 'r') &&
1630 NULL != (p = lcl_r1c1_get_row( p+1, rDetails, &aEnd, &ignored )))
1632 nRes = SCA_VALID_COL;
1635 else
1637 aEnd = aStart;
1638 nRes = SCA_VALID_COL;
1641 break;
1644 return (p != NULL && *p == '\0') ? nRes : 0;
1647 static inline void
1648 lcl_a1_append_c ( OUString &r, int nCol, bool bIsAbs )
1650 if( bIsAbs )
1651 r += "$";
1652 ScColToAlpha( r, sal::static_int_cast<SCCOL>(nCol) );
1655 static inline void
1656 lcl_a1_append_r ( OUString &r, int nRow, bool bIsAbs )
1658 if ( bIsAbs )
1659 r += "$";
1660 r += OUString::number( nRow+1 );
1663 static inline void
1664 lcl_r1c1_append_c ( OUString &r, int nCol, bool bIsAbs,
1665 const ScAddress::Details& rDetails )
1667 r += "C";
1668 if (bIsAbs)
1670 r += OUString::number( nCol + 1 );
1672 else
1674 nCol -= rDetails.nCol;
1675 if (nCol != 0) {
1676 r += "[" + OUString::number( nCol ) + "]";
1680 static inline void
1681 lcl_r1c1_append_r ( OUString &r, int nRow, bool bIsAbs,
1682 const ScAddress::Details& rDetails )
1684 r += "R";
1685 if (bIsAbs)
1687 r += OUString::number( nRow + 1 );
1689 else
1691 nRow -= rDetails.nRow;
1692 if (nRow != 0) {
1693 r += "[" + OUString::number( nRow ) + "]";
1698 static OUString
1699 getFileNameFromDoc( const ScDocument* pDoc )
1701 // TODO : er points at ScGlobal::GetAbsDocName()
1702 // as a better template. Look into it
1703 OUString sFileName;
1704 SfxObjectShell* pShell;
1706 if( NULL != pDoc &&
1707 NULL != (pShell = pDoc->GetDocumentShell() ) )
1709 uno::Reference< frame::XModel > xModel( pShell->GetModel(), uno::UNO_QUERY );
1710 if( xModel.is() )
1712 if( !xModel->getURL().isEmpty() )
1714 INetURLObject aURL( xModel->getURL() );
1715 sFileName = aURL.GetLastName();
1717 else
1718 sFileName = pShell->GetTitle();
1721 return sFileName;
1724 OUString ScAddress::Format(sal_uInt16 nFlags, const ScDocument* pDoc,
1725 const Details& rDetails) const
1727 OUString r;
1728 if( nFlags & SCA_VALID )
1729 nFlags |= ( SCA_VALID_ROW | SCA_VALID_COL | SCA_VALID_TAB );
1730 if( pDoc && (nFlags & SCA_VALID_TAB ) )
1732 if ( nTab >= pDoc->GetTableCount() )
1734 return ScGlobal::GetRscString( STR_NOREF_STR );
1736 if( nFlags & SCA_TAB_3D )
1738 OUString aTabName, aDocName;
1739 OUString aTmp;
1740 pDoc->GetName(nTab, aTmp);
1741 aTabName = aTmp; // TODO: remove use of String here.
1742 // External Reference, same as in ScCompiler::MakeTabStr()
1743 if( aTabName[0] == '\'' )
1744 { // "'Doc'#Tab"
1745 sal_Int32 nPos = ScCompiler::GetDocTabPos( aTabName);
1746 if (nPos != -1)
1748 aDocName = aTabName.copy( 0, nPos + 1 );
1749 aTabName = aTabName.copy( nPos + 1 );
1752 else if( nFlags & SCA_FORCE_DOC )
1754 // VBA has an 'external' flag that forces the addition of the
1755 // tab name _and_ the doc name. The VBA code would be
1756 // needlessly complicated if it constructed an actual external
1757 // reference so we add this somewhat cheesy kludge to force the
1758 // addition of the document name even for non-external references
1759 aDocName = getFileNameFromDoc( pDoc );
1761 ScCompiler::CheckTabQuotes( aTabName, rDetails.eConv);
1763 switch( rDetails.eConv )
1765 default :
1766 case formula::FormulaGrammar::CONV_OOO:
1767 r += aDocName;
1768 if( nFlags & SCA_TAB_ABSOLUTE )
1769 r += "$";
1770 r += aTabName;
1771 r += ".";
1772 break;
1774 case formula::FormulaGrammar::CONV_XL_A1:
1775 case formula::FormulaGrammar::CONV_XL_R1C1:
1776 case formula::FormulaGrammar::CONV_XL_OOX:
1777 if (!aDocName.isEmpty())
1779 r += "[" + aDocName + "]";
1781 r += aTabName;
1782 r += "!";
1783 break;
1787 switch( rDetails.eConv )
1789 default :
1790 case formula::FormulaGrammar::CONV_OOO:
1791 case formula::FormulaGrammar::CONV_XL_A1:
1792 case formula::FormulaGrammar::CONV_XL_OOX:
1793 if( nFlags & SCA_VALID_COL )
1794 lcl_a1_append_c ( r, nCol, nFlags & SCA_COL_ABSOLUTE );
1795 if( nFlags & SCA_VALID_ROW )
1796 lcl_a1_append_r ( r, nRow, nFlags & SCA_ROW_ABSOLUTE );
1797 break;
1799 case formula::FormulaGrammar::CONV_XL_R1C1:
1800 if( nFlags & SCA_VALID_ROW )
1801 lcl_r1c1_append_r ( r, nRow, nFlags & SCA_ROW_ABSOLUTE, rDetails );
1802 if( nFlags & SCA_VALID_COL )
1803 lcl_r1c1_append_c ( r, nCol, nFlags & SCA_COL_ABSOLUTE, rDetails );
1804 break;
1806 return r;
1809 static void
1810 lcl_Split_DocTab( const ScDocument* pDoc, SCTAB nTab,
1811 const ScAddress::Details& rDetails,
1812 sal_uInt16 nFlags,
1813 OUString& rTabName, OUString& rDocName )
1815 pDoc->GetName(nTab, rTabName);
1816 rDocName = "";
1817 // External reference, same as in ScCompiler::MakeTabStr()
1818 if ( rTabName[0] == '\'' )
1819 { // "'Doc'#Tab"
1820 sal_Int32 nPos = ScCompiler::GetDocTabPos( rTabName);
1821 if (nPos != -1)
1823 rDocName = rTabName.copy( 0, nPos + 1 );
1824 rTabName = rTabName.copy( nPos + 1 );
1827 else if( nFlags & SCA_FORCE_DOC )
1829 // VBA has an 'external' flag that forces the addition of the
1830 // tab name _and_ the doc name. The VBA code would be
1831 // needlessly complicated if it constructed an actual external
1832 // reference so we add this somewhat cheesy kludge to force the
1833 // addition of the document name even for non-external references
1834 rDocName = getFileNameFromDoc( pDoc );
1836 ScCompiler::CheckTabQuotes( rTabName, rDetails.eConv);
1839 static void
1840 lcl_ScRange_Format_XL_Header( OUString& r, const ScRange& rRange,
1841 sal_uInt16 nFlags, const ScDocument* pDoc,
1842 const ScAddress::Details& rDetails )
1844 if( nFlags & SCA_TAB_3D )
1846 OUString aTabName, aDocName;
1847 lcl_Split_DocTab( pDoc, rRange.aStart.Tab(), rDetails, nFlags,
1848 aTabName, aDocName );
1849 if( !aDocName.isEmpty() )
1851 r += "[" + aDocName + "]";
1853 r += aTabName;
1855 if( nFlags & SCA_TAB2_3D )
1857 lcl_Split_DocTab( pDoc, rRange.aEnd.Tab(), rDetails, nFlags,
1858 aTabName, aDocName );
1859 r += ":";
1860 r += aTabName;
1862 r += "!";
1866 OUString ScRange::Format( sal_uInt16 nFlags, const ScDocument* pDoc,
1867 const ScAddress::Details& rDetails ) const
1869 if( !( nFlags & SCA_VALID ) )
1871 return ScGlobal::GetRscString( STR_NOREF_STR );
1874 OUString r;
1875 #define absrel_differ(nFlags, mask) (((nFlags) & (mask)) ^ (((nFlags) >> 4) & (mask)))
1876 switch( rDetails.eConv ) {
1877 default :
1878 case formula::FormulaGrammar::CONV_OOO: {
1879 bool bOneTab = (aStart.Tab() == aEnd.Tab());
1880 if ( !bOneTab )
1881 nFlags |= SCA_TAB_3D;
1882 r = aStart.Format(nFlags, pDoc, rDetails);
1883 if( aStart != aEnd ||
1884 absrel_differ( nFlags, SCA_COL_ABSOLUTE ) ||
1885 absrel_differ( nFlags, SCA_ROW_ABSOLUTE ))
1887 nFlags = ( nFlags & SCA_VALID ) | ( ( nFlags >> 4 ) & 0x070F );
1888 if ( bOneTab )
1889 pDoc = NULL;
1890 else
1891 nFlags |= SCA_TAB_3D;
1892 OUString aName(aEnd.Format(nFlags, pDoc, rDetails));
1893 r += ":";
1894 r += aName;
1897 break;
1899 case formula::FormulaGrammar::CONV_XL_A1:
1900 case formula::FormulaGrammar::CONV_XL_OOX:
1901 lcl_ScRange_Format_XL_Header( r, *this, nFlags, pDoc, rDetails );
1902 if( aStart.Col() == 0 && aEnd.Col() >= MAXCOL )
1904 // Full col refs always require 2 rows (2:2)
1905 lcl_a1_append_r( r, aStart.Row(), nFlags & SCA_ROW_ABSOLUTE );
1906 r += ":";
1907 lcl_a1_append_r( r, aEnd.Row(), nFlags & SCA_ROW2_ABSOLUTE );
1909 else if( aStart.Row() == 0 && aEnd.Row() >= MAXROW )
1911 // Full row refs always require 2 cols (A:A)
1912 lcl_a1_append_c( r, aStart.Col(), nFlags & SCA_COL_ABSOLUTE );
1913 r += ":";
1914 lcl_a1_append_c( r, aEnd.Col(), nFlags & SCA_COL2_ABSOLUTE );
1916 else
1918 lcl_a1_append_c ( r, aStart.Col(), nFlags & SCA_COL_ABSOLUTE );
1919 lcl_a1_append_r ( r, aStart.Row(), nFlags & SCA_ROW_ABSOLUTE );
1920 if( aStart.Col() != aEnd.Col() ||
1921 absrel_differ( nFlags, SCA_COL_ABSOLUTE ) ||
1922 aStart.Row() != aEnd.Row() ||
1923 absrel_differ( nFlags, SCA_ROW_ABSOLUTE )) {
1924 r += ":";
1925 lcl_a1_append_c ( r, aEnd.Col(), nFlags & SCA_COL2_ABSOLUTE );
1926 lcl_a1_append_r ( r, aEnd.Row(), nFlags & SCA_ROW2_ABSOLUTE );
1929 break;
1931 case formula::FormulaGrammar::CONV_XL_R1C1:
1932 lcl_ScRange_Format_XL_Header( r, *this, nFlags, pDoc, rDetails );
1933 if( aStart.Col() == 0 && aEnd.Col() >= MAXCOL )
1935 lcl_r1c1_append_r( r, aStart.Row(), nFlags & SCA_ROW_ABSOLUTE, rDetails );
1936 if( aStart.Row() != aEnd.Row() ||
1937 absrel_differ( nFlags, SCA_ROW_ABSOLUTE )) {
1938 r += ":";
1939 lcl_r1c1_append_r( r, aEnd.Row(), nFlags & SCA_ROW2_ABSOLUTE, rDetails );
1942 else if( aStart.Row() == 0 && aEnd.Row() >= MAXROW )
1944 lcl_r1c1_append_c( r, aStart.Col(), nFlags & SCA_COL_ABSOLUTE, rDetails );
1945 if( aStart.Col() != aEnd.Col() ||
1946 absrel_differ( nFlags, SCA_COL_ABSOLUTE )) {
1947 r += ":";
1948 lcl_r1c1_append_c( r, aEnd.Col(), nFlags & SCA_COL2_ABSOLUTE, rDetails );
1951 else
1953 lcl_r1c1_append_r( r, aStart.Row(), nFlags & SCA_ROW_ABSOLUTE, rDetails );
1954 lcl_r1c1_append_c( r, aStart.Col(), nFlags & SCA_COL_ABSOLUTE, rDetails );
1955 if( aStart.Col() != aEnd.Col() ||
1956 absrel_differ( nFlags, SCA_COL_ABSOLUTE ) ||
1957 aStart.Row() != aEnd.Row() ||
1958 absrel_differ( nFlags, SCA_ROW_ABSOLUTE )) {
1959 r += ":";
1960 lcl_r1c1_append_r( r, aEnd.Row(), nFlags & SCA_ROW2_ABSOLUTE, rDetails );
1961 lcl_r1c1_append_c( r, aEnd.Col(), nFlags & SCA_COL2_ABSOLUTE, rDetails );
1965 #undef absrel_differ
1966 return r;
1969 bool ScAddress::Move( SCsCOL dx, SCsROW dy, SCsTAB dz, ScDocument* pDoc )
1971 SCsTAB nMaxTab = pDoc ? pDoc->GetTableCount() : MAXTAB;
1972 dx = Col() + dx;
1973 dy = Row() + dy;
1974 dz = Tab() + dz;
1975 bool bValid = true;
1976 if( dx < 0 )
1977 dx = 0, bValid = false;
1978 else if( dx > MAXCOL )
1979 dx = MAXCOL, bValid =false;
1980 if( dy < 0 )
1981 dy = 0, bValid = false;
1982 else if( dy > MAXROW )
1983 dy = MAXROW, bValid =false;
1984 if( dz < 0 )
1985 dz = 0, bValid = false;
1986 else if( dz > nMaxTab )
1987 dz = nMaxTab, bValid =false;
1988 Set( dx, dy, dz );
1989 return bValid;
1992 bool ScRange::Move( SCsCOL dx, SCsROW dy, SCsTAB dz, ScDocument* pDoc )
1994 // single & to process both
1995 return aStart.Move( dx, dy, dz, pDoc ) & aEnd.Move( dx, dy, dz, pDoc );
1998 OUString ScAddress::GetColRowString( bool bAbsolute,
1999 const Details& rDetails ) const
2001 OUString aString;
2003 switch( rDetails.eConv )
2005 default :
2006 case formula::FormulaGrammar::CONV_OOO:
2007 case formula::FormulaGrammar::CONV_XL_A1:
2008 case formula::FormulaGrammar::CONV_XL_OOX:
2009 if (bAbsolute)
2010 aString += "$";
2012 ScColToAlpha( aString, nCol);
2014 if ( bAbsolute )
2015 aString += "$";
2017 aString += OUString::number(nRow+1);
2018 break;
2020 case formula::FormulaGrammar::CONV_XL_R1C1:
2021 lcl_r1c1_append_r ( aString, nRow, bAbsolute, rDetails );
2022 lcl_r1c1_append_c ( aString, nCol, bAbsolute, rDetails );
2023 break;
2026 return aString;
2029 OUString ScRefAddress::GetRefString( ScDocument* pDoc, SCTAB nActTab,
2030 const ScAddress::Details& rDetails ) const
2032 if ( !pDoc )
2033 return EMPTY_OUSTRING;
2034 if ( Tab()+1 > pDoc->GetTableCount() )
2035 return ScGlobal::GetRscString( STR_NOREF_STR );
2037 sal_uInt16 nFlags = SCA_VALID;
2038 if ( nActTab != Tab() )
2040 nFlags |= SCA_TAB_3D;
2041 if ( !bRelTab )
2042 nFlags |= SCA_TAB_ABSOLUTE;
2044 if ( !bRelCol )
2045 nFlags |= SCA_COL_ABSOLUTE;
2046 if ( !bRelRow )
2047 nFlags |= SCA_ROW_ABSOLUTE;
2049 return aAdr.Format(nFlags, pDoc, rDetails);
2052 void ScColToAlpha( OUStringBuffer& rBuf, SCCOL nCol )
2054 if (nCol < 26*26)
2056 if (nCol < 26)
2057 rBuf.append( static_cast<sal_Unicode>( 'A' +
2058 static_cast<sal_uInt16>(nCol)));
2059 else
2061 rBuf.append( static_cast<sal_Unicode>( 'A' +
2062 (static_cast<sal_uInt16>(nCol) / 26) - 1));
2063 rBuf.append( static_cast<sal_Unicode>( 'A' +
2064 (static_cast<sal_uInt16>(nCol) % 26)));
2067 else
2069 OUString aStr;
2070 while (nCol >= 26)
2072 SCCOL nC = nCol % 26;
2073 aStr += OUString( static_cast<sal_Unicode>( 'A' +
2074 static_cast<sal_uInt16>(nC)));
2075 nCol = sal::static_int_cast<SCCOL>( nCol - nC );
2076 nCol = nCol / 26 - 1;
2078 aStr += OUString(static_cast<sal_Unicode>( 'A' +
2079 static_cast<sal_uInt16>(nCol)));
2080 rBuf.append(comphelper::string::reverseString(aStr));
2084 bool AlphaToCol( SCCOL& rCol, const OUString& rStr)
2086 SCCOL nResult = 0;
2087 sal_Int32 nStop = rStr.getLength();
2088 sal_Int32 nPos = 0;
2089 sal_Unicode c;
2090 while (nResult <= MAXCOL && nPos < nStop && (c = rStr[nPos]) != 0 &&
2091 rtl::isAsciiAlpha(c))
2093 if (nPos > 0)
2094 nResult = (nResult + 1) * 26;
2095 nResult += ScGlobal::ToUpperAlpha(c) - 'A';
2096 ++nPos;
2098 bool bOk = (ValidCol(nResult) && nPos > 0);
2099 if (bOk)
2100 rCol = nResult;
2101 return bOk;
2104 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */