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 .
21 #include <sal/config.h>
23 #include <comphelper/string.hxx>
25 #include <scitems.hxx>
27 #include <editeng/colritem.hxx>
28 #include <editeng/brushitem.hxx>
29 #include <editeng/editeng.hxx>
30 #include <editeng/fhgtitem.hxx>
31 #include <editeng/fontitem.hxx>
32 #include <editeng/postitem.hxx>
33 #include <editeng/udlnitem.hxx>
34 #include <editeng/wghtitem.hxx>
35 #include <editeng/borderline.hxx>
36 #include <editeng/boxitem.hxx>
37 #include <editeng/justifyitem.hxx>
38 #include <sal/log.hxx>
39 #include <sfx2/objsh.hxx>
40 #include <svl/numformat.hxx>
41 #include <svl/intitem.hxx>
43 #include <vcl/graphicfilter.hxx>
44 #include <svtools/parhtml.hxx>
45 #include <svtools/htmlkywd.hxx>
46 #include <svtools/htmltokn.h>
48 #include <vcl/outdev.hxx>
49 #include <vcl/svapp.hxx>
50 #include <tools/urlobj.hxx>
51 #include <osl/diagnose.h>
52 #include <o3tl/string_view.hxx>
54 #include <rtl/tencinfo.h>
57 #include <htmlpars.hxx>
59 #include <document.hxx>
60 #include <rangelst.hxx>
62 #include <orcus/css_parser.hpp>
64 #include <com/sun/star/document/XDocumentProperties.hpp>
65 #include <com/sun/star/document/XDocumentPropertiesSupplier.hpp>
66 #include <com/sun/star/frame/XModel.hpp>
68 #include <officecfg/Office/Common.hxx>
70 using ::editeng::SvxBorderLine
;
71 using namespace ::com::sun::star
;
73 ScHTMLStyles::ScHTMLStyles() : maEmpty() {}
75 void ScHTMLStyles::add(const char* pElemName
, size_t nElemName
, const char* pClassName
, size_t nClassName
,
76 const OUString
& aProp
, const OUString
& aValue
)
80 OUString
aElem(pElemName
, nElemName
, RTL_TEXTENCODING_UTF8
);
81 aElem
= aElem
.toAsciiLowerCase();
84 // Both element and class names given.
85 ElemsType::iterator itrElem
= m_ElemProps
.find(aElem
);
86 if (itrElem
== m_ElemProps
.end())
89 std::pair
<ElemsType::iterator
, bool> r
=
90 m_ElemProps
.insert(std::make_pair(aElem
, NamePropsType()));
97 NamePropsType
& rClsProps
= itrElem
->second
;
98 OUString
aClass(pClassName
, nClassName
, RTL_TEXTENCODING_UTF8
);
99 aClass
= aClass
.toAsciiLowerCase();
100 insertProp(rClsProps
, aClass
, aProp
, aValue
);
104 // Element name only. Add it to the element global.
105 insertProp(m_ElemGlobalProps
, aElem
, aProp
, aValue
);
112 // Class name only. Add it to the global.
113 OUString
aClass(pClassName
, nClassName
, RTL_TEXTENCODING_UTF8
);
114 aClass
= aClass
.toAsciiLowerCase();
115 insertProp(m_GlobalProps
, aClass
, aProp
, aValue
);
120 const OUString
& ScHTMLStyles::getPropertyValue(
121 const OUString
& rElem
, const OUString
& rClass
, const OUString
& rPropName
) const
123 // First, look into the element-class storage.
125 auto const itr
= m_ElemProps
.find(rElem
);
126 if (itr
!= m_ElemProps
.end())
128 const NamePropsType
& rClasses
= itr
->second
;
129 NamePropsType::const_iterator itr2
= rClasses
.find(rClass
);
130 if (itr2
!= rClasses
.end())
132 const PropsType
& rProps
= itr2
->second
;
133 PropsType::const_iterator itr3
= rProps
.find(rPropName
);
134 if (itr3
!= rProps
.end())
139 // Next, look into the class global storage.
141 auto const itr
= m_GlobalProps
.find(rClass
);
142 if (itr
!= m_GlobalProps
.end())
144 const PropsType
& rProps
= itr
->second
;
145 PropsType::const_iterator itr2
= rProps
.find(rPropName
);
146 if (itr2
!= rProps
.end())
150 // As the last resort, look into the element global storage.
152 auto const itr
= m_ElemGlobalProps
.find(rClass
);
153 if (itr
!= m_ElemGlobalProps
.end())
155 const PropsType
& rProps
= itr
->second
;
156 PropsType::const_iterator itr2
= rProps
.find(rPropName
);
157 if (itr2
!= rProps
.end())
162 return maEmpty
; // nothing found.
165 void ScHTMLStyles::insertProp(
166 NamePropsType
& rStore
, const OUString
& aName
,
167 const OUString
& aProp
, const OUString
& aValue
)
169 NamePropsType::iterator itr
= rStore
.find(aName
);
170 if (itr
== rStore
.end())
173 std::pair
<NamePropsType::iterator
, bool> r
=
174 rStore
.insert(std::make_pair(aName
, PropsType()));
182 PropsType
& rProps
= itr
->second
;
183 rProps
.emplace(aProp
, aValue
);
186 // BASE class for HTML parser classes
188 ScHTMLParser::ScHTMLParser( EditEngine
* pEditEngine
, ScDocument
* pDoc
) :
189 ScEEParser( pEditEngine
),
192 maFontHeights
[0] = officecfg::Office::Common::Filter::HTML::Import::FontSize::Size_1::get() * 20;
193 maFontHeights
[1] = officecfg::Office::Common::Filter::HTML::Import::FontSize::Size_2::get() * 20;
194 maFontHeights
[2] = officecfg::Office::Common::Filter::HTML::Import::FontSize::Size_3::get() * 20;
195 maFontHeights
[3] = officecfg::Office::Common::Filter::HTML::Import::FontSize::Size_4::get() * 20;
196 maFontHeights
[4] = officecfg::Office::Common::Filter::HTML::Import::FontSize::Size_5::get() * 20;
197 maFontHeights
[5] = officecfg::Office::Common::Filter::HTML::Import::FontSize::Size_6::get() * 20;
198 maFontHeights
[6] = officecfg::Office::Common::Filter::HTML::Import::FontSize::Size_7::get() * 20;
201 ScHTMLParser::~ScHTMLParser()
205 ScHTMLLayoutParser::ScHTMLLayoutParser(
206 EditEngine
* pEditP
, OUString _aBaseURL
, const Size
& aPageSizeP
,
207 ScDocument
* pDocP
) :
208 ScHTMLParser( pEditP
, pDocP
),
209 aPageSize( aPageSizeP
),
210 aBaseURL(std::move( _aBaseURL
)),
211 xLockedList( new ScRangeList
),
212 pLocalColOffset( new ScHTMLColOffset
),
222 nOffsetTolerance( SC_HTML_OFFSET_TOLERANCE_SMALL
),
224 bTabInTabCell( false ),
228 MakeColNoRef( pLocalColOffset
, 0, 0, 0, 0 );
229 MakeColNoRef( &maColOffset
, 0, 0, 0, 0 );
232 ScHTMLLayoutParser::~ScHTMLLayoutParser()
234 while ( !aTableStack
.empty() )
236 ScHTMLTableStackEntry
* pS
= aTableStack
.top().get();
237 if ( pS
->pLocalColOffset
!= pLocalColOffset
)
238 delete pS
->pLocalColOffset
;
241 delete pLocalColOffset
;
244 for( const auto& rEntry
: *pTables
)
245 delete rEntry
.second
;
250 ErrCode
ScHTMLLayoutParser::Read( SvStream
& rStream
, const OUString
& rBaseURL
)
252 Link
<HtmlImportInfo
&,void> aOldLink
= pEdit
->GetHtmlImportHdl();
253 pEdit
->SetHtmlImportHdl( LINK( this, ScHTMLLayoutParser
, HTMLImportHdl
) );
255 SfxObjectShell
* pObjSh
= mpDoc
->GetDocumentShell();
256 bool bLoading
= pObjSh
&& pObjSh
->IsLoading();
258 SvKeyValueIteratorRef xValues
;
259 SvKeyValueIterator
* pAttributes
= nullptr;
261 pAttributes
= pObjSh
->GetHeaderAttributes();
264 // When not loading, set up fake http headers to force the SfxHTMLParser to use UTF8
265 // (used when pasting from clipboard)
266 const char* pCharSet
= rtl_getBestMimeCharsetFromTextEncoding( RTL_TEXTENCODING_UTF8
);
269 OUString aContentType
= "text/html; charset=" +
270 OUString::createFromAscii( pCharSet
);
272 xValues
= new SvKeyValueIterator
;
273 xValues
->Append( SvKeyValue( OOO_STRING_SVTOOLS_HTML_META_content_type
, aContentType
) );
274 pAttributes
= xValues
.get();
278 ErrCode nErr
= pEdit
->Read( rStream
, rBaseURL
, EETextFormat::Html
, pAttributes
);
280 pEdit
->SetHtmlImportHdl( aOldLink
);
281 // Create column width
283 OutputDevice
* pDefaultDev
= Application::GetDefaultDevice();
284 sal_uInt16 nCount
= maColOffset
.size();
285 sal_uLong nOff
= maColOffset
[0];
287 for ( sal_uInt16 j
= 1; j
< nCount
; j
++ )
289 aSize
.setWidth( maColOffset
[j
] - nOff
);
290 aSize
= pDefaultDev
->PixelToLogic( aSize
, MapMode( MapUnit::MapTwip
) );
291 maColWidths
[ j
-1 ] = aSize
.Width();
292 nOff
= maColOffset
[j
];
297 const ScHTMLTable
* ScHTMLLayoutParser::GetGlobalTable() const
302 void ScHTMLLayoutParser::NewActEntry( const ScEEParseEntry
* pE
)
304 ScEEParser::NewActEntry( pE
);
307 if ( !pE
->aSel
.HasRange() )
308 { // Completely empty, following text ends up in the same paragraph!
309 mxActEntry
->aSel
.nStartPara
= pE
->aSel
.nEndPara
;
310 mxActEntry
->aSel
.nStartPos
= pE
->aSel
.nEndPos
;
313 mxActEntry
->aSel
.nEndPara
= mxActEntry
->aSel
.nStartPara
;
314 mxActEntry
->aSel
.nEndPos
= mxActEntry
->aSel
.nStartPos
;
317 void ScHTMLLayoutParser::EntryEnd( ScEEParseEntry
* pE
, const ESelection
& rSel
)
319 if ( rSel
.nEndPara
>= pE
->aSel
.nStartPara
)
321 pE
->aSel
.nEndPara
= rSel
.nEndPara
;
322 pE
->aSel
.nEndPos
= rSel
.nEndPos
;
324 else if ( rSel
.nStartPara
== pE
->aSel
.nStartPara
- 1 && !pE
->aSel
.HasRange() )
325 { // Did not attach a paragraph, but empty, do nothing
329 OSL_FAIL( "EntryEnd: EditEngine ESelection End < Start" );
333 void ScHTMLLayoutParser::NextRow( const HtmlImportInfo
* pInfo
)
337 if ( nRowMax
< ++nRowCnt
)
339 nColCnt
= nColCntStart
;
340 nColOffset
= nColOffsetStart
;
344 bool ScHTMLLayoutParser::SeekOffset( const ScHTMLColOffset
* pOffset
, sal_uInt16 nOffset
,
345 SCCOL
* pCol
, sal_uInt16 nOffsetTol
)
347 OSL_ENSURE( pOffset
, "ScHTMLLayoutParser::SeekOffset - illegal call" );
348 ScHTMLColOffset::const_iterator it
= pOffset
->find( nOffset
);
349 bool bFound
= it
!= pOffset
->end();
350 sal_uInt16 nPos
= it
- pOffset
->begin();
351 *pCol
= static_cast<SCCOL
>(nPos
);
354 sal_uInt16 nCount
= pOffset
->size();
357 // nPos is the position of insertion, that's where the next higher one is (or isn't)
358 if ( nPos
< nCount
&& (((*pOffset
)[nPos
] - nOffsetTol
) <= nOffset
) )
360 // Not smaller than everything else? Then compare with the next lower one
361 else if ( nPos
&& (((*pOffset
)[nPos
-1] + nOffsetTol
) >= nOffset
) )
369 void ScHTMLLayoutParser::MakeCol( ScHTMLColOffset
* pOffset
, sal_uInt16
& nOffset
,
370 sal_uInt16
& nWidth
, sal_uInt16 nOffsetTol
, sal_uInt16 nWidthTol
)
372 OSL_ENSURE( pOffset
, "ScHTMLLayoutParser::MakeCol - illegal call" );
374 if ( SeekOffset( pOffset
, nOffset
, &nPos
, nOffsetTol
) )
375 nOffset
= static_cast<sal_uInt16
>((*pOffset
)[nPos
]);
377 pOffset
->insert( nOffset
);
380 if ( SeekOffset( pOffset
, nOffset
+ nWidth
, &nPos
, nWidthTol
) )
381 nWidth
= static_cast<sal_uInt16
>((*pOffset
)[nPos
]) - nOffset
;
383 pOffset
->insert( nOffset
+ nWidth
);
387 void ScHTMLLayoutParser::MakeColNoRef( ScHTMLColOffset
* pOffset
, sal_uInt16 nOffset
,
388 sal_uInt16 nWidth
, sal_uInt16 nOffsetTol
, sal_uInt16 nWidthTol
)
390 OSL_ENSURE( pOffset
, "ScHTMLLayoutParser::MakeColNoRef - illegal call" );
392 if ( SeekOffset( pOffset
, nOffset
, &nPos
, nOffsetTol
) )
393 nOffset
= static_cast<sal_uInt16
>((*pOffset
)[nPos
]);
395 pOffset
->insert( nOffset
);
398 if ( !SeekOffset( pOffset
, nOffset
+ nWidth
, &nPos
, nWidthTol
) )
399 pOffset
->insert( nOffset
+ nWidth
);
403 void ScHTMLLayoutParser::ModifyOffset( ScHTMLColOffset
* pOffset
, sal_uInt16
& nOldOffset
,
404 sal_uInt16
& nNewOffset
, sal_uInt16 nOffsetTol
)
406 OSL_ENSURE( pOffset
, "ScHTMLLayoutParser::ModifyOffset - illegal call" );
408 if ( !SeekOffset( pOffset
, nOldOffset
, &nPos
, nOffsetTol
) )
410 if ( SeekOffset( pOffset
, nNewOffset
, &nPos
, nOffsetTol
) )
411 nNewOffset
= static_cast<sal_uInt16
>((*pOffset
)[nPos
]);
413 pOffset
->insert( nNewOffset
);
416 nOldOffset
= static_cast<sal_uInt16
>((*pOffset
)[nPos
]);
418 if ( SeekOffset( pOffset
, nNewOffset
, &nPos2
, nOffsetTol
) )
420 nNewOffset
= static_cast<sal_uInt16
>((*pOffset
)[nPos2
]);
423 tools::Long nDiff
= nNewOffset
- nOldOffset
;
428 const_cast<sal_uLong
&>((*pOffset
)[nPos
]) += nDiff
;
435 const_cast<sal_uLong
&>((*pOffset
)[nPos
]) += nDiff
;
436 } while ( ++nPos
< static_cast<sal_uInt16
>(pOffset
->size()) );
440 void ScHTMLLayoutParser::SkipLocked( ScEEParseEntry
* pE
, bool bJoin
)
442 if ( !mpDoc
->ValidCol(pE
->nCol
) )
445 // Or else this would create a wrong value at ScAddress (chance for an infinite loop)!
446 bool bBadCol
= false;
448 ScRange
aRange( pE
->nCol
, pE
->nRow
, 0,
449 pE
->nCol
+ pE
->nColOverlap
- 1, pE
->nRow
+ pE
->nRowOverlap
- 1, 0 );
453 for ( size_t i
= 0, nRanges
= xLockedList
->size(); i
< nRanges
; ++i
)
455 ScRange
& rR
= (*xLockedList
)[i
];
456 if ( rR
.Intersects( aRange
) )
458 pE
->nCol
= rR
.aEnd
.Col() + 1;
459 SCCOL nTmp
= pE
->nCol
+ pE
->nColOverlap
- 1;
460 if ( pE
->nCol
> mpDoc
->MaxCol() || nTmp
> mpDoc
->MaxCol() )
465 aRange
.aStart
.SetCol( pE
->nCol
);
466 aRange
.aEnd
.SetCol( nTmp
);
472 if ( bJoin
&& !bBadCol
)
473 xLockedList
->Join( aRange
);
476 void ScHTMLLayoutParser::Adjust()
478 xLockedList
->RemoveAll();
480 std::stack
< std::unique_ptr
<ScHTMLAdjustStackEntry
> > aStack
;
482 SCCOL nLastCol
= SCCOL_MAX
;
485 sal_uInt16 nPageWidth
= static_cast<sal_uInt16
>(aPageSize
.Width());
486 InnerMap
* pTab
= nullptr;
487 for (auto& pE
: maList
)
489 if ( pE
->nTab
< nTab
)
491 if ( !aStack
.empty() )
493 std::unique_ptr
<ScHTMLAdjustStackEntry
> pS
= std::move(aStack
.top());
496 nLastCol
= pS
->nLastCol
;
497 nNextRow
= pS
->nNextRow
;
498 nCurRow
= pS
->nCurRow
;
503 OuterMap::const_iterator it
= pTables
->find( nTab
);
504 if ( it
!= pTables
->end() )
509 SCROW nRow
= pE
->nRow
;
510 if ( pE
->nCol
<= nLastCol
)
512 if ( pE
->nRow
< nNextRow
)
513 pE
->nRow
= nCurRow
= nNextRow
;
515 nCurRow
= nNextRow
= pE
->nRow
;
519 InnerMap::const_iterator it
= pTab
->find( nCurRow
);
520 if ( it
!= pTab
->end() )
530 nLastCol
= pE
->nCol
; // Read column
531 if ( pE
->nTab
> nTab
)
533 aStack
.push( std::make_unique
<ScHTMLAdjustStackEntry
>(
534 nLastCol
, nNextRow
, nCurRow
) );
538 OuterMap::const_iterator it
= pTables
->find( nTab
);
539 if ( it
!= pTables
->end() )
546 InnerMap::const_iterator it
= pTab
->find( nCurRow
);
547 if ( it
!= pTab
->end() )
551 nNextRow
= nCurRow
+ nR
;
553 nNextRow
= nCurRow
+ 1;
556 pE
->nWidth
= nPageWidth
;
558 { // Real table, no paragraphs on the field
561 SCROW nRowSpan
= pE
->nRowOverlap
;
562 for ( SCROW j
=0; j
< nRowSpan
; j
++ )
563 { // RowSpan resulting from merged rows
565 InnerMap::const_iterator it
= pTab
->find( nRow
+j
);
566 if ( it
!= pTab
->end() )
570 pE
->nRowOverlap
+= nRows
- 1;
572 { // Merged rows move the next row
573 SCROW nTmp
= nCurRow
+ nRows
;
574 if ( nNextRow
< nTmp
)
582 (void)SeekOffset( &maColOffset
, pE
->nOffset
, &pE
->nCol
, nOffsetTolerance
);
583 SCCOL nColBeforeSkip
= pE
->nCol
;
584 SkipLocked(pE
.get(), false);
585 if ( pE
->nCol
!= nColBeforeSkip
)
587 SCCOL nCount
= static_cast<SCCOL
>(maColOffset
.size());
588 if ( nCount
<= pE
->nCol
)
590 pE
->nOffset
= static_cast<sal_uInt16
>(maColOffset
[nCount
-1]);
591 MakeCol( &maColOffset
, pE
->nOffset
, pE
->nWidth
, nOffsetTolerance
, nOffsetTolerance
);
595 pE
->nOffset
= static_cast<sal_uInt16
>(maColOffset
[pE
->nCol
]);
599 if ( pE
->nWidth
&& SeekOffset( &maColOffset
, pE
->nOffset
+ pE
->nWidth
, &nPos
, nOffsetTolerance
) )
600 pE
->nColOverlap
= (nPos
> pE
->nCol
? nPos
- pE
->nCol
: 1);
603 //FIXME: This may not be correct, but works anyway ...
606 xLockedList
->Join( ScRange( pE
->nCol
, pE
->nRow
, 0,
607 pE
->nCol
+ pE
->nColOverlap
- 1, pE
->nRow
+ pE
->nRowOverlap
- 1, 0 ) );
608 // Take over MaxDimensions
609 SCCOL nColTmp
= pE
->nCol
+ pE
->nColOverlap
;
610 if ( nColMax
< nColTmp
)
612 SCROW nRowTmp
= pE
->nRow
+ pE
->nRowOverlap
;
613 if ( nRowMax
< nRowTmp
)
618 sal_uInt16
ScHTMLLayoutParser::GetWidth( const ScEEParseEntry
* pE
)
622 sal_Int32 nTmp
= std::min( static_cast<sal_Int32
>( pE
->nCol
-
623 nColCntStart
+ pE
->nColOverlap
),
624 static_cast<sal_Int32
>( pLocalColOffset
->size() - 1));
625 SCCOL nPos
= (nTmp
< 0 ? 0 : static_cast<SCCOL
>(nTmp
));
626 sal_uInt16 nOff2
= static_cast<sal_uInt16
>((*pLocalColOffset
)[nPos
]);
627 if ( pE
->nOffset
< nOff2
)
628 return nOff2
- pE
->nOffset
;
632 void ScHTMLLayoutParser::SetWidths()
636 nTableWidth
= static_cast<sal_uInt16
>(aPageSize
.Width());
637 SCCOL nColsPerRow
= nMaxCol
- nColCntStart
;
638 if ( nColsPerRow
<= 0 )
640 if ( pLocalColOffset
->size() <= 2 )
641 { // Only PageSize, there was no width setting
642 sal_uInt16 nWidth
= nTableWidth
/ static_cast<sal_uInt16
>(nColsPerRow
);
643 sal_uInt16 nOff
= nColOffsetStart
;
644 pLocalColOffset
->clear();
645 for ( nCol
= 0; nCol
<= nColsPerRow
; ++nCol
, nOff
= nOff
+ nWidth
)
647 MakeColNoRef( pLocalColOffset
, nOff
, 0, 0, 0 );
649 nTableWidth
= static_cast<sal_uInt16
>(pLocalColOffset
->back() - pLocalColOffset
->front());
650 for ( size_t i
= nFirstTableCell
, nListSize
= maList
.size(); i
< nListSize
; ++i
)
652 auto& pE
= maList
[ i
];
653 if ( pE
->nTab
== nTable
)
655 pE
->nOffset
= static_cast<sal_uInt16
>((*pLocalColOffset
)[pE
->nCol
- nColCntStart
]);
656 pE
->nWidth
= 0; // to be recalculated later
661 { // Some without width
662 // Why actually no pE?
663 if ( nFirstTableCell
< maList
.size() )
665 std::unique_ptr
<sal_uInt16
[]> pOffsets(new sal_uInt16
[ nColsPerRow
+1 ]);
666 memset( pOffsets
.get(), 0, (nColsPerRow
+1) * sizeof(sal_uInt16
) );
667 std::unique_ptr
<sal_uInt16
[]> pWidths(new sal_uInt16
[ nColsPerRow
]);
668 memset( pWidths
.get(), 0, nColsPerRow
* sizeof(sal_uInt16
) );
669 pOffsets
[0] = nColOffsetStart
;
670 for ( size_t i
= nFirstTableCell
, nListSize
= maList
.size(); i
< nListSize
; ++i
)
672 auto& pE
= maList
[ i
];
673 if ( pE
->nTab
== nTable
&& pE
->nWidth
)
675 nCol
= pE
->nCol
- nColCntStart
;
676 if ( nCol
< nColsPerRow
)
678 if ( pE
->nColOverlap
== 1 )
680 if ( pWidths
[nCol
] < pE
->nWidth
)
681 pWidths
[nCol
] = pE
->nWidth
;
684 { // try to find a single undefined width
685 sal_uInt16 nTotal
= 0;
688 SCCOL nStop
= std::min( static_cast<SCCOL
>(nCol
+ pE
->nColOverlap
), nColsPerRow
);
689 for ( ; nCol
< nStop
; nCol
++ )
692 nTotal
= nTotal
+ pWidths
[nCol
];
704 if ( bFound
&& pE
->nWidth
> nTotal
)
705 pWidths
[nHere
] = pE
->nWidth
- nTotal
;
710 sal_uInt16 nWidths
= 0;
711 sal_uInt16 nUnknown
= 0;
712 for ( nCol
= 0; nCol
< nColsPerRow
; nCol
++ )
715 nWidths
= nWidths
+ pWidths
[nCol
];
721 sal_uInt16 nW
= ((nWidths
< nTableWidth
) ?
722 ((nTableWidth
- nWidths
) / nUnknown
) :
723 (nTableWidth
/ nUnknown
));
724 for ( nCol
= 0; nCol
< nColsPerRow
; nCol
++ )
726 if ( !pWidths
[nCol
] )
730 for ( nCol
= 1; nCol
<= nColsPerRow
; nCol
++ )
732 pOffsets
[nCol
] = pOffsets
[nCol
-1] + pWidths
[nCol
-1];
734 pLocalColOffset
->clear();
735 for ( nCol
= 0; nCol
<= nColsPerRow
; nCol
++ )
737 MakeColNoRef( pLocalColOffset
, pOffsets
[nCol
], 0, 0, 0 );
739 nTableWidth
= pOffsets
[nColsPerRow
] - pOffsets
[0];
741 for ( size_t i
= nFirstTableCell
, nListSize
= maList
.size(); i
< nListSize
; ++i
)
743 auto& pE
= maList
[ i
];
744 if ( pE
->nTab
== nTable
)
746 nCol
= pE
->nCol
- nColCntStart
;
747 OSL_ENSURE( nCol
< nColsPerRow
, "ScHTMLLayoutParser::SetWidths: column overflow" );
748 if ( nCol
< nColsPerRow
)
750 pE
->nOffset
= pOffsets
[nCol
];
751 nCol
= nCol
+ pE
->nColOverlap
;
752 if ( nCol
> nColsPerRow
)
754 pE
->nWidth
= pOffsets
[nCol
] - pE
->nOffset
;
760 if ( !pLocalColOffset
->empty() )
762 sal_uInt16 nMax
= static_cast<sal_uInt16
>(pLocalColOffset
->back());
763 if ( aPageSize
.Width() < nMax
)
764 aPageSize
.setWidth( nMax
);
765 if (nTableLevel
== 0)
767 // Local table is very outer table, create missing offsets.
768 for (auto it
= pLocalColOffset
->begin(); it
!= pLocalColOffset
->end(); ++it
)
770 // Only exact offsets, do not use MakeColNoRef().
771 if (maColOffset
.find(*it
) == maColOffset
.end())
772 maColOffset
.insert(*it
);
776 for ( size_t i
= nFirstTableCell
, nListSize
= maList
.size(); i
< nListSize
; ++i
)
778 auto& pE
= maList
[ i
];
779 if ( pE
->nTab
== nTable
)
783 pE
->nWidth
= GetWidth(pE
.get());
784 OSL_ENSURE( pE
->nWidth
, "SetWidths: pE->nWidth == 0" );
786 MakeCol( &maColOffset
, pE
->nOffset
, pE
->nWidth
, nOffsetTolerance
, nOffsetTolerance
);
791 void ScHTMLLayoutParser::Colonize( ScEEParseEntry
* pE
)
793 if ( pE
->nCol
== SCCOL_MAX
)
795 if ( pE
->nRow
== SCROW_MAX
)
797 SCCOL nCol
= pE
->nCol
;
798 SkipLocked( pE
); // Change of columns to the right
800 if ( nCol
< pE
->nCol
)
802 nCol
= pE
->nCol
- nColCntStart
;
803 SCCOL nCount
= static_cast<SCCOL
>(pLocalColOffset
->size());
805 nColOffset
= static_cast<sal_uInt16
>((*pLocalColOffset
)[nCol
]);
807 nColOffset
= static_cast<sal_uInt16
>((*pLocalColOffset
)[nCount
- 1]);
809 pE
->nOffset
= nColOffset
;
810 sal_uInt16 nWidth
= GetWidth( pE
);
811 MakeCol( pLocalColOffset
, pE
->nOffset
, nWidth
, nOffsetTolerance
, nOffsetTolerance
);
814 nColOffset
= pE
->nOffset
+ nWidth
;
815 if ( nTableWidth
< nColOffset
- nColOffsetStart
)
816 nTableWidth
= nColOffset
- nColOffsetStart
;
819 void ScHTMLLayoutParser::CloseEntry( const HtmlImportInfo
* pInfo
)
823 { // From the stack in TableOff
824 bTabInTabCell
= false;
825 NewActEntry(maList
.back().get()); // New free flying mxActEntry
828 if (mxActEntry
->nTab
== 0)
829 mxActEntry
->nWidth
= static_cast<sal_uInt16
>(aPageSize
.Width());
830 Colonize(mxActEntry
.get());
831 nColCnt
= mxActEntry
->nCol
+ mxActEntry
->nColOverlap
;
832 if ( nMaxCol
< nColCnt
)
833 nMaxCol
= nColCnt
; // TableStack MaxCol
834 if ( nColMax
< nColCnt
)
835 nColMax
= nColCnt
; // Global MaxCol for ScEEParser GetDimensions!
836 EntryEnd(mxActEntry
.get(), pInfo
->aSelection
);
837 ESelection
& rSel
= mxActEntry
->aSel
;
838 while ( rSel
.nStartPara
< rSel
.nEndPara
839 && pEdit
->GetTextLen( rSel
.nStartPara
) == 0 )
840 { // Strip preceding empty paragraphs
843 while ( rSel
.nEndPos
== 0 && rSel
.nEndPara
> rSel
.nStartPara
)
844 { // Strip successive empty paragraphs
846 rSel
.nEndPos
= pEdit
->GetTextLen( rSel
.nEndPara
);
848 if ( rSel
.nStartPara
> rSel
.nEndPara
)
849 { // Gives GPF in CreateTextObject
850 OSL_FAIL( "CloseEntry: EditEngine ESelection Start > End" );
851 rSel
.nEndPara
= rSel
.nStartPara
;
853 if ( rSel
.HasRange() )
854 mxActEntry
->aItemSet
.Put( ScLineBreakCell(true) );
855 maList
.push_back(mxActEntry
);
856 NewActEntry(mxActEntry
.get()); // New free flying mxActEntry
859 IMPL_LINK( ScHTMLLayoutParser
, HTMLImportHdl
, HtmlImportInfo
&, rInfo
, void )
861 switch ( rInfo
.eState
)
863 case HtmlImportState::NextToken
:
866 case HtmlImportState::Start
:
868 case HtmlImportState::End
:
869 if ( rInfo
.aSelection
.nEndPos
)
871 // If text remains: create paragraph, without calling CloseEntry().
872 if( bInCell
) // ...but only in opened table cells.
878 CloseEntry( &rInfo
);
880 while ( nTableLevel
> 0 )
881 TableOff( &rInfo
); // close tables, if </TABLE> missing
883 case HtmlImportState::SetAttr
:
885 case HtmlImportState::InsertText
:
887 case HtmlImportState::InsertPara
:
888 if ( nTableLevel
< 1 )
890 CloseEntry( &rInfo
);
894 case HtmlImportState::InsertField
:
897 OSL_FAIL("HTMLImportHdl: unknown ImportInfo.eState");
901 void ScHTMLLayoutParser::TableDataOn( HtmlImportInfo
* pInfo
)
907 OSL_FAIL( "dumbo doc! <TH> or <TD> without previous <TABLE>" );
911 bool bHorJustifyCenterTH
= (pInfo
->nToken
== HtmlTokenId::TABLEHEADER_ON
);
912 const HTMLOptions
& rOptions
= static_cast<HTMLParser
*>(pInfo
->pParser
)->GetOptions();
913 for (const auto & rOption
: rOptions
)
915 switch( rOption
.GetToken() )
917 case HtmlOptionId::COLSPAN
:
919 mxActEntry
->nColOverlap
= static_cast<SCCOL
>(rOption
.GetString().toInt32());
922 case HtmlOptionId::ROWSPAN
:
924 mxActEntry
->nRowOverlap
= static_cast<SCROW
>(rOption
.GetString().toInt32());
927 case HtmlOptionId::ALIGN
:
929 bHorJustifyCenterTH
= false;
930 SvxCellHorJustify eVal
;
931 const OUString
& rOptVal
= rOption
.GetString();
932 if ( rOptVal
.equalsIgnoreAsciiCase( OOO_STRING_SVTOOLS_HTML_AL_right
) )
933 eVal
= SvxCellHorJustify::Right
;
934 else if ( rOptVal
.equalsIgnoreAsciiCase( OOO_STRING_SVTOOLS_HTML_AL_center
) )
935 eVal
= SvxCellHorJustify::Center
;
936 else if ( rOptVal
.equalsIgnoreAsciiCase( OOO_STRING_SVTOOLS_HTML_AL_left
) )
937 eVal
= SvxCellHorJustify::Left
;
939 eVal
= SvxCellHorJustify::Standard
;
940 if ( eVal
!= SvxCellHorJustify::Standard
)
941 mxActEntry
->aItemSet
.Put(SvxHorJustifyItem(eVal
, ATTR_HOR_JUSTIFY
));
944 case HtmlOptionId::VALIGN
:
946 SvxCellVerJustify eVal
;
947 const OUString
& rOptVal
= rOption
.GetString();
948 if ( rOptVal
.equalsIgnoreAsciiCase( OOO_STRING_SVTOOLS_HTML_VA_top
) )
949 eVal
= SvxCellVerJustify::Top
;
950 else if ( rOptVal
.equalsIgnoreAsciiCase( OOO_STRING_SVTOOLS_HTML_VA_middle
) )
951 eVal
= SvxCellVerJustify::Center
;
952 else if ( rOptVal
.equalsIgnoreAsciiCase( OOO_STRING_SVTOOLS_HTML_VA_bottom
) )
953 eVal
= SvxCellVerJustify::Bottom
;
955 eVal
= SvxCellVerJustify::Standard
;
956 mxActEntry
->aItemSet
.Put(SvxVerJustifyItem(eVal
, ATTR_VER_JUSTIFY
));
959 case HtmlOptionId::WIDTH
:
961 mxActEntry
->nWidth
= GetWidthPixel(rOption
);
964 case HtmlOptionId::BGCOLOR
:
967 rOption
.GetColor( aColor
);
968 mxActEntry
->aItemSet
.Put(SvxBrushItem(aColor
, ATTR_BACKGROUND
));
971 case HtmlOptionId::SDVAL
:
973 mxActEntry
->pValStr
= rOption
.GetString();
976 case HtmlOptionId::SDNUM
:
978 mxActEntry
->pNumStr
= rOption
.GetString();
985 mxActEntry
->nCol
= nColCnt
;
986 mxActEntry
->nRow
= nRowCnt
;
987 mxActEntry
->nTab
= nTable
;
989 if ( bHorJustifyCenterTH
)
990 mxActEntry
->aItemSet
.Put(
991 SvxHorJustifyItem( SvxCellHorJustify::Center
, ATTR_HOR_JUSTIFY
) );
994 void ScHTMLLayoutParser::TableRowOn( const HtmlImportInfo
* pInfo
)
996 if ( nColCnt
> nColCntStart
)
997 NextRow( pInfo
); // The optional TableRowOff wasn't there
998 nColOffset
= nColOffsetStart
;
1001 void ScHTMLLayoutParser::TableRowOff( const HtmlImportInfo
* pInfo
)
1006 void ScHTMLLayoutParser::TableDataOff( const HtmlImportInfo
* pInfo
)
1009 CloseEntry( pInfo
); // Only if it really was one
1012 void ScHTMLLayoutParser::TableOn( HtmlImportInfo
* pInfo
)
1014 if ( ++nTableLevel
> 1 )
1016 sal_uInt16 nTmpColOffset
= nColOffset
; // Will be changed in Colonize()
1017 Colonize(mxActEntry
.get());
1018 aTableStack
.push( std::make_unique
<ScHTMLTableStackEntry
>(
1019 mxActEntry
, xLockedList
, pLocalColOffset
, nFirstTableCell
,
1020 nRowCnt
, nColCntStart
, nMaxCol
, nTable
,
1021 nTableWidth
, nColOffset
, nColOffsetStart
,
1023 sal_uInt16 nLastWidth
= nTableWidth
;
1024 nTableWidth
= GetWidth(mxActEntry
.get());
1025 if ( nTableWidth
== nLastWidth
&& nMaxCol
- nColCntStart
> 1 )
1026 { // There must be more than one, so this one cannot be enough
1027 nTableWidth
= nLastWidth
/ static_cast<sal_uInt16
>((nMaxCol
- nColCntStart
));
1029 nLastWidth
= nTableWidth
;
1030 if ( pInfo
->nToken
== HtmlTokenId::TABLE_ON
)
1031 { // It can still be TD or TH, if we didn't have a TABLE earlier
1032 const HTMLOptions
& rOptions
= static_cast<HTMLParser
*>(pInfo
->pParser
)->GetOptions();
1033 for (const auto & rOption
: rOptions
)
1035 switch( rOption
.GetToken() )
1037 case HtmlOptionId::WIDTH
:
1038 { // Percent: of document width or outer cell
1039 nTableWidth
= GetWidthPixel( rOption
);
1042 case HtmlOptionId::BORDER
:
1043 // Border is: ((pOption->GetString().Len() == 0) || (pOption->GetNumber() != 0));
1050 if ( bTabInTabCell
&& (nTableWidth
>= nLastWidth
) )
1051 { // Multiple tables in one cell, underneath each other
1052 bTabInTabCell
= false;
1056 { // It start's in this cell or next to each other
1057 bTabInTabCell
= false;
1058 nColCntStart
= nColCnt
;
1059 nColOffset
= nTmpColOffset
;
1060 nColOffsetStart
= nColOffset
;
1063 NewActEntry(!maList
.empty() ? maList
.back().get() : nullptr); // New free flying mxActEntry
1064 xLockedList
= new ScRangeList
;
1067 { // Simple table at the document level
1068 EntryEnd(mxActEntry
.get(), pInfo
->aSelection
);
1069 if (mxActEntry
->aSel
.HasRange())
1070 { // Flying text left
1071 CloseEntry( pInfo
);
1074 aTableStack
.push( std::make_unique
<ScHTMLTableStackEntry
>(
1075 mxActEntry
, xLockedList
, pLocalColOffset
, nFirstTableCell
,
1076 nRowCnt
, nColCntStart
, nMaxCol
, nTable
,
1077 nTableWidth
, nColOffset
, nColOffsetStart
,
1079 // As soon as we have multiple tables we need to be tolerant with the offsets.
1081 nOffsetTolerance
= SC_HTML_OFFSET_TOLERANCE_LARGE
;
1083 if ( pInfo
->nToken
== HtmlTokenId::TABLE_ON
)
1085 // It can still be TD or TH, if we didn't have a TABLE earlier
1086 const HTMLOptions
& rOptions
= static_cast<HTMLParser
*>(pInfo
->pParser
)->GetOptions();
1087 for (const auto & rOption
: rOptions
)
1089 switch( rOption
.GetToken() )
1091 case HtmlOptionId::WIDTH
:
1092 { // Percent: of document width or outer cell
1093 nTableWidth
= GetWidthPixel( rOption
);
1096 case HtmlOptionId::BORDER
:
1097 //BorderOn is: ((pOption->GetString().Len() == 0) || (pOption->GetNumber() != 0));
1104 nTable
= ++nMaxTable
;
1106 nFirstTableCell
= maList
.size();
1108 pLocalColOffset
= new ScHTMLColOffset
;
1109 MakeColNoRef( pLocalColOffset
, nColOffsetStart
, 0, 0, 0 );
1112 void ScHTMLLayoutParser::TableOff( const HtmlImportInfo
* pInfo
)
1115 CloseEntry( pInfo
);
1116 if ( nColCnt
> nColCntStart
)
1117 TableRowOff( pInfo
); // The optional TableRowOff wasn't
1120 OSL_FAIL( "dumbo doc! </TABLE> without opening <TABLE>" );
1123 if ( --nTableLevel
> 0 )
1124 { // Table in Table done
1125 if ( !aTableStack
.empty() )
1127 std::unique_ptr
<ScHTMLTableStackEntry
> pS
= std::move(aTableStack
.top());
1130 auto& pE
= pS
->xCellEntry
;
1131 SCROW nRows
= nRowCnt
- pS
->nRowCnt
;
1133 { // Insert size of table at this position
1134 SCROW nRow
= pS
->nRowCnt
;
1135 sal_uInt16 nTab
= pS
->nTable
;
1137 pTables
.reset( new OuterMap
);
1138 // Height of outer table
1139 OuterMap::const_iterator it
= pTables
->find( nTab
);
1141 if ( it
== pTables
->end() )
1143 pTab1
= new InnerMap
;
1144 (*pTables
)[ nTab
] = pTab1
;
1148 SCROW nRowSpan
= pE
->nRowOverlap
;
1150 SCROW nRowsPerRow1
; // Outer table
1151 SCROW nRowsPerRow2
; // Inner table
1153 { // LCM to which we can map the inner and outer rows
1154 nRowKGV
= std::lcm( nRowSpan
, nRows
);
1155 nRowsPerRow1
= nRowKGV
/ nRowSpan
;
1156 nRowsPerRow2
= nRowKGV
/ nRows
;
1160 nRowKGV
= nRowsPerRow1
= nRows
;
1163 InnerMap
* pTab2
= nullptr;
1164 if ( nRowsPerRow2
> 1 )
1165 { // Height of the inner table
1166 pTab2
= new InnerMap
;
1167 (*pTables
)[ nTable
] = pTab2
;
1169 // Abuse void* Data entry of the Table class for height mapping
1172 if ( nRowsPerRow1
> 1 )
1174 for ( SCROW j
=0; j
< nRowSpan
; j
++ )
1176 sal_uLong nRowKey
= nRow
+ j
;
1177 SCROW nR
= (*pTab1
)[ nRowKey
];
1179 (*pTab1
)[ nRowKey
] = nRowsPerRow1
;
1180 else if ( nRowsPerRow1
> nR
)
1181 (*pTab1
)[ nRowKey
] = nRowsPerRow1
;
1182 //TODO: How can we improve on this?
1183 else if ( nRowsPerRow1
< nR
&& nRowSpan
== 1
1184 && nTable
== nMaxTable
)
1185 { // Still some space left, merge in a better way (if possible)
1186 SCROW nAdd
= nRowsPerRow1
- (nR
% nRowsPerRow1
);
1188 if ( (nR
% nRows
) == 0 )
1189 { // Only if representable
1190 SCROW nR2
= (*pTab1
)[ nRowKey
+1 ];
1192 { // Only if we really have enough space
1193 (*pTab1
)[ nRowKey
] = nR
;
1194 (*pTab1
)[ nRowKey
+1 ] = nR2
- nAdd
;
1195 nRowsPerRow2
= nR
/ nRows
;
1201 if ( nRowsPerRow2
> 1 )
1204 { // nRowsPerRow2 could be've been incremented
1205 pTab2
= new InnerMap
;
1206 (*pTables
)[ nTable
] = pTab2
;
1208 for ( SCROW j
=0; j
< nRows
; j
++ )
1210 sal_uLong nRowKey
= nRow
+ j
;
1211 (*pTab2
)[ nRowKey
] = nRowsPerRow2
;
1220 pE
->nWidth
= nTableWidth
;
1221 else if ( pE
->nWidth
< nTableWidth
)
1223 sal_uInt16 nOldOffset
= pE
->nOffset
+ pE
->nWidth
;
1224 sal_uInt16 nNewOffset
= pE
->nOffset
+ nTableWidth
;
1225 ModifyOffset( pS
->pLocalColOffset
, nOldOffset
, nNewOffset
, nOffsetTolerance
);
1226 sal_uInt16 nTmp
= nNewOffset
- pE
->nOffset
- pE
->nWidth
;
1227 pE
->nWidth
= nNewOffset
- pE
->nOffset
;
1228 pS
->nTableWidth
= pS
->nTableWidth
+ nTmp
;
1229 if ( pS
->nColOffset
>= nOldOffset
)
1230 pS
->nColOffset
= pS
->nColOffset
+ nTmp
;
1233 nColCnt
= pE
->nCol
+ pE
->nColOverlap
;
1234 nRowCnt
= pS
->nRowCnt
;
1235 nColCntStart
= pS
->nColCntStart
;
1236 nMaxCol
= pS
->nMaxCol
;
1237 nTable
= pS
->nTable
;
1238 nTableWidth
= pS
->nTableWidth
;
1239 nFirstTableCell
= pS
->nFirstTableCell
;
1240 nColOffset
= pS
->nColOffset
;
1241 nColOffsetStart
= pS
->nColOffsetStart
;
1242 bFirstRow
= pS
->bFirstRow
;
1243 xLockedList
= pS
->xLockedList
;
1244 pLocalColOffset
= pS
->pLocalColOffset
;
1245 // mxActEntry is kept around if a table is started in the same row
1246 // (anything's possible in HTML); will be deleted by CloseEntry
1249 bTabInTabCell
= true;
1253 { // Simple table finished
1257 if ( !aTableStack
.empty() )
1259 ScHTMLTableStackEntry
* pS
= aTableStack
.top().get();
1260 delete pLocalColOffset
;
1261 pLocalColOffset
= pS
->pLocalColOffset
;
1267 void ScHTMLLayoutParser::Image( HtmlImportInfo
* pInfo
)
1269 mxActEntry
->maImageList
.push_back(std::make_unique
<ScHTMLImage
>());
1270 ScHTMLImage
* pImage
= mxActEntry
->maImageList
.back().get();
1271 const HTMLOptions
& rOptions
= static_cast<HTMLParser
*>(pInfo
->pParser
)->GetOptions();
1272 for (const auto & rOption
: rOptions
)
1274 switch( rOption
.GetToken() )
1276 case HtmlOptionId::SRC
:
1278 pImage
->aURL
= INetURLObject::GetAbsURL( aBaseURL
, rOption
.GetString() );
1281 case HtmlOptionId::ALT
:
1283 if (!mxActEntry
->bHasGraphic
)
1284 { // ALT text only if not any image loaded
1285 if (!mxActEntry
->aAltText
.isEmpty())
1286 mxActEntry
->aAltText
+= "; ";
1288 mxActEntry
->aAltText
+= rOption
.GetString();
1292 case HtmlOptionId::WIDTH
:
1294 pImage
->aSize
.setWidth( static_cast<tools::Long
>(rOption
.GetNumber()) );
1297 case HtmlOptionId::HEIGHT
:
1299 pImage
->aSize
.setHeight( static_cast<tools::Long
>(rOption
.GetNumber()) );
1302 case HtmlOptionId::HSPACE
:
1304 pImage
->aSpace
.setX( static_cast<tools::Long
>(rOption
.GetNumber()) );
1307 case HtmlOptionId::VSPACE
:
1309 pImage
->aSpace
.setY( static_cast<tools::Long
>(rOption
.GetNumber()) );
1315 if (pImage
->aURL
.isEmpty())
1317 OSL_FAIL( "Image: graphic without URL ?!?" );
1322 std::optional
<Graphic
> oGraphic(std::in_place
);
1323 GraphicFilter
& rFilter
= GraphicFilter::GetGraphicFilter();
1324 if ( ERRCODE_NONE
!= GraphicFilter::LoadGraphic( pImage
->aURL
, pImage
->aFilterName
,
1325 *oGraphic
, &rFilter
, &nFormat
) )
1327 return ; // Bad luck
1329 if (!mxActEntry
->bHasGraphic
)
1330 { // discard any ALT text in this cell if we have any image
1331 mxActEntry
->bHasGraphic
= true;
1332 mxActEntry
->aAltText
.clear();
1334 pImage
->aFilterName
= rFilter
.GetImportFormatName( nFormat
);
1335 pImage
->oGraphic
= std::move( oGraphic
);
1336 if ( !(pImage
->aSize
.Width() && pImage
->aSize
.Height()) )
1338 OutputDevice
* pDefaultDev
= Application::GetDefaultDevice();
1339 pImage
->aSize
= pDefaultDev
->LogicToPixel( pImage
->oGraphic
->GetPrefSize(),
1340 pImage
->oGraphic
->GetPrefMapMode() );
1342 if (mxActEntry
->maImageList
.empty())
1345 tools::Long nWidth
= 0;
1346 for (const std::unique_ptr
<ScHTMLImage
> & pI
: mxActEntry
->maImageList
)
1348 if ( pI
->nDir
& nHorizontal
)
1349 nWidth
+= pI
->aSize
.Width() + 2 * pI
->aSpace
.X();
1353 if ( mxActEntry
->nWidth
1354 && (nWidth
+ pImage
->aSize
.Width() + 2 * pImage
->aSpace
.X()
1355 >= mxActEntry
->nWidth
) )
1356 mxActEntry
->maImageList
.back()->nDir
= nVertical
;
1359 void ScHTMLLayoutParser::ColOn( HtmlImportInfo
* pInfo
)
1361 const HTMLOptions
& rOptions
= static_cast<HTMLParser
*>(pInfo
->pParser
)->GetOptions();
1362 for (const auto & rOption
: rOptions
)
1364 if( rOption
.GetToken() == HtmlOptionId::WIDTH
)
1366 sal_uInt16 nVal
= GetWidthPixel( rOption
);
1367 MakeCol( pLocalColOffset
, nColOffset
, nVal
, 0, 0 );
1368 nColOffset
= nColOffset
+ nVal
;
1373 sal_uInt16
ScHTMLLayoutParser::GetWidthPixel( const HTMLOption
& rOption
)
1375 const OUString
& rOptVal
= rOption
.GetString();
1376 if ( rOptVal
.indexOf('%') != -1 )
1378 sal_uInt16 nW
= (nTableWidth
? nTableWidth
: static_cast<sal_uInt16
>(aPageSize
.Width()));
1379 return static_cast<sal_uInt16
>((rOption
.GetNumber() * nW
) / 100);
1383 if ( rOptVal
.indexOf('*') != -1 )
1384 { // Relative to what?
1385 // TODO: Collect all relative values in ColArray and then MakeCol
1389 return static_cast<sal_uInt16
>(rOption
.GetNumber()); // Pixel
1393 void ScHTMLLayoutParser::AnchorOn( HtmlImportInfo
* pInfo
)
1395 const HTMLOptions
& rOptions
= static_cast<HTMLParser
*>(pInfo
->pParser
)->GetOptions();
1396 for (const auto & rOption
: rOptions
)
1398 if( rOption
.GetToken() == HtmlOptionId::NAME
)
1399 mxActEntry
->pName
= rOption
.GetString();
1403 bool ScHTMLLayoutParser::IsAtBeginningOfText( const HtmlImportInfo
* pInfo
)
1405 ESelection
& rSel
= mxActEntry
->aSel
;
1406 return rSel
.nStartPara
== rSel
.nEndPara
&&
1407 rSel
.nStartPara
<= pInfo
->aSelection
.nEndPara
&&
1408 pEdit
->GetTextLen( rSel
.nStartPara
) == 0;
1411 void ScHTMLLayoutParser::FontOn( HtmlImportInfo
* pInfo
)
1413 if ( !IsAtBeginningOfText( pInfo
) )
1416 // Only at the start of the text; applies to whole line
1417 const HTMLOptions
& rOptions
= static_cast<HTMLParser
*>(pInfo
->pParser
)->GetOptions();
1418 for (const auto & rOption
: rOptions
)
1420 switch( rOption
.GetToken() )
1422 case HtmlOptionId::FACE
:
1424 const OUString
& rFace
= rOption
.GetString();
1425 OUStringBuffer aFontName
;
1429 // Font list, VCL uses the semicolon as separator
1430 // HTML uses the comma
1431 std::u16string_view aFName
= o3tl::getToken(rFace
, 0, ',', nPos
);
1432 aFName
= comphelper::string::strip(aFName
, ' ');
1433 if( !aFontName
.isEmpty() )
1434 aFontName
.append(";");
1435 aFontName
.append(aFName
);
1437 if ( !aFontName
.isEmpty() )
1438 mxActEntry
->aItemSet
.Put( SvxFontItem( FAMILY_DONTKNOW
,
1439 aFontName
.makeStringAndClear(), OUString(), PITCH_DONTKNOW
,
1440 RTL_TEXTENCODING_DONTKNOW
, ATTR_FONT
) );
1443 case HtmlOptionId::SIZE
:
1445 sal_uInt16 nSize
= static_cast<sal_uInt16
>(rOption
.GetNumber());
1448 else if ( nSize
> SC_HTML_FONTSIZES
)
1449 nSize
= SC_HTML_FONTSIZES
;
1450 mxActEntry
->aItemSet
.Put( SvxFontHeightItem(
1451 maFontHeights
[nSize
-1], 100, ATTR_FONT_HEIGHT
) );
1454 case HtmlOptionId::COLOR
:
1457 rOption
.GetColor( aColor
);
1458 mxActEntry
->aItemSet
.Put( SvxColorItem( aColor
, ATTR_FONT_COLOR
) );
1466 void ScHTMLLayoutParser::ProcToken( HtmlImportInfo
* pInfo
)
1468 switch ( pInfo
->nToken
)
1470 case HtmlTokenId::META
:
1472 HTMLParser
* pParser
= static_cast<HTMLParser
*>(pInfo
->pParser
);
1473 uno::Reference
<document::XDocumentPropertiesSupplier
> xDPS(
1474 mpDoc
->GetDocumentShell()->GetModel(), uno::UNO_QUERY_THROW
);
1475 pParser
->ParseMetaOptions(
1476 xDPS
->getDocumentProperties(),
1477 mpDoc
->GetDocumentShell()->GetHeaderAttributes() );
1480 case HtmlTokenId::TITLE_ON
:
1486 case HtmlTokenId::TITLE_OFF
:
1488 if ( bInTitle
&& !aString
.isEmpty() )
1490 // Remove blanks from line breaks
1491 aString
= aString
.trim();
1492 uno::Reference
<document::XDocumentPropertiesSupplier
> xDPS(
1493 mpDoc
->GetDocumentShell()->GetModel(),
1494 uno::UNO_QUERY_THROW
);
1495 xDPS
->getDocumentProperties()->setTitle(aString
);
1500 case HtmlTokenId::TABLE_ON
:
1505 case HtmlTokenId::COL_ON
:
1510 case HtmlTokenId::TABLEHEADER_ON
: // Opens row
1513 CloseEntry( pInfo
);
1514 // Do not set bInCell to true, TableDataOn does that
1515 mxActEntry
->aItemSet
.Put(
1516 SvxWeightItem( WEIGHT_BOLD
, ATTR_FONT_WEIGHT
) );
1519 case HtmlTokenId::TABLEDATA_ON
: // Opens cell
1521 TableDataOn( pInfo
);
1524 case HtmlTokenId::TABLEHEADER_OFF
:
1525 case HtmlTokenId::TABLEDATA_OFF
: // Closes cell
1527 TableDataOff( pInfo
);
1530 case HtmlTokenId::TABLEROW_ON
: // Before first cell in row
1532 TableRowOn( pInfo
);
1535 case HtmlTokenId::TABLEROW_OFF
: // After last cell in row
1537 TableRowOff( pInfo
);
1540 case HtmlTokenId::TABLE_OFF
:
1545 case HtmlTokenId::IMAGE
:
1550 case HtmlTokenId::PARABREAK_OFF
:
1551 { // We continue vertically after an image
1552 if (!mxActEntry
->maImageList
.empty())
1553 mxActEntry
->maImageList
.back()->nDir
= nVertical
;
1556 case HtmlTokenId::ANCHOR_ON
:
1561 case HtmlTokenId::FONT_ON
:
1566 case HtmlTokenId::BIGPRINT_ON
:
1568 // TODO: Remember current font size and increase by 1
1569 if ( IsAtBeginningOfText( pInfo
) )
1570 mxActEntry
->aItemSet
.Put( SvxFontHeightItem(
1571 maFontHeights
[3], 100, ATTR_FONT_HEIGHT
) );
1574 case HtmlTokenId::SMALLPRINT_ON
:
1576 // TODO: Remember current font size and decrease by 1
1577 if ( IsAtBeginningOfText( pInfo
) )
1578 mxActEntry
->aItemSet
.Put( SvxFontHeightItem(
1579 maFontHeights
[0], 100, ATTR_FONT_HEIGHT
) );
1582 case HtmlTokenId::BOLD_ON
:
1583 case HtmlTokenId::STRONG_ON
:
1585 if ( IsAtBeginningOfText( pInfo
) )
1586 mxActEntry
->aItemSet
.Put( SvxWeightItem( WEIGHT_BOLD
,
1587 ATTR_FONT_WEIGHT
) );
1590 case HtmlTokenId::ITALIC_ON
:
1591 case HtmlTokenId::EMPHASIS_ON
:
1592 case HtmlTokenId::ADDRESS_ON
:
1593 case HtmlTokenId::BLOCKQUOTE_ON
:
1594 case HtmlTokenId::BLOCKQUOTE30_ON
:
1595 case HtmlTokenId::CITATION_ON
:
1596 case HtmlTokenId::VARIABLE_ON
:
1598 if ( IsAtBeginningOfText( pInfo
) )
1599 mxActEntry
->aItemSet
.Put( SvxPostureItem( ITALIC_NORMAL
,
1600 ATTR_FONT_POSTURE
) );
1603 case HtmlTokenId::DEFINSTANCE_ON
:
1605 if ( IsAtBeginningOfText( pInfo
) )
1607 mxActEntry
->aItemSet
.Put( SvxWeightItem( WEIGHT_BOLD
,
1608 ATTR_FONT_WEIGHT
) );
1609 mxActEntry
->aItemSet
.Put( SvxPostureItem( ITALIC_NORMAL
,
1610 ATTR_FONT_POSTURE
) );
1614 case HtmlTokenId::UNDERLINE_ON
:
1616 if ( IsAtBeginningOfText( pInfo
) )
1617 mxActEntry
->aItemSet
.Put( SvxUnderlineItem( LINESTYLE_SINGLE
,
1618 ATTR_FONT_UNDERLINE
) );
1621 case HtmlTokenId::TEXTTOKEN
:
1624 aString
+= pInfo
->aText
;
1631 // HTML DATA QUERY PARSER
1633 template< typename Type
>
1634 static Type
getLimitedValue( const Type
& rValue
, const Type
& rMin
, const Type
& rMax
)
1635 { return std::clamp( rValue
, rMin
, rMax
); }
1637 ScHTMLEntry::ScHTMLEntry( const SfxItemSet
& rItemSet
, ScHTMLTableId nTableId
) :
1638 ScEEParseEntry( rItemSet
),
1639 mbImportAlways( false )
1642 bEntirePara
= false;
1645 bool ScHTMLEntry::HasContents() const
1647 return mbImportAlways
|| aSel
.HasRange() || !aAltText
.isEmpty() || IsTable();
1650 void ScHTMLEntry::AdjustStart( const HtmlImportInfo
& rInfo
)
1652 // set start position
1653 aSel
.nStartPara
= rInfo
.aSelection
.nStartPara
;
1654 aSel
.nStartPos
= rInfo
.aSelection
.nStartPos
;
1655 // adjust end position
1656 if( (aSel
.nEndPara
< aSel
.nStartPara
) || ((aSel
.nEndPara
== aSel
.nStartPara
) && (aSel
.nEndPos
< aSel
.nStartPos
)) )
1658 aSel
.nEndPara
= aSel
.nStartPara
;
1659 aSel
.nEndPos
= aSel
.nStartPos
;
1663 void ScHTMLEntry::AdjustEnd( const HtmlImportInfo
& rInfo
)
1665 OSL_ENSURE( (aSel
.nEndPara
< rInfo
.aSelection
.nEndPara
) ||
1666 ((aSel
.nEndPara
== rInfo
.aSelection
.nEndPara
) && (aSel
.nEndPos
<= rInfo
.aSelection
.nEndPos
)),
1667 "ScHTMLQueryParser::AdjustEntryEnd - invalid end position" );
1669 aSel
.nEndPara
= rInfo
.aSelection
.nEndPara
;
1670 aSel
.nEndPos
= rInfo
.aSelection
.nEndPos
;
1673 void ScHTMLEntry::Strip( const EditEngine
& rEditEngine
)
1675 // strip leading empty paragraphs
1676 while( (aSel
.nStartPara
< aSel
.nEndPara
) && (rEditEngine
.GetTextLen( aSel
.nStartPara
) <= aSel
.nStartPos
) )
1681 // strip trailing empty paragraphs
1682 while( (aSel
.nStartPara
< aSel
.nEndPara
) && (aSel
.nEndPos
== 0) )
1685 aSel
.nEndPos
= rEditEngine
.GetTextLen( aSel
.nEndPara
);
1689 /** A map of ScHTMLTable objects.
1691 Organizes the tables with a unique table key. Stores nested tables inside
1692 the parent table and forms in this way a tree structure of tables. An
1693 instance of this class owns the contained table objects and deletes them
1696 class ScHTMLTableMap final
1699 typedef std::shared_ptr
< ScHTMLTable
> ScHTMLTablePtr
;
1700 typedef std::map
< ScHTMLTableId
, ScHTMLTablePtr
> ScHTMLTableStdMap
;
1703 typedef ScHTMLTableStdMap::iterator iterator
;
1704 typedef ScHTMLTableStdMap::const_iterator const_iterator
;
1707 ScHTMLTable
& mrParentTable
; /// Reference to parent table.
1708 ScHTMLTableStdMap maTables
; /// Container for all table objects.
1709 mutable ScHTMLTable
* mpCurrTable
; /// Current table, used for fast search.
1712 explicit ScHTMLTableMap( ScHTMLTable
& rParentTable
);
1714 const_iterator
begin() const { return maTables
.begin(); }
1715 const_iterator
end() const { return maTables
.end(); }
1717 /** Returns the specified table.
1718 @param nTableId Unique identifier of the table.
1719 @param bDeep true = searches deep in all nested table; false = only in this container. */
1720 ScHTMLTable
* FindTable( ScHTMLTableId nTableId
, bool bDeep
= true ) const;
1722 /** Inserts a new table into the container. This container owns the created table.
1723 @param bPreFormText true = New table is based on preformatted text (<pre> tag). */
1724 ScHTMLTable
* CreateTable( const HtmlImportInfo
& rInfo
, bool bPreFormText
, const ScDocument
& rDoc
);
1727 /** Sets a working table with its index for search optimization. */
1728 void SetCurrTable( ScHTMLTable
* pTable
) const
1729 { if( pTable
) mpCurrTable
= pTable
; }
1732 ScHTMLTableMap::ScHTMLTableMap( ScHTMLTable
& rParentTable
) :
1733 mrParentTable(rParentTable
),
1734 mpCurrTable(nullptr)
1738 ScHTMLTable
* ScHTMLTableMap::FindTable( ScHTMLTableId nTableId
, bool bDeep
) const
1740 ScHTMLTable
* pResult
= nullptr;
1741 if( mpCurrTable
&& (nTableId
== mpCurrTable
->GetTableId()) )
1742 pResult
= mpCurrTable
; // cached table
1745 const_iterator aFind
= maTables
.find( nTableId
);
1746 if( aFind
!= maTables
.end() )
1747 pResult
= aFind
->second
.get(); // table from this container
1750 // not found -> search deep in nested tables
1751 if( !pResult
&& bDeep
)
1752 for( const_iterator aIter
= begin(), aEnd
= end(); !pResult
&& (aIter
!= aEnd
); ++aIter
)
1753 pResult
= aIter
->second
->FindNestedTable( nTableId
);
1755 SetCurrTable( pResult
);
1759 ScHTMLTable
* ScHTMLTableMap::CreateTable( const HtmlImportInfo
& rInfo
, bool bPreFormText
, const ScDocument
& rDoc
)
1761 ScHTMLTable
* pTable
= new ScHTMLTable( mrParentTable
, rInfo
, bPreFormText
, rDoc
);
1762 maTables
[ pTable
->GetTableId() ].reset( pTable
);
1763 SetCurrTable( pTable
);
1769 /** Simplified forward iterator for convenience.
1771 Before the iterator can be dereferenced, it must be tested with the is()
1772 method. The iterator may be invalid directly after construction (e.g. empty
1775 class ScHTMLTableIterator
1778 /** Constructs the iterator for the passed table map.
1779 @param pTableMap Pointer to the table map (is allowed to be NULL). */
1780 explicit ScHTMLTableIterator( const ScHTMLTableMap
* pTableMap
);
1782 bool is() const { return mpTableMap
&& maIter
!= maEnd
; }
1783 ScHTMLTable
* operator->() { return maIter
->second
.get(); }
1784 ScHTMLTableIterator
& operator++() { ++maIter
; return *this; }
1787 ScHTMLTableMap::const_iterator maIter
;
1788 ScHTMLTableMap::const_iterator maEnd
;
1789 const ScHTMLTableMap
* mpTableMap
;
1794 ScHTMLTableIterator::ScHTMLTableIterator( const ScHTMLTableMap
* pTableMap
) :
1795 mpTableMap(pTableMap
)
1799 maIter
= pTableMap
->begin();
1800 maEnd
= pTableMap
->end();
1804 ScHTMLTableAutoId::ScHTMLTableAutoId( ScHTMLTableId
& rnUnusedId
) :
1805 mnTableId( rnUnusedId
),
1806 mrnUnusedId( rnUnusedId
)
1811 ScHTMLTable::ScHTMLTable( ScHTMLTable
& rParentTable
, const HtmlImportInfo
& rInfo
, bool bPreFormText
, const ScDocument
& rDoc
) :
1812 mpParentTable( &rParentTable
),
1813 maTableId( rParentTable
.maTableId
.mrnUnusedId
),
1814 maTableItemSet( rParentTable
.GetCurrItemSet() ),
1815 mrEditEngine( rParentTable
.mrEditEngine
),
1816 mrEEParseList( rParentTable
.mrEEParseList
),
1817 mpCurrEntryVector( nullptr ),
1819 mpParser(rParentTable
.mpParser
),
1821 mbBorderOn( false ),
1822 mbPreFormText( bPreFormText
),
1825 mbPushEmptyLine( false ),
1826 mbCaptionOn ( false )
1831 ImplDataOn( ScHTMLSize( 1, 1 ) );
1835 ProcessFormatOptions( maTableItemSet
, rInfo
);
1836 const HTMLOptions
& rOptions
= static_cast<HTMLParser
*>(rInfo
.pParser
)->GetOptions();
1837 for (const auto& rOption
: rOptions
)
1839 switch( rOption
.GetToken() )
1841 case HtmlOptionId::BORDER
:
1842 mbBorderOn
= rOption
.GetString().isEmpty() || (rOption
.GetNumber() != 0);
1844 case HtmlOptionId::ID
:
1845 maTableName
= rOption
.GetString();
1852 CreateNewEntry( rInfo
);
1855 ScHTMLTable::ScHTMLTable(
1857 EditEngine
& rEditEngine
,
1858 std::vector
<std::shared_ptr
<ScEEParseEntry
>>& rEEParseList
,
1859 ScHTMLTableId
& rnUnusedId
, ScHTMLParser
* pParser
, const ScDocument
& rDoc
1861 mpParentTable( nullptr ),
1862 maTableId( rnUnusedId
),
1863 maTableItemSet( rPool
),
1864 mrEditEngine( rEditEngine
),
1865 mrEEParseList( rEEParseList
),
1866 mpCurrEntryVector( nullptr ),
1870 mbBorderOn( false ),
1871 mbPreFormText( false ),
1874 mbPushEmptyLine( false ),
1875 mbCaptionOn ( false )
1877 // open the first "cell" of the document
1879 ImplDataOn( ScHTMLSize( 1, 1 ) );
1880 mxCurrEntry
= CreateEntry();
1883 ScHTMLTable::~ScHTMLTable()
1887 const SfxItemSet
& ScHTMLTable::GetCurrItemSet() const
1889 // first try cell item set, then row item set, then table item set
1890 return moDataItemSet
? *moDataItemSet
: (moRowItemSet
? *moRowItemSet
: maTableItemSet
);
1893 ScHTMLSize
ScHTMLTable::GetSpan( const ScHTMLPos
& rCellPos
) const
1895 ScHTMLSize
aSpan( 1, 1 );
1896 const ScRange
* pRange
= maVMergedCells
.Find( rCellPos
.MakeAddr() );
1898 pRange
= maHMergedCells
.Find( rCellPos
.MakeAddr() );
1900 aSpan
.Set( pRange
->aEnd
.Col() - pRange
->aStart
.Col() + 1, pRange
->aEnd
.Row() - pRange
->aStart
.Row() + 1 );
1904 ScHTMLTable
* ScHTMLTable::FindNestedTable( ScHTMLTableId nTableId
) const
1906 return mxNestedTables
? mxNestedTables
->FindTable( nTableId
) : nullptr;
1909 void ScHTMLTable::PutItem( const SfxPoolItem
& rItem
)
1911 OSL_ENSURE( mxCurrEntry
, "ScHTMLTable::PutItem - no current entry" );
1912 if( mxCurrEntry
&& mxCurrEntry
->IsEmpty() )
1913 mxCurrEntry
->GetItemSet().Put( rItem
);
1916 void ScHTMLTable::PutText( const HtmlImportInfo
& rInfo
)
1918 OSL_ENSURE( mxCurrEntry
, "ScHTMLTable::PutText - no current entry" );
1921 if( !mxCurrEntry
->HasContents() && IsSpaceCharInfo( rInfo
) )
1922 mxCurrEntry
->AdjustStart( rInfo
);
1924 mxCurrEntry
->AdjustEnd( rInfo
);
1926 maCaptionBuffer
.append(rInfo
.aText
);
1931 void ScHTMLTable::InsertPara( const HtmlImportInfo
& rInfo
)
1933 if( mxCurrEntry
&& mbDataOn
&& !IsEmptyCell() )
1934 mxCurrEntry
->SetImportAlways();
1936 CreateNewEntry( rInfo
);
1937 InsertLeadingEmptyLine();
1940 void ScHTMLTable::BreakOn()
1942 // empty line, if <br> is at start of cell
1943 mbPushEmptyLine
= !mbPreFormText
&& mbDataOn
&& IsEmptyCell();
1946 void ScHTMLTable::HeadingOn()
1948 // call directly, InsertPara() has not been called before
1949 InsertLeadingEmptyLine();
1952 void ScHTMLTable::InsertLeadingEmptyLine()
1954 // empty line, if <p>, </p>, <h?>, or </h*> are not at start of cell
1955 mbPushEmptyLine
= !mbPreFormText
&& mbDataOn
&& !IsEmptyCell();
1958 void ScHTMLTable::AnchorOn()
1960 OSL_ENSURE( mxCurrEntry
, "ScHTMLTable::AnchorOn - no current entry" );
1961 // don't skip entries with single hyperlinks
1963 mxCurrEntry
->SetImportAlways();
1966 ScHTMLTable
* ScHTMLTable::TableOn( const HtmlImportInfo
& rInfo
)
1969 return InsertNestedTable( rInfo
, false );
1972 ScHTMLTable
* ScHTMLTable::TableOff( const HtmlImportInfo
& rInfo
)
1974 return mbPreFormText
? this : CloseTable( rInfo
);
1977 void ScHTMLTable::CaptionOn()
1980 maCaptionBuffer
.setLength(0);
1983 void ScHTMLTable::CaptionOff()
1987 maCaption
= maCaptionBuffer
.makeStringAndClear().trim();
1988 mbCaptionOn
= false;
1991 ScHTMLTable
* ScHTMLTable::PreOn( const HtmlImportInfo
& rInfo
)
1994 return InsertNestedTable( rInfo
, true );
1997 ScHTMLTable
* ScHTMLTable::PreOff( const HtmlImportInfo
& rInfo
)
1999 return mbPreFormText
? CloseTable( rInfo
) : this;
2002 void ScHTMLTable::RowOn( const HtmlImportInfo
& rInfo
)
2004 PushEntry( rInfo
, true );
2005 if( mpParentTable
&& !mbPreFormText
) // no rows allowed in global and preformatted tables
2008 ProcessFormatOptions( *moRowItemSet
, rInfo
);
2010 CreateNewEntry( rInfo
);
2013 void ScHTMLTable::RowOff( const HtmlImportInfo
& rInfo
)
2015 PushEntry( rInfo
, true );
2016 if( mpParentTable
&& !mbPreFormText
) // no rows allowed in global and preformatted tables
2018 CreateNewEntry( rInfo
);
2024 * Decode a number format string stored in Excel-generated HTML's CSS
2027 OUString
decodeNumberFormat(const OUString
& rFmt
)
2029 OUStringBuffer aBuf
;
2030 const sal_Unicode
* p
= rFmt
.getStr();
2031 sal_Int32 n
= rFmt
.getLength();
2032 for (sal_Int32 i
= 0; i
< n
; ++i
, ++p
)
2040 // Parse all subsequent digits until first non-digit is found.
2041 sal_Int32 nDigitCount
= 0;
2042 const sal_Unicode
* p1
= p
;
2043 for (; i
< n
; ++i
, ++p
, ++nDigitCount
)
2045 if (*p
< '0' || '9' < *p
)
2055 // Hex-encoded character found. Decode it back into its
2056 // original character. An example of number format with
2057 // hex-encoded chars: "\0022$\0022\#\,\#\#0\.00"
2058 sal_uInt32 nVal
= OUString(p1
, nDigitCount
).toUInt32(16);
2059 aBuf
.append(static_cast<sal_Unicode
>(nVal
));
2065 return aBuf
.makeStringAndClear();
2070 void ScHTMLTable::DataOn( const HtmlImportInfo
& rInfo
)
2072 PushEntry( rInfo
, true );
2073 if( mpParentTable
&& !mbPreFormText
) // no cells allowed in global and preformatted tables
2075 // read needed options from the <td> tag
2076 ScHTMLSize
aSpanSize( 1, 1 );
2077 std::optional
<OUString
> pValStr
, pNumStr
;
2078 const HTMLOptions
& rOptions
= static_cast<HTMLParser
*>(rInfo
.pParser
)->GetOptions();
2079 sal_uInt32 nNumberFormat
= NUMBERFORMAT_ENTRY_NOT_FOUND
;
2080 for (const auto& rOption
: rOptions
)
2082 switch (rOption
.GetToken())
2084 case HtmlOptionId::COLSPAN
:
2085 aSpanSize
.mnCols
= static_cast<SCCOL
>( getLimitedValue
<sal_Int32
>( rOption
.GetString().toInt32(), 1, 256 ) );
2087 case HtmlOptionId::ROWSPAN
:
2088 aSpanSize
.mnRows
= static_cast<SCROW
>( getLimitedValue
<sal_Int32
>( rOption
.GetString().toInt32(), 1, 256 ) );
2090 case HtmlOptionId::SDVAL
:
2091 pValStr
= rOption
.GetString();
2093 case HtmlOptionId::SDNUM
:
2094 pNumStr
= rOption
.GetString();
2096 case HtmlOptionId::CLASS
:
2098 // Pick up the number format associated with this class (if
2100 OUString aClass
= rOption
.GetString();
2101 const ScHTMLStyles
& rStyles
= mpParser
->GetStyles();
2102 const OUString
& rVal
= rStyles
.getPropertyValue("td", aClass
, "mso-number-format");
2103 if (!rVal
.isEmpty())
2105 OUString aNumFmt
= decodeNumberFormat(rVal
);
2107 nNumberFormat
= GetFormatTable()->GetEntryKey(aNumFmt
);
2108 if (nNumberFormat
== NUMBERFORMAT_ENTRY_NOT_FOUND
)
2110 sal_Int32 nErrPos
= 0;
2111 SvNumFormatType nDummy
;
2112 bool bValidFmt
= GetFormatTable()->PutEntry(aNumFmt
, nErrPos
, nDummy
, nNumberFormat
);
2114 nNumberFormat
= NUMBERFORMAT_ENTRY_NOT_FOUND
;
2123 ImplDataOn( aSpanSize
);
2125 if (nNumberFormat
!= NUMBERFORMAT_ENTRY_NOT_FOUND
)
2126 moDataItemSet
->Put( SfxUInt32Item(ATTR_VALUE_FORMAT
, nNumberFormat
) );
2128 ProcessFormatOptions( *moDataItemSet
, rInfo
);
2129 CreateNewEntry( rInfo
);
2130 mxCurrEntry
->pValStr
= std::move(pValStr
);
2131 mxCurrEntry
->pNumStr
= std::move(pNumStr
);
2134 CreateNewEntry( rInfo
);
2137 void ScHTMLTable::DataOff( const HtmlImportInfo
& rInfo
)
2139 PushEntry( rInfo
, true );
2140 if( mpParentTable
&& !mbPreFormText
) // no cells allowed in global and preformatted tables
2142 CreateNewEntry( rInfo
);
2145 void ScHTMLTable::BodyOn( const HtmlImportInfo
& rInfo
)
2147 bool bPushed
= PushEntry( rInfo
);
2148 if( !mpParentTable
)
2150 // do not start new row, if nothing (no title) precedes the body.
2151 if( bPushed
|| !mbRowOn
)
2153 if( bPushed
|| !mbDataOn
)
2154 ImplDataOn( ScHTMLSize( 1, 1 ) );
2155 ProcessFormatOptions( *moDataItemSet
, rInfo
);
2157 CreateNewEntry( rInfo
);
2160 void ScHTMLTable::BodyOff( const HtmlImportInfo
& rInfo
)
2163 if( !mpParentTable
)
2168 CreateNewEntry( rInfo
);
2171 ScHTMLTable
* ScHTMLTable::CloseTable( const HtmlImportInfo
& rInfo
)
2173 if( mpParentTable
) // not allowed to close global table
2175 PushEntry( rInfo
, mbDataOn
);
2178 mpParentTable
->PushTableEntry( GetTableId() );
2179 mpParentTable
->CreateNewEntry( rInfo
);
2180 if( mbPreFormText
) // enclose preformatted table with empty lines in parent table
2181 mpParentTable
->InsertLeadingEmptyLine();
2182 return mpParentTable
;
2187 SCCOLROW
ScHTMLTable::GetDocSize( ScHTMLOrient eOrient
, SCCOLROW nCellPos
) const
2189 const ScSizeVec
& rSizes
= maCumSizes
[ eOrient
];
2190 size_t nIndex
= static_cast< size_t >( nCellPos
);
2191 if( nIndex
>= rSizes
.size() ) return 0;
2192 return (nIndex
== 0) ? rSizes
.front() : (rSizes
[ nIndex
] - rSizes
[ nIndex
- 1 ]);
2195 SCCOLROW
ScHTMLTable::GetDocSize( ScHTMLOrient eOrient
, SCCOLROW nCellBegin
, SCCOLROW nCellEnd
) const
2197 const ScSizeVec
& rSizes
= maCumSizes
[ eOrient
];
2198 size_t nBeginIdx
= static_cast< size_t >( std::max
< SCCOLROW
>( nCellBegin
, 0 ) );
2199 size_t nEndIdx
= static_cast< size_t >( std::min
< SCCOLROW
>( nCellEnd
, static_cast< SCCOLROW
>( rSizes
.size() ) ) );
2200 if (nBeginIdx
>= nEndIdx
) return 0;
2201 return rSizes
[ nEndIdx
- 1 ] - ((nBeginIdx
== 0) ? 0 : rSizes
[ nBeginIdx
- 1 ]);
2204 SCCOLROW
ScHTMLTable::GetDocSize( ScHTMLOrient eOrient
) const
2206 const ScSizeVec
& rSizes
= maCumSizes
[ eOrient
];
2207 return rSizes
.empty() ? 0 : rSizes
.back();
2210 ScHTMLSize
ScHTMLTable::GetDocSize( const ScHTMLPos
& rCellPos
) const
2212 ScHTMLSize aCellSpan
= GetSpan( rCellPos
);
2214 static_cast< SCCOL
>( GetDocSize( tdCol
, rCellPos
.mnCol
, rCellPos
.mnCol
+ aCellSpan
.mnCols
) ),
2215 static_cast< SCROW
>( GetDocSize( tdRow
, rCellPos
.mnRow
, rCellPos
.mnRow
+ aCellSpan
.mnRows
) ) );
2218 SCCOLROW
ScHTMLTable::GetDocPos( ScHTMLOrient eOrient
, SCCOLROW nCellPos
) const
2220 return maDocBasePos
.Get( eOrient
) + GetDocSize( eOrient
, 0, nCellPos
);
2223 ScHTMLPos
ScHTMLTable::GetDocPos( const ScHTMLPos
& rCellPos
) const
2226 static_cast< SCCOL
>( GetDocPos( tdCol
, rCellPos
.mnCol
) ),
2227 static_cast< SCROW
>( GetDocPos( tdRow
, rCellPos
.mnRow
) ) );
2230 void ScHTMLTable::GetDocRange( ScRange
& rRange
) const
2232 rRange
.aStart
= rRange
.aEnd
= maDocBasePos
.MakeAddr();
2233 ScAddress
aErrorPos( ScAddress::UNINITIALIZED
);
2234 if (!rRange
.aEnd
.Move( static_cast< SCCOL
>( GetDocSize( tdCol
) ) - 1,
2235 static_cast< SCROW
>( GetDocSize( tdRow
) ) - 1, 0, aErrorPos
, mrDoc
))
2237 assert(!"can't move");
2241 void ScHTMLTable::ApplyCellBorders( ScDocument
* pDoc
, const ScAddress
& rFirstPos
) const
2243 OSL_ENSURE( pDoc
, "ScHTMLTable::ApplyCellBorders - no document" );
2244 if( pDoc
&& mbBorderOn
)
2246 const SCCOL nLastCol
= maSize
.mnCols
- 1;
2247 const SCROW nLastRow
= maSize
.mnRows
- 1;
2248 const tools::Long nOuterLine
= SvxBorderLineWidth::Medium
;
2249 const tools::Long nInnerLine
= SvxBorderLineWidth::Hairline
;
2250 SvxBorderLine
aOuterLine(nullptr, nOuterLine
, SvxBorderLineStyle::SOLID
);
2251 SvxBorderLine
aInnerLine(nullptr, nInnerLine
, SvxBorderLineStyle::SOLID
);
2252 SvxBoxItem
aBorderItem( ATTR_BORDER
);
2254 for( SCCOL nCol
= 0; nCol
<= nLastCol
; ++nCol
)
2256 SvxBorderLine
* pLeftLine
= (nCol
== 0) ? &aOuterLine
: &aInnerLine
;
2257 SvxBorderLine
* pRightLine
= (nCol
== nLastCol
) ? &aOuterLine
: &aInnerLine
;
2258 SCCOL nCellCol1
= static_cast< SCCOL
>( GetDocPos( tdCol
, nCol
) ) + rFirstPos
.Col();
2259 SCCOL nCellCol2
= nCellCol1
+ static_cast< SCCOL
>( GetDocSize( tdCol
, nCol
) ) - 1;
2260 for( SCROW nRow
= 0; nRow
<= nLastRow
; ++nRow
)
2262 SvxBorderLine
* pTopLine
= (nRow
== 0) ? &aOuterLine
: &aInnerLine
;
2263 SvxBorderLine
* pBottomLine
= (nRow
== nLastRow
) ? &aOuterLine
: &aInnerLine
;
2264 SCROW nCellRow1
= GetDocPos( tdRow
, nRow
) + rFirstPos
.Row();
2265 SCROW nCellRow2
= nCellRow1
+ GetDocSize( tdRow
, nRow
) - 1;
2266 for( SCCOL nCellCol
= nCellCol1
; nCellCol
<= nCellCol2
; ++nCellCol
)
2268 aBorderItem
.SetLine( (nCellCol
== nCellCol1
) ? pLeftLine
: nullptr, SvxBoxItemLine::LEFT
);
2269 aBorderItem
.SetLine( (nCellCol
== nCellCol2
) ? pRightLine
: nullptr, SvxBoxItemLine::RIGHT
);
2270 for( SCROW nCellRow
= nCellRow1
; nCellRow
<= nCellRow2
; ++nCellRow
)
2272 aBorderItem
.SetLine( (nCellRow
== nCellRow1
) ? pTopLine
: nullptr, SvxBoxItemLine::TOP
);
2273 aBorderItem
.SetLine( (nCellRow
== nCellRow2
) ? pBottomLine
: nullptr, SvxBoxItemLine::BOTTOM
);
2274 pDoc
->ApplyAttr( nCellCol
, nCellRow
, rFirstPos
.Tab(), aBorderItem
);
2281 for( ScHTMLTableIterator
aIter( mxNestedTables
.get() ); aIter
.is(); ++aIter
)
2282 aIter
->ApplyCellBorders( pDoc
, rFirstPos
);
2285 SvNumberFormatter
* ScHTMLTable::GetFormatTable()
2287 return mpParser
->GetDoc().GetFormatTable();
2290 bool ScHTMLTable::IsEmptyCell() const
2292 return mpCurrEntryVector
&& mpCurrEntryVector
->empty();
2295 bool ScHTMLTable::IsSpaceCharInfo( const HtmlImportInfo
& rInfo
)
2297 return (rInfo
.nToken
== HtmlTokenId::TEXTTOKEN
) && (rInfo
.aText
.getLength() == 1) && (rInfo
.aText
[ 0 ] == ' ');
2300 ScHTMLTable::ScHTMLEntryPtr
ScHTMLTable::CreateEntry() const
2302 return std::make_unique
<ScHTMLEntry
>( GetCurrItemSet() );
2305 void ScHTMLTable::CreateNewEntry( const HtmlImportInfo
& rInfo
)
2307 OSL_ENSURE( !mxCurrEntry
, "ScHTMLTable::CreateNewEntry - old entry still present" );
2308 mxCurrEntry
= CreateEntry();
2309 mxCurrEntry
->aSel
= rInfo
.aSelection
;
2312 void ScHTMLTable::ImplPushEntryToVector( ScHTMLEntryVector
& rEntryVector
, ScHTMLEntryPtr
& rxEntry
)
2314 // HTML entry list does not own the entries
2315 rEntryVector
.push_back( rxEntry
.get() );
2316 // mrEEParseList (reference to member of ScEEParser) owns the entries
2317 mrEEParseList
.push_back(std::shared_ptr
<ScEEParseEntry
>(rxEntry
.release()));
2320 bool ScHTMLTable::PushEntry( ScHTMLEntryPtr
& rxEntry
)
2322 bool bPushed
= false;
2323 if( rxEntry
&& rxEntry
->HasContents() )
2325 if( mpCurrEntryVector
)
2327 if( mbPushEmptyLine
)
2329 ScHTMLEntryPtr xEmptyEntry
= CreateEntry();
2330 ImplPushEntryToVector( *mpCurrEntryVector
, xEmptyEntry
);
2331 mbPushEmptyLine
= false;
2333 ImplPushEntryToVector( *mpCurrEntryVector
, rxEntry
);
2336 else if( mpParentTable
)
2338 bPushed
= mpParentTable
->PushEntry( rxEntry
);
2342 OSL_FAIL( "ScHTMLTable::PushEntry - cannot push entry, no parent found" );
2348 bool ScHTMLTable::PushEntry( const HtmlImportInfo
& rInfo
, bool bLastInCell
)
2350 OSL_ENSURE( mxCurrEntry
, "ScHTMLTable::PushEntry - no current entry" );
2351 bool bPushed
= false;
2354 mxCurrEntry
->AdjustEnd( rInfo
);
2355 mxCurrEntry
->Strip( mrEditEngine
);
2357 // import entry always, if it is the last in cell, and cell is still empty
2358 if( bLastInCell
&& IsEmptyCell() )
2360 mxCurrEntry
->SetImportAlways();
2361 // don't insert empty lines before single empty entries
2362 if( mxCurrEntry
->IsEmpty() )
2363 mbPushEmptyLine
= false;
2366 bPushed
= PushEntry( mxCurrEntry
);
2367 mxCurrEntry
.reset();
2372 void ScHTMLTable::PushTableEntry( ScHTMLTableId nTableId
)
2374 OSL_ENSURE( nTableId
!= SC_HTML_GLOBAL_TABLE
, "ScHTMLTable::PushTableEntry - cannot push global table" );
2375 if( nTableId
!= SC_HTML_GLOBAL_TABLE
)
2377 ScHTMLEntryPtr
xEntry( new ScHTMLEntry( maTableItemSet
, nTableId
) );
2378 PushEntry( xEntry
);
2382 ScHTMLTable
* ScHTMLTable::GetExistingTable( ScHTMLTableId nTableId
) const
2384 ScHTMLTable
* pTable
= ((nTableId
!= SC_HTML_GLOBAL_TABLE
) && mxNestedTables
) ?
2385 mxNestedTables
->FindTable( nTableId
, false ) : nullptr;
2386 OSL_ENSURE( pTable
|| (nTableId
== SC_HTML_GLOBAL_TABLE
), "ScHTMLTable::GetExistingTable - table not found" );
2390 ScHTMLTable
* ScHTMLTable::InsertNestedTable( const HtmlImportInfo
& rInfo
, bool bPreFormText
)
2392 if( !mxNestedTables
)
2393 mxNestedTables
.reset( new ScHTMLTableMap( *this ) );
2394 if( bPreFormText
) // enclose new preformatted table with empty lines
2395 InsertLeadingEmptyLine();
2396 return mxNestedTables
->CreateTable( rInfo
, bPreFormText
, mrDoc
);
2399 void ScHTMLTable::InsertNewCell( const ScHTMLSize
& rSpanSize
)
2403 /* Find an unused cell by skipping all merged ranges that cover the
2404 current cell position stored in maCurrCell. */
2407 pRange
= maVMergedCells
.Find( maCurrCell
.MakeAddr() );
2409 pRange
= maHMergedCells
.Find( maCurrCell
.MakeAddr() );
2412 maCurrCell
.mnCol
= pRange
->aEnd
.Col() + 1;
2414 mpCurrEntryVector
= &maEntryMap
[ maCurrCell
];
2416 /* If the new cell is merged horizontally, try to find collisions with
2417 other vertically merged ranges. In this case, shrink existing
2418 vertically merged ranges (do not shrink the new cell). */
2419 SCCOL nColEnd
= maCurrCell
.mnCol
+ rSpanSize
.mnCols
;
2420 for( ScAddress
aAddr( maCurrCell
.MakeAddr() ); aAddr
.Col() < nColEnd
; aAddr
.IncCol() )
2421 if( (pRange
= maVMergedCells
.Find( aAddr
)) != nullptr )
2422 pRange
->aEnd
.SetRow( maCurrCell
.mnRow
- 1 );
2424 // insert the new range into the cell lists
2425 ScRange
aNewRange( maCurrCell
.MakeAddr() );
2426 ScAddress
aErrorPos( ScAddress::UNINITIALIZED
);
2427 if (!aNewRange
.aEnd
.Move( rSpanSize
.mnCols
- 1, rSpanSize
.mnRows
- 1, 0, aErrorPos
, mrDoc
))
2429 assert(!"can't move");
2431 if( rSpanSize
.mnRows
> 1 )
2433 maVMergedCells
.push_back( aNewRange
);
2434 /* Do not insert vertically merged ranges into maUsedCells yet,
2435 because they may be shrunken (see above). The final vertically
2436 merged ranges are inserted in FillEmptyCells(). */
2440 if( rSpanSize
.mnCols
> 1 )
2441 maHMergedCells
.push_back( aNewRange
);
2442 /* Insert horizontally merged ranges and single cells into
2443 maUsedCells, they will not be changed anymore. */
2444 maUsedCells
.Join( aNewRange
);
2447 // adjust table size
2448 maSize
.mnCols
= std::max
< SCCOL
>( maSize
.mnCols
, aNewRange
.aEnd
.Col() + 1 );
2449 maSize
.mnRows
= std::max
< SCROW
>( maSize
.mnRows
, aNewRange
.aEnd
.Row() + 1 );
2452 void ScHTMLTable::ImplRowOn()
2456 moRowItemSet
.emplace( maTableItemSet
);
2457 maCurrCell
.mnCol
= 0;
2462 void ScHTMLTable::ImplRowOff()
2468 moRowItemSet
.reset();
2470 mbRowOn
= mbDataOn
= false;
2474 void ScHTMLTable::ImplDataOn( const ScHTMLSize
& rSpanSize
)
2480 moDataItemSet
.emplace( *moRowItemSet
);
2481 InsertNewCell( rSpanSize
);
2483 mbPushEmptyLine
= false;
2486 void ScHTMLTable::ImplDataOff()
2490 moDataItemSet
.reset();
2492 mpCurrEntryVector
= nullptr;
2497 void ScHTMLTable::ProcessFormatOptions( SfxItemSet
& rItemSet
, const HtmlImportInfo
& rInfo
)
2499 // special handling for table header cells
2500 if( rInfo
.nToken
== HtmlTokenId::TABLEHEADER_ON
)
2502 rItemSet
.Put( SvxWeightItem( WEIGHT_BOLD
, ATTR_FONT_WEIGHT
) );
2503 rItemSet
.Put( SvxHorJustifyItem( SvxCellHorJustify::Center
, ATTR_HOR_JUSTIFY
) );
2506 const HTMLOptions
& rOptions
= static_cast<HTMLParser
*>(rInfo
.pParser
)->GetOptions();
2507 for (const auto& rOption
: rOptions
)
2509 switch( rOption
.GetToken() )
2511 case HtmlOptionId::ALIGN
:
2513 SvxCellHorJustify eVal
= SvxCellHorJustify::Standard
;
2514 const OUString
& rOptVal
= rOption
.GetString();
2515 if( rOptVal
.equalsIgnoreAsciiCase( OOO_STRING_SVTOOLS_HTML_AL_right
) )
2516 eVal
= SvxCellHorJustify::Right
;
2517 else if( rOptVal
.equalsIgnoreAsciiCase( OOO_STRING_SVTOOLS_HTML_AL_center
) )
2518 eVal
= SvxCellHorJustify::Center
;
2519 else if( rOptVal
.equalsIgnoreAsciiCase( OOO_STRING_SVTOOLS_HTML_AL_left
) )
2520 eVal
= SvxCellHorJustify::Left
;
2521 if( eVal
!= SvxCellHorJustify::Standard
)
2522 rItemSet
.Put( SvxHorJustifyItem( eVal
, ATTR_HOR_JUSTIFY
) );
2526 case HtmlOptionId::VALIGN
:
2528 SvxCellVerJustify eVal
= SvxCellVerJustify::Standard
;
2529 const OUString
& rOptVal
= rOption
.GetString();
2530 if( rOptVal
.equalsIgnoreAsciiCase( OOO_STRING_SVTOOLS_HTML_VA_top
) )
2531 eVal
= SvxCellVerJustify::Top
;
2532 else if( rOptVal
.equalsIgnoreAsciiCase( OOO_STRING_SVTOOLS_HTML_VA_middle
) )
2533 eVal
= SvxCellVerJustify::Center
;
2534 else if( rOptVal
.equalsIgnoreAsciiCase( OOO_STRING_SVTOOLS_HTML_VA_bottom
) )
2535 eVal
= SvxCellVerJustify::Bottom
;
2536 if( eVal
!= SvxCellVerJustify::Standard
)
2537 rItemSet
.Put( SvxVerJustifyItem( eVal
, ATTR_VER_JUSTIFY
) );
2541 case HtmlOptionId::BGCOLOR
:
2544 rOption
.GetColor( aColor
);
2545 rItemSet
.Put( SvxBrushItem( aColor
, ATTR_BACKGROUND
) );
2553 void ScHTMLTable::SetDocSize( ScHTMLOrient eOrient
, SCCOLROW nCellPos
, SCCOLROW nSize
)
2555 OSL_ENSURE( nCellPos
>= 0, "ScHTMLTable::SetDocSize - unexpected negative position" );
2556 ScSizeVec
& rSizes
= maCumSizes
[ eOrient
];
2557 size_t nIndex
= static_cast< size_t >( nCellPos
);
2558 // expand with height/width == 1
2559 while( nIndex
>= rSizes
.size() )
2560 rSizes
.push_back( rSizes
.empty() ? 1 : (rSizes
.back() + 1) );
2561 // update size of passed position and all following
2562 // #i109987# only grow, don't shrink - use the largest needed size
2563 SCCOLROW nDiff
= nSize
- ((nIndex
== 0) ? rSizes
.front() : (rSizes
[ nIndex
] - rSizes
[ nIndex
- 1 ]));
2565 std::for_each(rSizes
.begin() + nIndex
, rSizes
.end(), [&nDiff
](SCCOLROW
& rSize
) { rSize
+= nDiff
; });
2568 void ScHTMLTable::CalcNeededDocSize(
2569 ScHTMLOrient eOrient
, SCCOLROW nCellPos
, SCCOLROW nCellSpan
, SCCOLROW nRealDocSize
)
2571 SCCOLROW nDiffSize
= 0;
2572 // in merged columns/rows: reduce needed size by size of leading columns
2573 while( nCellSpan
> 1 )
2575 nDiffSize
+= GetDocSize( eOrient
, nCellPos
);
2579 // set remaining needed size to last column/row
2580 nRealDocSize
-= std::min
< SCCOLROW
>( nRealDocSize
- 1, nDiffSize
);
2581 SetDocSize( eOrient
, nCellPos
, nRealDocSize
);
2584 void ScHTMLTable::FillEmptyCells()
2586 for( ScHTMLTableIterator
aIter( mxNestedTables
.get() ); aIter
.is(); ++aIter
)
2587 aIter
->FillEmptyCells();
2589 // insert the final vertically merged ranges into maUsedCells
2590 for ( size_t i
= 0, nRanges
= maVMergedCells
.size(); i
< nRanges
; ++i
)
2592 ScRange
& rRange
= maVMergedCells
[ i
];
2593 maUsedCells
.Join( rRange
);
2596 for( ScAddress aAddr
; aAddr
.Row() < maSize
.mnRows
; aAddr
.IncRow() )
2598 for( aAddr
.SetCol( 0 ); aAddr
.Col() < maSize
.mnCols
; aAddr
.IncCol() )
2600 if( !maUsedCells
.Find( aAddr
) )
2602 // create a range for the lock list (used to calc. cell span)
2603 ScRange
aRange( aAddr
);
2606 aRange
.aEnd
.IncCol();
2608 while( (aRange
.aEnd
.Col() < maSize
.mnCols
) && !maUsedCells
.Find( aRange
.aEnd
) );
2609 aRange
.aEnd
.IncCol( -1 );
2610 maUsedCells
.Join( aRange
);
2612 // insert a dummy entry
2613 ScHTMLEntryPtr xEntry
= CreateEntry();
2614 ImplPushEntryToVector( maEntryMap
[ ScHTMLPos( aAddr
) ], xEntry
);
2620 void ScHTMLTable::RecalcDocSize()
2622 // recalc table sizes recursively from inner to outer
2623 for( ScHTMLTableIterator
aIter( mxNestedTables
.get() ); aIter
.is(); ++aIter
)
2624 aIter
->RecalcDocSize();
2626 /* Two passes: first calculates the sizes of single columns/rows, then
2627 the sizes of spanned columns/rows. This allows to fill nested tables
2628 into merged cells optimally. */
2629 static const sal_uInt16 PASS_SINGLE
= 0;
2630 static const sal_uInt16 PASS_SPANNED
= 1;
2631 for( sal_uInt16 nPass
= PASS_SINGLE
; nPass
<= PASS_SPANNED
; ++nPass
)
2633 // iterate through every table cell
2634 for( const auto& [rCellPos
, rEntryVector
] : maEntryMap
)
2636 ScHTMLSize aCellSpan
= GetSpan( rCellPos
);
2638 // process the dimension of the current cell in this pass?
2639 // (pass is single and span is 1) or (pass is not single and span is not 1)
2640 bool bProcessColWidth
= ((nPass
== PASS_SINGLE
) == (aCellSpan
.mnCols
== 1));
2641 bool bProcessRowHeight
= ((nPass
== PASS_SINGLE
) == (aCellSpan
.mnRows
== 1));
2642 if( bProcessColWidth
|| bProcessRowHeight
)
2644 ScHTMLSize
aDocSize( 1, 0 ); // resulting size of the cell in document
2646 // expand the cell size for each cell parse entry
2647 for( const auto& rpEntry
: rEntryVector
)
2649 ScHTMLTable
* pTable
= GetExistingTable( rpEntry
->GetTableId() );
2650 // find entry with maximum width
2651 if( bProcessColWidth
&& pTable
)
2652 aDocSize
.mnCols
= std::max( aDocSize
.mnCols
, static_cast< SCCOL
>( pTable
->GetDocSize( tdCol
) ) );
2653 // add up height of each entry
2654 if( bProcessRowHeight
)
2655 aDocSize
.mnRows
+= pTable
? pTable
->GetDocSize( tdRow
) : 1;
2657 if( !aDocSize
.mnRows
)
2658 aDocSize
.mnRows
= 1;
2660 if( bProcessColWidth
)
2661 CalcNeededDocSize( tdCol
, rCellPos
.mnCol
, aCellSpan
.mnCols
, aDocSize
.mnCols
);
2662 if( bProcessRowHeight
)
2663 CalcNeededDocSize( tdRow
, rCellPos
.mnRow
, aCellSpan
.mnRows
, aDocSize
.mnRows
);
2669 void ScHTMLTable::RecalcDocPos( const ScHTMLPos
& rBasePos
)
2671 maDocBasePos
= rBasePos
;
2672 // after the previous assignment it is allowed to call GetDocPos() methods
2674 // iterate through every table cell
2675 for( auto& [rCellPos
, rEntryVector
] : maEntryMap
)
2677 // fixed doc position of the entire cell (first entry)
2678 const ScHTMLPos
aCellDocPos( GetDocPos( rCellPos
) );
2679 // fixed doc size of the entire cell
2680 const ScHTMLSize
aCellDocSize( GetDocSize( rCellPos
) );
2682 // running doc position for single entries
2683 ScHTMLPos
aEntryDocPos( aCellDocPos
);
2685 ScHTMLEntry
* pEntry
= nullptr;
2686 for( const auto& rpEntry
: rEntryVector
)
2689 if( ScHTMLTable
* pTable
= GetExistingTable( pEntry
->GetTableId() ) )
2691 pTable
->RecalcDocPos( aEntryDocPos
); // recalc nested table
2692 pEntry
->nCol
= SCCOL_MAX
;
2693 pEntry
->nRow
= SCROW_MAX
;
2694 SCROW nTableRows
= static_cast< SCROW
>( pTable
->GetDocSize( tdRow
) );
2696 // use this entry to pad empty space right of table
2697 if( mpParentTable
) // ... but not in global table
2699 SCCOL nStartCol
= aEntryDocPos
.mnCol
+ static_cast< SCCOL
>( pTable
->GetDocSize( tdCol
) );
2700 SCCOL nNextCol
= aEntryDocPos
.mnCol
+ aCellDocSize
.mnCols
;
2701 if( nStartCol
< nNextCol
)
2703 pEntry
->nCol
= nStartCol
;
2704 pEntry
->nRow
= aEntryDocPos
.mnRow
;
2705 pEntry
->nColOverlap
= nNextCol
- nStartCol
;
2706 pEntry
->nRowOverlap
= nTableRows
;
2709 aEntryDocPos
.mnRow
+= nTableRows
;
2713 pEntry
->nCol
= aEntryDocPos
.mnCol
;
2714 pEntry
->nRow
= aEntryDocPos
.mnRow
;
2715 if( mpParentTable
) // do not merge in global table
2716 pEntry
->nColOverlap
= aCellDocSize
.mnCols
;
2717 ++aEntryDocPos
.mnRow
;
2721 // pEntry points now to last entry.
2724 if( (pEntry
== rEntryVector
.front()) && (pEntry
->GetTableId() == SC_HTML_NO_TABLE
) )
2726 // pEntry is the only entry in this cell - merge rows of cell with single non-table entry.
2727 pEntry
->nRowOverlap
= aCellDocSize
.mnRows
;
2731 // fill up incomplete entry lists
2732 SCROW nFirstUnusedRow
= aCellDocPos
.mnRow
+ aCellDocSize
.mnRows
;
2733 while( aEntryDocPos
.mnRow
< nFirstUnusedRow
)
2735 ScHTMLEntryPtr
xDummyEntry( new ScHTMLEntry( pEntry
->GetItemSet() ) );
2736 xDummyEntry
->nCol
= aEntryDocPos
.mnCol
;
2737 xDummyEntry
->nRow
= aEntryDocPos
.mnRow
;
2738 xDummyEntry
->nColOverlap
= aCellDocSize
.mnCols
;
2739 ImplPushEntryToVector( rEntryVector
, xDummyEntry
);
2740 ++aEntryDocPos
.mnRow
;
2747 ScHTMLGlobalTable::ScHTMLGlobalTable(
2749 EditEngine
& rEditEngine
,
2750 std::vector
<std::shared_ptr
<ScEEParseEntry
>>& rEEParseVector
,
2751 ScHTMLTableId
& rnUnusedId
,
2752 ScHTMLParser
* pParser
,
2753 const ScDocument
& rDoc
2755 ScHTMLTable( rPool
, rEditEngine
, rEEParseVector
, rnUnusedId
, pParser
, rDoc
)
2759 ScHTMLGlobalTable::~ScHTMLGlobalTable()
2763 void ScHTMLGlobalTable::Recalc()
2765 // Fills up empty cells with a dummy entry. */
2767 // recalc table sizes of all nested tables and this table
2769 // recalc document positions of all entries in this table and in nested tables
2770 RecalcDocPos( GetDocPos() );
2773 ScHTMLQueryParser::ScHTMLQueryParser( EditEngine
* pEditEngine
, ScDocument
* pDoc
) :
2774 ScHTMLParser( pEditEngine
, pDoc
),
2775 mnUnusedId( SC_HTML_GLOBAL_TABLE
),
2779 new ScHTMLGlobalTable(*pPool
, *pEdit
, maList
, mnUnusedId
, this, *pDoc
));
2780 mpCurrTable
= mxGlobTable
.get();
2783 ScHTMLQueryParser::~ScHTMLQueryParser()
2787 ErrCode
ScHTMLQueryParser::Read( SvStream
& rStrm
, const OUString
& rBaseURL
)
2789 SvKeyValueIteratorRef xValues
;
2790 SvKeyValueIterator
* pAttributes
= nullptr;
2792 SfxObjectShell
* pObjSh
= mpDoc
->GetDocumentShell();
2793 if( pObjSh
&& pObjSh
->IsLoading() )
2795 pAttributes
= pObjSh
->GetHeaderAttributes();
2799 /* When not loading, set up fake HTTP headers to force the SfxHTMLParser
2800 to use UTF8 (used when pasting from clipboard) */
2801 const char* pCharSet
= rtl_getBestMimeCharsetFromTextEncoding( RTL_TEXTENCODING_UTF8
);
2804 OUString aContentType
= "text/html; charset=" +
2805 OUString::createFromAscii( pCharSet
);
2807 xValues
= new SvKeyValueIterator
;
2808 xValues
->Append( SvKeyValue( OOO_STRING_SVTOOLS_HTML_META_content_type
, aContentType
) );
2809 pAttributes
= xValues
.get();
2813 Link
<HtmlImportInfo
&,void> aOldLink
= pEdit
->GetHtmlImportHdl();
2814 pEdit
->SetHtmlImportHdl( LINK( this, ScHTMLQueryParser
, HTMLImportHdl
) );
2815 ErrCode nErr
= pEdit
->Read( rStrm
, rBaseURL
, EETextFormat::Html
, pAttributes
);
2816 pEdit
->SetHtmlImportHdl( aOldLink
);
2818 mxGlobTable
->Recalc();
2819 nColMax
= static_cast< SCCOL
>( mxGlobTable
->GetDocSize( tdCol
) - 1 );
2820 nRowMax
= static_cast< SCROW
>( mxGlobTable
->GetDocSize( tdRow
) - 1 );
2825 const ScHTMLTable
* ScHTMLQueryParser::GetGlobalTable() const
2827 return mxGlobTable
.get();
2830 void ScHTMLQueryParser::ProcessToken( const HtmlImportInfo
& rInfo
)
2832 switch( rInfo
.nToken
)
2834 // --- meta data ---
2835 case HtmlTokenId::META
: MetaOn( rInfo
); break; // <meta>
2837 // --- title handling ---
2838 case HtmlTokenId::TITLE_ON
: TitleOn(); break; // <title>
2839 case HtmlTokenId::TITLE_OFF
: TitleOff( rInfo
); break; // </title>
2841 case HtmlTokenId::STYLE_ON
: break;
2842 case HtmlTokenId::STYLE_OFF
: ParseStyle(rInfo
.aText
); break;
2844 // --- body handling ---
2845 case HtmlTokenId::BODY_ON
: mpCurrTable
->BodyOn( rInfo
); break; // <body>
2846 case HtmlTokenId::BODY_OFF
: mpCurrTable
->BodyOff( rInfo
); break; // </body>
2848 // --- insert text ---
2849 case HtmlTokenId::TEXTTOKEN
: InsertText( rInfo
); break; // any text
2850 case HtmlTokenId::LINEBREAK
: mpCurrTable
->BreakOn(); break; // <br>
2851 case HtmlTokenId::HEAD1_ON
: // <h1>
2852 case HtmlTokenId::HEAD2_ON
: // <h2>
2853 case HtmlTokenId::HEAD3_ON
: // <h3>
2854 case HtmlTokenId::HEAD4_ON
: // <h4>
2855 case HtmlTokenId::HEAD5_ON
: // <h5>
2856 case HtmlTokenId::HEAD6_ON
: // <h6>
2857 case HtmlTokenId::PARABREAK_ON
: mpCurrTable
->HeadingOn(); break; // <p>
2859 // --- misc. contents ---
2860 case HtmlTokenId::ANCHOR_ON
: mpCurrTable
->AnchorOn(); break; // <a>
2862 // --- table handling ---
2863 case HtmlTokenId::TABLE_ON
: TableOn( rInfo
); break; // <table>
2864 case HtmlTokenId::TABLE_OFF
: TableOff( rInfo
); break; // </table>
2865 case HtmlTokenId::CAPTION_ON
: mpCurrTable
->CaptionOn(); break; // <caption>
2866 case HtmlTokenId::CAPTION_OFF
: mpCurrTable
->CaptionOff(); break; // </caption>
2867 case HtmlTokenId::TABLEROW_ON
: mpCurrTable
->RowOn( rInfo
); break; // <tr>
2868 case HtmlTokenId::TABLEROW_OFF
: mpCurrTable
->RowOff( rInfo
); break; // </tr>
2869 case HtmlTokenId::TABLEHEADER_ON
: // <th>
2870 case HtmlTokenId::TABLEDATA_ON
: mpCurrTable
->DataOn( rInfo
); break; // <td>
2871 case HtmlTokenId::TABLEHEADER_OFF
: // </th>
2872 case HtmlTokenId::TABLEDATA_OFF
: mpCurrTable
->DataOff( rInfo
); break; // </td>
2873 case HtmlTokenId::PREFORMTXT_ON
: PreOn( rInfo
); break; // <pre>
2874 case HtmlTokenId::PREFORMTXT_OFF
: PreOff( rInfo
); break; // </pre>
2876 // --- formatting ---
2877 case HtmlTokenId::FONT_ON
: FontOn( rInfo
); break; // <font>
2879 case HtmlTokenId::BIGPRINT_ON
: // <big>
2880 //! TODO: store current font size, use following size
2881 mpCurrTable
->PutItem( SvxFontHeightItem( maFontHeights
[ 3 ], 100, ATTR_FONT_HEIGHT
) );
2883 case HtmlTokenId::SMALLPRINT_ON
: // <small>
2884 //! TODO: store current font size, use preceding size
2885 mpCurrTable
->PutItem( SvxFontHeightItem( maFontHeights
[ 0 ], 100, ATTR_FONT_HEIGHT
) );
2888 case HtmlTokenId::BOLD_ON
: // <b>
2889 case HtmlTokenId::STRONG_ON
: // <strong>
2890 mpCurrTable
->PutItem( SvxWeightItem( WEIGHT_BOLD
, ATTR_FONT_WEIGHT
) );
2893 case HtmlTokenId::ITALIC_ON
: // <i>
2894 case HtmlTokenId::EMPHASIS_ON
: // <em>
2895 case HtmlTokenId::ADDRESS_ON
: // <address>
2896 case HtmlTokenId::BLOCKQUOTE_ON
: // <blockquote>
2897 case HtmlTokenId::BLOCKQUOTE30_ON
: // <bq>
2898 case HtmlTokenId::CITATION_ON
: // <cite>
2899 case HtmlTokenId::VARIABLE_ON
: // <var>
2900 mpCurrTable
->PutItem( SvxPostureItem( ITALIC_NORMAL
, ATTR_FONT_POSTURE
) );
2903 case HtmlTokenId::DEFINSTANCE_ON
: // <dfn>
2904 mpCurrTable
->PutItem( SvxWeightItem( WEIGHT_BOLD
, ATTR_FONT_WEIGHT
) );
2905 mpCurrTable
->PutItem( SvxPostureItem( ITALIC_NORMAL
, ATTR_FONT_POSTURE
) );
2908 case HtmlTokenId::UNDERLINE_ON
: // <u>
2909 mpCurrTable
->PutItem( SvxUnderlineItem( LINESTYLE_SINGLE
, ATTR_FONT_UNDERLINE
) );
2915 void ScHTMLQueryParser::InsertText( const HtmlImportInfo
& rInfo
)
2917 mpCurrTable
->PutText( rInfo
);
2919 maTitle
.append(rInfo
.aText
);
2922 void ScHTMLQueryParser::FontOn( const HtmlImportInfo
& rInfo
)
2924 const HTMLOptions
& rOptions
= static_cast<HTMLParser
*>(rInfo
.pParser
)->GetOptions();
2925 for (const auto& rOption
: rOptions
)
2927 switch( rOption
.GetToken() )
2929 case HtmlOptionId::FACE
:
2931 const OUString
& rFace
= rOption
.GetString();
2936 // font list separator: VCL = ';' HTML = ','
2937 std::u16string_view aFName
= comphelper::string::strip(o3tl::getToken(rFace
, 0, ',', nPos
), ' ');
2938 aFontName
= ScGlobal::addToken(aFontName
, aFName
, ';');
2940 if ( !aFontName
.isEmpty() )
2941 mpCurrTable
->PutItem( SvxFontItem( FAMILY_DONTKNOW
,
2942 aFontName
, OUString(), PITCH_DONTKNOW
,
2943 RTL_TEXTENCODING_DONTKNOW
, ATTR_FONT
) );
2946 case HtmlOptionId::SIZE
:
2948 sal_uInt32 nSize
= getLimitedValue
< sal_uInt32
>( rOption
.GetNumber(), 1, SC_HTML_FONTSIZES
);
2949 mpCurrTable
->PutItem( SvxFontHeightItem( maFontHeights
[ nSize
- 1 ], 100, ATTR_FONT_HEIGHT
) );
2952 case HtmlOptionId::COLOR
:
2955 rOption
.GetColor( aColor
);
2956 mpCurrTable
->PutItem( SvxColorItem( aColor
, ATTR_FONT_COLOR
) );
2964 void ScHTMLQueryParser::MetaOn( const HtmlImportInfo
& rInfo
)
2966 if( mpDoc
->GetDocumentShell() )
2968 HTMLParser
* pParser
= static_cast< HTMLParser
* >( rInfo
.pParser
);
2970 uno::Reference
<document::XDocumentPropertiesSupplier
> xDPS(
2971 mpDoc
->GetDocumentShell()->GetModel(), uno::UNO_QUERY_THROW
);
2972 pParser
->ParseMetaOptions(
2973 xDPS
->getDocumentProperties(),
2974 mpDoc
->GetDocumentShell()->GetHeaderAttributes() );
2978 void ScHTMLQueryParser::TitleOn()
2981 maTitle
.setLength(0);
2984 void ScHTMLQueryParser::TitleOff( const HtmlImportInfo
& rInfo
)
2989 OUString aTitle
= maTitle
.makeStringAndClear().trim();
2990 if (!aTitle
.isEmpty() && mpDoc
->GetDocumentShell())
2992 uno::Reference
<document::XDocumentPropertiesSupplier
> xDPS(
2993 mpDoc
->GetDocumentShell()->GetModel(), uno::UNO_QUERY_THROW
);
2995 xDPS
->getDocumentProperties()->setTitle(aTitle
);
2997 InsertText( rInfo
);
3001 void ScHTMLQueryParser::TableOn( const HtmlImportInfo
& rInfo
)
3003 mpCurrTable
= mpCurrTable
->TableOn( rInfo
);
3006 void ScHTMLQueryParser::TableOff( const HtmlImportInfo
& rInfo
)
3008 mpCurrTable
= mpCurrTable
->TableOff( rInfo
);
3011 void ScHTMLQueryParser::PreOn( const HtmlImportInfo
& rInfo
)
3013 mpCurrTable
= mpCurrTable
->PreOn( rInfo
);
3016 void ScHTMLQueryParser::PreOff( const HtmlImportInfo
& rInfo
)
3018 mpCurrTable
= mpCurrTable
->PreOff( rInfo
);
3021 void ScHTMLQueryParser::CloseTable( const HtmlImportInfo
& rInfo
)
3023 mpCurrTable
= mpCurrTable
->CloseTable( rInfo
);
3029 * Handler class for the CSS parser.
3031 class CSSHandler
: public orcus::css_handler
3038 MemStr() : mp(nullptr), mn(0) {}
3039 MemStr(const char* p
, size_t n
) : mp(p
), mn(n
) {}
3040 MemStr(const MemStr
& r
) : mp(r
.mp
), mn(r
.mn
) {}
3041 MemStr
& operator=(const MemStr
& r
) = default;
3044 typedef std::pair
<MemStr
, MemStr
> SelectorName
; // element : class
3045 typedef std::vector
<SelectorName
> SelectorNames
;
3047 SelectorNames maSelectorNames
; // current selector names
3048 MemStr maPropName
; // current property name.
3049 MemStr maPropValue
; // current property value.
3050 ScHTMLStyles
& mrStyles
;
3053 explicit CSSHandler(ScHTMLStyles
& rStyles
):
3059 // selector name starting with "@"
3060 static void at_rule_name(const char* /*p*/, size_t /*n*/)
3062 // TODO: For now, we ignore at-rule properties
3065 // selector name not starting with "." or "#" (i.e. element selectors)
3066 void simple_selector_type(const char* pElem
, size_t nElem
)
3068 MemStr
aElem(pElem
, nElem
); // element given
3069 MemStr
aClass(nullptr, 0); // class name not given - to be added in the "element global" storage
3070 SelectorName
aName(aElem
, aClass
);
3072 maSelectorNames
.push_back(aName
);
3075 // selector names starting with a "." (i.e. class selector)
3076 void simple_selector_class(const char* pClassName
, size_t nClassName
)
3078 MemStr
aElem(nullptr, 0); // no element given - should be added in the "global" storage
3079 MemStr
aClass(pClassName
, nClassName
);
3080 SelectorName
aName(aElem
, aClass
);
3082 maSelectorNames
.push_back(aName
);
3085 // TODO: Add other selectors
3087 void property_name(const char* p
, size_t n
)
3089 maPropName
= MemStr(p
, n
);
3092 void value(const char* p
, size_t n
)
3094 maPropValue
= MemStr(p
, n
);
3098 maSelectorNames
.clear();
3103 SelectorNames::const_iterator itr
= maSelectorNames
.begin(), itrEnd
= maSelectorNames
.end();
3104 for (; itr
!= itrEnd
; ++itr
)
3106 // Add this property to the collection for each selector.
3107 const SelectorName
& rSelName
= *itr
;
3108 const MemStr
& rElem
= rSelName
.first
;
3109 const MemStr
& rClass
= rSelName
.second
;
3110 OUString
aName(maPropName
.mp
, maPropName
.mn
, RTL_TEXTENCODING_UTF8
);
3111 OUString
aValue(maPropValue
.mp
, maPropValue
.mn
, RTL_TEXTENCODING_UTF8
);
3112 mrStyles
.add(rElem
.mp
, rElem
.mn
, rClass
.mp
, rClass
.mn
, aName
, aValue
);
3114 maPropName
= MemStr();
3115 maPropValue
= MemStr();
3122 void ScHTMLQueryParser::ParseStyle(std::u16string_view rStrm
)
3124 OString aStr
= OUStringToOString(rStrm
, RTL_TEXTENCODING_UTF8
);
3125 CSSHandler
aHdl(GetStyles());
3126 orcus::css_parser
<CSSHandler
> aParser(aStr
.getStr(), aStr
.getLength(), aHdl
);
3131 catch (const orcus::css::parse_error
& rOrcusParseError
)
3133 SAL_WARN("sc", "ScHTMLQueryParser::ParseStyle: " << rOrcusParseError
.what());
3134 // TODO: Parsing of CSS failed. Do nothing for now.
3138 IMPL_LINK( ScHTMLQueryParser
, HTMLImportHdl
, HtmlImportInfo
&, rInfo
, void )
3140 switch( rInfo
.eState
)
3142 case HtmlImportState::Start
:
3145 case HtmlImportState::NextToken
:
3146 ProcessToken( rInfo
);
3149 case HtmlImportState::InsertPara
:
3150 mpCurrTable
->InsertPara( rInfo
);
3153 case HtmlImportState::SetAttr
:
3154 case HtmlImportState::InsertText
:
3155 case HtmlImportState::InsertField
:
3158 case HtmlImportState::End
:
3159 while( mpCurrTable
->GetTableId() != SC_HTML_GLOBAL_TABLE
)
3160 CloseTable( rInfo
);
3164 OSL_FAIL( "ScHTMLQueryParser::HTMLImportHdl - unknown ImportInfo::eState" );
3168 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */