LibreOffice Module sc (master)  1
defnamesbuffer.cxx
Go to the documentation of this file.
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>
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 
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 
87 OUString lclGetPrefixedName( sal_Unicode cBuiltinId )
88 {
89  return spcOoxPrefix + lclGetBaseName( cBuiltinId );
90 }
91 
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 
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 
117 OUString lclGetUpcaseModelName( const OUString& rModelName )
118 {
119  // TODO: i18n?
120  return rModelName.toAsciiUpperCase();
121 }
122 
123 } // namespace
124 
126  mnSheet( -1 ),
127  mnFuncGroupId( -1 ),
128  mbMacro( false ),
129  mbFunction( false ),
130  mbVBName( false ),
131  mbHidden( false )
132 {
133 }
134 
136  WorkbookHelper( rHelper )
137 {
138 }
139 
140 const OUString& DefinedNameBase::getUpcaseModelName() const
141 {
142  if( maUpModelName.isEmpty() )
143  maUpModelName = lclGetUpcaseModelName( maModel.maName );
144  return maUpModelName;
145 }
146 
148  DefinedNameBase( rHelper ),
149  mpScRangeData(nullptr),
150  mnTokenIndex( -1 ),
151  mnCalcSheet( 0 ),
152  mcBuiltinId( BIFF_DEFNAME_UNKNOWN )
153 {
154 }
155 
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 );
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 
172 void DefinedName::setFormula( const OUString& rFormula )
173 {
174  maModel.maFormula = rFormula;
175 }
176 
178 {
179  sal_uInt32 nFlags;
180  nFlags = rStrm.readuInt32();
181  rStrm.skip( 1 ); // keyboard shortcut
182  maModel.mnSheet = rStrm.readInt32();
183  rStrm >> maModel.maName;
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 
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: nNameFlags = FILTER_CRITERIA; break;
229  case BIFF_DEFNAME_PRINTAREA: nNameFlags = PRINT_AREA; break;
230  case BIFF_DEFNAME_PRINTTITLES: nNameFlags = COLUMN_HEADER | ROW_HEADER; break;
231  }
232 
233  // create the name and insert it into the document, maCalcName will be changed to the resulting name
234  if (maModel.mnSheet >= 0)
236  else
238  mnTokenIndex = nIndex;
239 }
240 
241 std::unique_ptr<ScTokenArray> DefinedName::getScTokens(
242  const css::uno::Sequence<css::sheet::ExternalLinkInfo>& rExternalLinks )
243 {
245  aCompiler.SetExternalLinks( rExternalLinks);
246  std::unique_ptr<ScTokenArray> pArray(aCompiler.CompileString(maModel.maFormula));
247  // Compile the tokens into RPN once to populate information into tokens
248  // where necessary, e.g. for TableRef inner reference. RPN can be discarded
249  // after, a resulting error must be reset.
250  FormulaError nErr = pArray->GetCodeError();
251  aCompiler.CompileTokenArray();
253  pArray->DelRPN();
254  pArray->SetCodeError(nErr);
255 
256  return pArray;
257 }
258 
259 void DefinedName::convertFormula( const css::uno::Sequence<css::sheet::ExternalLinkInfo>& rExternalLinks )
260 {
261  // macro function or vba procedure
262  if(!mpScRangeData)
263  return;
264 
265  // convert and set formula of the defined name
266  {
267  std::unique_ptr<ScTokenArray> pTokenArray = getScTokens( rExternalLinks);
268  mpScRangeData->SetCode( *pTokenArray );
269  }
270 
271  ScTokenArray* pTokenArray = mpScRangeData->GetCode();
272  Sequence< FormulaToken > aFTokenSeq;
273  ScTokenConversion::ConvertToTokenSequence( getScDocument(), aFTokenSeq, *pTokenArray );
274  // set built-in names (print ranges, repeated titles, filter ranges)
275  if( isGlobalName() )
276  return;
277 
278  switch( mcBuiltinId )
279  {
281  {
282  Reference< XPrintAreas > xPrintAreas( getSheetFromDoc( mnCalcSheet ), UNO_QUERY );
283  ScRangeList aPrintRanges;
284  getFormulaParser().extractCellRangeList( aPrintRanges, aFTokenSeq, mnCalcSheet );
285  if( xPrintAreas.is() && !aPrintRanges.empty() )
286  xPrintAreas->setPrintAreas( AddressConverter::toApiSequence(aPrintRanges) );
287  }
288  break;
290  {
291  Reference< XPrintAreas > xPrintAreas( getSheetFromDoc( mnCalcSheet ), UNO_QUERY );
292  ScRangeList aTitleRanges;
293  getFormulaParser().extractCellRangeList( aTitleRanges, aFTokenSeq, mnCalcSheet );
294  if( xPrintAreas.is() && !aTitleRanges.empty() )
295  {
296  bool bHasRowTitles = false;
297  bool bHasColTitles = false;
298  const ScAddress& rMaxPos = getAddressConverter().getMaxAddress();
299  for (size_t i = 0, nSize = aTitleRanges.size(); i < nSize; ++i)
300  {
301  const ScRange& rRange = aTitleRanges[i];
302  bool bFullRow = (rRange.aStart.Col() == 0) && ( rRange.aEnd.Col() >= rMaxPos.Col() );
303  bool bFullCol = (rRange.aStart.Row() == 0) && ( rRange.aEnd.Row() >= rMaxPos.Row() );
304  if( !bHasRowTitles && bFullRow && !bFullCol )
305  {
306  xPrintAreas->setTitleRows( CellRangeAddress(rRange.aStart.Tab(),
307  rRange.aStart.Col(), rRange.aStart.Row(),
308  rRange.aEnd.Col(), rRange.aEnd.Row()) );
309  xPrintAreas->setPrintTitleRows( true );
310  bHasRowTitles = true;
311  }
312  else if( !bHasColTitles && bFullCol && !bFullRow )
313  {
314  xPrintAreas->setTitleColumns( CellRangeAddress(rRange.aStart.Tab(),
315  rRange.aStart.Col(), rRange.aStart.Row(),
316  rRange.aEnd.Col(), rRange.aEnd.Row()) );
317  xPrintAreas->setPrintTitleColumns( true );
318  bHasColTitles = true;
319  }
320  }
321  }
322  }
323  break;
324  }
325 }
326 
328 {
329  ScTokenArray* pTokenArray = mpScRangeData->GetCode();
330  Sequence< FormulaToken > aFTokenSeq;
331  ScTokenConversion::ConvertToTokenSequence(getScDocument(), aFTokenSeq, *pTokenArray);
332  return getFormulaParser().extractCellRange( orRange, aFTokenSeq );
333 }
334 
335 
337  WorkbookHelper( rHelper )
338 {
339 }
340 
342 {
343  DefinedNameRef xDefName = createDefinedName();
344  xDefName->importDefinedName( rAttribs );
345  return xDefName;
346 }
347 
349 {
350  createDefinedName()->importDefinedName( rStrm );
351 }
352 
354 {
355  // first insert all names without formula definition into the document, and insert them into the maps
356  int index = 0;
357  for( DefinedNameRef& xDefName : maDefNames )
358  {
359  xDefName->createNameObject( ++index );
360  // map by sheet index and original model name
361  maModelNameMap[ SheetNameKey( xDefName->getLocalCalcSheet(), xDefName->getUpcaseModelName() ) ] = xDefName;
362  // map by sheet index and built-in identifier
363  if( !xDefName->isGlobalName() && xDefName->isBuiltinName() )
364  maBuiltinMap[ BuiltinKey( xDefName->getLocalCalcSheet(), xDefName->getBuiltinId() ) ] = xDefName;
365  // map by API formula token identifier
366  sal_Int32 nTokenIndex = xDefName->getTokenIndex();
367  if( nTokenIndex >= 0 )
368  maTokenIdMap[ nTokenIndex ] = xDefName;
369  }
370 
371  /* Now convert all name formulas, so that the formula parser can find all
372  names in case of circular dependencies. */
373  maDefNames.forEachMem( &DefinedName::convertFormula, getExternalLinks().getLinkInfos());
374 }
375 
377 {
378  return maDefNames.get( nIndex );
379 }
380 
382 {
383  return maTokenIdMap.get( nIndex );
384 }
385 
386 DefinedNameRef DefinedNamesBuffer::getByModelName( const OUString& rModelName, sal_Int16 nCalcSheet ) const
387 {
388  OUString aUpcaseName = lclGetUpcaseModelName( rModelName );
389  DefinedNameRef xDefName = maModelNameMap.get( SheetNameKey( nCalcSheet, aUpcaseName ) );
390  // lookup global name, if no local name exists
391  if( !xDefName && (nCalcSheet >= 0) )
392  xDefName = maModelNameMap.get( SheetNameKey( -1, aUpcaseName ) );
393  return xDefName;
394 }
395 
396 DefinedNameRef DefinedNamesBuffer::getByBuiltinId( sal_Unicode cBuiltinId, sal_Int16 nCalcSheet ) const
397 {
398  return maBuiltinMap.get( BuiltinKey( nCalcSheet, cBuiltinId ) );
399 }
400 
402 {
403  DefinedNameRef xDefName = std::make_shared<DefinedName>( *this );
404  maDefNames.push_back( xDefName );
405  return xDefName;
406 }
407 
408 } // namespace oox::xls
409 
410 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
static css::uno::Sequence< css::table::CellRangeAddress > toApiSequence(const ScRangeList &orRanges)
Converts the passed range list to a sequence of cell range addresses.
Helper class to provide access to global workbook data.
DefinedNameRef importDefinedName(const AttributeList &rAttribs)
Imports a defined name from the passed attribute set.
sal_Int16 mnCalcSheet
Name index used in API token array.
ScAddress aStart
Definition: address.hxx:500
virtual void skip(sal_Int32 nBytes, size_t nAtomSize=1) override
OptValue< bool > getBool(sal_Int32 nAttrToken) const
SCROW Row() const
Definition: address.hxx:262
ScRangeData * mpScRangeData
SC_DLLPUBLIC void SetCode(const ScTokenArray &)
Definition: rangenam.cxx:605
virtual sal_Int32 readData(StreamDataSequence &orData, sal_Int32 nBytes, size_t nAtomSize=1) override
OptValue< OUString > getXString(sal_Int32 nAttrToken) const
const sal_Unicode BIFF_DEFNAME_PRINTTITLES
OptValue< sal_Int32 > getInteger(sal_Int32 nAttrToken) const
DefinedNameBase(const WorkbookHelper &rHelper)
sal_Int32 mnSheet
The formula string.
void finalizeImport()
Creates all defined names in the document.
std::unique_ptr< ScTokenArray > CompileString(const OUString &rFormula)
Tokenize formula expression string into an array of tokens.
Definition: compiler.cxx:4535
DefinedNameRef getByModelName(const OUString &rModelName, sal_Int16 nCalcSheet=-1) const
Returns a defined name by its model name.
value_type get(sal_Int32 nIndex) const
ScAddress aEnd
Definition: address.hxx:501
void SetExternalLinks(const css::uno::Sequence< css::sheet::ExternalLinkInfo > &rLinks)
Set external link info for ScAddress::CONV_XL_OOX.
Definition: compiler.hxx:441
OUString maCalcName
Model name converted to uppercase ASCII.
sal_uInt32 readuInt32()
OUString maUpModelName
Model data for this defined name.
const sal_Unicode BIFF_DEFNAME_CRITERIA
DefinedNameRef getByTokenIndex(sal_Int32 nIndex) const
Returns a defined name by token index (index in XDefinedNames container).
::std::pair< sal_Int16, OUString > SheetNameKey
OUString maFormula
The original name.
Base class for defined names and external names.
bool isBuiltinName() const
Returns true, if this defined name is a special builtin name.
sal_uInt16 sal_Unicode
StreamDataSeqPtr mxFormula
Identifier for built-in defined names.
SC_DLLPUBLIC void CheckLinkFormulaNeedingCheck(const ScTokenArray &rCode)
Check token array and set link check if ocDde/ocWebservice is contained.
Definition: documen8.cxx:1152
DefinedNameRef createDefinedName()
SCTAB Tab() const
Definition: address.hxx:271
DefinedNameModel()
True = name hidden in UI.
void convertFormula(const css::uno::Sequence< css::sheet::ExternalLinkInfo > &rExternalLinks)
Converts the formula string or BIFF token array for this defined name.
bool getFlag(Type nBitField, Type nMask)
bool empty() const
Definition: rangelst.hxx:89
#define SAL_N_ELEMENTS(arr)
virtual sal_Int64 tell() const override
void createNameObject(sal_Int32 nIndex)
Creates a defined name in the Calc document.
ScRangeData * createLocalNamedRangeObject(OUString &orName, const css::uno::Sequence< css::sheet::FormulaToken > &rTokens, sal_Int32 nIndex, sal_Int32 nNameFlags, sal_Int32 nTab) const
Creates and returns a defined name on-the-fly in the sheet.
css::uno::Sequence< sal_Int8 > StreamDataSequence
bool extractCellRange(ScRange &orRange, const ApiTokenSequence &rTokens) const
Tries to extract a cell range address from a formula token sequence.
int i
::std::pair< sal_Int16, sal_Unicode > BuiltinKey
ExternalLinkBuffer & getExternalLinks() const
Returns the external links read from the external links substream.
size_t size() const
Definition: rangelst.hxx:90
static SC_DLLPUBLIC void ConvertToTokenSequence(const ScDocument &rDoc, css::uno::Sequence< css::sheet::FormulaToken > &rSequence, const ScTokenArray &rTokenArray)
Definition: tokenuno.cxx:354
ScRangeData * createNamedRangeObject(OUString &orName, const css::uno::Sequence< css::sheet::FormulaToken > &rTokens, sal_Int32 nIndex, sal_Int32 nNameFlags) const
Creates and returns a defined name on-the-fly in the Calc document.
tuple index
bool mbVBName
True = function, false = command.
FormulaError
SCCOL Col() const
Definition: address.hxx:267
DefinedNamesBuffer(const WorkbookHelper &rHelper)
DefinedName(const WorkbookHelper &rHelper)
bool mbMacro
Function group identifier.
css::uno::Sequence< ApiToken > ApiTokenSequence
bool isGlobalName() const
Returns true, if this defined name is global in the document.
sal_Int16 getCalcSheetIndex(sal_Int32 nWorksheet) const
Returns the Calc index of the specified worksheet.
const ScAddress & getMaxAddress() const
Returns the biggest valid cell address in both Calc and the imported/exported Excel document...
std::unique_ptr< char[]> aBuffer
void extractCellRangeList(ScRangeList &orRanges, const ApiTokenSequence &rTokens, sal_Int32 nFilterBySheet) const
Tries to extract a cell range list from a formula token sequence.
sal_Int64 getRemaining() const
mapped_type get(key_type nKey) const
virtual void seek(sal_Int64 nPos) override
std::shared_ptr< DefinedName > DefinedNameRef
DefNameNameMap maModelNameMap
List of all defined names in insertion order.
const sal_Unicode BIFF_DEFNAME_UNKNOWN
const sal_Unicode BIFF_DEFNAME_PRINTAREA
bool mbHidden
True = VBA macro, false = sheet macro.
sal_Int32 mnFuncGroupId
Sheet index for local names.
std::unique_ptr< ScTokenArray > getScTokens(const css::uno::Sequence< css::sheet::ExternalLinkInfo > &rExternalLinks)
DefNameBuiltinMap maBuiltinMap
Maps all defined names by sheet index and model name.
void importDefinedName(const AttributeList &rAttribs)
Sets the attributes for this defined name from the passed attribute set.
const OUString & getUpcaseModelName() const
Returns the original name as imported from or exported to the file.
DefinedNameRef getByBuiltinId(sal_Unicode cBuiltinId, sal_Int16 nCalcSheet) const
Returns a built-in defined name by its built-in identifier.
sal_Int32 mnTokenIndex
ScRangeData of the defined name.
FormulaParser & getFormulaParser() const
Returns a shared import formula parser (import filter only!).
DefNameTokenIdMap maTokenIdMap
Maps all defined names by sheet index and built-in identifier.
css::uno::Reference< css::sheet::XSpreadsheet > getSheetFromDoc(sal_Int32 nSheet) const
Returns a reference to the specified spreadsheet in the document model.
bool isEof() const
bool getAbsoluteRange(ScRange &orRange) const
Tries to resolve the defined name to an absolute cell range.
void setFormula(const OUString &rFormula)
Sets the formula string from the body of the definedName element.
sal_Unicode mcBuiltinId
Calc sheet index for sheet-local names.
ScTokenArray * GetCode()
Definition: rangenam.hxx:120
DefinedNameRef getByIndex(sal_Int32 nIndex) const
Returns a defined name by zero-based index (order of appearance).
bool mbFunction
True = Macro name (VBA or sheet macro).
void forEachMem(FuncType pFunc) const
AddressConverter & getAddressConverter() const
Returns the converter for string to cell address/range conversion.
WorksheetBuffer & getWorksheets() const
Returns the worksheet buffer containing sheet names and properties.