fix baseline build (old cairo) - 'cairo_rectangle_int_t' does not name a type
[LibreOffice.git] / sc / source / filter / oox / defnamesbuffer.cxx
blobfa2a0cf6b685efbe9c867fd9949024972f1395ca
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 "defnamesbuffer.hxx"
22 #include <com/sun/star/sheet/ComplexReference.hpp>
23 #include <com/sun/star/sheet/ExternalReference.hpp>
24 #include <com/sun/star/sheet/NamedRangeFlag.hpp>
25 #include <com/sun/star/sheet/ReferenceFlags.hpp>
26 #include <com/sun/star/sheet/SingleReference.hpp>
27 #include <com/sun/star/sheet/XFormulaTokens.hpp>
28 #include <com/sun/star/sheet/XPrintAreas.hpp>
29 #include <osl/diagnose.h>
30 #include <rtl/ustrbuf.hxx>
31 #include <oox/helper/attributelist.hxx>
32 #include <oox/helper/propertyset.hxx>
33 #include "addressconverter.hxx"
34 #include "biffinputstream.hxx"
35 #include "externallinkbuffer.hxx"
36 #include "formulaparser.hxx"
37 #include "worksheetbuffer.hxx"
38 #include "tokenarray.hxx"
39 #include "tokenuno.hxx"
40 #include "compiler.hxx"
42 namespace oox {
43 namespace xls {
45 using namespace ::com::sun::star::sheet;
46 using namespace ::com::sun::star::table;
47 using namespace ::com::sun::star::uno;
49 namespace {
51 const sal_uInt32 BIFF12_DEFNAME_HIDDEN = 0x00000001;
52 const sal_uInt32 BIFF12_DEFNAME_FUNC = 0x00000002;
53 const sal_uInt32 BIFF12_DEFNAME_VBNAME = 0x00000004;
54 const sal_uInt32 BIFF12_DEFNAME_MACRO = 0x00000008;
55 const sal_uInt32 BIFF12_DEFNAME_BUILTIN = 0x00000020;
57 const sal_uInt16 BIFF_REFFLAG_COL1REL = 0x0001;
58 const sal_uInt16 BIFF_REFFLAG_ROW1REL = 0x0002;
59 const sal_uInt16 BIFF_REFFLAG_COL2REL = 0x0004;
60 const sal_uInt16 BIFF_REFFLAG_ROW2REL = 0x0008;
62 const sal_Char* const spcOoxPrefix = "_xlnm.";
64 const sal_Char* const sppcBaseNames[] =
66 "Consolidate_Area",
67 "Auto_Open",
68 "Auto_Close",
69 "Extract",
70 "Database",
71 "Criteria",
72 "Print_Area",
73 "Print_Titles",
74 "Recorder",
75 "Data_Form",
76 "Auto_Activate",
77 "Auto_Deactivate",
78 "Sheet_Title",
79 "_FilterDatabase"
82 OUString lclGetBaseName( sal_Unicode cBuiltinId )
84 OSL_ENSURE( cBuiltinId < SAL_N_ELEMENTS( sppcBaseNames ), "lclGetBaseName - unsupported built-in identifier" );
85 OUStringBuffer aBuffer;
86 if( cBuiltinId < SAL_N_ELEMENTS( sppcBaseNames ) )
87 aBuffer.appendAscii( sppcBaseNames[ cBuiltinId ] );
88 else
89 aBuffer.append( static_cast< sal_Int32 >( cBuiltinId ) );
90 return aBuffer.makeStringAndClear();
93 OUString lclGetPrefixedName( sal_Unicode cBuiltinId )
95 return OUStringBuffer().appendAscii( spcOoxPrefix ).append( lclGetBaseName( cBuiltinId ) ).makeStringAndClear();
98 /** returns the built-in name identifier from a perfixed built-in name, e.g. '_xlnm.Print_Area'. */
99 sal_Unicode lclGetBuiltinIdFromPrefixedName( const OUString& rModelName )
101 OUString aPrefix = OUString::createFromAscii( spcOoxPrefix );
102 sal_Int32 nPrefixLen = aPrefix.getLength();
103 if( rModelName.matchIgnoreAsciiCase( aPrefix ) )
105 for( sal_Unicode cBuiltinId = 0; cBuiltinId < SAL_N_ELEMENTS( sppcBaseNames ); ++cBuiltinId )
107 OUString aBaseName = lclGetBaseName( cBuiltinId );
108 sal_Int32 nBaseNameLen = aBaseName.getLength();
109 if( (rModelName.getLength() == nPrefixLen + nBaseNameLen) && rModelName.matchIgnoreAsciiCase( aBaseName, nPrefixLen ) )
110 return cBuiltinId;
113 return BIFF_DEFNAME_UNKNOWN;
116 /** returns the built-in name identifier from a built-in base name, e.g. 'Print_Area'. */
117 sal_Unicode lclGetBuiltinIdFromBaseName( const OUString& rModelName )
119 for( sal_Unicode cBuiltinId = 0; cBuiltinId < SAL_N_ELEMENTS( sppcBaseNames ); ++cBuiltinId )
120 if( rModelName.equalsIgnoreAsciiCaseAscii( sppcBaseNames[ cBuiltinId ] ) )
121 return cBuiltinId;
122 return BIFF_DEFNAME_UNKNOWN;
125 OUString lclGetUpcaseModelName( const OUString& rModelName )
127 // TODO: i18n?
128 return rModelName.toAsciiUpperCase();
131 void lclConvertRefFlags( sal_Int32& ornFlags, sal_Int32& ornAbsPos, sal_Int32& ornRelPos, sal_Int32 nBasePos, sal_Int32 nApiRelFlag, bool bRel )
133 if( getFlag( ornFlags, nApiRelFlag ) && !bRel )
135 // convert relative to absolute
136 setFlag( ornFlags, nApiRelFlag, false );
137 ornAbsPos = nBasePos + ornRelPos;
139 else if( !getFlag( ornFlags, nApiRelFlag ) && bRel )
141 // convert absolute to relative
142 setFlag( ornFlags, nApiRelFlag, true );
143 ornRelPos = ornAbsPos - nBasePos;
147 void lclConvertSingleRefFlags( SingleReference& orApiRef, const CellAddress& rBaseAddr, bool bColRel, bool bRowRel )
149 using namespace ::com::sun::star::sheet::ReferenceFlags;
150 lclConvertRefFlags(
151 orApiRef.Flags, orApiRef.Column, orApiRef.RelativeColumn,
152 rBaseAddr.Column, COLUMN_RELATIVE, bColRel );
153 lclConvertRefFlags(
154 orApiRef.Flags, orApiRef.Row, orApiRef.RelativeRow,
155 rBaseAddr.Row, ROW_RELATIVE, bRowRel );
158 Any lclConvertReference( const Any& rRefAny, const CellAddress& rBaseAddr, sal_uInt16 nRelFlags )
160 if( rRefAny.has< SingleReference >() && !getFlag( nRelFlags, BIFF_REFFLAG_COL2REL ) && !getFlag( nRelFlags, BIFF_REFFLAG_ROW2REL ) )
162 SingleReference aApiRef;
163 rRefAny >>= aApiRef;
164 lclConvertSingleRefFlags( aApiRef, rBaseAddr, getFlag( nRelFlags, BIFF_REFFLAG_COL1REL ), getFlag( nRelFlags, BIFF_REFFLAG_ROW1REL ) );
165 return Any( aApiRef );
167 if( rRefAny.has< ComplexReference >() )
169 ComplexReference aApiRef;
170 rRefAny >>= aApiRef;
171 lclConvertSingleRefFlags( aApiRef.Reference1, rBaseAddr, getFlag( nRelFlags, BIFF_REFFLAG_COL1REL ), getFlag( nRelFlags, BIFF_REFFLAG_ROW1REL ) );
172 lclConvertSingleRefFlags( aApiRef.Reference2, rBaseAddr, getFlag( nRelFlags, BIFF_REFFLAG_COL2REL ), getFlag( nRelFlags, BIFF_REFFLAG_ROW2REL ) );
173 return Any( aApiRef );
175 return Any();
178 } // namespace
180 DefinedNameModel::DefinedNameModel() :
181 mnSheet( -1 ),
182 mnFuncGroupId( -1 ),
183 mbMacro( false ),
184 mbFunction( false ),
185 mbVBName( false ),
186 mbHidden( false )
190 DefinedNameBase::DefinedNameBase( const WorkbookHelper& rHelper ) :
191 WorkbookHelper( rHelper )
195 const OUString& DefinedNameBase::getUpcaseModelName() const
197 if( maUpModelName.isEmpty() )
198 maUpModelName = lclGetUpcaseModelName( maModel.maName );
199 return maUpModelName;
202 Any DefinedNameBase::getReference( const CellAddress& rBaseAddr ) const
204 if( maRefAny.hasValue() && (maModel.maName.getLength() >= 2) && (maModel.maName[ 0 ] == '\x01') )
206 sal_Unicode cFlagsChar = getUpcaseModelName()[ 1 ];
207 if( ('A' <= cFlagsChar) && (cFlagsChar <= 'P') )
209 sal_uInt16 nRelFlags = static_cast< sal_uInt16 >( cFlagsChar - 'A' );
210 if( maRefAny.has< ExternalReference >() )
212 ExternalReference aApiExtRef;
213 maRefAny >>= aApiExtRef;
214 Any aRefAny = lclConvertReference( aApiExtRef.Reference, rBaseAddr, nRelFlags );
215 if( aRefAny.hasValue() )
217 aApiExtRef.Reference <<= aRefAny;
218 return Any( aApiExtRef );
221 else
223 return lclConvertReference( maRefAny, rBaseAddr, nRelFlags );
227 return Any();
230 ApiTokenSequence DefinedNameBase::importOoxFormula( sal_Int16 nBaseSheet )
232 return (!maModel.maFormula.isEmpty()) ?
233 getFormulaParser().importFormula( CellAddress( nBaseSheet, 0, 0 ), maModel.maFormula ) :
234 getFormulaParser().convertErrorToFormula( BIFF_ERR_NAME );
237 ApiTokenSequence DefinedNameBase::importBiff12Formula( sal_Int16 nBaseSheet, SequenceInputStream& rStrm )
239 return getFormulaParser().importFormula( CellAddress( nBaseSheet, 0, 0 ), FORMULATYPE_DEFINEDNAME, rStrm );
242 ApiTokenSequence DefinedNameBase::importBiffFormula( sal_Int16 nBaseSheet, BiffInputStream& rStrm, const sal_uInt16* pnFmlaSize )
244 return (!pnFmlaSize || (*pnFmlaSize > 0)) ?
245 getFormulaParser().importFormula( CellAddress( nBaseSheet, 0, 0 ), FORMULATYPE_DEFINEDNAME, rStrm, pnFmlaSize ) :
246 getFormulaParser().convertErrorToFormula( BIFF_ERR_NAME );
249 DefinedName::DefinedName( const WorkbookHelper& rHelper ) :
250 DefinedNameBase( rHelper ),
251 mpScRangeData(NULL),
252 mnTokenIndex( -1 ),
253 mnCalcSheet( 0 ),
254 mcBuiltinId( BIFF_DEFNAME_UNKNOWN ),
255 mnFmlaSize( 0 )
259 void DefinedName::importDefinedName( const AttributeList& rAttribs )
261 maModel.maName = rAttribs.getXString( XML_name, OUString() );
262 maModel.mnSheet = rAttribs.getInteger( XML_localSheetId, -1 );
263 maModel.mnFuncGroupId = rAttribs.getInteger( XML_functionGroupId, -1 );
264 maModel.mbMacro = rAttribs.getBool( XML_xlm, false );
265 maModel.mbFunction = rAttribs.getBool( XML_function, false );
266 maModel.mbVBName = rAttribs.getBool( XML_vbProcedure, false );
267 maModel.mbHidden = rAttribs.getBool( XML_hidden, false );
268 mnCalcSheet = (maModel.mnSheet >= 0) ? getWorksheets().getCalcSheetIndex( maModel.mnSheet ) : -1;
270 /* Detect built-in state from name itself, there is no built-in flag.
271 Built-in names are prexixed with '_xlnm.' instead. */
272 mcBuiltinId = lclGetBuiltinIdFromPrefixedName( maModel.maName );
275 void DefinedName::setFormula( const OUString& rFormula )
277 maModel.maFormula = rFormula;
280 void DefinedName::importDefinedName( SequenceInputStream& rStrm )
282 sal_uInt32 nFlags;
283 nFlags = rStrm.readuInt32();
284 rStrm.skip( 1 ); // keyboard shortcut
285 maModel.mnSheet = rStrm.readInt32();
286 rStrm >> maModel.maName;
287 mnCalcSheet = (maModel.mnSheet >= 0) ? getWorksheets().getCalcSheetIndex( maModel.mnSheet ) : -1;
289 // macro function/command, hidden flag
290 maModel.mnFuncGroupId = extractValue< sal_Int32 >( nFlags, 6, 9 );
291 maModel.mbMacro = getFlag( nFlags, BIFF12_DEFNAME_MACRO );
292 maModel.mbFunction = getFlag( nFlags, BIFF12_DEFNAME_FUNC );
293 maModel.mbVBName = getFlag( nFlags, BIFF12_DEFNAME_VBNAME );
294 maModel.mbHidden = getFlag( nFlags, BIFF12_DEFNAME_HIDDEN );
296 // get built-in name index from name
297 if( getFlag( nFlags, BIFF12_DEFNAME_BUILTIN ) )
298 mcBuiltinId = lclGetBuiltinIdFromBaseName( maModel.maName );
300 // store token array data
301 sal_Int64 nRecPos = rStrm.tell();
302 sal_Int32 nFmlaSize = rStrm.readInt32();
303 rStrm.skip( nFmlaSize );
304 sal_Int32 nAddDataSize = rStrm.readInt32();
305 if( !rStrm.isEof() && (nFmlaSize > 0) && (nAddDataSize >= 0) && (rStrm.getRemaining() >= nAddDataSize) )
307 sal_Int32 nTotalSize = 8 + nFmlaSize + nAddDataSize;
308 mxFormula.reset( new StreamDataSequence );
309 rStrm.seek( nRecPos );
310 rStrm.readData( *mxFormula, nTotalSize );
314 void DefinedName::createNameObject( sal_Int32 nIndex )
316 // do not create names for (macro) functions or VBA procedures
317 // #163146# do not ignore hidden names (may be regular names created by VBA scripts)
318 if( /*maModel.mbHidden ||*/ maModel.mbFunction || maModel.mbVBName )
319 return;
321 // skip BIFF names without stream position (e.g. BIFF3-BIFF4 internal 3D references)
322 if( (getFilterType() == FILTER_BIFF) && !mxBiffStrm.get() )
323 return;
325 // convert original name to final Calc name (TODO: filter invalid characters from model name)
326 maCalcName = isBuiltinName() ? lclGetPrefixedName( mcBuiltinId ) : maModel.maName;
328 // #163146# do not rename sheet-local names by default, this breaks VBA scripts
330 // special flags for this name
331 sal_Int32 nNameFlags = 0;
332 using namespace ::com::sun::star::sheet::NamedRangeFlag;
333 if( !isGlobalName() ) switch( mcBuiltinId )
335 case BIFF_DEFNAME_CRITERIA: nNameFlags = FILTER_CRITERIA; break;
336 case BIFF_DEFNAME_PRINTAREA: nNameFlags = PRINT_AREA; break;
337 case BIFF_DEFNAME_PRINTTITLES: nNameFlags = COLUMN_HEADER | ROW_HEADER; break;
340 // create the name and insert it into the document, maCalcName will be changed to the resulting name
341 if (maModel.mnSheet >= 0)
342 mpScRangeData = createLocalNamedRangeObject( maCalcName, ApiTokenSequence(), nIndex, nNameFlags, maModel.mnSheet );
343 else
344 mpScRangeData = createNamedRangeObject( maCalcName, ApiTokenSequence(), nIndex, nNameFlags );
345 mnTokenIndex = nIndex;
348 ApiTokenSequence
349 DefinedName::getTokens()
351 // convert and set formula of the defined name
352 ApiTokenSequence aTokens;
353 switch( getFilterType() )
355 case FILTER_OOXML:
357 if( mxFormula.get() )
359 SequenceInputStream aStrm( *mxFormula );
360 aTokens = importBiff12Formula( mnCalcSheet, aStrm );
362 else
363 aTokens = importOoxFormula( mnCalcSheet );
365 break;
366 case FILTER_BIFF:
368 OSL_ENSURE( mxBiffStrm.get(), "DefinedName::convertFormula - missing BIFF stream" );
369 if( mxBiffStrm.get() )
371 BiffInputStream& rStrm = mxBiffStrm->getStream();
372 BiffInputStreamPosGuard aStrmGuard( rStrm );
373 if( mxBiffStrm->restorePosition() )
374 aTokens = importBiffFormula( mnCalcSheet, rStrm, &mnFmlaSize );
377 break;
378 case FILTER_UNKNOWN:
379 break;
381 return aTokens;
384 std::unique_ptr<ScTokenArray> DefinedName::getScTokens()
386 ScTokenArray aTokenArray;
387 ScCompiler aCompiler(&getScDocument(), ScAddress(0, 0, mnCalcSheet));
388 aCompiler.SetGrammar(formula::FormulaGrammar::GRAM_OOXML);
389 std::unique_ptr<ScTokenArray> pArray(aCompiler.CompileString(maModel.maFormula));
390 // Compile the tokens into RPN once to populate information into tokens
391 // where necessary, e.g. for TableRef inner reference. RPN can be discarded
392 // after, a resulting error must be reset.
393 sal_uInt16 nErr = pArray->GetCodeError();
394 aCompiler.CompileTokenArray();
395 pArray->DelRPN();
396 pArray->SetCodeError(nErr);
398 return pArray;
401 void DefinedName::convertFormula()
403 // macro function or vba procedure
404 if(!mpScRangeData)
405 return;
407 // convert and set formula of the defined name
408 if ( getFilterType() == FILTER_OOXML )
410 std::unique_ptr<ScTokenArray> pTokenArray = getScTokens();
411 mpScRangeData->SetCode( *pTokenArray );
414 ScTokenArray* pTokenArray = mpScRangeData->GetCode();
415 Sequence< FormulaToken > aFTokenSeq;
416 (void)ScTokenConversion::ConvertToTokenSequence( this->getScDocument(), aFTokenSeq, *pTokenArray );
417 // set built-in names (print ranges, repeated titles, filter ranges)
418 if( !isGlobalName() ) switch( mcBuiltinId )
420 case BIFF_DEFNAME_PRINTAREA:
422 Reference< XPrintAreas > xPrintAreas( getSheetFromDoc( mnCalcSheet ), UNO_QUERY );
423 ApiCellRangeList aPrintRanges;
424 getFormulaParser().extractCellRangeList( aPrintRanges, aFTokenSeq, false, mnCalcSheet );
425 if( xPrintAreas.is() && !aPrintRanges.empty() )
426 xPrintAreas->setPrintAreas( aPrintRanges.toSequence() );
428 break;
429 case BIFF_DEFNAME_PRINTTITLES:
431 Reference< XPrintAreas > xPrintAreas( getSheetFromDoc( mnCalcSheet ), UNO_QUERY );
432 ApiCellRangeList aTitleRanges;
433 getFormulaParser().extractCellRangeList( aTitleRanges, aFTokenSeq, false, mnCalcSheet );
434 if( xPrintAreas.is() && !aTitleRanges.empty() )
436 bool bHasRowTitles = false;
437 bool bHasColTitles = false;
438 const CellAddress& rMaxPos = getAddressConverter().getMaxAddress();
439 for( ::std::vector< CellRangeAddress >::const_iterator aIt = aTitleRanges.begin(), aEnd = aTitleRanges.end(); (aIt != aEnd) && (!bHasRowTitles || !bHasColTitles); ++aIt )
441 bool bFullRow = (aIt->StartColumn == 0) && (aIt->EndColumn >= rMaxPos.Column);
442 bool bFullCol = (aIt->StartRow == 0) && (aIt->EndRow >= rMaxPos.Row);
443 if( !bHasRowTitles && bFullRow && !bFullCol )
445 xPrintAreas->setTitleRows( *aIt );
446 xPrintAreas->setPrintTitleRows( sal_True );
447 bHasRowTitles = true;
449 else if( !bHasColTitles && bFullCol && !bFullRow )
451 xPrintAreas->setTitleColumns( *aIt );
452 xPrintAreas->setPrintTitleColumns( sal_True );
453 bHasColTitles = true;
458 break;
462 bool DefinedName::getAbsoluteRange( CellRangeAddress& orRange ) const
464 ScTokenArray* pTokenArray = mpScRangeData->GetCode();
465 Sequence< FormulaToken > aFTokenSeq;
466 ScTokenConversion::ConvertToTokenSequence(getScDocument(), aFTokenSeq, *pTokenArray);
467 return getFormulaParser().extractCellRange( orRange, aFTokenSeq, false );
470 DefinedNamesBuffer::DefinedNamesBuffer( const WorkbookHelper& rHelper ) :
471 WorkbookHelper( rHelper )
475 DefinedNameRef DefinedNamesBuffer::importDefinedName( const AttributeList& rAttribs )
477 DefinedNameRef xDefName = createDefinedName();
478 xDefName->importDefinedName( rAttribs );
479 return xDefName;
482 void DefinedNamesBuffer::importDefinedName( SequenceInputStream& rStrm )
484 createDefinedName()->importDefinedName( rStrm );
487 void DefinedNamesBuffer::finalizeImport()
489 // first insert all names without formula definition into the document, and insert them into the maps
490 int index = 0;
491 for( DefNameVector::iterator aIt = maDefNames.begin(), aEnd = maDefNames.end(); aIt != aEnd; ++aIt )
493 DefinedNameRef xDefName = *aIt;
494 xDefName->createNameObject( ++index );
495 // map by sheet index and original model name
496 maModelNameMap[ SheetNameKey( xDefName->getLocalCalcSheet(), xDefName->getUpcaseModelName() ) ] = xDefName;
497 // map by sheet index and built-in identifier
498 if( !xDefName->isGlobalName() && xDefName->isBuiltinName() )
499 maBuiltinMap[ BuiltinKey( xDefName->getLocalCalcSheet(), xDefName->getBuiltinId() ) ] = xDefName;
500 // map by API formula token identifier
501 sal_Int32 nTokenIndex = xDefName->getTokenIndex();
502 if( nTokenIndex >= 0 )
503 maTokenIdMap[ nTokenIndex ] = xDefName;
506 /* Now convert all name formulas, so that the formula parser can find all
507 names in case of circular dependencies. */
508 maDefNames.forEachMem( &DefinedName::convertFormula );
511 DefinedNameRef DefinedNamesBuffer::getByIndex( sal_Int32 nIndex ) const
513 return maDefNames.get( nIndex );
516 DefinedNameRef DefinedNamesBuffer::getByTokenIndex( sal_Int32 nIndex ) const
518 return maTokenIdMap.get( nIndex );
521 DefinedNameRef DefinedNamesBuffer::getByModelName( const OUString& rModelName, sal_Int16 nCalcSheet ) const
523 OUString aUpcaseName = lclGetUpcaseModelName( rModelName );
524 DefinedNameRef xDefName = maModelNameMap.get( SheetNameKey( nCalcSheet, aUpcaseName ) );
525 // lookup global name, if no local name exists
526 if( !xDefName && (nCalcSheet >= 0) )
527 xDefName = maModelNameMap.get( SheetNameKey( -1, aUpcaseName ) );
528 return xDefName;
531 DefinedNameRef DefinedNamesBuffer::getByBuiltinId( sal_Unicode cBuiltinId, sal_Int16 nCalcSheet ) const
533 return maBuiltinMap.get( BuiltinKey( nCalcSheet, cBuiltinId ) );
536 DefinedNameRef DefinedNamesBuffer::createDefinedName()
538 DefinedNameRef xDefName( new DefinedName( *this ) );
539 maDefNames.push_back( xDefName );
540 return xDefName;
543 } // namespace xls
544 } // namespace oox
546 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */