use insert function instead of for loop
[LibreOffice.git] / sc / source / filter / excel / xehelper.cxx
blob18ea2dc3484a71a08699b8c2af194e1d51c5d47e
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 <com/sun/star/uri/XUriReference.hpp>
27 #include <com/sun/star/uri/XUriReferenceFactory.hpp>
28 #include <com/sun/star/uri/UriReferenceFactory.hpp>
29 #include <comphelper/processfactory.hxx>
30 #include <o3tl/string_view.hxx>
31 #include <sfx2/objsh.hxx>
32 #include <vcl/font.hxx>
33 #include <tools/urlobj.hxx>
34 #include <svl/itemset.hxx>
35 #include <svtools/ctrltool.hxx>
36 #include <svx/svdotext.hxx>
37 #include <editeng/outlobj.hxx>
38 #include <scitems.hxx>
39 #include <editeng/fhgtitem.hxx>
40 #include <editeng/flstitem.hxx>
41 #include <editeng/colritem.hxx>
42 #include <editeng/eeitem.hxx>
43 #include <editeng/flditem.hxx>
44 #include <editeng/escapementitem.hxx>
45 #include <editeng/svxfont.hxx>
46 #include <editeng/editids.hrc>
47 #include <osl/file.hxx>
49 #include <document.hxx>
50 #include <docpool.hxx>
51 #include <docsh.hxx>
52 #include <editutil.hxx>
53 #include <patattr.hxx>
54 #include <scmatrix.hxx>
55 #include <xestyle.hxx>
56 #include <fprogressbar.hxx>
57 #include <globstr.hrc>
58 #include <xltracer.hxx>
59 #include <xltools.hxx>
60 #include <xecontent.hxx>
61 #include <xelink.hxx>
62 #include <xehelper.hxx>
64 using ::com::sun::star::uno::Reference;
65 using ::com::sun::star::i18n::XBreakIterator;
67 // Export progress bar ========================================================
69 XclExpProgressBar::XclExpProgressBar( const XclExpRoot& rRoot ) :
70 XclExpRoot( rRoot ),
71 mxProgress( new ScfProgressBar( rRoot.GetDocShell(), STR_SAVE_DOC ) ),
72 mpSubProgress( nullptr ),
73 mpSubRowCreate( nullptr ),
74 mpSubRowFinal( nullptr ),
75 mnSegRowFinal( SCF_INV_SEGMENT ),
76 mnRowCount( 0 )
80 XclExpProgressBar::~XclExpProgressBar()
84 void XclExpProgressBar::Initialize()
86 const ScDocument& rDoc = GetDoc();
87 const XclExpTabInfo& rTabInfo = GetTabInfo();
88 SCTAB nScTabCount = rTabInfo.GetScTabCount();
90 // *** segment: creation of ROW records *** -------------------------------
92 sal_Int32 nSegRowCreate = mxProgress->AddSegment( 2000 );
93 mpSubRowCreate = &mxProgress->GetSegmentProgressBar( nSegRowCreate );
94 maSubSegRowCreate.resize( nScTabCount, SCF_INV_SEGMENT );
96 for( SCTAB nScTab = 0; nScTab < nScTabCount; ++nScTab )
98 if( rTabInfo.IsExportTab( nScTab ) )
100 SCCOL nLastUsedScCol;
101 SCROW nLastUsedScRow;
102 rDoc.GetTableArea( nScTab, nLastUsedScCol, nLastUsedScRow );
103 std::size_t nSegSize = static_cast< std::size_t >( nLastUsedScRow + 1 );
104 maSubSegRowCreate[ nScTab ] = mpSubRowCreate->AddSegment( nSegSize );
108 // *** segment: writing all ROW records *** -------------------------------
110 mnSegRowFinal = mxProgress->AddSegment( 1000 );
111 // sub progress bar and segment are created later in ActivateFinalRowsSegment()
114 void XclExpProgressBar::IncRowRecordCount()
116 ++mnRowCount;
119 void XclExpProgressBar::ActivateCreateRowsSegment()
121 OSL_ENSURE( (0 <= GetCurrScTab()) && (GetCurrScTab() < GetTabInfo().GetScTabCount()),
122 "XclExpProgressBar::ActivateCreateRowsSegment - invalid sheet" );
123 sal_Int32 nSeg = maSubSegRowCreate[ GetCurrScTab() ];
124 OSL_ENSURE( nSeg != SCF_INV_SEGMENT, "XclExpProgressBar::ActivateCreateRowsSegment - invalid segment" );
125 if( nSeg != SCF_INV_SEGMENT )
127 mpSubProgress = mpSubRowCreate;
128 mpSubProgress->ActivateSegment( nSeg );
130 else
131 mpSubProgress = nullptr;
134 void XclExpProgressBar::ActivateFinalRowsSegment()
136 if( !mpSubRowFinal && (mnRowCount > 0) )
138 mpSubRowFinal = &mxProgress->GetSegmentProgressBar( mnSegRowFinal );
139 mpSubRowFinal->AddSegment( mnRowCount );
141 mpSubProgress = mpSubRowFinal;
142 if( mpSubProgress )
143 mpSubProgress->Activate();
146 void XclExpProgressBar::Progress()
148 if( mpSubProgress && !mpSubProgress->IsFull() )
149 mpSubProgress->Progress();
152 // Calc->Excel cell address/range conversion ==================================
154 namespace {
156 /** Fills the passed Excel address with the passed Calc cell coordinates without checking any limits. */
157 void lclFillAddress( XclAddress& rXclPos, SCCOL nScCol, SCROW nScRow )
159 rXclPos.mnCol = static_cast< sal_uInt16 >( nScCol );
160 rXclPos.mnRow = static_cast< sal_uInt32 >( nScRow );
163 } // namespace
165 XclExpAddressConverter::XclExpAddressConverter( const XclExpRoot& rRoot ) :
166 XclAddressConverterBase( rRoot.GetTracer(), rRoot.GetXclMaxPos() )
170 // cell address ---------------------------------------------------------------
172 bool XclExpAddressConverter::CheckAddress( const ScAddress& rScPos, bool bWarn )
174 // ScAddress::operator<=() doesn't do what we want here
175 bool bValidCol = (0 <= rScPos.Col()) && (rScPos.Col() <= maMaxPos.Col());
176 bool bValidRow = (0 <= rScPos.Row()) && (rScPos.Row() <= maMaxPos.Row());
177 bool bValidTab = (0 <= rScPos.Tab()) && (rScPos.Tab() <= maMaxPos.Tab());
179 bool bValid = bValidCol && bValidRow && bValidTab;
180 if( !bValid )
182 mbColTrunc |= !bValidCol;
183 mbRowTrunc |= !bValidRow;
185 if( !bValid && bWarn )
187 mbTabTrunc |= (rScPos.Tab() > maMaxPos.Tab()); // do not warn for deleted refs
188 mrTracer.TraceInvalidAddress( rScPos, maMaxPos );
190 return bValid;
193 bool XclExpAddressConverter::ConvertAddress( XclAddress& rXclPos,
194 const ScAddress& rScPos, bool bWarn )
196 bool bValid = CheckAddress( rScPos, bWarn );
197 if( bValid )
198 lclFillAddress( rXclPos, rScPos.Col(), rScPos.Row() );
199 return bValid;
202 XclAddress XclExpAddressConverter::CreateValidAddress( const ScAddress& rScPos, bool bWarn )
204 XclAddress aXclPos( ScAddress::UNINITIALIZED );
205 if( !ConvertAddress( aXclPos, rScPos, bWarn ) )
206 lclFillAddress( aXclPos, ::std::min( rScPos.Col(), maMaxPos.Col() ), ::std::min( rScPos.Row(), maMaxPos.Row() ) );
207 return aXclPos;
210 // cell range -----------------------------------------------------------------
212 bool XclExpAddressConverter::CheckRange( const ScRange& rScRange, bool bWarn )
214 return CheckAddress( rScRange.aStart, bWarn ) && CheckAddress( rScRange.aEnd, bWarn );
217 bool XclExpAddressConverter::ValidateRange( ScRange& rScRange, bool bWarn )
219 rScRange.PutInOrder();
221 // check start position
222 bool bValidStart = CheckAddress( rScRange.aStart, bWarn );
223 if( bValidStart )
225 // check & correct end position
226 ScAddress& rScEnd = rScRange.aEnd;
227 if( !CheckAddress( rScEnd, bWarn ) )
229 rScEnd.SetCol( ::std::min( rScEnd.Col(), maMaxPos.Col() ) );
230 rScEnd.SetRow( ::std::min( rScEnd.Row(), maMaxPos.Row() ) );
231 rScEnd.SetTab( ::std::min( rScEnd.Tab(), maMaxPos.Tab() ) );
235 return bValidStart;
238 bool XclExpAddressConverter::ConvertRange( XclRange& rXclRange,
239 const ScRange& rScRange, bool bWarn )
241 // check start position
242 bool bValidStart = CheckAddress( rScRange.aStart, bWarn );
243 if( bValidStart )
245 lclFillAddress( rXclRange.maFirst, rScRange.aStart.Col(), rScRange.aStart.Row() );
247 // check & correct end position
248 SCCOL nScCol2 = rScRange.aEnd.Col();
249 SCROW nScRow2 = rScRange.aEnd.Row();
250 if( !CheckAddress( rScRange.aEnd, bWarn ) )
252 nScCol2 = ::std::min( nScCol2, maMaxPos.Col() );
253 nScRow2 = ::std::min( nScRow2, maMaxPos.Row() );
255 lclFillAddress( rXclRange.maLast, nScCol2, nScRow2 );
257 return bValidStart;
260 // cell range list ------------------------------------------------------------
262 void XclExpAddressConverter::ValidateRangeList( ScRangeList& rScRanges, bool bWarn )
264 for ( size_t nRange = rScRanges.size(); nRange > 0; )
266 ScRange & rScRange = rScRanges[ --nRange ];
267 if( !CheckRange( rScRange, bWarn ) )
268 rScRanges.Remove(nRange);
272 void XclExpAddressConverter::ConvertRangeList( XclRangeList& rXclRanges,
273 const ScRangeList& rScRanges, bool bWarn )
275 rXclRanges.clear();
276 for( size_t nPos = 0, nCount = rScRanges.size(); nPos < nCount; ++nPos )
278 const ScRange & rScRange = rScRanges[ nPos ];
279 XclRange aXclRange( ScAddress::UNINITIALIZED );
280 if( ConvertRange( aXclRange, rScRange, bWarn ) )
281 rXclRanges.push_back( aXclRange );
285 // EditEngine->String conversion ==============================================
287 namespace {
289 OUString lclGetUrlRepresentation( const SvxURLField& rUrlField )
291 const OUString& aRepr = rUrlField.GetRepresentation();
292 // no representation -> use URL
293 return aRepr.isEmpty() ? rUrlField.GetURL() : aRepr;
296 } // namespace
298 XclExpHyperlinkHelper::XclExpHyperlinkHelper( const XclExpRoot& rRoot, const ScAddress& rScPos ) :
299 XclExpRoot( rRoot ),
300 maScPos( rScPos ),
301 mbMultipleUrls( false )
305 XclExpHyperlinkHelper::~XclExpHyperlinkHelper()
309 OUString XclExpHyperlinkHelper::ProcessUrlField( const SvxURLField& rUrlField )
311 OUString aUrlRepr;
313 if( GetBiff() == EXC_BIFF8 ) // no HLINK records in BIFF2-BIFF7
315 // there was/is already a HLINK record
316 mbMultipleUrls = static_cast< bool >(mxLinkRec);
318 mxLinkRec = new XclExpHyperlink( GetRoot(), rUrlField, maScPos );
320 if( const OUString* pRepr = mxLinkRec->GetRepr() )
321 aUrlRepr = *pRepr;
323 // add URL to note text
324 maUrlList = ScGlobal::addToken( maUrlList, rUrlField.GetURL(), '\n' );
327 // no hyperlink representation from Excel HLINK record -> use it from text field
328 return aUrlRepr.isEmpty() ? lclGetUrlRepresentation(rUrlField) : aUrlRepr;
331 bool XclExpHyperlinkHelper::HasLinkRecord() const
333 return !mbMultipleUrls && mxLinkRec;
336 XclExpHyperlinkHelper::XclExpHyperlinkRef XclExpHyperlinkHelper::GetLinkRecord() const
338 if( HasLinkRecord() )
339 return mxLinkRec;
340 return XclExpHyperlinkRef();
343 namespace {
345 /** Creates a new formatted string from the passed unformatted string.
347 Creates a Unicode string or a byte string, depending on the current BIFF
348 version contained in the passed XclExpRoot object. May create a formatted
349 string object, if the text contains different script types.
351 @param pCellAttr
352 Cell attributes used for font formatting.
353 @param nFlags
354 Modifiers for string export.
355 @param nMaxLen
356 The maximum number of characters to store in this string.
357 @return
358 The new string object.
360 XclExpStringRef lclCreateFormattedString(
361 const XclExpRoot& rRoot, const OUString& rText, const ScPatternAttr* pCellAttr,
362 XclStrFlags nFlags, sal_uInt16 nMaxLen )
364 /* Create an empty Excel string object with correctly initialized BIFF mode,
365 because this function only uses Append() functions that require this. */
366 XclExpStringRef xString = XclExpStringHelper::CreateString( rRoot, OUString(), nFlags, nMaxLen );
368 // script type handling
369 Reference< XBreakIterator > xBreakIt = rRoot.GetDoc().GetBreakIterator();
370 namespace ApiScriptType = css::i18n::ScriptType;
371 // #i63255# get script type for leading weak characters
372 sal_Int16 nLastScript = XclExpStringHelper::GetLeadingScriptType( rRoot, rText );
374 // font buffer and cell item set
375 XclExpFontBuffer& rFontBuffer = rRoot.GetFontBuffer();
376 const SfxItemSet& rItemSet = pCellAttr ?
377 pCellAttr->GetItemSet() :
378 rRoot.GetDoc().getCellAttributeHelper().getDefaultCellAttribute().GetItemSet();
380 // process all script portions
381 sal_Int32 nPortionPos = 0;
382 sal_Int32 nTextLen = rText.getLength();
383 while( nPortionPos < nTextLen )
385 // get script type and end position of next script portion
386 sal_Int16 nScript = xBreakIt->getScriptType( rText, nPortionPos );
387 sal_Int32 nPortionEnd = xBreakIt->endOfScript( rText, nPortionPos, nScript );
389 // reuse previous script for following weak portions
390 if( nScript == ApiScriptType::WEAK )
391 nScript = nLastScript;
393 // construct font from current text portion
394 SvxFont aFont(XclExpFontHelper::GetFontFromItemSet(rRoot, rItemSet, nScript));
395 model::ComplexColor aComplexColor;
396 ScPatternAttr::fillColor(aComplexColor, rItemSet, ScAutoFontColorMode::Raw);
398 // Excel start position of this portion
399 sal_Int32 nXclPortionStart = xString->Len();
400 // add portion text to Excel string
401 XclExpStringHelper::AppendString( *xString, rRoot, rText.subView( nPortionPos, nPortionEnd - nPortionPos ) );
402 if( nXclPortionStart < xString->Len() )
404 // insert font into buffer
405 sal_uInt16 nFontIdx = rFontBuffer.Insert(aFont, aComplexColor, EXC_COLOR_CELLTEXT);
406 // insert font index into format run vector
407 xString->AppendFormat( nXclPortionStart, nFontIdx );
410 // go to next script portion
411 nLastScript = nScript;
412 nPortionPos = nPortionEnd;
415 return xString;
418 /** Creates a new formatted string from an edit engine text object.
420 Creates a Unicode string or a byte string, depending on the current BIFF
421 version contained in the passed XclExpRoot object.
423 @param rEE
424 The edit engine in use. The text object must already be set.
425 @param nFlags
426 Modifiers for string export.
427 @param nMaxLen
428 The maximum number of characters to store in this string.
429 @return
430 The new string object.
432 XclExpStringRef lclCreateFormattedString(
433 const XclExpRoot& rRoot, EditEngine& rEE, XclExpHyperlinkHelper* pLinkHelper,
434 XclStrFlags nFlags, sal_uInt16 nMaxLen )
436 /* Create an empty Excel string object with correctly initialized BIFF mode,
437 because this function only uses Append() functions that require this. */
438 XclExpStringRef xString = XclExpStringHelper::CreateString( rRoot, OUString(), nFlags, nMaxLen );
440 // font buffer and helper item set for edit engine -> Calc item conversion
441 XclExpFontBuffer& rFontBuffer = rRoot.GetFontBuffer();
442 SfxItemSetFixed<ATTR_PATTERN_START, ATTR_PATTERN_END> aItemSet( *rRoot.GetDoc().GetPool() );
444 // script type handling
445 Reference< XBreakIterator > xBreakIt = rRoot.GetDoc().GetBreakIterator();
446 namespace ApiScriptType = css::i18n::ScriptType;
447 // #i63255# get script type for leading weak characters
448 sal_Int16 nLastScript = XclExpStringHelper::GetLeadingScriptType( rRoot, rEE.GetText() );
450 // process all paragraphs
451 sal_Int32 nParaCount = rEE.GetParagraphCount();
452 for( sal_Int32 nPara = 0; nPara < nParaCount; ++nPara )
454 ESelection aSel( nPara, 0 );
455 OUString aParaText( rEE.GetText( nPara ) );
457 std::vector<sal_Int32> aPosList;
458 rEE.GetPortions( nPara, aPosList );
460 // process all portions in the paragraph
461 for( const auto& rPos : aPosList )
463 aSel.end.nIndex = rPos;
464 OUString aXclPortionText = aParaText.copy( aSel.start.nIndex, aSel.end.nIndex - aSel.start.nIndex );
466 aItemSet.ClearItem();
467 SfxItemSet aEditSet( rEE.GetAttribs( aSel ) );
468 ScPatternAttr::GetFromEditItemSet( aItemSet, aEditSet );
470 // get escapement value
471 short nEsc = aEditSet.Get( EE_CHAR_ESCAPEMENT ).GetEsc();
473 // process text fields
474 bool bIsHyperlink = false;
475 if (aSel.start.nIndex + 1 == aSel.end.nIndex)
477 // test if the character is a text field
478 if( const SvxFieldItem* pItem = aEditSet.GetItemIfSet( EE_FEATURE_FIELD, false ) )
480 const SvxFieldData* pField = pItem->GetField();
481 if( const SvxURLField* pUrlField = dynamic_cast<const SvxURLField*>( pField ) )
483 // convert URL field to string representation
484 aXclPortionText = pLinkHelper ?
485 pLinkHelper->ProcessUrlField( *pUrlField ) :
486 lclGetUrlRepresentation( *pUrlField );
487 bIsHyperlink = true;
489 else
491 OSL_FAIL( "lclCreateFormattedString - unknown text field" );
492 aXclPortionText.clear();
497 // Excel start position of this portion
498 sal_Int32 nXclPortionStart = xString->Len();
499 // add portion text to Excel string
500 XclExpStringHelper::AppendString( *xString, rRoot, aXclPortionText );
501 if( (nXclPortionStart < xString->Len()) || (aParaText.isEmpty()) )
503 /* Construct font from current edit engine text portion. Edit engine
504 creates different portions for different script types, no need to loop. */
505 sal_Int16 nScript = xBreakIt->getScriptType( aXclPortionText, 0 );
506 if( nScript == ApiScriptType::WEAK )
507 nScript = nLastScript;
508 SvxFont aFont( XclExpFontHelper::GetFontFromItemSet(rRoot, aItemSet, nScript));
509 model::ComplexColor aComplexColor;
510 ScPatternAttr::fillColor(aComplexColor, aItemSet, ScAutoFontColorMode::Raw);
512 nLastScript = nScript;
514 // add escapement
515 aFont.SetEscapement( nEsc );
516 // modify automatic font color for hyperlinks
517 if (bIsHyperlink && aItemSet.Get(ATTR_FONT_COLOR).GetValue() == COL_AUTO)
518 aComplexColor.setFinalColor(COL_LIGHTBLUE);
520 // insert font into buffer
521 sal_uInt16 nFontIdx = rFontBuffer.Insert(aFont, aComplexColor, EXC_COLOR_CELLTEXT);
522 // insert font index into format run vector
523 xString->AppendFormat( nXclPortionStart, nFontIdx );
526 aSel.start.nIndex = aSel.end.nIndex;
529 // add trailing newline (important for correct character index calculation)
530 if( nPara + 1 < nParaCount )
531 XclExpStringHelper::AppendChar( *xString, rRoot, '\n' );
534 if (xString->HasNewline() && nParaCount == 1)
536 // Found buggy Excel behaviour: although the content has newlines, it has not been wrapped.
537 xString->SetSingleLineForMultipleParagraphs(true);
539 return xString;
542 } // namespace
544 XclExpStringRef XclExpStringHelper::CreateString(
545 const XclExpRoot& rRoot, const OUString& rString, XclStrFlags nFlags, sal_uInt16 nMaxLen )
547 XclExpStringRef xString = std::make_shared<XclExpString>();
548 if( rRoot.GetBiff() == EXC_BIFF8 )
549 xString->Assign( rString, nFlags, nMaxLen );
550 else
551 xString->AssignByte( rString, rRoot.GetTextEncoding(), nFlags, nMaxLen );
552 return xString;
555 XclExpStringRef XclExpStringHelper::CreateString(
556 const XclExpRoot& rRoot, sal_Unicode cChar, XclStrFlags nFlags, sal_uInt16 nMaxLen )
558 XclExpStringRef xString = CreateString( rRoot, OUString(), nFlags, nMaxLen );
559 AppendChar( *xString, rRoot, cChar );
560 return xString;
563 void XclExpStringHelper::AppendString( XclExpString& rXclString, const XclExpRoot& rRoot, std::u16string_view rString )
565 if( rRoot.GetBiff() == EXC_BIFF8 )
566 rXclString.Append( rString );
567 else
568 rXclString.AppendByte( rString, rRoot.GetTextEncoding() );
571 void XclExpStringHelper::AppendChar( XclExpString& rXclString, const XclExpRoot& rRoot, sal_Unicode cChar )
573 if( rRoot.GetBiff() == EXC_BIFF8 )
574 rXclString.Append( rtl::OUStringChar(cChar) );
575 else
576 rXclString.AppendByte( cChar, rRoot.GetTextEncoding() );
579 XclExpStringRef XclExpStringHelper::CreateCellString(
580 const XclExpRoot& rRoot, const OUString& rString, const ScPatternAttr* pCellAttr,
581 XclStrFlags nFlags, sal_uInt16 nMaxLen )
583 return lclCreateFormattedString(rRoot, rString, pCellAttr, nFlags, nMaxLen);
586 XclExpStringRef XclExpStringHelper::CreateCellString(
587 const XclExpRoot& rRoot, const EditTextObject& rEditText, const ScPatternAttr* pCellAttr,
588 XclExpHyperlinkHelper& rLinkHelper, XclStrFlags nFlags, sal_uInt16 nMaxLen )
590 XclExpStringRef xString;
592 // formatted cell
593 ScEditEngineDefaulter& rEE = rRoot.GetEditEngine();
594 bool bOldUpdateMode = rEE.SetUpdateLayout( true );
596 // default items
597 const SfxItemSet& rItemSet = pCellAttr ?
598 pCellAttr->GetItemSet() :
599 rRoot.GetDoc().getCellAttributeHelper().getDefaultCellAttribute().GetItemSet();
600 auto pEEItemSet = std::make_unique<SfxItemSet>( rEE.GetEmptyItemSet() );
601 ScPatternAttr::FillToEditItemSet( *pEEItemSet, rItemSet );
602 rEE.SetDefaults( std::move(pEEItemSet) ); // edit engine takes ownership
604 // create the string
605 rEE.SetTextCurrentDefaults(rEditText);
606 xString = lclCreateFormattedString( rRoot, rEE, &rLinkHelper, nFlags, nMaxLen );
607 rEE.SetUpdateLayout( bOldUpdateMode );
609 return xString;
612 XclExpStringRef XclExpStringHelper::CreateString(
613 const XclExpRoot& rRoot, const SdrTextObj& rTextObj,
614 XclStrFlags nFlags )
616 XclExpStringRef xString;
617 if( const OutlinerParaObject* pParaObj = rTextObj.GetOutlinerParaObject() )
619 EditEngine& rEE = rRoot.GetDrawEditEngine();
620 bool bOldUpdateMode = rEE.SetUpdateLayout( true );
621 // create the string
622 rEE.SetText( pParaObj->GetTextObject() );
623 xString = lclCreateFormattedString( rRoot, rEE, nullptr, nFlags, EXC_STR_MAXLEN );
624 rEE.SetUpdateLayout( bOldUpdateMode );
625 // limit formats - TODO: BIFF dependent
626 if( !xString->IsEmpty() )
628 xString->LimitFormatCount( EXC_MAXRECSIZE_BIFF8 / 8 - 1 );
629 xString->AppendTrailingFormat( EXC_FONT_APP );
632 else
634 OSL_FAIL( "XclExpStringHelper::CreateString - textbox without para object" );
635 // create BIFF dependent empty Excel string
636 xString = CreateString( rRoot, OUString(), nFlags );
638 return xString;
641 XclExpStringRef XclExpStringHelper::CreateString(
642 const XclExpRoot& rRoot, const EditTextObject& rEditObj,
643 XclStrFlags nFlags )
645 XclExpStringRef xString;
646 EditEngine& rEE = rRoot.GetDrawEditEngine();
647 bool bOldUpdateMode = rEE.SetUpdateLayout( true );
648 rEE.SetText( rEditObj );
649 xString = lclCreateFormattedString( rRoot, rEE, nullptr, nFlags, EXC_STR_MAXLEN );
650 rEE.SetUpdateLayout( bOldUpdateMode );
651 // limit formats - TODO: BIFF dependent
652 if( !xString->IsEmpty() )
654 xString->LimitFormatCount( EXC_MAXRECSIZE_BIFF8 / 8 - 1 );
655 xString->AppendTrailingFormat( EXC_FONT_APP );
657 return xString;
660 sal_Int16 XclExpStringHelper::GetLeadingScriptType( const XclExpRoot& rRoot, const OUString& rString )
662 namespace ApiScriptType = css::i18n::ScriptType;
663 Reference< XBreakIterator > xBreakIt = rRoot.GetDoc().GetBreakIterator();
664 sal_Int32 nStrPos = 0;
665 sal_Int32 nStrLen = rString.getLength();
666 sal_Int16 nScript = ApiScriptType::WEAK;
667 while( (nStrPos < nStrLen) && (nScript == ApiScriptType::WEAK) )
669 nScript = xBreakIt->getScriptType( rString, nStrPos );
670 nStrPos = xBreakIt->endOfScript( rString, nStrPos, nScript );
672 return (nScript == ApiScriptType::WEAK) ? rRoot.GetDefApiScript() : nScript;
675 // Header/footer conversion ===================================================
677 XclExpHFConverter::XclExpHFConverter( const XclExpRoot& rRoot ) :
678 XclExpRoot( rRoot ),
679 mrEE( rRoot.GetHFEditEngine() ),
680 mnTotalHeight( 0 )
684 void XclExpHFConverter::GenerateString(
685 const EditTextObject* pLeftObj,
686 const EditTextObject* pCenterObj,
687 const EditTextObject* pRightObj )
689 maHFString.clear();
690 mnTotalHeight = 0;
691 AppendPortion( pLeftObj, 'L' );
692 AppendPortion( pCenterObj, 'C' );
693 AppendPortion( pRightObj, 'R' );
696 void XclExpHFConverter::AppendPortion( const EditTextObject* pTextObj, sal_Unicode cPortionCode )
698 if( !pTextObj ) return;
700 OUString aText;
701 sal_Int32 nHeight = 0;
702 SfxItemSetFixed<ATTR_PATTERN_START, ATTR_PATTERN_END> aItemSet( *GetDoc().GetPool() );
704 // edit engine
705 bool bOldUpdateMode = mrEE.SetUpdateLayout( true );
706 mrEE.SetText( *pTextObj );
708 // font information
709 XclFontData aFontData, aNewData;
710 if( const XclExpFont* pFirstFont = GetFontBuffer().GetFont( EXC_FONT_APP ) )
712 aFontData = pFirstFont->GetFontData();
713 aFontData.mnHeight = (aFontData.mnHeight + 10) / 20; // using pt here, not twips
715 else
716 aFontData.mnHeight = 10;
718 const FontList* pFontList = nullptr;
719 if( SfxObjectShell* pDocShell = GetDocShell() )
721 if( const SvxFontListItem* pInfoItem = static_cast< const SvxFontListItem* >(
722 pDocShell->GetItem( SID_ATTR_CHAR_FONTLIST ) ) )
723 pFontList = pInfoItem->GetFontList();
726 sal_Int32 nParaCount = mrEE.GetParagraphCount();
727 for( sal_Int32 nPara = 0; nPara < nParaCount; ++nPara )
729 ESelection aSel( nPara, 0 );
730 OUStringBuffer aParaText;
731 sal_Int32 nParaHeight = 0;
732 std::vector<sal_Int32> aPosList;
733 mrEE.GetPortions( nPara, aPosList );
735 for( const auto& rPos : aPosList )
737 aSel.end.nIndex = rPos;
738 if (aSel.start.nIndex < aSel.end.nIndex)
741 // --- font attributes ---
743 vcl::Font aFont;
744 model::ComplexColor aComplexColor;
745 aItemSet.ClearItem();
746 SfxItemSet aEditSet( mrEE.GetAttribs( aSel ) );
747 ScPatternAttr::GetFromEditItemSet( aItemSet, aEditSet );
748 ScPatternAttr::fillFontOnly(aFont, aItemSet);
749 ScPatternAttr::fillColor(aComplexColor, aItemSet, ScAutoFontColorMode::Raw);
751 // font name and style
752 aNewData.maName = XclTools::GetXclFontName( aFont.GetFamilyName() );
753 aNewData.mnWeight = (aFont.GetWeight() > WEIGHT_NORMAL) ? EXC_FONTWGHT_BOLD : EXC_FONTWGHT_NORMAL;
754 aNewData.mbItalic = (aFont.GetItalic() != ITALIC_NONE);
755 bool bNewFont = (aFontData.maName != aNewData.maName);
756 bool bNewStyle = (aFontData.mnWeight != aNewData.mnWeight) ||
757 (aFontData.mbItalic != aNewData.mbItalic);
758 if( bNewFont || (bNewStyle && pFontList) )
760 aParaText.append("&\"" + aNewData.maName);
761 if( pFontList )
763 FontMetric aFontMetric( pFontList->Get(
764 aNewData.maName,
765 (aNewData.mnWeight > EXC_FONTWGHT_NORMAL) ? WEIGHT_BOLD : WEIGHT_NORMAL,
766 aNewData.mbItalic ? ITALIC_NORMAL : ITALIC_NONE ) );
767 aNewData.maStyle = pFontList->GetStyleName( aFontMetric );
768 if( !aNewData.maStyle.isEmpty() )
769 aParaText.append("," + aNewData.maStyle);
771 aParaText.append("\"");
774 // height
775 // is calculated wrong in ScPatternAttr::GetFromEditItemSet, because already in twips and not 100thmm
776 // -> get it directly from edit engine item set
777 aNewData.mnHeight = ulimit_cast< sal_uInt16 >( aEditSet.Get( EE_CHAR_FONTHEIGHT ).GetHeight() );
778 aNewData.mnHeight = (aNewData.mnHeight + 10) / 20;
779 bool bFontHtChanged = (aFontData.mnHeight != aNewData.mnHeight);
780 if( bFontHtChanged )
781 aParaText.append("&" + OUString::number(aNewData.mnHeight));
782 // update maximum paragraph height, convert to twips
783 nParaHeight = ::std::max< sal_Int32 >( nParaHeight, aNewData.mnHeight * 20 );
785 // underline
786 aNewData.mnUnderline = EXC_FONTUNDERL_NONE;
787 switch( aFont.GetUnderline() )
789 case LINESTYLE_NONE: aNewData.mnUnderline = EXC_FONTUNDERL_NONE; break;
790 case LINESTYLE_SINGLE: aNewData.mnUnderline = EXC_FONTUNDERL_SINGLE; break;
791 case LINESTYLE_DOUBLE: aNewData.mnUnderline = EXC_FONTUNDERL_DOUBLE; break;
792 default: aNewData.mnUnderline = EXC_FONTUNDERL_SINGLE;
794 if( aFontData.mnUnderline != aNewData.mnUnderline )
796 sal_uInt8 nTmpUnderl = (aNewData.mnUnderline == EXC_FONTUNDERL_NONE) ?
797 aFontData.mnUnderline : aNewData.mnUnderline;
798 (nTmpUnderl == EXC_FONTUNDERL_SINGLE)? aParaText.append("&U") : aParaText.append("&E");
801 // font color
802 aNewData.maComplexColor = std::move(aComplexColor);
803 Color aNewColor = aNewData.maComplexColor.getFinalColor();
805 if (!aFontData.maComplexColor.getFinalColor().IsRGBEqual(aNewColor))
807 aParaText.append("&K" + aNewColor.AsRGBHexString());
810 // strikeout
811 aNewData.mbStrikeout = (aFont.GetStrikeout() != STRIKEOUT_NONE);
812 if( aFontData.mbStrikeout != aNewData.mbStrikeout )
813 aParaText.append("&S");
815 // super/sub script
816 const SvxEscapementItem& rEscapeItem = aEditSet.Get( EE_CHAR_ESCAPEMENT );
817 aNewData.SetScEscapement( rEscapeItem.GetEsc() );
818 if( aFontData.mnEscapem != aNewData.mnEscapem )
820 switch(aNewData.mnEscapem)
822 // close the previous super/sub script.
823 case EXC_FONTESC_NONE: (aFontData.mnEscapem == EXC_FONTESC_SUPER) ? aParaText.append("&X") : aParaText.append("&Y"); break;
824 case EXC_FONTESC_SUPER: aParaText.append("&X"); break;
825 case EXC_FONTESC_SUB: aParaText.append("&Y"); break;
826 default: break;
830 aFontData = aNewData;
832 // --- text content or text fields ---
834 const SvxFieldItem* pItem;
835 if( (aSel.start.nIndex + 1 == aSel.end.nIndex) && // fields are single characters
836 (pItem = aEditSet.GetItemIfSet( EE_FEATURE_FIELD, false )) )
838 if( const SvxFieldData* pFieldData = pItem->GetField() )
840 if( dynamic_cast<const SvxPageField*>( pFieldData) != nullptr )
841 aParaText.append("&P");
842 else if( dynamic_cast<const SvxPagesField*>( pFieldData) != nullptr )
843 aParaText.append("&N");
844 else if( dynamic_cast<const SvxDateField*>( pFieldData) != nullptr )
845 aParaText.append("&D");
846 else if( dynamic_cast<const SvxTimeField*>( pFieldData) != nullptr || dynamic_cast<const SvxExtTimeField*>( pFieldData) != nullptr )
847 aParaText.append("&T");
848 else if( dynamic_cast<const SvxTableField*>( pFieldData) != nullptr )
849 aParaText.append("&A");
850 else if( dynamic_cast<const SvxFileField*>( pFieldData) != nullptr ) // title -> file name
851 aParaText.append("&F");
852 else if( const SvxExtFileField* pFileField = dynamic_cast<const SvxExtFileField*>( pFieldData ) )
854 switch( pFileField->GetFormat() )
856 case SvxFileFormat::NameAndExt:
857 case SvxFileFormat::NameOnly:
858 aParaText.append("&F");
859 break;
860 case SvxFileFormat::PathOnly:
861 aParaText.append("&Z");
862 break;
863 case SvxFileFormat::PathFull:
864 aParaText.append("&Z&F");
865 break;
866 default:
867 OSL_FAIL( "XclExpHFConverter::AppendPortion - unknown file field" );
872 else
874 OUString aPortionText( mrEE.GetText( aSel ) );
875 aPortionText = aPortionText.replaceAll( "&", "&&" );
876 // #i17440# space between font height and numbers in text
877 if( bFontHtChanged && aParaText.getLength() && !aPortionText.isEmpty() )
879 sal_Unicode cLast = aParaText[ aParaText.getLength() - 1 ];
880 sal_Unicode cFirst = aPortionText[0];
881 if( ('0' <= cLast) && (cLast <= '9') && ('0' <= cFirst) && (cFirst <= '9') )
882 aParaText.append(" ");
884 aParaText.append(aPortionText);
888 aSel.start.nIndex = aSel.end.nIndex;
891 aText = ScGlobal::addToken( aText, aParaText, '\n' );
892 aParaText.setLength(0);
893 if( nParaHeight == 0 )
894 nParaHeight = aFontData.mnHeight * 20; // points -> twips
895 nHeight += nParaHeight;
898 mrEE.SetUpdateLayout( bOldUpdateMode );
900 if( !aText.isEmpty() )
902 maHFString += "&" + OUStringChar(cPortionCode) + aText;
903 mnTotalHeight = ::std::max( mnTotalHeight, nHeight );
907 // URL conversion =============================================================
909 namespace {
911 /** Encodes special parts of the path, i.e. directory separators and volume names.
912 @param pTableName Pointer to a table name to be encoded in this path, or 0. */
913 OUString lclEncodeDosPath(
914 XclBiff eBiff, std::u16string_view path, bool bIsRel, const OUString* pTableName)
916 OUStringBuffer aBuf;
918 if (!path.empty())
920 aBuf.append(EXC_URLSTART_ENCODED);
922 if ( path.length() > 2 && o3tl::starts_with(path, u"\\\\") )
924 // UNC
925 aBuf.append(OUStringChar(EXC_URL_DOSDRIVE) + "@");
926 path = path.substr(2);
928 else if ( path.length() > 2 && o3tl::starts_with(path.substr(1), u":\\") )
930 aBuf.append(OUStringChar(EXC_URL_DOSDRIVE) + OUStringChar(path[0]));
931 path = path.substr(3);
933 else if ( !bIsRel )
935 // URL probably points to a document on a Unix-like file system
936 aBuf.append(EXC_URL_DRIVEROOT);
939 // directories
940 auto nPos = std::u16string_view::npos;
941 while((nPos = path.find('\\')) != std::u16string_view::npos)
943 if ( o3tl::starts_with(path, u"..") )
944 // parent dir (NOTE: the MS-XLS spec doesn't mention this, and
945 // Excel seems confused by this token).
946 aBuf.append(EXC_URL_PARENTDIR);
947 else
948 aBuf.append(path.substr(0,nPos) + OUStringChar(EXC_URL_SUBDIR));
950 path = path.substr(nPos + 1);
953 // file name
954 if (pTableName) // enclose file name in brackets if table name follows
955 aBuf.append(OUString::Concat("[") + path + "]");
956 else
957 aBuf.append(path);
959 else // empty URL -> self reference
961 switch( eBiff )
963 case EXC_BIFF5:
964 aBuf.append(pTableName ? EXC_URLSTART_SELFENCODED : EXC_URLSTART_SELF);
965 break;
966 case EXC_BIFF8:
967 DBG_ASSERT( pTableName, "lclEncodeDosUrl - sheet name required for BIFF8" );
968 aBuf.append(EXC_URLSTART_SELF);
969 break;
970 default:
971 DBG_ERROR_BIFF();
975 // table name
976 if (pTableName)
977 aBuf.append(*pTableName);
979 // VirtualPath must be shorter than 255 chars ([MS-XLS].pdf 2.5.277)
980 // It's better to truncate, than generate invalid file that Excel cannot open.
981 if (aBuf.getLength() > 255)
982 aBuf.setLength(255);
984 return aBuf.makeStringAndClear();
987 bool isUrlRelative(const OUString& aUrl)
989 css::uno::Reference<css::uri::XUriReferenceFactory> xUriFactory(
990 css::uri::UriReferenceFactory::create(
991 comphelper::getProcessComponentContext()));
992 css::uno::Reference<css::uri::XUriReference> xUri(xUriFactory->parse(aUrl));
994 return !xUri->isAbsolute();
997 } // namespace
999 OUString XclExpUrlHelper::EncodeUrl( const XclExpRoot& rRoot, std::u16string_view rAbsUrl, const OUString* pTableName )
1001 OUString aDosPath;
1002 bool bIsRel = false;
1004 if (rRoot.IsRelUrl())
1006 OUString aUrlPath = INetURLObject::GetRelURL(
1007 rRoot.GetBasePath(), OUString(rAbsUrl),
1008 INetURLObject::EncodeMechanism::All,
1009 INetURLObject::DecodeMechanism::NONE,
1010 RTL_TEXTENCODING_UTF8, FSysStyle::Detect
1013 if (isUrlRelative(aUrlPath))
1015 bIsRel = true;
1016 osl::FileBase::getSystemPathFromFileURL(aUrlPath, aDosPath);
1017 aDosPath = aDosPath.replaceAll(u"/", u"\\");
1021 if (!bIsRel)
1022 aDosPath = INetURLObject(rAbsUrl).getFSysPath(FSysStyle::Dos);
1024 return lclEncodeDosPath(rRoot.GetBiff(), aDosPath, bIsRel, pTableName);
1027 OUString XclExpUrlHelper::EncodeDde( std::u16string_view rApplic, std::u16string_view rTopic )
1029 return rApplic + OUStringChar(EXC_DDE_DELIM) + rTopic;
1032 // Cached Value Lists =========================================================
1034 XclExpCachedMatrix::XclExpCachedMatrix( const ScMatrix& rMatrix )
1035 : mrMatrix( rMatrix )
1037 mrMatrix.IncRef();
1039 XclExpCachedMatrix::~XclExpCachedMatrix()
1041 mrMatrix.DecRef();
1044 void XclExpCachedMatrix::GetDimensions( SCSIZE & nCols, SCSIZE & nRows ) const
1046 mrMatrix.GetDimensions( nCols, nRows );
1048 OSL_ENSURE( nCols && nRows, "XclExpCachedMatrix::GetDimensions - empty matrix" );
1049 OSL_ENSURE( nCols <= 256, "XclExpCachedMatrix::GetDimensions - too many columns" );
1052 std::size_t XclExpCachedMatrix::GetSize() const
1054 SCSIZE nCols, nRows;
1056 GetDimensions( nCols, nRows );
1058 /* The returned size may be wrong if the matrix contains strings. The only
1059 effect is that the export stream has to update a wrong record size which is
1060 faster than to iterate through all cached values and calculate their sizes. */
1061 return 3 + 9 * (nCols * nRows);
1064 void XclExpCachedMatrix::Save( XclExpStream& rStrm ) const
1066 SCSIZE nCols, nRows;
1068 GetDimensions( nCols, nRows );
1070 if( rStrm.GetRoot().GetBiff() <= EXC_BIFF5 )
1071 // in BIFF2-BIFF7: 256 columns represented by 0 columns
1072 rStrm << static_cast< sal_uInt8 >( nCols ) << static_cast< sal_uInt16 >( nRows );
1073 else
1074 // in BIFF8: columns and rows decreased by 1
1075 rStrm << static_cast< sal_uInt8 >( nCols - 1 ) << static_cast< sal_uInt16 >( nRows - 1 );
1077 for( SCSIZE nRow = 0; nRow < nRows; ++nRow )
1079 for( SCSIZE nCol = 0; nCol < nCols; ++nCol )
1081 ScMatrixValue nMatVal = mrMatrix.Get( nCol, nRow );
1083 FormulaError nScError;
1084 if( ScMatValType::Empty == nMatVal.nType )
1086 rStrm.SetSliceSize( 9 );
1087 rStrm << EXC_CACHEDVAL_EMPTY;
1088 rStrm.WriteZeroBytes( 8 );
1090 else if( ScMatrix::IsNonValueType( nMatVal.nType ) )
1092 XclExpString aStr( nMatVal.GetString().getString(), XclStrFlags::NONE );
1093 rStrm.SetSliceSize( 6 );
1094 rStrm << EXC_CACHEDVAL_STRING << aStr;
1096 else if( ScMatValType::Boolean == nMatVal.nType )
1098 sal_Int8 nBool = sal_Int8(nMatVal.GetBoolean());
1099 rStrm.SetSliceSize( 9 );
1100 rStrm << EXC_CACHEDVAL_BOOL << nBool;
1101 rStrm.WriteZeroBytes( 7 );
1103 else if( (nScError = nMatVal.GetError()) != FormulaError::NONE )
1105 sal_Int8 nError ( XclTools::GetXclErrorCode( nScError ) );
1106 rStrm.SetSliceSize( 9 );
1107 rStrm << EXC_CACHEDVAL_ERROR << nError;
1108 rStrm.WriteZeroBytes( 7 );
1110 else
1112 rStrm.SetSliceSize( 9 );
1113 rStrm << EXC_CACHEDVAL_DOUBLE << nMatVal.fVal;
1119 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */