1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
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 <svl/numformat.hxx>
21 #include <svl/zforlist.hxx>
22 #include <svl/zformat.hxx>
23 #include <formula/token.hxx>
24 #include <sal/log.hxx>
25 #include <unotools/configmgr.hxx>
26 #include <osl/diagnose.h>
27 #include <o3tl/string_view.hxx>
29 #include <document.hxx>
32 #include <globstr.hrc>
33 #include <scresid.hxx>
34 #include <subtotal.hxx>
35 #include <docoptio.hxx>
36 #include <markdata.hxx>
37 #include <validat.hxx>
38 #include <scitems.hxx>
39 #include <stlpool.hxx>
40 #include <poolhelp.hxx>
41 #include <detdata.hxx>
42 #include <patattr.hxx>
43 #include <chgtrack.hxx>
44 #include <progress.hxx>
45 #include <paramisc.hxx>
46 #include <compiler.hxx>
47 #include <externalrefmgr.hxx>
49 #include <formulacell.hxx>
50 #include <tokenarray.hxx>
51 #include <tokenstringcontext.hxx>
54 using namespace formula
;
56 /** (Goal Seek) Find a value of x that is a root of f(x)
58 This function is used internally for the goal seek operation. It uses the
59 Regula Falsi (aka false position) algorithm to find a root of f(x). The
60 start value and the target value are to be given by the user in the
61 goal seek dialog. The f(x) in this case is defined as the formula in the
62 formula cell minus target value. This function may also perform additional
63 search in the horizontal directions when the f(x) is discrete in order to
64 ensure a non-zero slope necessary for deriving a subsequent x that is
65 reasonably close to the root of interest.
67 @change 24.10.2004 by Kohei Yoshida (kohei@openoffice.org)
71 @change 6 Aug 2013, fdo37341
73 bool ScDocument::Solver(SCCOL nFCol
, SCROW nFRow
, SCTAB nFTab
,
74 SCCOL nVCol
, SCROW nVRow
, SCTAB nVTab
,
75 const OUString
& sValStr
, double& nX
)
79 if ( ValidColRow( nFCol
, nFRow
) && ValidTab( nFTab
) &&
80 ValidColRow( nVCol
, nVRow
) && ValidTab( nVTab
) &&
81 nFTab
< GetTableCount() && maTabs
[nFTab
] &&
82 nVTab
< GetTableCount() && maTabs
[nVTab
] )
84 CellType eFType
= GetCellType(nFCol
, nFRow
, nFTab
);
85 CellType eVType
= GetCellType(nVCol
, nVRow
, nVTab
);
86 // #i108005# convert target value to number using default format,
87 // as previously done in ScInterpreter::GetDouble
88 ScFormulaCell
* pFormula
= nullptr;
89 double fTargetVal
= 0.0;
90 sal_uInt32 nFIndex
= 0;
91 if ( eFType
== CELLTYPE_FORMULA
&& eVType
== CELLTYPE_VALUE
&&
92 GetFormatTable()->IsNumberFormat( sValStr
, nFIndex
, fTargetVal
) )
94 ScAddress
aFormulaAdr( nFCol
, nFRow
, nFTab
);
95 pFormula
= GetFormulaCell( aFormulaAdr
);
99 bool bDoneIteration
= false;
100 ScAddress
aValueAdr( nVCol
, nVRow
, nVTab
);
101 double* pVCell
= GetValueCell( aValueAdr
);
103 ScRange
aVRange( aValueAdr
, aValueAdr
); // for SetDirty
104 // Original value to be restored later if necessary
105 double fSaveVal
= *pVCell
;
107 const sal_uInt16 nMaxIter
= 100;
108 const double fEps
= 1E-10;
109 const double fDelta
= 1E-6;
111 double fBestX
, fXPrev
;
112 double fBestF
, fFPrev
;
113 fBestX
= fXPrev
= fSaveVal
;
115 pFormula
->Interpret();
116 bool bError
= ( pFormula
->GetErrCode() != FormulaError::NONE
);
117 // bError always corresponds with fF
119 fFPrev
= pFormula
->GetValue() - fTargetVal
;
121 fBestF
= fabs( fFPrev
);
122 if ( fBestF
< fDelta
)
123 bDoneIteration
= true;
125 double fX
= fXPrev
+ fEps
;
129 sal_uInt16 nIter
= 0;
131 bool bHorMoveError
= false;
132 // Conform Regula Falsi Method
133 while ( !bDoneIteration
&& ( nIter
++ < nMaxIter
) )
136 SetDirty( aVRange
, false );
137 pFormula
->Interpret();
138 bError
= ( pFormula
->GetErrCode() != FormulaError::NONE
);
139 fF
= pFormula
->GetValue() - fTargetVal
;
141 if ( fF
== fFPrev
&& !bError
)
143 // HORIZONTAL SEARCH: Keep moving x in both directions until the f(x)
144 // becomes different from the previous f(x). This routine is needed
145 // when a given function is discrete, in which case the resulting slope
146 // may become zero which ultimately causes the goal seek operation
149 sal_uInt16 nHorIter
= 0;
150 const double fHorStepAngle
= 5.0;
151 const double fHorMaxAngle
= 80.0;
152 int const nHorMaxIter
= static_cast<int>( fHorMaxAngle
/ fHorStepAngle
);
153 bool bDoneHorMove
= false;
155 while ( !bDoneHorMove
&& !bHorMoveError
&& nHorIter
++ < nHorMaxIter
)
157 double fHorAngle
= fHorStepAngle
* static_cast<double>( nHorIter
);
158 double fHorTangent
= std::tan(basegfx::deg2rad(fHorAngle
));
161 while( nIdx
++ < 2 && !bDoneHorMove
)
165 fHorX
= fX
+ fabs( fF
) * fHorTangent
;
167 fHorX
= fX
- fabs( fF
) * fHorTangent
;
170 SetDirty( aVRange
, false );
171 pFormula
->Interpret();
172 bHorMoveError
= ( pFormula
->GetErrCode() != FormulaError::NONE
);
176 fF
= pFormula
->GetValue() - fTargetVal
;
185 bHorMoveError
= true;
190 // move closer to last valid value (fXPrev), keep fXPrev & fFPrev
191 double fDiff
= ( fXPrev
- fX
) / 2;
192 if ( fabs( fDiff
) < fEps
)
193 fDiff
= ( fDiff
< 0.0 ? - fEps
: fEps
);
196 else if ( bHorMoveError
)
198 else if ( fabs(fF
) < fDelta
)
202 bDoneIteration
= true;
206 if ( fabs(fF
) + fDelta
< fBestF
)
212 if ( ( fXPrev
- fX
) != 0 )
214 fSlope
= ( fFPrev
- fF
) / ( fXPrev
- fX
);
215 if ( fabs( fSlope
) < fEps
)
216 fSlope
= fSlope
< 0.0 ? -fEps
: fEps
;
223 fX
= fX
- ( fF
/ fSlope
);
227 // Try a nice rounded input value if possible.
228 const double fNiceDelta
= ( bDoneIteration
&& fabs( fBestX
) >= 1e-3 ? 1e-3 : fDelta
);
229 nX
= ::rtl::math::approxFloor( ( fBestX
/ fNiceDelta
) + 0.5 ) * fNiceDelta
;
231 if ( bDoneIteration
)
234 SetDirty( aVRange
, false );
235 pFormula
->Interpret();
236 if ( fabs( pFormula
->GetValue() - fTargetVal
) > fabs( fF
) )
240 else if ( bError
|| bHorMoveError
)
245 SetDirty( aVRange
, false );
246 pFormula
->Interpret();
247 if ( !bDoneIteration
)
249 SetError( nVCol
, nVRow
, nVTab
, FormulaError::NotAvailable
);
254 SetError( nVCol
, nVRow
, nVTab
, FormulaError::NotAvailable
);
260 void ScDocument::InsertMatrixFormula(SCCOL nCol1
, SCROW nRow1
,
261 SCCOL nCol2
, SCROW nRow2
,
262 const ScMarkData
& rMark
,
263 const OUString
& rFormula
,
264 const ScTokenArray
* pArr
,
265 const formula::FormulaGrammar::Grammar eGram
)
267 PutInOrder(nCol1
, nCol2
);
268 PutInOrder(nRow1
, nRow2
);
269 nCol2
= std::min
<SCCOL
>(nCol2
, MaxCol());
270 nRow2
= std::min
<SCROW
>(nRow2
, MaxRow());
271 if (!rMark
.GetSelectCount())
273 SAL_WARN("sc", "ScDocument::InsertMatrixFormula: No table marked");
276 if (utl::ConfigManager::IsFuzzing())
279 if (nCol2
- nCol1
> 64)
281 if (nRow2
- nRow1
> 64)
284 assert( ValidColRow( nCol1
, nRow1
) && ValidColRow( nCol2
, nRow2
));
286 SCTAB nTab1
= *rMark
.begin();
288 ScFormulaCell
* pCell
;
289 ScAddress
aPos( nCol1
, nRow1
, nTab1
);
291 pCell
= new ScFormulaCell(*this, aPos
, *pArr
, eGram
, ScMatrixMode::Formula
);
293 pCell
= new ScFormulaCell(*this, aPos
, rFormula
, eGram
, ScMatrixMode::Formula
);
294 pCell
->SetMatColsRows( nCol2
- nCol1
+ 1, nRow2
- nRow1
+ 1 );
295 SCTAB nMax
= GetTableCount();
296 for (const auto& rTab
: rMark
)
306 pCell
= maTabs
[rTab
]->SetFormulaCell(nCol1
, nRow1
, pCell
);
307 if (!pCell
) //NULL if nCol1/nRow1 is invalid, which it can't be here
311 maTabs
[rTab
]->SetFormulaCell(
314 *pCell
, *this, ScAddress(nCol1
, nRow1
, rTab
), ScCloneFlags::StartListening
));
317 ScSingleRefData aRefData
;
318 aRefData
.InitFlags();
319 aRefData
.SetRelCol(0);
320 aRefData
.SetRelRow(0);
321 aRefData
.SetRelTab(0); // 2D matrix, always same sheet
323 ScTokenArray
aArr(*this); // consists only of one single reference token.
324 formula::FormulaToken
* t
= aArr
.AddMatrixSingleReference(aRefData
);
326 for (const SCTAB
& nTab
: rMark
)
331 ScTable
* pTab
= FetchTable(nTab
);
335 for (SCCOL nCol
: GetWritableColumnsRange(nTab
, nCol1
, nCol2
))
337 aRefData
.SetRelCol(nCol1
- nCol
);
338 for (SCROW nRow
= nRow1
; nRow
<= nRow2
; ++nRow
)
340 if (nCol
== nCol1
&& nRow
== nRow1
)
341 // Skip the base position.
344 // Reference in each cell must point to the origin cell relative to the current cell.
345 aRefData
.SetRelRow(nRow1
- nRow
);
346 *t
->GetSingleRef() = aRefData
;
347 // Token array must be cloned so that each formula cell receives its own copy.
348 ScTokenArray
aTokArr(aArr
.CloneValue());
349 aPos
= ScAddress(nCol
, nRow
, nTab
);
350 pCell
= new ScFormulaCell(*this, aPos
, aTokArr
, eGram
, ScMatrixMode::Reference
);
351 pTab
->SetFormulaCell(nCol
, nRow
, pCell
);
357 void ScDocument::InsertTableOp(const ScTabOpParam
& rParam
, // multiple (repeated?) operation
358 SCCOL nCol1
, SCROW nRow1
, SCCOL nCol2
, SCROW nRow2
,
359 const ScMarkData
& rMark
)
361 PutInOrder(nCol1
, nCol2
);
362 PutInOrder(nRow1
, nRow2
);
363 assert( ValidColRow( nCol1
, nRow1
) && ValidColRow( nCol2
, nRow2
));
369 SCTAB nMax
= GetTableCount();
370 for (const auto& rTab
: rMark
)
385 OSL_FAIL("ScDocument::InsertTableOp: No table marked");
390 OUStringBuffer
aForString("="
391 + ScCompiler::GetNativeSymbol(ocTableOp
)
392 + ScCompiler::GetNativeSymbol( ocOpen
));
394 const OUString
& sSep
= ScCompiler::GetNativeSymbol( ocSep
);
395 if (rParam
.meMode
== ScTabOpParam::Column
) // column only
397 aRef
.Set( rParam
.aRefFormulaCell
.GetAddress(), true, false, false );
398 aForString
.append(aRef
.GetRefString(*this, nTab1
)
400 + rParam
.aRefColCell
.GetRefString(*this, nTab1
)
402 aRef
.Set( nCol1
, nRow1
, nTab1
, false, true, true );
403 aForString
.append(aRef
.GetRefString(*this, nTab1
));
405 nCol2
= std::min( nCol2
, static_cast<SCCOL
>(rParam
.aRefFormulaEnd
.Col() -
406 rParam
.aRefFormulaCell
.Col() + nCol1
+ 1));
408 else if (rParam
.meMode
== ScTabOpParam::Row
) // row only
410 aRef
.Set( rParam
.aRefFormulaCell
.GetAddress(), false, true, false );
411 aForString
.append(aRef
.GetRefString(*this, nTab1
)
413 + rParam
.aRefRowCell
.GetRefString(*this, nTab1
)
415 aRef
.Set( nCol1
, nRow1
, nTab1
, true, false, true );
416 aForString
.append(aRef
.GetRefString(*this, nTab1
));
418 nRow2
= std::min( nRow2
, static_cast<SCROW
>(rParam
.aRefFormulaEnd
.Row() -
419 rParam
.aRefFormulaCell
.Row() + nRow1
+ 1));
423 aForString
.append(rParam
.aRefFormulaCell
.GetRefString(*this, nTab1
)
425 + rParam
.aRefColCell
.GetRefString(*this, nTab1
)
427 aRef
.Set( nCol1
, nRow1
+ 1, nTab1
, false, true, true );
428 aForString
.append(aRef
.GetRefString(*this, nTab1
)
430 + rParam
.aRefRowCell
.GetRefString(*this, nTab1
)
432 aRef
.Set( nCol1
+ 1, nRow1
, nTab1
, true, false, true );
433 aForString
.append(aRef
.GetRefString(*this, nTab1
));
436 aForString
.append(ScCompiler::GetNativeSymbol( ocClose
));
438 ScFormulaCell
aRefCell( *this, ScAddress( nCol1
, nRow1
, nTab1
), aForString
.makeStringAndClear(),
439 formula::FormulaGrammar::GRAM_NATIVE
, ScMatrixMode::NONE
);
440 for( j
= nCol1
; j
<= nCol2
; j
++ )
441 for( k
= nRow1
; k
<= nRow2
; k
++ )
442 for (i
= 0; i
< GetTableCount(); i
++)
444 for (const auto& rTab
: rMark
)
449 maTabs
[rTab
]->SetFormulaCell(
450 j
, k
, new ScFormulaCell(aRefCell
, *this, ScAddress(j
, k
, rTab
), ScCloneFlags::StartListening
));
457 bool setCacheTableReferenced(const ScDocument
& rDoc
, formula::FormulaToken
& rToken
, ScExternalRefManager
& rRefMgr
, const ScAddress
& rPos
)
459 switch (rToken
.GetType())
461 case svExternalSingleRef
:
462 return rRefMgr
.setCacheTableReferenced(
463 rToken
.GetIndex(), rToken
.GetString().getString(), 1);
464 case svExternalDoubleRef
:
466 const ScComplexRefData
& rRef
= *rToken
.GetDoubleRef();
467 ScRange aAbs
= rRef
.toAbs(rDoc
, rPos
);
468 size_t nSheets
= aAbs
.aEnd
.Tab() - aAbs
.aStart
.Tab() + 1;
469 return rRefMgr
.setCacheTableReferenced(
470 rToken
.GetIndex(), rToken
.GetString().getString(), nSheets
);
473 /* TODO: external names aren't supported yet, but would
474 * have to be marked as well, if so. Mechanism would be
476 OSL_FAIL("ScDocument::MarkUsedExternalReferences: implement the svExternalName case!");
486 bool ScDocument::MarkUsedExternalReferences( const ScTokenArray
& rArr
, const ScAddress
& rPos
)
491 ScExternalRefManager
* pRefMgr
= nullptr;
492 formula::FormulaTokenArrayPlainIterator
aIter( rArr
);
493 bool bAllMarked
= false;
496 formula::FormulaToken
* t
= aIter
.GetNextReferenceOrName();
499 if (t
->IsExternalRef())
502 pRefMgr
= GetExternalRefManager();
504 bAllMarked
= setCacheTableReferenced(*this, *t
, *pRefMgr
, rPos
);
506 else if (t
->GetType() == svIndex
)
508 // this is a named range. Check if the range contains an external
510 ScRangeData
* pRangeData
= GetRangeName()->findByIndex(t
->GetIndex());
514 ScTokenArray
* pArray
= pRangeData
->GetCode();
515 formula::FormulaTokenArrayPlainIterator
aArrayIter(*pArray
);
516 for (t
= aArrayIter
.First(); t
; t
= aArrayIter
.Next())
518 if (!t
->IsExternalRef())
522 pRefMgr
= GetExternalRefManager();
524 bAllMarked
= setCacheTableReferenced(*this, *t
, *pRefMgr
, rPos
);
531 bool ScDocument::GetNextSpellingCell(SCCOL
& nCol
, SCROW
& nRow
, SCTAB nTab
,
532 bool bInSel
, const ScMarkData
& rMark
) const
534 if (const ScTable
* pTable
= FetchTable(nTab
))
535 return pTable
->GetNextSpellingCell( nCol
, nRow
, bInSel
, rMark
);
539 bool ScDocument::GetNextMarkedCell( SCCOL
& rCol
, SCROW
& rRow
, SCTAB nTab
,
540 const ScMarkData
& rMark
)
542 if (ScTable
* pTable
= FetchTable(nTab
))
543 return pTable
->GetNextMarkedCell( rCol
, rRow
, rMark
);
547 void ScDocument::ReplaceStyle(const SvxSearchItem
& rSearchItem
,
548 SCCOL nCol
, SCROW nRow
, SCTAB nTab
,
549 const ScMarkData
& rMark
)
551 if (ScTable
* pTable
= FetchTable(nTab
))
552 pTable
->ReplaceStyle(rSearchItem
, nCol
, nRow
, rMark
, true/*bIsUndoP*/);
555 void ScDocument::CompileDBFormula()
557 sc::CompileFormulaContext
aCxt(*this);
558 for (auto& rxTab
: maTabs
)
561 rxTab
->CompileDBFormula(aCxt
);
565 void ScDocument::CompileColRowNameFormula()
567 sc::CompileFormulaContext
aCxt(*this);
568 for (auto& rxTab
: maTabs
)
571 rxTab
->CompileColRowNameFormula(aCxt
);
575 void ScDocument::InvalidateTableArea()
577 for (auto& rxTab
: maTabs
)
581 rxTab
->InvalidateTableArea();
582 if ( rxTab
->IsScenario() )
583 rxTab
->InvalidateScenarioRanges();
587 sal_Int32
ScDocument::GetMaxStringLen( SCTAB nTab
, SCCOL nCol
,
588 SCROW nRowStart
, SCROW nRowEnd
, rtl_TextEncoding eCharSet
) const
590 if (const ScTable
* pTable
= FetchTable(nTab
))
591 return pTable
->GetMaxStringLen(nCol
, nRowStart
, nRowEnd
, eCharSet
);
595 sal_Int32
ScDocument::GetMaxNumberStringLen( sal_uInt16
& nPrecision
, SCTAB nTab
,
596 SCCOL nCol
, SCROW nRowStart
, SCROW nRowEnd
) const
598 if (const ScTable
* pTable
= FetchTable(nTab
))
599 return pTable
->GetMaxNumberStringLen(nPrecision
, nCol
, nRowStart
, nRowEnd
);
603 bool ScDocument::GetSelectionFunction( ScSubTotalFunc eFunc
,
604 const ScAddress
& rCursor
, const ScMarkData
& rMark
,
607 ScFunctionData
aData(eFunc
);
609 ScMarkData
aMark(rMark
);
611 if (!aMark
.IsMultiMarked() && !aMark
.IsCellMarked(rCursor
.Col(), rCursor
.Row()))
612 aMark
.SetMarkArea(rCursor
);
614 SCTAB nMax
= GetTableCount();
615 ScMarkData::const_iterator itr
= aMark
.begin(), itrEnd
= aMark
.end();
617 for (; itr
!= itrEnd
&& *itr
< nMax
&& !aData
.getError(); ++itr
)
619 maTabs
[*itr
]->UpdateSelectionFunction(aData
, aMark
);
621 rResult
= aData
.getResult();
622 if (aData
.getError())
625 return !aData
.getError();
628 double ScDocument::RoundValueAsShown( double fVal
, sal_uInt32 nFormat
, const ScInterpreterContext
* pContext
) const
630 const SvNumberFormatter
* pFormatter
= pContext
? pContext
->GetFormatTable() : GetFormatTable();
631 const SvNumberformat
* pFormat
= pFormatter
->GetEntry( nFormat
);
634 SvNumFormatType nType
= pFormat
->GetMaskedType();
635 if (nType
!= SvNumFormatType::DATE
&& nType
!= SvNumFormatType::TIME
&& nType
!= SvNumFormatType::DATETIME
)
637 // MSVC doesn't recognize all paths init nPrecision and wails about
638 // "potentially uninitialized local variable 'nPrecision' used"
639 // so init to some random sensible value preserving all decimals.
640 short nPrecision
= 20;
641 bool bStdPrecision
= ((nFormat
% SV_COUNTRY_LANGUAGE_OFFSET
) == 0);
644 sal_uInt16 nIdx
= pFormat
->GetSubformatIndex( fVal
);
645 nPrecision
= static_cast<short>(pFormat
->GetFormatPrecision( nIdx
));
648 case SvNumFormatType::PERCENT
: // 0.41% == 0.0041
651 case SvNumFormatType::SCIENTIFIC
: // 1.23e-3 == 0.00123
655 nExp
= static_cast<short>(floor( log10( fVal
) ));
656 else if ( fVal
< 0.0 )
657 nExp
= static_cast<short>(floor( log10( -fVal
) ));
659 short nInteger
= static_cast<short>(pFormat
->GetFormatIntegerDigits( nIdx
));
660 if ( nInteger
> 1 ) // Engineering notation
662 short nIncrement
= nExp
% nInteger
;
663 if ( nIncrement
!= 0 )
665 nPrecision
+= nIncrement
;
667 nPrecision
+= nInteger
;
672 case SvNumFormatType::FRACTION
: // get value of fraction representation
674 return pFormat
->GetRoundFractionValue( fVal
);
676 case SvNumFormatType::NUMBER
:
677 case SvNumFormatType::CURRENCY
:
678 { // tdf#106253 Thousands divisors for format "0,"
679 const sal_uInt16 nTD
= pFormat
->GetThousandDivisorPrecision( nIdx
);
680 if (nTD
== SvNumberFormatter::UNLIMITED_PRECISION
)
681 // Format contains General keyword, handled below.
682 bStdPrecision
= true;
692 nPrecision
= static_cast<short>(GetDocOptions().GetStdPrecision());
693 // #i115512# no rounding for automatic decimals
694 if (nPrecision
== static_cast<short>(SvNumberFormatter::UNLIMITED_PRECISION
))
697 double fRound
= ::rtl::math::round( fVal
, nPrecision
);
698 if ( ::rtl::math::approxEqual( fVal
, fRound
) )
699 return fVal
; // rounding might introduce some error
707 // conditional formats and validation ranges
709 sal_uLong
ScDocument::AddCondFormat( std::unique_ptr
<ScConditionalFormat
> pNew
, SCTAB nTab
)
714 if (ScTable
* pTable
= FetchTable(nTab
))
715 return pTable
->AddCondFormat(std::move(pNew
));
720 sal_uLong
ScDocument::AddValidationEntry( const ScValidationData
& rNew
)
723 return 0; // empty is always 0
725 if (!pValidationList
)
727 ScMutationGuard
aGuard(*this, ScMutationGuardFlags::CORE
);
728 pValidationList
.reset(new ScValidationDataList
);
732 for( const auto& rxData
: *pValidationList
)
734 const ScValidationData
* pData
= rxData
.get();
735 sal_uLong nKey
= pData
->GetKey();
736 if ( pData
->EqualEntries( rNew
) )
742 // might be called from ScPatternAttr::PutInPool; thus clone (real copy)
744 sal_uLong nNewKey
= nMax
+ 1;
745 std::unique_ptr
<ScValidationData
> pInsert(rNew
.Clone(this));
746 pInsert
->SetKey( nNewKey
);
747 ScMutationGuard
aGuard(*this, ScMutationGuardFlags::CORE
);
748 pValidationList
->InsertNew( std::move(pInsert
) );
752 const SfxPoolItem
* ScDocument::GetEffItem(
753 SCCOL nCol
, SCROW nRow
, SCTAB nTab
, sal_uInt16 nWhich
) const
755 const ScPatternAttr
* pPattern
= GetPattern( nCol
, nRow
, nTab
);
758 const SfxItemSet
& rSet
= pPattern
->GetItemSet();
759 if ( rSet
.GetItemState( ATTR_CONDITIONAL
) == SfxItemState::SET
)
761 const ScCondFormatIndexes
& rIndex
= pPattern
->GetItem(ATTR_CONDITIONAL
).GetCondFormatData();
762 ScConditionalFormatList
* pCondFormList
= GetCondFormList( nTab
);
763 if (!rIndex
.empty() && pCondFormList
)
765 for(const auto& rItem
: rIndex
)
767 const ScConditionalFormat
* pForm
= pCondFormList
->GetFormat( rItem
);
770 ScAddress
aPos(nCol
, nRow
, nTab
);
771 ScRefCellValue
aCell(const_cast<ScDocument
&>(*this), aPos
);
772 const OUString
& aStyle
= pForm
->GetCellStyle(aCell
, aPos
);
773 if (!aStyle
.isEmpty())
775 SfxStyleSheetBase
* pStyleSheet
= mxPoolHelper
->GetStylePool()->Find(
776 aStyle
, SfxStyleFamily::Para
);
777 const SfxPoolItem
* pItem
= nullptr;
778 if ( pStyleSheet
&& pStyleSheet
->GetItemSet().GetItemState(
779 nWhich
, true, &pItem
) == SfxItemState::SET
)
786 return &rSet
.Get( nWhich
);
788 OSL_FAIL("no pattern");
792 const SfxItemSet
* ScDocument::GetCondResult( SCCOL nCol
, SCROW nRow
, SCTAB nTab
, ScRefCellValue
* pCell
) const
794 ScConditionalFormatList
* pFormatList
= GetCondFormList(nTab
);
798 ScAddress
aPos(nCol
, nRow
, nTab
);
799 ScRefCellValue aCell
;
800 if( pCell
== nullptr )
802 aCell
.assign(const_cast<ScDocument
&>(*this), aPos
);
805 const ScPatternAttr
* pPattern
= GetPattern( nCol
, nRow
, nTab
);
806 const ScCondFormatIndexes
& rIndex
=
807 pPattern
->GetItem(ATTR_CONDITIONAL
).GetCondFormatData();
809 return GetCondResult(*pCell
, aPos
, *pFormatList
, rIndex
);
812 const SfxItemSet
* ScDocument::GetCondResult(
813 ScRefCellValue
& rCell
, const ScAddress
& rPos
, const ScConditionalFormatList
& rList
,
814 const ScCondFormatIndexes
& rIndex
) const
816 for (const auto& rItem
: rIndex
)
818 const ScConditionalFormat
* pForm
= rList
.GetFormat(rItem
);
822 const OUString
& aStyle
= pForm
->GetCellStyle(rCell
, rPos
);
823 if (!aStyle
.isEmpty())
825 SfxStyleSheetBase
* pStyleSheet
=
826 mxPoolHelper
->GetStylePool()->Find(aStyle
, SfxStyleFamily::Para
);
829 return &pStyleSheet
->GetItemSet();
831 // if style is not there, treat like no condition
838 ScConditionalFormat
* ScDocument::GetCondFormat(
839 SCCOL nCol
, SCROW nRow
, SCTAB nTab
) const
841 sal_uInt32 nIndex
= 0;
842 const ScCondFormatIndexes
& rCondFormats
= GetAttr(nCol
, nRow
, nTab
, ATTR_CONDITIONAL
)->GetCondFormatData();
844 if(!rCondFormats
.empty())
845 nIndex
= rCondFormats
[0];
849 ScConditionalFormatList
* pCondFormList
= GetCondFormList(nTab
);
851 return pCondFormList
->GetFormat( nIndex
);
854 OSL_FAIL("pCondFormList is 0");
861 ScConditionalFormatList
* ScDocument::GetCondFormList(SCTAB nTab
) const
864 return maTabs
[nTab
]->GetCondFormList();
868 void ScDocument::SetCondFormList( ScConditionalFormatList
* pList
, SCTAB nTab
)
870 if (ScTable
* pTable
= FetchTable(nTab
))
871 pTable
->SetCondFormList(pList
);
874 const ScValidationData
* ScDocument::GetValidationEntry( sal_uInt32 nIndex
) const
876 if ( pValidationList
)
877 return pValidationList
->GetData( nIndex
);
882 void ScDocument::DeleteConditionalFormat(sal_uLong nOldIndex
, SCTAB nTab
)
884 if (ScTable
* pTable
= FetchTable(nTab
))
885 pTable
->DeleteConditionalFormat(nOldIndex
);
888 bool ScDocument::HasDetectiveOperations() const
890 return pDetOpList
&& pDetOpList
->Count();
893 void ScDocument::AddDetectiveOperation( const ScDetOpData
& rData
)
896 pDetOpList
.reset(new ScDetOpList
);
898 pDetOpList
->Append( rData
);
901 void ScDocument::ClearDetectiveOperations()
903 pDetOpList
.reset(); // deletes also the entries
906 void ScDocument::SetDetOpList(std::unique_ptr
<ScDetOpList
> pNew
)
908 pDetOpList
= std::move(pNew
);
911 // Comparison of Documents
914 #define SC_DOCCOMP_MAXDIFF 256
915 #define SC_DOCCOMP_MINGOOD 128
916 #define SC_DOCCOMP_COLUMNS 10
917 #define SC_DOCCOMP_ROWS 100
919 sal_uInt16
ScDocument::RowDifferences( SCROW nThisRow
, SCTAB nThisTab
,
920 ScDocument
& rOtherDoc
, SCROW nOtherRow
, SCTAB nOtherTab
,
921 SCCOL nMaxCol
, const SCCOLROW
* pOtherCols
)
925 for (SCCOL nThisCol
=0; nThisCol
<=nMaxCol
; nThisCol
++)
929 nOtherCol
= static_cast<SCCOL
>(pOtherCols
[nThisCol
]);
931 nOtherCol
= nThisCol
;
933 if (ValidCol(nOtherCol
)) // only compare columns that are common to both docs
935 ScRefCellValue
aThisCell(*this, ScAddress(nThisCol
, nThisRow
, nThisTab
));
936 ScRefCellValue
aOtherCell(rOtherDoc
, ScAddress(nOtherCol
, nOtherRow
, nOtherTab
));
937 if (!aThisCell
.equalsWithoutFormat(aOtherCell
))
939 if (!aThisCell
.isEmpty() && !aOtherCell
.isEmpty())
942 nDif
+= 4; // content <-> empty counts more
945 if (!aThisCell
.isEmpty() || !aOtherCell
.isEmpty())
951 return static_cast<sal_uInt16
>((nDif
*64)/nUsed
); // max.256 (SC_DOCCOMP_MAXDIFF)
953 OSL_ENSURE(!nDif
,"Diff without Used");
957 sal_uInt16
ScDocument::ColDifferences( SCCOL nThisCol
, SCTAB nThisTab
,
958 ScDocument
& rOtherDoc
, SCCOL nOtherCol
, SCTAB nOtherTab
,
959 SCROW nMaxRow
, const SCCOLROW
* pOtherRows
)
962 //TODO: optimize e.g. with iterator?
965 sal_uInt64 nUsed
= 0;
966 for (SCROW nThisRow
=0; nThisRow
<=nMaxRow
; nThisRow
++)
970 nOtherRow
= pOtherRows
[nThisRow
];
972 nOtherRow
= nThisRow
;
974 if (ValidRow(nOtherRow
)) // only compare rows that are common to both docs
976 ScRefCellValue
aThisCell(*this, ScAddress(nThisCol
, nThisRow
, nThisTab
));
977 ScRefCellValue
aOtherCell(rOtherDoc
, ScAddress(nOtherCol
, nOtherRow
, nOtherTab
));
978 if (!aThisCell
.equalsWithoutFormat(aOtherCell
))
980 if (!aThisCell
.isEmpty() && !aOtherCell
.isEmpty())
983 nDif
+= 4; // content <-> empty counts more
986 if (!aThisCell
.isEmpty() || !aOtherCell
.isEmpty())
992 return static_cast<sal_uInt16
>((nDif
*64)/nUsed
); // max.256
994 OSL_ENSURE(!nDif
,"Diff without Used");
998 void ScDocument::FindOrder( SCCOLROW
* pOtherRows
, SCCOLROW nThisEndRow
, SCCOLROW nOtherEndRow
,
999 bool bColumns
, ScDocument
& rOtherDoc
, SCTAB nThisTab
, SCTAB nOtherTab
,
1000 SCCOLROW nEndCol
, const SCCOLROW
* pTranslate
, ScProgress
* pProgress
, sal_uInt64 nProAdd
)
1002 // bColumns=true: rows are columns and vice versa
1004 SCCOLROW nMaxCont
; // continue by how much
1005 SCCOLROW nMinGood
; // what is a hit (incl.)
1008 nMaxCont
= SC_DOCCOMP_COLUMNS
; // 10 columns
1009 nMinGood
= SC_DOCCOMP_MINGOOD
;
1011 //TODO: additional pass with nMinGood = 0 ????
1016 nMaxCont
= SC_DOCCOMP_ROWS
; // 100 rows
1017 nMinGood
= SC_DOCCOMP_MINGOOD
;
1019 bool bUseTotal
= bColumns
&& !pTranslate
; // only for the 1st pass
1021 SCCOLROW nOtherRow
= 0;
1024 bool bTotal
= false; // hold for several nThisRow
1025 SCCOLROW nUnknown
= 0;
1026 for (nThisRow
= 0; nThisRow
<= nThisEndRow
; nThisRow
++)
1028 SCCOLROW nTempOther
= nOtherRow
;
1029 bool bFound
= false;
1030 sal_uInt16 nBest
= SC_DOCCOMP_MAXDIFF
;
1031 SCCOLROW nMax
= std::min( nOtherEndRow
, static_cast<SCCOLROW
>(( nTempOther
+ nMaxCont
+ nUnknown
)) );
1032 for (SCCOLROW i
=nTempOther
; i
<=nMax
&& nBest
>0; i
++) // stop at 0
1035 nComp
= ColDifferences( static_cast<SCCOL
>(nThisRow
), nThisTab
, rOtherDoc
, static_cast<SCCOL
>(i
), nOtherTab
, nEndCol
, pTranslate
);
1037 nComp
= RowDifferences( nThisRow
, nThisTab
, rOtherDoc
, i
, nOtherTab
, static_cast<SCCOL
>(nEndCol
), pTranslate
);
1038 if ( nComp
< nBest
&& ( nComp
<= nMinGood
|| bTotal
) )
1044 if ( nComp
< SC_DOCCOMP_MAXDIFF
|| bFound
)
1046 else if ( i
== nTempOther
&& bUseTotal
)
1047 bTotal
= true; // only at the very top
1051 pOtherRows
[nThisRow
] = nTempOther
;
1052 nOtherRow
= nTempOther
+ 1;
1057 pOtherRows
[nThisRow
] = SCROW_MAX
;
1062 pProgress
->SetStateOnPercent(nProAdd
+static_cast<sal_uLong
>(nThisRow
));
1065 // fill in blocks that don't match
1067 SCROW nFillStart
= 0;
1069 bool bInFill
= false;
1070 for (nThisRow
= 0; nThisRow
<= nThisEndRow
+1; nThisRow
++)
1072 SCROW nThisOther
= ( nThisRow
<= nThisEndRow
) ? pOtherRows
[nThisRow
] : (nOtherEndRow
+1);
1073 if ( ValidRow(nThisOther
) )
1077 if ( nThisOther
> nFillStart
) // is there something to distribute?
1079 SCROW nDiff1
= nThisOther
- nFillStart
;
1080 SCROW nDiff2
= nThisRow
- nFillPos
;
1081 SCROW nMinDiff
= std::min(nDiff1
, nDiff2
);
1082 for (SCROW i
=0; i
<nMinDiff
; i
++)
1083 pOtherRows
[nFillPos
+i
] = nFillStart
+i
;
1088 nFillStart
= nThisOther
+ 1;
1089 nFillPos
= nThisRow
+ 1;
1096 void ScDocument::CompareDocument( ScDocument
& rOtherDoc
)
1101 SCTAB nThisCount
= GetTableCount();
1102 SCTAB nOtherCount
= rOtherDoc
.GetTableCount();
1103 std::unique_ptr
<SCTAB
[]> pOtherTabs(new SCTAB
[nThisCount
]);
1106 // compare tables with identical names
1108 OUString aOtherName
;
1109 for (nThisTab
=0; nThisTab
<nThisCount
; nThisTab
++)
1111 SCTAB nOtherTab
= SCTAB_MAX
;
1112 if (!IsScenario(nThisTab
)) // skip scenarios
1114 GetName( nThisTab
, aThisName
);
1115 for (SCTAB nTemp
=0; nTemp
<nOtherCount
&& nOtherTab
>MAXTAB
; nTemp
++)
1116 if (!rOtherDoc
.IsScenario(nTemp
))
1118 rOtherDoc
.GetName( nTemp
, aOtherName
);
1119 if ( aThisName
== aOtherName
)
1123 pOtherTabs
[nThisTab
] = nOtherTab
;
1125 // fill in, so that un-named tables don't get lost
1126 SCTAB nFillStart
= 0;
1128 bool bInFill
= false;
1129 for (nThisTab
= 0; nThisTab
<= nThisCount
; nThisTab
++)
1131 SCTAB nThisOther
= ( nThisTab
< nThisCount
) ? pOtherTabs
[nThisTab
] : nOtherCount
;
1132 if ( ValidTab(nThisOther
) )
1136 if ( nThisOther
> nFillStart
) // is there something to distribute?
1138 SCTAB nDiff1
= nThisOther
- nFillStart
;
1139 SCTAB nDiff2
= nThisTab
- nFillPos
;
1140 SCTAB nMinDiff
= std::min(nDiff1
, nDiff2
);
1141 for (SCTAB i
=0; i
<nMinDiff
; i
++)
1142 if ( !IsScenario(nFillPos
+i
) && !rOtherDoc
.IsScenario(nFillStart
+i
) )
1143 pOtherTabs
[nFillPos
+i
] = nFillStart
+i
;
1148 nFillStart
= nThisOther
+ 1;
1149 nFillPos
= nThisTab
+ 1;
1155 // compare tables in the original order
1157 for (nThisTab
=0; nThisTab
<nThisCount
; nThisTab
++)
1159 SCTAB nOtherTab
= pOtherTabs
[nThisTab
];
1160 if ( ValidTab(nOtherTab
) )
1162 SCCOL nThisEndCol
= 0;
1163 SCROW nThisEndRow
= 0;
1164 SCCOL nOtherEndCol
= 0;
1165 SCROW nOtherEndRow
= 0;
1166 GetCellArea( nThisTab
, nThisEndCol
, nThisEndRow
);
1167 rOtherDoc
.GetCellArea( nOtherTab
, nOtherEndCol
, nOtherEndRow
);
1168 SCCOL nEndCol
= std::max(nThisEndCol
, nOtherEndCol
);
1169 SCROW nEndRow
= std::max(nThisEndRow
, nOtherEndRow
);
1172 sal_uLong n1
,n2
; // for AppendDeleteRange
1174 //TODO: one Progress over all tables ???
1177 GetName( nThisTab
, aTabName
);
1178 OUString aTemplate
= ScResId(STR_PROGRESS_COMPARING
);
1179 sal_Int32 nIndex
= 0;
1180 OUString aProText
= o3tl::getToken(aTemplate
, 0, '#', nIndex
) +
1182 o3tl::getToken(aTemplate
, 0, '#', nIndex
);
1183 ScProgress
aProgress( GetDocumentShell(), aProText
, 3*nThisEndRow
, true ); // 2x FindOrder, 1x here
1184 tools::Long nProgressStart
= 2*nThisEndRow
; // start for here
1186 std::unique_ptr
<SCCOLROW
[]> pTempRows(new SCCOLROW
[nThisEndRow
+1]);
1187 std::unique_ptr
<SCCOLROW
[]> pOtherRows(new SCCOLROW
[nThisEndRow
+1]);
1188 std::unique_ptr
<SCCOLROW
[]> pOtherCols(new SCCOLROW
[nThisEndCol
+1]);
1190 // find inserted/deleted columns/rows:
1192 // 1) compare original rows (pTempRows)
1193 // 2) compare original columns (pOtherCols)
1194 // with this column order compare rows (pOtherRows)
1196 //TODO: compare columns twice with different nMinGood ???
1199 FindOrder( pTempRows
.get(), nThisEndRow
, nOtherEndRow
, false,
1200 rOtherDoc
, nThisTab
, nOtherTab
, nEndCol
, nullptr, &aProgress
, 0 );
1202 FindOrder( pOtherCols
.get(), nThisEndCol
, nOtherEndCol
, true,
1203 rOtherDoc
, nThisTab
, nOtherTab
, nEndRow
, nullptr, nullptr, 0 );
1204 FindOrder( pOtherRows
.get(), nThisEndRow
, nOtherEndRow
, false,
1205 rOtherDoc
, nThisTab
, nOtherTab
, nThisEndCol
,
1206 pOtherCols
.get(), &aProgress
, nThisEndRow
);
1208 sal_uLong nMatch1
= 0; // pTempRows, no columns
1209 for (nThisRow
= 0; nThisRow
<=nThisEndRow
; nThisRow
++)
1210 if (ValidRow(pTempRows
[nThisRow
]))
1211 nMatch1
+= SC_DOCCOMP_MAXDIFF
-
1212 RowDifferences( nThisRow
, nThisTab
, rOtherDoc
, pTempRows
[nThisRow
],
1213 nOtherTab
, nEndCol
, nullptr );
1215 sal_uLong nMatch2
= 0; // pOtherRows, pOtherCols
1216 for (nThisRow
= 0; nThisRow
<=nThisEndRow
; nThisRow
++)
1217 if (ValidRow(pOtherRows
[nThisRow
]))
1218 nMatch2
+= SC_DOCCOMP_MAXDIFF
-
1219 RowDifferences( nThisRow
, nThisTab
, rOtherDoc
, pOtherRows
[nThisRow
],
1220 nOtherTab
, nThisEndCol
, pOtherCols
.get() );
1222 if ( nMatch1
>= nMatch2
) // without columns ?
1225 for (nThisCol
= 0; nThisCol
<=nThisEndCol
; nThisCol
++)
1226 pOtherCols
[nThisCol
] = nThisCol
;
1228 // swap row-arrays (they get both deleted anyway)
1229 pTempRows
.swap(pOtherRows
);
1233 // remains for pOtherCols, pOtherRows
1236 // Generate Change-Actions
1237 // 1) columns from the right
1238 // 2) rows from below
1239 // 3) single cells in normal order
1241 // Actions for inserted/deleted columns
1243 SCCOL nLastOtherCol
= static_cast<SCCOL
>(nOtherEndCol
+ 1);
1244 // nThisEndCol ... 0
1245 for ( nThisCol
= nThisEndCol
+1; nThisCol
> 0; )
1248 SCCOL nOtherCol
= static_cast<SCCOL
>(pOtherCols
[nThisCol
]);
1249 if ( ValidCol(nOtherCol
) && nOtherCol
+1 < nLastOtherCol
)
1252 ScRange
aDelRange( nOtherCol
+1, 0, nOtherTab
,
1253 nLastOtherCol
-1, MaxRow(), nOtherTab
);
1254 pChangeTrack
->AppendDeleteRange( aDelRange
, &rOtherDoc
, n1
, n2
);
1256 if ( nOtherCol
> MaxCol() ) // inserted
1259 if ( nThisCol
== nThisEndCol
|| ValidCol(static_cast<SCCOL
>(pOtherCols
[nThisCol
+1])) )
1261 SCCOL nFirstNew
= nThisCol
;
1262 while ( nFirstNew
> 0 && pOtherCols
[nFirstNew
-1] > MaxCol() )
1264 SCCOL nDiff
= nThisCol
- nFirstNew
;
1265 ScRange
aRange( nLastOtherCol
, 0, nOtherTab
,
1266 nLastOtherCol
+nDiff
, MaxRow(), nOtherTab
);
1267 pChangeTrack
->AppendInsert( aRange
);
1271 nLastOtherCol
= nOtherCol
;
1273 if ( nLastOtherCol
> 0 ) // deleted at the very top
1275 ScRange
aDelRange( 0, 0, nOtherTab
,
1276 nLastOtherCol
-1, MaxRow(), nOtherTab
);
1277 pChangeTrack
->AppendDeleteRange( aDelRange
, &rOtherDoc
, n1
, n2
);
1280 // Actions for inserted/deleted rows
1282 SCROW nLastOtherRow
= nOtherEndRow
+ 1;
1283 // nThisEndRow ... 0
1284 for ( nThisRow
= nThisEndRow
+1; nThisRow
> 0; )
1287 SCROW nOtherRow
= pOtherRows
[nThisRow
];
1288 if ( ValidRow(nOtherRow
) && nOtherRow
+1 < nLastOtherRow
)
1291 ScRange
aDelRange( 0, nOtherRow
+1, nOtherTab
,
1292 MaxCol(), nLastOtherRow
-1, nOtherTab
);
1293 pChangeTrack
->AppendDeleteRange( aDelRange
, &rOtherDoc
, n1
, n2
);
1295 if ( nOtherRow
> MaxRow() ) // inserted
1298 if ( nThisRow
== nThisEndRow
|| ValidRow(pOtherRows
[nThisRow
+1]) )
1300 SCROW nFirstNew
= nThisRow
;
1301 while ( nFirstNew
> 0 && pOtherRows
[nFirstNew
-1] > MaxRow() )
1303 SCROW nDiff
= nThisRow
- nFirstNew
;
1304 ScRange
aRange( 0, nLastOtherRow
, nOtherTab
,
1305 MaxCol(), nLastOtherRow
+nDiff
, nOtherTab
);
1306 pChangeTrack
->AppendInsert( aRange
);
1310 nLastOtherRow
= nOtherRow
;
1312 if ( nLastOtherRow
> 0 ) // deleted at the very top
1314 ScRange
aDelRange( 0, 0, nOtherTab
,
1315 MaxCol(), nLastOtherRow
-1, nOtherTab
);
1316 pChangeTrack
->AppendDeleteRange( aDelRange
, &rOtherDoc
, n1
, n2
);
1319 // walk rows to find single cells
1321 for (nThisRow
= 0; nThisRow
<= nThisEndRow
; nThisRow
++)
1323 SCROW nOtherRow
= pOtherRows
[nThisRow
];
1324 for (nThisCol
= 0; nThisCol
<= nThisEndCol
; nThisCol
++)
1326 SCCOL nOtherCol
= static_cast<SCCOL
>(pOtherCols
[nThisCol
]);
1327 ScAddress
aThisPos( nThisCol
, nThisRow
, nThisTab
);
1328 ScCellValue aThisCell
;
1329 aThisCell
.assign(*this, aThisPos
);
1330 ScCellValue aOtherCell
; // start empty
1331 if ( ValidCol(nOtherCol
) && ValidRow(nOtherRow
) )
1333 ScAddress
aOtherPos( nOtherCol
, nOtherRow
, nOtherTab
);
1334 aOtherCell
.assign(rOtherDoc
, aOtherPos
);
1337 if (!aThisCell
.equalsWithoutFormat(aOtherCell
))
1339 ScRange
aRange( aThisPos
);
1340 ScChangeActionContent
* pAction
= new ScChangeActionContent( aRange
);
1341 pAction
->SetOldValue(aOtherCell
, &rOtherDoc
, this);
1342 pAction
->SetNewValue(aThisCell
, this);
1343 pChangeTrack
->Append( pAction
);
1346 aProgress
.SetStateOnPercent(nProgressStart
+nThisRow
);
1352 sal_Unicode
ScDocument::GetSheetSeparator() const
1354 const ScCompiler::Convention
* pConv
= ScCompiler::GetRefConvention(
1355 FormulaGrammar::extractRefConvention( GetGrammar()));
1357 return pConv
? pConv
->getSpecialSymbol( ScCompiler::Convention::SHEET_SEPARATOR
) : '.';
1360 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */