LanguageTool: don't crash if REST protocol isn't set
[LibreOffice.git] / sc / source / filter / excel / xehelper.cxx
blobbd60fd355cd50e17b8b0460c72f7159bdfb4ad46
1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /*
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 .
20 #include <sal/config.h>
22 #include <string_view>
24 #include <com/sun/star/i18n/XBreakIterator.hpp>
25 #include <com/sun/star/i18n/ScriptType.hpp>
26 #include <sfx2/objsh.hxx>
27 #include <vcl/font.hxx>
28 #include <tools/urlobj.hxx>
29 #include <svl/itemset.hxx>
30 #include <svtools/ctrltool.hxx>
31 #include <svx/svdotext.hxx>
32 #include <editeng/outlobj.hxx>
33 #include <scitems.hxx>
34 #include <editeng/fhgtitem.hxx>
35 #include <editeng/flstitem.hxx>
36 #include <editeng/colritem.hxx>
37 #include <editeng/eeitem.hxx>
38 #include <editeng/flditem.hxx>
39 #include <editeng/escapementitem.hxx>
40 #include <editeng/svxfont.hxx>
41 #include <editeng/editids.hrc>
43 #include <document.hxx>
44 #include <docpool.hxx>
45 #include <editutil.hxx>
46 #include <patattr.hxx>
47 #include <scmatrix.hxx>
48 #include <xestyle.hxx>
49 #include <fprogressbar.hxx>
50 #include <globstr.hrc>
51 #include <xltracer.hxx>
52 #include <xltools.hxx>
53 #include <xecontent.hxx>
54 #include <xelink.hxx>
55 #include <xehelper.hxx>
57 using ::com::sun::star::uno::Reference;
58 using ::com::sun::star::i18n::XBreakIterator;
60 // Export progress bar ========================================================
62 XclExpProgressBar::XclExpProgressBar( const XclExpRoot& rRoot ) :
63 XclExpRoot( rRoot ),
64 mxProgress( new ScfProgressBar( rRoot.GetDocShell(), STR_SAVE_DOC ) ),
65 mpSubProgress( nullptr ),
66 mpSubRowCreate( nullptr ),
67 mpSubRowFinal( nullptr ),
68 mnSegRowFinal( SCF_INV_SEGMENT ),
69 mnRowCount( 0 )
73 XclExpProgressBar::~XclExpProgressBar()
77 void XclExpProgressBar::Initialize()
79 const ScDocument& rDoc = GetDoc();
80 const XclExpTabInfo& rTabInfo = GetTabInfo();
81 SCTAB nScTabCount = rTabInfo.GetScTabCount();
83 // *** segment: creation of ROW records *** -------------------------------
85 sal_Int32 nSegRowCreate = mxProgress->AddSegment( 2000 );
86 mpSubRowCreate = &mxProgress->GetSegmentProgressBar( nSegRowCreate );
87 maSubSegRowCreate.resize( nScTabCount, SCF_INV_SEGMENT );
89 for( SCTAB nScTab = 0; nScTab < nScTabCount; ++nScTab )
91 if( rTabInfo.IsExportTab( nScTab ) )
93 SCCOL nLastUsedScCol;
94 SCROW nLastUsedScRow;
95 rDoc.GetTableArea( nScTab, nLastUsedScCol, nLastUsedScRow );
96 std::size_t nSegSize = static_cast< std::size_t >( nLastUsedScRow + 1 );
97 maSubSegRowCreate[ nScTab ] = mpSubRowCreate->AddSegment( nSegSize );
101 // *** segment: writing all ROW records *** -------------------------------
103 mnSegRowFinal = mxProgress->AddSegment( 1000 );
104 // sub progress bar and segment are created later in ActivateFinalRowsSegment()
107 void XclExpProgressBar::IncRowRecordCount()
109 ++mnRowCount;
112 void XclExpProgressBar::ActivateCreateRowsSegment()
114 OSL_ENSURE( (0 <= GetCurrScTab()) && (GetCurrScTab() < GetTabInfo().GetScTabCount()),
115 "XclExpProgressBar::ActivateCreateRowsSegment - invalid sheet" );
116 sal_Int32 nSeg = maSubSegRowCreate[ GetCurrScTab() ];
117 OSL_ENSURE( nSeg != SCF_INV_SEGMENT, "XclExpProgressBar::ActivateCreateRowsSegment - invalid segment" );
118 if( nSeg != SCF_INV_SEGMENT )
120 mpSubProgress = mpSubRowCreate;
121 mpSubProgress->ActivateSegment( nSeg );
123 else
124 mpSubProgress = nullptr;
127 void XclExpProgressBar::ActivateFinalRowsSegment()
129 if( !mpSubRowFinal && (mnRowCount > 0) )
131 mpSubRowFinal = &mxProgress->GetSegmentProgressBar( mnSegRowFinal );
132 mpSubRowFinal->AddSegment( mnRowCount );
134 mpSubProgress = mpSubRowFinal;
135 if( mpSubProgress )
136 mpSubProgress->Activate();
139 void XclExpProgressBar::Progress()
141 if( mpSubProgress && !mpSubProgress->IsFull() )
142 mpSubProgress->Progress();
145 // Calc->Excel cell address/range conversion ==================================
147 namespace {
149 /** Fills the passed Excel address with the passed Calc cell coordinates without checking any limits. */
150 void lclFillAddress( XclAddress& rXclPos, SCCOL nScCol, SCROW nScRow )
152 rXclPos.mnCol = static_cast< sal_uInt16 >( nScCol );
153 rXclPos.mnRow = static_cast< sal_uInt32 >( nScRow );
156 } // namespace
158 XclExpAddressConverter::XclExpAddressConverter( const XclExpRoot& rRoot ) :
159 XclAddressConverterBase( rRoot.GetTracer(), rRoot.GetXclMaxPos() )
163 // cell address ---------------------------------------------------------------
165 bool XclExpAddressConverter::CheckAddress( const ScAddress& rScPos, bool bWarn )
167 // ScAddress::operator<=() doesn't do what we want here
168 bool bValidCol = (0 <= rScPos.Col()) && (rScPos.Col() <= maMaxPos.Col());
169 bool bValidRow = (0 <= rScPos.Row()) && (rScPos.Row() <= maMaxPos.Row());
170 bool bValidTab = (0 <= rScPos.Tab()) && (rScPos.Tab() <= maMaxPos.Tab());
172 bool bValid = bValidCol && bValidRow && bValidTab;
173 if( !bValid )
175 mbColTrunc |= !bValidCol;
176 mbRowTrunc |= !bValidRow;
178 if( !bValid && bWarn )
180 mbTabTrunc |= (rScPos.Tab() > maMaxPos.Tab()); // do not warn for deleted refs
181 mrTracer.TraceInvalidAddress( rScPos, maMaxPos );
183 return bValid;
186 bool XclExpAddressConverter::ConvertAddress( XclAddress& rXclPos,
187 const ScAddress& rScPos, bool bWarn )
189 bool bValid = CheckAddress( rScPos, bWarn );
190 if( bValid )
191 lclFillAddress( rXclPos, rScPos.Col(), rScPos.Row() );
192 return bValid;
195 XclAddress XclExpAddressConverter::CreateValidAddress( const ScAddress& rScPos, bool bWarn )
197 XclAddress aXclPos( ScAddress::UNINITIALIZED );
198 if( !ConvertAddress( aXclPos, rScPos, bWarn ) )
199 lclFillAddress( aXclPos, ::std::min( rScPos.Col(), maMaxPos.Col() ), ::std::min( rScPos.Row(), maMaxPos.Row() ) );
200 return aXclPos;
203 // cell range -----------------------------------------------------------------
205 bool XclExpAddressConverter::CheckRange( const ScRange& rScRange, bool bWarn )
207 return CheckAddress( rScRange.aStart, bWarn ) && CheckAddress( rScRange.aEnd, bWarn );
210 bool XclExpAddressConverter::ValidateRange( ScRange& rScRange, bool bWarn )
212 rScRange.PutInOrder();
214 // check start position
215 bool bValidStart = CheckAddress( rScRange.aStart, bWarn );
216 if( bValidStart )
218 // check & correct end position
219 ScAddress& rScEnd = rScRange.aEnd;
220 if( !CheckAddress( rScEnd, bWarn ) )
222 rScEnd.SetCol( ::std::min( rScEnd.Col(), maMaxPos.Col() ) );
223 rScEnd.SetRow( ::std::min( rScEnd.Row(), maMaxPos.Row() ) );
224 rScEnd.SetTab( ::std::min( rScEnd.Tab(), maMaxPos.Tab() ) );
228 return bValidStart;
231 bool XclExpAddressConverter::ConvertRange( XclRange& rXclRange,
232 const ScRange& rScRange, bool bWarn )
234 // check start position
235 bool bValidStart = CheckAddress( rScRange.aStart, bWarn );
236 if( bValidStart )
238 lclFillAddress( rXclRange.maFirst, rScRange.aStart.Col(), rScRange.aStart.Row() );
240 // check & correct end position
241 SCCOL nScCol2 = rScRange.aEnd.Col();
242 SCROW nScRow2 = rScRange.aEnd.Row();
243 if( !CheckAddress( rScRange.aEnd, bWarn ) )
245 nScCol2 = ::std::min( nScCol2, maMaxPos.Col() );
246 nScRow2 = ::std::min( nScRow2, maMaxPos.Row() );
248 lclFillAddress( rXclRange.maLast, nScCol2, nScRow2 );
250 return bValidStart;
253 // cell range list ------------------------------------------------------------
255 void XclExpAddressConverter::ValidateRangeList( ScRangeList& rScRanges, bool bWarn )
257 for ( size_t nRange = rScRanges.size(); nRange > 0; )
259 ScRange & rScRange = rScRanges[ --nRange ];
260 if( !CheckRange( rScRange, bWarn ) )
261 rScRanges.Remove(nRange);
265 void XclExpAddressConverter::ConvertRangeList( XclRangeList& rXclRanges,
266 const ScRangeList& rScRanges, bool bWarn )
268 rXclRanges.clear();
269 for( size_t nPos = 0, nCount = rScRanges.size(); nPos < nCount; ++nPos )
271 const ScRange & rScRange = rScRanges[ nPos ];
272 XclRange aXclRange( ScAddress::UNINITIALIZED );
273 if( ConvertRange( aXclRange, rScRange, bWarn ) )
274 rXclRanges.push_back( aXclRange );
278 // EditEngine->String conversion ==============================================
280 namespace {
282 OUString lclGetUrlRepresentation( const SvxURLField& rUrlField )
284 const OUString& aRepr = rUrlField.GetRepresentation();
285 // no representation -> use URL
286 return aRepr.isEmpty() ? rUrlField.GetURL() : aRepr;
289 } // namespace
291 XclExpHyperlinkHelper::XclExpHyperlinkHelper( const XclExpRoot& rRoot, const ScAddress& rScPos ) :
292 XclExpRoot( rRoot ),
293 maScPos( rScPos ),
294 mbMultipleUrls( false )
298 XclExpHyperlinkHelper::~XclExpHyperlinkHelper()
302 OUString XclExpHyperlinkHelper::ProcessUrlField( const SvxURLField& rUrlField )
304 OUString aUrlRepr;
306 if( GetBiff() == EXC_BIFF8 ) // no HLINK records in BIFF2-BIFF7
308 // there was/is already a HLINK record
309 mbMultipleUrls = static_cast< bool >(mxLinkRec);
311 mxLinkRec = new XclExpHyperlink( GetRoot(), rUrlField, maScPos );
313 if( const OUString* pRepr = mxLinkRec->GetRepr() )
314 aUrlRepr = *pRepr;
316 // add URL to note text
317 maUrlList = ScGlobal::addToken( maUrlList, rUrlField.GetURL(), '\n' );
320 // no hyperlink representation from Excel HLINK record -> use it from text field
321 return aUrlRepr.isEmpty() ? lclGetUrlRepresentation(rUrlField) : aUrlRepr;
324 bool XclExpHyperlinkHelper::HasLinkRecord() const
326 return !mbMultipleUrls && mxLinkRec;
329 XclExpHyperlinkHelper::XclExpHyperlinkRef XclExpHyperlinkHelper::GetLinkRecord() const
331 if( HasLinkRecord() )
332 return mxLinkRec;
333 return XclExpHyperlinkRef();
336 namespace {
338 /** Creates a new formatted string from the passed unformatted string.
340 Creates a Unicode string or a byte string, depending on the current BIFF
341 version contained in the passed XclExpRoot object. May create a formatted
342 string object, if the text contains different script types.
344 @param pCellAttr
345 Cell attributes used for font formatting.
346 @param nFlags
347 Modifiers for string export.
348 @param nMaxLen
349 The maximum number of characters to store in this string.
350 @return
351 The new string object.
353 XclExpStringRef lclCreateFormattedString(
354 const XclExpRoot& rRoot, const OUString& rText, const ScPatternAttr* pCellAttr,
355 XclStrFlags nFlags, sal_uInt16 nMaxLen )
357 /* Create an empty Excel string object with correctly initialized BIFF mode,
358 because this function only uses Append() functions that require this. */
359 XclExpStringRef xString = XclExpStringHelper::CreateString( rRoot, OUString(), nFlags, nMaxLen );
361 // script type handling
362 Reference< XBreakIterator > xBreakIt = rRoot.GetDoc().GetBreakIterator();
363 namespace ApiScriptType = ::com::sun::star::i18n::ScriptType;
364 // #i63255# get script type for leading weak characters
365 sal_Int16 nLastScript = XclExpStringHelper::GetLeadingScriptType( rRoot, rText );
367 // font buffer and cell item set
368 XclExpFontBuffer& rFontBuffer = rRoot.GetFontBuffer();
369 const SfxItemSet& rItemSet = pCellAttr ? pCellAttr->GetItemSet() : rRoot.GetDoc().GetDefPattern()->GetItemSet();
371 // process all script portions
372 sal_Int32 nPortionPos = 0;
373 sal_Int32 nTextLen = rText.getLength();
374 while( nPortionPos < nTextLen )
376 // get script type and end position of next script portion
377 sal_Int16 nScript = xBreakIt->getScriptType( rText, nPortionPos );
378 sal_Int32 nPortionEnd = xBreakIt->endOfScript( rText, nPortionPos, nScript );
380 // reuse previous script for following weak portions
381 if( nScript == ApiScriptType::WEAK )
382 nScript = nLastScript;
384 // construct font from current text portion
385 SvxFont aFont( XclExpFontHelper::GetFontFromItemSet( rRoot, rItemSet, nScript ) );
387 // Excel start position of this portion
388 sal_Int32 nXclPortionStart = xString->Len();
389 // add portion text to Excel string
390 XclExpStringHelper::AppendString( *xString, rRoot, rText.copy( nPortionPos, nPortionEnd - nPortionPos ) );
391 if( nXclPortionStart < xString->Len() )
393 // insert font into buffer
394 sal_uInt16 nFontIdx = rFontBuffer.Insert( aFont, EXC_COLOR_CELLTEXT );
395 // insert font index into format run vector
396 xString->AppendFormat( nXclPortionStart, nFontIdx );
399 // go to next script portion
400 nLastScript = nScript;
401 nPortionPos = nPortionEnd;
404 return xString;
407 /** Creates a new formatted string from an edit engine text object.
409 Creates a Unicode string or a byte string, depending on the current BIFF
410 version contained in the passed XclExpRoot object.
412 @param rEE
413 The edit engine in use. The text object must already be set.
414 @param nFlags
415 Modifiers for string export.
416 @param nMaxLen
417 The maximum number of characters to store in this string.
418 @return
419 The new string object.
421 XclExpStringRef lclCreateFormattedString(
422 const XclExpRoot& rRoot, EditEngine& rEE, XclExpHyperlinkHelper* pLinkHelper,
423 XclStrFlags nFlags, sal_uInt16 nMaxLen )
425 /* Create an empty Excel string object with correctly initialized BIFF mode,
426 because this function only uses Append() functions that require this. */
427 XclExpStringRef xString = XclExpStringHelper::CreateString( rRoot, OUString(), nFlags, nMaxLen );
429 // font buffer and helper item set for edit engine -> Calc item conversion
430 XclExpFontBuffer& rFontBuffer = rRoot.GetFontBuffer();
431 SfxItemSetFixed<ATTR_PATTERN_START, ATTR_PATTERN_END> aItemSet( *rRoot.GetDoc().GetPool() );
433 // script type handling
434 Reference< XBreakIterator > xBreakIt = rRoot.GetDoc().GetBreakIterator();
435 namespace ApiScriptType = ::com::sun::star::i18n::ScriptType;
436 // #i63255# get script type for leading weak characters
437 sal_Int16 nLastScript = XclExpStringHelper::GetLeadingScriptType( rRoot, rEE.GetText() );
439 // process all paragraphs
440 sal_Int32 nParaCount = rEE.GetParagraphCount();
441 for( sal_Int32 nPara = 0; nPara < nParaCount; ++nPara )
443 ESelection aSel( nPara, 0 );
444 OUString aParaText( rEE.GetText( nPara ) );
446 std::vector<sal_Int32> aPosList;
447 rEE.GetPortions( nPara, aPosList );
449 // process all portions in the paragraph
450 for( const auto& rPos : aPosList )
452 aSel.nEndPos = rPos;
453 OUString aXclPortionText = aParaText.copy( aSel.nStartPos, aSel.nEndPos - aSel.nStartPos );
455 aItemSet.ClearItem();
456 SfxItemSet aEditSet( rEE.GetAttribs( aSel ) );
457 ScPatternAttr::GetFromEditItemSet( aItemSet, aEditSet );
459 // get escapement value
460 short nEsc = aEditSet.Get( EE_CHAR_ESCAPEMENT ).GetEsc();
462 // process text fields
463 bool bIsHyperlink = false;
464 if( aSel.nStartPos + 1 == aSel.nEndPos )
466 // test if the character is a text field
467 const SfxPoolItem* pItem;
468 if( aEditSet.GetItemState( EE_FEATURE_FIELD, false, &pItem ) == SfxItemState::SET )
470 const SvxFieldData* pField = static_cast< const SvxFieldItem* >( pItem )->GetField();
471 if( const SvxURLField* pUrlField = dynamic_cast<const SvxURLField*>( pField ) )
473 // convert URL field to string representation
474 aXclPortionText = pLinkHelper ?
475 pLinkHelper->ProcessUrlField( *pUrlField ) :
476 lclGetUrlRepresentation( *pUrlField );
477 bIsHyperlink = true;
479 else
481 OSL_FAIL( "lclCreateFormattedString - unknown text field" );
482 aXclPortionText.clear();
487 // Excel start position of this portion
488 sal_Int32 nXclPortionStart = xString->Len();
489 // add portion text to Excel string
490 XclExpStringHelper::AppendString( *xString, rRoot, aXclPortionText );
491 if( (nXclPortionStart < xString->Len()) || (aParaText.isEmpty()) )
493 /* Construct font from current edit engine text portion. Edit engine
494 creates different portions for different script types, no need to loop. */
495 sal_Int16 nScript = xBreakIt->getScriptType( aXclPortionText, 0 );
496 if( nScript == ApiScriptType::WEAK )
497 nScript = nLastScript;
498 SvxFont aFont( XclExpFontHelper::GetFontFromItemSet( rRoot, aItemSet, nScript ) );
499 nLastScript = nScript;
501 // add escapement
502 aFont.SetEscapement( nEsc );
503 // modify automatic font color for hyperlinks
504 if( bIsHyperlink && aItemSet.Get( ATTR_FONT_COLOR ).GetValue() == COL_AUTO)
505 aFont.SetColor( COL_LIGHTBLUE );
507 // insert font into buffer
508 sal_uInt16 nFontIdx = rFontBuffer.Insert( aFont, EXC_COLOR_CELLTEXT );
509 // insert font index into format run vector
510 xString->AppendFormat( nXclPortionStart, nFontIdx );
513 aSel.nStartPos = aSel.nEndPos;
516 // add trailing newline (important for correct character index calculation)
517 if( nPara + 1 < nParaCount )
518 XclExpStringHelper::AppendChar( *xString, rRoot, '\n' );
521 return xString;
524 } // namespace
526 XclExpStringRef XclExpStringHelper::CreateString(
527 const XclExpRoot& rRoot, const OUString& rString, XclStrFlags nFlags, sal_uInt16 nMaxLen )
529 XclExpStringRef xString = std::make_shared<XclExpString>();
530 if( rRoot.GetBiff() == EXC_BIFF8 )
531 xString->Assign( rString, nFlags, nMaxLen );
532 else
533 xString->AssignByte( rString, rRoot.GetTextEncoding(), nFlags, nMaxLen );
534 return xString;
537 XclExpStringRef XclExpStringHelper::CreateString(
538 const XclExpRoot& rRoot, sal_Unicode cChar, XclStrFlags nFlags, sal_uInt16 nMaxLen )
540 XclExpStringRef xString = CreateString( rRoot, OUString(), nFlags, nMaxLen );
541 AppendChar( *xString, rRoot, cChar );
542 return xString;
545 void XclExpStringHelper::AppendString( XclExpString& rXclString, const XclExpRoot& rRoot, const OUString& rString )
547 if( rRoot.GetBiff() == EXC_BIFF8 )
548 rXclString.Append( rString );
549 else
550 rXclString.AppendByte( rString, rRoot.GetTextEncoding() );
553 void XclExpStringHelper::AppendChar( XclExpString& rXclString, const XclExpRoot& rRoot, sal_Unicode cChar )
555 if( rRoot.GetBiff() == EXC_BIFF8 )
556 rXclString.Append( OUString(cChar) );
557 else
558 rXclString.AppendByte( cChar, rRoot.GetTextEncoding() );
561 XclExpStringRef XclExpStringHelper::CreateCellString(
562 const XclExpRoot& rRoot, const OUString& rString, const ScPatternAttr* pCellAttr,
563 XclStrFlags nFlags, sal_uInt16 nMaxLen )
565 return lclCreateFormattedString(rRoot, rString, pCellAttr, nFlags, nMaxLen);
568 XclExpStringRef XclExpStringHelper::CreateCellString(
569 const XclExpRoot& rRoot, const EditTextObject& rEditText, const ScPatternAttr* pCellAttr,
570 XclExpHyperlinkHelper& rLinkHelper, XclStrFlags nFlags, sal_uInt16 nMaxLen )
572 XclExpStringRef xString;
574 // formatted cell
575 ScEditEngineDefaulter& rEE = rRoot.GetEditEngine();
576 bool bOldUpdateMode = rEE.SetUpdateLayout( true );
578 // default items
579 const SfxItemSet& rItemSet = pCellAttr ? pCellAttr->GetItemSet() : rRoot.GetDoc().GetDefPattern()->GetItemSet();
580 auto pEEItemSet = std::make_unique<SfxItemSet>( rEE.GetEmptyItemSet() );
581 ScPatternAttr::FillToEditItemSet( *pEEItemSet, rItemSet );
582 rEE.SetDefaults( std::move(pEEItemSet) ); // edit engine takes ownership
584 // create the string
585 rEE.SetTextCurrentDefaults(rEditText);
586 xString = lclCreateFormattedString( rRoot, rEE, &rLinkHelper, nFlags, nMaxLen );
587 rEE.SetUpdateLayout( bOldUpdateMode );
589 return xString;
592 XclExpStringRef XclExpStringHelper::CreateString(
593 const XclExpRoot& rRoot, const SdrTextObj& rTextObj,
594 XclStrFlags nFlags )
596 XclExpStringRef xString;
597 if( const OutlinerParaObject* pParaObj = rTextObj.GetOutlinerParaObject() )
599 EditEngine& rEE = rRoot.GetDrawEditEngine();
600 bool bOldUpdateMode = rEE.SetUpdateLayout( true );
601 // create the string
602 rEE.SetText( pParaObj->GetTextObject() );
603 xString = lclCreateFormattedString( rRoot, rEE, nullptr, nFlags, EXC_STR_MAXLEN );
604 rEE.SetUpdateLayout( bOldUpdateMode );
605 // limit formats - TODO: BIFF dependent
606 if( !xString->IsEmpty() )
608 xString->LimitFormatCount( EXC_MAXRECSIZE_BIFF8 / 8 - 1 );
609 xString->AppendTrailingFormat( EXC_FONT_APP );
612 else
614 OSL_FAIL( "XclExpStringHelper::CreateString - textbox without para object" );
615 // create BIFF dependent empty Excel string
616 xString = CreateString( rRoot, OUString(), nFlags );
618 return xString;
621 XclExpStringRef XclExpStringHelper::CreateString(
622 const XclExpRoot& rRoot, const EditTextObject& rEditObj,
623 XclStrFlags nFlags )
625 XclExpStringRef xString;
626 EditEngine& rEE = rRoot.GetDrawEditEngine();
627 bool bOldUpdateMode = rEE.SetUpdateLayout( true );
628 rEE.SetText( rEditObj );
629 xString = lclCreateFormattedString( rRoot, rEE, nullptr, nFlags, EXC_STR_MAXLEN );
630 rEE.SetUpdateLayout( bOldUpdateMode );
631 // limit formats - TODO: BIFF dependent
632 if( !xString->IsEmpty() )
634 xString->LimitFormatCount( EXC_MAXRECSIZE_BIFF8 / 8 - 1 );
635 xString->AppendTrailingFormat( EXC_FONT_APP );
637 return xString;
640 sal_Int16 XclExpStringHelper::GetLeadingScriptType( const XclExpRoot& rRoot, const OUString& rString )
642 namespace ApiScriptType = ::com::sun::star::i18n::ScriptType;
643 Reference< XBreakIterator > xBreakIt = rRoot.GetDoc().GetBreakIterator();
644 sal_Int32 nStrPos = 0;
645 sal_Int32 nStrLen = rString.getLength();
646 sal_Int16 nScript = ApiScriptType::WEAK;
647 while( (nStrPos < nStrLen) && (nScript == ApiScriptType::WEAK) )
649 nScript = xBreakIt->getScriptType( rString, nStrPos );
650 nStrPos = xBreakIt->endOfScript( rString, nStrPos, nScript );
652 return (nScript == ApiScriptType::WEAK) ? rRoot.GetDefApiScript() : nScript;
655 // Header/footer conversion ===================================================
657 XclExpHFConverter::XclExpHFConverter( const XclExpRoot& rRoot ) :
658 XclExpRoot( rRoot ),
659 mrEE( rRoot.GetHFEditEngine() ),
660 mnTotalHeight( 0 )
664 void XclExpHFConverter::GenerateString(
665 const EditTextObject* pLeftObj,
666 const EditTextObject* pCenterObj,
667 const EditTextObject* pRightObj )
669 maHFString.clear();
670 mnTotalHeight = 0;
671 AppendPortion( pLeftObj, 'L' );
672 AppendPortion( pCenterObj, 'C' );
673 AppendPortion( pRightObj, 'R' );
676 void XclExpHFConverter::AppendPortion( const EditTextObject* pTextObj, sal_Unicode cPortionCode )
678 if( !pTextObj ) return;
680 OUString aText;
681 sal_Int32 nHeight = 0;
682 SfxItemSetFixed<ATTR_PATTERN_START, ATTR_PATTERN_END> aItemSet( *GetDoc().GetPool() );
684 // edit engine
685 bool bOldUpdateMode = mrEE.SetUpdateLayout( true );
686 mrEE.SetText( *pTextObj );
688 // font information
689 XclFontData aFontData, aNewData;
690 if( const XclExpFont* pFirstFont = GetFontBuffer().GetFont( EXC_FONT_APP ) )
692 aFontData = pFirstFont->GetFontData();
693 aFontData.mnHeight = (aFontData.mnHeight + 10) / 20; // using pt here, not twips
695 else
696 aFontData.mnHeight = 10;
698 const FontList* pFontList = nullptr;
699 if( SfxObjectShell* pDocShell = GetDocShell() )
701 if( const SvxFontListItem* pInfoItem = static_cast< const SvxFontListItem* >(
702 pDocShell->GetItem( SID_ATTR_CHAR_FONTLIST ) ) )
703 pFontList = pInfoItem->GetFontList();
706 sal_Int32 nParaCount = mrEE.GetParagraphCount();
707 for( sal_Int32 nPara = 0; nPara < nParaCount; ++nPara )
709 ESelection aSel( nPara, 0 );
710 OUStringBuffer aParaText;
711 sal_Int32 nParaHeight = 0;
712 std::vector<sal_Int32> aPosList;
713 mrEE.GetPortions( nPara, aPosList );
715 for( const auto& rPos : aPosList )
717 aSel.nEndPos = rPos;
718 if( aSel.nStartPos < aSel.nEndPos )
721 // --- font attributes ---
723 vcl::Font aFont;
724 aItemSet.ClearItem();
725 SfxItemSet aEditSet( mrEE.GetAttribs( aSel ) );
726 ScPatternAttr::GetFromEditItemSet( aItemSet, aEditSet );
727 ScPatternAttr::GetFont( aFont, aItemSet, SC_AUTOCOL_RAW );
729 // font name and style
730 aNewData.maName = XclTools::GetXclFontName( aFont.GetFamilyName() );
731 aNewData.mnWeight = (aFont.GetWeight() > WEIGHT_NORMAL) ? EXC_FONTWGHT_BOLD : EXC_FONTWGHT_NORMAL;
732 aNewData.mbItalic = (aFont.GetItalic() != ITALIC_NONE);
733 bool bNewFont = (aFontData.maName != aNewData.maName);
734 bool bNewStyle = (aFontData.mnWeight != aNewData.mnWeight) ||
735 (aFontData.mbItalic != aNewData.mbItalic);
736 if( bNewFont || (bNewStyle && pFontList) )
738 aParaText.append("&\"" + aNewData.maName);
739 if( pFontList )
741 FontMetric aFontMetric( pFontList->Get(
742 aNewData.maName,
743 (aNewData.mnWeight > EXC_FONTWGHT_NORMAL) ? WEIGHT_BOLD : WEIGHT_NORMAL,
744 aNewData.mbItalic ? ITALIC_NORMAL : ITALIC_NONE ) );
745 aNewData.maStyle = pFontList->GetStyleName( aFontMetric );
746 if( !aNewData.maStyle.isEmpty() )
747 aParaText.append("," + aNewData.maStyle);
749 aParaText.append("\"");
752 // height
753 // is calculated wrong in ScPatternAttr::GetFromEditItemSet, because already in twips and not 100thmm
754 // -> get it directly from edit engine item set
755 aNewData.mnHeight = ulimit_cast< sal_uInt16 >( aEditSet.Get( EE_CHAR_FONTHEIGHT ).GetHeight() );
756 aNewData.mnHeight = (aNewData.mnHeight + 10) / 20;
757 bool bFontHtChanged = (aFontData.mnHeight != aNewData.mnHeight);
758 if( bFontHtChanged )
759 aParaText.append("&" + OUString::number(aNewData.mnHeight));
760 // update maximum paragraph height, convert to twips
761 nParaHeight = ::std::max< sal_Int32 >( nParaHeight, aNewData.mnHeight * 20 );
763 // underline
764 aNewData.mnUnderline = EXC_FONTUNDERL_NONE;
765 switch( aFont.GetUnderline() )
767 case LINESTYLE_NONE: aNewData.mnUnderline = EXC_FONTUNDERL_NONE; break;
768 case LINESTYLE_SINGLE: aNewData.mnUnderline = EXC_FONTUNDERL_SINGLE; break;
769 case LINESTYLE_DOUBLE: aNewData.mnUnderline = EXC_FONTUNDERL_DOUBLE; break;
770 default: aNewData.mnUnderline = EXC_FONTUNDERL_SINGLE;
772 if( aFontData.mnUnderline != aNewData.mnUnderline )
774 sal_uInt8 nTmpUnderl = (aNewData.mnUnderline == EXC_FONTUNDERL_NONE) ?
775 aFontData.mnUnderline : aNewData.mnUnderline;
776 (nTmpUnderl == EXC_FONTUNDERL_SINGLE)? aParaText.append("&U") : aParaText.append("&E");
779 // font color
780 aNewData.maColor = aFont.GetColor();
781 if ( !aFontData.maColor.IsRGBEqual( aNewData.maColor ) )
783 aParaText.append("&K" + aNewData.maColor.AsRGBHexString());
786 // strikeout
787 aNewData.mbStrikeout = (aFont.GetStrikeout() != STRIKEOUT_NONE);
788 if( aFontData.mbStrikeout != aNewData.mbStrikeout )
789 aParaText.append("&S");
791 // super/sub script
792 const SvxEscapementItem& rEscapeItem = aEditSet.Get( EE_CHAR_ESCAPEMENT );
793 aNewData.SetScEscapement( rEscapeItem.GetEsc() );
794 if( aFontData.mnEscapem != aNewData.mnEscapem )
796 switch(aNewData.mnEscapem)
798 // close the previous super/sub script.
799 case EXC_FONTESC_NONE: (aFontData.mnEscapem == EXC_FONTESC_SUPER) ? aParaText.append("&X") : aParaText.append("&Y"); break;
800 case EXC_FONTESC_SUPER: aParaText.append("&X"); break;
801 case EXC_FONTESC_SUB: aParaText.append("&Y"); break;
802 default: break;
806 aFontData = aNewData;
808 // --- text content or text fields ---
810 const SfxPoolItem* pItem;
811 if( (aSel.nStartPos + 1 == aSel.nEndPos) && // fields are single characters
812 (aEditSet.GetItemState( EE_FEATURE_FIELD, false, &pItem ) == SfxItemState::SET) )
814 if( const SvxFieldData* pFieldData = static_cast< const SvxFieldItem* >( pItem )->GetField() )
816 if( dynamic_cast<const SvxPageField*>( pFieldData) != nullptr )
817 aParaText.append("&P");
818 else if( dynamic_cast<const SvxPagesField*>( pFieldData) != nullptr )
819 aParaText.append("&N");
820 else if( dynamic_cast<const SvxDateField*>( pFieldData) != nullptr )
821 aParaText.append("&D");
822 else if( dynamic_cast<const SvxTimeField*>( pFieldData) != nullptr || dynamic_cast<const SvxExtTimeField*>( pFieldData) != nullptr )
823 aParaText.append("&T");
824 else if( dynamic_cast<const SvxTableField*>( pFieldData) != nullptr )
825 aParaText.append("&A");
826 else if( dynamic_cast<const SvxFileField*>( pFieldData) != nullptr ) // title -> file name
827 aParaText.append("&F");
828 else if( const SvxExtFileField* pFileField = dynamic_cast<const SvxExtFileField*>( pFieldData ) )
830 switch( pFileField->GetFormat() )
832 case SvxFileFormat::NameAndExt:
833 case SvxFileFormat::NameOnly:
834 aParaText.append("&F");
835 break;
836 case SvxFileFormat::PathOnly:
837 aParaText.append("&Z");
838 break;
839 case SvxFileFormat::PathFull:
840 aParaText.append("&Z&F");
841 break;
842 default:
843 OSL_FAIL( "XclExpHFConverter::AppendPortion - unknown file field" );
848 else
850 OUString aPortionText( mrEE.GetText( aSel ) );
851 aPortionText = aPortionText.replaceAll( "&", "&&" );
852 // #i17440# space between font height and numbers in text
853 if( bFontHtChanged && aParaText.getLength() && !aPortionText.isEmpty() )
855 sal_Unicode cLast = aParaText[ aParaText.getLength() - 1 ];
856 sal_Unicode cFirst = aPortionText[0];
857 if( ('0' <= cLast) && (cLast <= '9') && ('0' <= cFirst) && (cFirst <= '9') )
858 aParaText.append(" ");
860 aParaText.append(aPortionText);
864 aSel.nStartPos = aSel.nEndPos;
867 aText = ScGlobal::addToken( aText, aParaText.makeStringAndClear(), '\n' );
868 if( nParaHeight == 0 )
869 nParaHeight = aFontData.mnHeight * 20; // points -> twips
870 nHeight += nParaHeight;
873 mrEE.SetUpdateLayout( bOldUpdateMode );
875 if( !aText.isEmpty() )
877 maHFString += "&" + OUStringChar(cPortionCode) + aText;
878 mnTotalHeight = ::std::max( mnTotalHeight, nHeight );
882 // URL conversion =============================================================
884 namespace {
886 /** Encodes special parts of the URL, i.e. directory separators and volume names.
887 @param pTableName Pointer to a table name to be encoded in this URL, or 0. */
888 OUString lclEncodeDosUrl(
889 XclBiff eBiff, const OUString& rUrl, std::u16string_view rBase, const OUString* pTableName)
891 OUStringBuffer aBuf;
893 if (!rUrl.isEmpty())
895 OUString aOldUrl = rUrl;
896 aBuf.append(EXC_URLSTART_ENCODED);
898 if ( aOldUrl.getLength() > 2 && aOldUrl.startsWith("\\\\") )
900 // UNC
901 aBuf.append(EXC_URL_DOSDRIVE).append('@');
902 aOldUrl = aOldUrl.copy(2);
904 else if ( aOldUrl.getLength() > 2 && aOldUrl.match(":\\", 1) )
906 // drive letter
907 sal_Unicode cThisDrive = rBase.empty() ? ' ' : rBase[0];
908 sal_Unicode cDrive = aOldUrl[0];
909 if (cThisDrive == cDrive)
910 // This document and the referenced document are under the same drive.
911 aBuf.append(EXC_URL_DRIVEROOT);
912 else
913 aBuf.append(EXC_URL_DOSDRIVE).append(cDrive);
914 aOldUrl = aOldUrl.copy(3);
916 else
918 // URL probably points to a document on a Unix-like file system
919 aBuf.append(EXC_URL_DRIVEROOT);
922 // directories
923 sal_Int32 nPos = -1;
924 while((nPos = aOldUrl.indexOf('\\')) != -1)
926 if ( aOldUrl.startsWith("..") )
927 // parent dir (NOTE: the MS-XLS spec doesn't mention this, and
928 // Excel seems confused by this token).
929 aBuf.append(EXC_URL_PARENTDIR);
930 else
931 aBuf.append(aOldUrl.subView(0,nPos)).append(EXC_URL_SUBDIR);
933 aOldUrl = aOldUrl.copy(nPos + 1);
936 // file name
937 if (pTableName) // enclose file name in brackets if table name follows
938 aBuf.append('[').append(aOldUrl).append(']');
939 else
940 aBuf.append(aOldUrl);
942 else // empty URL -> self reference
944 switch( eBiff )
946 case EXC_BIFF5:
947 aBuf.append(pTableName ? EXC_URLSTART_SELFENCODED : EXC_URLSTART_SELF);
948 break;
949 case EXC_BIFF8:
950 DBG_ASSERT( pTableName, "lclEncodeDosUrl - sheet name required for BIFF8" );
951 aBuf.append(EXC_URLSTART_SELF);
952 break;
953 default:
954 DBG_ERROR_BIFF();
958 // table name
959 if (pTableName)
960 aBuf.append(*pTableName);
962 // VirtualPath must be shorter than 255 chars ([MS-XLS].pdf 2.5.277)
963 // It's better to truncate, than generate invalid file that Excel cannot open.
964 if (aBuf.getLength() > 255)
965 aBuf.setLength(255);
967 return aBuf.makeStringAndClear();
970 } // namespace
972 OUString XclExpUrlHelper::EncodeUrl( const XclExpRoot& rRoot, const OUString& rAbsUrl, const OUString* pTableName )
974 OUString aDosUrl = INetURLObject(rAbsUrl).getFSysPath(FSysStyle::Dos);
975 OUString aDosBase = INetURLObject(rRoot.GetBasePath()).getFSysPath(FSysStyle::Dos);
976 return lclEncodeDosUrl(rRoot.GetBiff(), aDosUrl, aDosBase, pTableName);
979 OUString XclExpUrlHelper::EncodeDde( std::u16string_view rApplic, std::u16string_view rTopic )
981 return rApplic + OUStringChar(EXC_DDE_DELIM) + rTopic;
984 // Cached Value Lists =========================================================
986 XclExpCachedMatrix::XclExpCachedMatrix( const ScMatrix& rMatrix )
987 : mrMatrix( rMatrix )
989 mrMatrix.IncRef();
991 XclExpCachedMatrix::~XclExpCachedMatrix()
993 mrMatrix.DecRef();
996 void XclExpCachedMatrix::GetDimensions( SCSIZE & nCols, SCSIZE & nRows ) const
998 mrMatrix.GetDimensions( nCols, nRows );
1000 OSL_ENSURE( nCols && nRows, "XclExpCachedMatrix::GetDimensions - empty matrix" );
1001 OSL_ENSURE( nCols <= 256, "XclExpCachedMatrix::GetDimensions - too many columns" );
1004 std::size_t XclExpCachedMatrix::GetSize() const
1006 SCSIZE nCols, nRows;
1008 GetDimensions( nCols, nRows );
1010 /* The returned size may be wrong if the matrix contains strings. The only
1011 effect is that the export stream has to update a wrong record size which is
1012 faster than to iterate through all cached values and calculate their sizes. */
1013 return 3 + 9 * (nCols * nRows);
1016 void XclExpCachedMatrix::Save( XclExpStream& rStrm ) const
1018 SCSIZE nCols, nRows;
1020 GetDimensions( nCols, nRows );
1022 if( rStrm.GetRoot().GetBiff() <= EXC_BIFF5 )
1023 // in BIFF2-BIFF7: 256 columns represented by 0 columns
1024 rStrm << static_cast< sal_uInt8 >( nCols ) << static_cast< sal_uInt16 >( nRows );
1025 else
1026 // in BIFF8: columns and rows decreased by 1
1027 rStrm << static_cast< sal_uInt8 >( nCols - 1 ) << static_cast< sal_uInt16 >( nRows - 1 );
1029 for( SCSIZE nRow = 0; nRow < nRows; ++nRow )
1031 for( SCSIZE nCol = 0; nCol < nCols; ++nCol )
1033 ScMatrixValue nMatVal = mrMatrix.Get( nCol, nRow );
1035 FormulaError nScError;
1036 if( ScMatValType::Empty == nMatVal.nType )
1038 rStrm.SetSliceSize( 9 );
1039 rStrm << EXC_CACHEDVAL_EMPTY;
1040 rStrm.WriteZeroBytes( 8 );
1042 else if( ScMatrix::IsNonValueType( nMatVal.nType ) )
1044 XclExpString aStr( nMatVal.GetString().getString(), XclStrFlags::NONE );
1045 rStrm.SetSliceSize( 6 );
1046 rStrm << EXC_CACHEDVAL_STRING << aStr;
1048 else if( ScMatValType::Boolean == nMatVal.nType )
1050 sal_Int8 nBool = sal_Int8(nMatVal.GetBoolean());
1051 rStrm.SetSliceSize( 9 );
1052 rStrm << EXC_CACHEDVAL_BOOL << nBool;
1053 rStrm.WriteZeroBytes( 7 );
1055 else if( (nScError = nMatVal.GetError()) != FormulaError::NONE )
1057 sal_Int8 nError ( XclTools::GetXclErrorCode( nScError ) );
1058 rStrm.SetSliceSize( 9 );
1059 rStrm << EXC_CACHEDVAL_ERROR << nError;
1060 rStrm.WriteZeroBytes( 7 );
1062 else
1064 rStrm.SetSliceSize( 9 );
1065 rStrm << EXC_CACHEDVAL_DOUBLE << nMatVal.fVal;
1071 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */