1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
3 * This file is part of the LibreOffice project.
5 * This Source Code Form is subject to the terms of the Mozilla Public
6 * License, v. 2.0. If a copy of the MPL was not distributed with this
7 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
9 * This file incorporates work covered by the following license notice:
11 * Licensed to the Apache Software Foundation (ASF) under one or more
12 * contributor license agreements. See the NOTICE file distributed
13 * with this work for additional information regarding copyright
14 * ownership. The ASF licenses this file to you under the Apache
15 * License, Version 2.0 (the "License"); you may not use this file
16 * except in compliance with the License. You may obtain a copy of
17 * the License at http://www.apache.org/licenses/LICENSE-2.0 .
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>
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>
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
) :
71 mxProgress( new ScfProgressBar( rRoot
.GetDocShell(), STR_SAVE_DOC
) ),
72 mpSubProgress( nullptr ),
73 mpSubRowCreate( nullptr ),
74 mpSubRowFinal( nullptr ),
75 mnSegRowFinal( SCF_INV_SEGMENT
),
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()
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
);
131 mpSubProgress
= nullptr;
134 void XclExpProgressBar::ActivateFinalRowsSegment()
136 if( !mpSubRowFinal
&& (mnRowCount
> 0) )
138 mpSubRowFinal
= &mxProgress
->GetSegmentProgressBar( mnSegRowFinal
);
139 mpSubRowFinal
->AddSegment( mnRowCount
);
141 mpSubProgress
= mpSubRowFinal
;
143 mpSubProgress
->Activate();
146 void XclExpProgressBar::Progress()
148 if( mpSubProgress
&& !mpSubProgress
->IsFull() )
149 mpSubProgress
->Progress();
152 // Calc->Excel cell address/range conversion ==================================
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
);
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
;
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
);
193 bool XclExpAddressConverter::ConvertAddress( XclAddress
& rXclPos
,
194 const ScAddress
& rScPos
, bool bWarn
)
196 bool bValid
= CheckAddress( rScPos
, bWarn
);
198 lclFillAddress( rXclPos
, rScPos
.Col(), rScPos
.Row() );
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() ) );
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
);
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() ) );
238 bool XclExpAddressConverter::ConvertRange( XclRange
& rXclRange
,
239 const ScRange
& rScRange
, bool bWarn
)
241 // check start position
242 bool bValidStart
= CheckAddress( rScRange
.aStart
, bWarn
);
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
);
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
)
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 ==============================================
289 OUString
lclGetUrlRepresentation( const SvxURLField
& rUrlField
)
291 const OUString
& aRepr
= rUrlField
.GetRepresentation();
292 // no representation -> use URL
293 return aRepr
.isEmpty() ? rUrlField
.GetURL() : aRepr
;
298 XclExpHyperlinkHelper::XclExpHyperlinkHelper( const XclExpRoot
& rRoot
, const ScAddress
& rScPos
) :
301 mbMultipleUrls( false )
305 XclExpHyperlinkHelper::~XclExpHyperlinkHelper()
309 OUString
XclExpHyperlinkHelper::ProcessUrlField( const SvxURLField
& rUrlField
)
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() )
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() )
340 return XclExpHyperlinkRef();
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.
352 Cell attributes used for font formatting.
354 Modifiers for string export.
356 The maximum number of characters to store in this string.
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
;
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.
424 The edit engine in use. The text object must already be set.
426 Modifiers for string export.
428 The maximum number of characters to store in this string.
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
);
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
;
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);
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
);
551 xString
->AssignByte( rString
, rRoot
.GetTextEncoding(), nFlags
, nMaxLen
);
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
);
563 void XclExpStringHelper::AppendString( XclExpString
& rXclString
, const XclExpRoot
& rRoot
, std::u16string_view rString
)
565 if( rRoot
.GetBiff() == EXC_BIFF8
)
566 rXclString
.Append( rString
);
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
) );
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
;
593 ScEditEngineDefaulter
& rEE
= rRoot
.GetEditEngine();
594 bool bOldUpdateMode
= rEE
.SetUpdateLayout( true );
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
605 rEE
.SetTextCurrentDefaults(rEditText
);
606 xString
= lclCreateFormattedString( rRoot
, rEE
, &rLinkHelper
, nFlags
, nMaxLen
);
607 rEE
.SetUpdateLayout( bOldUpdateMode
);
612 XclExpStringRef
XclExpStringHelper::CreateString(
613 const XclExpRoot
& rRoot
, const SdrTextObj
& rTextObj
,
616 XclExpStringRef xString
;
617 if( const OutlinerParaObject
* pParaObj
= rTextObj
.GetOutlinerParaObject() )
619 EditEngine
& rEE
= rRoot
.GetDrawEditEngine();
620 bool bOldUpdateMode
= rEE
.SetUpdateLayout( true );
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
);
634 OSL_FAIL( "XclExpStringHelper::CreateString - textbox without para object" );
635 // create BIFF dependent empty Excel string
636 xString
= CreateString( rRoot
, OUString(), nFlags
);
641 XclExpStringRef
XclExpStringHelper::CreateString(
642 const XclExpRoot
& rRoot
, const EditTextObject
& rEditObj
,
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
);
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
) :
679 mrEE( rRoot
.GetHFEditEngine() ),
684 void XclExpHFConverter::GenerateString(
685 const EditTextObject
* pLeftObj
,
686 const EditTextObject
* pCenterObj
,
687 const EditTextObject
* pRightObj
)
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;
701 sal_Int32 nHeight
= 0;
702 SfxItemSetFixed
<ATTR_PATTERN_START
, ATTR_PATTERN_END
> aItemSet( *GetDoc().GetPool() );
705 bool bOldUpdateMode
= mrEE
.SetUpdateLayout( true );
706 mrEE
.SetText( *pTextObj
);
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
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 ---
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
);
763 FontMetric
aFontMetric( pFontList
->Get(
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("\"");
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
);
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 );
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");
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());
811 aNewData
.mbStrikeout
= (aFont
.GetStrikeout() != STRIKEOUT_NONE
);
812 if( aFontData
.mbStrikeout
!= aNewData
.mbStrikeout
)
813 aParaText
.append("&S");
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;
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");
860 case SvxFileFormat::PathOnly
:
861 aParaText
.append("&Z");
863 case SvxFileFormat::PathFull
:
864 aParaText
.append("&Z&F");
867 OSL_FAIL( "XclExpHFConverter::AppendPortion - unknown file field" );
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 =============================================================
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
)
920 aBuf
.append(EXC_URLSTART_ENCODED
);
922 if ( path
.length() > 2 && o3tl::starts_with(path
, u
"\\\\") )
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);
935 // URL probably points to a document on a Unix-like file system
936 aBuf
.append(EXC_URL_DRIVEROOT
);
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
);
948 aBuf
.append(path
.substr(0,nPos
) + OUStringChar(EXC_URL_SUBDIR
));
950 path
= path
.substr(nPos
+ 1);
954 if (pTableName
) // enclose file name in brackets if table name follows
955 aBuf
.append(OUString::Concat("[") + path
+ "]");
959 else // empty URL -> self reference
964 aBuf
.append(pTableName
? EXC_URLSTART_SELFENCODED
: EXC_URLSTART_SELF
);
967 DBG_ASSERT( pTableName
, "lclEncodeDosUrl - sheet name required for BIFF8" );
968 aBuf
.append(EXC_URLSTART_SELF
);
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)
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();
999 OUString
XclExpUrlHelper::EncodeUrl( const XclExpRoot
& rRoot
, std::u16string_view rAbsUrl
, const OUString
* pTableName
)
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
))
1016 osl::FileBase::getSystemPathFromFileURL(aUrlPath
, aDosPath
);
1017 aDosPath
= aDosPath
.replaceAll(u
"/", u
"\\");
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
)
1039 XclExpCachedMatrix::~XclExpCachedMatrix()
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
);
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 );
1112 rStrm
.SetSliceSize( 9 );
1113 rStrm
<< EXC_CACHEDVAL_DOUBLE
<< nMatVal
.fVal
;
1119 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */