Version 7.5.1.1, tag libreoffice-7.5.1.1
[LibreOffice.git] / sc / source / filter / html / htmlpars.cxx
blobc2414d8588029ccbdfe8e0b2b4c88394273ae3bb
1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /*
3 * This file is part of the LibreOffice project.
5 * This Source Code Form is subject to the terms of the Mozilla Public
6 * License, v. 2.0. If a copy of the MPL was not distributed with this
7 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
9 * This file incorporates work covered by the following license notice:
11 * Licensed to the Apache Software Foundation (ASF) under one or more
12 * contributor license agreements. See the NOTICE file distributed
13 * with this work for additional information regarding copyright
14 * ownership. The ASF licenses this file to you under the Apache
15 * License, Version 2.0 (the "License"); you may not use this file
16 * except in compliance with the License. You may obtain a copy of
17 * the License at http://www.apache.org/licenses/LICENSE-2.0 .
20 #include <memory>
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>
42 #include <utility>
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>
56 #include <attrib.hxx>
57 #include <htmlpars.hxx>
58 #include <global.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>
67 #include <numeric>
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)
78 if (pElemName)
80 OUString aElem(pElemName, nElemName, RTL_TEXTENCODING_UTF8);
81 aElem = aElem.toAsciiLowerCase();
82 if (pClassName)
84 // Both element and class names given.
85 ElemsType::iterator itrElem = m_ElemProps.find(aElem);
86 if (itrElem == m_ElemProps.end())
88 // new element
89 std::pair<ElemsType::iterator, bool> r =
90 m_ElemProps.insert(std::make_pair(aElem, NamePropsType()));
91 if (!r.second)
92 // insertion failed.
93 return;
94 itrElem = r.first;
97 NamePropsType& rClsProps = itrElem->second;
98 OUString aClass(pClassName, nClassName, RTL_TEXTENCODING_UTF8);
99 aClass = aClass.toAsciiLowerCase();
100 insertProp(rClsProps, aClass, aProp, aValue);
102 else
104 // Element name only. Add it to the element global.
105 insertProp(m_ElemGlobalProps, aElem, aProp, aValue);
108 else
110 if (pClassName)
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())
135 return itr3->second;
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())
147 return itr2->second;
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())
158 return itr2->second;
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())
172 // new element
173 std::pair<NamePropsType::iterator, bool> r =
174 rStore.insert(std::make_pair(aName, PropsType()));
175 if (!r.second)
176 // insertion failed.
177 return;
179 itr = r.first;
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 ),
190 mpDoc( pDoc )
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 ),
213 nFirstTableCell(0),
214 nTableLevel(0),
215 nTable(0),
216 nMaxTable(0),
217 nColCntStart(0),
218 nMaxCol(0),
219 nTableWidth(0),
220 nColOffset(0),
221 nColOffsetStart(0),
222 nOffsetTolerance( SC_HTML_OFFSET_TOLERANCE_SMALL ),
223 bFirstRow( true ),
224 bTabInTabCell( false ),
225 bInCell( false ),
226 bInTitle( 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;
239 aTableStack.pop();
241 delete pLocalColOffset;
242 if ( pTables )
244 for( const auto& rEntry : *pTables)
245 delete rEntry.second;
246 pTables.reset();
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;
260 if ( bLoading )
261 pAttributes = pObjSh->GetHeaderAttributes();
262 else
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 );
267 if( pCharSet )
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
282 Adjust();
283 OutputDevice* pDefaultDev = Application::GetDefaultDevice();
284 sal_uInt16 nCount = maColOffset.size();
285 sal_uLong nOff = maColOffset[0];
286 Size aSize;
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];
294 return nErr;
297 const ScHTMLTable* ScHTMLLayoutParser::GetGlobalTable() const
299 return nullptr;
302 void ScHTMLLayoutParser::NewActEntry( const ScEEParseEntry* pE )
304 ScEEParser::NewActEntry( pE );
305 if ( 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
327 else
329 OSL_FAIL( "EntryEnd: EditEngine ESelection End < Start" );
333 void ScHTMLLayoutParser::NextRow( const HtmlImportInfo* pInfo )
335 if ( bInCell )
336 CloseEntry( pInfo );
337 if ( nRowMax < ++nRowCnt )
338 nRowMax = nRowCnt;
339 nColCnt = nColCntStart;
340 nColOffset = nColOffsetStart;
341 bFirstRow = false;
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);
352 if ( bFound )
353 return true;
354 sal_uInt16 nCount = pOffset->size();
355 if ( !nCount )
356 return false;
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) )
359 return true;
360 // Not smaller than everything else? Then compare with the next lower one
361 else if ( nPos && (((*pOffset)[nPos-1] + nOffsetTol) >= nOffset) )
363 (*pCol)--;
364 return true;
366 return false;
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" );
373 SCCOL nPos;
374 if ( SeekOffset( pOffset, nOffset, &nPos, nOffsetTol ) )
375 nOffset = static_cast<sal_uInt16>((*pOffset)[nPos]);
376 else
377 pOffset->insert( nOffset );
378 if ( nWidth )
380 if ( SeekOffset( pOffset, nOffset + nWidth, &nPos, nWidthTol ) )
381 nWidth = static_cast<sal_uInt16>((*pOffset)[nPos]) - nOffset;
382 else
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" );
391 SCCOL nPos;
392 if ( SeekOffset( pOffset, nOffset, &nPos, nOffsetTol ) )
393 nOffset = static_cast<sal_uInt16>((*pOffset)[nPos]);
394 else
395 pOffset->insert( nOffset );
396 if ( nWidth )
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" );
407 SCCOL nPos;
408 if ( !SeekOffset( pOffset, nOldOffset, &nPos, nOffsetTol ) )
410 if ( SeekOffset( pOffset, nNewOffset, &nPos, nOffsetTol ) )
411 nNewOffset = static_cast<sal_uInt16>((*pOffset)[nPos]);
412 else
413 pOffset->insert( nNewOffset );
414 return ;
416 nOldOffset = static_cast<sal_uInt16>((*pOffset)[nPos]);
417 SCCOL nPos2;
418 if ( SeekOffset( pOffset, nNewOffset, &nPos2, nOffsetTol ) )
420 nNewOffset = static_cast<sal_uInt16>((*pOffset)[nPos2]);
421 return ;
423 tools::Long nDiff = nNewOffset - nOldOffset;
424 if ( nDiff < 0 )
428 const_cast<sal_uLong&>((*pOffset)[nPos]) += nDiff;
429 } while ( nPos-- );
431 else
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) )
443 return;
445 // Or else this would create a wrong value at ScAddress (chance for an infinite loop)!
446 bool bBadCol = false;
447 bool bAgain;
448 ScRange aRange( pE->nCol, pE->nRow, 0,
449 pE->nCol + pE->nColOverlap - 1, pE->nRow + pE->nRowOverlap - 1, 0 );
452 bAgain = false;
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() )
461 bBadCol = true;
462 else
464 bAgain = true;
465 aRange.aStart.SetCol( pE->nCol );
466 aRange.aEnd.SetCol( nTmp );
468 break;
471 } while ( bAgain );
472 if ( bJoin && !bBadCol )
473 xLockedList->Join( aRange );
476 void ScHTMLLayoutParser::Adjust()
478 xLockedList->RemoveAll();
480 std::stack< std::unique_ptr<ScHTMLAdjustStackEntry> > aStack;
481 sal_uInt16 nTab = 0;
482 SCCOL nLastCol = SCCOL_MAX;
483 SCROW nNextRow = 0;
484 SCROW nCurRow = 0;
485 sal_uInt16 nPageWidth = static_cast<sal_uInt16>(aPageSize.Width());
486 InnerMap* pTab = nullptr;
487 for (auto& pE : maList)
489 if ( pE->nTab < nTab )
490 { // Table finished
491 if ( !aStack.empty() )
493 std::unique_ptr<ScHTMLAdjustStackEntry> pS = std::move(aStack.top());
494 aStack.pop();
496 nLastCol = pS->nLastCol;
497 nNextRow = pS->nNextRow;
498 nCurRow = pS->nCurRow;
500 nTab = pE->nTab;
501 if (pTables)
503 OuterMap::const_iterator it = pTables->find( nTab );
504 if ( it != pTables->end() )
505 pTab = it->second;
509 SCROW nRow = pE->nRow;
510 if ( pE->nCol <= nLastCol )
511 { // Next row
512 if ( pE->nRow < nNextRow )
513 pE->nRow = nCurRow = nNextRow;
514 else
515 nCurRow = nNextRow = pE->nRow;
516 SCROW nR = 0;
517 if ( pTab )
519 InnerMap::const_iterator it = pTab->find( nCurRow );
520 if ( it != pTab->end() )
521 nR = it->second;
523 if ( nR )
524 nNextRow += nR;
525 else
526 nNextRow++;
528 else
529 pE->nRow = nCurRow;
530 nLastCol = pE->nCol; // Read column
531 if ( pE->nTab > nTab )
532 { // New table
533 aStack.push( std::make_unique<ScHTMLAdjustStackEntry>(
534 nLastCol, nNextRow, nCurRow ) );
535 nTab = pE->nTab;
536 if ( pTables )
538 OuterMap::const_iterator it = pTables->find( nTab );
539 if ( it != pTables->end() )
540 pTab = it->second;
542 // New line spacing
543 SCROW nR = 0;
544 if ( pTab )
546 InnerMap::const_iterator it = pTab->find( nCurRow );
547 if ( it != pTab->end() )
548 nR = it->second;
550 if ( nR )
551 nNextRow = nCurRow + nR;
552 else
553 nNextRow = nCurRow + 1;
555 if ( nTab == 0 )
556 pE->nWidth = nPageWidth;
557 else
558 { // Real table, no paragraphs on the field
559 if ( pTab )
561 SCROW nRowSpan = pE->nRowOverlap;
562 for ( SCROW j=0; j < nRowSpan; j++ )
563 { // RowSpan resulting from merged rows
564 SCROW nRows = 0;
565 InnerMap::const_iterator it = pTab->find( nRow+j );
566 if ( it != pTab->end() )
567 nRows = it->second;
568 if ( nRows > 1 )
570 pE->nRowOverlap += nRows - 1;
571 if ( j == 0 )
572 { // Merged rows move the next row
573 SCROW nTmp = nCurRow + nRows;
574 if ( nNextRow < nTmp )
575 nNextRow = nTmp;
581 // Real column
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 );
593 else
595 pE->nOffset = static_cast<sal_uInt16>(maColOffset[pE->nCol]);
598 SCCOL nPos;
599 if ( pE->nWidth && SeekOffset( &maColOffset, pE->nOffset + pE->nWidth, &nPos, nOffsetTolerance ) )
600 pE->nColOverlap = (nPos > pE->nCol ? nPos - pE->nCol : 1);
601 else
603 //FIXME: This may not be correct, but works anyway ...
604 pE->nColOverlap = 1;
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 )
611 nColMax = nColTmp;
612 SCROW nRowTmp = pE->nRow + pE->nRowOverlap;
613 if ( nRowMax < nRowTmp )
614 nRowMax = nRowTmp;
618 sal_uInt16 ScHTMLLayoutParser::GetWidth( const ScEEParseEntry* pE )
620 if ( pE->nWidth )
621 return pE->nWidth;
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;
629 return 0;
632 void ScHTMLLayoutParser::SetWidths()
634 SCCOL nCol;
635 if ( !nTableWidth )
636 nTableWidth = static_cast<sal_uInt16>(aPageSize.Width());
637 SCCOL nColsPerRow = nMaxCol - nColCntStart;
638 if ( nColsPerRow <= 0 )
639 nColsPerRow = 1;
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
660 else
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;
683 else
684 { // try to find a single undefined width
685 sal_uInt16 nTotal = 0;
686 bool bFound = false;
687 SCCOL nHere = 0;
688 SCCOL nStop = std::min( static_cast<SCCOL>(nCol + pE->nColOverlap), nColsPerRow );
689 for ( ; nCol < nStop; nCol++ )
691 if ( pWidths[nCol] )
692 nTotal = nTotal + pWidths[nCol];
693 else
695 if ( bFound )
697 bFound = false;
698 break; // for
700 bFound = true;
701 nHere = 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++ )
714 if ( pWidths[nCol] )
715 nWidths = nWidths + pWidths[nCol];
716 else
717 nUnknown++;
719 if ( nUnknown )
721 sal_uInt16 nW = ((nWidths < nTableWidth) ?
722 ((nTableWidth - nWidths) / nUnknown) :
723 (nTableWidth / nUnknown));
724 for ( nCol = 0; nCol < nColsPerRow; nCol++ )
726 if ( !pWidths[nCol] )
727 pWidths[nCol] = nW;
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 )
753 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 )
781 if ( !pE->nWidth )
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 )
794 pE->nCol = nColCnt;
795 if ( pE->nRow == SCROW_MAX )
796 pE->nRow = nRowCnt;
797 SCCOL nCol = pE->nCol;
798 SkipLocked( pE ); // Change of columns to the right
800 if ( nCol < pE->nCol )
801 { // Replaced
802 nCol = pE->nCol - nColCntStart;
803 SCCOL nCount = static_cast<SCCOL>(pLocalColOffset->size());
804 if ( nCol < nCount )
805 nColOffset = static_cast<sal_uInt16>((*pLocalColOffset)[nCol]);
806 else
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 );
812 if ( pE->nWidth )
813 pE->nWidth = nWidth;
814 nColOffset = pE->nOffset + nWidth;
815 if ( nTableWidth < nColOffset - nColOffsetStart )
816 nTableWidth = nColOffset - nColOffsetStart;
819 void ScHTMLLayoutParser::CloseEntry( const HtmlImportInfo* pInfo )
821 bInCell = false;
822 if ( bTabInTabCell )
823 { // From the stack in TableOff
824 bTabInTabCell = false;
825 NewActEntry(maList.back().get()); // New free flying mxActEntry
826 return ;
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
841 rSel.nStartPara++;
843 while ( rSel.nEndPos == 0 && rSel.nEndPara > rSel.nStartPara )
844 { // Strip successive empty paragraphs
845 rSel.nEndPara--;
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:
864 ProcToken( &rInfo );
865 break;
866 case HtmlImportState::Start:
867 break;
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.
874 bInCell = false;
875 NextRow( &rInfo );
876 bInCell = true;
878 CloseEntry( &rInfo );
880 while ( nTableLevel > 0 )
881 TableOff( &rInfo ); // close tables, if </TABLE> missing
882 break;
883 case HtmlImportState::SetAttr:
884 break;
885 case HtmlImportState::InsertText:
886 break;
887 case HtmlImportState::InsertPara:
888 if ( nTableLevel < 1 )
890 CloseEntry( &rInfo );
891 NextRow( &rInfo );
893 break;
894 case HtmlImportState::InsertField:
895 break;
896 default:
897 OSL_FAIL("HTMLImportHdl: unknown ImportInfo.eState");
901 void ScHTMLLayoutParser::TableDataOn( HtmlImportInfo* pInfo )
903 if ( bInCell )
904 CloseEntry( pInfo );
905 if ( !nTableLevel )
907 OSL_FAIL( "dumbo doc! <TH> or <TD> without previous <TABLE>" );
908 TableOn( pInfo );
910 bInCell = true;
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());
921 break;
922 case HtmlOptionId::ROWSPAN:
924 mxActEntry->nRowOverlap = static_cast<SCROW>(rOption.GetString().toInt32());
926 break;
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;
938 else
939 eVal = SvxCellHorJustify::Standard;
940 if ( eVal != SvxCellHorJustify::Standard )
941 mxActEntry->aItemSet.Put(SvxHorJustifyItem(eVal, ATTR_HOR_JUSTIFY));
943 break;
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;
954 else
955 eVal = SvxCellVerJustify::Standard;
956 mxActEntry->aItemSet.Put(SvxVerJustifyItem(eVal, ATTR_VER_JUSTIFY));
958 break;
959 case HtmlOptionId::WIDTH:
961 mxActEntry->nWidth = GetWidthPixel(rOption);
963 break;
964 case HtmlOptionId::BGCOLOR:
966 Color aColor;
967 rOption.GetColor( aColor );
968 mxActEntry->aItemSet.Put(SvxBrushItem(aColor, ATTR_BACKGROUND));
970 break;
971 case HtmlOptionId::SDVAL:
973 mxActEntry->pValStr = rOption.GetString();
975 break;
976 case HtmlOptionId::SDNUM:
978 mxActEntry->pNumStr = rOption.GetString();
980 break;
981 default: break;
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 )
1003 NextRow( pInfo );
1006 void ScHTMLLayoutParser::TableDataOff( const HtmlImportInfo* pInfo )
1008 if ( bInCell )
1009 CloseEntry( pInfo ); // Only if it really was one
1012 void ScHTMLLayoutParser::TableOn( HtmlImportInfo* pInfo )
1014 if ( ++nTableLevel > 1 )
1015 { // Table in Table
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,
1022 bFirstRow ) );
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 );
1041 break;
1042 case HtmlOptionId::BORDER:
1043 // Border is: ((pOption->GetString().Len() == 0) || (pOption->GetNumber() != 0));
1044 break;
1045 default: break;
1049 bInCell = false;
1050 if ( bTabInTabCell && (nTableWidth >= nLastWidth) )
1051 { // Multiple tables in one cell, underneath each other
1052 bTabInTabCell = false;
1053 NextRow( pInfo );
1055 else
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;
1066 else
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 );
1072 NextRow( pInfo );
1074 aTableStack.push( std::make_unique<ScHTMLTableStackEntry>(
1075 mxActEntry, xLockedList, pLocalColOffset, nFirstTableCell,
1076 nRowCnt, nColCntStart, nMaxCol, nTable,
1077 nTableWidth, nColOffset, nColOffsetStart,
1078 bFirstRow ) );
1079 // As soon as we have multiple tables we need to be tolerant with the offsets.
1080 if (nMaxTable > 0)
1081 nOffsetTolerance = SC_HTML_OFFSET_TOLERANCE_LARGE;
1082 nTableWidth = 0;
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 );
1095 break;
1096 case HtmlOptionId::BORDER:
1097 //BorderOn is: ((pOption->GetString().Len() == 0) || (pOption->GetNumber() != 0));
1098 break;
1099 default: break;
1104 nTable = ++nMaxTable;
1105 bFirstRow = true;
1106 nFirstTableCell = maList.size();
1108 pLocalColOffset = new ScHTMLColOffset;
1109 MakeColNoRef( pLocalColOffset, nColOffsetStart, 0, 0, 0 );
1112 void ScHTMLLayoutParser::TableOff( const HtmlImportInfo* pInfo )
1114 if ( bInCell )
1115 CloseEntry( pInfo );
1116 if ( nColCnt > nColCntStart )
1117 TableRowOff( pInfo ); // The optional TableRowOff wasn't
1118 if ( !nTableLevel )
1120 OSL_FAIL( "dumbo doc! </TABLE> without opening <TABLE>" );
1121 return ;
1123 if ( --nTableLevel > 0 )
1124 { // Table in Table done
1125 if ( !aTableStack.empty() )
1127 std::unique_ptr<ScHTMLTableStackEntry> pS = std::move(aTableStack.top());
1128 aTableStack.pop();
1130 auto& pE = pS->xCellEntry;
1131 SCROW nRows = nRowCnt - pS->nRowCnt;
1132 if ( nRows > 1 )
1133 { // Insert size of table at this position
1134 SCROW nRow = pS->nRowCnt;
1135 sal_uInt16 nTab = pS->nTable;
1136 if ( !pTables )
1137 pTables.reset( new OuterMap );
1138 // Height of outer table
1139 OuterMap::const_iterator it = pTables->find( nTab );
1140 InnerMap* pTab1;
1141 if ( it == pTables->end() )
1143 pTab1 = new InnerMap;
1144 (*pTables)[ nTab ] = pTab1;
1146 else
1147 pTab1 = it->second;
1148 SCROW nRowSpan = pE->nRowOverlap;
1149 SCROW nRowKGV;
1150 SCROW nRowsPerRow1; // Outer table
1151 SCROW nRowsPerRow2; // Inner table
1152 if ( nRowSpan > 1 )
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;
1158 else
1160 nRowKGV = nRowsPerRow1 = nRows;
1161 nRowsPerRow2 = 1;
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
1170 if ( nRowKGV > 1 )
1172 if ( nRowsPerRow1 > 1 )
1173 { // Outer
1174 for ( SCROW j=0; j < nRowSpan; j++ )
1176 sal_uLong nRowKey = nRow + j;
1177 SCROW nR = (*pTab1)[ nRowKey ];
1178 if ( !nR )
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);
1187 nR += nAdd;
1188 if ( (nR % nRows) == 0 )
1189 { // Only if representable
1190 SCROW nR2 = (*pTab1)[ nRowKey+1 ];
1191 if ( nR2 > nAdd )
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 )
1202 { // Inner
1203 if ( !pTab2 )
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;
1217 SetWidths();
1219 if ( !pE->nWidth )
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
1247 mxActEntry = pE;
1249 bTabInTabCell = true;
1250 bInCell = true;
1252 else
1253 { // Simple table finished
1254 SetWidths();
1255 nMaxCol = 0;
1256 nTable = 0;
1257 if ( !aTableStack.empty() )
1259 ScHTMLTableStackEntry* pS = aTableStack.top().get();
1260 delete pLocalColOffset;
1261 pLocalColOffset = pS->pLocalColOffset;
1262 aTableStack.pop();
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() );
1280 break;
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();
1291 break;
1292 case HtmlOptionId::WIDTH:
1294 pImage->aSize.setWidth( static_cast<tools::Long>(rOption.GetNumber()) );
1296 break;
1297 case HtmlOptionId::HEIGHT:
1299 pImage->aSize.setHeight( static_cast<tools::Long>(rOption.GetNumber()) );
1301 break;
1302 case HtmlOptionId::HSPACE:
1304 pImage->aSpace.setX( static_cast<tools::Long>(rOption.GetNumber()) );
1306 break;
1307 case HtmlOptionId::VSPACE:
1309 pImage->aSpace.setY( static_cast<tools::Long>(rOption.GetNumber()) );
1311 break;
1312 default: break;
1315 if (pImage->aURL.isEmpty())
1317 OSL_FAIL( "Image: graphic without URL ?!?" );
1318 return ;
1321 sal_uInt16 nFormat;
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())
1343 return;
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();
1350 else
1351 nWidth = 0;
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 )
1377 { // Percent
1378 sal_uInt16 nW = (nTableWidth ? nTableWidth : static_cast<sal_uInt16>(aPageSize.Width()));
1379 return static_cast<sal_uInt16>((rOption.GetNumber() * nW) / 100);
1381 else
1383 if ( rOptVal.indexOf('*') != -1 )
1384 { // Relative to what?
1385 // TODO: Collect all relative values in ColArray and then MakeCol
1386 return 0;
1388 else
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 ) )
1414 return;
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;
1426 sal_Int32 nPos = 0;
1427 while( nPos != -1 )
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 ) );
1442 break;
1443 case HtmlOptionId::SIZE :
1445 sal_uInt16 nSize = static_cast<sal_uInt16>(rOption.GetNumber());
1446 if ( nSize == 0 )
1447 nSize = 1;
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 ) );
1453 break;
1454 case HtmlOptionId::COLOR :
1456 Color aColor;
1457 rOption.GetColor( aColor );
1458 mxActEntry->aItemSet.Put( SvxColorItem( aColor, ATTR_FONT_COLOR ) );
1460 break;
1461 default: break;
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() );
1479 break;
1480 case HtmlTokenId::TITLE_ON:
1482 bInTitle = true;
1483 aString.clear();
1485 break;
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);
1497 bInTitle = false;
1499 break;
1500 case HtmlTokenId::TABLE_ON:
1502 TableOn( pInfo );
1504 break;
1505 case HtmlTokenId::COL_ON:
1507 ColOn( pInfo );
1509 break;
1510 case HtmlTokenId::TABLEHEADER_ON: // Opens row
1512 if ( bInCell )
1513 CloseEntry( pInfo );
1514 // Do not set bInCell to true, TableDataOn does that
1515 mxActEntry->aItemSet.Put(
1516 SvxWeightItem( WEIGHT_BOLD, ATTR_FONT_WEIGHT) );
1517 [[fallthrough]];
1519 case HtmlTokenId::TABLEDATA_ON: // Opens cell
1521 TableDataOn( pInfo );
1523 break;
1524 case HtmlTokenId::TABLEHEADER_OFF:
1525 case HtmlTokenId::TABLEDATA_OFF: // Closes cell
1527 TableDataOff( pInfo );
1529 break;
1530 case HtmlTokenId::TABLEROW_ON: // Before first cell in row
1532 TableRowOn( pInfo );
1534 break;
1535 case HtmlTokenId::TABLEROW_OFF: // After last cell in row
1537 TableRowOff( pInfo );
1539 break;
1540 case HtmlTokenId::TABLE_OFF:
1542 TableOff( pInfo );
1544 break;
1545 case HtmlTokenId::IMAGE:
1547 Image( pInfo );
1549 break;
1550 case HtmlTokenId::PARABREAK_OFF:
1551 { // We continue vertically after an image
1552 if (!mxActEntry->maImageList.empty())
1553 mxActEntry->maImageList.back()->nDir = nVertical;
1555 break;
1556 case HtmlTokenId::ANCHOR_ON:
1558 AnchorOn( pInfo );
1560 break;
1561 case HtmlTokenId::FONT_ON :
1563 FontOn( pInfo );
1565 break;
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 ) );
1573 break;
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 ) );
1581 break;
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 ) );
1589 break;
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 ) );
1602 break;
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 ) );
1613 break;
1614 case HtmlTokenId::UNDERLINE_ON :
1616 if ( IsAtBeginningOfText( pInfo ) )
1617 mxActEntry->aItemSet.Put( SvxUnderlineItem( LINESTYLE_SINGLE,
1618 ATTR_FONT_UNDERLINE ) );
1620 break;
1621 case HtmlTokenId::TEXTTOKEN:
1623 if ( bInTitle )
1624 aString += pInfo->aText;
1626 break;
1627 default: ;
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 )
1641 nTab = nTableId;
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" );
1668 // set 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) )
1678 ++aSel.nStartPara;
1679 aSel.nStartPos = 0;
1681 // strip trailing empty paragraphs
1682 while( (aSel.nStartPara < aSel.nEndPara) && (aSel.nEndPos == 0) )
1684 --aSel.nEndPara;
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
1694 on destruction.
1696 class ScHTMLTableMap final
1698 private:
1699 typedef std::shared_ptr< ScHTMLTable > ScHTMLTablePtr;
1700 typedef std::map< ScHTMLTableId, ScHTMLTablePtr > ScHTMLTableStdMap;
1702 public:
1703 typedef ScHTMLTableStdMap::iterator iterator;
1704 typedef ScHTMLTableStdMap::const_iterator const_iterator;
1706 private:
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.
1711 public:
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 );
1726 private:
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
1743 else
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 );
1756 return 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 );
1764 return pTable;
1767 namespace {
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
1773 container).
1775 class ScHTMLTableIterator
1777 public:
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; }
1786 private:
1787 ScHTMLTableMap::const_iterator maIter;
1788 ScHTMLTableMap::const_iterator maEnd;
1789 const ScHTMLTableMap* mpTableMap;
1794 ScHTMLTableIterator::ScHTMLTableIterator( const ScHTMLTableMap* pTableMap ) :
1795 mpTableMap(pTableMap)
1797 if( pTableMap )
1799 maIter = pTableMap->begin();
1800 maEnd = pTableMap->end();
1804 ScHTMLTableAutoId::ScHTMLTableAutoId( ScHTMLTableId& rnUnusedId ) :
1805 mnTableId( rnUnusedId ),
1806 mrnUnusedId( rnUnusedId )
1808 ++mrnUnusedId;
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 ),
1818 maSize( 1, 1 ),
1819 mpParser(rParentTable.mpParser),
1820 mrDoc(rDoc),
1821 mbBorderOn( false ),
1822 mbPreFormText( bPreFormText ),
1823 mbRowOn( false ),
1824 mbDataOn( false ),
1825 mbPushEmptyLine( false ),
1826 mbCaptionOn ( false )
1828 if( mbPreFormText )
1830 ImplRowOn();
1831 ImplDataOn( ScHTMLSize( 1, 1 ) );
1833 else
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);
1843 break;
1844 case HtmlOptionId::ID:
1845 maTableName = rOption.GetString();
1846 break;
1847 default: break;
1852 CreateNewEntry( rInfo );
1855 ScHTMLTable::ScHTMLTable(
1856 SfxItemPool& rPool,
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 ),
1867 maSize( 1, 1 ),
1868 mpParser(pParser),
1869 mrDoc(rDoc),
1870 mbBorderOn( false ),
1871 mbPreFormText( false ),
1872 mbRowOn( false ),
1873 mbDataOn( false ),
1874 mbPushEmptyLine( false ),
1875 mbCaptionOn ( false )
1877 // open the first "cell" of the document
1878 ImplRowOn();
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() );
1897 if (!pRange)
1898 pRange = maHMergedCells.Find( rCellPos.MakeAddr() );
1899 if (pRange)
1900 aSpan.Set( pRange->aEnd.Col() - pRange->aStart.Col() + 1, pRange->aEnd.Row() - pRange->aStart.Row() + 1 );
1901 return aSpan;
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" );
1919 if( mxCurrEntry )
1921 if( !mxCurrEntry->HasContents() && IsSpaceCharInfo( rInfo ) )
1922 mxCurrEntry->AdjustStart( rInfo );
1923 else
1924 mxCurrEntry->AdjustEnd( rInfo );
1925 if (mbCaptionOn)
1926 maCaptionBuffer.append(rInfo.aText);
1931 void ScHTMLTable::InsertPara( const HtmlImportInfo& rInfo )
1933 if( mxCurrEntry && mbDataOn && !IsEmptyCell() )
1934 mxCurrEntry->SetImportAlways();
1935 PushEntry( rInfo );
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
1962 if( mxCurrEntry )
1963 mxCurrEntry->SetImportAlways();
1966 ScHTMLTable* ScHTMLTable::TableOn( const HtmlImportInfo& rInfo )
1968 PushEntry( rInfo );
1969 return InsertNestedTable( rInfo, false );
1972 ScHTMLTable* ScHTMLTable::TableOff( const HtmlImportInfo& rInfo )
1974 return mbPreFormText ? this : CloseTable( rInfo );
1977 void ScHTMLTable::CaptionOn()
1979 mbCaptionOn = true;
1980 maCaptionBuffer.setLength(0);
1983 void ScHTMLTable::CaptionOff()
1985 if (!mbCaptionOn)
1986 return;
1987 maCaption = maCaptionBuffer.makeStringAndClear().trim();
1988 mbCaptionOn = false;
1991 ScHTMLTable* ScHTMLTable::PreOn( const HtmlImportInfo& rInfo )
1993 PushEntry( 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
2007 ImplRowOn();
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
2017 ImplRowOff();
2018 CreateNewEntry( rInfo );
2021 namespace {
2024 * Decode a number format string stored in Excel-generated HTML's CSS
2025 * region.
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)
2034 if (*p == '\\')
2036 // Skip '\'.
2037 ++i;
2038 ++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)
2047 --i;
2048 --p;
2049 break;
2053 if (nDigitCount)
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));
2062 else
2063 aBuf.append(*p);
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 ) );
2086 break;
2087 case HtmlOptionId::ROWSPAN:
2088 aSpanSize.mnRows = static_cast<SCROW>( getLimitedValue<sal_Int32>( rOption.GetString().toInt32(), 1, 256 ) );
2089 break;
2090 case HtmlOptionId::SDVAL:
2091 pValStr = rOption.GetString();
2092 break;
2093 case HtmlOptionId::SDNUM:
2094 pNumStr = rOption.GetString();
2095 break;
2096 case HtmlOptionId::CLASS:
2098 // Pick up the number format associated with this class (if
2099 // any).
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);
2113 if (!bValidFmt)
2114 nNumberFormat = NUMBERFORMAT_ENTRY_NOT_FOUND;
2118 break;
2119 default: break;
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);
2133 else
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
2141 ImplDataOff();
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 )
2152 ImplRowOn();
2153 if( bPushed || !mbDataOn )
2154 ImplDataOn( ScHTMLSize( 1, 1 ) );
2155 ProcessFormatOptions( *moDataItemSet, rInfo );
2157 CreateNewEntry( rInfo );
2160 void ScHTMLTable::BodyOff( const HtmlImportInfo& rInfo )
2162 PushEntry( rInfo );
2163 if( !mpParentTable )
2165 ImplDataOff();
2166 ImplRowOff();
2168 CreateNewEntry( rInfo );
2171 ScHTMLTable* ScHTMLTable::CloseTable( const HtmlImportInfo& rInfo )
2173 if( mpParentTable ) // not allowed to close global table
2175 PushEntry( rInfo, mbDataOn );
2176 ImplDataOff();
2177 ImplRowOff();
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;
2184 return this;
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 );
2213 return ScHTMLSize(
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
2225 return ScHTMLPos(
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 );
2334 bPushed = true;
2336 else if( mpParentTable )
2338 bPushed = mpParentTable->PushEntry( rxEntry );
2340 else
2342 OSL_FAIL( "ScHTMLTable::PushEntry - cannot push entry, no parent found" );
2345 return bPushed;
2348 bool ScHTMLTable::PushEntry( const HtmlImportInfo& rInfo, bool bLastInCell )
2350 OSL_ENSURE( mxCurrEntry, "ScHTMLTable::PushEntry - no current entry" );
2351 bool bPushed = false;
2352 if( mxCurrEntry )
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();
2369 return bPushed;
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" );
2387 return pTable;
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 )
2401 ScRange* pRange;
2403 /* Find an unused cell by skipping all merged ranges that cover the
2404 current cell position stored in maCurrCell. */
2405 for (;;)
2407 pRange = maVMergedCells.Find( maCurrCell.MakeAddr() );
2408 if (!pRange)
2409 pRange = maHMergedCells.Find( maCurrCell.MakeAddr() );
2410 if (!pRange)
2411 break;
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(). */
2438 else
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()
2454 if( mbRowOn )
2455 ImplRowOff();
2456 moRowItemSet.emplace( maTableItemSet );
2457 maCurrCell.mnCol = 0;
2458 mbRowOn = true;
2459 mbDataOn = false;
2462 void ScHTMLTable::ImplRowOff()
2464 if( mbDataOn )
2465 ImplDataOff();
2466 if( mbRowOn )
2468 moRowItemSet.reset();
2469 ++maCurrCell.mnRow;
2470 mbRowOn = mbDataOn = false;
2474 void ScHTMLTable::ImplDataOn( const ScHTMLSize& rSpanSize )
2476 if( mbDataOn )
2477 ImplDataOff();
2478 if( !mbRowOn )
2479 ImplRowOn();
2480 moDataItemSet.emplace( *moRowItemSet );
2481 InsertNewCell( rSpanSize );
2482 mbDataOn = true;
2483 mbPushEmptyLine = false;
2486 void ScHTMLTable::ImplDataOff()
2488 if( mbDataOn )
2490 moDataItemSet.reset();
2491 ++maCurrCell.mnCol;
2492 mpCurrEntryVector = nullptr;
2493 mbDataOn = false;
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 ) );
2524 break;
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 ) );
2539 break;
2541 case HtmlOptionId::BGCOLOR:
2543 Color aColor;
2544 rOption.GetColor( aColor );
2545 rItemSet.Put( SvxBrushItem( aColor, ATTR_BACKGROUND ) );
2547 break;
2548 default: break;
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 ]));
2564 if( nDiff > 0 )
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 );
2576 --nCellSpan;
2577 ++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 )
2688 pEntry = rpEntry;
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;
2711 else
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.
2722 if( pEntry )
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;
2729 else
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(
2748 SfxItemPool& rPool,
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. */
2766 FillEmptyCells();
2767 // recalc table sizes of all nested tables and this table
2768 RecalcDocSize();
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 ),
2776 mbTitleOn( false )
2778 mxGlobTable.reset(
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();
2797 else
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 );
2802 if( pCharSet )
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 );
2822 return nErr;
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 ) );
2882 break;
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 ) );
2886 break;
2888 case HtmlTokenId::BOLD_ON: // <b>
2889 case HtmlTokenId::STRONG_ON: // <strong>
2890 mpCurrTable->PutItem( SvxWeightItem( WEIGHT_BOLD, ATTR_FONT_WEIGHT ) );
2891 break;
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 ) );
2901 break;
2903 case HtmlTokenId::DEFINSTANCE_ON: // <dfn>
2904 mpCurrTable->PutItem( SvxWeightItem( WEIGHT_BOLD, ATTR_FONT_WEIGHT ) );
2905 mpCurrTable->PutItem( SvxPostureItem( ITALIC_NORMAL, ATTR_FONT_POSTURE ) );
2906 break;
2908 case HtmlTokenId::UNDERLINE_ON: // <u>
2909 mpCurrTable->PutItem( SvxUnderlineItem( LINESTYLE_SINGLE, ATTR_FONT_UNDERLINE ) );
2910 break;
2911 default: break;
2915 void ScHTMLQueryParser::InsertText( const HtmlImportInfo& rInfo )
2917 mpCurrTable->PutText( rInfo );
2918 if( mbTitleOn )
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();
2932 OUString aFontName;
2933 sal_Int32 nPos = 0;
2934 while( nPos != -1 )
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 ) );
2945 break;
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 ) );
2951 break;
2952 case HtmlOptionId::COLOR :
2954 Color aColor;
2955 rOption.GetColor( aColor );
2956 mpCurrTable->PutItem( SvxColorItem( aColor, ATTR_FONT_COLOR ) );
2958 break;
2959 default: break;
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()
2980 mbTitleOn = true;
2981 maTitle.setLength(0);
2984 void ScHTMLQueryParser::TitleOff( const HtmlImportInfo& rInfo )
2986 if( !mbTitleOn )
2987 return;
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 );
2998 mbTitleOn = false;
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 );
3026 namespace {
3029 * Handler class for the CSS parser.
3031 class CSSHandler: public orcus::css_handler
3033 struct MemStr
3035 const char* mp;
3036 size_t mn;
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;
3052 public:
3053 explicit CSSHandler(ScHTMLStyles& rStyles):
3054 maPropName(),
3055 maPropValue(),
3056 mrStyles(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);
3097 void end_block() {
3098 maSelectorNames.clear();
3101 void end_property()
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);
3129 aParser.parse();
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:
3143 break;
3145 case HtmlImportState::NextToken:
3146 ProcessToken( rInfo );
3147 break;
3149 case HtmlImportState::InsertPara:
3150 mpCurrTable->InsertPara( rInfo );
3151 break;
3153 case HtmlImportState::SetAttr:
3154 case HtmlImportState::InsertText:
3155 case HtmlImportState::InsertField:
3156 break;
3158 case HtmlImportState::End:
3159 while( mpCurrTable->GetTableId() != SC_HTML_GLOBAL_TABLE )
3160 CloseTable( rInfo );
3161 break;
3163 default:
3164 OSL_FAIL( "ScHTMLQueryParser::HTMLImportHdl - unknown ImportInfo::eState" );
3168 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */