xref: /core/sc/source/filter/excel/xename.cxx (revision bf4fe0c2ba1d0a44cc07c6afba6d56b8adf8af50)
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 <xename.hxx>
21 
22 #include <map>
23 
24 #include <document.hxx>
25 #include <rangenam.hxx>
26 #include <tokenarray.hxx>
27 #include <xehelper.hxx>
28 #include <xelink.hxx>
29 #include <excrecds.hxx>
30 #include <xlname.hxx>
31 #include <xeformula.hxx>
32 #include <xestring.hxx>
33 #include <xltools.hxx>
34 
35 #include <formula/grammar.hxx>
36 #include <oox/export/utils.hxx>
37 #include <oox/token/tokens.hxx>
38 #include <com/sun/star/sheet/NamedRangeFlag.hdl>
39 #include <xihelper.hxx>
40 
41 using namespace ::oox;
42 using namespace ::com::sun::star;
43 
44 // *** Helper classes ***
45 
46 namespace {
47 
48 /** Represents an internal defined name, supports writing it to a NAME record. */
49 class XclExpName : public XclExpRecord, protected XclExpRoot
50 {
51 public:
52     /** Creates a standard defined name. */
53     explicit            XclExpName( const XclExpRoot& rRoot, const OUString& rName );
54     /** Creates a built-in defined name. */
55     explicit            XclExpName( const XclExpRoot& rRoot, sal_Unicode cBuiltIn );
56 
57     /** Sets a token array containing the definition of this name. */
58     void                SetTokenArray( const XclTokenArrayRef& xTokArr );
59     /** Changes this defined name to be local on the specified Calc sheet. */
60     void                SetLocalTab( SCTAB nScTab );
61     /** Hides or unhides the defined name. */
62     void                SetHidden( bool bHidden = true );
63     /** Changes this name to be the call to a VB macro function or procedure.
64         @param bVBasic  true = Visual Basic macro, false = Sheet macro.
65         @param bFunc  true = Macro function; false = Macro procedure. */
66     void                SetMacroCall( bool bVBasic, bool bFunc );
67 
68     /** Sets the name's symbol value
69         @param sValue   the name's symbolic value */
70     void                SetSymbol( const OUString& rValue );
71 
72     /** Returns the original name (title) of this defined name. */
GetOrigName() const73     const OUString& GetOrigName() const { return maOrigName; }
74     /** Returns the Excel built-in name index of this defined name.
75         @return  The built-in name index or EXC_BUILTIN_UNKNOWN for user-defined names. */
GetBuiltInName() const76     sal_Unicode  GetBuiltInName() const { return mcBuiltIn; }
77 
78     /** Returns the symbol value for this defined name. */
GetSymbol() const79     const OUString& GetSymbol() const { return msSymbol; }
80 
81     /** Returns true, if this is a document-global defined name. */
IsGlobal() const82     bool         IsGlobal() const { return mnXclTab == EXC_NAME_GLOBAL; }
83     /** Returns the Calc sheet of a local defined name. */
GetScTab() const84     SCTAB        GetScTab() const { return mnScTab; }
85 
86     /** Returns true, if this defined name is volatile. */
87     bool                IsVolatile() const;
88     /** Returns true, if this defined name describes a macro call.
89         @param bFunc  true = Macro function; false = Macro procedure. */
90     bool                IsMacroCall( bool bVBasic, bool bFunc ) const;
91 
92     /** Writes the entire NAME record to the passed stream. */
93     virtual void        Save( XclExpStream& rStrm ) override;
94 
95     virtual void        SaveXml( XclExpXmlStream& rStrm ) override;
96 
97 private:
98     /** Writes the body of the NAME record to the passed stream. */
99     virtual void        WriteBody( XclExpStream& rStrm ) override;
100     /** Convert localized range separators */
101     OUString            GetWithDefaultRangeSeparator( const OUString& rSymbol ) const;
102 
103 private:
104     OUString            maOrigName;     /// The original user-defined name.
105     OUString            msSymbol;       /// The value of the symbol
106     XclExpStringRef     mxName;         /// The name as Excel string object.
107     XclTokenArrayRef    mxTokArr;       /// The definition of the defined name.
108     sal_Unicode         mcBuiltIn;      /// The built-in index for built-in names.
109     SCTAB               mnScTab;        /// The Calc sheet index for local names.
110     sal_uInt16          mnFlags;        /// Additional flags for this defined name.
111     sal_uInt16          mnExtSheet;     /// The 1-based index to a global EXTERNSHEET record.
112     sal_uInt16          mnXclTab;       /// The 1-based Excel sheet index for local names.
113 };
114 
115 }
116 
117 /** Implementation class of the name manager. */
118 class XclExpNameManagerImpl : protected XclExpRoot
119 {
120 public:
121     explicit            XclExpNameManagerImpl( const XclExpRoot& rRoot );
122 
123     /** Creates NAME records for built-in and user defined names. */
124     void                Initialize();
125 
126     /** Inserts the Calc name with the passed index and returns the Excel NAME index. */
127     sal_uInt16          InsertName( SCTAB nTab, sal_uInt16 nScNameIdx, SCTAB nCurrTab );
128 
129     /** Inserts a new built-in defined name. */
130     sal_uInt16          InsertBuiltInName( sal_Unicode cBuiltIn, const XclTokenArrayRef& xTokArr, SCTAB nScTab, const ScRangeList& aRangeList );
131     sal_uInt16          InsertBuiltInName( sal_Unicode cBuiltIn, const XclTokenArrayRef& xTokArr, const ScRange& aRange );
132     /** Inserts a new defined name. Sets another unused name, if rName already exists. */
133     sal_uInt16          InsertUniqueName( const OUString& rName, const XclTokenArrayRef& xTokArr, SCTAB nScTab );
134     /** Returns index of an existing name, or creates a name without definition. */
135     sal_uInt16          InsertRawName( const OUString& rName );
136     /** Searches or inserts a defined name describing a macro name.
137         @param bVBasic  true = Visual Basic macro; false = Sheet macro.
138         @param bFunc  true = Macro function; false = Macro procedure. */
139     sal_uInt16          InsertMacroCall( const OUString& rMacroName, bool bVBasic, bool bFunc, bool bHidden );
140 
141     /** Returns the NAME record at the specified position or 0 on error. */
142     const XclExpName*   GetName( sal_uInt16 nNameIdx ) const;
143 
144     /** Writes the entire list of NAME records.
145         @descr  In BIFF7 and lower, writes the entire global link table, which
146             consists of an EXTERNCOUNT record, several EXTERNSHEET records, and
147             the list of NAME records. */
148     void                Save( XclExpStream& rStrm );
149 
150     void                SaveXml( XclExpXmlStream& rStrm );
151 
152 private:
153     typedef XclExpRecordList< XclExpName >      XclExpNameList;
154     typedef XclExpNameList::RecordRefType       XclExpNameRef;
155 
156     typedef ::std::map< ::std::pair<SCTAB, OUString>, sal_uInt16> NamedExpMap;
157 
158 private:
159     /**
160      * @param nTab 0-based table index, or SCTAB_GLOBAL for global names.
161      * @param nScIdx calc's name index.
162      *
163      * @return excel's name index.
164      */
165     sal_uInt16          FindNamedExp( SCTAB nTab, const OUString& sName );
166 
167     /** Returns the index of an existing built-in NAME record with the passed definition, otherwise 0. */
168     sal_uInt16          FindBuiltInNameIdx( const OUString& rName,
169                             const OUString& sSymbol ) const;
170     /** Returns an unused name for the passed name. */
171     OUString            GetUnusedName( const OUString& rName ) const;
172 
173     /** Appends a new NAME record to the record list.
174         @return  The 1-based NAME record index used elsewhere in the Excel file. */
175     sal_uInt16          Append( XclExpName* pName );
Append(XclExpNameRef const & rxName)176     sal_uInt16          Append( XclExpNameRef const & rxName ) { return Append(rxName.get()); }
177     /** Creates a new NAME record for the passed user-defined name.
178         @return  The 1-based NAME record index used elsewhere in the Excel file. */
179     sal_uInt16          CreateName( SCTAB nTab, const ScRangeData& rRangeData );
180 
181     /** Creates NAME records for all built-in names in the document. */
182     void                CreateBuiltInNames();
183     /** Creates NAME records for all user-defined names in the document. */
184     void                CreateUserNames();
185 
186 private:
187     /**
188      * Maps Calc's named range to Excel's NAME records.  Global names use
189      * -1 as their table index, whereas sheet-local names have 0-based table
190      *  index.
191      */
192     NamedExpMap         maNamedExpMap;
193     XclExpNameList      maNameList;         /// List of NAME records.
194     size_t              mnFirstUserIdx;     /// List index of first user-defined NAME record.
195 };
196 
197 // *** Implementation ***
198 
XclExpName(const XclExpRoot & rRoot,const OUString & rName)199 XclExpName::XclExpName( const XclExpRoot& rRoot, const OUString& rName ) :
200     XclExpRecord( EXC_ID_NAME ),
201     XclExpRoot( rRoot ),
202     maOrigName( rName ),
203     mxName( XclExpStringHelper::CreateString( rRoot, rName, XclStrFlags::EightBitLength ) ),
204     mcBuiltIn( EXC_BUILTIN_UNKNOWN ),
205     mnScTab( SCTAB_GLOBAL ),
206     mnFlags( EXC_NAME_DEFAULT ),
207     mnExtSheet( EXC_NAME_GLOBAL ),
208     mnXclTab( EXC_NAME_GLOBAL )
209 {
210 }
211 
XclExpName(const XclExpRoot & rRoot,sal_Unicode cBuiltIn)212 XclExpName::XclExpName( const XclExpRoot& rRoot, sal_Unicode cBuiltIn ) :
213     XclExpRecord( EXC_ID_NAME ),
214     XclExpRoot( rRoot ),
215     mcBuiltIn( cBuiltIn ),
216     mnScTab( SCTAB_GLOBAL ),
217     mnFlags( EXC_NAME_DEFAULT ),
218     mnExtSheet( EXC_NAME_GLOBAL ),
219     mnXclTab( EXC_NAME_GLOBAL )
220 {
221     // filter source range is hidden in Excel
222     if( cBuiltIn == EXC_BUILTIN_FILTERDATABASE )
223         SetHidden();
224 
225     // special case for BIFF5/7 filter source range - name appears as plain text without built-in flag
226     if( (GetBiff() <= EXC_BIFF5) && (cBuiltIn == EXC_BUILTIN_FILTERDATABASE) )
227     {
228         OUString aName( XclTools::GetXclBuiltInDefName( EXC_BUILTIN_FILTERDATABASE ) );
229         mxName = XclExpStringHelper::CreateString( rRoot, aName, XclStrFlags::EightBitLength );
230         maOrigName = XclTools::GetXclBuiltInDefName( cBuiltIn );
231     }
232     else
233     {
234         maOrigName =  XclTools::GetBuiltInDefNameXml( cBuiltIn ) ;
235         mxName = XclExpStringHelper::CreateString( rRoot, cBuiltIn, XclStrFlags::EightBitLength );
236         ::set_flag( mnFlags, EXC_NAME_BUILTIN );
237     }
238 }
239 
SetTokenArray(const XclTokenArrayRef & xTokArr)240 void XclExpName::SetTokenArray( const XclTokenArrayRef& xTokArr )
241 {
242     mxTokArr = xTokArr;
243 }
244 
SetLocalTab(SCTAB nScTab)245 void XclExpName::SetLocalTab( SCTAB nScTab )
246 {
247     OSL_ENSURE( GetTabInfo().IsExportTab( nScTab ), "XclExpName::SetLocalTab - invalid sheet index" );
248     if( !GetTabInfo().IsExportTab( nScTab ) )
249         return;
250 
251     mnScTab = nScTab;
252     GetGlobalLinkManager().FindExtSheet( mnExtSheet, mnXclTab, nScTab );
253 
254     // special handling for NAME record
255     switch( GetBiff() )
256     {
257         case EXC_BIFF5: // EXTERNSHEET index is positive in NAME record
258             mnExtSheet = ~mnExtSheet + 1;
259         break;
260         case EXC_BIFF8: // EXTERNSHEET index not used, but must be created in link table
261             mnExtSheet = 0;
262         break;
263         default:    DBG_ERROR_BIFF();
264     }
265 
266     // Excel sheet index is 1-based
267     ++mnXclTab;
268 }
269 
SetHidden(bool bHidden)270 void XclExpName::SetHidden( bool bHidden )
271 {
272     ::set_flag( mnFlags, EXC_NAME_HIDDEN, bHidden );
273 }
274 
SetMacroCall(bool bVBasic,bool bFunc)275 void XclExpName::SetMacroCall( bool bVBasic, bool bFunc )
276 {
277     ::set_flag( mnFlags, EXC_NAME_PROC );
278     ::set_flag( mnFlags, EXC_NAME_VB, bVBasic );
279     ::set_flag( mnFlags, EXC_NAME_FUNC, bFunc );
280 }
281 
SetSymbol(const OUString & rSymbol)282 void XclExpName::SetSymbol( const OUString& rSymbol )
283 {
284     msSymbol = rSymbol;
285 }
286 
IsVolatile() const287 bool XclExpName::IsVolatile() const
288 {
289     return mxTokArr && mxTokArr->IsVolatile();
290 }
291 
IsMacroCall(bool bVBasic,bool bFunc) const292 bool XclExpName::IsMacroCall( bool bVBasic, bool bFunc ) const
293 {
294     return
295         (::get_flag( mnFlags, EXC_NAME_VB ) == bVBasic) &&
296         (::get_flag( mnFlags, EXC_NAME_FUNC ) == bFunc);
297 }
298 
Save(XclExpStream & rStrm)299 void XclExpName::Save( XclExpStream& rStrm )
300 {
301     OSL_ENSURE( mxName && (mxName->Len() > 0), "XclExpName::Save - missing name" );
302     OSL_ENSURE( !(IsGlobal() && ::get_flag( mnFlags, EXC_NAME_BUILTIN )), "XclExpName::Save - global built-in name" );
303     SetRecSize( 11 + mxName->GetSize() + (mxTokArr ? mxTokArr->GetSize() : 2) );
304     XclExpRecord::Save( rStrm );
305 }
306 
GetWithDefaultRangeSeparator(const OUString & rSymbol) const307 OUString XclExpName::GetWithDefaultRangeSeparator( const OUString& rSymbol ) const
308 {
309     sal_Int32 nPos = rSymbol.indexOf(';');
310     if ( nPos > -1 )
311     {
312         // convert with validation
313         ScRange aRange;
314         ScAddress::Details detailsXL( ::formula::FormulaGrammar::CONV_XL_A1 );
315         ScRefFlags nRes = aRange.Parse( rSymbol.copy(0, nPos), GetDoc(), detailsXL );
316         if ( nRes & ScRefFlags::VALID )
317         {
318             nRes = aRange.Parse( rSymbol.copy(nPos+1), GetDoc(), detailsXL );
319             if ( nRes & ScRefFlags::VALID )
320             {
321                 return rSymbol.replaceFirst(";", ",");
322             }
323         }
324     }
325     if (rSymbol.isEmpty())
326         return u"#NAME?"_ustr;
327     return rSymbol;
328 }
329 
SaveXml(XclExpXmlStream & rStrm)330 void XclExpName::SaveXml( XclExpXmlStream& rStrm )
331 {
332     sax_fastparser::FSHelperPtr& rWorkbook = rStrm.GetCurrentStream();
333     // Sheets where IsExportTab is not true are not exported, so use mnXclTab
334     // (1 based) to get the sheetid as of the exported document's perspective.
335     SCTAB nXlsxTab = mnXclTab - 1;
336     rWorkbook->startElement( XML_definedName,
337             // OOXTODO: XML_comment, "",
338             // OOXTODO: XML_customMenu, "",
339             // OOXTODO: XML_description, "",
340             XML_function, ToPsz( ::get_flag( mnFlags, EXC_NAME_VB ) ),
341             // OOXTODO: XML_functionGroupId, "",
342             // OOXTODO: XML_help, "",
343             XML_hidden, ToPsz( ::get_flag( mnFlags, EXC_NAME_HIDDEN ) ),
344             XML_localSheetId, sax_fastparser::UseIf(OString::number(nXlsxTab), mnScTab != SCTAB_GLOBAL),
345             XML_name, maOrigName.toUtf8(),
346             // OOXTODO: XML_publishToServer, "",
347             // OOXTODO: XML_shortcutKey, "",
348             // OOXTODO: XML_statusBar, "",
349             XML_vbProcedure, ToPsz( ::get_flag( mnFlags, EXC_NAME_VB ) )
350             // OOXTODO: XML_workbookParameter, "",
351             // OOXTODO: XML_xlm, ""
352     );
353     rWorkbook->writeEscaped( GetWithDefaultRangeSeparator( msSymbol ) );
354     rWorkbook->endElement( XML_definedName );
355 }
356 
WriteBody(XclExpStream & rStrm)357 void XclExpName::WriteBody( XclExpStream& rStrm )
358 {
359     sal_uInt16 nFmlaSize = mxTokArr ? mxTokArr->GetSize() : 0;
360 
361     rStrm   << mnFlags                  // flags
362             << sal_uInt8( 0 );          // keyboard shortcut
363     mxName->WriteLenField( rStrm );     // length of name
364     rStrm   << nFmlaSize                // size of token array
365             << mnExtSheet               // BIFF5/7: EXTSHEET index, BIFF8: not used
366             << mnXclTab                 // 1-based sheet index for local names
367             << sal_uInt32( 0 );         // length of menu/descr/help/status text
368     mxName->WriteFlagField( rStrm );    // BIFF8 flag field (no-op in <=BIFF7)
369     mxName->WriteBuffer( rStrm );       // character array of the name
370     if( mxTokArr )
371         mxTokArr->WriteArray( rStrm );  // token array without size
372 }
373 
374 /** Returns true (needed fixing) if FormulaToken was not absolute and 3D.
375     So, regardless of whether the fix was successful or not, true is still returned since a fix was required.*/
lcl_EnsureAbs3DToken(const SCTAB nTab,formula::FormulaToken * pTok,const bool bFix=true)376 static bool lcl_EnsureAbs3DToken( const SCTAB nTab, formula::FormulaToken* pTok, const bool bFix = true )
377 {
378     bool bFixRequired = false;
379     if ( !pTok || ( pTok->GetType() != formula::svSingleRef && pTok->GetType() != formula::svDoubleRef ) )
380         return bFixRequired;
381 
382     ScSingleRefData* pRef1 = pTok->GetSingleRef();
383     if ( !pRef1 )
384         return bFixRequired;
385 
386     ScSingleRefData* pRef2 = nullptr;
387     if ( pTok->GetType() == formula::svDoubleRef )
388         pRef2 = pTok->GetSingleRef2();
389 
390     if ( pRef1->IsTabRel() || !pRef1->IsFlag3D() )
391     {
392         bFixRequired = true;
393         if ( bFix )
394         {
395             if ( pRef1->IsTabRel() && nTab != SCTAB_GLOBAL )
396                 pRef1->SetAbsTab( nTab + pRef1->Tab() );  //XLS requirement
397             if ( !pRef1->IsTabRel() )
398             {
399                 pRef1->SetFlag3D( true );  //XLSX requirement
400                 if ( pRef2 && !pRef2->IsTabRel() )
401                     pRef2->SetFlag3D( pRef2->Tab() != pRef1->Tab() );
402             }
403         }
404     }
405 
406     if ( pRef2 && pRef2->IsTabRel() && !pRef1->IsTabRel() )
407     {
408         bFixRequired = true;
409         if ( bFix && nTab != SCTAB_GLOBAL )
410         {
411             pRef2->SetAbsTab( nTab + pRef2->Tab() );
412             pRef2->SetFlag3D( pRef2->Tab() != pRef1->Tab() );
413         }
414     }
415     return bFixRequired;
416 }
417 
XclExpNameManagerImpl(const XclExpRoot & rRoot)418 XclExpNameManagerImpl::XclExpNameManagerImpl( const XclExpRoot& rRoot ) :
419     XclExpRoot( rRoot ),
420     mnFirstUserIdx( 0 )
421 {
422 }
423 
Initialize()424 void XclExpNameManagerImpl::Initialize()
425 {
426     CreateBuiltInNames();
427     mnFirstUserIdx = maNameList.GetSize();
428     CreateUserNames();
429 }
430 
InsertName(SCTAB nTab,sal_uInt16 nScNameIdx,SCTAB nCurrTab)431 sal_uInt16 XclExpNameManagerImpl::InsertName( SCTAB nTab, sal_uInt16 nScNameIdx, SCTAB nCurrTab )
432 {
433     sal_uInt16 nNameIdx = 0;
434     const ScRangeData* pData = nullptr;
435     ScRangeName* pRN = (nTab == SCTAB_GLOBAL) ? GetDoc().GetRangeName() : GetDoc().GetRangeName(nTab);
436     if (pRN)
437         pData = pRN->findByIndex(nScNameIdx);
438 
439     if (pData)
440     {
441         bool bEmulateGlobalRelativeTable = false;
442         const ScTokenArray* pCode = pData->GetCode();
443         if ( pCode
444             && nTab == SCTAB_GLOBAL
445             && (pData->HasType( ScRangeData::Type::AbsPos ) || pData->HasType( ScRangeData::Type::AbsArea )) )
446         {
447             bEmulateGlobalRelativeTable = lcl_EnsureAbs3DToken( nTab, pCode->FirstToken(), /*bFix=*/false );
448         }
449         nNameIdx = FindNamedExp( bEmulateGlobalRelativeTable ? nCurrTab : nTab, pData->GetName() );
450         if (!nNameIdx)
451             nNameIdx = CreateName(nTab, *pData);
452     }
453 
454     return nNameIdx;
455 }
456 
InsertBuiltInName(sal_Unicode cBuiltIn,const XclTokenArrayRef & xTokArr,const ScRange & aRange)457 sal_uInt16 XclExpNameManagerImpl::InsertBuiltInName( sal_Unicode cBuiltIn, const XclTokenArrayRef& xTokArr, const ScRange& aRange )
458 {
459     XclExpNameRef xName = new XclExpName( GetRoot(), cBuiltIn );
460     xName->SetTokenArray( xTokArr );
461     xName->SetLocalTab( aRange.aStart.Tab() );
462     OUString sSymbol(aRange.Format(GetDoc(), ScRefFlags::RANGE_ABS_3D, ScAddress::Details( ::formula::FormulaGrammar::CONV_XL_A1)));
463     xName->SetSymbol( sSymbol );
464     return Append( xName );
465 }
466 
InsertBuiltInName(sal_Unicode cBuiltIn,const XclTokenArrayRef & xTokArr,SCTAB nScTab,const ScRangeList & rRangeList)467 sal_uInt16 XclExpNameManagerImpl::InsertBuiltInName( sal_Unicode cBuiltIn, const XclTokenArrayRef& xTokArr, SCTAB nScTab, const ScRangeList& rRangeList )
468 {
469     XclExpNameRef xName = new XclExpName( GetRoot(), cBuiltIn );
470     xName->SetTokenArray( xTokArr );
471     xName->SetLocalTab( nScTab );
472     OUString sSymbol;
473     rRangeList.Format( sSymbol, ScRefFlags::RANGE_ABS_3D, GetDoc(), ::formula::FormulaGrammar::CONV_XL_A1 );
474     xName->SetSymbol( sSymbol );
475     return Append( xName );
476 }
477 
InsertUniqueName(const OUString & rName,const XclTokenArrayRef & xTokArr,SCTAB nScTab)478 sal_uInt16 XclExpNameManagerImpl::InsertUniqueName(
479         const OUString& rName, const XclTokenArrayRef& xTokArr, SCTAB nScTab )
480 {
481     OSL_ENSURE( !rName.isEmpty(), "XclExpNameManagerImpl::InsertUniqueName - empty name" );
482     XclExpNameRef xName = new XclExpName( GetRoot(), GetUnusedName( rName ) );
483     xName->SetTokenArray( xTokArr );
484     xName->SetLocalTab( nScTab );
485     return Append( xName );
486 }
487 
InsertRawName(const OUString & rName)488 sal_uInt16 XclExpNameManagerImpl::InsertRawName( const OUString& rName )
489 {
490     // empty name? may occur in broken external Calc tokens
491     if( rName.isEmpty() )
492         return 0;
493 
494     // try to find an existing NAME record, regardless of its type
495     for( size_t nListIdx = mnFirstUserIdx, nListSize = maNameList.GetSize(); nListIdx < nListSize; ++nListIdx )
496     {
497         XclExpNameRef xName = maNameList.GetRecord( nListIdx );
498         if( xName->IsGlobal() && (xName->GetOrigName() == rName) )
499             return static_cast< sal_uInt16 >( nListIdx + 1 );
500     }
501 
502     // create a new NAME record
503     XclExpNameRef xName = new XclExpName( GetRoot(), rName );
504     return Append( xName );
505 }
506 
InsertMacroCall(const OUString & rMacroName,bool bVBasic,bool bFunc,bool bHidden)507 sal_uInt16 XclExpNameManagerImpl::InsertMacroCall( const OUString& rMacroName, bool bVBasic, bool bFunc, bool bHidden )
508 {
509     // empty name? may occur in broken external Calc tokens
510     if( rMacroName.isEmpty() )
511         return 0;
512 
513     // try to find an existing NAME record
514     for( size_t nListIdx = mnFirstUserIdx, nListSize = maNameList.GetSize(); nListIdx < nListSize; ++nListIdx )
515     {
516         XclExpNameRef xName = maNameList.GetRecord( nListIdx );
517         if( xName->IsMacroCall( bVBasic, bFunc ) && (xName->GetOrigName() == rMacroName) )
518             return static_cast< sal_uInt16 >( nListIdx + 1 );
519     }
520 
521     // create a new NAME record
522     XclExpNameRef xName = new XclExpName( GetRoot(), rMacroName );
523     xName->SetMacroCall( bVBasic, bFunc );
524     xName->SetHidden( bHidden );
525 
526     // for sheet macros, add a #NAME! error
527     if( !bVBasic )
528         xName->SetTokenArray( GetFormulaCompiler().CreateErrorFormula( EXC_ERR_NAME ) );
529 
530     return Append( xName );
531 }
532 
GetName(sal_uInt16 nNameIdx) const533 const XclExpName* XclExpNameManagerImpl::GetName( sal_uInt16 nNameIdx ) const
534 {
535     OSL_ENSURE( maNameList.HasRecord( nNameIdx - 1 ), "XclExpNameManagerImpl::GetName - wrong record index" );
536     return maNameList.GetRecord( nNameIdx - 1 );
537 }
538 
Save(XclExpStream & rStrm)539 void XclExpNameManagerImpl::Save( XclExpStream& rStrm )
540 {
541     maNameList.Save( rStrm );
542 }
543 
SaveXml(XclExpXmlStream & rStrm)544 void XclExpNameManagerImpl::SaveXml( XclExpXmlStream& rStrm )
545 {
546     if( maNameList.IsEmpty() )
547         return;
548     sax_fastparser::FSHelperPtr& rWorkbook = rStrm.GetCurrentStream();
549     rWorkbook->startElement(XML_definedNames);
550     maNameList.SaveXml( rStrm );
551     rWorkbook->endElement( XML_definedNames );
552 }
553 
554 // private --------------------------------------------------------------------
555 
FindNamedExp(SCTAB nTab,const OUString & sName)556 sal_uInt16 XclExpNameManagerImpl::FindNamedExp( SCTAB nTab, const OUString& sName )
557 {
558     NamedExpMap::key_type key(nTab, sName);
559     NamedExpMap::const_iterator itr = maNamedExpMap.find(key);
560     return (itr == maNamedExpMap.end()) ? 0 : itr->second;
561 }
562 
FindBuiltInNameIdx(const OUString & rName,const OUString & sSymbol) const563 sal_uInt16 XclExpNameManagerImpl::FindBuiltInNameIdx(
564         const OUString& rName, const OUString& sSymbol ) const
565 {
566     /*  Get built-in index from the name. Special case: the database range
567         'unnamed' will be mapped to Excel's built-in '_FilterDatabase' name. */
568     sal_Unicode cBuiltIn = XclTools::GetBuiltInDefNameIndex( rName );
569 
570     if( cBuiltIn < EXC_BUILTIN_UNKNOWN )
571     {
572         // try to find the record in existing built-in NAME record list
573         for( size_t nPos = 0; nPos < mnFirstUserIdx; ++nPos )
574         {
575             XclExpNameRef xName = maNameList.GetRecord( nPos );
576             if( xName->GetBuiltInName() == cBuiltIn && xName->GetSymbol().replace(';', ',') == sSymbol.replace(';', ',') )
577             {
578                 // tdf#112567 restore the original built-in names with non-localized separators
579                 // TODO: support more localizations, if needed
580                 if ( xName->GetSymbol() != sSymbol )
581                 {
582                     xName->SetSymbol(xName->GetSymbol().replace(';', ','));
583                 }
584                 return static_cast< sal_uInt16 >( nPos + 1 );
585             }
586         }
587     }
588     return 0;
589 }
590 
GetUnusedName(const OUString & rName) const591 OUString XclExpNameManagerImpl::GetUnusedName( const OUString& rName ) const
592 {
593     OUString aNewName( rName );
594     sal_Int32 nAppIdx = 0;
595     bool bExist = true;
596     while( bExist )
597     {
598         // search the list of user-defined names
599         bExist = false;
600         for( size_t nPos = mnFirstUserIdx, nSize = maNameList.GetSize(); !bExist && (nPos < nSize); ++nPos )
601         {
602             XclExpNameRef xName = maNameList.GetRecord( nPos );
603             bExist = xName->GetOrigName() == aNewName;
604             // name exists -> create a new name "<originalname>_<counter>"
605             if( bExist )
606                 aNewName = rName + "_" + OUString::number( ++nAppIdx );
607         }
608     }
609     return aNewName;
610 }
611 
Append(XclExpName * pName)612 sal_uInt16 XclExpNameManagerImpl::Append( XclExpName* pName )
613 {
614     if( maNameList.GetSize() == 0xFFFF )
615         return 0;
616     maNameList.AppendRecord( pName );
617     return static_cast< sal_uInt16 >( maNameList.GetSize() );  // 1-based
618 }
619 
CreateName(SCTAB nTab,const ScRangeData & rRangeData)620 sal_uInt16 XclExpNameManagerImpl::CreateName( SCTAB nTab, const ScRangeData& rRangeData )
621 {
622     const OUString& rName = rRangeData.GetName();
623 
624     /*  #i38821# recursive names: first insert the (empty) name object,
625         otherwise a recursive call of this function from the formula compiler
626         with the same defined name will not find it and will create it again. */
627     size_t nOldListSize = maNameList.GetSize();
628     XclExpNameRef xName = new XclExpName( GetRoot(), rName );
629     if (nTab != SCTAB_GLOBAL)
630         xName->SetLocalTab(nTab);
631     sal_uInt16 nNameIdx = Append( xName );
632     // store the index of the NAME record in the lookup map
633     NamedExpMap::key_type key(nTab, rRangeData.GetName());
634     maNamedExpMap[key] = nNameIdx;
635 
636     // Check if it is a hidden named range
637     if ((rRangeData.GetUnoType() & sheet::NamedRangeFlag::HIDDEN) == sheet::NamedRangeFlag::HIDDEN)
638         xName->SetHidden(true);
639 
640     /*  Create the definition formula.
641         This may cause recursive creation of other defined names. */
642     if( const ScTokenArray* pScTokArr = const_cast< ScRangeData& >( rRangeData ).GetCode() )
643     {
644         XclTokenArrayRef xTokArr;
645         OUString sSymbol;
646         // MSO requires named ranges to have absolute sheet references
647         if ( rRangeData.HasType( ScRangeData::Type::AbsPos ) || rRangeData.HasType( ScRangeData::Type::AbsArea ) )
648         {
649             // Don't modify the actual document; use a temporary copy to create the export formulas.
650             ScTokenArray aTokenCopy( pScTokArr->CloneValue() );
651             lcl_EnsureAbs3DToken(nTab, aTokenCopy.FirstToken());
652 
653             xTokArr = GetFormulaCompiler().CreateFormula(EXC_FMLATYPE_NAME, aTokenCopy);
654             if ( GetOutput() != EXC_OUTPUT_BINARY )
655             {
656                 ScCompiler aComp(GetDoc(), rRangeData.GetPos(), aTokenCopy,
657                                  formula::FormulaGrammar::GRAM_OOXML);
658                 aComp.CreateStringFromTokenArray( sSymbol );
659             }
660         }
661         else
662         {
663             bool bOOXML = GetOutput() == EXC_OUTPUT_XML_2007;
664             xTokArr = GetFormulaCompiler().CreateFormula( EXC_FMLATYPE_NAME, *pScTokArr, bOOXML ?
665                      &rRangeData.GetPos() : nullptr );
666             sSymbol = rRangeData.GetSymbol( (GetOutput() == EXC_OUTPUT_BINARY) ?
667                      formula::FormulaGrammar::GRAM_ENGLISH_XL_A1 : formula::FormulaGrammar::GRAM_OOXML);
668         }
669         xName->SetTokenArray( xTokArr );
670         xName->SetSymbol( sSymbol );
671 
672         /*  Try to replace by existing built-in name - complete token array is
673             needed for comparison, and due to the recursion problem above this
674             cannot be done earlier. If a built-in name is found, the created NAME
675             record for this name and all following records in the list must be
676             deleted, otherwise they may contain wrong name list indexes. */
677         sal_uInt16 nBuiltInIdx = FindBuiltInNameIdx( rName, sSymbol );
678         if( nBuiltInIdx != 0 )
679         {
680             // delete the new NAME records
681             while( maNameList.GetSize() > nOldListSize )
682                 maNameList.RemoveRecord( maNameList.GetSize() - 1 );
683             // use index of the found built-in NAME record
684             key = NamedExpMap::key_type(nTab, rRangeData.GetName());
685             maNamedExpMap[key] = nNameIdx = nBuiltInIdx;
686         }
687     }
688 
689     return nNameIdx;
690 }
691 
CreateBuiltInNames()692 void XclExpNameManagerImpl::CreateBuiltInNames()
693 {
694     ScDocument& rDoc = GetDoc();
695     XclExpTabInfo& rTabInfo = GetTabInfo();
696 
697     /*  #i2394# built-in defined names must be sorted by the name of the
698         containing sheet. Example: SheetA!Print_Range must be stored *before*
699         SheetB!Print_Range, regardless of the position of SheetA in the document! */
700     for( SCTAB nScTabIdx = 0, nScTabCount = rTabInfo.GetScTabCount(); nScTabIdx < nScTabCount; ++nScTabIdx )
701     {
702         // find real sheet index from the nScTabIdx counter
703         SCTAB nScTab = rTabInfo.GetRealScTab( nScTabIdx );
704         // create NAME records for all built-in names of this sheet
705         if( rTabInfo.IsExportTab( nScTab ) )
706         {
707             // *** 1) print ranges *** ----------------------------------------
708 
709             if( rDoc.HasPrintRange() )
710             {
711                 ScRangeList aRangeList;
712                 for( sal_uInt16 nIdx = 0, nCount = rDoc.GetPrintRangeCount( nScTab ); nIdx < nCount; ++nIdx )
713                 {
714                     const ScRange* pPrintRange = rDoc.GetPrintRange( nScTab, nIdx );
715                     if (!pPrintRange)
716                         continue;
717                     ScRange aRange( *pPrintRange );
718                     // Calc document does not care about sheet index in print ranges
719                     aRange.aStart.SetTab( nScTab );
720                     aRange.aEnd.SetTab( nScTab );
721                     aRange.PutInOrder();
722 
723                     // tdf#148170 - convert print range to an excel cell range
724                     XclRange aXclRange(ScAddress::UNINITIALIZED);
725                     // create no warning if ranges are shrunken
726                     if (GetAddressConverter().ConvertRange(aXclRange, aRange, false))
727                     {
728                         XclImpAddressConverter::FillRange(aXclRange, aRange);
729                         aRangeList.push_back(aRange);
730                     }
731                 }
732                 // create the NAME record
733                 if( !aRangeList.empty() )
734                     GetNameManager().InsertBuiltInName( EXC_BUILTIN_PRINTAREA, aRangeList );
735             }
736 
737             // *** 2) print titles *** ----------------------------------------
738 
739             ScRangeList aTitleList;
740             // repeated columns
741             if( std::optional<ScRange> oColRange = rDoc.GetRepeatColRange( nScTab ) )
742                 aTitleList.push_back( ScRange(
743                     oColRange->aStart.Col(), 0, nScTab,
744                     oColRange->aEnd.Col(), GetXclMaxPos().Row(), nScTab ) );
745             // repeated rows
746             if( std::optional<ScRange> oRowRange = rDoc.GetRepeatRowRange( nScTab ) )
747                 aTitleList.push_back( ScRange(
748                     0, oRowRange->aStart.Row(), nScTab,
749                     GetXclMaxPos().Col(), oRowRange->aEnd.Row(), nScTab ) );
750             // create the NAME record
751             GetAddressConverter().ValidateRangeList( aTitleList, false );
752             if( !aTitleList.empty() )
753                 GetNameManager().InsertBuiltInName( EXC_BUILTIN_PRINTTITLES, aTitleList );
754 
755             // *** 3) filter ranges *** ---------------------------------------
756 
757             if( GetBiff() == EXC_BIFF8 )
758                 GetFilterManager().InitTabFilter( nScTab );
759         }
760     }
761 }
762 
CreateUserNames()763 void XclExpNameManagerImpl::CreateUserNames()
764 {
765     std::vector<ScRangeData*> vEmulateAsLocalRange;
766     const ScRangeName& rNamedRanges = GetNamedRanges();
767     for (const auto& rEntry : rNamedRanges)
768     {
769         // skip definitions of shared formulas
770         if (!FindNamedExp(SCTAB_GLOBAL, rEntry.second->GetName()))
771         {
772             const ScTokenArray* pCode = rEntry.second->GetCode();
773             if ( pCode
774                 && (rEntry.second->HasType( ScRangeData::Type::AbsPos ) || rEntry.second->HasType( ScRangeData::Type::AbsArea ))
775                 && lcl_EnsureAbs3DToken( SCTAB_GLOBAL, pCode->FirstToken(), /*bFix=*/false ) )
776             {
777                 vEmulateAsLocalRange.emplace_back(rEntry.second.get());
778             }
779             else
780                 CreateName(SCTAB_GLOBAL, *rEntry.second);
781         }
782     }
783     //look at sheets containing local range names
784     ScRangeName::TabNameCopyMap rLocalNames;
785     GetDoc().GetAllTabRangeNames(rLocalNames);
786     for (const auto& [rTab, pRangeName] : rLocalNames)
787     {
788         for (const auto& rEntry : *pRangeName)
789         {
790             // skip definitions of shared formulas
791             if (!FindNamedExp(rTab, rEntry.second->GetName()))
792                 CreateName(rTab, *rEntry.second);
793         }
794     }
795 
796     // Emulate relative global variables by creating a copy in each local range.
797     // Creating AFTER true local range names so that conflicting global names will be ignored.
798     for ( SCTAB nTab = 0; nTab < GetDoc().GetTableCount(); ++nTab )
799     {
800         for ( auto rangeDataItr : vEmulateAsLocalRange )
801         {
802             if ( !FindNamedExp(nTab, rangeDataItr->GetName()) )
803                 CreateName(nTab, *rangeDataItr );
804         }
805     }
806 }
807 
XclExpNameManager(const XclExpRoot & rRoot)808 XclExpNameManager::XclExpNameManager( const XclExpRoot& rRoot ) :
809     XclExpRoot( rRoot ),
810     mxImpl( std::make_shared<XclExpNameManagerImpl>( rRoot ) )
811 {
812 }
813 
~XclExpNameManager()814 XclExpNameManager::~XclExpNameManager()
815 {
816 }
817 
Initialize()818 void XclExpNameManager::Initialize()
819 {
820     mxImpl->Initialize();
821 }
822 
InsertName(SCTAB nTab,sal_uInt16 nScNameIdx,SCTAB nCurrTab)823 sal_uInt16 XclExpNameManager::InsertName( SCTAB nTab, sal_uInt16 nScNameIdx, SCTAB nCurrTab )
824 {
825     return mxImpl->InsertName( nTab, nScNameIdx, nCurrTab );
826 }
827 
InsertBuiltInName(sal_Unicode cBuiltIn,const ScRange & rRange)828 sal_uInt16 XclExpNameManager::InsertBuiltInName( sal_Unicode cBuiltIn, const ScRange& rRange )
829 {
830     XclTokenArrayRef xTokArr = GetFormulaCompiler().CreateFormula( EXC_FMLATYPE_NAME, rRange );
831     return mxImpl->InsertBuiltInName( cBuiltIn, xTokArr, rRange );
832 }
833 
InsertBuiltInName(sal_Unicode cBuiltIn,const ScRangeList & rRangeList)834 sal_uInt16 XclExpNameManager::InsertBuiltInName( sal_Unicode cBuiltIn, const ScRangeList& rRangeList )
835 {
836     sal_uInt16 nNameIdx = 0;
837     if( !rRangeList.empty() )
838     {
839         XclTokenArrayRef xTokArr = GetFormulaCompiler().CreateFormula( EXC_FMLATYPE_NAME, rRangeList );
840         nNameIdx = mxImpl->InsertBuiltInName( cBuiltIn, xTokArr, rRangeList.front().aStart.Tab(), rRangeList );
841     }
842     return nNameIdx;
843 }
844 
InsertUniqueName(const OUString & rName,const XclTokenArrayRef & xTokArr,SCTAB nScTab)845 sal_uInt16 XclExpNameManager::InsertUniqueName(
846         const OUString& rName, const XclTokenArrayRef& xTokArr, SCTAB nScTab )
847 {
848     return mxImpl->InsertUniqueName( rName, xTokArr, nScTab );
849 }
850 
InsertRawName(const OUString & rName)851 sal_uInt16 XclExpNameManager::InsertRawName( const OUString& rName )
852 {
853     return mxImpl->InsertRawName( rName );
854 }
855 
InsertMacroCall(const OUString & rMacroName,bool bVBasic,bool bFunc,bool bHidden)856 sal_uInt16 XclExpNameManager::InsertMacroCall( const OUString& rMacroName, bool bVBasic, bool bFunc, bool bHidden )
857 {
858     return mxImpl->InsertMacroCall( rMacroName, bVBasic, bFunc, bHidden );
859 }
860 
GetOrigName(sal_uInt16 nNameIdx) const861 OUString XclExpNameManager::GetOrigName( sal_uInt16 nNameIdx ) const
862 {
863     const XclExpName* pName = mxImpl->GetName( nNameIdx );
864     return pName ? pName->GetOrigName() : OUString();
865 }
866 
GetScTab(sal_uInt16 nNameIdx) const867 SCTAB XclExpNameManager::GetScTab( sal_uInt16 nNameIdx ) const
868 {
869     const XclExpName* pName = mxImpl->GetName( nNameIdx );
870     return pName ? pName->GetScTab() : SCTAB_GLOBAL;
871 }
872 
IsVolatile(sal_uInt16 nNameIdx) const873 bool XclExpNameManager::IsVolatile( sal_uInt16 nNameIdx ) const
874 {
875     const XclExpName* pName = mxImpl->GetName( nNameIdx );
876     return pName && pName->IsVolatile();
877 }
878 
Save(XclExpStream & rStrm)879 void XclExpNameManager::Save( XclExpStream& rStrm )
880 {
881     mxImpl->Save( rStrm );
882 }
883 
SaveXml(XclExpXmlStream & rStrm)884 void XclExpNameManager::SaveXml( XclExpXmlStream& rStrm )
885 {
886     mxImpl->SaveXml( rStrm );
887 }
888 
889 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
890