1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /*
3  * This file is part of the LibreOffice project.
4  *
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/.
8  *
9  * This file incorporates work covered by the following license notice:
10  *
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 .
18  */
19 
20 #include <memory>
21 #include <defnamesbuffer.hxx>
22 
23 #include <com/sun/star/sheet/NamedRangeFlag.hpp>
24 #include <com/sun/star/sheet/XPrintAreas.hpp>
25 #include <com/sun/star/sheet/XSpreadsheet.hpp>
26 #include <osl/diagnose.h>
27 #include <rtl/ustrbuf.hxx>
28 #include <oox/helper/binaryinputstream.hxx>
29 #include <oox/helper/attributelist.hxx>
30 #include <oox/token/tokens.hxx>
31 #include <addressconverter.hxx>
32 #include <biffhelper.hxx>
33 #include <externallinkbuffer.hxx>
34 #include <formulabase.hxx>
35 #include <formulaparser.hxx>
36 #include <worksheetbuffer.hxx>
37 #include <tokenarray.hxx>
38 #include <tokenuno.hxx>
39 #include <compiler.hxx>
40 #include <document.hxx>
41 
42 namespace oox::xls {
43 
44 using namespace ::com::sun::star::sheet;
45 using namespace ::com::sun::star::table;
46 using namespace ::com::sun::star::uno;
47 
48 namespace {
49 
50 const sal_uInt32 BIFF12_DEFNAME_HIDDEN      = 0x00000001;
51 const sal_uInt32 BIFF12_DEFNAME_FUNC        = 0x00000002;
52 const sal_uInt32 BIFF12_DEFNAME_VBNAME      = 0x00000004;
53 const sal_uInt32 BIFF12_DEFNAME_MACRO       = 0x00000008;
54 const sal_uInt32 BIFF12_DEFNAME_BUILTIN     = 0x00000020;
55 
56 constexpr OUStringLiteral spcOoxPrefix(u"_xlnm.");
57 
58 const char* const sppcBaseNames[] =
59 {
60     "Consolidate_Area",
61     "Auto_Open",
62     "Auto_Close",
63     "Extract",
64     "Database",
65     "Criteria",
66     "Print_Area",
67     "Print_Titles",
68     "Recorder",
69     "Data_Form",
70     "Auto_Activate",
71     "Auto_Deactivate",
72     "Sheet_Title",
73     "_FilterDatabase"
74 };
75 
lclGetBaseName(sal_Unicode cBuiltinId)76 OUString lclGetBaseName( sal_Unicode cBuiltinId )
77 {
78     OSL_ENSURE( cBuiltinId < SAL_N_ELEMENTS( sppcBaseNames ), "lclGetBaseName - unsupported built-in identifier" );
79     OUStringBuffer aBuffer;
80     if( cBuiltinId < SAL_N_ELEMENTS( sppcBaseNames ) )
81         aBuffer.appendAscii( sppcBaseNames[ cBuiltinId ] );
82     else
83         aBuffer.append( static_cast< sal_Int32 >( cBuiltinId ) );
84     return aBuffer.makeStringAndClear();
85 }
86 
lclGetPrefixedName(sal_Unicode cBuiltinId)87 OUString lclGetPrefixedName( sal_Unicode cBuiltinId )
88 {
89     return spcOoxPrefix + lclGetBaseName( cBuiltinId );
90 }
91 
92 /** returns the built-in name identifier from a prefixed built-in name, e.g. '_xlnm.Print_Area'. */
lclGetBuiltinIdFromPrefixedName(const OUString & rModelName)93 sal_Unicode lclGetBuiltinIdFromPrefixedName( const OUString& rModelName )
94 {
95     if( rModelName.matchIgnoreAsciiCase( spcOoxPrefix ) )
96     {
97         for( sal_Unicode cBuiltinId = 0; cBuiltinId < SAL_N_ELEMENTS( sppcBaseNames ); ++cBuiltinId )
98         {
99             OUString aBaseName = lclGetBaseName( cBuiltinId );
100             sal_Int32 nBaseNameLen = aBaseName.getLength();
101             if( (rModelName.getLength() == spcOoxPrefix.getLength() + nBaseNameLen) && rModelName.matchIgnoreAsciiCase( aBaseName, spcOoxPrefix.getLength() ) )
102                 return cBuiltinId;
103         }
104     }
105     return BIFF_DEFNAME_UNKNOWN;
106 }
107 
108 /** returns the built-in name identifier from a built-in base name, e.g. 'Print_Area'. */
lclGetBuiltinIdFromBaseName(const OUString & rModelName)109 sal_Unicode lclGetBuiltinIdFromBaseName( const OUString& rModelName )
110 {
111     for( sal_Unicode cBuiltinId = 0; cBuiltinId < SAL_N_ELEMENTS( sppcBaseNames ); ++cBuiltinId )
112         if( rModelName.equalsIgnoreAsciiCaseAscii( sppcBaseNames[ cBuiltinId ] ) )
113             return cBuiltinId;
114     return BIFF_DEFNAME_UNKNOWN;
115 }
116 
lclGetUpcaseModelName(const OUString & rModelName)117 OUString lclGetUpcaseModelName( const OUString& rModelName )
118 {
119     // TODO: i18n?
120     return rModelName.toAsciiUpperCase();
121 }
122 
123 } // namespace
124 
DefinedNameModel()125 DefinedNameModel::DefinedNameModel() :
126     mnSheet( -1 ),
127     mnFuncGroupId( -1 ),
128     mbMacro( false ),
129     mbFunction( false ),
130     mbVBName( false ),
131     mbHidden( false )
132 {
133 }
134 
DefinedNameBase(const WorkbookHelper & rHelper)135 DefinedNameBase::DefinedNameBase( const WorkbookHelper& rHelper ) :
136     WorkbookHelper( rHelper )
137 {
138 }
139 
getUpcaseModelName() const140 const OUString& DefinedNameBase::getUpcaseModelName() const
141 {
142     if( maUpModelName.isEmpty() )
143         maUpModelName = lclGetUpcaseModelName( maModel.maName );
144     return maUpModelName;
145 }
146 
DefinedName(const WorkbookHelper & rHelper)147 DefinedName::DefinedName( const WorkbookHelper& rHelper ) :
148     DefinedNameBase( rHelper ),
149     maScRangeData(nullptr, false),
150     mnTokenIndex( -1 ),
151     mnCalcSheet( 0 ),
152     mcBuiltinId( BIFF_DEFNAME_UNKNOWN )
153 {
154 }
155 
importDefinedName(const AttributeList & rAttribs)156 void DefinedName::importDefinedName( const AttributeList& rAttribs )
157 {
158     maModel.maName        = rAttribs.getXString( XML_name, OUString() );
159     maModel.mnSheet       = rAttribs.getInteger( XML_localSheetId, -1 );
160     maModel.mnFuncGroupId = rAttribs.getInteger( XML_functionGroupId, -1 );
161     maModel.mbMacro       = rAttribs.getBool( XML_xlm, false );
162     maModel.mbFunction    = rAttribs.getBool( XML_function, false );
163     maModel.mbVBName      = rAttribs.getBool( XML_vbProcedure, false );
164     maModel.mbHidden      = rAttribs.getBool( XML_hidden, false );
165     mnCalcSheet = (maModel.mnSheet >= 0) ? getWorksheets().getCalcSheetIndex( maModel.mnSheet ) : -1;
166 
167     /*  Detect built-in state from name itself, there is no built-in flag.
168         Built-in names are prefixed with '_xlnm.' instead. */
169     mcBuiltinId = lclGetBuiltinIdFromPrefixedName( maModel.maName );
170 }
171 
setFormula(const OUString & rFormula)172 void DefinedName::setFormula( const OUString& rFormula )
173 {
174     maModel.maFormula = rFormula;
175 }
176 
importDefinedName(SequenceInputStream & rStrm)177 void DefinedName::importDefinedName( SequenceInputStream& rStrm )
178 {
179     sal_uInt32 nFlags;
180     nFlags = rStrm.readuInt32();
181     rStrm.skip( 1 );    // keyboard shortcut
182     maModel.mnSheet = rStrm.readInt32();
183     rStrm >> maModel.maName;
184     mnCalcSheet = (maModel.mnSheet >= 0) ? getWorksheets().getCalcSheetIndex( maModel.mnSheet ) : -1;
185 
186     // macro function/command, hidden flag
187     maModel.mnFuncGroupId = extractValue< sal_Int32 >( nFlags, 6, 9 );
188     maModel.mbMacro       = getFlag( nFlags, BIFF12_DEFNAME_MACRO );
189     maModel.mbFunction    = getFlag( nFlags, BIFF12_DEFNAME_FUNC );
190     maModel.mbVBName      = getFlag( nFlags, BIFF12_DEFNAME_VBNAME );
191     maModel.mbHidden      = getFlag( nFlags, BIFF12_DEFNAME_HIDDEN );
192 
193     // get built-in name index from name
194     if( getFlag( nFlags, BIFF12_DEFNAME_BUILTIN ) )
195         mcBuiltinId = lclGetBuiltinIdFromBaseName( maModel.maName );
196 
197     // store token array data
198     sal_Int64 nRecPos = rStrm.tell();
199     sal_Int32 nFmlaSize = rStrm.readInt32();
200     rStrm.skip( nFmlaSize );
201     sal_Int32 nAddDataSize = rStrm.readInt32();
202     if( !rStrm.isEof() && (nFmlaSize > 0) && (nAddDataSize >= 0) && (rStrm.getRemaining() >= nAddDataSize) )
203     {
204         sal_Int32 nTotalSize = 8 + nFmlaSize + nAddDataSize;
205         mxFormula.reset( new StreamDataSequence );
206         rStrm.seek( nRecPos );
207         rStrm.readData( *mxFormula, nTotalSize );
208     }
209 }
210 
createNameObject(sal_Int32 nIndex)211 void DefinedName::createNameObject( sal_Int32 nIndex )
212 {
213     // do not create names for (macro) functions or VBA procedures
214     // #163146# do not ignore hidden names (may be regular names created by VBA scripts)
215     if( /*maModel.mbHidden ||*/ maModel.mbFunction || maModel.mbVBName )
216         return;
217 
218     // convert original name to final Calc name (TODO: filter invalid characters from model name)
219     maCalcName = isBuiltinName() ? lclGetPrefixedName( mcBuiltinId ) : maModel.maName;
220 
221     // #163146# do not rename sheet-local names by default, this breaks VBA scripts
222 
223     // special flags for this name
224     sal_Int32 nNameFlags = 0;
225     using namespace ::com::sun::star::sheet::NamedRangeFlag;
226     if( !isGlobalName() ) switch( mcBuiltinId )
227     {
228         case BIFF_DEFNAME_CRITERIA:
229         case BIFF_DEFNAME_FILTERDATABASE: nNameFlags = FILTER_CRITERIA;               break;
230         case BIFF_DEFNAME_PRINTAREA:      nNameFlags = PRINT_AREA;                    break;
231         case BIFF_DEFNAME_PRINTTITLES:    nNameFlags = COLUMN_HEADER | ROW_HEADER;    break;
232     }
233 
234     // create the name and insert it into the document, maCalcName will be changed to the resulting name
235     if (maModel.mnSheet >= 0)
236         maScRangeData = createLocalNamedRangeObject( maCalcName, ApiTokenSequence(), nIndex, nNameFlags, maModel.mnSheet, maModel.mbHidden );
237     else
238         maScRangeData = createNamedRangeObject( maCalcName, ApiTokenSequence(), nIndex, nNameFlags, maModel.mbHidden );
239     mnTokenIndex = nIndex;
240 }
241 
getScTokens(const css::uno::Sequence<css::sheet::ExternalLinkInfo> & rExternalLinks)242 std::unique_ptr<ScTokenArray> DefinedName::getScTokens(
243         const css::uno::Sequence<css::sheet::ExternalLinkInfo>& rExternalLinks )
244 {
245     ScCompiler aCompiler(getScDocument(), ScAddress(0, 0, mnCalcSheet), formula::FormulaGrammar::GRAM_OOXML);
246     aCompiler.SetExternalLinks( rExternalLinks);
247     std::unique_ptr<ScTokenArray> pArray(aCompiler.CompileString(maModel.maFormula));
248     // Compile the tokens into RPN once to populate information into tokens
249     // where necessary, e.g. for TableRef inner reference. RPN can be discarded
250     // after, a resulting error must be reset.
251     FormulaError nErr = pArray->GetCodeError();
252     aCompiler.CompileTokenArray();
253     getScDocument().CheckLinkFormulaNeedingCheck( *pArray);
254     pArray->DelRPN();
255     pArray->SetCodeError(nErr);
256 
257     return pArray;
258 }
259 
convertFormula(const css::uno::Sequence<css::sheet::ExternalLinkInfo> & rExternalLinks)260 void DefinedName::convertFormula( const css::uno::Sequence<css::sheet::ExternalLinkInfo>& rExternalLinks )
261 {
262     ScRangeData* pScRangeData = maScRangeData.first;
263     // macro function or vba procedure
264     if (!pScRangeData)
265         return;
266 
267     // convert and set formula of the defined name
268     {
269         std::unique_ptr<ScTokenArray> pTokenArray = getScTokens( rExternalLinks);
270         pScRangeData->SetCode( *pTokenArray );
271     }
272 
273     ScTokenArray* pTokenArray = pScRangeData->GetCode();
274     Sequence< FormulaToken > aFTokenSeq;
275     ScTokenConversion::ConvertToTokenSequence( getScDocument(), aFTokenSeq, *pTokenArray );
276     // set built-in names (print ranges, repeated titles, filter ranges)
277     if( isGlobalName() )
278         return;
279 
280     switch( mcBuiltinId )
281     {
282     case BIFF_DEFNAME_PRINTAREA:
283     {
284         Reference< XPrintAreas > xPrintAreas( getSheetFromDoc( mnCalcSheet ), UNO_QUERY );
285         ScRangeList aPrintRanges;
286         getFormulaParser().extractCellRangeList( aPrintRanges, aFTokenSeq, mnCalcSheet );
287         if( xPrintAreas.is() && !aPrintRanges.empty() )
288             xPrintAreas->setPrintAreas( AddressConverter::toApiSequence(aPrintRanges) );
289     }
290     break;
291     case BIFF_DEFNAME_PRINTTITLES:
292     {
293         Reference< XPrintAreas > xPrintAreas( getSheetFromDoc( mnCalcSheet ), UNO_QUERY );
294         ScRangeList aTitleRanges;
295         getFormulaParser().extractCellRangeList( aTitleRanges, aFTokenSeq, mnCalcSheet );
296         if( xPrintAreas.is() && !aTitleRanges.empty() )
297         {
298             bool bHasRowTitles = false;
299             bool bHasColTitles = false;
300             const ScAddress& rMaxPos = getAddressConverter().getMaxAddress();
301             for (size_t i = 0, nSize = aTitleRanges.size(); i < nSize; ++i)
302             {
303                 const ScRange& rRange = aTitleRanges[i];
304                 bool bFullRow = (rRange.aStart.Col() == 0) && ( rRange.aEnd.Col() >= rMaxPos.Col() );
305                 bool bFullCol = (rRange.aStart.Row() == 0) && ( rRange.aEnd.Row() >= rMaxPos.Row() );
306                 if( !bHasRowTitles && bFullRow && !bFullCol )
307                 {
308                     xPrintAreas->setTitleRows( CellRangeAddress(rRange.aStart.Tab(),
309                                                                 rRange.aStart.Col(), rRange.aStart.Row(),
310                                                                 rRange.aEnd.Col(), rRange.aEnd.Row()) );
311                     xPrintAreas->setPrintTitleRows( true );
312                     bHasRowTitles = true;
313                 }
314                 else if( !bHasColTitles && bFullCol && !bFullRow )
315                 {
316                     xPrintAreas->setTitleColumns( CellRangeAddress(rRange.aStart.Tab(),
317                                                                    rRange.aStart.Col(), rRange.aStart.Row(),
318                                                                    rRange.aEnd.Col(), rRange.aEnd.Row()) );
319                     xPrintAreas->setPrintTitleColumns( true );
320                     bHasColTitles = true;
321                 }
322             }
323         }
324     }
325     break;
326     }
327 }
328 
getAbsoluteRange(ScRange & orRange) const329 bool DefinedName::getAbsoluteRange( ScRange& orRange ) const
330 {
331     ScRangeData* pScRangeData = maScRangeData.first;
332     ScTokenArray* pTokenArray = pScRangeData->GetCode();
333     Sequence< FormulaToken > aFTokenSeq;
334     ScTokenConversion::ConvertToTokenSequence(getScDocument(), aFTokenSeq, *pTokenArray);
335     return getFormulaParser().extractCellRange( orRange, aFTokenSeq );
336 }
337 
~DefinedName()338 DefinedName::~DefinedName()
339 {
340     // this kind of field is owned by us - see lcl_addNewByNameAndTokens
341     bool bOwned = maScRangeData.second;
342     if (bOwned)
343         delete maScRangeData.first;
344 }
345 
DefinedNamesBuffer(const WorkbookHelper & rHelper)346 DefinedNamesBuffer::DefinedNamesBuffer( const WorkbookHelper& rHelper ) :
347     WorkbookHelper( rHelper )
348 {
349 }
350 
importDefinedName(const AttributeList & rAttribs)351 DefinedNameRef DefinedNamesBuffer::importDefinedName( const AttributeList& rAttribs )
352 {
353     DefinedNameRef xDefName = createDefinedName();
354     xDefName->importDefinedName( rAttribs );
355     return xDefName;
356 }
357 
importDefinedName(SequenceInputStream & rStrm)358 void DefinedNamesBuffer::importDefinedName( SequenceInputStream& rStrm )
359 {
360     createDefinedName()->importDefinedName( rStrm );
361 }
362 
finalizeImport()363 void DefinedNamesBuffer::finalizeImport()
364 {
365     // first insert all names without formula definition into the document, and insert them into the maps
366     int index = 0;
367     for( DefinedNameRef& xDefName : maDefNames )
368     {
369         xDefName->createNameObject( ++index );
370         // map by sheet index and original model name
371         maModelNameMap[ SheetNameKey( xDefName->getLocalCalcSheet(), xDefName->getUpcaseModelName() ) ] = xDefName;
372         // map by sheet index and built-in identifier
373         if( !xDefName->isGlobalName() && xDefName->isBuiltinName() )
374             maBuiltinMap[ BuiltinKey( xDefName->getLocalCalcSheet(), xDefName->getBuiltinId() ) ] = xDefName;
375         // map by API formula token identifier
376         sal_Int32 nTokenIndex = xDefName->getTokenIndex();
377         if( nTokenIndex >= 0 )
378             maTokenIdMap[ nTokenIndex ] = xDefName;
379     }
380 
381     /*  Now convert all name formulas, so that the formula parser can find all
382         names in case of circular dependencies. */
383     maDefNames.forEachMem( &DefinedName::convertFormula, getExternalLinks().getLinkInfos());
384 }
385 
getByIndex(sal_Int32 nIndex) const386 DefinedNameRef DefinedNamesBuffer::getByIndex( sal_Int32 nIndex ) const
387 {
388     return maDefNames.get( nIndex );
389 }
390 
getByTokenIndex(sal_Int32 nIndex) const391 DefinedNameRef DefinedNamesBuffer::getByTokenIndex( sal_Int32 nIndex ) const
392 {
393     return maTokenIdMap.get( nIndex );
394 }
395 
getByModelName(const OUString & rModelName,sal_Int16 nCalcSheet) const396 DefinedNameRef DefinedNamesBuffer::getByModelName( const OUString& rModelName, sal_Int16 nCalcSheet ) const
397 {
398     OUString aUpcaseName = lclGetUpcaseModelName( rModelName );
399     DefinedNameRef xDefName = maModelNameMap.get( SheetNameKey( nCalcSheet, aUpcaseName ) );
400     // lookup global name, if no local name exists
401     if( !xDefName && (nCalcSheet >= 0) )
402         xDefName = maModelNameMap.get( SheetNameKey( -1, aUpcaseName ) );
403     return xDefName;
404 }
405 
getByBuiltinId(sal_Unicode cBuiltinId,sal_Int16 nCalcSheet) const406 DefinedNameRef DefinedNamesBuffer::getByBuiltinId( sal_Unicode cBuiltinId, sal_Int16 nCalcSheet ) const
407 {
408     return maBuiltinMap.get( BuiltinKey( nCalcSheet, cBuiltinId ) );
409 }
410 
createDefinedName()411 DefinedNameRef DefinedNamesBuffer::createDefinedName()
412 {
413     DefinedNameRef xDefName = std::make_shared<DefinedName>( *this );
414     maDefNames.push_back( xDefName );
415     return xDefName;
416 }
417 
418 } // namespace oox::xls
419 
420 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
421