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