    /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
    /*
     * This file is part of the LibreOffice project.
     *
     * This Source Code Form is subject to the terms of the Mozilla Public
     * License, v. 2.0. If a copy of the MPL was not distributed with this
     * file, You can obtain one at http://mozilla.org/MPL/2.0/.
     *
     * This file incorporates work covered by the following license notice:
     *
     *   Licensed to the Apache Software Foundation (ASF) under one or more
     *   contributor license agreements. See the NOTICE file distributed
     *   with this work for additional information regarding copyright
     *   ownership. The ASF licenses this file to you under the Apache
     *   License, Version 2.0 (the "License"); you may not use this file
     *   except in compliance with the License. You may obtain a copy of
     *   the License at http://www.apache.org/licenses/LICENSE-2.0 .
     */

    #include <memory>
    #include <sal/config.h>

    #include <comphelper/string.hxx>

    #include <scitems.hxx>
    #include <editeng/eeitem.hxx>

    #include <svtools/htmlcfg.hxx>
    #include <svx/algitem.hxx>
    #include <editeng/colritem.hxx>
    #include <editeng/brushitem.hxx>
    #include <editeng/editeng.hxx>
    #include <editeng/fhgtitem.hxx>
    #include <editeng/fontitem.hxx>
    #include <editeng/postitem.hxx>
    #include <editeng/udlnitem.hxx>
    #include <editeng/wghtitem.hxx>
    #include <editeng/boxitem.hxx>
    #include <editeng/justifyitem.hxx>
    #include <sfx2/objsh.hxx>
    #include <svl/eitem.hxx>
    #include <svl/intitem.hxx>
    #include <vcl/graphicfilter.hxx>
    #include <svtools/parhtml.hxx>
    #include <svtools/htmlkywd.hxx>
    #include <svtools/htmltokn.h>
    #include <sfx2/docfile.hxx>

    #include <vcl/svapp.hxx>
    #include <tools/urlobj.hxx>
    #include <tools/tenccvt.hxx>

    #include <rtl/tencinfo.h>

    #include <htmlpars.hxx>
    #include <global.hxx>
    #include <document.hxx>
    #include <rangelst.hxx>

    #include <o3tl/make_unique.hxx>
    #include <orcus/css_parser.hpp>

    #include <com/sun/star/document/XDocumentProperties.hpp>
    #include <com/sun/star/document/XDocumentPropertiesSupplier.hpp>
    #include <utility>

    using ::editeng::SvxBorderLine;
    using namespace ::com::sun::star;

    ScHTMLStyles::ScHTMLStyles() : maEmpty() {}

    void ScHTMLStyles::add(const char* pElemName, size_t nElemName, const char* pClassName, size_t nClassName,
                           const OUString& aProp, const OUString& aValue)
    {
        if (pElemName)
        {
            OUString aElem(pElemName, nElemName, RTL_TEXTENCODING_UTF8);
            aElem = aElem.toAsciiLowerCase();
            if (pClassName)
            {
                // Both element and class names given.
                ElemsType::iterator itrElem = m_ElemProps.find(aElem);
                if (itrElem == m_ElemProps.end())
                {
                    // new element
                    std::pair<ElemsType::iterator, bool> r =
                        m_ElemProps.insert(std::make_pair(aElem, o3tl::make_unique<NamePropsType>()));
                    if (!r.second)
                        // insertion failed.
                        return;
                    itrElem = r.first;
                }

                NamePropsType *const pClsProps = itrElem->second.get();
                OUString aClass(pClassName, nClassName, RTL_TEXTENCODING_UTF8);
                aClass = aClass.toAsciiLowerCase();
                insertProp(*pClsProps, aClass, aProp, aValue);
            }
            else
            {
                // Element name only. Add it to the element global.
                insertProp(m_ElemGlobalProps, aElem, aProp, aValue);
            }
        }
        else
        {
            if (pClassName)
            {
                // Class name only. Add it to the global.
                OUString aClass(pClassName, nClassName, RTL_TEXTENCODING_UTF8);
                aClass = aClass.toAsciiLowerCase();
                insertProp(m_GlobalProps, aClass, aProp, aValue);
            }
        }
    }

    const OUString& ScHTMLStyles::getPropertyValue(
        const OUString& rElem, const OUString& rClass, const OUString& rPropName) const
    {
        // First, look into the element-class storage.
        {
            auto const itr = m_ElemProps.find(rElem);
            if (itr != m_ElemProps.end())
            {
                const NamePropsType *const pClasses = itr->second.get();
                NamePropsType::const_iterator itr2 = pClasses->find(rClass);
                if (itr2 != pClasses->end())
                {
                    const PropsType *const pProps = itr2->second.get();
                    PropsType::const_iterator itr3 = pProps->find(rPropName);
                    if (itr3 != pProps->end())
                        return itr3->second;
                }
            }
        }
        // Next, look into the class global storage.
        {
            auto const itr = m_GlobalProps.find(rClass);
            if (itr != m_GlobalProps.end())
            {
                const PropsType *const pProps = itr->second.get();
                PropsType::const_iterator itr2 = pProps->find(rPropName);
                if (itr2 != pProps->end())
                    return itr2->second;
            }
        }
        // As the last resort, look into the element global storage.
        {
            auto const itr = m_ElemGlobalProps.find(rClass);
            if (itr != m_ElemGlobalProps.end())
            {
                const PropsType *const pProps = itr->second.get();
                PropsType::const_iterator itr2 = pProps->find(rPropName);
                if (itr2 != pProps->end())
                    return itr2->second;
            }
        }

        return maEmpty; // nothing found.
    }

    void ScHTMLStyles::insertProp(
        NamePropsType& rStore, const OUString& aName,
        const OUString& aProp, const OUString& aValue)
    {
        NamePropsType::iterator itr = rStore.find(aName);
        if (itr == rStore.end())
        {
            // new element
            std::pair<NamePropsType::iterator, bool> r =
                rStore.insert(std::make_pair(aName, o3tl::make_unique<PropsType>()));
            if (!r.second)
                // insertion failed.
                return;

            itr = r.first;
        }

        PropsType *const pProps = itr->second.get();
        pProps->emplace(aProp, aValue);
    }

    // BASE class for HTML parser classes

    ScHTMLParser::ScHTMLParser( EditEngine* pEditEngine, ScDocument* pDoc ) :
        ScEEParser( pEditEngine ),
        mpDoc( pDoc )
    {
        SvxHtmlOptions& rHtmlOptions = SvxHtmlOptions::Get();
        for( sal_uInt16 nIndex = 0; nIndex < SC_HTML_FONTSIZES; ++nIndex )
            maFontHeights[ nIndex ] = rHtmlOptions.GetFontSize( nIndex ) * 20;
    }

    ScHTMLParser::~ScHTMLParser()
    {
    }

    ScHTMLLayoutParser::ScHTMLLayoutParser(
        EditEngine* pEditP, const OUString& rBaseURL, const Size& aPageSizeP,
        ScDocument* pDocP ) :
            ScHTMLParser( pEditP, pDocP ),
            aPageSize( aPageSizeP ),
            aBaseURL( rBaseURL ),
            xLockedList( new ScRangeList ),
            pLocalColOffset( new ScHTMLColOffset ),
            nFirstTableCell(0),
            nTableLevel(0),
            nTable(0),
            nMaxTable(0),
            nColCntStart(0),
            nMaxCol(0),
            nTableWidth(0),
            nColOffset(0),
            nColOffsetStart(0),
            nOffsetTolerance( SC_HTML_OFFSET_TOLERANCE_SMALL ),
            bFirstRow( true ),
            bTabInTabCell( false ),
            bInCell( false ),
            bInTitle( false )
    {
        MakeColNoRef( pLocalColOffset.get(), 0, 0, 0, 0 );
        MakeColNoRef( &maColOffset, 0, 0, 0, 0 );
    }

    ScHTMLLayoutParser::~ScHTMLLayoutParser()
    {
        while ( !aTableStack.empty() )
            aTableStack.pop();
        pLocalColOffset.reset();
        if ( pTables )
        {
            for( OuterMap::const_iterator it = pTables->begin(); it != pTables->end(); ++it)
                delete it->second;
            pTables.reset();
        }
    }

    ErrCode ScHTMLLayoutParser::Read( SvStream& rStream, const OUString& rBaseURL )
    {
        Link<HtmlImportInfo&,void> aOldLink = pEdit->GetHtmlImportHdl();
        pEdit->SetHtmlImportHdl( LINK( this, ScHTMLLayoutParser, HTMLImportHdl ) );

        SfxObjectShell* pObjSh = mpDoc->GetDocumentShell();
        bool bLoading = pObjSh && pObjSh->IsLoading();

        SvKeyValueIteratorRef xValues;
        SvKeyValueIterator* pAttributes = nullptr;
        if ( bLoading )
            pAttributes = pObjSh->GetHeaderAttributes();
        else
        {
            // When not loading, set up fake http headers to force the SfxHTMLParser to use UTF8
            // (used when pasting from clipboard)
            const sal_Char* pCharSet = rtl_getBestMimeCharsetFromTextEncoding( RTL_TEXTENCODING_UTF8 );
            if( pCharSet )
            {
                OUString aContentType = "text/html; charset=";
                aContentType += OUString::createFromAscii( pCharSet );

                xValues = new SvKeyValueIterator;
                xValues->Append( SvKeyValue( OOO_STRING_SVTOOLS_HTML_META_content_type, aContentType ) );
                pAttributes = xValues.get();
            }
        }

        ErrCode nErr = pEdit->Read( rStream, rBaseURL, EETextFormat::Html, pAttributes );

        pEdit->SetHtmlImportHdl( aOldLink );
        // Create column width
        Adjust();
        OutputDevice* pDefaultDev = Application::GetDefaultDevice();
        sal_uInt16 nCount = maColOffset.size();
        sal_uLong nOff = maColOffset[0];
        Size aSize;
        for ( sal_uInt16 j = 1; j < nCount; j++ )
        {
            aSize.setWidth( maColOffset[j] - nOff );
            aSize = pDefaultDev->PixelToLogic( aSize, MapMode( MapUnit::MapTwip ) );
            maColWidths[ j-1 ] = aSize.Width();
            nOff = maColOffset[j];
        }
        return nErr;
    }

    const ScHTMLTable* ScHTMLLayoutParser::GetGlobalTable() const
    {
        return nullptr;
    }

    void ScHTMLLayoutParser::NewActEntry( const ScEEParseEntry* pE )
    {
        ScEEParser::NewActEntry( pE );
        if ( pE )
        {
            if ( !pE->aSel.HasRange() )
            {   // Completely empty, following text ends up in the same paragraph!
                mxActEntry->aSel.nStartPara = pE->aSel.nEndPara;
                mxActEntry->aSel.nStartPos = pE->aSel.nEndPos;
            }
        }
        mxActEntry->aSel.nEndPara = mxActEntry->aSel.nStartPara;
        mxActEntry->aSel.nEndPos = mxActEntry->aSel.nStartPos;
    }

    void ScHTMLLayoutParser::EntryEnd( ScEEParseEntry* pE, const ESelection& rSel )
    {
        if ( rSel.nEndPara >= pE->aSel.nStartPara )
        {
            pE->aSel.nEndPara = rSel.nEndPara;
            pE->aSel.nEndPos = rSel.nEndPos;
        }
        else if ( rSel.nStartPara == pE->aSel.nStartPara - 1 && !pE->aSel.HasRange() )
        {   // Did not attach a paragraph, but empty, do nothing
        }
        else
        {
            OSL_FAIL( "EntryEnd: EditEngine ESelection End < Start" );
        }
    }

    void ScHTMLLayoutParser::NextRow( const HtmlImportInfo* pInfo )
    {
        if ( bInCell )
            CloseEntry( pInfo );
        if ( nRowMax < ++nRowCnt )
            nRowMax = nRowCnt;
        nColCnt = nColCntStart;
        nColOffset = nColOffsetStart;
        bFirstRow = false;
    }

    bool ScHTMLLayoutParser::SeekOffset( const ScHTMLColOffset* pOffset, sal_uInt16 nOffset,
            SCCOL* pCol, sal_uInt16 nOffsetTol )
    {
        OSL_ENSURE( pOffset, "ScHTMLLayoutParser::SeekOffset - illegal call" );
        ScHTMLColOffset::const_iterator it = pOffset->find( nOffset );
        bool bFound = it != pOffset->end();
        sal_uInt16 nPos = it - pOffset->begin();
        *pCol = static_cast<SCCOL>(nPos);
        if ( bFound )
            return true;
        sal_uInt16 nCount = pOffset->size();
        if ( !nCount )
            return false;
        // nPos is the position of insertion, that's where the next higher one is (or isn't)
        if ( nPos < nCount && (((*pOffset)[nPos] - nOffsetTol) <= nOffset) )
            return true;
        // Not smaller than everything else? Then compare with the next lower one
        else if ( nPos && (((*pOffset)[nPos-1] + nOffsetTol) >= nOffset) )
        {
            (*pCol)--;
            return true;
        }
        return false;
    }

    void ScHTMLLayoutParser::MakeCol( ScHTMLColOffset* pOffset, sal_uInt16& nOffset,
            sal_uInt16& nWidth, sal_uInt16 nOffsetTol, sal_uInt16 nWidthTol )
    {
        OSL_ENSURE( pOffset, "ScHTMLLayoutParser::MakeCol - illegal call" );
        SCCOL nPos;
        if ( SeekOffset( pOffset, nOffset, &nPos, nOffsetTol ) )
            nOffset = static_cast<sal_uInt16>((*pOffset)[nPos]);
        else
            pOffset->insert( nOffset );
        if ( nWidth )
        {
            if ( SeekOffset( pOffset, nOffset + nWidth, &nPos, nWidthTol ) )
                nWidth = static_cast<sal_uInt16>((*pOffset)[nPos]) - nOffset;
            else
                pOffset->insert( nOffset + nWidth );
        }
    }

    void ScHTMLLayoutParser::MakeColNoRef( ScHTMLColOffset* pOffset, sal_uInt16 nOffset,
            sal_uInt16 nWidth, sal_uInt16 nOffsetTol, sal_uInt16 nWidthTol )
    {
        OSL_ENSURE( pOffset, "ScHTMLLayoutParser::MakeColNoRef - illegal call" );
        SCCOL nPos;
        if ( SeekOffset( pOffset, nOffset, &nPos, nOffsetTol ) )
            nOffset = static_cast<sal_uInt16>((*pOffset)[nPos]);
        else
            pOffset->insert( nOffset );
        if ( nWidth )
        {
            if ( !SeekOffset( pOffset, nOffset + nWidth, &nPos, nWidthTol ) )
                pOffset->insert( nOffset + nWidth );
        }
    }

    void ScHTMLLayoutParser::ModifyOffset( ScHTMLColOffset* pOffset, sal_uInt16& nOldOffset,
                sal_uInt16& nNewOffset, sal_uInt16 nOffsetTol )
    {
        OSL_ENSURE( pOffset, "ScHTMLLayoutParser::ModifyOffset - illegal call" );
        SCCOL nPos;
        if ( !SeekOffset( pOffset, nOldOffset, &nPos, nOffsetTol ) )
        {
            if ( SeekOffset( pOffset, nNewOffset, &nPos, nOffsetTol ) )
                nNewOffset = static_cast<sal_uInt16>((*pOffset)[nPos]);
            else
                pOffset->insert( nNewOffset );
            return ;
        }
        nOldOffset = static_cast<sal_uInt16>((*pOffset)[nPos]);
        SCCOL nPos2;
        if ( SeekOffset( pOffset, nNewOffset, &nPos2, nOffsetTol ) )
        {
            nNewOffset = static_cast<sal_uInt16>((*pOffset)[nPos2]);
            return ;
        }
        long nDiff = nNewOffset - nOldOffset;
        if ( nDiff < 0 )
        {
            do
            {
                const_cast<sal_uLong&>((*pOffset)[nPos]) += nDiff;
            } while ( nPos-- );
        }
        else
        {
            do
            {
                const_cast<sal_uLong&>((*pOffset)[nPos]) += nDiff;
            } while ( ++nPos < static_cast<sal_uInt16>(pOffset->size()) );
        }
    }

    void ScHTMLLayoutParser::SkipLocked( ScEEParseEntry* pE, bool bJoin )
    {
        if ( ValidCol(pE->nCol) )
        {   // Or else this would create a wrong value at ScAddress (chance for an infinite loop)!
            bool bBadCol = false;
            bool bAgain;
            ScRange aRange( pE->nCol, pE->nRow, 0,
                pE->nCol + pE->nColOverlap - 1, pE->nRow + pE->nRowOverlap - 1, 0 );
            do
            {
                bAgain = false;
                for ( size_t i =  0, nRanges = xLockedList->size(); i < nRanges; ++i )
                {
                    ScRange & rR = (*xLockedList)[i];
                    if ( rR.Intersects( aRange ) )
                    {
                        pE->nCol = rR.aEnd.Col() + 1;
                        SCCOL nTmp = pE->nCol + pE->nColOverlap - 1;
                        if ( pE->nCol > MAXCOL || nTmp > MAXCOL )
                            bBadCol = true;
                        else
                        {
                            bAgain = true;
                            aRange.aStart.SetCol( pE->nCol );
                            aRange.aEnd.SetCol( nTmp );
                        }
                        break;
                    }
                }
            } while ( bAgain );
            if ( bJoin && !bBadCol )
                xLockedList->Join( aRange );
        }
    }

    void ScHTMLLayoutParser::Adjust()
    {
        xLockedList->RemoveAll();

        std::stack< ScHTMLAdjustStackEntry* > aStack;
        ScHTMLAdjustStackEntry* pS = nullptr;
        sal_uInt16 nTab = 0;
        SCCOL nLastCol = SCCOL_MAX;
        SCROW nNextRow = 0;
        SCROW nCurRow = 0;
        sal_uInt16 nPageWidth = static_cast<sal_uInt16>(aPageSize.Width());
        InnerMap* pTab = nullptr;
        for (auto& pE : maList)
        {
            if ( pE->nTab < nTab )
            {   // Table finished
                if ( !aStack.empty() )
                {
                    pS = aStack.top();
                    aStack.pop();

                    nLastCol = pS->nLastCol;
                    nNextRow = pS->nNextRow;
                    nCurRow = pS->nCurRow;
                }
                delete pS;
                pS = nullptr;
                nTab = pE->nTab;
                if (pTables)
                {
                    OuterMap::const_iterator it = pTables->find( nTab );
                    if ( it != pTables->end() )
                        pTab = it->second;
                }

            }
            SCROW nRow = pE->nRow;
            if ( pE->nCol <= nLastCol )
            {   // Next row
                if ( pE->nRow < nNextRow )
                    pE->nRow = nCurRow = nNextRow;
                else
                    nCurRow = nNextRow = pE->nRow;
                SCROW nR = 0;
                if ( pTab )
                {
                    InnerMap::const_iterator it = pTab->find( nCurRow );
                    if ( it != pTab->end() )
                        nR = it->second;
                }
                if ( nR )
                    nNextRow += nR;
                else
                    nNextRow++;
            }
            else
                pE->nRow = nCurRow;
            nLastCol = pE->nCol; // Read column
            if ( pE->nTab > nTab )
            {   // New table
                aStack.push( new ScHTMLAdjustStackEntry(
                    nLastCol, nNextRow, nCurRow ) );
                nTab = pE->nTab;
                if ( pTables )
                {
                    OuterMap::const_iterator it = pTables->find( nTab );
                    if ( it != pTables->end() )
                        pTab = it->second;
                }
                // New line spacing
                SCROW nR = 0;
                if ( pTab )
                {
                    InnerMap::const_iterator it = pTab->find( nCurRow );
                    if ( it != pTab->end() )
                        nR = it->second;
                }
                if ( nR )
                    nNextRow = nCurRow + nR;
                else
                    nNextRow = nCurRow + 1;
            }
            if ( nTab == 0 )
                pE->nWidth = nPageWidth;
            else
            {   // Real table, no paragraphs on the field
                if ( pTab )
                {
                    SCROW nRowSpan = pE->nRowOverlap;
                    for ( SCROW j=0; j < nRowSpan; j++ )
                    {   // RowSpan resulting from merged rows
                        SCROW nRows = 0;
                        InnerMap::const_iterator it = pTab->find( nRow+j );
                        if ( it != pTab->end() )
                            nRows = it->second;
                        if ( nRows > 1 )
                        {
                            pE->nRowOverlap += nRows - 1;
                            if ( j == 0 )
                            {   // Merged rows move the next row
                                SCROW nTmp = nCurRow + nRows;
                                if ( nNextRow < nTmp )
                                    nNextRow = nTmp;
                            }
                        }
                    }
                }
            }
            // Real column
            (void)SeekOffset( &maColOffset, pE->nOffset, &pE->nCol, nOffsetTolerance );
            SCCOL nColBeforeSkip = pE->nCol;
            SkipLocked(pE.get(), false);
            if ( pE->nCol != nColBeforeSkip )
            {
                SCCOL nCount = static_cast<SCCOL>(maColOffset.size());
                if ( nCount <= pE->nCol )
                {
                    pE->nOffset = static_cast<sal_uInt16>(maColOffset[nCount-1]);
                    MakeCol( &maColOffset, pE->nOffset, pE->nWidth, nOffsetTolerance, nOffsetTolerance );
                }
                else
                {
                    pE->nOffset = static_cast<sal_uInt16>(maColOffset[pE->nCol]);
                }
            }
            SCCOL nPos;
            if ( pE->nWidth && SeekOffset( &maColOffset, pE->nOffset + pE->nWidth, &nPos, nOffsetTolerance ) )
                pE->nColOverlap = (nPos > pE->nCol ? nPos - pE->nCol : 1);
            else
            {
            //FIXME: This may not be correct, but works anyway ...
                pE->nColOverlap = 1;
            }
            xLockedList->Join( ScRange( pE->nCol, pE->nRow, 0,
                pE->nCol + pE->nColOverlap - 1, pE->nRow + pE->nRowOverlap - 1, 0 ) );
            // Take over MaxDimensions
            SCCOL nColTmp = pE->nCol + pE->nColOverlap;
            if ( nColMax < nColTmp )
                nColMax = nColTmp;
            SCROW nRowTmp = pE->nRow + pE->nRowOverlap;
            if ( nRowMax < nRowTmp )
                nRowMax = nRowTmp;
        }
        while ( !aStack.empty() )
        {
            delete aStack.top();
            aStack.pop();
        }
    }

    sal_uInt16 ScHTMLLayoutParser::GetWidth( const ScEEParseEntry* pE )
    {
        if ( pE->nWidth )
            return pE->nWidth;
        sal_Int32 nTmp = std::min( static_cast<sal_Int32>( pE->nCol -
                    nColCntStart + pE->nColOverlap),
                static_cast<sal_Int32>( pLocalColOffset->size() - 1));
        SCCOL nPos = (nTmp < 0 ? 0 : static_cast<SCCOL>(nTmp));
        sal_uInt16 nOff2 = static_cast<sal_uInt16>((*pLocalColOffset)[nPos]);
        if ( pE->nOffset < nOff2 )
            return nOff2 - pE->nOffset;
        return 0;
    }

    void ScHTMLLayoutParser::SetWidths()
    {
        SCCOL nCol;
        if ( !nTableWidth )
            nTableWidth = static_cast<sal_uInt16>(aPageSize.Width());
        SCCOL nColsPerRow = nMaxCol - nColCntStart;
        if ( nColsPerRow <= 0 )
            nColsPerRow = 1;
        if ( pLocalColOffset->size() <= 2 )
        {   // Only PageSize, there was no width setting
            sal_uInt16 nWidth = nTableWidth / static_cast<sal_uInt16>(nColsPerRow);
            sal_uInt16 nOff = nColOffsetStart;
            pLocalColOffset->clear();
            for ( nCol = 0; nCol <= nColsPerRow; ++nCol, nOff = nOff + nWidth )
            {
                MakeColNoRef( pLocalColOffset.get(), nOff, 0, 0, 0 );
            }
            nTableWidth = static_cast<sal_uInt16>(pLocalColOffset->back() - pLocalColOffset->front());
            for ( size_t i = nFirstTableCell, nListSize = maList.size(); i < nListSize; ++i )
            {
                auto& pE = maList[ i ];
                if ( pE->nTab == nTable )
                {
                    pE->nOffset = static_cast<sal_uInt16>((*pLocalColOffset)[pE->nCol - nColCntStart]);
                    pE->nWidth = 0; // to be recalculated later
                }
            }
        }
        else
        {   // Some without width
            // Why actually no pE?
            if ( nFirstTableCell < maList.size() )
            {
                std::unique_ptr<sal_uInt16[]> pOffsets(new sal_uInt16[ nColsPerRow+1 ]);
                memset( pOffsets.get(), 0, (nColsPerRow+1) * sizeof(sal_uInt16) );
                std::unique_ptr<sal_uInt16[]> pWidths(new sal_uInt16[ nColsPerRow ]);
                memset( pWidths.get(), 0, nColsPerRow * sizeof(sal_uInt16) );
                pOffsets[0] = nColOffsetStart;
                for ( size_t i = nFirstTableCell, nListSize = maList.size(); i < nListSize; ++i )
                {
                    auto& pE = maList[ i ];
                    if ( pE->nTab == nTable && pE->nWidth )
                    {
                        nCol = pE->nCol - nColCntStart;
                        if ( nCol < nColsPerRow )
                        {
                            if ( pE->nColOverlap == 1 )
                            {
                                if ( pWidths[nCol] < pE->nWidth )
                                    pWidths[nCol] = pE->nWidth;
                            }
                            else
                            {   // try to find a single undefined width
                                sal_uInt16 nTotal = 0;
                                bool bFound = false;
                                SCCOL nHere = 0;
                                SCCOL nStop = std::min( static_cast<SCCOL>(nCol + pE->nColOverlap), nColsPerRow );
                                for ( ; nCol < nStop; nCol++ )
                                {
                                    if ( pWidths[nCol] )
                                        nTotal = nTotal + pWidths[nCol];
                                    else
                                    {
                                        if ( bFound )
                                        {
                                            bFound = false;
                                            break;  // for
                                        }
                                        bFound = true;
                                        nHere = nCol;
                                    }
                                }
                                if ( bFound && pE->nWidth > nTotal )
                                    pWidths[nHere] = pE->nWidth - nTotal;
                            }
                        }
                    }
                }
                sal_uInt16 nWidths = 0;
                sal_uInt16 nUnknown = 0;
                for ( nCol = 0; nCol < nColsPerRow; nCol++ )
                {
                    if ( pWidths[nCol] )
                        nWidths = nWidths + pWidths[nCol];
                    else
                        nUnknown++;
                }
                if ( nUnknown )
                {
                    sal_uInt16 nW = ((nWidths < nTableWidth) ?
                        ((nTableWidth - nWidths) / nUnknown) :
                        (nTableWidth / nUnknown));
                    for ( nCol = 0; nCol < nColsPerRow; nCol++ )
                    {
                        if ( !pWidths[nCol] )
                            pWidths[nCol] = nW;
                    }
                }
                for ( nCol = 1; nCol <= nColsPerRow; nCol++ )
                {
                    pOffsets[nCol] = pOffsets[nCol-1] + pWidths[nCol-1];
                }
                pLocalColOffset->clear();
                for ( nCol = 0; nCol <= nColsPerRow; nCol++ )
                {
                    MakeColNoRef( pLocalColOffset.get(), pOffsets[nCol], 0, 0, 0 );
                }
                nTableWidth = pOffsets[nColsPerRow] - pOffsets[0];

                for ( size_t i = nFirstTableCell, nListSize = maList.size(); i < nListSize; ++i )
                {
                    auto& pE = maList[ i ];
                    if ( pE->nTab == nTable )
                    {
                        nCol = pE->nCol - nColCntStart;
                        OSL_ENSURE( nCol < nColsPerRow, "ScHTMLLayoutParser::SetWidths: column overflow" );
                        if ( nCol < nColsPerRow )
                        {
                            pE->nOffset = pOffsets[nCol];
                            nCol = nCol + pE->nColOverlap;
                            if ( nCol > nColsPerRow )
                                nCol = nColsPerRow;
                            pE->nWidth = pOffsets[nCol] - pE->nOffset;
                        }
                    }
                }
            }
        }
        if ( !pLocalColOffset->empty() )
        {
            sal_uInt16 nMax = static_cast<sal_uInt16>(pLocalColOffset->back());
            if ( aPageSize.Width() < nMax )
                aPageSize.setWidth( nMax );
        }
        for ( size_t i = nFirstTableCell, nListSize = maList.size(); i < nListSize; ++i )
        {
            auto& pE = maList[ i ];
            if ( pE->nTab == nTable )
            {
                if ( !pE->nWidth )
                {
                    pE->nWidth = GetWidth(pE.get());
                    OSL_ENSURE( pE->nWidth, "SetWidths: pE->nWidth == 0" );
                }
                MakeCol( &maColOffset, pE->nOffset, pE->nWidth, nOffsetTolerance, nOffsetTolerance );
            }
        }
    }

    void ScHTMLLayoutParser::Colonize( ScEEParseEntry* pE )
    {
        if ( pE->nCol == SCCOL_MAX )
            pE->nCol = nColCnt;
        if ( pE->nRow == SCROW_MAX )
            pE->nRow = nRowCnt;
        SCCOL nCol = pE->nCol;
        SkipLocked( pE ); // Change of columns to the right

        if ( nCol < pE->nCol )
        {   // Replaced
            nCol = pE->nCol - nColCntStart;
            SCCOL nCount = static_cast<SCCOL>(pLocalColOffset->size());
            if ( nCol < nCount )
                nColOffset = static_cast<sal_uInt16>((*pLocalColOffset)[nCol]);
            else
                nColOffset = static_cast<sal_uInt16>((*pLocalColOffset)[nCount - 1]);
        }
        pE->nOffset = nColOffset;
        sal_uInt16 nWidth = GetWidth( pE );
        MakeCol( pLocalColOffset.get(), pE->nOffset, nWidth, nOffsetTolerance, nOffsetTolerance );
        if ( pE->nWidth )
            pE->nWidth = nWidth;
        nColOffset = pE->nOffset + nWidth;
        if ( nTableWidth < nColOffset - nColOffsetStart )
            nTableWidth = nColOffset - nColOffsetStart;
    }

    void ScHTMLLayoutParser::CloseEntry( const HtmlImportInfo* pInfo )
    {
        bInCell = false;
        if ( bTabInTabCell )
        {   // From the stack in TableOff
            bTabInTabCell = false;
            NewActEntry(maList.back().get()); // New free flying mxActEntry
            return ;
        }
        if (mxActEntry->nTab == 0)
            mxActEntry->nWidth = static_cast<sal_uInt16>(aPageSize.Width());
        Colonize(mxActEntry.get());
        nColCnt = mxActEntry->nCol + mxActEntry->nColOverlap;
        if ( nMaxCol < nColCnt )
            nMaxCol = nColCnt;      // TableStack MaxCol
        if ( nColMax < nColCnt )
            nColMax = nColCnt;      // Global MaxCol for ScEEParser GetDimensions!
        EntryEnd(mxActEntry.get(), pInfo->aSelection);
        ESelection& rSel = mxActEntry->aSel;
        while ( rSel.nStartPara < rSel.nEndPara
                && pEdit->GetTextLen( rSel.nStartPara ) == 0 )
        {   // Strip preceding empty paragraphs
            rSel.nStartPara++;
        }
        while ( rSel.nEndPos == 0 && rSel.nEndPara > rSel.nStartPara )
        {   // Strip successive empty paragraphs
            rSel.nEndPara--;
            rSel.nEndPos = pEdit->GetTextLen( rSel.nEndPara );
        }
        if ( rSel.nStartPara > rSel.nEndPara )
        {   // Gives GPF in CreateTextObject
            OSL_FAIL( "CloseEntry: EditEngine ESelection Start > End" );
            rSel.nEndPara = rSel.nStartPara;
        }
        if ( rSel.HasRange() )
            mxActEntry->aItemSet.Put( SfxBoolItem( ATTR_LINEBREAK, true ) );
        maList.push_back(mxActEntry);
        NewActEntry(mxActEntry.get()); // New free flying mxActEntry
    }

    IMPL_LINK( ScHTMLLayoutParser, HTMLImportHdl, HtmlImportInfo&, rInfo, void )
    {
        switch ( rInfo.eState )
        {
            case HtmlImportState::NextToken:
                ProcToken( &rInfo );
                break;
            case HtmlImportState::Start:
                break;
            case HtmlImportState::End:
                if ( rInfo.aSelection.nEndPos )
                {
                    // If text remains: create paragraph, without calling CloseEntry().
                    if( bInCell )   // ...but only in opened table cells.
                    {
                        bInCell = false;
                        NextRow( &rInfo );
                        bInCell = true;
                    }
                    CloseEntry( &rInfo );
                }
                while ( nTableLevel > 0 )
                    TableOff( &rInfo );      // close tables, if </TABLE> missing
                break;
            case HtmlImportState::SetAttr:
                break;
            case HtmlImportState::InsertText:
                break;
            case HtmlImportState::InsertPara:
                if ( nTableLevel < 1 )
                {
                    CloseEntry( &rInfo );
                    NextRow( &rInfo );
                }
                break;
            case HtmlImportState::InsertField:
                break;
            default:
                OSL_FAIL("HTMLImportHdl: unknown ImportInfo.eState");
        }
    }

    // Greatest common divisor (Euclid)
    // Special case: 0 and something gives 1
    static SCROW lcl_GGT( SCROW a, SCROW b )
    {
        if ( !a || !b )
            return 1;
        do
        {
            if ( a > b )
                a -= SCROW(a / b) * b;
            else
                b -= SCROW(b / a) * a;
        } while ( a && b );
        return ((a != 0) ? a : b);
    }

    // Lowest common multiple: a * b / GCD(a,b)
    static SCROW lcl_KGV( SCROW a, SCROW b )
    {
        if ( a > b )    // Make overflow even less likely
            return (a / lcl_GGT(a,b)) * b;
        else
            return (b / lcl_GGT(a,b)) * a;
    }

    void ScHTMLLayoutParser::TableDataOn( HtmlImportInfo* pInfo )
    {
        if ( bInCell )
            CloseEntry( pInfo );
        if ( !nTableLevel )
        {
            OSL_FAIL( "dumbo doc! <TH> or <TD> without previous <TABLE>" );
            TableOn( pInfo );
        }
        bInCell = true;
        bool bHorJustifyCenterTH = (pInfo->nToken == HtmlTokenId::TABLEHEADER_ON);
        const HTMLOptions& rOptions = static_cast<HTMLParser*>(pInfo->pParser)->GetOptions();
        for (const auto & rOption : rOptions)
        {
            switch( rOption.GetToken() )
            {
                case HtmlOptionId::COLSPAN:
                {
                    mxActEntry->nColOverlap = static_cast<SCCOL>(rOption.GetString().toInt32());
                }
                break;
                case HtmlOptionId::ROWSPAN:
                {
                    mxActEntry->nRowOverlap = static_cast<SCROW>(rOption.GetString().toInt32());
                }
                break;
                case HtmlOptionId::ALIGN:
                {
                    bHorJustifyCenterTH = false;
                    SvxCellHorJustify eVal;
                    const OUString& rOptVal = rOption.GetString();
                    if ( rOptVal.equalsIgnoreAsciiCase( OOO_STRING_SVTOOLS_HTML_AL_right )  )
                        eVal = SvxCellHorJustify::Right;
                    else if ( rOptVal.equalsIgnoreAsciiCase( OOO_STRING_SVTOOLS_HTML_AL_center ) )
                        eVal = SvxCellHorJustify::Center;
                    else if ( rOptVal.equalsIgnoreAsciiCase( OOO_STRING_SVTOOLS_HTML_AL_left ) )
                        eVal = SvxCellHorJustify::Left;
                    else
                        eVal = SvxCellHorJustify::Standard;
                    if ( eVal != SvxCellHorJustify::Standard )
                        mxActEntry->aItemSet.Put(SvxHorJustifyItem(eVal, ATTR_HOR_JUSTIFY));
                }
                break;
                case HtmlOptionId::VALIGN:
                {
                    SvxCellVerJustify eVal;
                    const OUString& rOptVal = rOption.GetString();
                    if ( rOptVal.equalsIgnoreAsciiCase( OOO_STRING_SVTOOLS_HTML_VA_top ) )
                        eVal = SvxCellVerJustify::Top;
                    else if ( rOptVal.equalsIgnoreAsciiCase( OOO_STRING_SVTOOLS_HTML_VA_middle ) )
                        eVal = SvxCellVerJustify::Center;
                    else if ( rOptVal.equalsIgnoreAsciiCase( OOO_STRING_SVTOOLS_HTML_VA_bottom ) )
                        eVal = SvxCellVerJustify::Bottom;
                    else
                        eVal = SvxCellVerJustify::Standard;
                    mxActEntry->aItemSet.Put(SvxVerJustifyItem(eVal, ATTR_VER_JUSTIFY));
                }
                break;
                case HtmlOptionId::WIDTH:
                {
                    mxActEntry->nWidth = GetWidthPixel(rOption);
                }
                break;
                case HtmlOptionId::BGCOLOR:
                {
                    Color aColor;
                    rOption.GetColor( aColor );
                    mxActEntry->aItemSet.Put(SvxBrushItem(aColor, ATTR_BACKGROUND));
                }
                break;
                case HtmlOptionId::SDVAL:
                {
                    mxActEntry->pValStr = rOption.GetString();
                }
                break;
                case HtmlOptionId::SDNUM:
                {
                    mxActEntry->pNumStr = rOption.GetString();
                }
                break;
                default: break;
            }
        }

        mxActEntry->nCol = nColCnt;
        mxActEntry->nRow = nRowCnt;
        mxActEntry->nTab = nTable;

        if ( bHorJustifyCenterTH )
            mxActEntry->aItemSet.Put(
                SvxHorJustifyItem( SvxCellHorJustify::Center, ATTR_HOR_JUSTIFY) );
    }

    void ScHTMLLayoutParser::TableRowOn( const HtmlImportInfo* pInfo )
    {
        if ( nColCnt > nColCntStart )
            NextRow( pInfo ); // The optional TableRowOff wasn't there
        nColOffset = nColOffsetStart;
    }

    void ScHTMLLayoutParser::TableRowOff( const HtmlImportInfo* pInfo )
    {
        NextRow( pInfo );
    }

    void ScHTMLLayoutParser::TableDataOff( const HtmlImportInfo* pInfo )
    {
        if ( bInCell )
            CloseEntry( pInfo ); // Only if it really was one
    }

    void ScHTMLLayoutParser::TableOn( HtmlImportInfo* pInfo )
    {
        if ( ++nTableLevel > 1 )
        {   // Table in Table
            sal_uInt16 nTmpColOffset = nColOffset; // Will be changed in Colonize()
            Colonize(mxActEntry.get());
            aTableStack.push( o3tl::make_unique<ScHTMLTableStackEntry>(
                mxActEntry, xLockedList, std::move(pLocalColOffset), nFirstTableCell,
                nRowCnt, nColCntStart, nMaxCol, nTable,
                nTableWidth, nColOffset, nColOffsetStart,
                bFirstRow ) );
            sal_uInt16 nLastWidth = nTableWidth;
            nTableWidth = GetWidth(mxActEntry.get());
            if ( nTableWidth == nLastWidth && nMaxCol - nColCntStart > 1 )
            {   // There must be more than one, so this one cannot be enough
                nTableWidth = nLastWidth / static_cast<sal_uInt16>((nMaxCol - nColCntStart));
            }
            nLastWidth = nTableWidth;
            if ( pInfo->nToken == HtmlTokenId::TABLE_ON )
            {   // It can still be TD or TH, if we didn't have a TABLE earlier
                const HTMLOptions& rOptions = static_cast<HTMLParser*>(pInfo->pParser)->GetOptions();
                for (const auto & rOption : rOptions)
                {
                    switch( rOption.GetToken() )
                    {
                        case HtmlOptionId::WIDTH:
                        {   // Percent: of document width or outer cell
                            nTableWidth = GetWidthPixel( rOption );
                        }
                        break;
                        case HtmlOptionId::BORDER:
                            // Border is: ((pOption->GetString().Len() == 0) || (pOption->GetNumber() != 0));
                        break;
                        default: break;
                    }
                }
            }
            bInCell = false;
            if ( bTabInTabCell && (nTableWidth >= nLastWidth) )
            {   // Multiple tables in one cell, underneath each other
                bTabInTabCell = false;
                NextRow( pInfo );
            }
            else
            {   // It start's in this cell or next to each other
                bTabInTabCell = false;
                nColCntStart = nColCnt;
                nColOffset = nTmpColOffset;
                nColOffsetStart = nColOffset;
            }

            NewActEntry(!maList.empty() ? maList.back().get() : nullptr); // New free flying mxActEntry
            xLockedList = new ScRangeList;
        }
        else
        {   // Simple table at the document level
            EntryEnd(mxActEntry.get(), pInfo->aSelection);
            if (mxActEntry->aSel.HasRange())
            {   // Flying text left
                CloseEntry( pInfo );
                NextRow( pInfo );
            }
            aTableStack.push( o3tl::make_unique<ScHTMLTableStackEntry>(
                mxActEntry, xLockedList, std::move(pLocalColOffset), nFirstTableCell,
                nRowCnt, nColCntStart, nMaxCol, nTable,
                nTableWidth, nColOffset, nColOffsetStart,
                bFirstRow ) );
            // As soon as we have multiple tables we need to be tolerant with the offsets.
            if (nMaxTable > 0)
                nOffsetTolerance = SC_HTML_OFFSET_TOLERANCE_LARGE;
            nTableWidth = 0;
            if ( pInfo->nToken == HtmlTokenId::TABLE_ON )
            {
                // It can still be TD or TH, if we didn't have a TABLE earlier
                const HTMLOptions& rOptions = static_cast<HTMLParser*>(pInfo->pParser)->GetOptions();
                for (const auto & rOption : rOptions)
                {
                    switch( rOption.GetToken() )
                    {
                        case HtmlOptionId::WIDTH:
                        {   // Percent: of document width or outer cell
                            nTableWidth = GetWidthPixel( rOption );
                        }
                        break;
                        case HtmlOptionId::BORDER:
                            //BorderOn is: ((pOption->GetString().Len() == 0) || (pOption->GetNumber() != 0));
                        break;
                        default: break;
                    }
                }
            }
        }
        nTable = ++nMaxTable;
        bFirstRow = true;
        nFirstTableCell = maList.size();

        pLocalColOffset.reset( new ScHTMLColOffset );
        MakeColNoRef( pLocalColOffset.get(), nColOffsetStart, 0, 0, 0 );
    }

    void ScHTMLLayoutParser::TableOff( const HtmlImportInfo* pInfo )
    {
        if ( bInCell )
            CloseEntry( pInfo );
        if ( nColCnt > nColCntStart )
            TableRowOff( pInfo ); // The optional TableRowOff wasn't
        if ( !nTableLevel )
        {
            OSL_FAIL( "dumbo doc! </TABLE> without opening <TABLE>" );
            return ;
        }
        if ( --nTableLevel > 0 )
        {   // Table in Table done
            if ( !aTableStack.empty() )
            {
                std::unique_ptr<ScHTMLTableStackEntry> pS = std::move(aTableStack.top());
                aTableStack.pop();

                auto& pE = pS->xCellEntry;
                SCROW nRows = nRowCnt - pS->nRowCnt;
                if ( nRows > 1 )
                {   // Insert size of table at this position
                    SCROW nRow = pS->nRowCnt;
                    sal_uInt16 nTab = pS->nTable;
                    if ( !pTables )
                        pTables.reset( new OuterMap );
                    // Height of outer table
                    OuterMap::const_iterator it = pTables->find( nTab );
                    InnerMap* pTab1;
                    if ( it == pTables->end() )
                    {
                        pTab1 = new InnerMap;
                        (*pTables)[ nTab ] = pTab1;
                    }
                    else
                        pTab1 = it->second;
                    SCROW nRowSpan = pE->nRowOverlap;
                    SCROW nRowKGV;
                    SCROW nRowsPerRow1; // Outer table
                    SCROW nRowsPerRow2; // Inner table
                    if ( nRowSpan > 1 )
                    {   // LCM to which we can map the inner and outer rows
                        nRowKGV = lcl_KGV( nRowSpan, nRows );
                        nRowsPerRow1 = nRowKGV / nRowSpan;
                        nRowsPerRow2 = nRowKGV / nRows;
                    }
                    else
                    {
                        nRowKGV = nRowsPerRow1 = nRows;
                        nRowsPerRow2 = 1;
                    }
                    InnerMap* pTab2 = nullptr;
                    if ( nRowsPerRow2 > 1 )
                    {   // Height of the inner table
                        pTab2 = new InnerMap;
                        (*pTables)[ nTable ] = pTab2;
                    }
                    // Abuse void* Data entry of the Table class for height mapping
                    if ( nRowKGV > 1 )
                    {
                        if ( nRowsPerRow1 > 1 )
                        {   // Outer
                            for ( SCROW j=0; j < nRowSpan; j++ )
                            {
                                sal_uLong nRowKey = nRow + j;
                                SCROW nR = (*pTab1)[ nRowKey ];
                                if ( !nR )
                                    (*pTab1)[ nRowKey ] = nRowsPerRow1;
                                else if ( nRowsPerRow1 > nR )
                                    (*pTab1)[ nRowKey ] = nRowsPerRow1;
                                //TODO: How can we improve on this?
                                else if ( nRowsPerRow1 < nR && nRowSpan == 1
                                  && nTable == nMaxTable )
                                {   // Still some space left, merge in a better way (if possible)
                                    SCROW nAdd = nRowsPerRow1 - (nR % nRowsPerRow1);
                                    nR += nAdd;
                                    if ( (nR % nRows) == 0 )
                                    {   // Only if representable
                                        SCROW nR2 = (*pTab1)[ nRowKey+1 ];
                                        if ( nR2 > nAdd )
                                        {   // Only if we really have enough space
                                            (*pTab1)[ nRowKey ] = nR;
                                            (*pTab1)[ nRowKey+1 ] = nR2 - nAdd;
                                            nRowsPerRow2 = nR / nRows;
                                        }
                                    }
                                }
                            }
                        }
                        if ( nRowsPerRow2 > 1 )
                        {   // Inner
                            if ( !pTab2 )
                            {   // nRowsPerRow2 could be've been incremented
                                pTab2 = new InnerMap;
                                (*pTables)[ nTable ] = pTab2;
                            }
                            for ( SCROW j=0; j < nRows; j++ )
                            {
                                sal_uLong nRowKey = nRow + j;
                                (*pTab2)[ nRowKey ] = nRowsPerRow2;
                            }
                        }
                    }
                }

                SetWidths();

                if ( !pE->nWidth )
                    pE->nWidth = nTableWidth;
                else if ( pE->nWidth < nTableWidth )
                {
                    sal_uInt16 nOldOffset = pE->nOffset + pE->nWidth;
                    sal_uInt16 nNewOffset = pE->nOffset + nTableWidth;
                    ModifyOffset( pS->pLocalColOffset.get(), nOldOffset, nNewOffset, nOffsetTolerance );
                    sal_uInt16 nTmp = nNewOffset - pE->nOffset - pE->nWidth;
                    pE->nWidth = nNewOffset - pE->nOffset;
                    pS->nTableWidth = pS->nTableWidth + nTmp;
                    if ( pS->nColOffset >= nOldOffset )
                        pS->nColOffset = pS->nColOffset + nTmp;
                }

                nColCnt = pE->nCol + pE->nColOverlap;
                nRowCnt = pS->nRowCnt;
                nColCntStart = pS->nColCntStart;
                nMaxCol = pS->nMaxCol;
                nTable = pS->nTable;
                nTableWidth = pS->nTableWidth;
                nFirstTableCell = pS->nFirstTableCell;
                nColOffset = pS->nColOffset;
                nColOffsetStart = pS->nColOffsetStart;
                bFirstRow = pS->bFirstRow;
                xLockedList = pS->xLockedList;
                pLocalColOffset = std::move( pS->pLocalColOffset );
                // mxActEntry is kept around if a table is started in the same row
                // (anything's possible in HTML); will be deleted by CloseEntry
                mxActEntry = pE;
            }
            bTabInTabCell = true;
            bInCell = true;
        }
        else
        {   // Simple table finished
            SetWidths();
            nMaxCol = 0;
            nTable = 0;
            if ( !aTableStack.empty() )
            {
                std::unique_ptr<ScHTMLTableStackEntry> pS = std::move(aTableStack.top());
                aTableStack.pop();
                pLocalColOffset = std::move( pS->pLocalColOffset );
            }
        }
    }

    void ScHTMLLayoutParser::Image( HtmlImportInfo* pInfo )
    {
        mxActEntry->maImageList.push_back(o3tl::make_unique<ScHTMLImage>());
        ScHTMLImage* pImage = mxActEntry->maImageList.back().get();
        const HTMLOptions& rOptions = static_cast<HTMLParser*>(pInfo->pParser)->GetOptions();
        for (const auto & rOption : rOptions)
        {
            switch( rOption.GetToken() )
            {
                case HtmlOptionId::SRC:
                {
                    pImage->aURL = INetURLObject::GetAbsURL( aBaseURL, rOption.GetString() );
                }
                break;
                case HtmlOptionId::ALT:
                {
                    if (!mxActEntry->bHasGraphic)
                    {   // ALT text only if not any image loaded
                        if (!mxActEntry->aAltText.isEmpty())
                            mxActEntry->aAltText += "; ";

                        mxActEntry->aAltText += rOption.GetString();
                    }
                }
                break;
                case HtmlOptionId::WIDTH:
                {
                    pImage->aSize.setWidth( static_cast<long>(rOption.GetNumber()) );
                }
                break;
                case HtmlOptionId::HEIGHT:
                {
                    pImage->aSize.setHeight( static_cast<long>(rOption.GetNumber()) );
                }
                break;
                case HtmlOptionId::HSPACE:
                {
                    pImage->aSpace.setX( static_cast<long>(rOption.GetNumber()) );
                }
                break;
                case HtmlOptionId::VSPACE:
                {
                    pImage->aSpace.setY( static_cast<long>(rOption.GetNumber()) );
                }
                break;
                default: break;
            }
        }
        if (pImage->aURL.isEmpty())
        {
            OSL_FAIL( "Image: graphic without URL ?!?" );
            return ;
        }

        sal_uInt16 nFormat;
        Graphic* pGraphic = new Graphic;
        GraphicFilter& rFilter = GraphicFilter::GetGraphicFilter();
        if ( ERRCODE_NONE != GraphicFilter::LoadGraphic( pImage->aURL, pImage->aFilterName,
                *pGraphic, &rFilter, &nFormat ) )
        {
            delete pGraphic;
            return ; // Bad luck
        }
        if (!mxActEntry->bHasGraphic)
        {   // discard any ALT text in this cell if we have any image
            mxActEntry->bHasGraphic = true;
            mxActEntry->aAltText.clear();
        }
        pImage->aFilterName = rFilter.GetImportFormatName( nFormat );
        pImage->pGraphic.reset( pGraphic );
        if ( !(pImage->aSize.Width() && pImage->aSize.Height()) )
        {
            OutputDevice* pDefaultDev = Application::GetDefaultDevice();
            pImage->aSize = pDefaultDev->LogicToPixel( pGraphic->GetPrefSize(),
                pGraphic->GetPrefMapMode() );
        }
        if (mxActEntry->maImageList.size() > 0)
        {
            long nWidth = 0;
            for (std::unique_ptr<ScHTMLImage> & pI : mxActEntry->maImageList)
            {
                if ( pI->nDir & nHorizontal )
                    nWidth += pI->aSize.Width() + 2 * pI->aSpace.X();
                else
                    nWidth = 0;
            }
            if ( mxActEntry->nWidth
              && (nWidth + pImage->aSize.Width() + 2 * pImage->aSpace.X()
                    >= mxActEntry->nWidth) )
                mxActEntry->maImageList.back()->nDir = nVertical;
        }
    }

    void ScHTMLLayoutParser::ColOn( HtmlImportInfo* pInfo )
    {
        const HTMLOptions& rOptions = static_cast<HTMLParser*>(pInfo->pParser)->GetOptions();
        for (const auto & rOption : rOptions)
        {
            if( rOption.GetToken() == HtmlOptionId::WIDTH )
            {
                sal_uInt16 nVal = GetWidthPixel( rOption );
                MakeCol( pLocalColOffset.get(), nColOffset, nVal, 0, 0 );
                nColOffset = nColOffset + nVal;
            }
        }
    }

    sal_uInt16 ScHTMLLayoutParser::GetWidthPixel( const HTMLOption& rOption )
    {
        const OUString& rOptVal = rOption.GetString();
        if ( rOptVal.indexOf('%') != -1 )
        {   // Percent
            sal_uInt16 nW = (nTableWidth ? nTableWidth : static_cast<sal_uInt16>(aPageSize.Width()));
            return static_cast<sal_uInt16>((rOption.GetNumber() * nW) / 100);
        }
        else
        {
            if ( rOptVal.indexOf('*') != -1 )
            {   // Relative to what?
                // TODO: Collect all relative values in ColArray and then MakeCol
                return 0;
            }
            else
                return static_cast<sal_uInt16>(rOption.GetNumber()); // Pixel
        }
    }

    void ScHTMLLayoutParser::AnchorOn( HtmlImportInfo* pInfo )
    {
        const HTMLOptions& rOptions = static_cast<HTMLParser*>(pInfo->pParser)->GetOptions();
        for (const auto & rOption : rOptions)
        {
            if( rOption.GetToken() == HtmlOptionId::NAME )
                mxActEntry->pName = rOption.GetString();
        }
    }

    bool ScHTMLLayoutParser::IsAtBeginningOfText( const HtmlImportInfo* pInfo )
    {
        ESelection& rSel = mxActEntry->aSel;
        return rSel.nStartPara == rSel.nEndPara &&
            rSel.nStartPara <= pInfo->aSelection.nEndPara &&
            pEdit->GetTextLen( rSel.nStartPara ) == 0;
    }

    void ScHTMLLayoutParser::FontOn( HtmlImportInfo* pInfo )
    {
        if ( IsAtBeginningOfText( pInfo ) )
        {   // Only at the start of the text; applies to whole line
            const HTMLOptions& rOptions = static_cast<HTMLParser*>(pInfo->pParser)->GetOptions();
            for (const auto & rOption : rOptions)
            {
                switch( rOption.GetToken() )
                {
                    case HtmlOptionId::FACE :
                    {
                        const OUString& rFace = rOption.GetString();
                        OUStringBuffer aFontName;
                        sal_Int32 nPos = 0;
                        while( nPos != -1 )
                        {
                            // Font list, VCL uses the semicolon as separator
                            // HTML uses the comma
                            OUString aFName = rFace.getToken( 0, ',', nPos );
                            aFName = comphelper::string::strip(aFName, ' ');
                            if( !aFontName.isEmpty() )
                                aFontName.append(";");
                            aFontName.append(aFName);
                        }
                        if ( !aFontName.isEmpty() )
                            mxActEntry->aItemSet.Put( SvxFontItem( FAMILY_DONTKNOW,
                                aFontName.makeStringAndClear(), EMPTY_OUSTRING, PITCH_DONTKNOW,
                                RTL_TEXTENCODING_DONTKNOW, ATTR_FONT ) );
                    }
                    break;
                    case HtmlOptionId::SIZE :
                    {
                        sal_uInt16 nSize = static_cast<sal_uInt16>(rOption.GetNumber());
                        if ( nSize == 0 )
                            nSize = 1;
                        else if ( nSize > SC_HTML_FONTSIZES )
                            nSize = SC_HTML_FONTSIZES;
                        mxActEntry->aItemSet.Put( SvxFontHeightItem(
                            maFontHeights[nSize-1], 100, ATTR_FONT_HEIGHT ) );
                    }
                    break;
                    case HtmlOptionId::COLOR :
                    {
                        Color aColor;
                        rOption.GetColor( aColor );
                        mxActEntry->aItemSet.Put( SvxColorItem( aColor, ATTR_FONT_COLOR ) );
                    }
                    break;
                    default: break;
                }
            }
        }
    }

    void ScHTMLLayoutParser::ProcToken( HtmlImportInfo* pInfo )
    {
        switch ( pInfo->nToken )
        {
            case HtmlTokenId::META:
            {
                HTMLParser* pParser = static_cast<HTMLParser*>(pInfo->pParser);
                uno::Reference<document::XDocumentPropertiesSupplier> xDPS(
                    mpDoc->GetDocumentShell()->GetModel(), uno::UNO_QUERY_THROW);
                pParser->ParseMetaOptions(
                    xDPS->getDocumentProperties(),
                    mpDoc->GetDocumentShell()->GetHeaderAttributes() );
            }
            break;
            case HtmlTokenId::TITLE_ON:
            {
                bInTitle = true;
                aString.clear();
            }
            break;
            case HtmlTokenId::TITLE_OFF:
            {
                if ( bInTitle && !aString.isEmpty() )
                {
                    // Remove blanks from line brakes
                    aString = aString.trim();
                    uno::Reference<document::XDocumentPropertiesSupplier> xDPS(
                        mpDoc->GetDocumentShell()->GetModel(),
                        uno::UNO_QUERY_THROW);
                    xDPS->getDocumentProperties()->setTitle(aString);
                }
                bInTitle = false;
            }
            break;
            case HtmlTokenId::TABLE_ON:
            {
                TableOn( pInfo );
            }
            break;
            case HtmlTokenId::COL_ON:
            {
                ColOn( pInfo );
            }
            break;
            case HtmlTokenId::TABLEHEADER_ON:       // Opens row
            {
                if ( bInCell )
                    CloseEntry( pInfo );
                // Do not set bInCell to true, TableDataOn does that
                mxActEntry->aItemSet.Put(
                    SvxWeightItem( WEIGHT_BOLD, ATTR_FONT_WEIGHT) );
                SAL_FALLTHROUGH;
            }
            case HtmlTokenId::TABLEDATA_ON:         // Opens cell
            {
                TableDataOn( pInfo );
            }
            break;
            case HtmlTokenId::TABLEHEADER_OFF:
            case HtmlTokenId::TABLEDATA_OFF:        // Closes cell
            {
                TableDataOff( pInfo );
            }
            break;
            case HtmlTokenId::TABLEROW_ON:          // Before first cell in row
            {
                TableRowOn( pInfo );
            }
            break;
            case HtmlTokenId::TABLEROW_OFF:         // After last cell in row
            {
                TableRowOff( pInfo );
            }
            break;
            case HtmlTokenId::TABLE_OFF:
            {
                TableOff( pInfo );
            }
            break;
            case HtmlTokenId::IMAGE:
            {
                Image( pInfo );
            }
            break;
            case HtmlTokenId::PARABREAK_OFF:
            {   // We continue vertically after an image
                if (mxActEntry->maImageList.size() > 0)
                    mxActEntry->maImageList.back()->nDir = nVertical;
            }
            break;
            case HtmlTokenId::ANCHOR_ON:
            {
                AnchorOn( pInfo );
            }
            break;
            case HtmlTokenId::FONT_ON :
            {
                FontOn( pInfo );
            }
            break;
            case HtmlTokenId::BIGPRINT_ON :
            {
                // TODO: Remember current font size and increase by 1
                if ( IsAtBeginningOfText( pInfo ) )
                    mxActEntry->aItemSet.Put( SvxFontHeightItem(
                        maFontHeights[3], 100, ATTR_FONT_HEIGHT ) );
            }
            break;
            case HtmlTokenId::SMALLPRINT_ON :
            {
                // TODO: Remember current font size and decrease by 1
                if ( IsAtBeginningOfText( pInfo ) )
                    mxActEntry->aItemSet.Put( SvxFontHeightItem(
                        maFontHeights[0], 100, ATTR_FONT_HEIGHT ) );
            }
            break;
            case HtmlTokenId::BOLD_ON :
            case HtmlTokenId::STRONG_ON :
            {
                if ( IsAtBeginningOfText( pInfo ) )
                    mxActEntry->aItemSet.Put( SvxWeightItem( WEIGHT_BOLD,
                        ATTR_FONT_WEIGHT ) );
            }
            break;
            case HtmlTokenId::ITALIC_ON :
            case HtmlTokenId::EMPHASIS_ON :
            case HtmlTokenId::ADDRESS_ON :
            case HtmlTokenId::BLOCKQUOTE_ON :
            case HtmlTokenId::BLOCKQUOTE30_ON :
            case HtmlTokenId::CITIATION_ON :
            case HtmlTokenId::VARIABLE_ON :
            {
                if ( IsAtBeginningOfText( pInfo ) )
                    mxActEntry->aItemSet.Put( SvxPostureItem( ITALIC_NORMAL,
                        ATTR_FONT_POSTURE ) );
            }
            break;
            case HtmlTokenId::DEFINSTANCE_ON :
            {
                if ( IsAtBeginningOfText( pInfo ) )
                {
                    mxActEntry->aItemSet.Put( SvxWeightItem( WEIGHT_BOLD,
                        ATTR_FONT_WEIGHT ) );
                    mxActEntry->aItemSet.Put( SvxPostureItem( ITALIC_NORMAL,
                        ATTR_FONT_POSTURE ) );
                }
            }
            break;
            case HtmlTokenId::UNDERLINE_ON :
            {
                if ( IsAtBeginningOfText( pInfo ) )
                    mxActEntry->aItemSet.Put( SvxUnderlineItem( LINESTYLE_SINGLE,
                        ATTR_FONT_UNDERLINE ) );
            }
            break;
            case HtmlTokenId::TEXTTOKEN:
            {
                if ( bInTitle )
                    aString += pInfo->aText;
            }
            break;
            default: ;
        }
    }

    // HTML DATA QUERY PARSER

    template< typename Type >
    inline Type getLimitedValue( const Type& rValue, const Type& rMin, const Type& rMax )
    { return std::max( std::min( rValue, rMax ), rMin ); }

    ScHTMLEntry::ScHTMLEntry( const SfxItemSet& rItemSet, ScHTMLTableId nTableId ) :
        ScEEParseEntry( rItemSet ),
        mbImportAlways( false )
    {
        nTab = nTableId;
        bEntirePara = false;
    }

    bool ScHTMLEntry::HasContents() const
    {
         return mbImportAlways || aSel.HasRange() || !aAltText.isEmpty() || IsTable();
    }

    void ScHTMLEntry::AdjustStart( const HtmlImportInfo& rInfo )
    {
        // set start position
        aSel.nStartPara = rInfo.aSelection.nStartPara;
        aSel.nStartPos = rInfo.aSelection.nStartPos;
        // adjust end position
        if( (aSel.nEndPara < aSel.nStartPara) || ((aSel.nEndPara == aSel.nStartPara) && (aSel.nEndPos < aSel.nStartPos)) )
        {
            aSel.nEndPara = aSel.nStartPara;
            aSel.nEndPos = aSel.nStartPos;
        }
    }

    void ScHTMLEntry::AdjustEnd( const HtmlImportInfo& rInfo )
    {
        OSL_ENSURE( (aSel.nEndPara < rInfo.aSelection.nEndPara) ||
                    ((aSel.nEndPara == rInfo.aSelection.nEndPara) && (aSel.nEndPos <= rInfo.aSelection.nEndPos)),
                    "ScHTMLQueryParser::AdjustEntryEnd - invalid end position" );
        // set end position
        aSel.nEndPara = rInfo.aSelection.nEndPara;
        aSel.nEndPos = rInfo.aSelection.nEndPos;
    }

    void ScHTMLEntry::Strip( const EditEngine& rEditEngine )
    {
        // strip leading empty paragraphs
        while( (aSel.nStartPara < aSel.nEndPara) && (rEditEngine.GetTextLen( aSel.nStartPara ) <= aSel.nStartPos) )
        {
            ++aSel.nStartPara;
            aSel.nStartPos = 0;
        }
        // strip trailing empty paragraphs
        while( (aSel.nStartPara < aSel.nEndPara) && (aSel.nEndPos == 0) )
        {
            --aSel.nEndPara;
            aSel.nEndPos = rEditEngine.GetTextLen( aSel.nEndPara );
        }
    }

    /** A map of ScHTMLTable objects.

        Organizes the tables with a unique table key. Stores nested tables inside
        the parent table and forms in this way a tree structure of tables. An
        instance of this class ownes the contained table objects and deletes them
        on destruction.
     */
    class ScHTMLTableMap final
    {
    private:
        typedef std::shared_ptr< ScHTMLTable >          ScHTMLTablePtr;
        typedef std::map< ScHTMLTableId, ScHTMLTablePtr > ScHTMLTableStdMap;

    public:
        typedef ScHTMLTableStdMap::iterator             iterator;
        typedef ScHTMLTableStdMap::const_iterator       const_iterator;

    private:
        ScHTMLTable&        mrParentTable;      /// Reference to parent table.
        ScHTMLTableStdMap   maTables;           /// Container for all table objects.
        mutable ScHTMLTable* mpCurrTable;       /// Current table, used for fast search.

    public:
        explicit            ScHTMLTableMap( ScHTMLTable& rParentTable );

        const_iterator begin() const { return maTables.begin(); }
        const_iterator end() const { return maTables.end(); }

        /** Returns the specified table.
            @param nTableId  Unique identifier of the table.
            @param bDeep  true = searches deep in all nested table; false = only in this container. */
        ScHTMLTable*        FindTable( ScHTMLTableId nTableId, bool bDeep = true ) const;

        /** Inserts a new table into the container. This container owns the created table.
            @param bPreFormText  true = New table is based on preformatted text (<pre> tag). */
        ScHTMLTable*        CreateTable( const HtmlImportInfo& rInfo, bool bPreFormText );

    private:
        /** Sets a working table with its index for search optimization. */
        void         SetCurrTable( ScHTMLTable* pTable ) const
                                { if( pTable ) mpCurrTable = pTable; }
    };

    ScHTMLTableMap::ScHTMLTableMap( ScHTMLTable& rParentTable ) :
        mrParentTable(rParentTable),
        mpCurrTable(nullptr)
    {
    }

    ScHTMLTable* ScHTMLTableMap::FindTable( ScHTMLTableId nTableId, bool bDeep ) const
    {
        ScHTMLTable* pResult = nullptr;
        if( mpCurrTable && (nTableId == mpCurrTable->GetTableId()) )
            pResult = mpCurrTable;              // cached table
        else
        {
            const_iterator aFind = maTables.find( nTableId );
            if( aFind != maTables.end() )
                pResult = aFind->second.get();  // table from this container
        }

        // not found -> search deep in nested tables
        if( !pResult && bDeep )
            for( const_iterator aIter = begin(), aEnd = end(); !pResult && (aIter != aEnd); ++aIter )
                pResult = aIter->second->FindNestedTable( nTableId );

        SetCurrTable( pResult );
        return pResult;
    }

    ScHTMLTable* ScHTMLTableMap::CreateTable( const HtmlImportInfo& rInfo, bool bPreFormText )
    {
        ScHTMLTable* pTable = new ScHTMLTable( mrParentTable, rInfo, bPreFormText );
        maTables[ pTable->GetTableId() ].reset( pTable );
        SetCurrTable( pTable );
        return pTable;
    }

    /** Simplified forward iterator for convenience.

        Before the iterator can be dereferenced, it must be tested with the is()
        method. The iterator may be invalid directly after construction (e.g. empty
        container).
     */
    class ScHTMLTableIterator
    {
    public:
        /** Constructs the iterator for the passed table map.
            @param pTableMap  Pointer to the table map (is allowed to be NULL). */
        explicit            ScHTMLTableIterator( const ScHTMLTableMap* pTableMap );

        bool         is() const { return mpTableMap && maIter != maEnd; }
        ScHTMLTable* operator->() { return maIter->second.get(); }
        ScHTMLTableIterator& operator++() { ++maIter; return *this; }

    private:
        ScHTMLTableMap::const_iterator maIter;
        ScHTMLTableMap::const_iterator maEnd;
        const ScHTMLTableMap* mpTableMap;
    };

    ScHTMLTableIterator::ScHTMLTableIterator( const ScHTMLTableMap* pTableMap ) :
        mpTableMap(pTableMap)
    {
        if( pTableMap )
        {
            maIter = pTableMap->begin();
            maEnd = pTableMap->end();
        }
    }

    ScHTMLTableAutoId::ScHTMLTableAutoId( ScHTMLTableId& rnUnusedId ) :
        mnTableId( rnUnusedId ),
        mrnUnusedId( rnUnusedId )
    {
        ++mrnUnusedId;
    }

    ScHTMLTable::ScHTMLTable( ScHTMLTable& rParentTable, const HtmlImportInfo& rInfo, bool bPreFormText ) :
        mpParentTable( &rParentTable ),
        maTableId( rParentTable.maTableId.mrnUnusedId ),
        maTableItemSet( rParentTable.GetCurrItemSet() ),
        mrEditEngine( rParentTable.mrEditEngine ),
        mrEEParseList( rParentTable.mrEEParseList ),
        mpCurrEntryVector( nullptr ),
        maSize( 1, 1 ),
        mpParser(rParentTable.mpParser),
        mbBorderOn( false ),
        mbPreFormText( bPreFormText ),
        mbRowOn( false ),
        mbDataOn( false ),
        mbPushEmptyLine( false )
    {
        if( mbPreFormText )
        {
            ImplRowOn();
            ImplDataOn( ScHTMLSize( 1, 1 ) );
        }
        else
        {
            ProcessFormatOptions( maTableItemSet, rInfo );
            const HTMLOptions& rOptions = static_cast<HTMLParser*>(rInfo.pParser)->GetOptions();
            HTMLOptions::const_iterator itr = rOptions.begin(), itrEnd = rOptions.end();
            for (; itr != itrEnd; ++itr)
            {
                switch( itr->GetToken() )
                {
                    case HtmlOptionId::BORDER:
                        mbBorderOn = itr->GetString().isEmpty() || (itr->GetNumber() != 0);
                    break;
                    case HtmlOptionId::ID:
                        maTableName = itr->GetString();
                    break;
                    default: break;
                }
            }
        }

        CreateNewEntry( rInfo );
    }

    ScHTMLTable::ScHTMLTable(
        SfxItemPool& rPool,
        EditEngine& rEditEngine,
        std::vector<std::shared_ptr<ScEEParseEntry>>& rEEParseList,
        ScHTMLTableId& rnUnusedId, ScHTMLParser* pParser
    ) :
        mpParentTable( nullptr ),
        maTableId( rnUnusedId ),
        maTableItemSet( rPool ),
        mrEditEngine( rEditEngine ),
        mrEEParseList( rEEParseList ),
        mpCurrEntryVector( nullptr ),
        maSize( 1, 1 ),
        mpParser(pParser),
        mbBorderOn( false ),
        mbPreFormText( false ),
        mbRowOn( false ),
        mbDataOn( false ),
        mbPushEmptyLine( false )
    {
        // open the first "cell" of the document
        ImplRowOn();
        ImplDataOn( ScHTMLSize( 1, 1 ) );
        mxCurrEntry = CreateEntry();
    }

    ScHTMLTable::~ScHTMLTable()
    {
    }

    const SfxItemSet& ScHTMLTable::GetCurrItemSet() const
    {
        // first try cell item set, then row item set, then table item set
        return mxDataItemSet.get() ? *mxDataItemSet : (mxRowItemSet.get() ? *mxRowItemSet : maTableItemSet);
    }

    ScHTMLSize ScHTMLTable::GetSpan( const ScHTMLPos& rCellPos ) const
    {
        ScHTMLSize aSpan( 1, 1 );
        const ScRange* pRange = nullptr;
        if(  ( (pRange = maVMergedCells.Find( rCellPos.MakeAddr() ) ) != nullptr)
          || ( (pRange = maHMergedCells.Find( rCellPos.MakeAddr() ) ) != nullptr)
          )
            aSpan.Set( pRange->aEnd.Col() - pRange->aStart.Col() + 1, pRange->aEnd.Row() - pRange->aStart.Row() + 1 );
        return aSpan;
    }

    ScHTMLTable* ScHTMLTable::FindNestedTable( ScHTMLTableId nTableId ) const
    {
        return mxNestedTables.get() ? mxNestedTables->FindTable( nTableId ) : nullptr;
    }

    void ScHTMLTable::PutItem( const SfxPoolItem& rItem )
    {
        OSL_ENSURE( mxCurrEntry.get(), "ScHTMLTable::PutItem - no current entry" );
        if( mxCurrEntry.get() && mxCurrEntry->IsEmpty() )
            mxCurrEntry->GetItemSet().Put( rItem );
    }

    void ScHTMLTable::PutText( const HtmlImportInfo& rInfo )
    {
        OSL_ENSURE( mxCurrEntry.get(), "ScHTMLTable::PutText - no current entry" );
        if( mxCurrEntry.get() )
        {
            if( !mxCurrEntry->HasContents() && IsSpaceCharInfo( rInfo ) )
                mxCurrEntry->AdjustStart( rInfo );
            else
                mxCurrEntry->AdjustEnd( rInfo );
        }
    }

    void ScHTMLTable::InsertPara( const HtmlImportInfo& rInfo )
    {
        if( mxCurrEntry.get() && mbDataOn && !IsEmptyCell() )
            mxCurrEntry->SetImportAlways();
        PushEntry( rInfo );
        CreateNewEntry( rInfo );
        InsertLeadingEmptyLine();
    }

    void ScHTMLTable::BreakOn()
    {
        // empty line, if <br> is at start of cell
        mbPushEmptyLine = !mbPreFormText && mbDataOn && IsEmptyCell();
    }

    void ScHTMLTable::HeadingOn()
    {
        // call directly, InsertPara() has not been called before
        InsertLeadingEmptyLine();
    }

    void ScHTMLTable::InsertLeadingEmptyLine()
    {
        // empty line, if <p>, </p>, <h?>, or </h*> are not at start of cell
        mbPushEmptyLine = !mbPreFormText && mbDataOn && !IsEmptyCell();
    }

    void ScHTMLTable::AnchorOn()
    {
        OSL_ENSURE( mxCurrEntry.get(), "ScHTMLTable::AnchorOn - no current entry" );
        // don't skip entries with single hyperlinks
        if( mxCurrEntry.get() )
            mxCurrEntry->SetImportAlways();
    }

    ScHTMLTable* ScHTMLTable::TableOn( const HtmlImportInfo& rInfo )
    {
        PushEntry( rInfo );
        return InsertNestedTable( rInfo, false );
    }

    ScHTMLTable* ScHTMLTable::TableOff( const HtmlImportInfo& rInfo )
    {
        return mbPreFormText ? this : CloseTable( rInfo );
    }

    ScHTMLTable* ScHTMLTable::PreOn( const HtmlImportInfo& rInfo )
    {
        PushEntry( rInfo );
        return InsertNestedTable( rInfo, true );
    }

    ScHTMLTable* ScHTMLTable::PreOff( const HtmlImportInfo& rInfo )
    {
        return mbPreFormText ? CloseTable( rInfo ) : this;
    }

    void ScHTMLTable::RowOn( const HtmlImportInfo& rInfo )
    {
        PushEntry( rInfo, true );
        if( mpParentTable && !mbPreFormText )   // no rows allowed in global and preformatted tables
        {
            ImplRowOn();
            ProcessFormatOptions( *mxRowItemSet, rInfo );
        }
        CreateNewEntry( rInfo );
    }

    void ScHTMLTable::RowOff( const HtmlImportInfo& rInfo )
    {
        PushEntry( rInfo, true );
        if( mpParentTable && !mbPreFormText )   // no rows allowed in global and preformatted tables
            ImplRowOff();
        CreateNewEntry( rInfo );
    }

    namespace {

    /**
     * Decode a number format string stored in Excel-generated HTML's CSS
     * region.
     */
    OUString decodeNumberFormat(const OUString& rFmt)
    {
        OUStringBuffer aBuf;
        const sal_Unicode* p = rFmt.getStr();
        sal_Int32 n = rFmt.getLength();
        for (sal_Int32 i = 0; i < n; ++i, ++p)
        {
            if (*p == '\\')
            {
                // Skip '\'.
                ++i;
                ++p;

                // Parse all subsequent digits until first non-digit is found.
                sal_Int32 nDigitCount = 0;
                const sal_Unicode* p1 = p;
                for (; i < n; ++i, ++p, ++nDigitCount)
                {
                    if (*p < '0' || '9' < *p)
                    {
                        --i;
                        --p;
                        break;
                    }

                }
                if (nDigitCount)
                {
                    // Hex-encoded character found. Decode it back into its
                    // original character. An example of number format with
                    // hex-encoded chars: "\0022$\0022\#\,\#\#0\.00"
                    sal_uInt32 nVal = OUString(p1, nDigitCount).toUInt32(16);
                    aBuf.append(static_cast<sal_Unicode>(nVal));
                }
            }
            else
                aBuf.append(*p);
        }
        return aBuf.makeStringAndClear();
    }

    }

    void ScHTMLTable::DataOn( const HtmlImportInfo& rInfo )
    {
        PushEntry( rInfo, true );
        if( mpParentTable && !mbPreFormText )   // no cells allowed in global and preformatted tables
        {
            // read needed options from the <td> tag
            ScHTMLSize aSpanSize( 1, 1 );
            boost::optional<OUString> pValStr, pNumStr;
            const HTMLOptions& rOptions = static_cast<HTMLParser*>(rInfo.pParser)->GetOptions();
            HTMLOptions::const_iterator itr = rOptions.begin(), itrEnd = rOptions.end();
            sal_uInt32 nNumberFormat = NUMBERFORMAT_ENTRY_NOT_FOUND;
            for (; itr != itrEnd; ++itr)
            {
                switch (itr->GetToken())
                {
                    case HtmlOptionId::COLSPAN:
                        aSpanSize.mnCols = static_cast<SCCOL>( getLimitedValue<sal_Int32>( itr->GetString().toInt32(), 1, 256 ) );
                    break;
                    case HtmlOptionId::ROWSPAN:
                        aSpanSize.mnRows = static_cast<SCROW>( getLimitedValue<sal_Int32>( itr->GetString().toInt32(), 1, 256 ) );
                    break;
                    case HtmlOptionId::SDVAL:
                        pValStr = itr->GetString();
                    break;
                    case HtmlOptionId::SDNUM:
                        pNumStr = itr->GetString();
                    break;
                    case HtmlOptionId::CLASS:
                    {
                        // Pick up the number format associated with this class (if
                        // any).
                        OUString aClass = itr->GetString();
                        const ScHTMLStyles& rStyles = mpParser->GetStyles();
                        const OUString& rVal = rStyles.getPropertyValue("td", aClass, "mso-number-format");
                        if (!rVal.isEmpty())
                        {
                            OUString aNumFmt = decodeNumberFormat(rVal);

                            nNumberFormat = GetFormatTable()->GetEntryKey(aNumFmt);
                            if (nNumberFormat == NUMBERFORMAT_ENTRY_NOT_FOUND)
                            {
                                sal_Int32 nErrPos  = 0;
                                SvNumFormatType nDummy;
                                bool bValidFmt = GetFormatTable()->PutEntry(aNumFmt, nErrPos, nDummy, nNumberFormat);
                                if (!bValidFmt)
                                    nNumberFormat = NUMBERFORMAT_ENTRY_NOT_FOUND;
                            }
                        }
                    }
                    break;
                    default: break;
                }
            }

            ImplDataOn( aSpanSize );

            if (nNumberFormat != NUMBERFORMAT_ENTRY_NOT_FOUND)
                mxDataItemSet->Put( SfxUInt32Item(ATTR_VALUE_FORMAT, nNumberFormat) );

            ProcessFormatOptions( *mxDataItemSet, rInfo );
            CreateNewEntry( rInfo );
            mxCurrEntry->pValStr = std::move(pValStr);
            mxCurrEntry->pNumStr = std::move(pNumStr);
        }
        else
            CreateNewEntry( rInfo );
    }

    void ScHTMLTable::DataOff( const HtmlImportInfo& rInfo )
    {
        PushEntry( rInfo, true );
        if( mpParentTable && !mbPreFormText )   // no cells allowed in global and preformatted tables
            ImplDataOff();
        CreateNewEntry( rInfo );
    }

    void ScHTMLTable::BodyOn( const HtmlImportInfo& rInfo )
    {
        bool bPushed = PushEntry( rInfo );
        if( !mpParentTable )
        {
            // do not start new row, if nothing (no title) precedes the body.
            if( bPushed || !mbRowOn )
                ImplRowOn();
            if( bPushed || !mbDataOn )
                ImplDataOn( ScHTMLSize( 1, 1 ) );
            ProcessFormatOptions( *mxDataItemSet, rInfo );
        }
        CreateNewEntry( rInfo );
    }

    void ScHTMLTable::BodyOff( const HtmlImportInfo& rInfo )
    {
        PushEntry( rInfo );
        if( !mpParentTable )
        {
            ImplDataOff();
            ImplRowOff();
        }
        CreateNewEntry( rInfo );
    }

    ScHTMLTable* ScHTMLTable::CloseTable( const HtmlImportInfo& rInfo )
    {
        if( mpParentTable )     // not allowed to close global table
        {
            PushEntry( rInfo, mbDataOn );
            ImplDataOff();
            ImplRowOff();
            mpParentTable->PushTableEntry( GetTableId() );
            mpParentTable->CreateNewEntry( rInfo );
            if( mbPreFormText ) // enclose preformatted table with empty lines in parent table
                mpParentTable->InsertLeadingEmptyLine();
            return mpParentTable;
        }
        return this;
    }

    SCCOLROW ScHTMLTable::GetDocSize( ScHTMLOrient eOrient, SCCOLROW nCellPos ) const
    {
        const ScSizeVec& rSizes = maCumSizes[ eOrient ];
        size_t nIndex = static_cast< size_t >( nCellPos );
        if( nIndex >= rSizes.size() ) return 0;
        return (nIndex == 0) ? rSizes.front() : (rSizes[ nIndex ] - rSizes[ nIndex - 1 ]);
    }

    SCCOLROW ScHTMLTable::GetDocSize( ScHTMLOrient eOrient, SCCOLROW nCellBegin, SCCOLROW nCellEnd ) const
    {
        const ScSizeVec& rSizes = maCumSizes[ eOrient ];
        size_t nBeginIdx = static_cast< size_t >( std::max< SCCOLROW >( nCellBegin, 0 ) );
        size_t nEndIdx = static_cast< size_t >( std::min< SCCOLROW >( nCellEnd, static_cast< SCCOLROW >( rSizes.size() ) ) );
        if (nBeginIdx >= nEndIdx ) return 0;
        return rSizes[ nEndIdx - 1 ] - ((nBeginIdx == 0) ? 0 : rSizes[ nBeginIdx - 1 ]);
    }

    SCCOLROW ScHTMLTable::GetDocSize( ScHTMLOrient eOrient ) const
    {
        const ScSizeVec& rSizes = maCumSizes[ eOrient ];
        return rSizes.empty() ? 0 : rSizes.back();
    }

    ScHTMLSize ScHTMLTable::GetDocSize( const ScHTMLPos& rCellPos ) const
    {
        ScHTMLSize aCellSpan = GetSpan( rCellPos );
        return ScHTMLSize(
            static_cast< SCCOL >( GetDocSize( tdCol, rCellPos.mnCol, rCellPos.mnCol + aCellSpan.mnCols ) ),
            static_cast< SCROW >( GetDocSize( tdRow, rCellPos.mnRow, rCellPos.mnRow + aCellSpan.mnRows ) ) );
    }

    SCCOLROW ScHTMLTable::GetDocPos( ScHTMLOrient eOrient, SCCOLROW nCellPos ) const
    {
        return maDocBasePos.Get( eOrient ) + GetDocSize( eOrient, 0, nCellPos );
    }

    ScHTMLPos ScHTMLTable::GetDocPos( const ScHTMLPos& rCellPos ) const
    {
        return ScHTMLPos(
            static_cast< SCCOL >( GetDocPos( tdCol, rCellPos.mnCol ) ),
            static_cast< SCROW >( GetDocPos( tdRow, rCellPos.mnRow ) ) );
    }

    void ScHTMLTable::GetDocRange( ScRange& rRange ) const
    {
        rRange.aStart = rRange.aEnd = maDocBasePos.MakeAddr();
        ScAddress aErrorPos( ScAddress::UNINITIALIZED );
        if (!rRange.aEnd.Move( static_cast< SCCOL >( GetDocSize( tdCol ) ) - 1,
                    static_cast< SCROW >( GetDocSize( tdRow ) ) - 1, 0, aErrorPos))
        {
            assert(!"can't move");
        }
    }

    void ScHTMLTable::ApplyCellBorders( ScDocument* pDoc, const ScAddress& rFirstPos ) const
    {
        OSL_ENSURE( pDoc, "ScHTMLTable::ApplyCellBorders - no document" );
        if( pDoc && mbBorderOn )
        {
            const SCCOL nLastCol = maSize.mnCols - 1;
            const SCROW nLastRow = maSize.mnRows - 1;
            const long nOuterLine = DEF_LINE_WIDTH_2;
            const long nInnerLine = DEF_LINE_WIDTH_0;
            SvxBorderLine aOuterLine(nullptr, nOuterLine, SvxBorderLineStyle::SOLID);
            SvxBorderLine aInnerLine(nullptr, nInnerLine, SvxBorderLineStyle::SOLID);
            SvxBoxItem aBorderItem( ATTR_BORDER );

            for( SCCOL nCol = 0; nCol <= nLastCol; ++nCol )
            {
                SvxBorderLine* pLeftLine = (nCol == 0) ? &aOuterLine : &aInnerLine;
                SvxBorderLine* pRightLine = (nCol == nLastCol) ? &aOuterLine : &aInnerLine;
                SCCOL nCellCol1 = static_cast< SCCOL >( GetDocPos( tdCol, nCol ) ) + rFirstPos.Col();
                SCCOL nCellCol2 = nCellCol1 + static_cast< SCCOL >( GetDocSize( tdCol, nCol ) ) - 1;
                for( SCROW nRow = 0; nRow <= nLastRow; ++nRow )
                {
                    SvxBorderLine* pTopLine = (nRow == 0) ? &aOuterLine : &aInnerLine;
                    SvxBorderLine* pBottomLine = (nRow == nLastRow) ? &aOuterLine : &aInnerLine;
                    SCROW nCellRow1 = GetDocPos( tdRow, nRow ) + rFirstPos.Row();
                    SCROW nCellRow2 = nCellRow1 + GetDocSize( tdRow, nRow ) - 1;
                    for( SCCOL nCellCol = nCellCol1; nCellCol <= nCellCol2; ++nCellCol )
                    {
                        aBorderItem.SetLine( (nCellCol == nCellCol1) ? pLeftLine : nullptr, SvxBoxItemLine::LEFT );
                        aBorderItem.SetLine( (nCellCol == nCellCol2) ? pRightLine : nullptr, SvxBoxItemLine::RIGHT );
                        for( SCROW nCellRow = nCellRow1; nCellRow <= nCellRow2; ++nCellRow )
                        {
                            aBorderItem.SetLine( (nCellRow == nCellRow1) ? pTopLine : nullptr, SvxBoxItemLine::TOP );
                            aBorderItem.SetLine( (nCellRow == nCellRow2) ? pBottomLine : nullptr, SvxBoxItemLine::BOTTOM );
                            pDoc->ApplyAttr( nCellCol, nCellRow, rFirstPos.Tab(), aBorderItem );
                        }
                    }
                }
            }
        }

        for( ScHTMLTableIterator aIter( mxNestedTables.get() ); aIter.is(); ++aIter )
            aIter->ApplyCellBorders( pDoc, rFirstPos );
    }

    SvNumberFormatter* ScHTMLTable::GetFormatTable()
    {
        return mpParser->GetDoc().GetFormatTable();
    }

    bool ScHTMLTable::IsEmptyCell() const
    {
        return mpCurrEntryVector && mpCurrEntryVector->empty();
    }

    bool ScHTMLTable::IsSpaceCharInfo( const HtmlImportInfo& rInfo )
    {
        return (rInfo.nToken == HtmlTokenId::TEXTTOKEN) && (rInfo.aText.getLength() == 1) && (rInfo.aText[ 0 ] == ' ');
    }

    ScHTMLTable::ScHTMLEntryPtr ScHTMLTable::CreateEntry() const
    {
        return o3tl::make_unique<ScHTMLEntry>( GetCurrItemSet() );
    }

    void ScHTMLTable::CreateNewEntry( const HtmlImportInfo& rInfo )
    {
        OSL_ENSURE( !mxCurrEntry.get(), "ScHTMLTable::CreateNewEntry - old entry still present" );
        mxCurrEntry = CreateEntry();
        mxCurrEntry->aSel = rInfo.aSelection;
    }

    void ScHTMLTable::ImplPushEntryToVector( ScHTMLEntryVector& rEntryVector, ScHTMLEntryPtr& rxEntry )
    {
        // HTML entry list does not own the entries
        rEntryVector.push_back( rxEntry.get() );
        // mrEEParseList (reference to member of ScEEParser) owns the entries
        mrEEParseList.push_back(std::shared_ptr<ScEEParseEntry>(rxEntry.release()));
    }

    bool ScHTMLTable::PushEntry( ScHTMLEntryPtr& rxEntry )
    {
        bool bPushed = false;
        if( rxEntry.get() && rxEntry->HasContents() )
        {
            if( mpCurrEntryVector )
            {
                if( mbPushEmptyLine )
                {
                    ScHTMLEntryPtr xEmptyEntry = CreateEntry();
                    ImplPushEntryToVector( *mpCurrEntryVector, xEmptyEntry );
                    mbPushEmptyLine = false;
                }
                ImplPushEntryToVector( *mpCurrEntryVector, rxEntry );
                bPushed = true;
            }
            else if( mpParentTable )
            {
                bPushed = mpParentTable->PushEntry( rxEntry );
            }
            else
            {
                OSL_FAIL( "ScHTMLTable::PushEntry - cannot push entry, no parent found" );
            }
        }
        return bPushed;
    }

    bool ScHTMLTable::PushEntry( const HtmlImportInfo& rInfo, bool bLastInCell )
    {
        OSL_ENSURE( mxCurrEntry.get(), "ScHTMLTable::PushEntry - no current entry" );
        bool bPushed = false;
        if( mxCurrEntry.get() )
        {
            mxCurrEntry->AdjustEnd( rInfo );
            mxCurrEntry->Strip( mrEditEngine );

            // import entry always, if it is the last in cell, and cell is still empty
            if( bLastInCell && IsEmptyCell() )
            {
                mxCurrEntry->SetImportAlways();
                // don't insert empty lines before single empty entries
                if( mxCurrEntry->IsEmpty() )
                    mbPushEmptyLine = false;
            }

            bPushed = PushEntry( mxCurrEntry );
            mxCurrEntry.reset();
        }
        return bPushed;
    }

    void ScHTMLTable::PushTableEntry( ScHTMLTableId nTableId )
    {
        OSL_ENSURE( nTableId != SC_HTML_GLOBAL_TABLE, "ScHTMLTable::PushTableEntry - cannot push global table" );
        if( nTableId != SC_HTML_GLOBAL_TABLE )
        {
            ScHTMLEntryPtr xEntry( new ScHTMLEntry( maTableItemSet, nTableId ) );
            PushEntry( xEntry );
        }
    }

    ScHTMLTable* ScHTMLTable::GetExistingTable( ScHTMLTableId nTableId ) const
    {
        ScHTMLTable* pTable = ((nTableId != SC_HTML_GLOBAL_TABLE) && mxNestedTables.get()) ?
            mxNestedTables->FindTable( nTableId, false ) : nullptr;
        OSL_ENSURE( pTable || (nTableId == SC_HTML_GLOBAL_TABLE), "ScHTMLTable::GetExistingTable - table not found" );
        return pTable;
    }

    ScHTMLTable* ScHTMLTable::InsertNestedTable( const HtmlImportInfo& rInfo, bool bPreFormText )
    {
        if( !mxNestedTables.get() )
            mxNestedTables.reset( new ScHTMLTableMap( *this ) );
        if( bPreFormText )      // enclose new preformatted table with empty lines
            InsertLeadingEmptyLine();
        return mxNestedTables->CreateTable( rInfo, bPreFormText );
    }

    void ScHTMLTable::InsertNewCell( const ScHTMLSize& rSpanSize )
    {
        ScRange* pRange;

        /*  Find an unused cell by skipping all merged ranges that cover the
            current cell position stored in maCurrCell. */
        while( ((pRange = maVMergedCells.Find( maCurrCell.MakeAddr() )) != nullptr) || ((pRange = maHMergedCells.Find( maCurrCell.MakeAddr() )) != nullptr) )
            maCurrCell.mnCol = pRange->aEnd.Col() + 1;
        mpCurrEntryVector = &maEntryMap[ maCurrCell ];

        /*  If the new cell is merged horizontally, try to find collisions with
            other vertically merged ranges. In this case, shrink existing
            vertically merged ranges (do not shrink the new cell). */
        SCCOL nColEnd = maCurrCell.mnCol + rSpanSize.mnCols;
        for( ScAddress aAddr( maCurrCell.MakeAddr() ); aAddr.Col() < nColEnd; aAddr.IncCol() )
            if( (pRange = maVMergedCells.Find( aAddr )) != nullptr )
                pRange->aEnd.SetRow( maCurrCell.mnRow - 1 );

        // insert the new range into the cell lists
        ScRange aNewRange( maCurrCell.MakeAddr() );
        ScAddress aErrorPos( ScAddress::UNINITIALIZED );
        if (!aNewRange.aEnd.Move( rSpanSize.mnCols - 1, rSpanSize.mnRows - 1, 0, aErrorPos))
        {
            assert(!"can't move");
        }
        if( rSpanSize.mnRows > 1 )
        {
            maVMergedCells.push_back( aNewRange );
            /*  Do not insert vertically merged ranges into maUsedCells yet,
                because they may be shrunken (see above). The final vertically
                merged ranges are inserted in FillEmptyCells(). */
        }
        else
        {
            if( rSpanSize.mnCols > 1 )
                maHMergedCells.push_back( aNewRange );
            /*  Insert horizontally merged ranges and single cells into
                maUsedCells, they will not be changed anymore. */
            maUsedCells.Join( aNewRange );
        }

        // adjust table size
        maSize.mnCols = std::max< SCCOL >( maSize.mnCols, aNewRange.aEnd.Col() + 1 );
        maSize.mnRows = std::max< SCROW >( maSize.mnRows, aNewRange.aEnd.Row() + 1 );
    }

    void ScHTMLTable::ImplRowOn()
    {
        if( mbRowOn )
            ImplRowOff();
        mxRowItemSet.reset( new SfxItemSet( maTableItemSet ) );
        maCurrCell.mnCol = 0;
        mbRowOn = true;
        mbDataOn = false;
    }

    void ScHTMLTable::ImplRowOff()
    {
        if( mbDataOn )
            ImplDataOff();
        if( mbRowOn )
        {
            mxRowItemSet.reset();
            ++maCurrCell.mnRow;
            mbRowOn = mbDataOn = false;
        }
    }

    void ScHTMLTable::ImplDataOn( const ScHTMLSize& rSpanSize )
    {
        if( mbDataOn )
            ImplDataOff();
        if( !mbRowOn )
            ImplRowOn();
        mxDataItemSet.reset( new SfxItemSet( *mxRowItemSet ) );
        InsertNewCell( rSpanSize );
        mbDataOn = true;
        mbPushEmptyLine = false;
    }

    void ScHTMLTable::ImplDataOff()
    {
        if( mbDataOn )
        {
            mxDataItemSet.reset();
            ++maCurrCell.mnCol;
            mpCurrEntryVector = nullptr;
            mbDataOn = false;
        }
    }

    void ScHTMLTable::ProcessFormatOptions( SfxItemSet& rItemSet, const HtmlImportInfo& rInfo )
    {
        // special handling for table header cells
        if( rInfo.nToken == HtmlTokenId::TABLEHEADER_ON )
        {
            rItemSet.Put( SvxWeightItem( WEIGHT_BOLD, ATTR_FONT_WEIGHT ) );
            rItemSet.Put( SvxHorJustifyItem( SvxCellHorJustify::Center, ATTR_HOR_JUSTIFY ) );
        }

        const HTMLOptions& rOptions = static_cast<HTMLParser*>(rInfo.pParser)->GetOptions();
        HTMLOptions::const_iterator itr = rOptions.begin(), itrEnd = rOptions.end();
        for (; itr != itrEnd; ++itr)
        {
            switch( itr->GetToken() )
            {
                case HtmlOptionId::ALIGN:
                {
                    SvxCellHorJustify eVal = SvxCellHorJustify::Standard;
                    const OUString& rOptVal = itr->GetString();
                    if( rOptVal.equalsIgnoreAsciiCase( OOO_STRING_SVTOOLS_HTML_AL_right ) )
                        eVal = SvxCellHorJustify::Right;
                    else if( rOptVal.equalsIgnoreAsciiCase( OOO_STRING_SVTOOLS_HTML_AL_center ) )
                        eVal = SvxCellHorJustify::Center;
                    else if( rOptVal.equalsIgnoreAsciiCase( OOO_STRING_SVTOOLS_HTML_AL_left ) )
                        eVal = SvxCellHorJustify::Left;
                    if( eVal != SvxCellHorJustify::Standard )
                        rItemSet.Put( SvxHorJustifyItem( eVal, ATTR_HOR_JUSTIFY ) );
                }
                break;

                case HtmlOptionId::VALIGN:
                {
                    SvxCellVerJustify eVal = SvxCellVerJustify::Standard;
                    const OUString& rOptVal = itr->GetString();
                    if( rOptVal.equalsIgnoreAsciiCase( OOO_STRING_SVTOOLS_HTML_VA_top ) )
                        eVal = SvxCellVerJustify::Top;
                    else if( rOptVal.equalsIgnoreAsciiCase( OOO_STRING_SVTOOLS_HTML_VA_middle ) )
                        eVal = SvxCellVerJustify::Center;
                    else if( rOptVal.equalsIgnoreAsciiCase( OOO_STRING_SVTOOLS_HTML_VA_bottom ) )
                        eVal = SvxCellVerJustify::Bottom;
                    if( eVal != SvxCellVerJustify::Standard )
                        rItemSet.Put( SvxVerJustifyItem( eVal, ATTR_VER_JUSTIFY ) );
                }
                break;

                case HtmlOptionId::BGCOLOR:
                {
                    Color aColor;
                    itr->GetColor( aColor );
                    rItemSet.Put( SvxBrushItem( aColor, ATTR_BACKGROUND ) );
                }
                break;
                default: break;
            }
        }
    }

    void ScHTMLTable::SetDocSize( ScHTMLOrient eOrient, SCCOLROW nCellPos, SCCOLROW nSize )
    {
        OSL_ENSURE( nCellPos >= 0, "ScHTMLTable::SetDocSize - unexpected negative position" );
        ScSizeVec& rSizes = maCumSizes[ eOrient ];
        size_t nIndex = static_cast< size_t >( nCellPos );
        // expand with height/width == 1
        while( nIndex >= rSizes.size() )
            rSizes.push_back( rSizes.empty() ? 1 : (rSizes.back() + 1) );
        // update size of passed position and all following
        // #i109987# only grow, don't shrink - use the largest needed size
        SCCOLROW nDiff = nSize - ((nIndex == 0) ? rSizes.front() : (rSizes[ nIndex ] - rSizes[ nIndex - 1 ]));
        if( nDiff > 0 )
            for( ScSizeVec::iterator aIt = rSizes.begin() + nIndex, aEnd = rSizes.end(); aIt != aEnd; ++aIt )
                *aIt += nDiff;
    }

    void ScHTMLTable::CalcNeededDocSize(
            ScHTMLOrient eOrient, SCCOLROW nCellPos, SCCOLROW nCellSpan, SCCOLROW nRealDocSize )
    {
        SCCOLROW nDiffSize = 0;
        // in merged columns/rows: reduce needed size by size of leading columns
        while( nCellSpan > 1 )
        {
            nDiffSize += GetDocSize( eOrient, nCellPos );
            --nCellSpan;
            ++nCellPos;
        }
        // set remaining needed size to last column/row
        nRealDocSize -= std::min< SCCOLROW >( nRealDocSize - 1, nDiffSize );
        SetDocSize( eOrient, nCellPos, nRealDocSize );
    }

    void ScHTMLTable::FillEmptyCells()
    {
        for( ScHTMLTableIterator aIter( mxNestedTables.get() ); aIter.is(); ++aIter )
            aIter->FillEmptyCells();

        // insert the final vertically merged ranges into maUsedCells
        for ( size_t i = 0, nRanges = maVMergedCells.size(); i < nRanges; ++i )
        {
            ScRange & rRange = maVMergedCells[ i ];
            maUsedCells.Join( rRange );
        }

        for( ScAddress aAddr; aAddr.Row() < maSize.mnRows; aAddr.IncRow() )
        {
            for( aAddr.SetCol( 0 ); aAddr.Col() < maSize.mnCols; aAddr.IncCol() )
            {
                if( !maUsedCells.Find( aAddr ) )
                {
                    // create a range for the lock list (used to calc. cell span)
                    ScRange aRange( aAddr );
                    do
                    {
                        aRange.aEnd.IncCol();
                    }
                    while( (aRange.aEnd.Col() < maSize.mnCols) && !maUsedCells.Find( aRange.aEnd ) );
                    aRange.aEnd.IncCol( -1 );
                    maUsedCells.Join( aRange );

                    // insert a dummy entry
                    ScHTMLEntryPtr xEntry = CreateEntry();
                    ImplPushEntryToVector( maEntryMap[ ScHTMLPos( aAddr ) ], xEntry );
                }
            }
        }
    }

    void ScHTMLTable::RecalcDocSize()
    {
        // recalc table sizes recursively from inner to outer
        for( ScHTMLTableIterator aIter( mxNestedTables.get() ); aIter.is(); ++aIter )
            aIter->RecalcDocSize();

        /*  Two passes: first calculates the sizes of single columns/rows, then
            the sizes of spanned columns/rows. This allows to fill nested tables
            into merged cells optimally. */
        static const sal_uInt16 PASS_SINGLE = 0;
        static const sal_uInt16 PASS_SPANNED = 1;
        for( sal_uInt16 nPass = PASS_SINGLE; nPass <= PASS_SPANNED; ++nPass )
        {
            // iterate through every table cell
            ScHTMLEntryMap::const_iterator aMapIterEnd = maEntryMap.end();
            for( ScHTMLEntryMap::const_iterator aMapIter = maEntryMap.begin(); aMapIter != aMapIterEnd; ++aMapIter )
            {
                const ScHTMLPos& rCellPos = aMapIter->first;
                ScHTMLSize aCellSpan = GetSpan( rCellPos );

                const ScHTMLEntryVector& rEntryVector = aMapIter->second;
                ScHTMLEntryVector::const_iterator aVectorIter;
                ScHTMLEntryVector::const_iterator aVectorIterEnd = rEntryVector.end();

                // process the dimension of the current cell in this pass?
                // (pass is single and span is 1) or (pass is not single and span is not 1)
                bool bProcessColWidth = ((nPass == PASS_SINGLE) == (aCellSpan.mnCols == 1));
                bool bProcessRowHeight = ((nPass == PASS_SINGLE) == (aCellSpan.mnRows == 1));
                if( bProcessColWidth || bProcessRowHeight )
                {
                    ScHTMLSize aDocSize( 1, 0 );    // resulting size of the cell in document

                    // expand the cell size for each cell parse entry
                    for( aVectorIter = rEntryVector.begin(); aVectorIter != aVectorIterEnd; ++aVectorIter )
                    {
                        ScHTMLTable* pTable = GetExistingTable( (*aVectorIter)->GetTableId() );
                        // find entry with maximum width
                        if( bProcessColWidth && pTable )
                            aDocSize.mnCols = std::max( aDocSize.mnCols, static_cast< SCCOL >( pTable->GetDocSize( tdCol ) ) );
                        // add up height of each entry
                        if( bProcessRowHeight )
                            aDocSize.mnRows += pTable ? pTable->GetDocSize( tdRow ) : 1;
                    }
                    if( !aDocSize.mnRows )
                        aDocSize.mnRows = 1;

                    if( bProcessColWidth )
                        CalcNeededDocSize( tdCol, rCellPos.mnCol, aCellSpan.mnCols, aDocSize.mnCols );
                    if( bProcessRowHeight )
                        CalcNeededDocSize( tdRow, rCellPos.mnRow, aCellSpan.mnRows, aDocSize.mnRows );
                }
            }
        }
    }

    void ScHTMLTable::RecalcDocPos( const ScHTMLPos& rBasePos )
    {
        maDocBasePos = rBasePos;
        // after the previous assignment it is allowed to call GetDocPos() methods

        // iterate through every table cell
        ScHTMLEntryMap::iterator aMapIterEnd = maEntryMap.end();
        for( ScHTMLEntryMap::iterator aMapIter = maEntryMap.begin(); aMapIter != aMapIterEnd; ++aMapIter )
        {
            // fixed doc position of the entire cell (first entry)
            const ScHTMLPos aCellDocPos( GetDocPos( aMapIter->first ) );
            // fixed doc size of the entire cell
            const ScHTMLSize aCellDocSize( GetDocSize( aMapIter->first ) );

            // running doc position for single entries
            ScHTMLPos aEntryDocPos( aCellDocPos );

            ScHTMLEntryVector& rEntryVector = aMapIter->second;
            ScHTMLEntry* pEntry = nullptr;
            ScHTMLEntryVector::iterator aVectorIterEnd = rEntryVector.end();
            for( ScHTMLEntryVector::iterator aVectorIter = rEntryVector.begin(); aVectorIter != aVectorIterEnd; ++aVectorIter )
            {
                pEntry = *aVectorIter;
                if( ScHTMLTable* pTable = GetExistingTable( pEntry->GetTableId() ) )
                {
                    pTable->RecalcDocPos( aEntryDocPos );   // recalc nested table
                    pEntry->nCol = SCCOL_MAX;
                    pEntry->nRow = SCROW_MAX;
                    SCROW nTableRows = static_cast< SCROW >( pTable->GetDocSize( tdRow ) );

                    // use this entry to pad empty space right of table
                    if( mpParentTable )     // ... but not in global table
                    {
                        SCCOL nStartCol = aEntryDocPos.mnCol + static_cast< SCCOL >( pTable->GetDocSize( tdCol ) );
                        SCCOL nNextCol = aEntryDocPos.mnCol + aCellDocSize.mnCols;
                        if( nStartCol < nNextCol )
                        {
                            pEntry->nCol = nStartCol;
                            pEntry->nRow = aEntryDocPos.mnRow;
                            pEntry->nColOverlap = nNextCol - nStartCol;
                            pEntry->nRowOverlap = nTableRows;
                        }
                    }
                    aEntryDocPos.mnRow += nTableRows;
                }
                else
                {
                    pEntry->nCol = aEntryDocPos.mnCol;
                    pEntry->nRow = aEntryDocPos.mnRow;
                    if( mpParentTable )    // do not merge in global table
                        pEntry->nColOverlap = aCellDocSize.mnCols;
                    ++aEntryDocPos.mnRow;
                }
            }

            // pEntry points now to last entry.
            if( pEntry )
            {
                if( (pEntry == rEntryVector.front()) && (pEntry->GetTableId() == SC_HTML_NO_TABLE) )
                {
                    // pEntry is the only entry in this cell - merge rows of cell with single non-table entry.
                    pEntry->nRowOverlap = aCellDocSize.mnRows;
                }
                else
                {
                    // fill up incomplete entry lists
                    SCROW nFirstUnusedRow = aCellDocPos.mnRow + aCellDocSize.mnRows;
                    while( aEntryDocPos.mnRow < nFirstUnusedRow )
                    {
                        ScHTMLEntryPtr xDummyEntry( new ScHTMLEntry( pEntry->GetItemSet() ) );
                        xDummyEntry->nCol = aEntryDocPos.mnCol;
                        xDummyEntry->nRow = aEntryDocPos.mnRow;
                        xDummyEntry->nColOverlap = aCellDocSize.mnCols;
                        ImplPushEntryToVector( rEntryVector, xDummyEntry );
                        ++aEntryDocPos.mnRow;
                    }
                }
            }
        }
    }

    ScHTMLGlobalTable::ScHTMLGlobalTable(
        SfxItemPool& rPool,
        EditEngine& rEditEngine,
        std::vector<std::shared_ptr<ScEEParseEntry>>& rEEParseVector,
        ScHTMLTableId& rnUnusedId,
        ScHTMLParser* pParser
    ) :
        ScHTMLTable( rPool, rEditEngine, rEEParseVector, rnUnusedId, pParser )
    {
    }

    ScHTMLGlobalTable::~ScHTMLGlobalTable()
    {
    }

    void ScHTMLGlobalTable::Recalc()
    {
        // Fills up empty cells with a dummy entry. */
        FillEmptyCells();
        // recalc table sizes of all nested tables and this table
        RecalcDocSize();
        // recalc document positions of all entries in this table and in nested tables
        RecalcDocPos( GetDocPos() );
    }

    ScHTMLQueryParser::ScHTMLQueryParser( EditEngine* pEditEngine, ScDocument* pDoc ) :
        ScHTMLParser( pEditEngine, pDoc ),
        mnUnusedId( SC_HTML_GLOBAL_TABLE ),
        mbTitleOn( false )
    {
        mxGlobTable.reset(
            new ScHTMLGlobalTable(*pPool, *pEdit, maList, mnUnusedId, this));
        mpCurrTable = mxGlobTable.get();
    }

    ScHTMLQueryParser::~ScHTMLQueryParser()
    {
    }

    ErrCode ScHTMLQueryParser::Read( SvStream& rStrm, const OUString& rBaseURL  )
    {
        SvKeyValueIteratorRef xValues;
        SvKeyValueIterator* pAttributes = nullptr;

        SfxObjectShell* pObjSh = mpDoc->GetDocumentShell();
        if( pObjSh && pObjSh->IsLoading() )
        {
            pAttributes = pObjSh->GetHeaderAttributes();
        }
        else
        {
            /*  When not loading, set up fake HTTP headers to force the SfxHTMLParser
                to use UTF8 (used when pasting from clipboard) */
            const sal_Char* pCharSet = rtl_getBestMimeCharsetFromTextEncoding( RTL_TEXTENCODING_UTF8 );
            if( pCharSet )
            {
                OUString aContentType = "text/html; charset=";
                aContentType += OUString::createFromAscii( pCharSet );

                xValues = new SvKeyValueIterator;
                xValues->Append( SvKeyValue( OOO_STRING_SVTOOLS_HTML_META_content_type, aContentType ) );
                pAttributes = xValues.get();
            }
        }

        Link<HtmlImportInfo&,void> aOldLink = pEdit->GetHtmlImportHdl();
        pEdit->SetHtmlImportHdl( LINK( this, ScHTMLQueryParser, HTMLImportHdl ) );
        ErrCode nErr = pEdit->Read( rStrm, rBaseURL, EETextFormat::Html, pAttributes );
        pEdit->SetHtmlImportHdl( aOldLink );

        mxGlobTable->Recalc();
        nColMax = static_cast< SCCOL >( mxGlobTable->GetDocSize( tdCol ) - 1 );
        nRowMax = static_cast< SCROW >( mxGlobTable->GetDocSize( tdRow ) - 1 );

        return nErr;
    }

    const ScHTMLTable* ScHTMLQueryParser::GetGlobalTable() const
    {
        return mxGlobTable.get();
    }

    void ScHTMLQueryParser::ProcessToken( const HtmlImportInfo& rInfo )
    {
        switch( rInfo.nToken )
        {
    // --- meta data ---
            case HtmlTokenId::META:             MetaOn( rInfo );                break;  // <meta>

    // --- title handling ---
            case HtmlTokenId::TITLE_ON:         TitleOn();                      break;  // <title>
            case HtmlTokenId::TITLE_OFF:        TitleOff( rInfo );              break;  // </title>

            case HtmlTokenId::STYLE_ON:                                         break;
            case HtmlTokenId::STYLE_OFF:        ParseStyle(rInfo.aText);        break;

    // --- body handling ---
            case HtmlTokenId::BODY_ON:          mpCurrTable->BodyOn( rInfo );   break;  // <body>
            case HtmlTokenId::BODY_OFF:         mpCurrTable->BodyOff( rInfo );  break;  // </body>

    // --- insert text ---
            case HtmlTokenId::TEXTTOKEN:        InsertText( rInfo );            break;  // any text
            case HtmlTokenId::LINEBREAK:        mpCurrTable->BreakOn();         break;  // <br>
            case HtmlTokenId::HEAD1_ON:                                                 // <h1>
            case HtmlTokenId::HEAD2_ON:                                                 // <h2>
            case HtmlTokenId::HEAD3_ON:                                                 // <h3>
            case HtmlTokenId::HEAD4_ON:                                                 // <h4>
            case HtmlTokenId::HEAD5_ON:                                                 // <h5>
            case HtmlTokenId::HEAD6_ON:                                                 // <h6>
            case HtmlTokenId::PARABREAK_ON:     mpCurrTable->HeadingOn();       break;  // <p>

    // --- misc. contents ---
            case HtmlTokenId::ANCHOR_ON:        mpCurrTable->AnchorOn();        break;  // <a>

    // --- table handling ---
            case HtmlTokenId::TABLE_ON:         TableOn( rInfo );               break;  // <table>
            case HtmlTokenId::TABLE_OFF:        TableOff( rInfo );              break;  // </table>
            case HtmlTokenId::TABLEROW_ON:      mpCurrTable->RowOn( rInfo );    break;  // <tr>
            case HtmlTokenId::TABLEROW_OFF:     mpCurrTable->RowOff( rInfo );   break;  // </tr>
            case HtmlTokenId::TABLEHEADER_ON:                                           // <th>
            case HtmlTokenId::TABLEDATA_ON:     mpCurrTable->DataOn( rInfo );   break;  // <td>
            case HtmlTokenId::TABLEHEADER_OFF:                                          // </th>
            case HtmlTokenId::TABLEDATA_OFF:    mpCurrTable->DataOff( rInfo );  break;  // </td>
            case HtmlTokenId::PREFORMTXT_ON:    PreOn( rInfo );                 break;  // <pre>
            case HtmlTokenId::PREFORMTXT_OFF:   PreOff( rInfo );                break;  // </pre>

    // --- formatting ---
            case HtmlTokenId::FONT_ON:          FontOn( rInfo );                break;  // <font>

            case HtmlTokenId::BIGPRINT_ON:      // <big>
                //! TODO: store current font size, use following size
                mpCurrTable->PutItem( SvxFontHeightItem( maFontHeights[ 3 ], 100, ATTR_FONT_HEIGHT ) );
            break;
            case HtmlTokenId::SMALLPRINT_ON:    // <small>
                //! TODO: store current font size, use preceding size
                mpCurrTable->PutItem( SvxFontHeightItem( maFontHeights[ 0 ], 100, ATTR_FONT_HEIGHT ) );
            break;

            case HtmlTokenId::BOLD_ON:          // <b>
            case HtmlTokenId::STRONG_ON:        // <strong>
                mpCurrTable->PutItem( SvxWeightItem( WEIGHT_BOLD, ATTR_FONT_WEIGHT ) );
            break;

            case HtmlTokenId::ITALIC_ON:        // <i>
            case HtmlTokenId::EMPHASIS_ON:      // <em>
            case HtmlTokenId::ADDRESS_ON:       // <address>
            case HtmlTokenId::BLOCKQUOTE_ON:    // <blockquote>
            case HtmlTokenId::BLOCKQUOTE30_ON:  // <bq>
            case HtmlTokenId::CITIATION_ON:     // <cite>
            case HtmlTokenId::VARIABLE_ON:      // <var>
                mpCurrTable->PutItem( SvxPostureItem( ITALIC_NORMAL, ATTR_FONT_POSTURE ) );
            break;

            case HtmlTokenId::DEFINSTANCE_ON:   // <dfn>
                mpCurrTable->PutItem( SvxWeightItem( WEIGHT_BOLD, ATTR_FONT_WEIGHT ) );
                mpCurrTable->PutItem( SvxPostureItem( ITALIC_NORMAL, ATTR_FONT_POSTURE ) );
            break;

            case HtmlTokenId::UNDERLINE_ON:     // <u>
                mpCurrTable->PutItem( SvxUnderlineItem( LINESTYLE_SINGLE, ATTR_FONT_UNDERLINE ) );
            break;
            default: break;
        }
    }

    void ScHTMLQueryParser::InsertText( const HtmlImportInfo& rInfo )
    {
        mpCurrTable->PutText( rInfo );
        if( mbTitleOn )
            maTitle.append(rInfo.aText);
    }

    void ScHTMLQueryParser::FontOn( const HtmlImportInfo& rInfo )
    {
        const HTMLOptions& rOptions = static_cast<HTMLParser*>(rInfo.pParser)->GetOptions();
        HTMLOptions::const_iterator itr = rOptions.begin(), itrEnd = rOptions.end();
        for (; itr != itrEnd; ++itr)
        {
            switch( itr->GetToken() )
            {
                case HtmlOptionId::FACE :
                {
                    const OUString& rFace = itr->GetString();
                    OUString aFontName;
                    sal_Int32 nPos = 0;
                    while( nPos != -1 )
                    {
                        // font list separator: VCL = ';' HTML = ','
                        OUString aFName = comphelper::string::strip(rFace.getToken(0, ',', nPos), ' ');
                        aFontName = ScGlobal::addToken(aFontName, aFName, ';');
                    }
                    if ( !aFontName.isEmpty() )
                        mpCurrTable->PutItem( SvxFontItem( FAMILY_DONTKNOW,
                            aFontName, EMPTY_OUSTRING, PITCH_DONTKNOW,
                            RTL_TEXTENCODING_DONTKNOW, ATTR_FONT ) );
                }
                break;
                case HtmlOptionId::SIZE :
                {
                    sal_uInt32 nSize = getLimitedValue< sal_uInt32 >( itr->GetNumber(), 1, SC_HTML_FONTSIZES );
                    mpCurrTable->PutItem( SvxFontHeightItem( maFontHeights[ nSize - 1 ], 100, ATTR_FONT_HEIGHT ) );
                }
                break;
                case HtmlOptionId::COLOR :
                {
                    Color aColor;
                    itr->GetColor( aColor );
                    mpCurrTable->PutItem( SvxColorItem( aColor, ATTR_FONT_COLOR ) );
                }
                break;
                default: break;
            }
        }
    }

    void ScHTMLQueryParser::MetaOn( const HtmlImportInfo& rInfo )
    {
        if( mpDoc->GetDocumentShell() )
        {
            HTMLParser* pParser = static_cast< HTMLParser* >( rInfo.pParser );

            uno::Reference<document::XDocumentPropertiesSupplier> xDPS(
                mpDoc->GetDocumentShell()->GetModel(), uno::UNO_QUERY_THROW);
            pParser->ParseMetaOptions(
                xDPS->getDocumentProperties(),
                mpDoc->GetDocumentShell()->GetHeaderAttributes() );
        }
    }

    void ScHTMLQueryParser::TitleOn()
    {
        mbTitleOn = true;
        maTitle.setLength(0);
    }

    void ScHTMLQueryParser::TitleOff( const HtmlImportInfo& rInfo )
    {
        if( mbTitleOn )
        {
            OUString aTitle = maTitle.makeStringAndClear().trim();
            if (!aTitle.isEmpty() && mpDoc->GetDocumentShell())
            {
                uno::Reference<document::XDocumentPropertiesSupplier> xDPS(
                    mpDoc->GetDocumentShell()->GetModel(), uno::UNO_QUERY_THROW);

                xDPS->getDocumentProperties()->setTitle(aTitle);
            }
            InsertText( rInfo );
            mbTitleOn = false;
        }
    }

    void ScHTMLQueryParser::TableOn( const HtmlImportInfo& rInfo )
    {
        mpCurrTable = mpCurrTable->TableOn( rInfo );
    }

    void ScHTMLQueryParser::TableOff( const HtmlImportInfo& rInfo )
    {
        mpCurrTable = mpCurrTable->TableOff( rInfo );
    }

    void ScHTMLQueryParser::PreOn( const HtmlImportInfo& rInfo )
    {
        mpCurrTable = mpCurrTable->PreOn( rInfo );
    }

    void ScHTMLQueryParser::PreOff( const HtmlImportInfo& rInfo )
    {
        mpCurrTable = mpCurrTable->PreOff( rInfo );
    }

    void ScHTMLQueryParser::CloseTable( const HtmlImportInfo& rInfo )
    {
        mpCurrTable = mpCurrTable->CloseTable( rInfo );
    }

    namespace {

    /**
     * Handler class for the CSS parser.
     */
    class CSSHandler
    {
        struct MemStr
        {
            const char* mp;
            size_t      mn;

            MemStr() : mp(nullptr), mn(0) {}
            MemStr(const char* p, size_t n) : mp(p), mn(n) {}
            MemStr& operator=(const MemStr& r)
            {
                mp = r.mp;
                mn = r.mn;
                return *this;
            }
        };

        typedef std::pair<MemStr, MemStr> SelectorName; // element : class
        typedef std::vector<SelectorName> SelectorNames;
        SelectorNames maSelectorNames; /// current selector names.
        MemStr maPropName;  /// current property name.
        MemStr maPropValue; /// current property value.

        ScHTMLStyles& mrStyles;
    public:
        explicit CSSHandler(ScHTMLStyles& rStyles) : mrStyles(rStyles) {}

        static void at_rule_name(const char* /*p*/, size_t /*n*/)
        {
            // TODO: For now, we ignore at-rule properties
        }

        void property_name(const char* p, size_t n)
        {
            maPropName = MemStr(p, n);
        }

        void value(const char* p, size_t n)
        {
            maPropValue = MemStr(p, n);
        }

        static void begin_parse() {}

        static void end_parse() {}

        static void begin_block() {}

        void end_block()
        {
            maSelectorNames.clear();
        }

        static void begin_property() {}

        void end_property()
        {
            SelectorNames::const_iterator itr = maSelectorNames.begin(), itrEnd = maSelectorNames.end();
            for (; itr != itrEnd; ++itr)
            {
                // Add this property to the collection for each selector.
                const SelectorName& rSelName = *itr;
                const MemStr& rElem = rSelName.first;
                const MemStr& rClass = rSelName.second;
                OUString aName(maPropName.mp, maPropName.mn, RTL_TEXTENCODING_UTF8);
                OUString aValue(maPropValue.mp, maPropValue.mn, RTL_TEXTENCODING_UTF8);
                mrStyles.add(rElem.mp, rElem.mn, rClass.mp, rClass.mn, aName, aValue);
            }
            maPropName = MemStr();
            maPropValue = MemStr();
        }

        // new members
        static void simple_selector_type(const char* /*p*/, size_t /*n*/) {}

        static void simple_selector_class(const char* /*p*/, size_t /*n*/) {}

        static void simple_selector_pseudo_element(orcus::css::pseudo_element_t /*pe*/) {}

        static void simple_selector_pseudo_class(orcus::css::pseudo_class_t /*pc*/) {}

        static void simple_selector_id(const char* /*p*/, size_t /*n*/) {}

        static void end_simple_selector() {}

        static void end_selector() {}

        static void combinator(orcus::css::combinator_t /*combinator*/) {}

        static void rgb(uint8_t /*red*/ , uint8_t /*green*/ , uint8_t /*blue*/ ) {}

        static void rgba(uint8_t /*red*/ , uint8_t /*green*/ , uint8_t /*blue*/ , double /*alpha*/ ) {}

        static void hsl(uint8_t /*hue*/ , uint8_t /*sat*/ , uint8_t /*light*/ ) {}

        static void hsla(uint8_t /*hue*/ , uint8_t /*sat*/ , uint8_t /*light*/ , double /*alpha*/ ) {}

        static void url(const char* /*p*/, size_t /*n*/) {}

    };

    }

    void ScHTMLQueryParser::ParseStyle(const OUString& rStrm)
    {
        OString aStr = OUStringToOString(rStrm, RTL_TEXTENCODING_UTF8);
        CSSHandler aHdl(GetStyles());
        orcus::css_parser<CSSHandler> aParser(aStr.getStr(), aStr.getLength(), aHdl);
        try
        {
            aParser.parse();
        }
        catch (const orcus::css::parse_error&)
        {
            // TODO: Parsing of CSS failed.  Do nothing for now.
        }
    }

    IMPL_LINK( ScHTMLQueryParser, HTMLImportHdl, HtmlImportInfo&, rInfo, void )
    {
        switch( rInfo.eState )
        {
            case HtmlImportState::Start:
            break;

            case HtmlImportState::NextToken:
                ProcessToken( rInfo );
            break;

            case HtmlImportState::InsertPara:
                mpCurrTable->InsertPara( rInfo );
            break;

            case HtmlImportState::SetAttr:
            case HtmlImportState::InsertText:
            case HtmlImportState::InsertField:
            break;

            case HtmlImportState::End:
                while( mpCurrTable->GetTableId() != SC_HTML_GLOBAL_TABLE )
                    CloseTable( rInfo );
            break;

            default:
                OSL_FAIL( "ScHTMLQueryParser::HTMLImportHdl - unknown ImportInfo::eState" );
        }
    }

    /* vim:set shiftwidth=4 softtabstop=4 expandtab: */

↑ V1029 Numeric Truncation Error. Result of the 'size' function is written to the 16-bit variable.

↑ V1029 Numeric Truncation Error. Result of the 'size' function is written to the 16-bit variable.

↑ V1029 Numeric Truncation Error. Result of the 'size' function is written to the 16-bit variable.

↑ V690 The 'MemStr' class implements the '=' operator, but lacks a copy constructor. It is dangerous to use such a class.

↑ V1029 Numeric Truncation Error. Result of the 'size' function is written to the 16-bit variable.