LanguageTool: don't crash if REST protocol isn't set
[LibreOffice.git] / sc / source / filter / html / htmlpars.cxx
blobebccbf860f49166131ea8756b32d9676f20bd674
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 <svtools/htmlcfg.hxx>
28 #include <editeng/colritem.hxx>
29 #include <editeng/brushitem.hxx>
30 #include <editeng/editeng.hxx>
31 #include <editeng/fhgtitem.hxx>
32 #include <editeng/fontitem.hxx>
33 #include <editeng/postitem.hxx>
34 #include <editeng/udlnitem.hxx>
35 #include <editeng/wghtitem.hxx>
36 #include <editeng/borderline.hxx>
37 #include <editeng/boxitem.hxx>
38 #include <editeng/justifyitem.hxx>
39 #include <sfx2/objsh.hxx>
40 #include <svl/numformat.hxx>
41 #include <svl/intitem.hxx>
42 #include <vcl/graphicfilter.hxx>
43 #include <svtools/parhtml.hxx>
44 #include <svtools/htmlkywd.hxx>
45 #include <svtools/htmltokn.h>
47 #include <vcl/outdev.hxx>
48 #include <vcl/svapp.hxx>
49 #include <tools/urlobj.hxx>
50 #include <osl/diagnose.h>
52 #include <rtl/tencinfo.h>
54 #include <attrib.hxx>
55 #include <htmlpars.hxx>
56 #include <global.hxx>
57 #include <document.hxx>
58 #include <rangelst.hxx>
60 #include <orcus/css_parser.hpp>
62 #include <com/sun/star/document/XDocumentProperties.hpp>
63 #include <com/sun/star/document/XDocumentPropertiesSupplier.hpp>
64 #include <com/sun/star/frame/XModel.hpp>
65 #include <numeric>
66 #include <utility>
67 #include <officecfg/Office/Common.hxx>
69 using ::editeng::SvxBorderLine;
70 using namespace ::com::sun::star;
72 ScHTMLStyles::ScHTMLStyles() : maEmpty() {}
74 void ScHTMLStyles::add(const char* pElemName, size_t nElemName, const char* pClassName, size_t nClassName,
75 const OUString& aProp, const OUString& aValue)
77 if (pElemName)
79 OUString aElem(pElemName, nElemName, RTL_TEXTENCODING_UTF8);
80 aElem = aElem.toAsciiLowerCase();
81 if (pClassName)
83 // Both element and class names given.
84 ElemsType::iterator itrElem = m_ElemProps.find(aElem);
85 if (itrElem == m_ElemProps.end())
87 // new element
88 std::pair<ElemsType::iterator, bool> r =
89 m_ElemProps.insert(std::make_pair(aElem, NamePropsType()));
90 if (!r.second)
91 // insertion failed.
92 return;
93 itrElem = r.first;
96 NamePropsType& rClsProps = itrElem->second;
97 OUString aClass(pClassName, nClassName, RTL_TEXTENCODING_UTF8);
98 aClass = aClass.toAsciiLowerCase();
99 insertProp(rClsProps, aClass, aProp, aValue);
101 else
103 // Element name only. Add it to the element global.
104 insertProp(m_ElemGlobalProps, aElem, aProp, aValue);
107 else
109 if (pClassName)
111 // Class name only. Add it to the global.
112 OUString aClass(pClassName, nClassName, RTL_TEXTENCODING_UTF8);
113 aClass = aClass.toAsciiLowerCase();
114 insertProp(m_GlobalProps, aClass, aProp, aValue);
119 const OUString& ScHTMLStyles::getPropertyValue(
120 const OUString& rElem, const OUString& rClass, const OUString& rPropName) const
122 // First, look into the element-class storage.
124 auto const itr = m_ElemProps.find(rElem);
125 if (itr != m_ElemProps.end())
127 const NamePropsType& rClasses = itr->second;
128 NamePropsType::const_iterator itr2 = rClasses.find(rClass);
129 if (itr2 != rClasses.end())
131 const PropsType& rProps = itr2->second;
132 PropsType::const_iterator itr3 = rProps.find(rPropName);
133 if (itr3 != rProps.end())
134 return itr3->second;
138 // Next, look into the class global storage.
140 auto const itr = m_GlobalProps.find(rClass);
141 if (itr != m_GlobalProps.end())
143 const PropsType& rProps = itr->second;
144 PropsType::const_iterator itr2 = rProps.find(rPropName);
145 if (itr2 != rProps.end())
146 return itr2->second;
149 // As the last resort, look into the element global storage.
151 auto const itr = m_ElemGlobalProps.find(rClass);
152 if (itr != m_ElemGlobalProps.end())
154 const PropsType& rProps = itr->second;
155 PropsType::const_iterator itr2 = rProps.find(rPropName);
156 if (itr2 != rProps.end())
157 return itr2->second;
161 return maEmpty; // nothing found.
164 void ScHTMLStyles::insertProp(
165 NamePropsType& rStore, const OUString& aName,
166 const OUString& aProp, const OUString& aValue)
168 NamePropsType::iterator itr = rStore.find(aName);
169 if (itr == rStore.end())
171 // new element
172 std::pair<NamePropsType::iterator, bool> r =
173 rStore.insert(std::make_pair(aName, PropsType()));
174 if (!r.second)
175 // insertion failed.
176 return;
178 itr = r.first;
181 PropsType& rProps = itr->second;
182 rProps.emplace(aProp, aValue);
185 // BASE class for HTML parser classes
187 ScHTMLParser::ScHTMLParser( EditEngine* pEditEngine, ScDocument* pDoc ) :
188 ScEEParser( pEditEngine ),
189 mpDoc( pDoc )
191 maFontHeights[0] = officecfg::Office::Common::Filter::HTML::Import::FontSize::Size_1::get() * 20;
192 maFontHeights[1] = officecfg::Office::Common::Filter::HTML::Import::FontSize::Size_2::get() * 20;
193 maFontHeights[2] = officecfg::Office::Common::Filter::HTML::Import::FontSize::Size_3::get() * 20;
194 maFontHeights[3] = officecfg::Office::Common::Filter::HTML::Import::FontSize::Size_4::get() * 20;
195 maFontHeights[4] = officecfg::Office::Common::Filter::HTML::Import::FontSize::Size_5::get() * 20;
196 maFontHeights[5] = officecfg::Office::Common::Filter::HTML::Import::FontSize::Size_6::get() * 20;
197 maFontHeights[6] = officecfg::Office::Common::Filter::HTML::Import::FontSize::Size_7::get() * 20;
200 ScHTMLParser::~ScHTMLParser()
204 ScHTMLLayoutParser::ScHTMLLayoutParser(
205 EditEngine* pEditP, const OUString& rBaseURL, const Size& aPageSizeP,
206 ScDocument* pDocP ) :
207 ScHTMLParser( pEditP, pDocP ),
208 aPageSize( aPageSizeP ),
209 aBaseURL( rBaseURL ),
210 xLockedList( new ScRangeList ),
211 pLocalColOffset( new ScHTMLColOffset ),
212 nFirstTableCell(0),
213 nTableLevel(0),
214 nTable(0),
215 nMaxTable(0),
216 nColCntStart(0),
217 nMaxCol(0),
218 nTableWidth(0),
219 nColOffset(0),
220 nColOffsetStart(0),
221 nOffsetTolerance( SC_HTML_OFFSET_TOLERANCE_SMALL ),
222 bFirstRow( true ),
223 bTabInTabCell( false ),
224 bInCell( false ),
225 bInTitle( false )
227 MakeColNoRef( pLocalColOffset, 0, 0, 0, 0 );
228 MakeColNoRef( &maColOffset, 0, 0, 0, 0 );
231 ScHTMLLayoutParser::~ScHTMLLayoutParser()
233 while ( !aTableStack.empty() )
235 ScHTMLTableStackEntry * pS = aTableStack.top().get();
236 if ( pS->pLocalColOffset != pLocalColOffset )
237 delete pS->pLocalColOffset;
238 aTableStack.pop();
240 delete pLocalColOffset;
241 if ( pTables )
243 for( const auto& rEntry : *pTables)
244 delete rEntry.second;
245 pTables.reset();
249 ErrCode ScHTMLLayoutParser::Read( SvStream& rStream, const OUString& rBaseURL )
251 Link<HtmlImportInfo&,void> aOldLink = pEdit->GetHtmlImportHdl();
252 pEdit->SetHtmlImportHdl( LINK( this, ScHTMLLayoutParser, HTMLImportHdl ) );
254 SfxObjectShell* pObjSh = mpDoc->GetDocumentShell();
255 bool bLoading = pObjSh && pObjSh->IsLoading();
257 SvKeyValueIteratorRef xValues;
258 SvKeyValueIterator* pAttributes = nullptr;
259 if ( bLoading )
260 pAttributes = pObjSh->GetHeaderAttributes();
261 else
263 // When not loading, set up fake http headers to force the SfxHTMLParser to use UTF8
264 // (used when pasting from clipboard)
265 const char* pCharSet = rtl_getBestMimeCharsetFromTextEncoding( RTL_TEXTENCODING_UTF8 );
266 if( pCharSet )
268 OUString aContentType = "text/html; charset=" +
269 OUString::createFromAscii( pCharSet );
271 xValues = new SvKeyValueIterator;
272 xValues->Append( SvKeyValue( OOO_STRING_SVTOOLS_HTML_META_content_type, aContentType ) );
273 pAttributes = xValues.get();
277 ErrCode nErr = pEdit->Read( rStream, rBaseURL, EETextFormat::Html, pAttributes );
279 pEdit->SetHtmlImportHdl( aOldLink );
280 // Create column width
281 Adjust();
282 OutputDevice* pDefaultDev = Application::GetDefaultDevice();
283 sal_uInt16 nCount = maColOffset.size();
284 sal_uLong nOff = maColOffset[0];
285 Size aSize;
286 for ( sal_uInt16 j = 1; j < nCount; j++ )
288 aSize.setWidth( maColOffset[j] - nOff );
289 aSize = pDefaultDev->PixelToLogic( aSize, MapMode( MapUnit::MapTwip ) );
290 maColWidths[ j-1 ] = aSize.Width();
291 nOff = maColOffset[j];
293 return nErr;
296 const ScHTMLTable* ScHTMLLayoutParser::GetGlobalTable() const
298 return nullptr;
301 void ScHTMLLayoutParser::NewActEntry( const ScEEParseEntry* pE )
303 ScEEParser::NewActEntry( pE );
304 if ( pE )
306 if ( !pE->aSel.HasRange() )
307 { // Completely empty, following text ends up in the same paragraph!
308 mxActEntry->aSel.nStartPara = pE->aSel.nEndPara;
309 mxActEntry->aSel.nStartPos = pE->aSel.nEndPos;
312 mxActEntry->aSel.nEndPara = mxActEntry->aSel.nStartPara;
313 mxActEntry->aSel.nEndPos = mxActEntry->aSel.nStartPos;
316 void ScHTMLLayoutParser::EntryEnd( ScEEParseEntry* pE, const ESelection& rSel )
318 if ( rSel.nEndPara >= pE->aSel.nStartPara )
320 pE->aSel.nEndPara = rSel.nEndPara;
321 pE->aSel.nEndPos = rSel.nEndPos;
323 else if ( rSel.nStartPara == pE->aSel.nStartPara - 1 && !pE->aSel.HasRange() )
324 { // Did not attach a paragraph, but empty, do nothing
326 else
328 OSL_FAIL( "EntryEnd: EditEngine ESelection End < Start" );
332 void ScHTMLLayoutParser::NextRow( const HtmlImportInfo* pInfo )
334 if ( bInCell )
335 CloseEntry( pInfo );
336 if ( nRowMax < ++nRowCnt )
337 nRowMax = nRowCnt;
338 nColCnt = nColCntStart;
339 nColOffset = nColOffsetStart;
340 bFirstRow = false;
343 bool ScHTMLLayoutParser::SeekOffset( const ScHTMLColOffset* pOffset, sal_uInt16 nOffset,
344 SCCOL* pCol, sal_uInt16 nOffsetTol )
346 OSL_ENSURE( pOffset, "ScHTMLLayoutParser::SeekOffset - illegal call" );
347 ScHTMLColOffset::const_iterator it = pOffset->find( nOffset );
348 bool bFound = it != pOffset->end();
349 sal_uInt16 nPos = it - pOffset->begin();
350 *pCol = static_cast<SCCOL>(nPos);
351 if ( bFound )
352 return true;
353 sal_uInt16 nCount = pOffset->size();
354 if ( !nCount )
355 return false;
356 // nPos is the position of insertion, that's where the next higher one is (or isn't)
357 if ( nPos < nCount && (((*pOffset)[nPos] - nOffsetTol) <= nOffset) )
358 return true;
359 // Not smaller than everything else? Then compare with the next lower one
360 else if ( nPos && (((*pOffset)[nPos-1] + nOffsetTol) >= nOffset) )
362 (*pCol)--;
363 return true;
365 return false;
368 void ScHTMLLayoutParser::MakeCol( ScHTMLColOffset* pOffset, sal_uInt16& nOffset,
369 sal_uInt16& nWidth, sal_uInt16 nOffsetTol, sal_uInt16 nWidthTol )
371 OSL_ENSURE( pOffset, "ScHTMLLayoutParser::MakeCol - illegal call" );
372 SCCOL nPos;
373 if ( SeekOffset( pOffset, nOffset, &nPos, nOffsetTol ) )
374 nOffset = static_cast<sal_uInt16>((*pOffset)[nPos]);
375 else
376 pOffset->insert( nOffset );
377 if ( nWidth )
379 if ( SeekOffset( pOffset, nOffset + nWidth, &nPos, nWidthTol ) )
380 nWidth = static_cast<sal_uInt16>((*pOffset)[nPos]) - nOffset;
381 else
382 pOffset->insert( nOffset + nWidth );
386 void ScHTMLLayoutParser::MakeColNoRef( ScHTMLColOffset* pOffset, sal_uInt16 nOffset,
387 sal_uInt16 nWidth, sal_uInt16 nOffsetTol, sal_uInt16 nWidthTol )
389 OSL_ENSURE( pOffset, "ScHTMLLayoutParser::MakeColNoRef - illegal call" );
390 SCCOL nPos;
391 if ( SeekOffset( pOffset, nOffset, &nPos, nOffsetTol ) )
392 nOffset = static_cast<sal_uInt16>((*pOffset)[nPos]);
393 else
394 pOffset->insert( nOffset );
395 if ( nWidth )
397 if ( !SeekOffset( pOffset, nOffset + nWidth, &nPos, nWidthTol ) )
398 pOffset->insert( nOffset + nWidth );
402 void ScHTMLLayoutParser::ModifyOffset( ScHTMLColOffset* pOffset, sal_uInt16& nOldOffset,
403 sal_uInt16& nNewOffset, sal_uInt16 nOffsetTol )
405 OSL_ENSURE( pOffset, "ScHTMLLayoutParser::ModifyOffset - illegal call" );
406 SCCOL nPos;
407 if ( !SeekOffset( pOffset, nOldOffset, &nPos, nOffsetTol ) )
409 if ( SeekOffset( pOffset, nNewOffset, &nPos, nOffsetTol ) )
410 nNewOffset = static_cast<sal_uInt16>((*pOffset)[nPos]);
411 else
412 pOffset->insert( nNewOffset );
413 return ;
415 nOldOffset = static_cast<sal_uInt16>((*pOffset)[nPos]);
416 SCCOL nPos2;
417 if ( SeekOffset( pOffset, nNewOffset, &nPos2, nOffsetTol ) )
419 nNewOffset = static_cast<sal_uInt16>((*pOffset)[nPos2]);
420 return ;
422 tools::Long nDiff = nNewOffset - nOldOffset;
423 if ( nDiff < 0 )
427 const_cast<sal_uLong&>((*pOffset)[nPos]) += nDiff;
428 } while ( nPos-- );
430 else
434 const_cast<sal_uLong&>((*pOffset)[nPos]) += nDiff;
435 } while ( ++nPos < static_cast<sal_uInt16>(pOffset->size()) );
439 void ScHTMLLayoutParser::SkipLocked( ScEEParseEntry* pE, bool bJoin )
441 if ( !mpDoc->ValidCol(pE->nCol) )
442 return;
444 // Or else this would create a wrong value at ScAddress (chance for an infinite loop)!
445 bool bBadCol = false;
446 bool bAgain;
447 ScRange aRange( pE->nCol, pE->nRow, 0,
448 pE->nCol + pE->nColOverlap - 1, pE->nRow + pE->nRowOverlap - 1, 0 );
451 bAgain = false;
452 for ( size_t i = 0, nRanges = xLockedList->size(); i < nRanges; ++i )
454 ScRange & rR = (*xLockedList)[i];
455 if ( rR.Intersects( aRange ) )
457 pE->nCol = rR.aEnd.Col() + 1;
458 SCCOL nTmp = pE->nCol + pE->nColOverlap - 1;
459 if ( pE->nCol > mpDoc->MaxCol() || nTmp > mpDoc->MaxCol() )
460 bBadCol = true;
461 else
463 bAgain = true;
464 aRange.aStart.SetCol( pE->nCol );
465 aRange.aEnd.SetCol( nTmp );
467 break;
470 } while ( bAgain );
471 if ( bJoin && !bBadCol )
472 xLockedList->Join( aRange );
475 void ScHTMLLayoutParser::Adjust()
477 xLockedList->RemoveAll();
479 std::stack< std::unique_ptr<ScHTMLAdjustStackEntry> > aStack;
480 sal_uInt16 nTab = 0;
481 SCCOL nLastCol = SCCOL_MAX;
482 SCROW nNextRow = 0;
483 SCROW nCurRow = 0;
484 sal_uInt16 nPageWidth = static_cast<sal_uInt16>(aPageSize.Width());
485 InnerMap* pTab = nullptr;
486 for (auto& pE : maList)
488 if ( pE->nTab < nTab )
489 { // Table finished
490 if ( !aStack.empty() )
492 std::unique_ptr<ScHTMLAdjustStackEntry> pS = std::move(aStack.top());
493 aStack.pop();
495 nLastCol = pS->nLastCol;
496 nNextRow = pS->nNextRow;
497 nCurRow = pS->nCurRow;
499 nTab = pE->nTab;
500 if (pTables)
502 OuterMap::const_iterator it = pTables->find( nTab );
503 if ( it != pTables->end() )
504 pTab = it->second;
508 SCROW nRow = pE->nRow;
509 if ( pE->nCol <= nLastCol )
510 { // Next row
511 if ( pE->nRow < nNextRow )
512 pE->nRow = nCurRow = nNextRow;
513 else
514 nCurRow = nNextRow = pE->nRow;
515 SCROW nR = 0;
516 if ( pTab )
518 InnerMap::const_iterator it = pTab->find( nCurRow );
519 if ( it != pTab->end() )
520 nR = it->second;
522 if ( nR )
523 nNextRow += nR;
524 else
525 nNextRow++;
527 else
528 pE->nRow = nCurRow;
529 nLastCol = pE->nCol; // Read column
530 if ( pE->nTab > nTab )
531 { // New table
532 aStack.push( std::make_unique<ScHTMLAdjustStackEntry>(
533 nLastCol, nNextRow, nCurRow ) );
534 nTab = pE->nTab;
535 if ( pTables )
537 OuterMap::const_iterator it = pTables->find( nTab );
538 if ( it != pTables->end() )
539 pTab = it->second;
541 // New line spacing
542 SCROW nR = 0;
543 if ( pTab )
545 InnerMap::const_iterator it = pTab->find( nCurRow );
546 if ( it != pTab->end() )
547 nR = it->second;
549 if ( nR )
550 nNextRow = nCurRow + nR;
551 else
552 nNextRow = nCurRow + 1;
554 if ( nTab == 0 )
555 pE->nWidth = nPageWidth;
556 else
557 { // Real table, no paragraphs on the field
558 if ( pTab )
560 SCROW nRowSpan = pE->nRowOverlap;
561 for ( SCROW j=0; j < nRowSpan; j++ )
562 { // RowSpan resulting from merged rows
563 SCROW nRows = 0;
564 InnerMap::const_iterator it = pTab->find( nRow+j );
565 if ( it != pTab->end() )
566 nRows = it->second;
567 if ( nRows > 1 )
569 pE->nRowOverlap += nRows - 1;
570 if ( j == 0 )
571 { // Merged rows move the next row
572 SCROW nTmp = nCurRow + nRows;
573 if ( nNextRow < nTmp )
574 nNextRow = nTmp;
580 // Real column
581 (void)SeekOffset( &maColOffset, pE->nOffset, &pE->nCol, nOffsetTolerance );
582 SCCOL nColBeforeSkip = pE->nCol;
583 SkipLocked(pE.get(), false);
584 if ( pE->nCol != nColBeforeSkip )
586 SCCOL nCount = static_cast<SCCOL>(maColOffset.size());
587 if ( nCount <= pE->nCol )
589 pE->nOffset = static_cast<sal_uInt16>(maColOffset[nCount-1]);
590 MakeCol( &maColOffset, pE->nOffset, pE->nWidth, nOffsetTolerance, nOffsetTolerance );
592 else
594 pE->nOffset = static_cast<sal_uInt16>(maColOffset[pE->nCol]);
597 SCCOL nPos;
598 if ( pE->nWidth && SeekOffset( &maColOffset, pE->nOffset + pE->nWidth, &nPos, nOffsetTolerance ) )
599 pE->nColOverlap = (nPos > pE->nCol ? nPos - pE->nCol : 1);
600 else
602 //FIXME: This may not be correct, but works anyway ...
603 pE->nColOverlap = 1;
605 xLockedList->Join( ScRange( pE->nCol, pE->nRow, 0,
606 pE->nCol + pE->nColOverlap - 1, pE->nRow + pE->nRowOverlap - 1, 0 ) );
607 // Take over MaxDimensions
608 SCCOL nColTmp = pE->nCol + pE->nColOverlap;
609 if ( nColMax < nColTmp )
610 nColMax = nColTmp;
611 SCROW nRowTmp = pE->nRow + pE->nRowOverlap;
612 if ( nRowMax < nRowTmp )
613 nRowMax = nRowTmp;
617 sal_uInt16 ScHTMLLayoutParser::GetWidth( const ScEEParseEntry* pE )
619 if ( pE->nWidth )
620 return pE->nWidth;
621 sal_Int32 nTmp = std::min( static_cast<sal_Int32>( pE->nCol -
622 nColCntStart + pE->nColOverlap),
623 static_cast<sal_Int32>( pLocalColOffset->size() - 1));
624 SCCOL nPos = (nTmp < 0 ? 0 : static_cast<SCCOL>(nTmp));
625 sal_uInt16 nOff2 = static_cast<sal_uInt16>((*pLocalColOffset)[nPos]);
626 if ( pE->nOffset < nOff2 )
627 return nOff2 - pE->nOffset;
628 return 0;
631 void ScHTMLLayoutParser::SetWidths()
633 SCCOL nCol;
634 if ( !nTableWidth )
635 nTableWidth = static_cast<sal_uInt16>(aPageSize.Width());
636 SCCOL nColsPerRow = nMaxCol - nColCntStart;
637 if ( nColsPerRow <= 0 )
638 nColsPerRow = 1;
639 if ( pLocalColOffset->size() <= 2 )
640 { // Only PageSize, there was no width setting
641 sal_uInt16 nWidth = nTableWidth / static_cast<sal_uInt16>(nColsPerRow);
642 sal_uInt16 nOff = nColOffsetStart;
643 pLocalColOffset->clear();
644 for ( nCol = 0; nCol <= nColsPerRow; ++nCol, nOff = nOff + nWidth )
646 MakeColNoRef( pLocalColOffset, nOff, 0, 0, 0 );
648 nTableWidth = static_cast<sal_uInt16>(pLocalColOffset->back() - pLocalColOffset->front());
649 for ( size_t i = nFirstTableCell, nListSize = maList.size(); i < nListSize; ++i )
651 auto& pE = maList[ i ];
652 if ( pE->nTab == nTable )
654 pE->nOffset = static_cast<sal_uInt16>((*pLocalColOffset)[pE->nCol - nColCntStart]);
655 pE->nWidth = 0; // to be recalculated later
659 else
660 { // Some without width
661 // Why actually no pE?
662 if ( nFirstTableCell < maList.size() )
664 std::unique_ptr<sal_uInt16[]> pOffsets(new sal_uInt16[ nColsPerRow+1 ]);
665 memset( pOffsets.get(), 0, (nColsPerRow+1) * sizeof(sal_uInt16) );
666 std::unique_ptr<sal_uInt16[]> pWidths(new sal_uInt16[ nColsPerRow ]);
667 memset( pWidths.get(), 0, nColsPerRow * sizeof(sal_uInt16) );
668 pOffsets[0] = nColOffsetStart;
669 for ( size_t i = nFirstTableCell, nListSize = maList.size(); i < nListSize; ++i )
671 auto& pE = maList[ i ];
672 if ( pE->nTab == nTable && pE->nWidth )
674 nCol = pE->nCol - nColCntStart;
675 if ( nCol < nColsPerRow )
677 if ( pE->nColOverlap == 1 )
679 if ( pWidths[nCol] < pE->nWidth )
680 pWidths[nCol] = pE->nWidth;
682 else
683 { // try to find a single undefined width
684 sal_uInt16 nTotal = 0;
685 bool bFound = false;
686 SCCOL nHere = 0;
687 SCCOL nStop = std::min( static_cast<SCCOL>(nCol + pE->nColOverlap), nColsPerRow );
688 for ( ; nCol < nStop; nCol++ )
690 if ( pWidths[nCol] )
691 nTotal = nTotal + pWidths[nCol];
692 else
694 if ( bFound )
696 bFound = false;
697 break; // for
699 bFound = true;
700 nHere = nCol;
703 if ( bFound && pE->nWidth > nTotal )
704 pWidths[nHere] = pE->nWidth - nTotal;
709 sal_uInt16 nWidths = 0;
710 sal_uInt16 nUnknown = 0;
711 for ( nCol = 0; nCol < nColsPerRow; nCol++ )
713 if ( pWidths[nCol] )
714 nWidths = nWidths + pWidths[nCol];
715 else
716 nUnknown++;
718 if ( nUnknown )
720 sal_uInt16 nW = ((nWidths < nTableWidth) ?
721 ((nTableWidth - nWidths) / nUnknown) :
722 (nTableWidth / nUnknown));
723 for ( nCol = 0; nCol < nColsPerRow; nCol++ )
725 if ( !pWidths[nCol] )
726 pWidths[nCol] = nW;
729 for ( nCol = 1; nCol <= nColsPerRow; nCol++ )
731 pOffsets[nCol] = pOffsets[nCol-1] + pWidths[nCol-1];
733 pLocalColOffset->clear();
734 for ( nCol = 0; nCol <= nColsPerRow; nCol++ )
736 MakeColNoRef( pLocalColOffset, pOffsets[nCol], 0, 0, 0 );
738 nTableWidth = pOffsets[nColsPerRow] - pOffsets[0];
740 for ( size_t i = nFirstTableCell, nListSize = maList.size(); i < nListSize; ++i )
742 auto& pE = maList[ i ];
743 if ( pE->nTab == nTable )
745 nCol = pE->nCol - nColCntStart;
746 OSL_ENSURE( nCol < nColsPerRow, "ScHTMLLayoutParser::SetWidths: column overflow" );
747 if ( nCol < nColsPerRow )
749 pE->nOffset = pOffsets[nCol];
750 nCol = nCol + pE->nColOverlap;
751 if ( nCol > nColsPerRow )
752 nCol = nColsPerRow;
753 pE->nWidth = pOffsets[nCol] - pE->nOffset;
759 if ( !pLocalColOffset->empty() )
761 sal_uInt16 nMax = static_cast<sal_uInt16>(pLocalColOffset->back());
762 if ( aPageSize.Width() < nMax )
763 aPageSize.setWidth( nMax );
764 if (nTableLevel == 0)
766 // Local table is very outer table, create missing offsets.
767 for (auto it = pLocalColOffset->begin(); it != pLocalColOffset->end(); ++it)
769 // Only exact offsets, do not use MakeColNoRef().
770 if (maColOffset.find(*it) == maColOffset.end())
771 maColOffset.insert(*it);
775 for ( size_t i = nFirstTableCell, nListSize = maList.size(); i < nListSize; ++i )
777 auto& pE = maList[ i ];
778 if ( pE->nTab == nTable )
780 if ( !pE->nWidth )
782 pE->nWidth = GetWidth(pE.get());
783 OSL_ENSURE( pE->nWidth, "SetWidths: pE->nWidth == 0" );
785 MakeCol( &maColOffset, pE->nOffset, pE->nWidth, nOffsetTolerance, nOffsetTolerance );
790 void ScHTMLLayoutParser::Colonize( ScEEParseEntry* pE )
792 if ( pE->nCol == SCCOL_MAX )
793 pE->nCol = nColCnt;
794 if ( pE->nRow == SCROW_MAX )
795 pE->nRow = nRowCnt;
796 SCCOL nCol = pE->nCol;
797 SkipLocked( pE ); // Change of columns to the right
799 if ( nCol < pE->nCol )
800 { // Replaced
801 nCol = pE->nCol - nColCntStart;
802 SCCOL nCount = static_cast<SCCOL>(pLocalColOffset->size());
803 if ( nCol < nCount )
804 nColOffset = static_cast<sal_uInt16>((*pLocalColOffset)[nCol]);
805 else
806 nColOffset = static_cast<sal_uInt16>((*pLocalColOffset)[nCount - 1]);
808 pE->nOffset = nColOffset;
809 sal_uInt16 nWidth = GetWidth( pE );
810 MakeCol( pLocalColOffset, pE->nOffset, nWidth, nOffsetTolerance, nOffsetTolerance );
811 if ( pE->nWidth )
812 pE->nWidth = nWidth;
813 nColOffset = pE->nOffset + nWidth;
814 if ( nTableWidth < nColOffset - nColOffsetStart )
815 nTableWidth = nColOffset - nColOffsetStart;
818 void ScHTMLLayoutParser::CloseEntry( const HtmlImportInfo* pInfo )
820 bInCell = false;
821 if ( bTabInTabCell )
822 { // From the stack in TableOff
823 bTabInTabCell = false;
824 NewActEntry(maList.back().get()); // New free flying mxActEntry
825 return ;
827 if (mxActEntry->nTab == 0)
828 mxActEntry->nWidth = static_cast<sal_uInt16>(aPageSize.Width());
829 Colonize(mxActEntry.get());
830 nColCnt = mxActEntry->nCol + mxActEntry->nColOverlap;
831 if ( nMaxCol < nColCnt )
832 nMaxCol = nColCnt; // TableStack MaxCol
833 if ( nColMax < nColCnt )
834 nColMax = nColCnt; // Global MaxCol for ScEEParser GetDimensions!
835 EntryEnd(mxActEntry.get(), pInfo->aSelection);
836 ESelection& rSel = mxActEntry->aSel;
837 while ( rSel.nStartPara < rSel.nEndPara
838 && pEdit->GetTextLen( rSel.nStartPara ) == 0 )
839 { // Strip preceding empty paragraphs
840 rSel.nStartPara++;
842 while ( rSel.nEndPos == 0 && rSel.nEndPara > rSel.nStartPara )
843 { // Strip successive empty paragraphs
844 rSel.nEndPara--;
845 rSel.nEndPos = pEdit->GetTextLen( rSel.nEndPara );
847 if ( rSel.nStartPara > rSel.nEndPara )
848 { // Gives GPF in CreateTextObject
849 OSL_FAIL( "CloseEntry: EditEngine ESelection Start > End" );
850 rSel.nEndPara = rSel.nStartPara;
852 if ( rSel.HasRange() )
853 mxActEntry->aItemSet.Put( ScLineBreakCell(true) );
854 maList.push_back(mxActEntry);
855 NewActEntry(mxActEntry.get()); // New free flying mxActEntry
858 IMPL_LINK( ScHTMLLayoutParser, HTMLImportHdl, HtmlImportInfo&, rInfo, void )
860 switch ( rInfo.eState )
862 case HtmlImportState::NextToken:
863 ProcToken( &rInfo );
864 break;
865 case HtmlImportState::Start:
866 break;
867 case HtmlImportState::End:
868 if ( rInfo.aSelection.nEndPos )
870 // If text remains: create paragraph, without calling CloseEntry().
871 if( bInCell ) // ...but only in opened table cells.
873 bInCell = false;
874 NextRow( &rInfo );
875 bInCell = true;
877 CloseEntry( &rInfo );
879 while ( nTableLevel > 0 )
880 TableOff( &rInfo ); // close tables, if </TABLE> missing
881 break;
882 case HtmlImportState::SetAttr:
883 break;
884 case HtmlImportState::InsertText:
885 break;
886 case HtmlImportState::InsertPara:
887 if ( nTableLevel < 1 )
889 CloseEntry( &rInfo );
890 NextRow( &rInfo );
892 break;
893 case HtmlImportState::InsertField:
894 break;
895 default:
896 OSL_FAIL("HTMLImportHdl: unknown ImportInfo.eState");
900 void ScHTMLLayoutParser::TableDataOn( HtmlImportInfo* pInfo )
902 if ( bInCell )
903 CloseEntry( pInfo );
904 if ( !nTableLevel )
906 OSL_FAIL( "dumbo doc! <TH> or <TD> without previous <TABLE>" );
907 TableOn( pInfo );
909 bInCell = true;
910 bool bHorJustifyCenterTH = (pInfo->nToken == HtmlTokenId::TABLEHEADER_ON);
911 const HTMLOptions& rOptions = static_cast<HTMLParser*>(pInfo->pParser)->GetOptions();
912 for (const auto & rOption : rOptions)
914 switch( rOption.GetToken() )
916 case HtmlOptionId::COLSPAN:
918 mxActEntry->nColOverlap = static_cast<SCCOL>(rOption.GetString().toInt32());
920 break;
921 case HtmlOptionId::ROWSPAN:
923 mxActEntry->nRowOverlap = static_cast<SCROW>(rOption.GetString().toInt32());
925 break;
926 case HtmlOptionId::ALIGN:
928 bHorJustifyCenterTH = false;
929 SvxCellHorJustify eVal;
930 const OUString& rOptVal = rOption.GetString();
931 if ( rOptVal.equalsIgnoreAsciiCase( OOO_STRING_SVTOOLS_HTML_AL_right ) )
932 eVal = SvxCellHorJustify::Right;
933 else if ( rOptVal.equalsIgnoreAsciiCase( OOO_STRING_SVTOOLS_HTML_AL_center ) )
934 eVal = SvxCellHorJustify::Center;
935 else if ( rOptVal.equalsIgnoreAsciiCase( OOO_STRING_SVTOOLS_HTML_AL_left ) )
936 eVal = SvxCellHorJustify::Left;
937 else
938 eVal = SvxCellHorJustify::Standard;
939 if ( eVal != SvxCellHorJustify::Standard )
940 mxActEntry->aItemSet.Put(SvxHorJustifyItem(eVal, ATTR_HOR_JUSTIFY));
942 break;
943 case HtmlOptionId::VALIGN:
945 SvxCellVerJustify eVal;
946 const OUString& rOptVal = rOption.GetString();
947 if ( rOptVal.equalsIgnoreAsciiCase( OOO_STRING_SVTOOLS_HTML_VA_top ) )
948 eVal = SvxCellVerJustify::Top;
949 else if ( rOptVal.equalsIgnoreAsciiCase( OOO_STRING_SVTOOLS_HTML_VA_middle ) )
950 eVal = SvxCellVerJustify::Center;
951 else if ( rOptVal.equalsIgnoreAsciiCase( OOO_STRING_SVTOOLS_HTML_VA_bottom ) )
952 eVal = SvxCellVerJustify::Bottom;
953 else
954 eVal = SvxCellVerJustify::Standard;
955 mxActEntry->aItemSet.Put(SvxVerJustifyItem(eVal, ATTR_VER_JUSTIFY));
957 break;
958 case HtmlOptionId::WIDTH:
960 mxActEntry->nWidth = GetWidthPixel(rOption);
962 break;
963 case HtmlOptionId::BGCOLOR:
965 Color aColor;
966 rOption.GetColor( aColor );
967 mxActEntry->aItemSet.Put(SvxBrushItem(aColor, ATTR_BACKGROUND));
969 break;
970 case HtmlOptionId::SDVAL:
972 mxActEntry->pValStr = rOption.GetString();
974 break;
975 case HtmlOptionId::SDNUM:
977 mxActEntry->pNumStr = rOption.GetString();
979 break;
980 default: break;
984 mxActEntry->nCol = nColCnt;
985 mxActEntry->nRow = nRowCnt;
986 mxActEntry->nTab = nTable;
988 if ( bHorJustifyCenterTH )
989 mxActEntry->aItemSet.Put(
990 SvxHorJustifyItem( SvxCellHorJustify::Center, ATTR_HOR_JUSTIFY) );
993 void ScHTMLLayoutParser::TableRowOn( const HtmlImportInfo* pInfo )
995 if ( nColCnt > nColCntStart )
996 NextRow( pInfo ); // The optional TableRowOff wasn't there
997 nColOffset = nColOffsetStart;
1000 void ScHTMLLayoutParser::TableRowOff( const HtmlImportInfo* pInfo )
1002 NextRow( pInfo );
1005 void ScHTMLLayoutParser::TableDataOff( const HtmlImportInfo* pInfo )
1007 if ( bInCell )
1008 CloseEntry( pInfo ); // Only if it really was one
1011 void ScHTMLLayoutParser::TableOn( HtmlImportInfo* pInfo )
1013 if ( ++nTableLevel > 1 )
1014 { // Table in Table
1015 sal_uInt16 nTmpColOffset = nColOffset; // Will be changed in Colonize()
1016 Colonize(mxActEntry.get());
1017 aTableStack.push( std::make_unique<ScHTMLTableStackEntry>(
1018 mxActEntry, xLockedList, pLocalColOffset, nFirstTableCell,
1019 nRowCnt, nColCntStart, nMaxCol, nTable,
1020 nTableWidth, nColOffset, nColOffsetStart,
1021 bFirstRow ) );
1022 sal_uInt16 nLastWidth = nTableWidth;
1023 nTableWidth = GetWidth(mxActEntry.get());
1024 if ( nTableWidth == nLastWidth && nMaxCol - nColCntStart > 1 )
1025 { // There must be more than one, so this one cannot be enough
1026 nTableWidth = nLastWidth / static_cast<sal_uInt16>((nMaxCol - nColCntStart));
1028 nLastWidth = nTableWidth;
1029 if ( pInfo->nToken == HtmlTokenId::TABLE_ON )
1030 { // It can still be TD or TH, if we didn't have a TABLE earlier
1031 const HTMLOptions& rOptions = static_cast<HTMLParser*>(pInfo->pParser)->GetOptions();
1032 for (const auto & rOption : rOptions)
1034 switch( rOption.GetToken() )
1036 case HtmlOptionId::WIDTH:
1037 { // Percent: of document width or outer cell
1038 nTableWidth = GetWidthPixel( rOption );
1040 break;
1041 case HtmlOptionId::BORDER:
1042 // Border is: ((pOption->GetString().Len() == 0) || (pOption->GetNumber() != 0));
1043 break;
1044 default: break;
1048 bInCell = false;
1049 if ( bTabInTabCell && (nTableWidth >= nLastWidth) )
1050 { // Multiple tables in one cell, underneath each other
1051 bTabInTabCell = false;
1052 NextRow( pInfo );
1054 else
1055 { // It start's in this cell or next to each other
1056 bTabInTabCell = false;
1057 nColCntStart = nColCnt;
1058 nColOffset = nTmpColOffset;
1059 nColOffsetStart = nColOffset;
1062 NewActEntry(!maList.empty() ? maList.back().get() : nullptr); // New free flying mxActEntry
1063 xLockedList = new ScRangeList;
1065 else
1066 { // Simple table at the document level
1067 EntryEnd(mxActEntry.get(), pInfo->aSelection);
1068 if (mxActEntry->aSel.HasRange())
1069 { // Flying text left
1070 CloseEntry( pInfo );
1071 NextRow( pInfo );
1073 aTableStack.push( std::make_unique<ScHTMLTableStackEntry>(
1074 mxActEntry, xLockedList, pLocalColOffset, nFirstTableCell,
1075 nRowCnt, nColCntStart, nMaxCol, nTable,
1076 nTableWidth, nColOffset, nColOffsetStart,
1077 bFirstRow ) );
1078 // As soon as we have multiple tables we need to be tolerant with the offsets.
1079 if (nMaxTable > 0)
1080 nOffsetTolerance = SC_HTML_OFFSET_TOLERANCE_LARGE;
1081 nTableWidth = 0;
1082 if ( pInfo->nToken == HtmlTokenId::TABLE_ON )
1084 // It can still be TD or TH, if we didn't have a TABLE earlier
1085 const HTMLOptions& rOptions = static_cast<HTMLParser*>(pInfo->pParser)->GetOptions();
1086 for (const auto & rOption : rOptions)
1088 switch( rOption.GetToken() )
1090 case HtmlOptionId::WIDTH:
1091 { // Percent: of document width or outer cell
1092 nTableWidth = GetWidthPixel( rOption );
1094 break;
1095 case HtmlOptionId::BORDER:
1096 //BorderOn is: ((pOption->GetString().Len() == 0) || (pOption->GetNumber() != 0));
1097 break;
1098 default: break;
1103 nTable = ++nMaxTable;
1104 bFirstRow = true;
1105 nFirstTableCell = maList.size();
1107 pLocalColOffset = new ScHTMLColOffset;
1108 MakeColNoRef( pLocalColOffset, nColOffsetStart, 0, 0, 0 );
1111 void ScHTMLLayoutParser::TableOff( const HtmlImportInfo* pInfo )
1113 if ( bInCell )
1114 CloseEntry( pInfo );
1115 if ( nColCnt > nColCntStart )
1116 TableRowOff( pInfo ); // The optional TableRowOff wasn't
1117 if ( !nTableLevel )
1119 OSL_FAIL( "dumbo doc! </TABLE> without opening <TABLE>" );
1120 return ;
1122 if ( --nTableLevel > 0 )
1123 { // Table in Table done
1124 if ( !aTableStack.empty() )
1126 std::unique_ptr<ScHTMLTableStackEntry> pS = std::move(aTableStack.top());
1127 aTableStack.pop();
1129 auto& pE = pS->xCellEntry;
1130 SCROW nRows = nRowCnt - pS->nRowCnt;
1131 if ( nRows > 1 )
1132 { // Insert size of table at this position
1133 SCROW nRow = pS->nRowCnt;
1134 sal_uInt16 nTab = pS->nTable;
1135 if ( !pTables )
1136 pTables.reset( new OuterMap );
1137 // Height of outer table
1138 OuterMap::const_iterator it = pTables->find( nTab );
1139 InnerMap* pTab1;
1140 if ( it == pTables->end() )
1142 pTab1 = new InnerMap;
1143 (*pTables)[ nTab ] = pTab1;
1145 else
1146 pTab1 = it->second;
1147 SCROW nRowSpan = pE->nRowOverlap;
1148 SCROW nRowKGV;
1149 SCROW nRowsPerRow1; // Outer table
1150 SCROW nRowsPerRow2; // Inner table
1151 if ( nRowSpan > 1 )
1152 { // LCM to which we can map the inner and outer rows
1153 nRowKGV = std::lcm( nRowSpan, nRows );
1154 nRowsPerRow1 = nRowKGV / nRowSpan;
1155 nRowsPerRow2 = nRowKGV / nRows;
1157 else
1159 nRowKGV = nRowsPerRow1 = nRows;
1160 nRowsPerRow2 = 1;
1162 InnerMap* pTab2 = nullptr;
1163 if ( nRowsPerRow2 > 1 )
1164 { // Height of the inner table
1165 pTab2 = new InnerMap;
1166 (*pTables)[ nTable ] = pTab2;
1168 // Abuse void* Data entry of the Table class for height mapping
1169 if ( nRowKGV > 1 )
1171 if ( nRowsPerRow1 > 1 )
1172 { // Outer
1173 for ( SCROW j=0; j < nRowSpan; j++ )
1175 sal_uLong nRowKey = nRow + j;
1176 SCROW nR = (*pTab1)[ nRowKey ];
1177 if ( !nR )
1178 (*pTab1)[ nRowKey ] = nRowsPerRow1;
1179 else if ( nRowsPerRow1 > nR )
1180 (*pTab1)[ nRowKey ] = nRowsPerRow1;
1181 //TODO: How can we improve on this?
1182 else if ( nRowsPerRow1 < nR && nRowSpan == 1
1183 && nTable == nMaxTable )
1184 { // Still some space left, merge in a better way (if possible)
1185 SCROW nAdd = nRowsPerRow1 - (nR % nRowsPerRow1);
1186 nR += nAdd;
1187 if ( (nR % nRows) == 0 )
1188 { // Only if representable
1189 SCROW nR2 = (*pTab1)[ nRowKey+1 ];
1190 if ( nR2 > nAdd )
1191 { // Only if we really have enough space
1192 (*pTab1)[ nRowKey ] = nR;
1193 (*pTab1)[ nRowKey+1 ] = nR2 - nAdd;
1194 nRowsPerRow2 = nR / nRows;
1200 if ( nRowsPerRow2 > 1 )
1201 { // Inner
1202 if ( !pTab2 )
1203 { // nRowsPerRow2 could be've been incremented
1204 pTab2 = new InnerMap;
1205 (*pTables)[ nTable ] = pTab2;
1207 for ( SCROW j=0; j < nRows; j++ )
1209 sal_uLong nRowKey = nRow + j;
1210 (*pTab2)[ nRowKey ] = nRowsPerRow2;
1216 SetWidths();
1218 if ( !pE->nWidth )
1219 pE->nWidth = nTableWidth;
1220 else if ( pE->nWidth < nTableWidth )
1222 sal_uInt16 nOldOffset = pE->nOffset + pE->nWidth;
1223 sal_uInt16 nNewOffset = pE->nOffset + nTableWidth;
1224 ModifyOffset( pS->pLocalColOffset, nOldOffset, nNewOffset, nOffsetTolerance );
1225 sal_uInt16 nTmp = nNewOffset - pE->nOffset - pE->nWidth;
1226 pE->nWidth = nNewOffset - pE->nOffset;
1227 pS->nTableWidth = pS->nTableWidth + nTmp;
1228 if ( pS->nColOffset >= nOldOffset )
1229 pS->nColOffset = pS->nColOffset + nTmp;
1232 nColCnt = pE->nCol + pE->nColOverlap;
1233 nRowCnt = pS->nRowCnt;
1234 nColCntStart = pS->nColCntStart;
1235 nMaxCol = pS->nMaxCol;
1236 nTable = pS->nTable;
1237 nTableWidth = pS->nTableWidth;
1238 nFirstTableCell = pS->nFirstTableCell;
1239 nColOffset = pS->nColOffset;
1240 nColOffsetStart = pS->nColOffsetStart;
1241 bFirstRow = pS->bFirstRow;
1242 xLockedList = pS->xLockedList;
1243 pLocalColOffset = pS->pLocalColOffset;
1244 // mxActEntry is kept around if a table is started in the same row
1245 // (anything's possible in HTML); will be deleted by CloseEntry
1246 mxActEntry = pE;
1248 bTabInTabCell = true;
1249 bInCell = true;
1251 else
1252 { // Simple table finished
1253 SetWidths();
1254 nMaxCol = 0;
1255 nTable = 0;
1256 if ( !aTableStack.empty() )
1258 ScHTMLTableStackEntry* pS = aTableStack.top().get();
1259 delete pLocalColOffset;
1260 pLocalColOffset = pS->pLocalColOffset;
1261 aTableStack.pop();
1266 void ScHTMLLayoutParser::Image( HtmlImportInfo* pInfo )
1268 mxActEntry->maImageList.push_back(std::make_unique<ScHTMLImage>());
1269 ScHTMLImage* pImage = mxActEntry->maImageList.back().get();
1270 const HTMLOptions& rOptions = static_cast<HTMLParser*>(pInfo->pParser)->GetOptions();
1271 for (const auto & rOption : rOptions)
1273 switch( rOption.GetToken() )
1275 case HtmlOptionId::SRC:
1277 pImage->aURL = INetURLObject::GetAbsURL( aBaseURL, rOption.GetString() );
1279 break;
1280 case HtmlOptionId::ALT:
1282 if (!mxActEntry->bHasGraphic)
1283 { // ALT text only if not any image loaded
1284 if (!mxActEntry->aAltText.isEmpty())
1285 mxActEntry->aAltText += "; ";
1287 mxActEntry->aAltText += rOption.GetString();
1290 break;
1291 case HtmlOptionId::WIDTH:
1293 pImage->aSize.setWidth( static_cast<tools::Long>(rOption.GetNumber()) );
1295 break;
1296 case HtmlOptionId::HEIGHT:
1298 pImage->aSize.setHeight( static_cast<tools::Long>(rOption.GetNumber()) );
1300 break;
1301 case HtmlOptionId::HSPACE:
1303 pImage->aSpace.setX( static_cast<tools::Long>(rOption.GetNumber()) );
1305 break;
1306 case HtmlOptionId::VSPACE:
1308 pImage->aSpace.setY( static_cast<tools::Long>(rOption.GetNumber()) );
1310 break;
1311 default: break;
1314 if (pImage->aURL.isEmpty())
1316 OSL_FAIL( "Image: graphic without URL ?!?" );
1317 return ;
1320 sal_uInt16 nFormat;
1321 std::unique_ptr<Graphic> pGraphic(new Graphic);
1322 GraphicFilter& rFilter = GraphicFilter::GetGraphicFilter();
1323 if ( ERRCODE_NONE != GraphicFilter::LoadGraphic( pImage->aURL, pImage->aFilterName,
1324 *pGraphic, &rFilter, &nFormat ) )
1326 return ; // Bad luck
1328 if (!mxActEntry->bHasGraphic)
1329 { // discard any ALT text in this cell if we have any image
1330 mxActEntry->bHasGraphic = true;
1331 mxActEntry->aAltText.clear();
1333 pImage->aFilterName = rFilter.GetImportFormatName( nFormat );
1334 pImage->pGraphic = std::move( pGraphic );
1335 if ( !(pImage->aSize.Width() && pImage->aSize.Height()) )
1337 OutputDevice* pDefaultDev = Application::GetDefaultDevice();
1338 pImage->aSize = pDefaultDev->LogicToPixel( pImage->pGraphic->GetPrefSize(),
1339 pImage->pGraphic->GetPrefMapMode() );
1341 if (mxActEntry->maImageList.empty())
1342 return;
1344 tools::Long nWidth = 0;
1345 for (const std::unique_ptr<ScHTMLImage> & pI : mxActEntry->maImageList)
1347 if ( pI->nDir & nHorizontal )
1348 nWidth += pI->aSize.Width() + 2 * pI->aSpace.X();
1349 else
1350 nWidth = 0;
1352 if ( mxActEntry->nWidth
1353 && (nWidth + pImage->aSize.Width() + 2 * pImage->aSpace.X()
1354 >= mxActEntry->nWidth) )
1355 mxActEntry->maImageList.back()->nDir = nVertical;
1358 void ScHTMLLayoutParser::ColOn( HtmlImportInfo* pInfo )
1360 const HTMLOptions& rOptions = static_cast<HTMLParser*>(pInfo->pParser)->GetOptions();
1361 for (const auto & rOption : rOptions)
1363 if( rOption.GetToken() == HtmlOptionId::WIDTH )
1365 sal_uInt16 nVal = GetWidthPixel( rOption );
1366 MakeCol( pLocalColOffset, nColOffset, nVal, 0, 0 );
1367 nColOffset = nColOffset + nVal;
1372 sal_uInt16 ScHTMLLayoutParser::GetWidthPixel( const HTMLOption& rOption )
1374 const OUString& rOptVal = rOption.GetString();
1375 if ( rOptVal.indexOf('%') != -1 )
1376 { // Percent
1377 sal_uInt16 nW = (nTableWidth ? nTableWidth : static_cast<sal_uInt16>(aPageSize.Width()));
1378 return static_cast<sal_uInt16>((rOption.GetNumber() * nW) / 100);
1380 else
1382 if ( rOptVal.indexOf('*') != -1 )
1383 { // Relative to what?
1384 // TODO: Collect all relative values in ColArray and then MakeCol
1385 return 0;
1387 else
1388 return static_cast<sal_uInt16>(rOption.GetNumber()); // Pixel
1392 void ScHTMLLayoutParser::AnchorOn( HtmlImportInfo* pInfo )
1394 const HTMLOptions& rOptions = static_cast<HTMLParser*>(pInfo->pParser)->GetOptions();
1395 for (const auto & rOption : rOptions)
1397 if( rOption.GetToken() == HtmlOptionId::NAME )
1398 mxActEntry->pName = rOption.GetString();
1402 bool ScHTMLLayoutParser::IsAtBeginningOfText( const HtmlImportInfo* pInfo )
1404 ESelection& rSel = mxActEntry->aSel;
1405 return rSel.nStartPara == rSel.nEndPara &&
1406 rSel.nStartPara <= pInfo->aSelection.nEndPara &&
1407 pEdit->GetTextLen( rSel.nStartPara ) == 0;
1410 void ScHTMLLayoutParser::FontOn( HtmlImportInfo* pInfo )
1412 if ( !IsAtBeginningOfText( pInfo ) )
1413 return;
1415 // Only at the start of the text; applies to whole line
1416 const HTMLOptions& rOptions = static_cast<HTMLParser*>(pInfo->pParser)->GetOptions();
1417 for (const auto & rOption : rOptions)
1419 switch( rOption.GetToken() )
1421 case HtmlOptionId::FACE :
1423 const OUString& rFace = rOption.GetString();
1424 OUStringBuffer aFontName;
1425 sal_Int32 nPos = 0;
1426 while( nPos != -1 )
1428 // Font list, VCL uses the semicolon as separator
1429 // HTML uses the comma
1430 OUString aFName = rFace.getToken( 0, ',', nPos );
1431 aFName = comphelper::string::strip(aFName, ' ');
1432 if( !aFontName.isEmpty() )
1433 aFontName.append(";");
1434 aFontName.append(aFName);
1436 if ( !aFontName.isEmpty() )
1437 mxActEntry->aItemSet.Put( SvxFontItem( FAMILY_DONTKNOW,
1438 aFontName.makeStringAndClear(), OUString(), PITCH_DONTKNOW,
1439 RTL_TEXTENCODING_DONTKNOW, ATTR_FONT ) );
1441 break;
1442 case HtmlOptionId::SIZE :
1444 sal_uInt16 nSize = static_cast<sal_uInt16>(rOption.GetNumber());
1445 if ( nSize == 0 )
1446 nSize = 1;
1447 else if ( nSize > SC_HTML_FONTSIZES )
1448 nSize = SC_HTML_FONTSIZES;
1449 mxActEntry->aItemSet.Put( SvxFontHeightItem(
1450 maFontHeights[nSize-1], 100, ATTR_FONT_HEIGHT ) );
1452 break;
1453 case HtmlOptionId::COLOR :
1455 Color aColor;
1456 rOption.GetColor( aColor );
1457 mxActEntry->aItemSet.Put( SvxColorItem( aColor, ATTR_FONT_COLOR ) );
1459 break;
1460 default: break;
1465 void ScHTMLLayoutParser::ProcToken( HtmlImportInfo* pInfo )
1467 switch ( pInfo->nToken )
1469 case HtmlTokenId::META:
1471 HTMLParser* pParser = static_cast<HTMLParser*>(pInfo->pParser);
1472 uno::Reference<document::XDocumentPropertiesSupplier> xDPS(
1473 mpDoc->GetDocumentShell()->GetModel(), uno::UNO_QUERY_THROW);
1474 pParser->ParseMetaOptions(
1475 xDPS->getDocumentProperties(),
1476 mpDoc->GetDocumentShell()->GetHeaderAttributes() );
1478 break;
1479 case HtmlTokenId::TITLE_ON:
1481 bInTitle = true;
1482 aString.clear();
1484 break;
1485 case HtmlTokenId::TITLE_OFF:
1487 if ( bInTitle && !aString.isEmpty() )
1489 // Remove blanks from line breaks
1490 aString = aString.trim();
1491 uno::Reference<document::XDocumentPropertiesSupplier> xDPS(
1492 mpDoc->GetDocumentShell()->GetModel(),
1493 uno::UNO_QUERY_THROW);
1494 xDPS->getDocumentProperties()->setTitle(aString);
1496 bInTitle = false;
1498 break;
1499 case HtmlTokenId::TABLE_ON:
1501 TableOn( pInfo );
1503 break;
1504 case HtmlTokenId::COL_ON:
1506 ColOn( pInfo );
1508 break;
1509 case HtmlTokenId::TABLEHEADER_ON: // Opens row
1511 if ( bInCell )
1512 CloseEntry( pInfo );
1513 // Do not set bInCell to true, TableDataOn does that
1514 mxActEntry->aItemSet.Put(
1515 SvxWeightItem( WEIGHT_BOLD, ATTR_FONT_WEIGHT) );
1516 [[fallthrough]];
1518 case HtmlTokenId::TABLEDATA_ON: // Opens cell
1520 TableDataOn( pInfo );
1522 break;
1523 case HtmlTokenId::TABLEHEADER_OFF:
1524 case HtmlTokenId::TABLEDATA_OFF: // Closes cell
1526 TableDataOff( pInfo );
1528 break;
1529 case HtmlTokenId::TABLEROW_ON: // Before first cell in row
1531 TableRowOn( pInfo );
1533 break;
1534 case HtmlTokenId::TABLEROW_OFF: // After last cell in row
1536 TableRowOff( pInfo );
1538 break;
1539 case HtmlTokenId::TABLE_OFF:
1541 TableOff( pInfo );
1543 break;
1544 case HtmlTokenId::IMAGE:
1546 Image( pInfo );
1548 break;
1549 case HtmlTokenId::PARABREAK_OFF:
1550 { // We continue vertically after an image
1551 if (!mxActEntry->maImageList.empty())
1552 mxActEntry->maImageList.back()->nDir = nVertical;
1554 break;
1555 case HtmlTokenId::ANCHOR_ON:
1557 AnchorOn( pInfo );
1559 break;
1560 case HtmlTokenId::FONT_ON :
1562 FontOn( pInfo );
1564 break;
1565 case HtmlTokenId::BIGPRINT_ON :
1567 // TODO: Remember current font size and increase by 1
1568 if ( IsAtBeginningOfText( pInfo ) )
1569 mxActEntry->aItemSet.Put( SvxFontHeightItem(
1570 maFontHeights[3], 100, ATTR_FONT_HEIGHT ) );
1572 break;
1573 case HtmlTokenId::SMALLPRINT_ON :
1575 // TODO: Remember current font size and decrease by 1
1576 if ( IsAtBeginningOfText( pInfo ) )
1577 mxActEntry->aItemSet.Put( SvxFontHeightItem(
1578 maFontHeights[0], 100, ATTR_FONT_HEIGHT ) );
1580 break;
1581 case HtmlTokenId::BOLD_ON :
1582 case HtmlTokenId::STRONG_ON :
1584 if ( IsAtBeginningOfText( pInfo ) )
1585 mxActEntry->aItemSet.Put( SvxWeightItem( WEIGHT_BOLD,
1586 ATTR_FONT_WEIGHT ) );
1588 break;
1589 case HtmlTokenId::ITALIC_ON :
1590 case HtmlTokenId::EMPHASIS_ON :
1591 case HtmlTokenId::ADDRESS_ON :
1592 case HtmlTokenId::BLOCKQUOTE_ON :
1593 case HtmlTokenId::BLOCKQUOTE30_ON :
1594 case HtmlTokenId::CITATION_ON :
1595 case HtmlTokenId::VARIABLE_ON :
1597 if ( IsAtBeginningOfText( pInfo ) )
1598 mxActEntry->aItemSet.Put( SvxPostureItem( ITALIC_NORMAL,
1599 ATTR_FONT_POSTURE ) );
1601 break;
1602 case HtmlTokenId::DEFINSTANCE_ON :
1604 if ( IsAtBeginningOfText( pInfo ) )
1606 mxActEntry->aItemSet.Put( SvxWeightItem( WEIGHT_BOLD,
1607 ATTR_FONT_WEIGHT ) );
1608 mxActEntry->aItemSet.Put( SvxPostureItem( ITALIC_NORMAL,
1609 ATTR_FONT_POSTURE ) );
1612 break;
1613 case HtmlTokenId::UNDERLINE_ON :
1615 if ( IsAtBeginningOfText( pInfo ) )
1616 mxActEntry->aItemSet.Put( SvxUnderlineItem( LINESTYLE_SINGLE,
1617 ATTR_FONT_UNDERLINE ) );
1619 break;
1620 case HtmlTokenId::TEXTTOKEN:
1622 if ( bInTitle )
1623 aString += pInfo->aText;
1625 break;
1626 default: ;
1630 // HTML DATA QUERY PARSER
1632 template< typename Type >
1633 static Type getLimitedValue( const Type& rValue, const Type& rMin, const Type& rMax )
1634 { return std::clamp( rValue, rMin, rMax ); }
1636 ScHTMLEntry::ScHTMLEntry( const SfxItemSet& rItemSet, ScHTMLTableId nTableId ) :
1637 ScEEParseEntry( rItemSet ),
1638 mbImportAlways( false )
1640 nTab = nTableId;
1641 bEntirePara = false;
1644 bool ScHTMLEntry::HasContents() const
1646 return mbImportAlways || aSel.HasRange() || !aAltText.isEmpty() || IsTable();
1649 void ScHTMLEntry::AdjustStart( const HtmlImportInfo& rInfo )
1651 // set start position
1652 aSel.nStartPara = rInfo.aSelection.nStartPara;
1653 aSel.nStartPos = rInfo.aSelection.nStartPos;
1654 // adjust end position
1655 if( (aSel.nEndPara < aSel.nStartPara) || ((aSel.nEndPara == aSel.nStartPara) && (aSel.nEndPos < aSel.nStartPos)) )
1657 aSel.nEndPara = aSel.nStartPara;
1658 aSel.nEndPos = aSel.nStartPos;
1662 void ScHTMLEntry::AdjustEnd( const HtmlImportInfo& rInfo )
1664 OSL_ENSURE( (aSel.nEndPara < rInfo.aSelection.nEndPara) ||
1665 ((aSel.nEndPara == rInfo.aSelection.nEndPara) && (aSel.nEndPos <= rInfo.aSelection.nEndPos)),
1666 "ScHTMLQueryParser::AdjustEntryEnd - invalid end position" );
1667 // set end position
1668 aSel.nEndPara = rInfo.aSelection.nEndPara;
1669 aSel.nEndPos = rInfo.aSelection.nEndPos;
1672 void ScHTMLEntry::Strip( const EditEngine& rEditEngine )
1674 // strip leading empty paragraphs
1675 while( (aSel.nStartPara < aSel.nEndPara) && (rEditEngine.GetTextLen( aSel.nStartPara ) <= aSel.nStartPos) )
1677 ++aSel.nStartPara;
1678 aSel.nStartPos = 0;
1680 // strip trailing empty paragraphs
1681 while( (aSel.nStartPara < aSel.nEndPara) && (aSel.nEndPos == 0) )
1683 --aSel.nEndPara;
1684 aSel.nEndPos = rEditEngine.GetTextLen( aSel.nEndPara );
1688 /** A map of ScHTMLTable objects.
1690 Organizes the tables with a unique table key. Stores nested tables inside
1691 the parent table and forms in this way a tree structure of tables. An
1692 instance of this class owns the contained table objects and deletes them
1693 on destruction.
1695 class ScHTMLTableMap final
1697 private:
1698 typedef std::shared_ptr< ScHTMLTable > ScHTMLTablePtr;
1699 typedef std::map< ScHTMLTableId, ScHTMLTablePtr > ScHTMLTableStdMap;
1701 public:
1702 typedef ScHTMLTableStdMap::iterator iterator;
1703 typedef ScHTMLTableStdMap::const_iterator const_iterator;
1705 private:
1706 ScHTMLTable& mrParentTable; /// Reference to parent table.
1707 ScHTMLTableStdMap maTables; /// Container for all table objects.
1708 mutable ScHTMLTable* mpCurrTable; /// Current table, used for fast search.
1710 public:
1711 explicit ScHTMLTableMap( ScHTMLTable& rParentTable );
1713 const_iterator begin() const { return maTables.begin(); }
1714 const_iterator end() const { return maTables.end(); }
1716 /** Returns the specified table.
1717 @param nTableId Unique identifier of the table.
1718 @param bDeep true = searches deep in all nested table; false = only in this container. */
1719 ScHTMLTable* FindTable( ScHTMLTableId nTableId, bool bDeep = true ) const;
1721 /** Inserts a new table into the container. This container owns the created table.
1722 @param bPreFormText true = New table is based on preformatted text (<pre> tag). */
1723 ScHTMLTable* CreateTable( const HtmlImportInfo& rInfo, bool bPreFormText, const ScDocument& rDoc );
1725 private:
1726 /** Sets a working table with its index for search optimization. */
1727 void SetCurrTable( ScHTMLTable* pTable ) const
1728 { if( pTable ) mpCurrTable = pTable; }
1731 ScHTMLTableMap::ScHTMLTableMap( ScHTMLTable& rParentTable ) :
1732 mrParentTable(rParentTable),
1733 mpCurrTable(nullptr)
1737 ScHTMLTable* ScHTMLTableMap::FindTable( ScHTMLTableId nTableId, bool bDeep ) const
1739 ScHTMLTable* pResult = nullptr;
1740 if( mpCurrTable && (nTableId == mpCurrTable->GetTableId()) )
1741 pResult = mpCurrTable; // cached table
1742 else
1744 const_iterator aFind = maTables.find( nTableId );
1745 if( aFind != maTables.end() )
1746 pResult = aFind->second.get(); // table from this container
1749 // not found -> search deep in nested tables
1750 if( !pResult && bDeep )
1751 for( const_iterator aIter = begin(), aEnd = end(); !pResult && (aIter != aEnd); ++aIter )
1752 pResult = aIter->second->FindNestedTable( nTableId );
1754 SetCurrTable( pResult );
1755 return pResult;
1758 ScHTMLTable* ScHTMLTableMap::CreateTable( const HtmlImportInfo& rInfo, bool bPreFormText, const ScDocument& rDoc )
1760 ScHTMLTable* pTable = new ScHTMLTable( mrParentTable, rInfo, bPreFormText, rDoc );
1761 maTables[ pTable->GetTableId() ].reset( pTable );
1762 SetCurrTable( pTable );
1763 return pTable;
1766 namespace {
1768 /** Simplified forward iterator for convenience.
1770 Before the iterator can be dereferenced, it must be tested with the is()
1771 method. The iterator may be invalid directly after construction (e.g. empty
1772 container).
1774 class ScHTMLTableIterator
1776 public:
1777 /** Constructs the iterator for the passed table map.
1778 @param pTableMap Pointer to the table map (is allowed to be NULL). */
1779 explicit ScHTMLTableIterator( const ScHTMLTableMap* pTableMap );
1781 bool is() const { return mpTableMap && maIter != maEnd; }
1782 ScHTMLTable* operator->() { return maIter->second.get(); }
1783 ScHTMLTableIterator& operator++() { ++maIter; return *this; }
1785 private:
1786 ScHTMLTableMap::const_iterator maIter;
1787 ScHTMLTableMap::const_iterator maEnd;
1788 const ScHTMLTableMap* mpTableMap;
1793 ScHTMLTableIterator::ScHTMLTableIterator( const ScHTMLTableMap* pTableMap ) :
1794 mpTableMap(pTableMap)
1796 if( pTableMap )
1798 maIter = pTableMap->begin();
1799 maEnd = pTableMap->end();
1803 ScHTMLTableAutoId::ScHTMLTableAutoId( ScHTMLTableId& rnUnusedId ) :
1804 mnTableId( rnUnusedId ),
1805 mrnUnusedId( rnUnusedId )
1807 ++mrnUnusedId;
1810 ScHTMLTable::ScHTMLTable( ScHTMLTable& rParentTable, const HtmlImportInfo& rInfo, bool bPreFormText, const ScDocument& rDoc ) :
1811 mpParentTable( &rParentTable ),
1812 maTableId( rParentTable.maTableId.mrnUnusedId ),
1813 maTableItemSet( rParentTable.GetCurrItemSet() ),
1814 mrEditEngine( rParentTable.mrEditEngine ),
1815 mrEEParseList( rParentTable.mrEEParseList ),
1816 mpCurrEntryVector( nullptr ),
1817 maSize( 1, 1 ),
1818 mpParser(rParentTable.mpParser),
1819 mrDoc(rDoc),
1820 mbBorderOn( false ),
1821 mbPreFormText( bPreFormText ),
1822 mbRowOn( false ),
1823 mbDataOn( false ),
1824 mbPushEmptyLine( false ),
1825 mbCaptionOn ( false )
1827 if( mbPreFormText )
1829 ImplRowOn();
1830 ImplDataOn( ScHTMLSize( 1, 1 ) );
1832 else
1834 ProcessFormatOptions( maTableItemSet, rInfo );
1835 const HTMLOptions& rOptions = static_cast<HTMLParser*>(rInfo.pParser)->GetOptions();
1836 for (const auto& rOption : rOptions)
1838 switch( rOption.GetToken() )
1840 case HtmlOptionId::BORDER:
1841 mbBorderOn = rOption.GetString().isEmpty() || (rOption.GetNumber() != 0);
1842 break;
1843 case HtmlOptionId::ID:
1844 maTableName = rOption.GetString();
1845 break;
1846 default: break;
1851 CreateNewEntry( rInfo );
1854 ScHTMLTable::ScHTMLTable(
1855 SfxItemPool& rPool,
1856 EditEngine& rEditEngine,
1857 std::vector<std::shared_ptr<ScEEParseEntry>>& rEEParseList,
1858 ScHTMLTableId& rnUnusedId, ScHTMLParser* pParser, const ScDocument& rDoc
1860 mpParentTable( nullptr ),
1861 maTableId( rnUnusedId ),
1862 maTableItemSet( rPool ),
1863 mrEditEngine( rEditEngine ),
1864 mrEEParseList( rEEParseList ),
1865 mpCurrEntryVector( nullptr ),
1866 maSize( 1, 1 ),
1867 mpParser(pParser),
1868 mrDoc(rDoc),
1869 mbBorderOn( false ),
1870 mbPreFormText( false ),
1871 mbRowOn( false ),
1872 mbDataOn( false ),
1873 mbPushEmptyLine( false ),
1874 mbCaptionOn ( false )
1876 // open the first "cell" of the document
1877 ImplRowOn();
1878 ImplDataOn( ScHTMLSize( 1, 1 ) );
1879 mxCurrEntry = CreateEntry();
1882 ScHTMLTable::~ScHTMLTable()
1886 const SfxItemSet& ScHTMLTable::GetCurrItemSet() const
1888 // first try cell item set, then row item set, then table item set
1889 return moDataItemSet ? *moDataItemSet : (moRowItemSet ? *moRowItemSet : maTableItemSet);
1892 ScHTMLSize ScHTMLTable::GetSpan( const ScHTMLPos& rCellPos ) const
1894 ScHTMLSize aSpan( 1, 1 );
1895 const ScRange* pRange = maVMergedCells.Find( rCellPos.MakeAddr() );
1896 if (!pRange)
1897 pRange = maHMergedCells.Find( rCellPos.MakeAddr() );
1898 if (pRange)
1899 aSpan.Set( pRange->aEnd.Col() - pRange->aStart.Col() + 1, pRange->aEnd.Row() - pRange->aStart.Row() + 1 );
1900 return aSpan;
1903 ScHTMLTable* ScHTMLTable::FindNestedTable( ScHTMLTableId nTableId ) const
1905 return mxNestedTables ? mxNestedTables->FindTable( nTableId ) : nullptr;
1908 void ScHTMLTable::PutItem( const SfxPoolItem& rItem )
1910 OSL_ENSURE( mxCurrEntry, "ScHTMLTable::PutItem - no current entry" );
1911 if( mxCurrEntry && mxCurrEntry->IsEmpty() )
1912 mxCurrEntry->GetItemSet().Put( rItem );
1915 void ScHTMLTable::PutText( const HtmlImportInfo& rInfo )
1917 OSL_ENSURE( mxCurrEntry, "ScHTMLTable::PutText - no current entry" );
1918 if( mxCurrEntry )
1920 if( !mxCurrEntry->HasContents() && IsSpaceCharInfo( rInfo ) )
1921 mxCurrEntry->AdjustStart( rInfo );
1922 else
1923 mxCurrEntry->AdjustEnd( rInfo );
1924 if (mbCaptionOn)
1925 maCaptionBuffer.append(rInfo.aText);
1930 void ScHTMLTable::InsertPara( const HtmlImportInfo& rInfo )
1932 if( mxCurrEntry && mbDataOn && !IsEmptyCell() )
1933 mxCurrEntry->SetImportAlways();
1934 PushEntry( rInfo );
1935 CreateNewEntry( rInfo );
1936 InsertLeadingEmptyLine();
1939 void ScHTMLTable::BreakOn()
1941 // empty line, if <br> is at start of cell
1942 mbPushEmptyLine = !mbPreFormText && mbDataOn && IsEmptyCell();
1945 void ScHTMLTable::HeadingOn()
1947 // call directly, InsertPara() has not been called before
1948 InsertLeadingEmptyLine();
1951 void ScHTMLTable::InsertLeadingEmptyLine()
1953 // empty line, if <p>, </p>, <h?>, or </h*> are not at start of cell
1954 mbPushEmptyLine = !mbPreFormText && mbDataOn && !IsEmptyCell();
1957 void ScHTMLTable::AnchorOn()
1959 OSL_ENSURE( mxCurrEntry, "ScHTMLTable::AnchorOn - no current entry" );
1960 // don't skip entries with single hyperlinks
1961 if( mxCurrEntry )
1962 mxCurrEntry->SetImportAlways();
1965 ScHTMLTable* ScHTMLTable::TableOn( const HtmlImportInfo& rInfo )
1967 PushEntry( rInfo );
1968 return InsertNestedTable( rInfo, false );
1971 ScHTMLTable* ScHTMLTable::TableOff( const HtmlImportInfo& rInfo )
1973 return mbPreFormText ? this : CloseTable( rInfo );
1976 void ScHTMLTable::CaptionOn()
1978 mbCaptionOn = true;
1979 maCaptionBuffer.setLength(0);
1982 void ScHTMLTable::CaptionOff()
1984 if (!mbCaptionOn)
1985 return;
1986 maCaption = maCaptionBuffer.makeStringAndClear().trim();
1987 mbCaptionOn = false;
1990 ScHTMLTable* ScHTMLTable::PreOn( const HtmlImportInfo& rInfo )
1992 PushEntry( rInfo );
1993 return InsertNestedTable( rInfo, true );
1996 ScHTMLTable* ScHTMLTable::PreOff( const HtmlImportInfo& rInfo )
1998 return mbPreFormText ? CloseTable( rInfo ) : this;
2001 void ScHTMLTable::RowOn( const HtmlImportInfo& rInfo )
2003 PushEntry( rInfo, true );
2004 if( mpParentTable && !mbPreFormText ) // no rows allowed in global and preformatted tables
2006 ImplRowOn();
2007 ProcessFormatOptions( *moRowItemSet, rInfo );
2009 CreateNewEntry( rInfo );
2012 void ScHTMLTable::RowOff( const HtmlImportInfo& rInfo )
2014 PushEntry( rInfo, true );
2015 if( mpParentTable && !mbPreFormText ) // no rows allowed in global and preformatted tables
2016 ImplRowOff();
2017 CreateNewEntry( rInfo );
2020 namespace {
2023 * Decode a number format string stored in Excel-generated HTML's CSS
2024 * region.
2026 OUString decodeNumberFormat(const OUString& rFmt)
2028 OUStringBuffer aBuf;
2029 const sal_Unicode* p = rFmt.getStr();
2030 sal_Int32 n = rFmt.getLength();
2031 for (sal_Int32 i = 0; i < n; ++i, ++p)
2033 if (*p == '\\')
2035 // Skip '\'.
2036 ++i;
2037 ++p;
2039 // Parse all subsequent digits until first non-digit is found.
2040 sal_Int32 nDigitCount = 0;
2041 const sal_Unicode* p1 = p;
2042 for (; i < n; ++i, ++p, ++nDigitCount)
2044 if (*p < '0' || '9' < *p)
2046 --i;
2047 --p;
2048 break;
2052 if (nDigitCount)
2054 // Hex-encoded character found. Decode it back into its
2055 // original character. An example of number format with
2056 // hex-encoded chars: "\0022$\0022\#\,\#\#0\.00"
2057 sal_uInt32 nVal = OUString(p1, nDigitCount).toUInt32(16);
2058 aBuf.append(static_cast<sal_Unicode>(nVal));
2061 else
2062 aBuf.append(*p);
2064 return aBuf.makeStringAndClear();
2069 void ScHTMLTable::DataOn( const HtmlImportInfo& rInfo )
2071 PushEntry( rInfo, true );
2072 if( mpParentTable && !mbPreFormText ) // no cells allowed in global and preformatted tables
2074 // read needed options from the <td> tag
2075 ScHTMLSize aSpanSize( 1, 1 );
2076 std::optional<OUString> pValStr, pNumStr;
2077 const HTMLOptions& rOptions = static_cast<HTMLParser*>(rInfo.pParser)->GetOptions();
2078 sal_uInt32 nNumberFormat = NUMBERFORMAT_ENTRY_NOT_FOUND;
2079 for (const auto& rOption : rOptions)
2081 switch (rOption.GetToken())
2083 case HtmlOptionId::COLSPAN:
2084 aSpanSize.mnCols = static_cast<SCCOL>( getLimitedValue<sal_Int32>( rOption.GetString().toInt32(), 1, 256 ) );
2085 break;
2086 case HtmlOptionId::ROWSPAN:
2087 aSpanSize.mnRows = static_cast<SCROW>( getLimitedValue<sal_Int32>( rOption.GetString().toInt32(), 1, 256 ) );
2088 break;
2089 case HtmlOptionId::SDVAL:
2090 pValStr = rOption.GetString();
2091 break;
2092 case HtmlOptionId::SDNUM:
2093 pNumStr = rOption.GetString();
2094 break;
2095 case HtmlOptionId::CLASS:
2097 // Pick up the number format associated with this class (if
2098 // any).
2099 OUString aClass = rOption.GetString();
2100 const ScHTMLStyles& rStyles = mpParser->GetStyles();
2101 const OUString& rVal = rStyles.getPropertyValue("td", aClass, "mso-number-format");
2102 if (!rVal.isEmpty())
2104 OUString aNumFmt = decodeNumberFormat(rVal);
2106 nNumberFormat = GetFormatTable()->GetEntryKey(aNumFmt);
2107 if (nNumberFormat == NUMBERFORMAT_ENTRY_NOT_FOUND)
2109 sal_Int32 nErrPos = 0;
2110 SvNumFormatType nDummy;
2111 bool bValidFmt = GetFormatTable()->PutEntry(aNumFmt, nErrPos, nDummy, nNumberFormat);
2112 if (!bValidFmt)
2113 nNumberFormat = NUMBERFORMAT_ENTRY_NOT_FOUND;
2117 break;
2118 default: break;
2122 ImplDataOn( aSpanSize );
2124 if (nNumberFormat != NUMBERFORMAT_ENTRY_NOT_FOUND)
2125 moDataItemSet->Put( SfxUInt32Item(ATTR_VALUE_FORMAT, nNumberFormat) );
2127 ProcessFormatOptions( *moDataItemSet, rInfo );
2128 CreateNewEntry( rInfo );
2129 mxCurrEntry->pValStr = std::move(pValStr);
2130 mxCurrEntry->pNumStr = std::move(pNumStr);
2132 else
2133 CreateNewEntry( rInfo );
2136 void ScHTMLTable::DataOff( const HtmlImportInfo& rInfo )
2138 PushEntry( rInfo, true );
2139 if( mpParentTable && !mbPreFormText ) // no cells allowed in global and preformatted tables
2140 ImplDataOff();
2141 CreateNewEntry( rInfo );
2144 void ScHTMLTable::BodyOn( const HtmlImportInfo& rInfo )
2146 bool bPushed = PushEntry( rInfo );
2147 if( !mpParentTable )
2149 // do not start new row, if nothing (no title) precedes the body.
2150 if( bPushed || !mbRowOn )
2151 ImplRowOn();
2152 if( bPushed || !mbDataOn )
2153 ImplDataOn( ScHTMLSize( 1, 1 ) );
2154 ProcessFormatOptions( *moDataItemSet, rInfo );
2156 CreateNewEntry( rInfo );
2159 void ScHTMLTable::BodyOff( const HtmlImportInfo& rInfo )
2161 PushEntry( rInfo );
2162 if( !mpParentTable )
2164 ImplDataOff();
2165 ImplRowOff();
2167 CreateNewEntry( rInfo );
2170 ScHTMLTable* ScHTMLTable::CloseTable( const HtmlImportInfo& rInfo )
2172 if( mpParentTable ) // not allowed to close global table
2174 PushEntry( rInfo, mbDataOn );
2175 ImplDataOff();
2176 ImplRowOff();
2177 mpParentTable->PushTableEntry( GetTableId() );
2178 mpParentTable->CreateNewEntry( rInfo );
2179 if( mbPreFormText ) // enclose preformatted table with empty lines in parent table
2180 mpParentTable->InsertLeadingEmptyLine();
2181 return mpParentTable;
2183 return this;
2186 SCCOLROW ScHTMLTable::GetDocSize( ScHTMLOrient eOrient, SCCOLROW nCellPos ) const
2188 const ScSizeVec& rSizes = maCumSizes[ eOrient ];
2189 size_t nIndex = static_cast< size_t >( nCellPos );
2190 if( nIndex >= rSizes.size() ) return 0;
2191 return (nIndex == 0) ? rSizes.front() : (rSizes[ nIndex ] - rSizes[ nIndex - 1 ]);
2194 SCCOLROW ScHTMLTable::GetDocSize( ScHTMLOrient eOrient, SCCOLROW nCellBegin, SCCOLROW nCellEnd ) const
2196 const ScSizeVec& rSizes = maCumSizes[ eOrient ];
2197 size_t nBeginIdx = static_cast< size_t >( std::max< SCCOLROW >( nCellBegin, 0 ) );
2198 size_t nEndIdx = static_cast< size_t >( std::min< SCCOLROW >( nCellEnd, static_cast< SCCOLROW >( rSizes.size() ) ) );
2199 if (nBeginIdx >= nEndIdx ) return 0;
2200 return rSizes[ nEndIdx - 1 ] - ((nBeginIdx == 0) ? 0 : rSizes[ nBeginIdx - 1 ]);
2203 SCCOLROW ScHTMLTable::GetDocSize( ScHTMLOrient eOrient ) const
2205 const ScSizeVec& rSizes = maCumSizes[ eOrient ];
2206 return rSizes.empty() ? 0 : rSizes.back();
2209 ScHTMLSize ScHTMLTable::GetDocSize( const ScHTMLPos& rCellPos ) const
2211 ScHTMLSize aCellSpan = GetSpan( rCellPos );
2212 return ScHTMLSize(
2213 static_cast< SCCOL >( GetDocSize( tdCol, rCellPos.mnCol, rCellPos.mnCol + aCellSpan.mnCols ) ),
2214 static_cast< SCROW >( GetDocSize( tdRow, rCellPos.mnRow, rCellPos.mnRow + aCellSpan.mnRows ) ) );
2217 SCCOLROW ScHTMLTable::GetDocPos( ScHTMLOrient eOrient, SCCOLROW nCellPos ) const
2219 return maDocBasePos.Get( eOrient ) + GetDocSize( eOrient, 0, nCellPos );
2222 ScHTMLPos ScHTMLTable::GetDocPos( const ScHTMLPos& rCellPos ) const
2224 return ScHTMLPos(
2225 static_cast< SCCOL >( GetDocPos( tdCol, rCellPos.mnCol ) ),
2226 static_cast< SCROW >( GetDocPos( tdRow, rCellPos.mnRow ) ) );
2229 void ScHTMLTable::GetDocRange( ScRange& rRange ) const
2231 rRange.aStart = rRange.aEnd = maDocBasePos.MakeAddr();
2232 ScAddress aErrorPos( ScAddress::UNINITIALIZED );
2233 if (!rRange.aEnd.Move( static_cast< SCCOL >( GetDocSize( tdCol ) ) - 1,
2234 static_cast< SCROW >( GetDocSize( tdRow ) ) - 1, 0, aErrorPos, mrDoc ))
2236 assert(!"can't move");
2240 void ScHTMLTable::ApplyCellBorders( ScDocument* pDoc, const ScAddress& rFirstPos ) const
2242 OSL_ENSURE( pDoc, "ScHTMLTable::ApplyCellBorders - no document" );
2243 if( pDoc && mbBorderOn )
2245 const SCCOL nLastCol = maSize.mnCols - 1;
2246 const SCROW nLastRow = maSize.mnRows - 1;
2247 const tools::Long nOuterLine = SvxBorderLineWidth::Medium;
2248 const tools::Long nInnerLine = SvxBorderLineWidth::Hairline;
2249 SvxBorderLine aOuterLine(nullptr, nOuterLine, SvxBorderLineStyle::SOLID);
2250 SvxBorderLine aInnerLine(nullptr, nInnerLine, SvxBorderLineStyle::SOLID);
2251 SvxBoxItem aBorderItem( ATTR_BORDER );
2253 for( SCCOL nCol = 0; nCol <= nLastCol; ++nCol )
2255 SvxBorderLine* pLeftLine = (nCol == 0) ? &aOuterLine : &aInnerLine;
2256 SvxBorderLine* pRightLine = (nCol == nLastCol) ? &aOuterLine : &aInnerLine;
2257 SCCOL nCellCol1 = static_cast< SCCOL >( GetDocPos( tdCol, nCol ) ) + rFirstPos.Col();
2258 SCCOL nCellCol2 = nCellCol1 + static_cast< SCCOL >( GetDocSize( tdCol, nCol ) ) - 1;
2259 for( SCROW nRow = 0; nRow <= nLastRow; ++nRow )
2261 SvxBorderLine* pTopLine = (nRow == 0) ? &aOuterLine : &aInnerLine;
2262 SvxBorderLine* pBottomLine = (nRow == nLastRow) ? &aOuterLine : &aInnerLine;
2263 SCROW nCellRow1 = GetDocPos( tdRow, nRow ) + rFirstPos.Row();
2264 SCROW nCellRow2 = nCellRow1 + GetDocSize( tdRow, nRow ) - 1;
2265 for( SCCOL nCellCol = nCellCol1; nCellCol <= nCellCol2; ++nCellCol )
2267 aBorderItem.SetLine( (nCellCol == nCellCol1) ? pLeftLine : nullptr, SvxBoxItemLine::LEFT );
2268 aBorderItem.SetLine( (nCellCol == nCellCol2) ? pRightLine : nullptr, SvxBoxItemLine::RIGHT );
2269 for( SCROW nCellRow = nCellRow1; nCellRow <= nCellRow2; ++nCellRow )
2271 aBorderItem.SetLine( (nCellRow == nCellRow1) ? pTopLine : nullptr, SvxBoxItemLine::TOP );
2272 aBorderItem.SetLine( (nCellRow == nCellRow2) ? pBottomLine : nullptr, SvxBoxItemLine::BOTTOM );
2273 pDoc->ApplyAttr( nCellCol, nCellRow, rFirstPos.Tab(), aBorderItem );
2280 for( ScHTMLTableIterator aIter( mxNestedTables.get() ); aIter.is(); ++aIter )
2281 aIter->ApplyCellBorders( pDoc, rFirstPos );
2284 SvNumberFormatter* ScHTMLTable::GetFormatTable()
2286 return mpParser->GetDoc().GetFormatTable();
2289 bool ScHTMLTable::IsEmptyCell() const
2291 return mpCurrEntryVector && mpCurrEntryVector->empty();
2294 bool ScHTMLTable::IsSpaceCharInfo( const HtmlImportInfo& rInfo )
2296 return (rInfo.nToken == HtmlTokenId::TEXTTOKEN) && (rInfo.aText.getLength() == 1) && (rInfo.aText[ 0 ] == ' ');
2299 ScHTMLTable::ScHTMLEntryPtr ScHTMLTable::CreateEntry() const
2301 return std::make_unique<ScHTMLEntry>( GetCurrItemSet() );
2304 void ScHTMLTable::CreateNewEntry( const HtmlImportInfo& rInfo )
2306 OSL_ENSURE( !mxCurrEntry, "ScHTMLTable::CreateNewEntry - old entry still present" );
2307 mxCurrEntry = CreateEntry();
2308 mxCurrEntry->aSel = rInfo.aSelection;
2311 void ScHTMLTable::ImplPushEntryToVector( ScHTMLEntryVector& rEntryVector, ScHTMLEntryPtr& rxEntry )
2313 // HTML entry list does not own the entries
2314 rEntryVector.push_back( rxEntry.get() );
2315 // mrEEParseList (reference to member of ScEEParser) owns the entries
2316 mrEEParseList.push_back(std::shared_ptr<ScEEParseEntry>(rxEntry.release()));
2319 bool ScHTMLTable::PushEntry( ScHTMLEntryPtr& rxEntry )
2321 bool bPushed = false;
2322 if( rxEntry && rxEntry->HasContents() )
2324 if( mpCurrEntryVector )
2326 if( mbPushEmptyLine )
2328 ScHTMLEntryPtr xEmptyEntry = CreateEntry();
2329 ImplPushEntryToVector( *mpCurrEntryVector, xEmptyEntry );
2330 mbPushEmptyLine = false;
2332 ImplPushEntryToVector( *mpCurrEntryVector, rxEntry );
2333 bPushed = true;
2335 else if( mpParentTable )
2337 bPushed = mpParentTable->PushEntry( rxEntry );
2339 else
2341 OSL_FAIL( "ScHTMLTable::PushEntry - cannot push entry, no parent found" );
2344 return bPushed;
2347 bool ScHTMLTable::PushEntry( const HtmlImportInfo& rInfo, bool bLastInCell )
2349 OSL_ENSURE( mxCurrEntry, "ScHTMLTable::PushEntry - no current entry" );
2350 bool bPushed = false;
2351 if( mxCurrEntry )
2353 mxCurrEntry->AdjustEnd( rInfo );
2354 mxCurrEntry->Strip( mrEditEngine );
2356 // import entry always, if it is the last in cell, and cell is still empty
2357 if( bLastInCell && IsEmptyCell() )
2359 mxCurrEntry->SetImportAlways();
2360 // don't insert empty lines before single empty entries
2361 if( mxCurrEntry->IsEmpty() )
2362 mbPushEmptyLine = false;
2365 bPushed = PushEntry( mxCurrEntry );
2366 mxCurrEntry.reset();
2368 return bPushed;
2371 void ScHTMLTable::PushTableEntry( ScHTMLTableId nTableId )
2373 OSL_ENSURE( nTableId != SC_HTML_GLOBAL_TABLE, "ScHTMLTable::PushTableEntry - cannot push global table" );
2374 if( nTableId != SC_HTML_GLOBAL_TABLE )
2376 ScHTMLEntryPtr xEntry( new ScHTMLEntry( maTableItemSet, nTableId ) );
2377 PushEntry( xEntry );
2381 ScHTMLTable* ScHTMLTable::GetExistingTable( ScHTMLTableId nTableId ) const
2383 ScHTMLTable* pTable = ((nTableId != SC_HTML_GLOBAL_TABLE) && mxNestedTables) ?
2384 mxNestedTables->FindTable( nTableId, false ) : nullptr;
2385 OSL_ENSURE( pTable || (nTableId == SC_HTML_GLOBAL_TABLE), "ScHTMLTable::GetExistingTable - table not found" );
2386 return pTable;
2389 ScHTMLTable* ScHTMLTable::InsertNestedTable( const HtmlImportInfo& rInfo, bool bPreFormText )
2391 if( !mxNestedTables )
2392 mxNestedTables.reset( new ScHTMLTableMap( *this ) );
2393 if( bPreFormText ) // enclose new preformatted table with empty lines
2394 InsertLeadingEmptyLine();
2395 return mxNestedTables->CreateTable( rInfo, bPreFormText, mrDoc );
2398 void ScHTMLTable::InsertNewCell( const ScHTMLSize& rSpanSize )
2400 ScRange* pRange;
2402 /* Find an unused cell by skipping all merged ranges that cover the
2403 current cell position stored in maCurrCell. */
2404 for (;;)
2406 pRange = maVMergedCells.Find( maCurrCell.MakeAddr() );
2407 if (!pRange)
2408 pRange = maHMergedCells.Find( maCurrCell.MakeAddr() );
2409 if (!pRange)
2410 break;
2411 maCurrCell.mnCol = pRange->aEnd.Col() + 1;
2413 mpCurrEntryVector = &maEntryMap[ maCurrCell ];
2415 /* If the new cell is merged horizontally, try to find collisions with
2416 other vertically merged ranges. In this case, shrink existing
2417 vertically merged ranges (do not shrink the new cell). */
2418 SCCOL nColEnd = maCurrCell.mnCol + rSpanSize.mnCols;
2419 for( ScAddress aAddr( maCurrCell.MakeAddr() ); aAddr.Col() < nColEnd; aAddr.IncCol() )
2420 if( (pRange = maVMergedCells.Find( aAddr )) != nullptr )
2421 pRange->aEnd.SetRow( maCurrCell.mnRow - 1 );
2423 // insert the new range into the cell lists
2424 ScRange aNewRange( maCurrCell.MakeAddr() );
2425 ScAddress aErrorPos( ScAddress::UNINITIALIZED );
2426 if (!aNewRange.aEnd.Move( rSpanSize.mnCols - 1, rSpanSize.mnRows - 1, 0, aErrorPos, mrDoc ))
2428 assert(!"can't move");
2430 if( rSpanSize.mnRows > 1 )
2432 maVMergedCells.push_back( aNewRange );
2433 /* Do not insert vertically merged ranges into maUsedCells yet,
2434 because they may be shrunken (see above). The final vertically
2435 merged ranges are inserted in FillEmptyCells(). */
2437 else
2439 if( rSpanSize.mnCols > 1 )
2440 maHMergedCells.push_back( aNewRange );
2441 /* Insert horizontally merged ranges and single cells into
2442 maUsedCells, they will not be changed anymore. */
2443 maUsedCells.Join( aNewRange );
2446 // adjust table size
2447 maSize.mnCols = std::max< SCCOL >( maSize.mnCols, aNewRange.aEnd.Col() + 1 );
2448 maSize.mnRows = std::max< SCROW >( maSize.mnRows, aNewRange.aEnd.Row() + 1 );
2451 void ScHTMLTable::ImplRowOn()
2453 if( mbRowOn )
2454 ImplRowOff();
2455 moRowItemSet.emplace( maTableItemSet );
2456 maCurrCell.mnCol = 0;
2457 mbRowOn = true;
2458 mbDataOn = false;
2461 void ScHTMLTable::ImplRowOff()
2463 if( mbDataOn )
2464 ImplDataOff();
2465 if( mbRowOn )
2467 moRowItemSet.reset();
2468 ++maCurrCell.mnRow;
2469 mbRowOn = mbDataOn = false;
2473 void ScHTMLTable::ImplDataOn( const ScHTMLSize& rSpanSize )
2475 if( mbDataOn )
2476 ImplDataOff();
2477 if( !mbRowOn )
2478 ImplRowOn();
2479 moDataItemSet.emplace( *moRowItemSet );
2480 InsertNewCell( rSpanSize );
2481 mbDataOn = true;
2482 mbPushEmptyLine = false;
2485 void ScHTMLTable::ImplDataOff()
2487 if( mbDataOn )
2489 moDataItemSet.reset();
2490 ++maCurrCell.mnCol;
2491 mpCurrEntryVector = nullptr;
2492 mbDataOn = false;
2496 void ScHTMLTable::ProcessFormatOptions( SfxItemSet& rItemSet, const HtmlImportInfo& rInfo )
2498 // special handling for table header cells
2499 if( rInfo.nToken == HtmlTokenId::TABLEHEADER_ON )
2501 rItemSet.Put( SvxWeightItem( WEIGHT_BOLD, ATTR_FONT_WEIGHT ) );
2502 rItemSet.Put( SvxHorJustifyItem( SvxCellHorJustify::Center, ATTR_HOR_JUSTIFY ) );
2505 const HTMLOptions& rOptions = static_cast<HTMLParser*>(rInfo.pParser)->GetOptions();
2506 for (const auto& rOption : rOptions)
2508 switch( rOption.GetToken() )
2510 case HtmlOptionId::ALIGN:
2512 SvxCellHorJustify eVal = SvxCellHorJustify::Standard;
2513 const OUString& rOptVal = rOption.GetString();
2514 if( rOptVal.equalsIgnoreAsciiCase( OOO_STRING_SVTOOLS_HTML_AL_right ) )
2515 eVal = SvxCellHorJustify::Right;
2516 else if( rOptVal.equalsIgnoreAsciiCase( OOO_STRING_SVTOOLS_HTML_AL_center ) )
2517 eVal = SvxCellHorJustify::Center;
2518 else if( rOptVal.equalsIgnoreAsciiCase( OOO_STRING_SVTOOLS_HTML_AL_left ) )
2519 eVal = SvxCellHorJustify::Left;
2520 if( eVal != SvxCellHorJustify::Standard )
2521 rItemSet.Put( SvxHorJustifyItem( eVal, ATTR_HOR_JUSTIFY ) );
2523 break;
2525 case HtmlOptionId::VALIGN:
2527 SvxCellVerJustify eVal = SvxCellVerJustify::Standard;
2528 const OUString& rOptVal = rOption.GetString();
2529 if( rOptVal.equalsIgnoreAsciiCase( OOO_STRING_SVTOOLS_HTML_VA_top ) )
2530 eVal = SvxCellVerJustify::Top;
2531 else if( rOptVal.equalsIgnoreAsciiCase( OOO_STRING_SVTOOLS_HTML_VA_middle ) )
2532 eVal = SvxCellVerJustify::Center;
2533 else if( rOptVal.equalsIgnoreAsciiCase( OOO_STRING_SVTOOLS_HTML_VA_bottom ) )
2534 eVal = SvxCellVerJustify::Bottom;
2535 if( eVal != SvxCellVerJustify::Standard )
2536 rItemSet.Put( SvxVerJustifyItem( eVal, ATTR_VER_JUSTIFY ) );
2538 break;
2540 case HtmlOptionId::BGCOLOR:
2542 Color aColor;
2543 rOption.GetColor( aColor );
2544 rItemSet.Put( SvxBrushItem( aColor, ATTR_BACKGROUND ) );
2546 break;
2547 default: break;
2552 void ScHTMLTable::SetDocSize( ScHTMLOrient eOrient, SCCOLROW nCellPos, SCCOLROW nSize )
2554 OSL_ENSURE( nCellPos >= 0, "ScHTMLTable::SetDocSize - unexpected negative position" );
2555 ScSizeVec& rSizes = maCumSizes[ eOrient ];
2556 size_t nIndex = static_cast< size_t >( nCellPos );
2557 // expand with height/width == 1
2558 while( nIndex >= rSizes.size() )
2559 rSizes.push_back( rSizes.empty() ? 1 : (rSizes.back() + 1) );
2560 // update size of passed position and all following
2561 // #i109987# only grow, don't shrink - use the largest needed size
2562 SCCOLROW nDiff = nSize - ((nIndex == 0) ? rSizes.front() : (rSizes[ nIndex ] - rSizes[ nIndex - 1 ]));
2563 if( nDiff > 0 )
2564 std::for_each(rSizes.begin() + nIndex, rSizes.end(), [&nDiff](SCCOLROW& rSize) { rSize += nDiff; });
2567 void ScHTMLTable::CalcNeededDocSize(
2568 ScHTMLOrient eOrient, SCCOLROW nCellPos, SCCOLROW nCellSpan, SCCOLROW nRealDocSize )
2570 SCCOLROW nDiffSize = 0;
2571 // in merged columns/rows: reduce needed size by size of leading columns
2572 while( nCellSpan > 1 )
2574 nDiffSize += GetDocSize( eOrient, nCellPos );
2575 --nCellSpan;
2576 ++nCellPos;
2578 // set remaining needed size to last column/row
2579 nRealDocSize -= std::min< SCCOLROW >( nRealDocSize - 1, nDiffSize );
2580 SetDocSize( eOrient, nCellPos, nRealDocSize );
2583 void ScHTMLTable::FillEmptyCells()
2585 for( ScHTMLTableIterator aIter( mxNestedTables.get() ); aIter.is(); ++aIter )
2586 aIter->FillEmptyCells();
2588 // insert the final vertically merged ranges into maUsedCells
2589 for ( size_t i = 0, nRanges = maVMergedCells.size(); i < nRanges; ++i )
2591 ScRange & rRange = maVMergedCells[ i ];
2592 maUsedCells.Join( rRange );
2595 for( ScAddress aAddr; aAddr.Row() < maSize.mnRows; aAddr.IncRow() )
2597 for( aAddr.SetCol( 0 ); aAddr.Col() < maSize.mnCols; aAddr.IncCol() )
2599 if( !maUsedCells.Find( aAddr ) )
2601 // create a range for the lock list (used to calc. cell span)
2602 ScRange aRange( aAddr );
2605 aRange.aEnd.IncCol();
2607 while( (aRange.aEnd.Col() < maSize.mnCols) && !maUsedCells.Find( aRange.aEnd ) );
2608 aRange.aEnd.IncCol( -1 );
2609 maUsedCells.Join( aRange );
2611 // insert a dummy entry
2612 ScHTMLEntryPtr xEntry = CreateEntry();
2613 ImplPushEntryToVector( maEntryMap[ ScHTMLPos( aAddr ) ], xEntry );
2619 void ScHTMLTable::RecalcDocSize()
2621 // recalc table sizes recursively from inner to outer
2622 for( ScHTMLTableIterator aIter( mxNestedTables.get() ); aIter.is(); ++aIter )
2623 aIter->RecalcDocSize();
2625 /* Two passes: first calculates the sizes of single columns/rows, then
2626 the sizes of spanned columns/rows. This allows to fill nested tables
2627 into merged cells optimally. */
2628 static const sal_uInt16 PASS_SINGLE = 0;
2629 static const sal_uInt16 PASS_SPANNED = 1;
2630 for( sal_uInt16 nPass = PASS_SINGLE; nPass <= PASS_SPANNED; ++nPass )
2632 // iterate through every table cell
2633 for( const auto& [rCellPos, rEntryVector] : maEntryMap )
2635 ScHTMLSize aCellSpan = GetSpan( rCellPos );
2637 // process the dimension of the current cell in this pass?
2638 // (pass is single and span is 1) or (pass is not single and span is not 1)
2639 bool bProcessColWidth = ((nPass == PASS_SINGLE) == (aCellSpan.mnCols == 1));
2640 bool bProcessRowHeight = ((nPass == PASS_SINGLE) == (aCellSpan.mnRows == 1));
2641 if( bProcessColWidth || bProcessRowHeight )
2643 ScHTMLSize aDocSize( 1, 0 ); // resulting size of the cell in document
2645 // expand the cell size for each cell parse entry
2646 for( const auto& rpEntry : rEntryVector )
2648 ScHTMLTable* pTable = GetExistingTable( rpEntry->GetTableId() );
2649 // find entry with maximum width
2650 if( bProcessColWidth && pTable )
2651 aDocSize.mnCols = std::max( aDocSize.mnCols, static_cast< SCCOL >( pTable->GetDocSize( tdCol ) ) );
2652 // add up height of each entry
2653 if( bProcessRowHeight )
2654 aDocSize.mnRows += pTable ? pTable->GetDocSize( tdRow ) : 1;
2656 if( !aDocSize.mnRows )
2657 aDocSize.mnRows = 1;
2659 if( bProcessColWidth )
2660 CalcNeededDocSize( tdCol, rCellPos.mnCol, aCellSpan.mnCols, aDocSize.mnCols );
2661 if( bProcessRowHeight )
2662 CalcNeededDocSize( tdRow, rCellPos.mnRow, aCellSpan.mnRows, aDocSize.mnRows );
2668 void ScHTMLTable::RecalcDocPos( const ScHTMLPos& rBasePos )
2670 maDocBasePos = rBasePos;
2671 // after the previous assignment it is allowed to call GetDocPos() methods
2673 // iterate through every table cell
2674 for( auto& [rCellPos, rEntryVector] : maEntryMap )
2676 // fixed doc position of the entire cell (first entry)
2677 const ScHTMLPos aCellDocPos( GetDocPos( rCellPos ) );
2678 // fixed doc size of the entire cell
2679 const ScHTMLSize aCellDocSize( GetDocSize( rCellPos ) );
2681 // running doc position for single entries
2682 ScHTMLPos aEntryDocPos( aCellDocPos );
2684 ScHTMLEntry* pEntry = nullptr;
2685 for( const auto& rpEntry : rEntryVector )
2687 pEntry = rpEntry;
2688 if( ScHTMLTable* pTable = GetExistingTable( pEntry->GetTableId() ) )
2690 pTable->RecalcDocPos( aEntryDocPos ); // recalc nested table
2691 pEntry->nCol = SCCOL_MAX;
2692 pEntry->nRow = SCROW_MAX;
2693 SCROW nTableRows = static_cast< SCROW >( pTable->GetDocSize( tdRow ) );
2695 // use this entry to pad empty space right of table
2696 if( mpParentTable ) // ... but not in global table
2698 SCCOL nStartCol = aEntryDocPos.mnCol + static_cast< SCCOL >( pTable->GetDocSize( tdCol ) );
2699 SCCOL nNextCol = aEntryDocPos.mnCol + aCellDocSize.mnCols;
2700 if( nStartCol < nNextCol )
2702 pEntry->nCol = nStartCol;
2703 pEntry->nRow = aEntryDocPos.mnRow;
2704 pEntry->nColOverlap = nNextCol - nStartCol;
2705 pEntry->nRowOverlap = nTableRows;
2708 aEntryDocPos.mnRow += nTableRows;
2710 else
2712 pEntry->nCol = aEntryDocPos.mnCol;
2713 pEntry->nRow = aEntryDocPos.mnRow;
2714 if( mpParentTable ) // do not merge in global table
2715 pEntry->nColOverlap = aCellDocSize.mnCols;
2716 ++aEntryDocPos.mnRow;
2720 // pEntry points now to last entry.
2721 if( pEntry )
2723 if( (pEntry == rEntryVector.front()) && (pEntry->GetTableId() == SC_HTML_NO_TABLE) )
2725 // pEntry is the only entry in this cell - merge rows of cell with single non-table entry.
2726 pEntry->nRowOverlap = aCellDocSize.mnRows;
2728 else
2730 // fill up incomplete entry lists
2731 SCROW nFirstUnusedRow = aCellDocPos.mnRow + aCellDocSize.mnRows;
2732 while( aEntryDocPos.mnRow < nFirstUnusedRow )
2734 ScHTMLEntryPtr xDummyEntry( new ScHTMLEntry( pEntry->GetItemSet() ) );
2735 xDummyEntry->nCol = aEntryDocPos.mnCol;
2736 xDummyEntry->nRow = aEntryDocPos.mnRow;
2737 xDummyEntry->nColOverlap = aCellDocSize.mnCols;
2738 ImplPushEntryToVector( rEntryVector, xDummyEntry );
2739 ++aEntryDocPos.mnRow;
2746 ScHTMLGlobalTable::ScHTMLGlobalTable(
2747 SfxItemPool& rPool,
2748 EditEngine& rEditEngine,
2749 std::vector<std::shared_ptr<ScEEParseEntry>>& rEEParseVector,
2750 ScHTMLTableId& rnUnusedId,
2751 ScHTMLParser* pParser,
2752 const ScDocument& rDoc
2754 ScHTMLTable( rPool, rEditEngine, rEEParseVector, rnUnusedId, pParser, rDoc )
2758 ScHTMLGlobalTable::~ScHTMLGlobalTable()
2762 void ScHTMLGlobalTable::Recalc()
2764 // Fills up empty cells with a dummy entry. */
2765 FillEmptyCells();
2766 // recalc table sizes of all nested tables and this table
2767 RecalcDocSize();
2768 // recalc document positions of all entries in this table and in nested tables
2769 RecalcDocPos( GetDocPos() );
2772 ScHTMLQueryParser::ScHTMLQueryParser( EditEngine* pEditEngine, ScDocument* pDoc ) :
2773 ScHTMLParser( pEditEngine, pDoc ),
2774 mnUnusedId( SC_HTML_GLOBAL_TABLE ),
2775 mbTitleOn( false )
2777 mxGlobTable.reset(
2778 new ScHTMLGlobalTable(*pPool, *pEdit, maList, mnUnusedId, this, *pDoc));
2779 mpCurrTable = mxGlobTable.get();
2782 ScHTMLQueryParser::~ScHTMLQueryParser()
2786 ErrCode ScHTMLQueryParser::Read( SvStream& rStrm, const OUString& rBaseURL )
2788 SvKeyValueIteratorRef xValues;
2789 SvKeyValueIterator* pAttributes = nullptr;
2791 SfxObjectShell* pObjSh = mpDoc->GetDocumentShell();
2792 if( pObjSh && pObjSh->IsLoading() )
2794 pAttributes = pObjSh->GetHeaderAttributes();
2796 else
2798 /* When not loading, set up fake HTTP headers to force the SfxHTMLParser
2799 to use UTF8 (used when pasting from clipboard) */
2800 const char* pCharSet = rtl_getBestMimeCharsetFromTextEncoding( RTL_TEXTENCODING_UTF8 );
2801 if( pCharSet )
2803 OUString aContentType = "text/html; charset=" +
2804 OUString::createFromAscii( pCharSet );
2806 xValues = new SvKeyValueIterator;
2807 xValues->Append( SvKeyValue( OOO_STRING_SVTOOLS_HTML_META_content_type, aContentType ) );
2808 pAttributes = xValues.get();
2812 Link<HtmlImportInfo&,void> aOldLink = pEdit->GetHtmlImportHdl();
2813 pEdit->SetHtmlImportHdl( LINK( this, ScHTMLQueryParser, HTMLImportHdl ) );
2814 ErrCode nErr = pEdit->Read( rStrm, rBaseURL, EETextFormat::Html, pAttributes );
2815 pEdit->SetHtmlImportHdl( aOldLink );
2817 mxGlobTable->Recalc();
2818 nColMax = static_cast< SCCOL >( mxGlobTable->GetDocSize( tdCol ) - 1 );
2819 nRowMax = static_cast< SCROW >( mxGlobTable->GetDocSize( tdRow ) - 1 );
2821 return nErr;
2824 const ScHTMLTable* ScHTMLQueryParser::GetGlobalTable() const
2826 return mxGlobTable.get();
2829 void ScHTMLQueryParser::ProcessToken( const HtmlImportInfo& rInfo )
2831 switch( rInfo.nToken )
2833 // --- meta data ---
2834 case HtmlTokenId::META: MetaOn( rInfo ); break; // <meta>
2836 // --- title handling ---
2837 case HtmlTokenId::TITLE_ON: TitleOn(); break; // <title>
2838 case HtmlTokenId::TITLE_OFF: TitleOff( rInfo ); break; // </title>
2840 case HtmlTokenId::STYLE_ON: break;
2841 case HtmlTokenId::STYLE_OFF: ParseStyle(rInfo.aText); break;
2843 // --- body handling ---
2844 case HtmlTokenId::BODY_ON: mpCurrTable->BodyOn( rInfo ); break; // <body>
2845 case HtmlTokenId::BODY_OFF: mpCurrTable->BodyOff( rInfo ); break; // </body>
2847 // --- insert text ---
2848 case HtmlTokenId::TEXTTOKEN: InsertText( rInfo ); break; // any text
2849 case HtmlTokenId::LINEBREAK: mpCurrTable->BreakOn(); break; // <br>
2850 case HtmlTokenId::HEAD1_ON: // <h1>
2851 case HtmlTokenId::HEAD2_ON: // <h2>
2852 case HtmlTokenId::HEAD3_ON: // <h3>
2853 case HtmlTokenId::HEAD4_ON: // <h4>
2854 case HtmlTokenId::HEAD5_ON: // <h5>
2855 case HtmlTokenId::HEAD6_ON: // <h6>
2856 case HtmlTokenId::PARABREAK_ON: mpCurrTable->HeadingOn(); break; // <p>
2858 // --- misc. contents ---
2859 case HtmlTokenId::ANCHOR_ON: mpCurrTable->AnchorOn(); break; // <a>
2861 // --- table handling ---
2862 case HtmlTokenId::TABLE_ON: TableOn( rInfo ); break; // <table>
2863 case HtmlTokenId::TABLE_OFF: TableOff( rInfo ); break; // </table>
2864 case HtmlTokenId::CAPTION_ON: mpCurrTable->CaptionOn(); break; // <caption>
2865 case HtmlTokenId::CAPTION_OFF: mpCurrTable->CaptionOff(); break; // </caption>
2866 case HtmlTokenId::TABLEROW_ON: mpCurrTable->RowOn( rInfo ); break; // <tr>
2867 case HtmlTokenId::TABLEROW_OFF: mpCurrTable->RowOff( rInfo ); break; // </tr>
2868 case HtmlTokenId::TABLEHEADER_ON: // <th>
2869 case HtmlTokenId::TABLEDATA_ON: mpCurrTable->DataOn( rInfo ); break; // <td>
2870 case HtmlTokenId::TABLEHEADER_OFF: // </th>
2871 case HtmlTokenId::TABLEDATA_OFF: mpCurrTable->DataOff( rInfo ); break; // </td>
2872 case HtmlTokenId::PREFORMTXT_ON: PreOn( rInfo ); break; // <pre>
2873 case HtmlTokenId::PREFORMTXT_OFF: PreOff( rInfo ); break; // </pre>
2875 // --- formatting ---
2876 case HtmlTokenId::FONT_ON: FontOn( rInfo ); break; // <font>
2878 case HtmlTokenId::BIGPRINT_ON: // <big>
2879 //! TODO: store current font size, use following size
2880 mpCurrTable->PutItem( SvxFontHeightItem( maFontHeights[ 3 ], 100, ATTR_FONT_HEIGHT ) );
2881 break;
2882 case HtmlTokenId::SMALLPRINT_ON: // <small>
2883 //! TODO: store current font size, use preceding size
2884 mpCurrTable->PutItem( SvxFontHeightItem( maFontHeights[ 0 ], 100, ATTR_FONT_HEIGHT ) );
2885 break;
2887 case HtmlTokenId::BOLD_ON: // <b>
2888 case HtmlTokenId::STRONG_ON: // <strong>
2889 mpCurrTable->PutItem( SvxWeightItem( WEIGHT_BOLD, ATTR_FONT_WEIGHT ) );
2890 break;
2892 case HtmlTokenId::ITALIC_ON: // <i>
2893 case HtmlTokenId::EMPHASIS_ON: // <em>
2894 case HtmlTokenId::ADDRESS_ON: // <address>
2895 case HtmlTokenId::BLOCKQUOTE_ON: // <blockquote>
2896 case HtmlTokenId::BLOCKQUOTE30_ON: // <bq>
2897 case HtmlTokenId::CITATION_ON: // <cite>
2898 case HtmlTokenId::VARIABLE_ON: // <var>
2899 mpCurrTable->PutItem( SvxPostureItem( ITALIC_NORMAL, ATTR_FONT_POSTURE ) );
2900 break;
2902 case HtmlTokenId::DEFINSTANCE_ON: // <dfn>
2903 mpCurrTable->PutItem( SvxWeightItem( WEIGHT_BOLD, ATTR_FONT_WEIGHT ) );
2904 mpCurrTable->PutItem( SvxPostureItem( ITALIC_NORMAL, ATTR_FONT_POSTURE ) );
2905 break;
2907 case HtmlTokenId::UNDERLINE_ON: // <u>
2908 mpCurrTable->PutItem( SvxUnderlineItem( LINESTYLE_SINGLE, ATTR_FONT_UNDERLINE ) );
2909 break;
2910 default: break;
2914 void ScHTMLQueryParser::InsertText( const HtmlImportInfo& rInfo )
2916 mpCurrTable->PutText( rInfo );
2917 if( mbTitleOn )
2918 maTitle.append(rInfo.aText);
2921 void ScHTMLQueryParser::FontOn( const HtmlImportInfo& rInfo )
2923 const HTMLOptions& rOptions = static_cast<HTMLParser*>(rInfo.pParser)->GetOptions();
2924 for (const auto& rOption : rOptions)
2926 switch( rOption.GetToken() )
2928 case HtmlOptionId::FACE :
2930 const OUString& rFace = rOption.GetString();
2931 OUString aFontName;
2932 sal_Int32 nPos = 0;
2933 while( nPos != -1 )
2935 // font list separator: VCL = ';' HTML = ','
2936 OUString aFName = comphelper::string::strip(rFace.getToken(0, ',', nPos), ' ');
2937 aFontName = ScGlobal::addToken(aFontName, aFName, ';');
2939 if ( !aFontName.isEmpty() )
2940 mpCurrTable->PutItem( SvxFontItem( FAMILY_DONTKNOW,
2941 aFontName, OUString(), PITCH_DONTKNOW,
2942 RTL_TEXTENCODING_DONTKNOW, ATTR_FONT ) );
2944 break;
2945 case HtmlOptionId::SIZE :
2947 sal_uInt32 nSize = getLimitedValue< sal_uInt32 >( rOption.GetNumber(), 1, SC_HTML_FONTSIZES );
2948 mpCurrTable->PutItem( SvxFontHeightItem( maFontHeights[ nSize - 1 ], 100, ATTR_FONT_HEIGHT ) );
2950 break;
2951 case HtmlOptionId::COLOR :
2953 Color aColor;
2954 rOption.GetColor( aColor );
2955 mpCurrTable->PutItem( SvxColorItem( aColor, ATTR_FONT_COLOR ) );
2957 break;
2958 default: break;
2963 void ScHTMLQueryParser::MetaOn( const HtmlImportInfo& rInfo )
2965 if( mpDoc->GetDocumentShell() )
2967 HTMLParser* pParser = static_cast< HTMLParser* >( rInfo.pParser );
2969 uno::Reference<document::XDocumentPropertiesSupplier> xDPS(
2970 mpDoc->GetDocumentShell()->GetModel(), uno::UNO_QUERY_THROW);
2971 pParser->ParseMetaOptions(
2972 xDPS->getDocumentProperties(),
2973 mpDoc->GetDocumentShell()->GetHeaderAttributes() );
2977 void ScHTMLQueryParser::TitleOn()
2979 mbTitleOn = true;
2980 maTitle.setLength(0);
2983 void ScHTMLQueryParser::TitleOff( const HtmlImportInfo& rInfo )
2985 if( !mbTitleOn )
2986 return;
2988 OUString aTitle = maTitle.makeStringAndClear().trim();
2989 if (!aTitle.isEmpty() && mpDoc->GetDocumentShell())
2991 uno::Reference<document::XDocumentPropertiesSupplier> xDPS(
2992 mpDoc->GetDocumentShell()->GetModel(), uno::UNO_QUERY_THROW);
2994 xDPS->getDocumentProperties()->setTitle(aTitle);
2996 InsertText( rInfo );
2997 mbTitleOn = false;
3000 void ScHTMLQueryParser::TableOn( const HtmlImportInfo& rInfo )
3002 mpCurrTable = mpCurrTable->TableOn( rInfo );
3005 void ScHTMLQueryParser::TableOff( const HtmlImportInfo& rInfo )
3007 mpCurrTable = mpCurrTable->TableOff( rInfo );
3010 void ScHTMLQueryParser::PreOn( const HtmlImportInfo& rInfo )
3012 mpCurrTable = mpCurrTable->PreOn( rInfo );
3015 void ScHTMLQueryParser::PreOff( const HtmlImportInfo& rInfo )
3017 mpCurrTable = mpCurrTable->PreOff( rInfo );
3020 void ScHTMLQueryParser::CloseTable( const HtmlImportInfo& rInfo )
3022 mpCurrTable = mpCurrTable->CloseTable( rInfo );
3025 namespace {
3028 * Handler class for the CSS parser.
3030 class CSSHandler: public orcus::css_handler
3032 struct MemStr
3034 const char* mp;
3035 size_t mn;
3037 MemStr() : mp(nullptr), mn(0) {}
3038 MemStr(const char* p, size_t n) : mp(p), mn(n) {}
3039 MemStr(const MemStr& r) : mp(r.mp), mn(r.mn) {}
3040 MemStr& operator=(const MemStr& r) = default;
3043 typedef std::pair<MemStr, MemStr> SelectorName; // element : class
3044 typedef std::vector<SelectorName> SelectorNames;
3046 SelectorNames maSelectorNames; // current selector names
3047 MemStr maPropName; // current property name.
3048 MemStr maPropValue; // current property value.
3049 ScHTMLStyles& mrStyles;
3051 public:
3052 explicit CSSHandler(ScHTMLStyles& rStyles):
3053 maPropName(),
3054 maPropValue(),
3055 mrStyles(rStyles)
3058 // selector name starting with "@"
3059 static void at_rule_name(const char* /*p*/, size_t /*n*/)
3061 // TODO: For now, we ignore at-rule properties
3064 // selector name not starting with "." or "#" (i.e. element selectors)
3065 void simple_selector_type(const char* pElem, size_t nElem)
3067 MemStr aElem(pElem, nElem); // element given
3068 MemStr aClass(nullptr, 0); // class name not given - to be added in the "element global" storage
3069 SelectorName aName(aElem, aClass);
3071 maSelectorNames.push_back(aName);
3074 // selector names starting with a "." (i.e. class selector)
3075 void simple_selector_class(const char* pClassName, size_t nClassName)
3077 MemStr aElem(nullptr, 0); // no element given - should be added in the "global" storage
3078 MemStr aClass(pClassName, nClassName);
3079 SelectorName aName(aElem, aClass);
3081 maSelectorNames.push_back(aName);
3084 // TODO: Add other selectors
3086 void property_name(const char* p, size_t n)
3088 maPropName = MemStr(p, n);
3091 void value(const char* p, size_t n)
3093 maPropValue = MemStr(p, n);
3096 void end_block() {
3097 maSelectorNames.clear();
3100 void end_property()
3102 SelectorNames::const_iterator itr = maSelectorNames.begin(), itrEnd = maSelectorNames.end();
3103 for (; itr != itrEnd; ++itr)
3105 // Add this property to the collection for each selector.
3106 const SelectorName& rSelName = *itr;
3107 const MemStr& rElem = rSelName.first;
3108 const MemStr& rClass = rSelName.second;
3109 OUString aName(maPropName.mp, maPropName.mn, RTL_TEXTENCODING_UTF8);
3110 OUString aValue(maPropValue.mp, maPropValue.mn, RTL_TEXTENCODING_UTF8);
3111 mrStyles.add(rElem.mp, rElem.mn, rClass.mp, rClass.mn, aName, aValue);
3113 maPropName = MemStr();
3114 maPropValue = MemStr();
3121 void ScHTMLQueryParser::ParseStyle(std::u16string_view rStrm)
3123 OString aStr = OUStringToOString(rStrm, RTL_TEXTENCODING_UTF8);
3124 CSSHandler aHdl(GetStyles());
3125 orcus::css_parser<CSSHandler> aParser(aStr.getStr(), aStr.getLength(), aHdl);
3128 aParser.parse();
3130 catch (const orcus::css::parse_error& rOrcusParseError)
3132 SAL_WARN("sc", "ScHTMLQueryParser::ParseStyle: " << rOrcusParseError.what());
3133 // TODO: Parsing of CSS failed. Do nothing for now.
3137 IMPL_LINK( ScHTMLQueryParser, HTMLImportHdl, HtmlImportInfo&, rInfo, void )
3139 switch( rInfo.eState )
3141 case HtmlImportState::Start:
3142 break;
3144 case HtmlImportState::NextToken:
3145 ProcessToken( rInfo );
3146 break;
3148 case HtmlImportState::InsertPara:
3149 mpCurrTable->InsertPara( rInfo );
3150 break;
3152 case HtmlImportState::SetAttr:
3153 case HtmlImportState::InsertText:
3154 case HtmlImportState::InsertField:
3155 break;
3157 case HtmlImportState::End:
3158 while( mpCurrTable->GetTableId() != SC_HTML_GLOBAL_TABLE )
3159 CloseTable( rInfo );
3160 break;
3162 default:
3163 OSL_FAIL( "ScHTMLQueryParser::HTMLImportHdl - unknown ImportInfo::eState" );
3167 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */