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 .
21 #include <defnamesbuffer.hxx>
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 <o3tl/string_view.hxx>
27 #include <osl/diagnose.h>
28 #include <rtl/ustrbuf.hxx>
29 #include <oox/helper/binaryinputstream.hxx>
30 #include <oox/helper/attributelist.hxx>
31 #include <oox/token/tokens.hxx>
32 #include <addressconverter.hxx>
33 #include <biffhelper.hxx>
34 #include <externallinkbuffer.hxx>
35 #include <formulabase.hxx>
36 #include <formulaparser.hxx>
37 #include <worksheetbuffer.hxx>
38 #include <tokenarray.hxx>
39 #include <tokenuno.hxx>
40 #include <compiler.hxx>
41 #include <document.hxx>
45 using namespace ::com::sun::star::sheet
;
46 using namespace ::com::sun::star::table
;
47 using namespace ::com::sun::star::uno
;
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 constexpr OUString
spcOoxPrefix(u
"_xlnm."_ustr
);
59 const char* const sppcBaseNames
[] =
77 OUString
lclGetBaseName( sal_Unicode cBuiltinId
)
79 OSL_ENSURE( cBuiltinId
< SAL_N_ELEMENTS( sppcBaseNames
), "lclGetBaseName - unsupported built-in identifier" );
80 OUStringBuffer aBuffer
;
81 if( cBuiltinId
< SAL_N_ELEMENTS( sppcBaseNames
) )
82 aBuffer
.appendAscii( sppcBaseNames
[ cBuiltinId
] );
84 aBuffer
.append( static_cast< sal_Int32
>( cBuiltinId
) );
85 return aBuffer
.makeStringAndClear();
88 OUString
lclGetPrefixedName( sal_Unicode cBuiltinId
)
90 return spcOoxPrefix
+ lclGetBaseName( cBuiltinId
);
93 /** returns the built-in name identifier from a prefixed built-in name, e.g. '_xlnm.Print_Area'. */
94 sal_Unicode
lclGetBuiltinIdFromPrefixedName( std::u16string_view aModelName
)
96 if( o3tl::matchIgnoreAsciiCase( aModelName
, spcOoxPrefix
) )
98 for( sal_Unicode cBuiltinId
= 0; cBuiltinId
< SAL_N_ELEMENTS( sppcBaseNames
); ++cBuiltinId
)
100 OUString aBaseName
= lclGetBaseName( cBuiltinId
);
101 sal_Int32 nBaseNameLen
= aBaseName
.getLength();
102 if( (sal_Int32(aModelName
.size()) == spcOoxPrefix
.getLength() + nBaseNameLen
) && o3tl::matchIgnoreAsciiCase( aModelName
, aBaseName
, spcOoxPrefix
.getLength() ) )
106 return BIFF_DEFNAME_UNKNOWN
;
109 /** returns the built-in name identifier from a built-in base name, e.g. 'Print_Area'. */
110 sal_Unicode
lclGetBuiltinIdFromBaseName( std::u16string_view rModelName
)
112 for( sal_Unicode cBuiltinId
= 0; cBuiltinId
< SAL_N_ELEMENTS( sppcBaseNames
); ++cBuiltinId
)
113 if( o3tl::equalsIgnoreAsciiCase( rModelName
, sppcBaseNames
[ cBuiltinId
] ) )
115 return BIFF_DEFNAME_UNKNOWN
;
118 OUString
lclGetUpcaseModelName( const OUString
& rModelName
)
121 return rModelName
.toAsciiUpperCase();
126 DefinedNameModel::DefinedNameModel() :
136 DefinedNameBase::DefinedNameBase( const WorkbookHelper
& rHelper
) :
137 WorkbookHelper( rHelper
)
141 const OUString
& DefinedNameBase::getUpcaseModelName() const
143 if( maUpModelName
.isEmpty() )
144 maUpModelName
= lclGetUpcaseModelName( maModel
.maName
);
145 return maUpModelName
;
148 DefinedName::DefinedName( const WorkbookHelper
& rHelper
) :
149 DefinedNameBase( rHelper
),
150 maScRangeData(nullptr, false),
153 mcBuiltinId( BIFF_DEFNAME_UNKNOWN
)
157 void DefinedName::importDefinedName( const AttributeList
& rAttribs
)
159 maModel
.maName
= rAttribs
.getXString( XML_name
, OUString() );
160 maModel
.mnSheet
= rAttribs
.getInteger( XML_localSheetId
, -1 );
161 maModel
.mnFuncGroupId
= rAttribs
.getInteger( XML_functionGroupId
, -1 );
162 maModel
.mbMacro
= rAttribs
.getBool( XML_xlm
, false );
163 maModel
.mbFunction
= rAttribs
.getBool( XML_function
, false );
164 maModel
.mbVBName
= rAttribs
.getBool( XML_vbProcedure
, false );
165 maModel
.mbHidden
= rAttribs
.getBool( XML_hidden
, false );
166 mnCalcSheet
= (maModel
.mnSheet
>= 0) ? getWorksheets().getCalcSheetIndex( maModel
.mnSheet
) : -1;
168 /* Detect built-in state from name itself, there is no built-in flag.
169 Built-in names are prefixed with '_xlnm.' instead. */
170 mcBuiltinId
= lclGetBuiltinIdFromPrefixedName( maModel
.maName
);
173 void DefinedName::setFormula( const OUString
& rFormula
)
175 maModel
.maFormula
= rFormula
;
178 void DefinedName::importDefinedName( SequenceInputStream
& rStrm
)
181 nFlags
= rStrm
.readuInt32();
182 rStrm
.skip( 1 ); // keyboard shortcut
183 maModel
.mnSheet
= rStrm
.readInt32();
184 rStrm
>> maModel
.maName
;
185 mnCalcSheet
= (maModel
.mnSheet
>= 0) ? getWorksheets().getCalcSheetIndex( maModel
.mnSheet
) : -1;
187 // macro function/command, hidden flag
188 maModel
.mnFuncGroupId
= extractValue
< sal_Int32
>( nFlags
, 6, 9 );
189 maModel
.mbMacro
= getFlag( nFlags
, BIFF12_DEFNAME_MACRO
);
190 maModel
.mbFunction
= getFlag( nFlags
, BIFF12_DEFNAME_FUNC
);
191 maModel
.mbVBName
= getFlag( nFlags
, BIFF12_DEFNAME_VBNAME
);
192 maModel
.mbHidden
= getFlag( nFlags
, BIFF12_DEFNAME_HIDDEN
);
194 // get built-in name index from name
195 if( getFlag( nFlags
, BIFF12_DEFNAME_BUILTIN
) )
196 mcBuiltinId
= lclGetBuiltinIdFromBaseName( maModel
.maName
);
198 // store token array data
199 sal_Int64 nRecPos
= rStrm
.tell();
200 sal_Int32 nFmlaSize
= rStrm
.readInt32();
201 rStrm
.skip( nFmlaSize
);
202 sal_Int32 nAddDataSize
= rStrm
.readInt32();
203 if( !rStrm
.isEof() && (nFmlaSize
> 0) && (nAddDataSize
>= 0) && (rStrm
.getRemaining() >= nAddDataSize
) )
205 sal_Int32 nTotalSize
= 8 + nFmlaSize
+ nAddDataSize
;
206 mxFormula
.reset( new StreamDataSequence
);
207 rStrm
.seek( nRecPos
);
208 rStrm
.readData( *mxFormula
, nTotalSize
);
212 void DefinedName::createNameObject( sal_Int32 nIndex
)
214 // do not create names for (macro) functions or VBA procedures
215 // #163146# do not ignore hidden names (may be regular names created by VBA scripts)
216 if( /*maModel.mbHidden ||*/ maModel
.mbFunction
|| maModel
.mbVBName
)
219 // convert original name to final Calc name (TODO: filter invalid characters from model name)
220 maCalcName
= isBuiltinName() ? lclGetPrefixedName( mcBuiltinId
) : maModel
.maName
;
222 // #163146# do not rename sheet-local names by default, this breaks VBA scripts
224 // special flags for this name
225 sal_Int32 nNameFlags
= 0;
226 using namespace ::com::sun::star::sheet
;
227 if( !isGlobalName() ) switch( mcBuiltinId
)
229 case BIFF_DEFNAME_CRITERIA
:
230 case BIFF_DEFNAME_FILTERDATABASE
:
231 nNameFlags
= NamedRangeFlag::FILTER_CRITERIA
;
233 case BIFF_DEFNAME_PRINTAREA
:
234 nNameFlags
= NamedRangeFlag::PRINT_AREA
;
236 case BIFF_DEFNAME_PRINTTITLES
:
237 nNameFlags
= NamedRangeFlag::COLUMN_HEADER
| NamedRangeFlag::ROW_HEADER
;
241 // Set the appropriate flag if it is a hidden named range
242 if (maModel
.mbHidden
)
243 nNameFlags
|= NamedRangeFlag::HIDDEN
;
245 // create the name and insert it into the document, maCalcName will be changed to the resulting name
246 if (maModel
.mnSheet
>= 0)
247 maScRangeData
= createLocalNamedRangeObject(maCalcName
, nIndex
, nNameFlags
, maModel
.mnSheet
);
249 maScRangeData
= createNamedRangeObject( maCalcName
, nIndex
, nNameFlags
);
250 mnTokenIndex
= nIndex
;
253 bool DefinedName::isValid(
254 const css::uno::Sequence
<css::sheet::ExternalLinkInfo
>& rExternalLinks
) const
257 OUString aExternDocName
;
258 OUString aStartTabName
;
259 OUString aEndTabName
;
260 ScRefFlags nFlags
= ScRefFlags::VALID
| ScRefFlags::TAB_VALID
;
261 aRange
.Parse_XL_Header(maModel
.maFormula
.getStr(), getScDocument(), aExternDocName
,
262 aStartTabName
, aEndTabName
, nFlags
, /*bOnlyAcceptSingle=*/false,
264 // aExternDocName is something like 'file:///path/to/my.xlsx' in the valid case, and it's an int
265 // when it's invalid.
266 bool bInvalidExternalRef
= aExternDocName
.toInt32() > 0;
267 return !bInvalidExternalRef
;
270 std::unique_ptr
<ScTokenArray
> DefinedName::getScTokens(
271 const css::uno::Sequence
<css::sheet::ExternalLinkInfo
>& rExternalLinks
)
273 ScCompiler
aCompiler(getScDocument(), ScAddress(0, 0, mnCalcSheet
), formula::FormulaGrammar::GRAM_OOXML
);
274 aCompiler
.SetExternalLinks( rExternalLinks
);
275 std::unique_ptr
<ScTokenArray
> pArray(aCompiler
.CompileString(maModel
.maFormula
));
276 // Compile the tokens into RPN once to populate information into tokens
277 // where necessary, e.g. for TableRef inner reference. RPN can be discarded
278 // after, a resulting error must be reset.
279 FormulaError nErr
= pArray
->GetCodeError();
280 aCompiler
.CompileTokenArray();
281 getScDocument().CheckLinkFormulaNeedingCheck( *pArray
);
283 pArray
->SetCodeError(nErr
);
288 void DefinedName::convertFormula( const css::uno::Sequence
<css::sheet::ExternalLinkInfo
>& rExternalLinks
)
290 ScRangeData
* pScRangeData
= maScRangeData
.first
;
291 // macro function or vba procedure
295 // convert and set formula of the defined name
297 std::unique_ptr
<ScTokenArray
> pTokenArray
= getScTokens( rExternalLinks
);
298 pScRangeData
->SetCode( *pTokenArray
);
301 ScTokenArray
* pTokenArray
= pScRangeData
->GetCode();
302 /* TODO: conversion to FormulaToken sequence would be completely
303 * unnecessary if getFormulaParser().extractCellRangeList() could operate
304 * on ScTokenArray instead. */
305 Sequence
< FormulaToken
> aFTokenSeq
;
306 ScTokenConversion::ConvertToTokenSequence( getScDocument(), aFTokenSeq
, *pTokenArray
, true);
307 // set built-in names (print ranges, repeated titles, filter ranges)
311 switch( mcBuiltinId
)
313 case BIFF_DEFNAME_PRINTAREA
:
315 Reference
< XPrintAreas
> xPrintAreas( getSheetFromDoc( mnCalcSheet
), UNO_QUERY
);
316 ScRangeList aPrintRanges
;
317 getFormulaParser().extractCellRangeList( aPrintRanges
, aFTokenSeq
, mnCalcSheet
);
318 if( xPrintAreas
.is() && !aPrintRanges
.empty() )
319 xPrintAreas
->setPrintAreas( AddressConverter::toApiSequence(aPrintRanges
) );
322 case BIFF_DEFNAME_PRINTTITLES
:
324 Reference
< XPrintAreas
> xPrintAreas( getSheetFromDoc( mnCalcSheet
), UNO_QUERY
);
325 ScRangeList aTitleRanges
;
326 getFormulaParser().extractCellRangeList( aTitleRanges
, aFTokenSeq
, mnCalcSheet
);
327 if( xPrintAreas
.is() && !aTitleRanges
.empty() )
329 bool bHasRowTitles
= false;
330 bool bHasColTitles
= false;
331 const ScAddress
& rMaxPos
= getAddressConverter().getMaxAddress();
332 for (size_t i
= 0, nSize
= aTitleRanges
.size(); i
< nSize
; ++i
)
334 const ScRange
& rRange
= aTitleRanges
[i
];
335 bool bFullRow
= (rRange
.aStart
.Col() == 0) && ( rRange
.aEnd
.Col() >= rMaxPos
.Col() );
336 bool bFullCol
= (rRange
.aStart
.Row() == 0) && ( rRange
.aEnd
.Row() >= rMaxPos
.Row() );
337 if( !bHasRowTitles
&& bFullRow
&& !bFullCol
)
339 xPrintAreas
->setTitleRows( CellRangeAddress(rRange
.aStart
.Tab(),
340 rRange
.aStart
.Col(), rRange
.aStart
.Row(),
341 rRange
.aEnd
.Col(), rRange
.aEnd
.Row()) );
342 xPrintAreas
->setPrintTitleRows( true );
343 bHasRowTitles
= true;
345 else if( !bHasColTitles
&& bFullCol
&& !bFullRow
)
347 xPrintAreas
->setTitleColumns( CellRangeAddress(rRange
.aStart
.Tab(),
348 rRange
.aStart
.Col(), rRange
.aStart
.Row(),
349 rRange
.aEnd
.Col(), rRange
.aEnd
.Row()) );
350 xPrintAreas
->setPrintTitleColumns( true );
351 bHasColTitles
= true;
360 bool DefinedName::getAbsoluteRange( ScRange
& orRange
) const
362 ScRangeData
* pScRangeData
= maScRangeData
.first
;
363 ScTokenArray
* pTokenArray
= pScRangeData
->GetCode();
364 /* TODO: conversion to FormulaToken sequence would be completely
365 * unnecessary if getFormulaParser().extractCellRange() could operate
366 * on ScTokenArray instead. */
367 Sequence
< FormulaToken
> aFTokenSeq
;
368 ScTokenConversion::ConvertToTokenSequence(getScDocument(), aFTokenSeq
, *pTokenArray
, true);
369 return getFormulaParser().extractCellRange( orRange
, aFTokenSeq
);
372 DefinedName::~DefinedName()
374 // this kind of field is owned by us - see lcl_addNewByNameAndTokens
375 bool bOwned
= maScRangeData
.second
;
377 delete maScRangeData
.first
;
380 DefinedNamesBuffer::DefinedNamesBuffer( const WorkbookHelper
& rHelper
) :
381 WorkbookHelper( rHelper
)
385 DefinedNameRef
DefinedNamesBuffer::importDefinedName( const AttributeList
& rAttribs
)
387 DefinedNameRef xDefName
= createDefinedName();
388 xDefName
->importDefinedName( rAttribs
);
392 void DefinedNamesBuffer::importDefinedName( SequenceInputStream
& rStrm
)
394 createDefinedName()->importDefinedName( rStrm
);
397 void DefinedNamesBuffer::finalizeImport()
399 // first insert all names without formula definition into the document, and insert them into the maps
401 for( DefinedNameRef
& xDefName
: maDefNames
)
403 if (!xDefName
->isValid(getExternalLinks().getLinkInfos()))
408 xDefName
->createNameObject( ++index
);
409 // map by sheet index and original model name
410 maModelNameMap
[ SheetNameKey( xDefName
->getLocalCalcSheet(), xDefName
->getUpcaseModelName() ) ] = xDefName
;
411 // map by sheet index and built-in identifier
412 if( !xDefName
->isGlobalName() && xDefName
->isBuiltinName() )
413 maBuiltinMap
[ BuiltinKey( xDefName
->getLocalCalcSheet(), xDefName
->getBuiltinId() ) ] = xDefName
;
414 // map by API formula token identifier
415 sal_Int32 nTokenIndex
= xDefName
->getTokenIndex();
416 if( nTokenIndex
>= 0 )
417 maTokenIdMap
[ nTokenIndex
] = xDefName
;
420 /* Now convert all name formulas, so that the formula parser can find all
421 names in case of circular dependencies. */
422 maDefNames
.forEachMem( &DefinedName::convertFormula
, getExternalLinks().getLinkInfos());
425 DefinedNameRef
DefinedNamesBuffer::getByIndex( sal_Int32 nIndex
) const
427 return maDefNames
.get( nIndex
);
430 DefinedNameRef
DefinedNamesBuffer::getByTokenIndex( sal_Int32 nIndex
) const
432 return maTokenIdMap
.get( nIndex
);
435 DefinedNameRef
DefinedNamesBuffer::getByModelName( const OUString
& rModelName
, sal_Int16 nCalcSheet
) const
437 OUString aUpcaseName
= lclGetUpcaseModelName( rModelName
);
438 DefinedNameRef xDefName
= maModelNameMap
.get( SheetNameKey( nCalcSheet
, aUpcaseName
) );
439 // lookup global name, if no local name exists
440 if( !xDefName
&& (nCalcSheet
>= 0) )
441 xDefName
= maModelNameMap
.get( SheetNameKey( -1, aUpcaseName
) );
445 DefinedNameRef
DefinedNamesBuffer::getByBuiltinId( sal_Unicode cBuiltinId
, sal_Int16 nCalcSheet
) const
447 return maBuiltinMap
.get( BuiltinKey( nCalcSheet
, cBuiltinId
) );
450 DefinedNameRef
DefinedNamesBuffer::createDefinedName()
452 DefinedNameRef xDefName
= std::make_shared
<DefinedName
>( *this );
453 maDefNames
.push_back( xDefName
);
457 } // namespace oox::xls
459 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */