/* -*- 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 <hintids.hxx>
#include <comphelper/configuration.hxx>
#include <comphelper/flagguard.hxx>
#include <utility>
#include <vcl/svapp.hxx>
#include <editeng/boxitem.hxx>
#include <editeng/brushitem.hxx>
#include <editeng/adjustitem.hxx>
#include <editeng/fhgtitem.hxx>
#include <editeng/ulspitem.hxx>
#include <editeng/lrspitem.hxx>
#include <editeng/formatbreakitem.hxx>
#include <editeng/spltitem.hxx>
#include <unotools/configmgr.hxx>
#include <svtools/htmltokn.h>
#include <svtools/htmlkywd.hxx>
#include <svl/numformat.hxx>
#include <svl/urihelper.hxx>
#include <svx/sdrobjectuser.hxx>
#include <svx/svdotext.hxx>
#include <sal/log.hxx>
#include <osl/diagnose.h>
 
#include <dcontact.hxx>
#include <fmtornt.hxx>
#include <frmfmt.hxx>
#include <fmtfsize.hxx>
#include <fmtsrnd.hxx>
#include <fmtpdsc.hxx>
#include <fmtcntnt.hxx>
#include <fmtanchr.hxx>
#include <fmtlsplt.hxx>
#include <frmatr.hxx>
#include <pam.hxx>
#include <doc.hxx>
#include <IDocumentLayoutAccess.hxx>
#include <IDocumentMarkAccess.hxx>
#include <ndtxt.hxx>
#include <shellio.hxx>
#include <poolfmt.hxx>
#include <swtable.hxx>
#include <cellatr.hxx>
#include <htmltbl.hxx>
#include <swtblfmt.hxx>
#include "htmlnum.hxx"
#include "swhtml.hxx"
#include "swcss1.hxx"
#include <txtftn.hxx>
#include <itabenum.hxx>
#include <tblafmt.hxx>
#include <SwStyleNameMapper.hxx>
#include <frameformats.hxx>
 
#define NETSCAPE_DFLT_BORDER 1
#define NETSCAPE_DFLT_CELLSPACING 2
 
using ::editeng::SvxBorderLine;
using namespace ::com::sun::star;
 
HTMLOptionEnum<sal_Int16> const aHTMLTableVAlignTable[] =
{
    { OOO_STRING_SVTOOLS_HTML_VA_top,     text::VertOrientation::NONE       },
    { OOO_STRING_SVTOOLS_HTML_VA_middle,  text::VertOrientation::CENTER     },
    { OOO_STRING_SVTOOLS_HTML_VA_bottom,  text::VertOrientation::BOTTOM     },
    { nullptr,                            0               }
};
 
// table tags options
 
namespace {
 
struct HTMLTableOptions
{
    sal_uInt16 nCols;
    sal_uInt16 nWidth;
    sal_uInt16 nHeight;
    sal_uInt16 nCellPadding;
    sal_uInt16 nCellSpacing;
    sal_uInt16 nBorder;
    sal_uInt16 nHSpace;
    sal_uInt16 nVSpace;
 
    SvxAdjust eAdjust;
    sal_Int16 eVertOri;
    HTMLTableFrame eFrame;
    HTMLTableRules eRules;
 
    bool bPercentWidth : 1;
    bool bTableAdjust : 1;
    bool bBGColor : 1;
 
    Color aBorderColor;
    Color aBGColor;
 
    OUString aBGImage, aStyle, aId, aClass, aDir;
 
    HTMLTableOptions( const HTMLOptions& rOptions, SvxAdjust eParentAdjust );
};
 
class HTMLTableContext
{
    SwHTMLNumRuleInfo m_aNumRuleInfo; // Numbering valid before the table
 
    SwTableNode *m_pTableNd;              // table node
    SwFrameFormat *m_pFrameFormat;        // the Fly frame::Frame, containing the table
    std::unique_ptr<SwPosition> m_pPos;   // position behind the table
 
    size_t m_nContextStAttrMin;
    size_t m_nContextStMin;
 
    bool    m_bRestartPRE : 1;
    bool    m_bRestartXMP : 1;
    bool    m_bRestartListing : 1;
 
    HTMLTableContext(const HTMLTableContext&) = delete;
    HTMLTableContext& operator=(const HTMLTableContext&) = delete;
 
public:
 
    std::shared_ptr<HTMLAttrTable> m_xAttrTab;        // attributes
 
    HTMLTableContext( SwPosition *pPs, size_t nCntxtStMin,
                       size_t nCntxtStAttrMin ) :
        m_pTableNd( nullptr ),
        m_pFrameFormat( nullptr ),
        m_pPos( pPs ),
        m_nContextStAttrMin( nCntxtStAttrMin ),
        m_nContextStMin( nCntxtStMin ),
        m_bRestartPRE( false ),
        m_bRestartXMP( false ),
        m_bRestartListing( false ),
        m_xAttrTab(std::make_shared<HTMLAttrTable>())
    {
        memset(m_xAttrTab.get(), 0, sizeof(HTMLAttrTable));
    }
 
    void SetNumInfo( const SwHTMLNumRuleInfo& rInf ) { m_aNumRuleInfo.Set(rInf); }
    const SwHTMLNumRuleInfo& GetNumInfo() const { return m_aNumRuleInfo; };
 
    void SavePREListingXMP( SwHTMLParser& rParser );
    void RestorePREListingXMP( SwHTMLParser& rParser );
 
    SwPosition *GetPos() const { return m_pPos.get(); }
 
    void SetTableNode( SwTableNode *pNd ) { m_pTableNd = pNd; }
    SwTableNode *GetTableNode() const { return m_pTableNd; }
 
    void SetFrameFormat( SwFrameFormat *pFormat ) { m_pFrameFormat = pFormat; }
    SwFrameFormat *GetFrameFormat() const { return m_pFrameFormat; }
 
    size_t GetContextStMin() const { return m_nContextStMin; }
    size_t GetContextStAttrMin() const { return m_nContextStAttrMin; }
};
 
}
 
// Cell content is a linked list with SwStartNodes and
// HTMLTables.
 
class HTMLTableCnts
{
    std::unique_ptr<HTMLTableCnts> m_pNext;               // next content
 
    // Only one of the next two pointers must be set!
    const SwStartNode *m_pStartNode;      // a paragraph
    std::shared_ptr<HTMLTable> m_xTable;                  // a table
 
    std::shared_ptr<SwHTMLTableLayoutCnts> m_xLayoutInfo;
 
    bool m_bNoBreak;
 
    void InitCtor();
 
public:
 
    explicit HTMLTableCnts(const SwStartNode* pStNd);
    explicit HTMLTableCnts(std::shared_ptr<HTMLTable> xTab);
 
    ~HTMLTableCnts();                   // only allowed in ~HTMLTableCell
 
    // Determine SwStartNode and HTMLTable respectively
    const SwStartNode *GetStartNode() const { return m_pStartNode; }
    const std::shared_ptr<HTMLTable>& GetTable() const { return m_xTable; }
    std::shared_ptr<HTMLTable>& GetTable() { return m_xTable; }
 
    // Add a new node at the end of the list
    void Add( std::unique_ptr<HTMLTableCnts> pNewCnts );
 
    // Determine next node
    const HTMLTableCnts *Next() const { return m_pNext.get(); }
    HTMLTableCnts *Next() { return m_pNext.get(); }
 
    inline void SetTableBox( SwTableBox *pBox );
 
    void SetNoBreak() { m_bNoBreak = true; }
 
    const std::shared_ptr<SwHTMLTableLayoutCnts>& CreateLayoutInfo();
};
 
namespace {
 
// Cell of a HTML table
class HTMLTableCell
{
    std::shared_ptr<HTMLTableCnts> m_xContents;       // cell content
    std::shared_ptr<SvxBrushItem> m_xBGBrush;         // cell background
    std::shared_ptr<SvxBoxItem> m_xBoxItem;
 
    double m_nValue;
    sal_uInt32 m_nNumFormat;
    sal_uInt16 m_nRowSpan;           // cell ROWSPAN
    sal_uInt16 m_nColSpan;           // cell COLSPAN
    sal_uInt16 m_nWidth;             // cell WIDTH
    sal_Int16 m_eVertOrient;             // vertical alignment of the cell
    bool m_bProtected : 1;           // cell must not filled
    bool m_bRelWidth : 1;            // nWidth is given in %
    bool m_bHasNumFormat : 1;
    bool m_bHasValue : 1;
    bool m_bNoWrap : 1;
    bool mbCovered : 1;
 
public:
 
    HTMLTableCell();                // new cells always empty
 
    // Fill a not empty cell
    void Set( std::shared_ptr<HTMLTableCnts> const& rCnts, sal_uInt16 nRSpan, sal_uInt16 nCSpan,
              sal_Int16 eVertOri, std::shared_ptr<SvxBrushItem> const& rBGBrush,
              std::shared_ptr<SvxBoxItem> const& rBoxItem,
              bool bHasNumFormat, sal_uInt32 nNumFormat,
              bool bHasValue, double nValue, bool bNoWrap, bool bCovered );
 
    // Protect an empty 1x1 cell
    void SetProtected();
 
    // Set/Get cell content
    void SetContents(std::shared_ptr<HTMLTableCnts> const& rCnts) { m_xContents = rCnts; }
    const std::shared_ptr<HTMLTableCnts>& GetContents() const { return m_xContents; }
 
    // Set/Get cell ROWSPAN/COLSPAN
    void SetRowSpan( sal_uInt16 nRSpan ) { m_nRowSpan = nRSpan; }
    sal_uInt16 GetRowSpan() const { return m_nRowSpan; }
 
    void SetColSpan( sal_uInt16 nCSpan ) { m_nColSpan = nCSpan; }
    sal_uInt16 GetColSpan() const { return m_nColSpan; }
 
    inline void SetWidth( sal_uInt16 nWidth, bool bRelWidth );
 
    const std::shared_ptr<SvxBrushItem>& GetBGBrush() const { return m_xBGBrush; }
    const std::shared_ptr<SvxBoxItem>& GetBoxItem() const { return m_xBoxItem; }
 
    inline bool GetNumFormat( sal_uInt32& rNumFormat ) const;
    inline bool GetValue( double& rValue ) const;
 
    sal_Int16 GetVertOri() const { return m_eVertOrient; }
 
    // Is the cell filled or protected ?
    bool IsUsed() const { return m_xContents || m_bProtected; }
 
    std::unique_ptr<SwHTMLTableLayoutCell> CreateLayoutInfo();
 
    bool IsCovered() const { return mbCovered; }
};
 
}
 
 
namespace {
 
// Row of a HTML table
class HTMLTableRow
{
    std::vector<HTMLTableCell> m_aCells;                ///< cells of the row
    std::unique_ptr<SvxBrushItem> m_xBGBrush; // background of cell from STYLE
 
    SvxAdjust m_eAdjust;
    sal_uInt16 m_nHeight;                     // options of <TR>/<TD>
    sal_uInt16 m_nEmptyRows;                  // number of empty rows are following
    sal_Int16 m_eVertOri;
    bool m_bIsEndOfGroup : 1;
    bool m_bBottomBorder : 1;            // Is there a line after the row?
 
public:
 
    explicit HTMLTableRow( sal_uInt16 nCells );    // cells of the row are empty
 
    void SetBottomBorder(bool bIn) { m_bBottomBorder = bIn; }
    bool GetBottomBorder() const { return m_bBottomBorder; }
 
    inline void SetHeight( sal_uInt16 nHeight );
    sal_uInt16 GetHeight() const { return m_nHeight; }
 
    const HTMLTableCell& GetCell(sal_uInt16 nCell) const;
    HTMLTableCell& GetCell(sal_uInt16 nCell)
    {
        return const_cast<HTMLTableCell&>(const_cast<const HTMLTableRow&>(*this).GetCell(nCell));
    }
 
    void SetAdjust( SvxAdjust eAdj ) { m_eAdjust = eAdj; }
    SvxAdjust GetAdjust() const { return m_eAdjust; }
 
    void SetVertOri( sal_Int16 eV) { m_eVertOri = eV; }
    sal_Int16 GetVertOri() const { return m_eVertOri; }
 
    void SetBGBrush(std::unique_ptr<SvxBrushItem>& rBrush ) { m_xBGBrush = std::move(rBrush); }
    const std::unique_ptr<SvxBrushItem>& GetBGBrush() const { return m_xBGBrush; }
 
    void SetEndOfGroup() { m_bIsEndOfGroup = true; }
    bool IsEndOfGroup() const { return m_bIsEndOfGroup; }
 
    void IncEmptyRows() { m_nEmptyRows++; }
    sal_uInt16 GetEmptyRows() const { return m_nEmptyRows; }
 
    // Expand row by adding empty cells
    void Expand( sal_uInt16 nCells, bool bOneCell=false );
 
    // Shrink row by deleting empty cells
    void Shrink( sal_uInt16 nCells );
};
 
// Column of a HTML table
class HTMLTableColumn
{
    bool m_bIsEndOfGroup;
 
    sal_uInt16 m_nWidth;                      // options of <COL>
    bool m_bRelWidth;
 
    SvxAdjust m_eAdjust;
    sal_Int16 m_eVertOri;
 
    SwFrameFormat *m_aFrameFormats[6];
 
    static inline sal_uInt16 GetFrameFormatIdx( bool bBorderLine,
                                sal_Int16 eVertOri );
 
public:
 
    bool m_bLeftBorder;                   // is there a line before the column
 
    HTMLTableColumn();
 
    inline void SetWidth( sal_uInt16 nWidth, bool bRelWidth);
 
    void SetAdjust( SvxAdjust eAdj ) { m_eAdjust = eAdj; }
    SvxAdjust GetAdjust() const { return m_eAdjust; }
 
    void SetVertOri( sal_Int16 eV) { m_eVertOri = eV; }
    sal_Int16 GetVertOri() const { return m_eVertOri; }
 
    void SetEndOfGroup() { m_bIsEndOfGroup = true; }
    bool IsEndOfGroup() const { return m_bIsEndOfGroup; }
 
    inline void SetFrameFormat( SwFrameFormat *pFormat, bool bBorderLine,
                           sal_Int16 eVertOri );
    inline SwFrameFormat *GetFrameFormat( bool bBorderLine,
                                sal_Int16 eVertOri ) const;
 
    std::unique_ptr<SwHTMLTableLayoutColumn> CreateLayoutInfo();
};
 
}
 
// HTML table
typedef std::vector<SdrObject *> SdrObjects;
 
class HTMLTable : public sdr::ObjectUser
{
    OUString m_aId;
    OUString m_aStyle;
    OUString m_aClass;
    OUString m_aDir;
 
    std::optional<SdrObjects> m_xResizeDrawObjects;// SDR objects
    std::optional<std::vector<sal_uInt16>> m_xDrawObjectPercentWidths;   // column of draw object and its rel. width
 
    std::vector<HTMLTableRow> m_aRows;         ///< table rows
    std::vector<HTMLTableColumn> m_aColumns;   ///< table columns
 
    sal_uInt16 m_nRows;                   // number of rows
    sal_uInt16 m_nCols;                   // number of columns
    sal_uInt16 m_nFilledColumns;             // number of filled columns
 
    sal_uInt16 m_nCurrentRow;                 // current Row
    sal_uInt16 m_nCurrentColumn;                 // current Column
 
    sal_uInt16 m_nLeftMargin;             // Space to the left margin (from paragraph edge)
    sal_uInt16 m_nRightMargin;            // Space to the right margin (from paragraph edge)
 
    sal_uInt16 m_nCellPadding;            // Space from border to Text
    sal_uInt16 m_nCellSpacing;            // Space between two cells
    sal_uInt16 m_nHSpace;
    sal_uInt16 m_nVSpace;
 
    sal_uInt16 m_nBoxes;                  // number of boxes in the table
 
    const SwStartNode *m_pPrevStartNode;   // the Table-Node or the Start-Node of the section before
    const SwTable *m_pSwTable;        // SW-Table (only on Top-Level)
public:
    std::unique_ptr<SwTableBox> m_xBox1;    // TableBox, generated when the Top-Level-Table was build
private:
    SwTableBoxFormat *m_pBoxFormat;         // frame::Frame-Format from SwTableBox
    SwTableLineFormat *m_pLineFormat;       // frame::Frame-Format from SwTableLine
    SwTableLineFormat *m_pLineFrameFormatNoHeight;
    std::unique_ptr<SvxBrushItem> m_xBackgroundBrush;          // background of the table
    std::unique_ptr<SvxBrushItem> m_xInheritedBackgroundBrush; // "inherited" background of the table
    const SwStartNode *m_pCaptionStartNode;   // Start-Node of the table-caption
    //lines for the border
    SvxBorderLine m_aTopBorderLine;
    SvxBorderLine m_aBottomBorderLine;
    SvxBorderLine m_aLeftBorderLine;
    SvxBorderLine m_aRightBorderLine;
    SvxBorderLine m_aBorderLine;
    SvxBorderLine m_aInheritedLeftBorderLine;
    SvxBorderLine m_aInheritedRightBorderLine;
    bool m_bTopBorder;                // is there a line on the top of the table
    bool m_bRightBorder;              // is there a line on the top right of the table
    bool m_bTopAllowed;                  // is it allowed to set the border?
    bool m_bRightAllowed;
    bool m_bFillerTopBorder;          // gets the left/right filler-cell a border on the
    bool m_bFillerBottomBorder;       // top or in the bottom
    bool m_bInheritedLeftBorder;
    bool m_bInheritedRightBorder;
    bool m_bBordersSet;               // the border is set already
    bool m_bForceFrame;
    bool m_bTableAdjustOfTag;         // comes nTableAdjust from <TABLE>?
    sal_uInt32 m_nHeadlineRepeat;         // repeating rows
    bool m_bIsParentHead;
    bool m_bHasParentSection;
    bool m_bHasToFly;
    bool m_bFixedCols;
    bool m_bColSpec;                  // where there COL(GROUP)-elements?
    bool m_bPercentWidth;                 // width is declared in %
 
    SwHTMLParser *m_pParser;          // the current parser
    std::unique_ptr<HTMLTableCnts> m_xParentContents;
 
    std::unique_ptr<HTMLTableContext> m_pContext;    // the context of the table
 
    std::shared_ptr<SwHTMLTableLayout> m_xLayoutInfo;
 
    // the following parameters are from the <TABLE>-Tag
    sal_uInt16 m_nWidth;                  // width of the table
    sal_uInt16 m_nHeight;                 // absolute height of the table
    SvxAdjust m_eTableAdjust;             // drawing::Alignment of the table
    sal_Int16 m_eVertOrientation;         // Default vertical direction of the cells
    sal_uInt16 m_nBorder;                 // width of the external border
    HTMLTableFrame m_eFrame;          // frame around the table
    HTMLTableRules m_eRules;          // frame in the table
    bool m_bTopCaption;               // Caption of the table
 
    void InitCtor(const HTMLTableOptions& rOptions);
 
    // Correction of the Row-Spans for all cells above the chosen cell and the cell itself for the indicated content. The chosen cell gets the Row-Span 1
    void FixRowSpan( sal_uInt16 nRow, sal_uInt16 nCol, const HTMLTableCnts *pCnts );
 
    // Protects the chosen cell and the cells among
    void ProtectRowSpan( sal_uInt16 nRow, sal_uInt16 nCol, sal_uInt16 nRowSpan );
 
    // Looking for the SwStartNodes of the box ahead
    // If nRow==nCell==USHRT_MAX, return the last Start-Node of the table.
    const SwStartNode* GetPrevBoxStartNode( sal_uInt16 nRow, sal_uInt16 nCell ) const;
 
    sal_uInt16 GetTopCellSpace( sal_uInt16 nRow ) const;
    sal_uInt16 GetBottomCellSpace( sal_uInt16 nRow, sal_uInt16 nRowSpan ) const;
 
    // Conforming of the frame::Frame-Format of the box
    void FixFrameFormat( SwTableBox *pBox, sal_uInt16 nRow, sal_uInt16 nCol,
                      sal_uInt16 nRowSpan, sal_uInt16 nColSpan,
                      bool bFirstPara=true, bool bLastPara=true ) const;
 
    // Create a table with the content (lines/boxes)
    void MakeTable_( SwTableBox *pUpper );
 
    // Generate a new SwTableBox, which contains a SwStartNode
    SwTableBox *NewTableBox( const SwStartNode *pStNd,
                             SwTableLine *pUpper ) const;
 
    // Generate a SwTableLine from the cells of the rectangle
    // (nTopRow/nLeftCol) inclusive to (nBottomRow/nRightRow) exclusive
    SwTableLine *MakeTableLine( SwTableBox *pUpper,
                                sal_uInt16 nTopRow, sal_uInt16 nLeftCol,
                                sal_uInt16 nBottomRow, sal_uInt16 nRightCol );
 
    // Generate a SwTableBox from the content of the cell
    SwTableBox *MakeTableBox( SwTableLine *pUpper,
                              HTMLTableCnts *pCnts,
                              sal_uInt16 nTopRow, sal_uInt16 nLeftCol,
                              sal_uInt16 nBootomRow, sal_uInt16 nRightCol );
 
    // Autolayout-Algorithm
 
    // Setting the border with the help of guidelines of the Parent-Table
    void InheritBorders( const HTMLTable *pParent,
                         sal_uInt16 nRow, sal_uInt16 nCol,
                         sal_uInt16 nRowSpan,
                         bool bFirstPara, bool bLastPara );
 
    // Inherit the left and the right border of the surrounding table
    void InheritVertBorders( const HTMLTable *pParent,
                             sal_uInt16 nCol, sal_uInt16 nColSpan );
 
    // Set the border with the help of the information from the user
    void SetBorders();
 
    // is the border already set?
    bool BordersSet() const { return m_bBordersSet; }
 
    const std::unique_ptr<SvxBrushItem>& GetBGBrush() const { return m_xBackgroundBrush; }
    const std::unique_ptr<SvxBrushItem>& GetInhBGBrush() const { return m_xInheritedBackgroundBrush; }
 
    sal_uInt16 GetBorderWidth( const SvxBorderLine& rBLine,
                           bool bWithDistance=false ) const;
 
    virtual void ObjectInDestruction(const SdrObject& rObject) override;
 
public:
 
    bool m_bFirstCell;                // is there a cell created already?
 
    HTMLTable(SwHTMLParser* pPars,
              bool bParHead, bool bHasParentSec,
              bool bHasToFly,
              const HTMLTableOptions& rOptions);
 
    virtual ~HTMLTable();
 
    // Identifying of a cell
    const HTMLTableCell& GetCell(sal_uInt16 nRow, sal_uInt16 nCell) const;
    HTMLTableCell& GetCell(sal_uInt16 nRow, sal_uInt16 nCell)
    {
        return const_cast<HTMLTableCell&>(const_cast<const HTMLTable&>(*this).GetCell(nRow, nCell));
    }
 
    // set/determine caption
    inline void SetCaption( const SwStartNode *pStNd, bool bTop );
    const SwStartNode *GetCaptionStartNode() const { return m_pCaptionStartNode; }
    bool IsTopCaption() const { return m_bTopCaption; }
 
    SvxAdjust GetTableAdjust( bool bAny ) const
    {
        return (m_bTableAdjustOfTag || bAny) ? m_eTableAdjust : SvxAdjust::End;
    }
 
    sal_uInt16 GetHSpace() const { return m_nHSpace; }
    sal_uInt16 GetVSpace() const { return m_nVSpace; }
 
    // get inherited drawing::Alignment of rows and column
    SvxAdjust GetInheritedAdjust() const;
    sal_Int16 GetInheritedVertOri() const;
 
    // Insert a cell on the current position
    void InsertCell( std::shared_ptr<HTMLTableCnts> const& rCnts, sal_uInt16 nRowSpan, sal_uInt16 nColSpan,
                     sal_uInt16 nWidth, bool bRelWidth, sal_uInt16 nHeight,
                     sal_Int16 eVertOri, std::shared_ptr<SvxBrushItem> const& rBGBrushItem,
                     std::shared_ptr<SvxBoxItem> const& rBoxItem,
                     bool bHasNumFormat, sal_uInt32 nNumFormat,
                     bool bHasValue, double nValue, bool bNoWrap );
 
    // announce the start/end of a new row
    void OpenRow(SvxAdjust eAdjust, sal_Int16 eVertOri, std::unique_ptr<SvxBrushItem>& rBGBrush);
    void CloseRow( bool bEmpty );
 
    // announce the end of a new section
    inline void CloseSection( bool bHead );
 
    // announce the end of a column-group
    inline void CloseColGroup( sal_uInt16 nSpan, sal_uInt16 nWidth, bool bRelWidth,
                               SvxAdjust eAdjust, sal_Int16 eVertOri );
 
    // insert a new column
    void InsertCol( sal_uInt16 nSpan, sal_uInt16 nWidth, bool bRelWidth,
                    SvxAdjust eAdjust, sal_Int16 eVertOri );
 
    // End a table definition (needs to be called for every table)
    void CloseTable();
 
    // Construct a SwTable (including child tables)
    void MakeTable( SwTableBox *pUpper, sal_uInt16 nAbsAvail,
                    sal_uInt16 nRelAvail=0, sal_uInt16 nAbsLeftSpace=0,
                    sal_uInt16 nAbsRightSpace=0, sal_uInt16 nInhAbsSpace=0 );
 
    bool IsNewDoc() const { return m_pParser->IsNewDoc(); }
 
    void SetHasParentSection( bool bSet ) { m_bHasParentSection = bSet; }
    bool HasParentSection() const { return m_bHasParentSection; }
 
    void SetParentContents(std::unique_ptr<HTMLTableCnts> pCnts) { m_xParentContents = std::move(pCnts); }
    std::unique_ptr<HTMLTableCnts>& GetParentContents() { return m_xParentContents; }
 
    void MakeParentContents();
 
    bool HasToFly() const { return m_bHasToFly; }
 
    void SetTable( const SwStartNode *pStNd, std::unique_ptr<HTMLTableContext> pCntxt,
                   sal_uInt16 nLeft, sal_uInt16 nRight,
                   const SwTable *pSwTab=nullptr, bool bFrcFrame=false );
 
    HTMLTableContext *GetContext() const { return m_pContext.get(); }
 
    const std::shared_ptr<SwHTMLTableLayout>& CreateLayoutInfo();
 
    bool HasColTags() const { return m_bColSpec; }
 
    sal_uInt16 IncGrfsThatResize() { return m_pSwTable ? const_cast<SwTable *>(m_pSwTable)->IncGrfsThatResize() : 0; }
 
    void RegisterDrawObject( SdrObject *pObj, sal_uInt8 nPercentWidth );
 
    const SwTable *GetSwTable() const { return m_pSwTable; }
 
    void SetBGBrush(const SvxBrushItem& rBrush) { m_xBackgroundBrush.reset(new SvxBrushItem(rBrush)); }
 
    const OUString& GetId() const { return m_aId; }
    const OUString& GetClass() const { return m_aClass; }
    const OUString& GetStyle() const { return m_aStyle; }
    const OUString& GetDirection() const { return m_aDir; }
 
    void IncBoxCount() { m_nBoxes++; }
    bool IsOverflowing() const { return m_nBoxes > 64000; }
};
 
void HTMLTableCnts::InitCtor()
{
    m_pNext = nullptr;
    m_xLayoutInfo.reset();
    m_bNoBreak = false;
}
 
HTMLTableCnts::HTMLTableCnts(const SwStartNode* pStNd)
    : m_pStartNode(pStNd)
{
    InitCtor();
}
 
HTMLTableCnts::HTMLTableCnts(std::shared_ptr<HTMLTable> xTab)
    : m_pStartNode(nullptr)
    , m_xTable(std::move(xTab))
{
    InitCtor();
}
 
HTMLTableCnts::~HTMLTableCnts()
{
    m_xTable.reset();    // we don't need the tables anymore
    m_pNext.reset();
}
 
void HTMLTableCnts::Add( std::unique_ptr<HTMLTableCnts> pNewCnts )
{
    HTMLTableCnts *pCnts = this;
 
    while( pCnts->m_pNext )
        pCnts = pCnts->m_pNext.get();
 
    pCnts->m_pNext = std::move(pNewCnts);
}
 
inline void HTMLTableCnts::SetTableBox( SwTableBox *pBox )
{
    OSL_ENSURE(m_xLayoutInfo, "There is no layout info");
    if (m_xLayoutInfo)
        m_xLayoutInfo->SetTableBox(pBox);
}
 
const std::shared_ptr<SwHTMLTableLayoutCnts>& HTMLTableCnts::CreateLayoutInfo()
{
    if (!m_xLayoutInfo)
    {
        std::shared_ptr<SwHTMLTableLayoutCnts> xNextInfo;
        if (m_pNext)
            xNextInfo = m_pNext->CreateLayoutInfo();
        std::shared_ptr<SwHTMLTableLayout> xTableInfo;
        if (m_xTable)
            xTableInfo = m_xTable->CreateLayoutInfo();
        m_xLayoutInfo = std::make_shared<SwHTMLTableLayoutCnts>(m_pStartNode, xTableInfo, m_bNoBreak, xNextInfo);
    }
 
    return m_xLayoutInfo;
}
 
HTMLTableCell::HTMLTableCell():
    m_nValue(0),
    m_nNumFormat(0),
    m_nRowSpan(1),
    m_nColSpan(1),
    m_nWidth( 0 ),
    m_eVertOrient( text::VertOrientation::NONE ),
    m_bProtected(false),
    m_bRelWidth( false ),
    m_bHasNumFormat(false),
    m_bHasValue(false),
    m_bNoWrap(false),
    mbCovered(false)
{}
 
void HTMLTableCell::Set( std::shared_ptr<HTMLTableCnts> const& rCnts, sal_uInt16 nRSpan, sal_uInt16 nCSpan,
                         sal_Int16 eVert, std::shared_ptr<SvxBrushItem> const& rBrush,
                         std::shared_ptr<SvxBoxItem> const& rBoxItem,
                         bool bHasNF, sal_uInt32 nNF, bool bHasV, double nVal,
                         bool bNWrap, bool bCovered )
{
    m_xContents = rCnts;
    m_nRowSpan = nRSpan;
    m_nColSpan = nCSpan;
    m_bProtected = false;
    m_eVertOrient = eVert;
    m_xBGBrush = rBrush;
    m_xBoxItem = rBoxItem;
 
    m_bHasNumFormat = bHasNF;
    m_bHasValue = bHasV;
    m_nNumFormat = nNF;
    m_nValue = nVal;
 
    m_bNoWrap = bNWrap;
    mbCovered = bCovered;
}
 
inline void HTMLTableCell::SetWidth( sal_uInt16 nWdth, bool bRelWdth )
{
    m_nWidth = nWdth;
    m_bRelWidth = bRelWdth;
}
 
void HTMLTableCell::SetProtected()
{
    // The content of this cell doesn't have to be anchored anywhere else,
    // since they're not gonna be deleted
 
    m_xContents.reset();
 
    // Copy background color
    if (m_xBGBrush)
        m_xBGBrush = std::make_shared<SvxBrushItem>(*m_xBGBrush);
 
    m_nRowSpan = 1;
    m_nColSpan = 1;
    m_bProtected = true;
}
 
inline bool HTMLTableCell::GetNumFormat( sal_uInt32& rNumFormat ) const
{
    rNumFormat = m_nNumFormat;
    return m_bHasNumFormat;
}
 
inline bool HTMLTableCell::GetValue( double& rValue ) const
{
    rValue = m_nValue;
    return m_bHasValue;
}
 
std::unique_ptr<SwHTMLTableLayoutCell> HTMLTableCell::CreateLayoutInfo()
{
    std::shared_ptr<SwHTMLTableLayoutCnts> xCntInfo;
    if (m_xContents)
        xCntInfo = m_xContents->CreateLayoutInfo();
    return std::unique_ptr<SwHTMLTableLayoutCell>(new SwHTMLTableLayoutCell(std::move(xCntInfo),
                                      m_nRowSpan, m_nColSpan, m_nWidth, m_bRelWidth, m_bNoWrap));
}
 
HTMLTableRow::HTMLTableRow(sal_uInt16 const nCells)
    : m_aCells(nCells)
    , m_eAdjust(SvxAdjust::End)
    , m_nHeight(0)
    , m_nEmptyRows(0)
    , m_eVertOri(text::VertOrientation::TOP)
    , m_bIsEndOfGroup(false)
    , m_bBottomBorder(false)
{
    assert(nCells == m_aCells.size() &&
            "wrong Cell count in new HTML table row");
}
 
inline void HTMLTableRow::SetHeight( sal_uInt16 nHght )
{
    if( nHght > m_nHeight  )
        m_nHeight = nHght;
}
 
const HTMLTableCell& HTMLTableRow::GetCell(sal_uInt16 nCell) const
{
    OSL_ENSURE( nCell < m_aCells.size(),
        "invalid cell index in HTML table row" );
    return m_aCells.at(nCell);
}
 
void HTMLTableRow::Expand( sal_uInt16 nCells, bool bOneCell )
{
    // This row will be filled with a single cell if bOneCell is set.
    // This will only work for rows that don't allow adding cells!
 
    sal_uInt16 nColSpan = nCells - m_aCells.size();
    for (sal_uInt16 i = m_aCells.size(); i < nCells; ++i)
    {
        m_aCells.emplace_back();
        if (bOneCell)
            m_aCells.back().SetColSpan(nColSpan);
        --nColSpan;
    }
 
    OSL_ENSURE(nCells == m_aCells.size(),
            "wrong Cell count in expanded HTML table row");
}
 
void HTMLTableRow::Shrink( sal_uInt16 nCells )
{
    OSL_ENSURE(nCells < m_aCells.size(), "number of cells too large");
 
#if OSL_DEBUG_LEVEL > 0
    sal_uInt16 const nEnd = m_aCells.size();
#endif
    // The colspan of empty cells at the end has to be fixed to the new
    // number of cells.
    sal_uInt16 i=nCells;
    while( i )
    {
        HTMLTableCell& rCell = m_aCells[--i];
        if (!rCell.GetContents())
        {
#if OSL_DEBUG_LEVEL > 0
            OSL_ENSURE( rCell.GetColSpan() == nEnd - i,
                    "invalid col span for empty cell at row end" );
#endif
            rCell.SetColSpan( nCells-i);
        }
        else
            break;
    }
#if OSL_DEBUG_LEVEL > 0
    for( i=nCells; i<nEnd; i++ )
    {
        HTMLTableCell& rCell = m_aCells[i];
        OSL_ENSURE( rCell.GetRowSpan() == 1,
                    "RowSpan of to be deleted cell is wrong" );
        OSL_ENSURE( rCell.GetColSpan() == nEnd - i,
                    "ColSpan of to be deleted cell is wrong" );
        OSL_ENSURE( !rCell.GetContents(), "To be deleted cell has content" );
    }
#endif
 
    m_aCells.erase(m_aCells.begin() + nCells, m_aCells.end());
}
 
HTMLTableColumn::HTMLTableColumn():
    m_bIsEndOfGroup(false),
    m_nWidth(0), m_bRelWidth(false),
    m_eAdjust(SvxAdjust::End), m_eVertOri(text::VertOrientation::TOP),
    m_bLeftBorder(false)
{
    for(SwFrameFormat* & rp : m_aFrameFormats)
        rp = nullptr;
}
 
inline void HTMLTableColumn::SetWidth( sal_uInt16 nWdth, bool bRelWdth )
{
    if( m_bRelWidth==bRelWdth )
    {
        if( nWdth > m_nWidth )
            m_nWidth = nWdth;
    }
    else
        m_nWidth = nWdth;
    m_bRelWidth = bRelWdth;
}
 
inline std::unique_ptr<SwHTMLTableLayoutColumn> HTMLTableColumn::CreateLayoutInfo()
{
    return std::unique_ptr<SwHTMLTableLayoutColumn>(new SwHTMLTableLayoutColumn( m_nWidth, m_bRelWidth, m_bLeftBorder ));
}
 
inline sal_uInt16 HTMLTableColumn::GetFrameFormatIdx( bool bBorderLine,
                                             sal_Int16 eVertOrient )
{
    OSL_ENSURE( text::VertOrientation::TOP != eVertOrient, "Top is not allowed" );
    sal_uInt16 n = bBorderLine ? 3 : 0;
    switch( eVertOrient )
    {
    case text::VertOrientation::CENTER:   n+=1;   break;
    case text::VertOrientation::BOTTOM:   n+=2;   break;
    default:
        ;
    }
    return n;
}
 
inline void HTMLTableColumn::SetFrameFormat( SwFrameFormat *pFormat, bool bBorderLine,
                                        sal_Int16 eVertOrient )
{
    m_aFrameFormats[GetFrameFormatIdx(bBorderLine,eVertOrient)] = pFormat;
}
 
inline SwFrameFormat *HTMLTableColumn::GetFrameFormat( bool bBorderLine,
                                             sal_Int16 eVertOrient ) const
{
    return m_aFrameFormats[GetFrameFormatIdx(bBorderLine,eVertOrient)];
}
 
void HTMLTable::InitCtor(const HTMLTableOptions& rOptions)
{
    m_nRows = 0;
    m_nCurrentRow = 0; m_nCurrentColumn = 0;
 
    m_pBoxFormat = nullptr; m_pLineFormat = nullptr;
    m_pLineFrameFormatNoHeight = nullptr;
    m_xInheritedBackgroundBrush.reset();
 
    m_pPrevStartNode = nullptr;
    m_pSwTable = nullptr;
 
    m_bTopBorder = false; m_bRightBorder = false;
    m_bTopAllowed = true; m_bRightAllowed = true;
    m_bFillerTopBorder = false; m_bFillerBottomBorder = false;
    m_bInheritedLeftBorder = false; m_bInheritedRightBorder = false;
    m_bBordersSet = false;
    m_bForceFrame = false;
    m_nHeadlineRepeat = 0;
 
    m_nLeftMargin = 0;
    m_nRightMargin = 0;
 
    const Color& rBorderColor = rOptions.aBorderColor;
 
    tools::Long nBorderOpt = static_cast<tools::Long>(rOptions.nBorder);
    tools::Long nPWidth = nBorderOpt==USHRT_MAX ? NETSCAPE_DFLT_BORDER
                                         : nBorderOpt;
    tools::Long nPHeight = nBorderOpt==USHRT_MAX ? 0 : nBorderOpt;
    SvxCSS1Parser::PixelToTwip( nPWidth, nPHeight );
 
    // nBorder tells the width of the border as it's used in the width calculation of NetScape
    // If pOption->nBorder == USHRT_MAX, there wasn't a BORDER option given
    // Nonetheless, a 1 pixel wide border will be used for width calculation
    m_nBorder = o3tl::narrowing<sal_uInt16>(nPWidth);
    if( nBorderOpt==USHRT_MAX )
        nPWidth = 0;
 
    if ( rOptions.nCellSpacing != 0 )
    {
        m_aTopBorderLine.SetBorderLineStyle(SvxBorderLineStyle::DOUBLE);
    }
    m_aTopBorderLine.SetWidth( nPHeight );
    m_aTopBorderLine.SetColor( rBorderColor );
    m_aBottomBorderLine = m_aTopBorderLine;
 
    if( nPWidth == nPHeight )
    {
        m_aLeftBorderLine = m_aTopBorderLine;
    }
    else
    {
        if ( rOptions.nCellSpacing != 0 )
        {
            m_aLeftBorderLine.SetBorderLineStyle(SvxBorderLineStyle::DOUBLE);
        }
        m_aLeftBorderLine.SetWidth( nPWidth );
        m_aLeftBorderLine.SetColor( rBorderColor );
    }
    m_aRightBorderLine = m_aLeftBorderLine;
 
    if( rOptions.nCellSpacing != 0 )
        m_aBorderLine.SetBorderLineStyle(SvxBorderLineStyle::DOUBLE);
    m_aBorderLine.SetWidth(SvxBorderLineWidth::Hairline);
    m_aBorderLine.SetColor( rBorderColor );
 
    if( m_nCellPadding )
    {
        if( m_nCellPadding==USHRT_MAX )
            m_nCellPadding = MIN_BORDER_DIST; // default
        else
        {
            m_nCellPadding = SwHTMLParser::ToTwips( m_nCellPadding );
            if( m_nCellPadding<MIN_BORDER_DIST  )
                m_nCellPadding = MIN_BORDER_DIST;
        }
    }
    if( m_nCellSpacing )
    {
        if( m_nCellSpacing==USHRT_MAX )
            m_nCellSpacing = NETSCAPE_DFLT_CELLSPACING;
        m_nCellSpacing = SwHTMLParser::ToTwips( m_nCellSpacing );
    }
 
    nPWidth = rOptions.nHSpace;
    nPHeight = rOptions.nVSpace;
    SvxCSS1Parser::PixelToTwip( nPWidth, nPHeight );
    m_nHSpace = o3tl::narrowing<sal_uInt16>(nPWidth);
    m_nVSpace = o3tl::narrowing<sal_uInt16>(nPHeight);
 
    m_bColSpec = false;
 
    m_xBackgroundBrush.reset(m_pParser->CreateBrushItem(
                    rOptions.bBGColor ? &(rOptions.aBGColor) : nullptr,
                    rOptions.aBGImage, OUString(), OUString(), OUString()));
 
    m_pContext = nullptr;
    m_xParentContents.reset();
 
    m_aId = rOptions.aId;
    m_aClass = rOptions.aClass;
    m_aStyle = rOptions.aStyle;
    m_aDir = rOptions.aDir;
}
 
HTMLTable::HTMLTable(SwHTMLParser* pPars,
                     bool bParHead,
                     bool bHasParentSec, bool bHasToFlw,
                     const HTMLTableOptions& rOptions) :
    m_aColumns(rOptions.nCols),
    m_nCols(rOptions.nCols),
    m_nFilledColumns( 0 ),
    m_nCellPadding(rOptions.nCellPadding),
    m_nCellSpacing(rOptions.nCellSpacing),
    m_nBoxes( 1 ),
    m_pCaptionStartNode( nullptr ),
    m_bTableAdjustOfTag( rOptions.bTableAdjust ),
    m_bIsParentHead( bParHead ),
    m_bHasParentSection( bHasParentSec ),
    m_bHasToFly( bHasToFlw ),
    m_bFixedCols( rOptions.nCols>0 ),
    m_bPercentWidth( rOptions.bPercentWidth ),
    m_pParser( pPars ),
    m_nWidth( rOptions.nWidth ),
    m_nHeight( rOptions.nHeight ),
    m_eTableAdjust( rOptions.eAdjust ),
    m_eVertOrientation( rOptions.eVertOri ),
    m_eFrame( rOptions.eFrame ),
    m_eRules( rOptions.eRules ),
    m_bTopCaption( false ),
    m_bFirstCell(true)
{
    InitCtor(rOptions);
    m_pParser->RegisterHTMLTable(this);
}
 
void SwHTMLParser::DeregisterHTMLTable(HTMLTable* pOld)
{
    if (pOld->m_xBox1)
        m_aOrphanedTableBoxes.emplace_back(std::move(pOld->m_xBox1));
    std::erase(m_aTables, pOld);
}
 
SwDoc* SwHTMLParser::GetDoc() const
{
    return m_xDoc.get();
}
 
bool SwHTMLParser::IsReqIF() const
{
    return m_bReqIF;
}
 
// if any m_xResizeDrawObjects members are deleted during parse, remove them
// from m_xResizeDrawObjects and m_xDrawObjectPercentWidths
void HTMLTable::ObjectInDestruction(const SdrObject& rObject)
{
    auto it = std::find(m_xResizeDrawObjects->begin(), m_xResizeDrawObjects->end(), &rObject);
    assert(it != m_xResizeDrawObjects->end());
    auto nIndex = std::distance(m_xResizeDrawObjects->begin(), it);
    m_xResizeDrawObjects->erase(it);
    auto otherit = m_xDrawObjectPercentWidths->begin() + nIndex * 3;
    m_xDrawObjectPercentWidths->erase(otherit, otherit + 3);
}
 
HTMLTable::~HTMLTable()
{
    m_pParser->DeregisterHTMLTable(this);
 
    if (m_xResizeDrawObjects)
    {
        size_t nCount = m_xResizeDrawObjects->size();
        for (size_t i = 0; i < nCount; ++i)
        {
            SdrObject *pObj = (*m_xResizeDrawObjects)[i];
            pObj->RemoveObjectUser(*this);
        }
        m_xResizeDrawObjects.reset();
    }
 
    m_xDrawObjectPercentWidths.reset();
 
    m_pContext.reset();
 
    // pLayoutInfo has either already been deleted or is now owned by SwTable
}
 
const std::shared_ptr<SwHTMLTableLayout>& HTMLTable::CreateLayoutInfo()
{
    sal_uInt16 nW = m_bPercentWidth ? m_nWidth : SwHTMLParser::ToTwips( m_nWidth );
 
    sal_uInt16 nBorderWidth = GetBorderWidth( m_aBorderLine, true );
    sal_uInt16 nLeftBorderWidth =
        m_aColumns[0].m_bLeftBorder ? GetBorderWidth(m_aLeftBorderLine, true) : 0;
    sal_uInt16 nRightBorderWidth =
        m_bRightBorder ? GetBorderWidth( m_aRightBorderLine, true ) : 0;
 
    m_xLayoutInfo = std::make_shared<SwHTMLTableLayout>(
                        m_pSwTable,
                        m_nRows, m_nCols, m_bFixedCols, m_bColSpec,
                        nW, m_bPercentWidth, m_nBorder, m_nCellPadding,
                        m_nCellSpacing, m_eTableAdjust,
                        m_nLeftMargin, m_nRightMargin,
                        nBorderWidth, nLeftBorderWidth, nRightBorderWidth);
 
    bool bExportable = true;
    sal_uInt16 i;
    for( i=0; i<m_nRows; i++ )
    {
        HTMLTableRow& rRow = m_aRows[i];
        for( sal_uInt16 j=0; j<m_nCols; j++ )
        {
            m_xLayoutInfo->SetCell(rRow.GetCell(j).CreateLayoutInfo(), i, j);
            SwHTMLTableLayoutCell* pLayoutCell = m_xLayoutInfo->GetCell(i, j );
 
            if( bExportable )
            {
                const std::shared_ptr<SwHTMLTableLayoutCnts>& rLayoutCnts =
                    pLayoutCell->GetContents();
                bExportable = !rLayoutCnts ||
                              (rLayoutCnts->GetStartNode() && !rLayoutCnts->GetNext());
            }
        }
    }
 
    m_xLayoutInfo->SetExportable( bExportable );
 
    for( i=0; i<m_nCols; i++ )
        m_xLayoutInfo->SetColumn(m_aColumns[i].CreateLayoutInfo(), i);
 
    return m_xLayoutInfo;
}
 
inline void HTMLTable::SetCaption( const SwStartNode *pStNd, bool bTop )
{
    m_pCaptionStartNode = pStNd;
    m_bTopCaption = bTop;
}
 
void HTMLTable::FixRowSpan( sal_uInt16 nRow, sal_uInt16 nCol,
                            const HTMLTableCnts *pCnts )
{
    sal_uInt16 nRowSpan=1;
    while (true)
    {
        HTMLTableCell& rCell = GetCell(nRow, nCol);
        if (rCell.GetContents().get() != pCnts)
            break;
        rCell.SetRowSpan(nRowSpan);
        if (m_xLayoutInfo)
            m_xLayoutInfo->GetCell(nRow,nCol)->SetRowSpan(nRowSpan);
 
        if( !nRow ) break;
        nRowSpan++; nRow--;
    }
}
 
void HTMLTable::ProtectRowSpan( sal_uInt16 nRow, sal_uInt16 nCol, sal_uInt16 nRowSpan )
{
    for( sal_uInt16 i=0; i<nRowSpan; i++ )
    {
        GetCell(nRow+i,nCol).SetProtected();
        if (m_xLayoutInfo)
            m_xLayoutInfo->GetCell(nRow+i,nCol)->SetProtected();
    }
}
 
// Search the SwStartNode of the last used predecessor box
const SwStartNode* HTMLTable::GetPrevBoxStartNode( sal_uInt16 nRow, sal_uInt16 nCol ) const
{
    const HTMLTableCnts *pPrevCnts = nullptr;
 
    if( 0==nRow )
    {
        // always the predecessor cell
        if( nCol>0 )
            pPrevCnts = GetCell(0, nCol - 1).GetContents().get();
        else
            return m_pPrevStartNode;
    }
    else if( USHRT_MAX==nRow && USHRT_MAX==nCol )
        // contents of preceding cell
        pPrevCnts = GetCell(m_nRows - 1, m_nCols - 1).GetContents().get();
    else
    {
        sal_uInt16 i;
        const HTMLTableRow& rPrevRow = m_aRows[nRow-1];
 
        // maybe a cell in the current row
        i = nCol;
        while( i )
        {
            i--;
            if( 1 == rPrevRow.GetCell(i).GetRowSpan() )
            {
                pPrevCnts = GetCell(nRow, i).GetContents().get();
                break;
            }
        }
 
        // otherwise the last filled cell of the row before
        if( !pPrevCnts )
        {
            i = m_nCols;
            while( !pPrevCnts && i )
            {
                i--;
                pPrevCnts = rPrevRow.GetCell(i).GetContents().get();
            }
        }
    }
    OSL_ENSURE( pPrevCnts, "No previous filled cell found" );
    if( !pPrevCnts )
    {
        pPrevCnts = GetCell(0, 0).GetContents().get();
        if( !pPrevCnts )
            return m_pPrevStartNode;
    }
 
    while( pPrevCnts->Next() )
        pPrevCnts = pPrevCnts->Next();
 
    const SwStartNode* pRet = pPrevCnts->GetStartNode();
    if (pRet)
        return pRet;
    HTMLTable* pTable = pPrevCnts->GetTable().get();
    if (!pTable)
        return nullptr;
    return pTable->GetPrevBoxStartNode(USHRT_MAX, USHRT_MAX);
}
 
sal_uInt16 HTMLTable::GetTopCellSpace( sal_uInt16 nRow ) const
{
    sal_uInt16 nSpace = m_nCellPadding;
 
    if( nRow == 0 )
    {
        nSpace += m_nBorder + m_nCellSpacing;
    }
 
    return nSpace;
}
 
sal_uInt16 HTMLTable::GetBottomCellSpace( sal_uInt16 nRow, sal_uInt16 nRowSpan ) const
{
    sal_uInt16 nSpace = m_nCellSpacing + m_nCellPadding;
 
    if( nRow+nRowSpan == m_nRows )
    {
        nSpace = nSpace + m_nBorder;
    }
 
    return nSpace;
}
 
void HTMLTable::FixFrameFormat( SwTableBox *pBox,
                             sal_uInt16 nRow, sal_uInt16 nCol,
                             sal_uInt16 nRowSpan, sal_uInt16 nColSpan,
                             bool bFirstPara, bool bLastPara ) const
{
    SwFrameFormat *pFrameFormat = nullptr;      // frame::Frame format
    sal_Int16 eVOri = text::VertOrientation::NONE;
    const SvxBrushItem *pBGBrushItem = nullptr;   // background
    std::shared_ptr<SvxBoxItem> pBoxItem;
    bool bTopLine = false, bBottomLine = false, bLastBottomLine = false;
    bool bReUsable = false;     // Format reusable?
    sal_uInt16 nEmptyRows = 0;
    bool bHasNumFormat = false;
    bool bHasValue = false;
    sal_uInt32 nNumFormat = 0;
    double nValue = 0.0;
 
    const HTMLTableColumn& rColumn = m_aColumns[nCol];
 
    if( pBox->GetSttNd() )
    {
        // Determine background color/graphic
        const HTMLTableCell& rCell = GetCell(nRow, nCol);
        pBoxItem = rCell.GetBoxItem();
        pBGBrushItem = rCell.GetBGBrush().get();
        if( !pBGBrushItem )
        {
            // If a cell spans multiple rows, a background to that row should be copied to the cell.
            if (nRowSpan > 1)
            {
                pBGBrushItem = m_aRows[nRow].GetBGBrush().get();
            }
        }
 
        bTopLine = 0==nRow && m_bTopBorder && bFirstPara;
        if (m_aRows[nRow+nRowSpan-1].GetBottomBorder() && bLastPara)
        {
            nEmptyRows = m_aRows[nRow+nRowSpan-1].GetEmptyRows();
            if( nRow+nRowSpan == m_nRows )
                bLastBottomLine = true;
            else
                bBottomLine = true;
        }
 
        eVOri = rCell.GetVertOri();
        bHasNumFormat = rCell.GetNumFormat( nNumFormat );
        if( bHasNumFormat )
            bHasValue = rCell.GetValue( nValue );
 
        if( nColSpan==1 && !bTopLine && !bLastBottomLine && !nEmptyRows &&
            !pBGBrushItem && !bHasNumFormat && !pBoxItem)
        {
            pFrameFormat = rColumn.GetFrameFormat( bBottomLine, eVOri );
            bReUsable = !pFrameFormat;
        }
    }
 
    if( !pFrameFormat )
    {
        pFrameFormat = pBox->ClaimFrameFormat();
 
        // calculate width of the box
        SwTwips nFrameWidth = static_cast<SwTwips>(m_xLayoutInfo->GetColumn(nCol)
                                                ->GetRelColWidth());
        for( sal_uInt16 i=1; i<nColSpan; i++ )
            nFrameWidth += static_cast<SwTwips>(m_xLayoutInfo->GetColumn(nCol+i)
                                             ->GetRelColWidth());
 
        // Only set the border on edit boxes.
        // On setting the upper and lower border, keep in mind if
        // it's the first or the last paragraph of the cell
        if( pBox->GetSttNd() )
        {
            bool bSet = (m_nCellPadding > 0);
 
            SvxBoxItem aBoxItem( RES_BOX );
            tools::Long nInnerFrameWidth = nFrameWidth;
 
            if( bTopLine )
            {
                aBoxItem.SetLine( &m_aTopBorderLine, SvxBoxItemLine::TOP );
                bSet = true;
            }
            if( bLastBottomLine )
            {
                aBoxItem.SetLine( &m_aBottomBorderLine, SvxBoxItemLine::BOTTOM );
                bSet = true;
            }
            else if( bBottomLine )
            {
                if( nEmptyRows && !m_aBorderLine.GetInWidth() )
                {
                    // For now, empty rows can only be emulated by thick lines, if it's a single line
                    SvxBorderLine aThickBorderLine( m_aBorderLine );
 
                    sal_uInt16 nBorderWidth = m_aBorderLine.GetOutWidth();
                    nBorderWidth *= (nEmptyRows + 1);
                    aThickBorderLine.SetBorderLineStyle(SvxBorderLineStyle::SOLID);
                    aThickBorderLine.SetWidth( nBorderWidth );
                    aBoxItem.SetLine( &aThickBorderLine, SvxBoxItemLine::BOTTOM );
                }
                else
                {
                    aBoxItem.SetLine( &m_aBorderLine, SvxBoxItemLine::BOTTOM );
                }
                bSet = true;
            }
            if (m_aColumns[nCol].m_bLeftBorder)
            {
                const SvxBorderLine& rBorderLine =
                    0==nCol ? m_aLeftBorderLine : m_aBorderLine;
                aBoxItem.SetLine( &rBorderLine, SvxBoxItemLine::LEFT );
                nInnerFrameWidth -= GetBorderWidth( rBorderLine );
                bSet = true;
            }
            if( m_bRightBorder )
            {
                aBoxItem.SetLine( &m_aRightBorderLine, SvxBoxItemLine::RIGHT );
                nInnerFrameWidth -= GetBorderWidth( m_aRightBorderLine );
                bSet = true;
            }
 
            if (pBoxItem)
            {
                pFrameFormat->SetFormatAttr( *pBoxItem );
            }
            else if (bSet)
            {
                // BorderDist is not part of a cell with fixed width
                sal_uInt16 nBDist = static_cast< sal_uInt16 >(
                    (2*m_nCellPadding <= nInnerFrameWidth) ? m_nCellPadding
                                                      : (nInnerFrameWidth / 2) );
                // We only set the item if there's a border or a border distance
                // If the latter is missing, there's gonna be a border and we'll have to set the distance
                aBoxItem.SetAllDistances(nBDist ? nBDist : MIN_BORDER_DIST);
                pFrameFormat->SetFormatAttr( aBoxItem );
            }
            else
                pFrameFormat->ResetFormatAttr( RES_BOX );
 
            if( pBGBrushItem )
            {
                pFrameFormat->SetFormatAttr( *pBGBrushItem );
            }
            else
                pFrameFormat->ResetFormatAttr( RES_BACKGROUND );
 
            // Only set format if there's a value or the box is empty
            if( bHasNumFormat && (bHasValue || pBox->IsEmpty()) )
            {
                bool bLock = pFrameFormat->GetDoc()->GetNumberFormatter()
                                     ->IsTextFormat( nNumFormat );
                SfxItemSetFixed<RES_BOXATR_FORMAT, RES_BOXATR_VALUE>
                    aItemSet( *pFrameFormat->GetAttrSet().GetPool() );
                SvxAdjust eAdjust = SvxAdjust::End;
                SwContentNode *pCNd = nullptr;
                if( !bLock )
                {
                    const SwStartNode *pSttNd = pBox->GetSttNd();
                    pCNd = pSttNd->GetNodes()[pSttNd->GetIndex()+1]
                                 ->GetContentNode();
                    const SvxAdjustItem *pItem;
                    if( pCNd && pCNd->HasSwAttrSet() &&
                        (pItem = pCNd->GetpSwAttrSet()->GetItemIfSet(
                            RES_PARATR_ADJUST, false )) )
                    {
                        eAdjust = pItem->GetAdjust();
                    }
                }
                aItemSet.Put( SwTableBoxNumFormat(nNumFormat) );
                if( bHasValue )
                    aItemSet.Put( SwTableBoxValue(nValue) );
 
                if( bLock )
                    pFrameFormat->LockModify();
                pFrameFormat->SetFormatAttr( aItemSet );
                if( bLock )
                    pFrameFormat->UnlockModify();
                else if( pCNd && SvxAdjust::End != eAdjust )
                {
                    SvxAdjustItem aAdjItem( eAdjust, RES_PARATR_ADJUST );
                    pCNd->SetAttr( aAdjItem );
                }
            }
            else
                pFrameFormat->ResetFormatAttr( RES_BOXATR_FORMAT );
 
            OSL_ENSURE( eVOri != text::VertOrientation::TOP, "text::VertOrientation::TOP is not allowed!" );
            if( text::VertOrientation::NONE != eVOri )
            {
                pFrameFormat->SetFormatAttr( SwFormatVertOrient( 0, eVOri ) );
            }
            else
                pFrameFormat->ResetFormatAttr( RES_VERT_ORIENT );
 
            if( bReUsable )
                const_cast<HTMLTableColumn&>(rColumn).SetFrameFormat(pFrameFormat, bBottomLine, eVOri);
        }
        else
        {
            pFrameFormat->ResetFormatAttr( RES_BOX );
            pFrameFormat->ResetFormatAttr( RES_BACKGROUND );
            pFrameFormat->ResetFormatAttr( RES_VERT_ORIENT );
            pFrameFormat->ResetFormatAttr( RES_BOXATR_FORMAT );
        }
 
        if (m_pParser->IsReqIF())
        {
            // ReqIF case, cells would have no formatting. Apply the default
            // table autoformat on them, so imported and UI-created tables look
            // the same.
            SwTableAutoFormatTable& rTable = m_pParser->GetDoc()->GetTableStyles();
            SwTableAutoFormat* pTableFormat = rTable.FindAutoFormat(
                SwStyleNameMapper::GetUIName(RES_POOLTABLESTYLE_DEFAULT, OUString()));
            if (pTableFormat)
            {
                sal_uInt8 nPos = SwTableAutoFormat::CountPos(nCol, m_nCols, nRow, m_nRows);
                const SfxItemSet& rAttrSet = pFrameFormat->GetAttrSet();
                std::unique_ptr<SvxBoxItem> pOldBoxItem;
                if (const SvxBoxItem* pBoxItem2 = rAttrSet.GetItemIfSet(RES_BOX))
                    pOldBoxItem.reset(pBoxItem2->Clone());
                pTableFormat->UpdateToSet(nPos, m_nRows==1, m_nCols==1,
                                          const_cast<SfxItemSet&>(rAttrSet),
                                          SwTableAutoFormatUpdateFlags::Box,
                                          pFrameFormat->GetDoc()->GetNumberFormatter());
                if (pOldBoxItem)
                {
                    // There was an old item, so it's guaranteed that there's a new item
                    const SvxBoxItem* pBoxItem2(rAttrSet.GetItem(RES_BOX));
                    if (*pBoxItem2 != *pOldBoxItem)
                    {
                        std::unique_ptr<SvxBoxItem> pNewBoxItem(pBoxItem2->Clone());
                        // Restore the box elements that could have been already set
                        for (auto eLine : { SvxBoxItemLine::TOP, SvxBoxItemLine::BOTTOM,
                                            SvxBoxItemLine::LEFT, SvxBoxItemLine::RIGHT })
                        {
                            if (auto pLine = pOldBoxItem->GetLine(eLine))
                                pNewBoxItem->SetLine(pLine, eLine);
                            if (auto nDistance = pOldBoxItem->GetDistance(eLine, true))
                                pNewBoxItem->SetDistance(nDistance, eLine);
                        }
 
                        pFrameFormat->SetFormatAttr(*pNewBoxItem);
                    }
                }
            }
        }
    }
    else
    {
        OSL_ENSURE( pBox->GetSttNd() ||
                SfxItemState::SET!=pFrameFormat->GetAttrSet().GetItemState(
                                    RES_VERT_ORIENT, false ),
                "Box without content has vertical orientation" );
        pBox->ChgFrameFormat( static_cast<SwTableBoxFormat*>(pFrameFormat) );
    }
 
}
 
SwTableBox *HTMLTable::NewTableBox( const SwStartNode *pStNd,
                                    SwTableLine *pUpper ) const
{
    SwTableBox *pBox;
 
    if (m_xBox1 && m_xBox1->GetSttNd() == pStNd)
    {
        // If the StartNode is the StartNode of the initially created box, we take that box
        pBox = const_cast<HTMLTable*>(this)->m_xBox1.release();
        pBox->SetUpper(pUpper);
    }
    else
        pBox = new SwTableBox( m_pBoxFormat, *pStNd, pUpper );
 
    return pBox;
}
 
static void ResetLineFrameFormatAttrs( SwFrameFormat *pFrameFormat )
{
    pFrameFormat->ResetFormatAttr( RES_FRM_SIZE );
    pFrameFormat->ResetFormatAttr( RES_BACKGROUND );
    OSL_ENSURE( SfxItemState::SET!=pFrameFormat->GetAttrSet().GetItemState(
                                RES_VERT_ORIENT, false ),
            "Cell has vertical orientation" );
}
 
// !!! could be simplified
SwTableLine *HTMLTable::MakeTableLine( SwTableBox *pUpper,
                                       sal_uInt16 nTopRow, sal_uInt16 nLeftCol,
                                       sal_uInt16 nBottomRow, sal_uInt16 nRightCol )
{
    SwTableLine *pLine;
    if (!pUpper && 0 == nTopRow)
        pLine = (m_pSwTable->GetTabLines())[0];
    else
        pLine = new SwTableLine( m_pLineFrameFormatNoHeight ? m_pLineFrameFormatNoHeight
                                                     : m_pLineFormat,
                                 0, pUpper );
 
    const HTMLTableRow& rTopRow = m_aRows[nTopRow];
    sal_uInt16 nRowHeight = rTopRow.GetHeight();
    const SvxBrushItem *pBGBrushItem = nullptr;
    if (nTopRow > 0 || nBottomRow < m_nRows)
    {
        // It doesn't make sense to set a color on a line,
        // if it's the outermost and simultaneously sole line of a table in a table
        pBGBrushItem = rTopRow.GetBGBrush().get();
    }
    if( nTopRow==nBottomRow-1 && (nRowHeight || pBGBrushItem) )
    {
        SwTableLineFormat *pFrameFormat = pLine->ClaimFrameFormat();
        ResetLineFrameFormatAttrs( pFrameFormat );
 
        if( nRowHeight )
        {
            // set table height. Since it's a minimum height it can be calculated like in Netscape,
            // so without considering the actual border width
            nRowHeight += GetTopCellSpace( nTopRow ) +
                       GetBottomCellSpace( nTopRow, 1 );
 
            pFrameFormat->SetFormatAttr( SwFormatFrameSize( SwFrameSize::Minimum, 0, nRowHeight ) );
        }
 
        if( pBGBrushItem )
        {
            pFrameFormat->SetFormatAttr( *pBGBrushItem );
        }
 
    }
    else if( !m_pLineFrameFormatNoHeight )
    {
        // else, we'll have to remove the height from the attribute and remember the format
        m_pLineFrameFormatNoHeight = pLine->ClaimFrameFormat();
 
        ResetLineFrameFormatAttrs( m_pLineFrameFormatNoHeight );
    }
 
    SwTableBoxes& rBoxes = pLine->GetTabBoxes();
 
    sal_uInt16 nStartCol = nLeftCol;
    while( nStartCol<nRightCol )
    {
        sal_uInt16 nCol = nStartCol;
        sal_uInt16 nSplitCol = nRightCol;
        bool bSplitted = false;
        while( !bSplitted )
        {
            OSL_ENSURE( nCol < nRightCol, "Gone too far" );
 
            HTMLTableCell& rCell = GetCell(nTopRow,nCol);
            const bool bSplit = 1 == rCell.GetColSpan();
 
            OSL_ENSURE((nCol != nRightCol-1) || bSplit, "Split-Flag wrong");
            if( bSplit )
            {
                SwTableBox* pBox = nullptr;
                HTMLTableCell& rCell2 = GetCell(nTopRow, nStartCol);
                if (rCell2.GetColSpan() == (nCol+1-nStartCol))
                {
                    // The HTML tables represent a box. So we need to split behind that box
                    nSplitCol = nCol + 1;
 
                    sal_Int32 nBoxRowSpan = rCell2.GetRowSpan();
                    if (!rCell2.GetContents() || rCell2.IsCovered())
                    {
                        if (rCell2.IsCovered())
                            nBoxRowSpan = -1 * nBoxRowSpan;
 
                        const SwStartNode* pPrevStartNd =
                            GetPrevBoxStartNode( nTopRow, nStartCol );
                        auto xCnts = std::make_shared<HTMLTableCnts>(
                            m_pParser->InsertTableSection(pPrevStartNd));
                        const std::shared_ptr<SwHTMLTableLayoutCnts> xCntsLayoutInfo =
                            xCnts->CreateLayoutInfo();
 
                        rCell2.SetContents(xCnts);
                        SwHTMLTableLayoutCell *pCurrCell = m_xLayoutInfo->GetCell(nTopRow, nStartCol);
                        pCurrCell->SetContents(xCntsLayoutInfo);
                        if( nBoxRowSpan < 0 )
                            pCurrCell->SetRowSpan( 0 );
 
                        // check COLSPAN if needed
                        for( sal_uInt16 j=nStartCol+1; j<nSplitCol; j++ )
                        {
                            GetCell(nTopRow, j).SetContents(xCnts);
                            m_xLayoutInfo->GetCell(nTopRow, j)
                                       ->SetContents(xCntsLayoutInfo);
                        }
                    }
 
                    pBox = MakeTableBox(pLine, rCell2.GetContents().get(),
                                        nTopRow, nStartCol,
                                        nBottomRow, nSplitCol);
 
                    if (1 != nBoxRowSpan && pBox)
                        pBox->setRowSpan( nBoxRowSpan );
 
                    bSplitted = true;
                }
 
                OSL_ENSURE( pBox, "Colspan trouble" );
 
                if( pBox )
                    rBoxes.push_back( pBox );
            }
            nCol++;
        }
        nStartCol = nSplitCol;
    }
 
    return pLine;
}
 
SwTableBox *HTMLTable::MakeTableBox( SwTableLine *pUpper,
                                     HTMLTableCnts *pCnts,
                                     sal_uInt16 nTopRow, sal_uInt16 nLeftCol,
                                     sal_uInt16 nBottomRow, sal_uInt16 nRightCol )
{
    SwTableBox *pBox;
    sal_uInt16 nColSpan = nRightCol - nLeftCol;
    sal_uInt16 nRowSpan = nBottomRow - nTopRow;
 
    if( !pCnts->Next() )
    {
        // just one content section
        if( pCnts->GetStartNode() )
        {
            // ... that's not a table
            pBox = NewTableBox( pCnts->GetStartNode(), pUpper );
            pCnts->SetTableBox( pBox );
        }
        else if (HTMLTable* pTable = pCnts->GetTable().get())
        {
            pTable->InheritVertBorders( this, nLeftCol,
                                                   nRightCol-nLeftCol );
            // ... that's a table. We'll build a new box and put the rows of the table
            // in the rows of the box
            pBox = new SwTableBox( m_pBoxFormat, 0, pUpper );
            sal_uInt16 nAbs, nRel;
            m_xLayoutInfo->GetAvail( nLeftCol, nColSpan, nAbs, nRel );
            sal_uInt16 nLSpace = m_xLayoutInfo->GetLeftCellSpace( nLeftCol, nColSpan );
            sal_uInt16 nRSpace = m_xLayoutInfo->GetRightCellSpace( nLeftCol, nColSpan );
            sal_uInt16 nInhSpace = m_xLayoutInfo->GetInhCellSpace( nLeftCol, nColSpan );
            pCnts->GetTable()->MakeTable( pBox, nAbs, nRel, nLSpace, nRSpace,
                                          nInhSpace );
        }
        else
        {
            return nullptr;
        }
    }
    else
    {
        // multiple content sections: we'll build a box with rows
        pBox = new SwTableBox( m_pBoxFormat, 0, pUpper );
        SwTableLines& rLines = pBox->GetTabLines();
        bool bFirstPara = true;
 
        while( pCnts )
        {
            if( pCnts->GetStartNode() )
            {
                // normal paragraphs are gonna be boxes in a row
                SwTableLine *pLine =
                    new SwTableLine( m_pLineFrameFormatNoHeight ? m_pLineFrameFormatNoHeight
                                                         : m_pLineFormat, 0, pBox );
                if( !m_pLineFrameFormatNoHeight )
                {
                    // If there's no line format without height yet, we can use that one
                    m_pLineFrameFormatNoHeight = pLine->ClaimFrameFormat();
 
                    ResetLineFrameFormatAttrs( m_pLineFrameFormatNoHeight );
                }
 
                SwTableBox* pCntBox = NewTableBox( pCnts->GetStartNode(),
                                                   pLine );
                pCnts->SetTableBox( pCntBox );
                FixFrameFormat( pCntBox, nTopRow, nLeftCol, nRowSpan, nColSpan,
                             bFirstPara, nullptr==pCnts->Next() );
                pLine->GetTabBoxes().push_back( pCntBox );
 
                rLines.push_back( pLine );
            }
            else
            {
                pCnts->GetTable()->InheritVertBorders( this, nLeftCol,
                                                       nRightCol-nLeftCol );
                // Tables are entered directly
                sal_uInt16 nAbs, nRel;
                m_xLayoutInfo->GetAvail( nLeftCol, nColSpan, nAbs, nRel );
                sal_uInt16 nLSpace = m_xLayoutInfo->GetLeftCellSpace( nLeftCol,
                                                                nColSpan );
                sal_uInt16 nRSpace = m_xLayoutInfo->GetRightCellSpace( nLeftCol,
                                                                 nColSpan );
                sal_uInt16 nInhSpace = m_xLayoutInfo->GetInhCellSpace( nLeftCol, nColSpan );
                pCnts->GetTable()->MakeTable( pBox, nAbs, nRel, nLSpace,
                                              nRSpace, nInhSpace );
            }
 
            pCnts = pCnts->Next();
            bFirstPara = false;
        }
    }
 
    FixFrameFormat( pBox, nTopRow, nLeftCol, nRowSpan, nColSpan );
 
    return pBox;
}
 
void HTMLTable::InheritBorders( const HTMLTable *pParent,
                                sal_uInt16 nRow, sal_uInt16 nCol,
                                sal_uInt16 nRowSpan,
                                bool bFirstPara, bool bLastPara )
{
    OSL_ENSURE( m_nRows>0 && m_nCols>0 && m_nCurrentRow==m_nRows,
            "Was CloseTable not called?" );
 
    // The child table needs a border, if the surrounding cell has a margin on that side.
    // The upper/lower border is only set if the table is the first/last paragraph in that cell
    // It can't be determined if a border for that table is needed or possible for the left or right side,
    // since that's depending on if filler cells are gonna be added. We'll only collect info for now
 
    if( 0==nRow && pParent->m_bTopBorder && bFirstPara )
    {
        m_bTopBorder = true;
        m_bFillerTopBorder = true; // fillers get a border too
        m_aTopBorderLine = pParent->m_aTopBorderLine;
    }
    if (pParent->m_aRows[nRow+nRowSpan-1].GetBottomBorder() && bLastPara)
    {
        m_aRows[m_nRows-1].SetBottomBorder(true);
        m_bFillerBottomBorder = true; // fillers get a border too
        m_aBottomBorderLine =
            nRow+nRowSpan==pParent->m_nRows ? pParent->m_aBottomBorderLine
                                          : pParent->m_aBorderLine;
    }
 
    // The child table mustn't get an upper or lower border, if that's already done by the surrounding table
    // It can get an upper border if the table is not the first paragraph in that cell
    m_bTopAllowed = ( !bFirstPara || (pParent->m_bTopAllowed &&
                 (0==nRow || !pParent->m_aRows[nRow-1].GetBottomBorder())) );
 
    // The child table has to inherit the color of the cell it's contained in, if it doesn't have one
    const SvxBrushItem *pInhBG = pParent->GetCell(nRow, nCol).GetBGBrush().get();
    if( !pInhBG && pParent != this &&
        pParent->GetCell(nRow,nCol).GetRowSpan() == pParent->m_nRows )
    {
        // the whole surrounding table is a table in a table and consists only of a single line
        // that's gonna be GC-ed (correctly). That's why the background of that line is copied.
        pInhBG = pParent->m_aRows[nRow].GetBGBrush().get();
        if( !pInhBG )
            pInhBG = pParent->GetBGBrush().get();
        if( !pInhBG )
            pInhBG = pParent->GetInhBGBrush().get();
    }
    if( pInhBG )
        m_xInheritedBackgroundBrush.reset(new SvxBrushItem(*pInhBG));
}
 
void HTMLTable::InheritVertBorders( const HTMLTable *pParent,
                                 sal_uInt16 nCol, sal_uInt16 nColSpan )
{
    sal_uInt16 nInhLeftBorderWidth = 0;
    sal_uInt16 nInhRightBorderWidth = 0;
 
    if( nCol+nColSpan==pParent->m_nCols && pParent->m_bRightBorder )
    {
        m_bInheritedRightBorder = true; // just remember for now
        m_aInheritedRightBorderLine = pParent->m_aRightBorderLine;
        nInhRightBorderWidth =
            GetBorderWidth( m_aInheritedRightBorderLine, true ) + MIN_BORDER_DIST;
    }
 
    if (pParent->m_aColumns[nCol].m_bLeftBorder)
    {
        m_bInheritedLeftBorder = true;  // just remember for now
        m_aInheritedLeftBorderLine = 0==nCol ? pParent->m_aLeftBorderLine
                                     : pParent->m_aBorderLine;
        nInhLeftBorderWidth =
            GetBorderWidth( m_aInheritedLeftBorderLine, true ) + MIN_BORDER_DIST;
    }
 
    if( !m_bInheritedLeftBorder && (m_bFillerTopBorder || m_bFillerBottomBorder) )
        nInhLeftBorderWidth = 2 * MIN_BORDER_DIST;
    if( !m_bInheritedRightBorder && (m_bFillerTopBorder || m_bFillerBottomBorder) )
        nInhRightBorderWidth = 2 * MIN_BORDER_DIST;
    m_xLayoutInfo->SetInhBorderWidths( nInhLeftBorderWidth,
                                     nInhRightBorderWidth );
 
    m_bRightAllowed = ( pParent->m_bRightAllowed &&
                  (nCol+nColSpan==pParent->m_nCols ||
                   !pParent->m_aColumns[nCol+nColSpan].m_bLeftBorder) );
}
 
void HTMLTable::SetBorders()
{
    sal_uInt16 i;
    for( i=1; i<m_nCols; i++ )
        if( HTMLTableRules::All==m_eRules || HTMLTableRules::Cols==m_eRules ||
            ((HTMLTableRules::Rows==m_eRules || HTMLTableRules::Groups==m_eRules) &&
             m_aColumns[i-1].IsEndOfGroup()))
        {
            m_aColumns[i].m_bLeftBorder = true;
        }
 
    for( i=0; i<m_nRows-1; i++ )
        if( HTMLTableRules::All==m_eRules || HTMLTableRules::Rows==m_eRules ||
            ((HTMLTableRules::Cols==m_eRules || HTMLTableRules::Groups==m_eRules) &&
             m_aRows[i].IsEndOfGroup()))
        {
            m_aRows[i].SetBottomBorder(true);
        }
 
    if( m_bTopAllowed && (HTMLTableFrame::Above==m_eFrame || HTMLTableFrame::HSides==m_eFrame ||
                     HTMLTableFrame::Box==m_eFrame) )
        m_bTopBorder = true;
    if( HTMLTableFrame::Below==m_eFrame || HTMLTableFrame::HSides==m_eFrame ||
        HTMLTableFrame::Box==m_eFrame )
    {
        m_aRows[m_nRows-1].SetBottomBorder(true);
    }
    if( HTMLTableFrame::RHS==m_eFrame || HTMLTableFrame::VSides==m_eFrame ||
                      HTMLTableFrame::Box==m_eFrame )
        m_bRightBorder = true;
    if( HTMLTableFrame::LHS==m_eFrame || HTMLTableFrame::VSides==m_eFrame || HTMLTableFrame::Box==m_eFrame )
    {
        m_aColumns[0].m_bLeftBorder = true;
    }
 
    for( i=0; i<m_nRows; i++ )
    {
        HTMLTableRow& rRow = m_aRows[i];
        for (sal_uInt16 j=0; j<m_nCols; ++j)
        {
            HTMLTableCell& rCell = rRow.GetCell(j);
            if (rCell.GetContents())
            {
                HTMLTableCnts *pCnts = rCell.GetContents().get();
                bool bFirstPara = true;
                while( pCnts )
                {
                    HTMLTable *pTable = pCnts->GetTable().get();
                    if( pTable && !pTable->BordersSet() )
                    {
                        pTable->InheritBorders(this, i, j,
                                               rCell.GetRowSpan(),
                                               bFirstPara,
                                               nullptr==pCnts->Next());
                        pTable->SetBorders();
                    }
                    bFirstPara = false;
                    pCnts = pCnts->Next();
                }
            }
        }
    }
 
    m_bBordersSet = true;
}
 
sal_uInt16 HTMLTable::GetBorderWidth( const SvxBorderLine& rBLine,
                                  bool bWithDistance ) const
{
    sal_uInt16 nBorderWidth = rBLine.GetWidth();
    if( bWithDistance )
    {
        if( m_nCellPadding )
            nBorderWidth = nBorderWidth + m_nCellPadding;
        else if( nBorderWidth )
            nBorderWidth = nBorderWidth + MIN_BORDER_DIST;
    }
 
    return nBorderWidth;
}
 
const HTMLTableCell& HTMLTable::GetCell(sal_uInt16 nRow, sal_uInt16 nCell) const
{
    OSL_ENSURE(nRow < m_aRows.size(), "invalid row index in HTML table");
    return m_aRows[nRow].GetCell(nCell);
}
 
SvxAdjust HTMLTable::GetInheritedAdjust() const
{
    SvxAdjust eAdjust = (m_nCurrentColumn<m_nCols ? m_aColumns[m_nCurrentColumn].GetAdjust()
                                       : SvxAdjust::End );
    if( SvxAdjust::End==eAdjust )
        eAdjust = m_aRows[m_nCurrentRow].GetAdjust();
 
    return eAdjust;
}
 
sal_Int16 HTMLTable::GetInheritedVertOri() const
{
    // text::VertOrientation::TOP is default!
    sal_Int16 eVOri = m_aRows[m_nCurrentRow].GetVertOri();
    if( text::VertOrientation::TOP==eVOri && m_nCurrentColumn<m_nCols )
        eVOri = m_aColumns[m_nCurrentColumn].GetVertOri();
    if( text::VertOrientation::TOP==eVOri )
        eVOri = m_eVertOrientation;
 
    OSL_ENSURE( m_eVertOrientation != text::VertOrientation::TOP, "text::VertOrientation::TOP is not allowed!" );
    return eVOri;
}
 
void HTMLTable::InsertCell( std::shared_ptr<HTMLTableCnts> const& rCnts,
                            sal_uInt16 nRowSpan, sal_uInt16 nColSpan,
                            sal_uInt16 nCellWidth, bool bRelWidth, sal_uInt16 nCellHeight,
                            sal_Int16 eVertOrient, std::shared_ptr<SvxBrushItem> const& rBGBrushItem,
                            std::shared_ptr<SvxBoxItem> const& rBoxItem,
                            bool bHasNumFormat, sal_uInt32 nNumFormat,
                            bool bHasValue, double nValue, bool bNoWrap )
{
    if( !nRowSpan || static_cast<sal_uInt32>(m_nCurrentRow) + nRowSpan > USHRT_MAX )
        nRowSpan = 1;
 
    if( !nColSpan || static_cast<sal_uInt32>(m_nCurrentColumn) + nColSpan > USHRT_MAX )
        nColSpan = 1;
 
    sal_uInt16 nColsReq = m_nCurrentColumn + nColSpan;
    sal_uInt16 nRowsReq = m_nCurrentRow + nRowSpan;
    sal_uInt16 i, j;
 
    // if we need more columns than we currently have, we need to add cells for all rows
    if( m_nCols < nColsReq )
    {
        m_aColumns.resize(nColsReq);
        for( i=0; i<m_nRows; i++ )
            m_aRows[i].Expand( nColsReq, i<m_nCurrentRow );
        m_nCols = nColsReq;
        OSL_ENSURE(m_aColumns.size() == m_nCols,
                "wrong number of columns after expanding");
    }
    if( nColsReq > m_nFilledColumns )
        m_nFilledColumns = nColsReq;
 
    // if we need more rows than we currently have, we need to add cells
    if( m_nRows < nRowsReq )
    {
        for( i=m_nRows; i<nRowsReq; i++ )
            m_aRows.emplace_back(m_nCols);
        m_nRows = nRowsReq;
        OSL_ENSURE(m_nRows == m_aRows.size(), "wrong number of rows in Insert");
    }
 
    // Check if we have an overlap and could remove that
    sal_uInt16 nSpanedCols = 0;
    if( m_nCurrentRow>0 )
    {
        HTMLTableRow& rCurRow = m_aRows[m_nCurrentRow];
        for( i=m_nCurrentColumn; i<nColsReq; i++ )
        {
            HTMLTableCell& rCell = rCurRow.GetCell(i);
            if (rCell.GetContents())
            {
                // A cell from a row further above overlaps this one.
                // Content and colors are coming from that cell and can be overwritten
                // or deleted (content) or copied (color) by ProtectRowSpan
                nSpanedCols = i + rCell.GetColSpan();
                FixRowSpan( m_nCurrentRow-1, i, rCell.GetContents().get() );
                if (rCell.GetRowSpan() > nRowSpan)
                    ProtectRowSpan( nRowsReq, i,
                                    rCell.GetRowSpan()-nRowSpan );
            }
        }
        for( i=nColsReq; i<nSpanedCols; i++ )
        {
            // These contents are anchored in the row above in any case
            HTMLTableCell& rCell = rCurRow.GetCell(i);
            FixRowSpan( m_nCurrentRow-1, i, rCell.GetContents().get() );
            ProtectRowSpan( m_nCurrentRow, i, rCell.GetRowSpan() );
        }
    }
 
    // Fill the cells
    for( i=nColSpan; i>0; i-- )
    {
        for( j=nRowSpan; j>0; j-- )
        {
            const bool bCovered = i != nColSpan || j != nRowSpan;
            GetCell( nRowsReq-j, nColsReq-i )
                .Set( rCnts, j, i, eVertOrient, rBGBrushItem, rBoxItem,
                       bHasNumFormat, nNumFormat, bHasValue, nValue, bNoWrap, bCovered );
        }
    }
 
    Size aTwipSz( bRelWidth ? 0 : nCellWidth, nCellHeight );
    if( aTwipSz.Width() || aTwipSz.Height() )
    {
        aTwipSz = o3tl::convert(aTwipSz, o3tl::Length::px, o3tl::Length::twip);
    }
 
    // Only set width on the first cell!
    if( nCellWidth )
    {
        sal_uInt16 nTmp = bRelWidth ? nCellWidth : o3tl::narrowing<sal_uInt16>(aTwipSz.Width());
        GetCell( m_nCurrentRow, m_nCurrentColumn ).SetWidth( nTmp, bRelWidth );
    }
 
    // Remember height
    if( nCellHeight && 1==nRowSpan )
    {
        m_aRows[m_nCurrentRow].SetHeight(o3tl::narrowing<sal_uInt16>(aTwipSz.Height()));
    }
 
    // Set the column counter behind the new cells
    m_nCurrentColumn = nColsReq;
    if( nSpanedCols > m_nCurrentColumn )
        m_nCurrentColumn = nSpanedCols;
 
    // and search for the next free cell
    while( m_nCurrentColumn<m_nCols && GetCell(m_nCurrentRow,m_nCurrentColumn).IsUsed() )
        m_nCurrentColumn++;
}
 
inline void HTMLTable::CloseSection( bool bHead )
{
    // Close the preceding sections if there's already a row
    OSL_ENSURE( m_nCurrentRow<=m_nRows, "invalid current row" );
    if( m_nCurrentRow>0 && m_nCurrentRow<=m_nRows )
        m_aRows[m_nCurrentRow-1].SetEndOfGroup();
    if( bHead )
        m_nHeadlineRepeat = m_nCurrentRow;
}
 
void HTMLTable::OpenRow(SvxAdjust eAdjust, sal_Int16 eVertOrient,
                        std::unique_ptr<SvxBrushItem>& rBGBrushItem)
{
    sal_uInt16 nRowsReq = m_nCurrentRow+1;
 
    // create the next row if it's not there already
    if( m_nRows<nRowsReq )
    {
        for( sal_uInt16 i=m_nRows; i<nRowsReq; i++ )
            m_aRows.emplace_back(m_nCols);
        m_nRows = nRowsReq;
        OSL_ENSURE( m_nRows == m_aRows.size(),
                "Row number in OpenRow is wrong" );
    }
 
    HTMLTableRow& rCurRow = m_aRows[m_nCurrentRow];
    rCurRow.SetAdjust(eAdjust);
    rCurRow.SetVertOri(eVertOrient);
    if (rBGBrushItem)
        m_aRows[m_nCurrentRow].SetBGBrush(rBGBrushItem);
 
    // reset the column counter
    m_nCurrentColumn=0;
 
    // and search for the next free cell
    while( m_nCurrentColumn<m_nCols && GetCell(m_nCurrentRow,m_nCurrentColumn).IsUsed() )
        m_nCurrentColumn++;
}
 
void HTMLTable::CloseRow( bool bEmpty )
{
    OSL_ENSURE( m_nCurrentRow<m_nRows, "current row after table end" );
 
    // empty cells just get a slightly thicker lower border!
    if( bEmpty )
    {
        if( m_nCurrentRow > 0 )
            m_aRows[m_nCurrentRow-1].IncEmptyRows();
        return;
    }
 
    HTMLTableRow& rRow = m_aRows[m_nCurrentRow];
 
    // modify the COLSPAN of all empty cells at the row end in a way, that they're forming a single cell
    // that can be done here (and not earlier) since there's no more cells in that row
    sal_uInt16 i=m_nCols;
    while( i )
    {
        HTMLTableCell& rCell = rRow.GetCell(--i);
        if (!rCell.GetContents())
        {
            sal_uInt16 nColSpan = m_nCols-i;
            if( nColSpan > 1 )
                rCell.SetColSpan(nColSpan);
        }
        else
            break;
    }
 
    m_nCurrentRow++;
}
 
inline void HTMLTable::CloseColGroup( sal_uInt16 nSpan, sal_uInt16 _nWidth,
                                      bool bRelWidth, SvxAdjust eAdjust,
                                      sal_Int16 eVertOrient )
{
    if( nSpan )
        InsertCol( nSpan, _nWidth, bRelWidth, eAdjust, eVertOrient );
 
    OSL_ENSURE( m_nCurrentColumn<=m_nCols, "invalid column" );
    if( m_nCurrentColumn>0 && m_nCurrentColumn<=m_nCols )
        m_aColumns[m_nCurrentColumn-1].SetEndOfGroup();
}
 
void HTMLTable::InsertCol( sal_uInt16 nSpan, sal_uInt16 nColWidth, bool bRelWidth,
                           SvxAdjust eAdjust, sal_Int16 eVertOrient )
{
    // #i35143# - no columns, if rows already exist.
    if ( m_nRows > 0 )
        return;
 
    sal_uInt16 i;
 
    if( !nSpan )
        nSpan = 1;
 
    sal_uInt16 nColsReq = m_nCurrentColumn + nSpan;
 
    if( m_nCols < nColsReq )
    {
        m_aColumns.resize(nColsReq);
        m_nCols = nColsReq;
    }
 
    sal_uInt16 nTwipWidth(bRelWidth ? 0 : o3tl::convert(nColWidth, o3tl::Length::px, o3tl::Length::twip));
 
    for( i=m_nCurrentColumn; i<nColsReq; i++ )
    {
        HTMLTableColumn& rCol = m_aColumns[i];
        sal_uInt16 nTmp = bRelWidth ? nColWidth : o3tl::narrowing<sal_uInt16>(nTwipWidth);
        rCol.SetWidth( nTmp, bRelWidth );
        rCol.SetAdjust( eAdjust );
        rCol.SetVertOri( eVertOrient );
    }
 
    m_bColSpec = true;
 
    m_nCurrentColumn = nColsReq;
}
 
void HTMLTable::CloseTable()
{
    sal_uInt16 i;
 
    // The number of table rows is only dependent on the <TR> elements (i.e. nCurRow).
    // Rows that are spanned via ROWSPAN behind nCurRow need to be deleted
    // and we need to adjust the ROWSPAN in the rows above
    if( m_nRows>m_nCurrentRow )
    {
        HTMLTableRow& rPrevRow = m_aRows[m_nCurrentRow-1];
        for( i=0; i<m_nCols; i++ )
        {
            HTMLTableCell& rCell = rPrevRow.GetCell(i);
            if (rCell.GetRowSpan() > 1)
            {
                FixRowSpan(m_nCurrentRow-1, i, rCell.GetContents().get());
                ProtectRowSpan(m_nCurrentRow, i, m_aRows[m_nCurrentRow].GetCell(i).GetRowSpan());
            }
        }
        for( i=m_nRows-1; i>=m_nCurrentRow; i-- )
            m_aRows.erase(m_aRows.begin() + i);
        m_nRows = m_nCurrentRow;
    }
 
    // if the table has no column, we need to add one
    if( 0==m_nCols )
    {
        m_aColumns.resize(1);
        for( i=0; i<m_nRows; i++ )
            m_aRows[i].Expand(1);
        m_nCols = 1;
        m_nFilledColumns = 1;
    }
 
    // if the table has no row, we need to add one
    if( 0==m_nRows )
    {
        m_aRows.emplace_back(m_nCols);
        m_nRows = 1;
        m_nCurrentRow = 1;
    }
 
    if( m_nFilledColumns < m_nCols )
    {
        m_aColumns.erase(m_aColumns.begin() + m_nFilledColumns, m_aColumns.begin() + m_nCols);
        for( i=0; i<m_nRows; i++ )
            m_aRows[i].Shrink( m_nFilledColumns );
        m_nCols = m_nFilledColumns;
    }
}
 
void HTMLTable::MakeTable_( SwTableBox *pBox )
{
    SwTableLines& rLines = (pBox ? pBox->GetTabLines()
                                 : const_cast<SwTable *>(m_pSwTable)->GetTabLines() );
 
    for( sal_uInt16 i=0; i<m_nRows; i++ )
    {
        SwTableLine *pLine = MakeTableLine( pBox, i, 0, i+1, m_nCols );
        if( pBox || i > 0 )
            rLines.push_back( pLine );
    }
}
 
/* How are tables aligned?
 
first row: without paragraph indents
second row: with paragraph indents
 
ALIGN=          LEFT            RIGHT           CENTER          -
-------------------------------------------------------------------------
xxx for tables with WIDTH=nn% the percentage value is important:
xxx nn = 100        text::HoriOrientation::FULL       text::HoriOrientation::FULL       text::HoriOrientation::FULL       text::HoriOrientation::FULL %
xxx                 text::HoriOrientation::NONE       text::HoriOrientation::NONE       text::HoriOrientation::NONE %     text::HoriOrientation::NONE %
xxx nn < 100        frame F        frame F        text::HoriOrientation::CENTER %   text::HoriOrientation::LEFT %
xxx                 frame F        frame F        text::HoriOrientation::CENTER %   text::HoriOrientation::NONE %
 
for tables with WIDTH=nn% the percentage value is important:
nn = 100        text::HoriOrientation::LEFT       text::HoriOrientation::RIGHT      text::HoriOrientation::CENTER %   text::HoriOrientation::LEFT %
                text::HoriOrientation::LEFT_AND   text::HoriOrientation::RIGHT      text::HoriOrientation::CENTER %   text::HoriOrientation::LEFT_AND %
nn < 100        frame F        frame F        text::HoriOrientation::CENTER %   text::HoriOrientation::LEFT %
                frame F        frame F        text::HoriOrientation::CENTER %   text::HoriOrientation::NONE %
 
otherwise the calculated width w
w = avail*      text::HoriOrientation::LEFT       text::HoriOrientation::RIGHT      text::HoriOrientation::CENTER     text::HoriOrientation::LEFT
                HORI_LEDT_AND   text::HoriOrientation::RIGHT      text::HoriOrientation::CENTER     text::HoriOrientation::LEFT_AND
w < avail       frame L        frame L        text::HoriOrientation::CENTER     text::HoriOrientation::LEFT
                frame L        frame L        text::HoriOrientation::CENTER     text::HoriOrientation::NONE
 
xxx *) if for the table no size was specified, always
xxx   text::HoriOrientation::FULL is taken
 
*/
 
void HTMLTable::MakeTable( SwTableBox *pBox, sal_uInt16 nAbsAvail,
                           sal_uInt16 nRelAvail, sal_uInt16 nAbsLeftSpace,
                           sal_uInt16 nAbsRightSpace, sal_uInt16 nInhAbsSpace )
{
    OSL_ENSURE( m_nRows>0 && m_nCols>0 && m_nCurrentRow==m_nRows,
            "Was CloseTable not called?" );
 
    OSL_ENSURE(m_xLayoutInfo == nullptr, "Table already has layout info");
 
    // Calculate borders of the table and all contained tables
    SetBorders();
 
    // Step 1: needed layout structures are created (including tables in tables)
    CreateLayoutInfo();
 
    if (!comphelper::IsFuzzing()) // skip slow path for fuzzing
    {
        // Step 2: the minimal and maximal column width is calculated
        // (including tables in tables). Since we don't have boxes yet,
        // we'll work on the start nodes
        m_xLayoutInfo->AutoLayoutPass1();
 
        // Step 3: the actual column widths of this table are calculated (not tables in tables)
        // We need this now to decide if we need filler cells
        // (Pass1 was needed because of this as well)
        m_xLayoutInfo->AutoLayoutPass2( nAbsAvail, nRelAvail, nAbsLeftSpace,
                                      nAbsRightSpace, nInhAbsSpace );
    }
 
    // Set adjustment for the top table
    sal_Int16 eHoriOri;
    if (m_bForceFrame)
    {
        // The table should go in a text frame and it's narrower than the
        // available space and not 100% wide. So it gets a border
        eHoriOri = m_bPercentWidth ? text::HoriOrientation::FULL : text::HoriOrientation::LEFT;
    }
    else switch (m_eTableAdjust)
    {
        // The table either fits the page but shouldn't get a text frame,
        // or it's wider than the page so it doesn't need a text frame
 
    case SvxAdjust::Right:
        // Don't be considerate of the right margin in right-adjusted tables
        eHoriOri = text::HoriOrientation::RIGHT;
        break;
    case SvxAdjust::Center:
        // Centred tables are not considerate of margins
        eHoriOri = text::HoriOrientation::CENTER;
        break;
    case SvxAdjust::Left:
    default:
        // left-adjusted tables are only considerate of the left margin
        eHoriOri = m_nLeftMargin ? text::HoriOrientation::LEFT_AND_WIDTH : text::HoriOrientation::LEFT;
        break;
    }
 
    if (!m_pSwTable)
    {
        SAL_WARN("sw.html", "no table");
        return;
    }
 
    // get the table format and adapt it
    SwFrameFormat *pFrameFormat = m_pSwTable->GetFrameFormat();
    pFrameFormat->SetFormatAttr( SwFormatHoriOrient(0, eHoriOri) );
    if (text::HoriOrientation::LEFT_AND_WIDTH == eHoriOri)
    {
        OSL_ENSURE( m_nLeftMargin || m_nRightMargin,
                "There are still leftovers from relative margins" );
 
        // The right margin will be ignored anyway.
        SvxLRSpaceItem aLRItem( m_pSwTable->GetFrameFormat()->GetLRSpace() );
        aLRItem.SetLeft( m_nLeftMargin );
        aLRItem.SetRight( m_nRightMargin );
        pFrameFormat->SetFormatAttr( aLRItem );
    }
 
    if (m_bPercentWidth && text::HoriOrientation::FULL != eHoriOri)
    {
        pFrameFormat->LockModify();
        SwFormatFrameSize aFrameSize( pFrameFormat->GetFrameSize() );
        aFrameSize.SetWidthPercent( static_cast<sal_uInt8>(m_nWidth) );
        pFrameFormat->SetFormatAttr( aFrameSize );
        pFrameFormat->UnlockModify();
    }
 
    // get the default line and box format
    // remember the first box and unlist it from the first row
    SwTableLine *pLine1 = (m_pSwTable->GetTabLines())[0];
    m_xBox1.reset((pLine1->GetTabBoxes())[0]);
    pLine1->GetTabBoxes().erase(pLine1->GetTabBoxes().begin());
 
    m_pLineFormat = pLine1->GetFrameFormat();
    m_pBoxFormat = m_xBox1->GetFrameFormat();
 
    MakeTable_( pBox );
 
    // Finally, we'll do a garbage collection for the top level table
 
    if( 1==m_nRows && m_nHeight && 1==m_pSwTable->GetTabLines().size() )
    {
        // Set height of a one-row table as the minimum width of the row
        // Was originally a fixed height, but that made problems
        // and is not Netscape 4.0 compliant
        m_nHeight = SwHTMLParser::ToTwips( m_nHeight );
        if( m_nHeight < MINLAY )
            m_nHeight = MINLAY;
 
        (m_pSwTable->GetTabLines())[0]->ClaimFrameFormat();
        (m_pSwTable->GetTabLines())[0]->GetFrameFormat()
            ->SetFormatAttr( SwFormatFrameSize( SwFrameSize::Minimum, 0, m_nHeight ) );
    }
 
    if( GetBGBrush() )
        m_pSwTable->GetFrameFormat()->SetFormatAttr( *GetBGBrush() );
 
    const_cast<SwTable *>(m_pSwTable)->SetRowsToRepeat( static_cast< sal_uInt16 >(m_nHeadlineRepeat) );
    const_cast<SwTable *>(m_pSwTable)->GCLines();
 
    bool bIsInFlyFrame = m_pContext && m_pContext->GetFrameFormat();
    if( bIsInFlyFrame && !m_nWidth )
    {
        SvxAdjust eAdjust = GetTableAdjust(false);
        if (eAdjust != SvxAdjust::Left &&
            eAdjust != SvxAdjust::Right)
        {
            // If a table with a width attribute isn't flowed around left or right
            // we'll stack it with a border of 100% width, so its size will
            // be adapted. That text frame mustn't be modified
            OSL_ENSURE( HasToFly(), "Why is the table in a frame?" );
            sal_uInt32 nMin = m_xLayoutInfo->GetMin();
            if( nMin > USHRT_MAX )
                nMin = USHRT_MAX;
            SwFormatFrameSize aFlyFrameSize( SwFrameSize::Variable, static_cast<SwTwips>(nMin), MINLAY );
            aFlyFrameSize.SetWidthPercent( 100 );
            m_pContext->GetFrameFormat()->SetFormatAttr( aFlyFrameSize );
            bIsInFlyFrame = false;
        }
        else
        {
            // left or right adjusted table without width mustn't be adjusted in width
            // as they would only shrink but never grow
            m_xLayoutInfo->SetMustNotRecalc( true );
            if( m_pContext->GetFrameFormat()->GetAnchor().GetAnchorNode()
                ->FindTableNode() )
            {
                sal_uInt32 nMax = m_xLayoutInfo->GetMax();
                if( nMax > USHRT_MAX )
                    nMax = USHRT_MAX;
                SwFormatFrameSize aFlyFrameSize( SwFrameSize::Variable, static_cast<SwTwips>(nMax), MINLAY );
                m_pContext->GetFrameFormat()->SetFormatAttr( aFlyFrameSize );
                bIsInFlyFrame = false;
            }
            else
            {
                m_xLayoutInfo->SetMustNotResize( true );
            }
        }
    }
    m_xLayoutInfo->SetMayBeInFlyFrame( bIsInFlyFrame );
 
    // Only tables with relative width or without width should be modified
    m_xLayoutInfo->SetMustResize( m_bPercentWidth || !m_nWidth );
 
    if (!pLine1->GetTabBoxes().empty())
        m_xLayoutInfo->SetWidths();
    else
        SAL_WARN("sw.html", "no table box");
 
    const_cast<SwTable *>(m_pSwTable)->SetHTMLTableLayout(m_xLayoutInfo);
 
    if( !m_xResizeDrawObjects )
        return;
 
    sal_uInt16 nCount = m_xResizeDrawObjects->size();
    for( sal_uInt16 i=0; i<nCount; i++ )
    {
        SdrObject *pObj = (*m_xResizeDrawObjects)[i];
        sal_uInt16 nRow = (*m_xDrawObjectPercentWidths)[3*i];
        sal_uInt16 nCol = (*m_xDrawObjectPercentWidths)[3*i+1];
        sal_uInt8 nPercentWidth = static_cast<sal_uInt8>((*m_xDrawObjectPercentWidths)[3*i+2]);
 
        SwHTMLTableLayoutCell *pLayoutCell =
            m_xLayoutInfo->GetCell( nRow, nCol );
        sal_uInt16 nColSpan = pLayoutCell->GetColSpan();
 
        sal_uInt16 nWidth2, nDummy;
        m_xLayoutInfo->GetAvail( nCol, nColSpan, nWidth2, nDummy );
        nWidth2 = static_cast< sal_uInt16 >((static_cast<tools::Long>(m_nWidth) * nPercentWidth) / 100);
 
        SwHTMLParser::ResizeDrawObject( pObj, nWidth2 );
    }
 
}
 
void HTMLTable::SetTable( const SwStartNode *pStNd, std::unique_ptr<HTMLTableContext> pCntxt,
                          sal_uInt16 nLeft, sal_uInt16 nRight,
                          const SwTable *pSwTab, bool bFrcFrame )
{
    m_pPrevStartNode = pStNd;
    m_pSwTable = pSwTab;
    m_pContext = std::move(pCntxt);
 
    m_nLeftMargin = nLeft;
    m_nRightMargin = nRight;
 
    m_bForceFrame = bFrcFrame;
}
 
void HTMLTable::RegisterDrawObject( SdrObject *pObj, sal_uInt8 nPercentWidth )
{
    if( !m_xResizeDrawObjects )
        m_xResizeDrawObjects.emplace();
    m_xResizeDrawObjects->push_back( pObj );
    pObj->AddObjectUser(*this);
 
    if( !m_xDrawObjectPercentWidths )
        m_xDrawObjectPercentWidths.emplace();
    m_xDrawObjectPercentWidths->push_back( m_nCurrentRow );
    m_xDrawObjectPercentWidths->push_back( m_nCurrentColumn );
    m_xDrawObjectPercentWidths->push_back( o3tl::narrowing<sal_uInt16>(nPercentWidth) );
}
 
void HTMLTable::MakeParentContents()
{
    if( !GetContext() && !HasParentSection() )
    {
        SetParentContents(
            m_pParser->InsertTableContents( m_bIsParentHead ) );
 
        SetHasParentSection( true );
    }
}
 
void HTMLTableContext::SavePREListingXMP( SwHTMLParser& rParser )
{
    m_bRestartPRE = rParser.IsReadPRE();
    m_bRestartXMP = rParser.IsReadXMP();
    m_bRestartListing = rParser.IsReadListing();
    rParser.FinishPREListingXMP();
}
 
void HTMLTableContext::RestorePREListingXMP( SwHTMLParser& rParser )
{
    rParser.FinishPREListingXMP();
 
    if( m_bRestartPRE )
        rParser.StartPRE();
 
    if( m_bRestartXMP )
        rParser.StartXMP();
 
    if( m_bRestartListing )
        rParser.StartListing();
}
 
const SwStartNode *SwHTMLParser::InsertTableSection
    ( const SwStartNode *pPrevStNd )
{
    OSL_ENSURE( pPrevStNd, "Start-Node is NULL" );
 
    m_pCSS1Parser->SetTDTagStyles();
    SwTextFormatColl *pColl = m_pCSS1Parser->GetTextCollFromPool( RES_POOLCOLL_TABLE );
 
    const SwStartNode *pStNd;
    if (m_xTable->m_bFirstCell )
    {
        SwNode *const pNd = & m_pPam->GetPoint()->GetNode();
        pNd->GetTextNode()->ChgFormatColl( pColl );
        pStNd = pNd->FindTableBoxStartNode();
        m_xTable->m_bFirstCell = false;
    }
    else if (pPrevStNd)
    {
        const SwNode* pNd;
        if( pPrevStNd->IsTableNode() )
            pNd = pPrevStNd;
        else
            pNd = pPrevStNd->EndOfSectionNode();
        SwNodeIndex nIdx( *pNd, 1 );
        pStNd = m_xDoc->GetNodes().MakeTextSection( nIdx.GetNode(), SwTableBoxStartNode,
                                                  pColl );
        m_xTable->IncBoxCount();
    }
    else
    {
        eState = SvParserState::Error;
        return nullptr;
    }
 
    //Added defaults to CJK and CTL
    SwContentNode *pCNd = m_xDoc->GetNodes()[pStNd->GetIndex()+1] ->GetContentNode();
    SvxFontHeightItem aFontHeight( 40, 100, RES_CHRATR_FONTSIZE );
    pCNd->SetAttr( aFontHeight );
    SvxFontHeightItem aFontHeightCJK( 40, 100, RES_CHRATR_CJK_FONTSIZE );
    pCNd->SetAttr( aFontHeightCJK );
    SvxFontHeightItem aFontHeightCTL( 40, 100, RES_CHRATR_CTL_FONTSIZE );
    pCNd->SetAttr( aFontHeightCTL );
 
    return pStNd;
}
 
const SwStartNode *SwHTMLParser::InsertTableSection( sal_uInt16 nPoolId )
{
    switch( nPoolId )
    {
    case RES_POOLCOLL_TABLE_HDLN:
        m_pCSS1Parser->SetTHTagStyles();
        break;
    case RES_POOLCOLL_TABLE:
        m_pCSS1Parser->SetTDTagStyles();
        break;
    }
 
    SwTextFormatColl *pColl = m_pCSS1Parser->GetTextCollFromPool( nPoolId );
 
    SwNode *const pNd = & m_pPam->GetPoint()->GetNode();
    const SwStartNode *pStNd;
    if (m_xTable->m_bFirstCell)
    {
        SwTextNode* pTextNd = pNd->GetTextNode();
        if (!pTextNd)
        {
            eState = SvParserState::Error;
            return nullptr;
        }
        pTextNd->ChgFormatColl(pColl);
        m_xTable->m_bFirstCell = false;
        pStNd = pNd->FindTableBoxStartNode();
    }
    else
    {
        SwTableNode *pTableNd = pNd->FindTableNode();
        if (!pTableNd)
        {
            eState = SvParserState::Error;
            return nullptr;
        }
        if( pTableNd->GetTable().GetHTMLTableLayout() )
        { // if there is already a HTMTableLayout, this table is already finished
          // and we have to look for the right table in the environment
            SwTableNode *pOutTable = pTableNd;
            do {
                pTableNd = pOutTable;
                pOutTable = pOutTable->StartOfSectionNode()->FindTableNode();
            } while( pOutTable && pTableNd->GetTable().GetHTMLTableLayout() );
        }
        pStNd = m_xDoc->GetNodes().MakeTextSection( *pTableNd->EndOfSectionNode(), SwTableBoxStartNode,
                                                  pColl );
 
        m_pPam->GetPoint()->Assign( pStNd->GetIndex() + 1 );
        m_xTable->IncBoxCount();
    }
 
    if (!pStNd)
    {
        eState = SvParserState::Error;
    }
 
    return pStNd;
}
 
SwStartNode *SwHTMLParser::InsertTempTableCaptionSection()
{
    SwTextFormatColl *pColl = m_pCSS1Parser->GetTextCollFromPool( RES_POOLCOLL_TEXT );
    SwStartNode *pStNd = m_xDoc->GetNodes().MakeTextSection( m_xDoc->GetNodes().GetEndOfExtras(),
                                          SwNormalStartNode, pColl );
 
    m_pPam->GetPoint()->Assign( pStNd->GetIndex() + 1);
 
    return pStNd;
}
 
sal_Int32 SwHTMLParser::StripTrailingLF()
{
    sal_Int32 nStripped = 0;
 
    if (IsReqIF())
    {
        // One <br> is exactly one line-break in the ReqIF case.
        return nStripped;
    }
 
    const sal_Int32 nLen = m_pPam->GetPoint()->GetContentIndex();
    if( nLen )
    {
        SwTextNode* pTextNd = m_pPam->GetPoint()->GetNode().GetTextNode();
        // careful, when comments aren't ignored!!!
        if( pTextNd )
        {
            sal_Int32 nPos = nLen;
            sal_Int32 nLFCount = 0;
            while (nPos && ('\x0a' == pTextNd->GetText()[--nPos]))
                nLFCount++;
 
            if( nLFCount )
            {
                if( nLFCount > 2 )
                {
                    // On Netscape, a paragraph end matches 2 LFs
                    // (1 is just a newline, 2 creates a blank line)
                    // We already have this space with the lower paragraph gap
                    // If there's a paragraph after the <BR>, we take the maximum
                    // of the gap that results from the <BR> and <P>
                    // That's why we need to delete 2 respectively all if less than 2
                    nLFCount = 2;
                }
 
                nPos = nLen - nLFCount;
                SwContentIndex nIdx( pTextNd, nPos );
                pTextNd->EraseText( nIdx, nLFCount );
                nStripped = nLFCount;
            }
        }
    }
 
    return nStripped;
}
 
SvxBrushItem* SwHTMLParser::CreateBrushItem( const Color *pColor,
                                             const OUString& rImageURL,
                                             const OUString& rStyle,
                                             const OUString& rId,
                                             const OUString& rClass )
{
    SvxBrushItem *pBrushItem = nullptr;
 
    if( !rStyle.isEmpty() || !rId.isEmpty() || !rClass.isEmpty() )
    {
        SfxItemSetFixed<RES_BACKGROUND, RES_BACKGROUND> aItemSet( m_xDoc->GetAttrPool() );
        SvxCSS1PropertyInfo aPropInfo;
 
        if( !rClass.isEmpty() )
        {
            OUString aClass( rClass );
            SwCSS1Parser::GetScriptFromClass( aClass );
            const SvxCSS1MapEntry *pClass = m_pCSS1Parser->GetClass( aClass );
            if( pClass )
                aItemSet.Put( pClass->GetItemSet() );
        }
 
        if( !rId.isEmpty() )
        {
            const SvxCSS1MapEntry *pId = m_pCSS1Parser->GetId( rId );
            if( pId )
                aItemSet.Put( pId->GetItemSet() );
        }
 
        m_pCSS1Parser->ParseStyleOption( rStyle, aItemSet, aPropInfo );
        if( const SvxBrushItem *pItem = aItemSet.GetItemIfSet( RES_BACKGROUND, false ) )
        {
            pBrushItem = new SvxBrushItem( *pItem );
        }
    }
 
    if( !pBrushItem && (pColor || !rImageURL.isEmpty()) )
    {
        pBrushItem = new SvxBrushItem(RES_BACKGROUND);
 
        if( pColor )
            pBrushItem->SetColor(*pColor);
 
        if( !rImageURL.isEmpty() )
        {
            pBrushItem->SetGraphicLink( URIHelper::SmartRel2Abs( INetURLObject(m_sBaseURL), rImageURL, Link<OUString *, bool>(), false) );
            pBrushItem->SetGraphicPos( GPOS_TILED );
        }
    }
 
    return pBrushItem;
}
 
class SectionSaveStruct : public SwPendingData
{
    sal_uInt16 m_nBaseFontStMinSave, m_nFontStMinSave, m_nFontStHeadStartSave;
    sal_uInt16 m_nDefListDeepSave;
    size_t m_nContextStMinSave;
    size_t m_nContextStAttrMinSave;
 
public:
 
    std::shared_ptr<HTMLTable> m_xTable;
 
    explicit SectionSaveStruct( SwHTMLParser& rParser );
 
#if OSL_DEBUG_LEVEL > 0
    size_t GetContextStAttrMin() const { return m_nContextStAttrMinSave; }
#endif
    void Restore( SwHTMLParser& rParser );
};
 
SectionSaveStruct::SectionSaveStruct( SwHTMLParser& rParser ) :
    m_nBaseFontStMinSave(rParser.m_nBaseFontStMin),
    m_nFontStMinSave(rParser.m_nFontStMin),
    m_nFontStHeadStartSave(rParser.m_nFontStHeadStart),
    m_nDefListDeepSave(rParser.m_nDefListDeep),
    m_nContextStMinSave(rParser.m_nContextStMin),
    m_nContextStAttrMinSave(rParser.m_nContextStAttrMin)
{
    // Freeze font stacks
    rParser.m_nBaseFontStMin = rParser.m_aBaseFontStack.size();
 
    rParser.m_nFontStMin = rParser.m_aFontStack.size();
 
    // Freeze context stack
    rParser.m_nContextStMin = rParser.m_aContexts.size();
    rParser.m_nContextStAttrMin = rParser.m_nContextStMin;
 
    // And remember a few counters
    rParser.m_nDefListDeep = 0;
}
 
void SectionSaveStruct::Restore( SwHTMLParser& rParser )
{
    // Unfreeze font stacks
    sal_uInt16 nMin = rParser.m_nBaseFontStMin;
    if( rParser.m_aBaseFontStack.size() > nMin )
        rParser.m_aBaseFontStack.erase( rParser.m_aBaseFontStack.begin() + nMin,
                rParser.m_aBaseFontStack.end() );
    rParser.m_nBaseFontStMin = m_nBaseFontStMinSave;
 
    nMin = rParser.m_nFontStMin;
    if( rParser.m_aFontStack.size() > nMin )
        rParser.m_aFontStack.erase( rParser.m_aFontStack.begin() + nMin,
                rParser.m_aFontStack.end() );
    rParser.m_nFontStMin = m_nFontStMinSave;
    rParser.m_nFontStHeadStart = m_nFontStHeadStartSave;
 
    OSL_ENSURE( rParser.m_aContexts.size() == rParser.m_nContextStMin &&
            rParser.m_aContexts.size() == rParser.m_nContextStAttrMin,
            "The Context Stack was not cleaned up" );
    rParser.m_nContextStMin = m_nContextStMinSave;
    rParser.m_nContextStAttrMin = m_nContextStAttrMinSave;
 
    // Reconstruct a few counters
    rParser.m_nDefListDeep = m_nDefListDeepSave;
 
    // Reset a few flags
    rParser.m_bNoParSpace = false;
    rParser.m_nOpenParaToken = HtmlTokenId::NONE;
 
    rParser.m_aParaAttrs.clear();
}
 
class CellSaveStruct : public SectionSaveStruct
{
    OUString m_aStyle, m_aId, m_aClass;
    OUString m_aBGImage;
    Color m_aBGColor;
    std::shared_ptr<SvxBoxItem> m_xBoxItem;
 
    std::shared_ptr<HTMLTableCnts> m_xCnts;              // List of all contents
    HTMLTableCnts* m_pCurrCnts;                          // current content or 0
    std::optional<SwNodeIndex> m_oNoBreakEndNodeIndex; // Paragraph index of a <NOBR>
 
    double m_nValue;
 
    sal_uInt32 m_nNumFormat;
 
    sal_uInt16 m_nRowSpan, m_nColSpan, m_nWidth, m_nHeight;
    sal_Int32 m_nNoBreakEndContentPos;     // Character index of a <NOBR>
 
    sal_Int16 m_eVertOri;
 
    bool m_bHead : 1;
    bool m_bPercentWidth : 1;
    bool m_bHasNumFormat : 1;
    bool m_bHasValue : 1;
    bool m_bBGColor : 1;
    bool m_bNoWrap : 1;       // NOWRAP option
    bool m_bNoBreak : 1;      // NOBREAK tag
 
public:
 
    CellSaveStruct( SwHTMLParser& rParser, HTMLTable const *pCurTable, bool bHd,
                     bool bReadOpt );
 
    void AddContents( std::unique_ptr<HTMLTableCnts> pNewCnts );
    bool HasFirstContents() const { return bool(m_xCnts); }
 
    void ClearIsInSection() { m_pCurrCnts = nullptr; }
    bool IsInSection() const { return m_pCurrCnts!=nullptr; }
 
    void InsertCell( SwHTMLParser& rParser, HTMLTable *pCurTable );
 
    bool IsHeaderCell() const { return m_bHead; }
 
    void StartNoBreak( const SwPosition& rPos );
    void EndNoBreak( const SwPosition& rPos );
    void CheckNoBreak( const SwPosition& rPos );
};
 
CellSaveStruct::CellSaveStruct( SwHTMLParser& rParser, HTMLTable const *pCurTable,
                                  bool bHd, bool bReadOpt ) :
    SectionSaveStruct( rParser ),
    m_pCurrCnts( nullptr ),
    m_nValue( 0.0 ),
    m_nNumFormat( 0 ),
    m_nRowSpan( 1 ),
    m_nColSpan( 1 ),
    m_nWidth( 0 ),
    m_nHeight( 0 ),
    m_nNoBreakEndContentPos( 0 ),
    m_eVertOri( pCurTable->GetInheritedVertOri() ),
    m_bHead( bHd ),
    m_bPercentWidth( false ),
    m_bHasNumFormat( false ),
    m_bHasValue( false ),
    m_bBGColor( false ),
    m_bNoWrap( false ),
    m_bNoBreak( false )
{
    OUString aNumFormat, aValue, aDir, aLang;
    SvxAdjust eAdjust( pCurTable->GetInheritedAdjust() );
 
    if( bReadOpt )
    {
        const HTMLOptions& rOptions = rParser.GetOptions();
        for (size_t i = rOptions.size(); i; )
        {
            const HTMLOption& rOption = rOptions[--i];
            switch( rOption.GetToken() )
            {
            case HtmlOptionId::ID:
                m_aId = rOption.GetString();
                break;
            case HtmlOptionId::COLSPAN:
                m_nColSpan = o3tl::narrowing<sal_uInt16>(rOption.GetNumber());
                if (m_nColSpan > 256)
                {
                    SAL_INFO("sw.html", "ignoring huge COLSPAN " << m_nColSpan);
                    m_nColSpan = 1;
                }
                break;
            case HtmlOptionId::ROWSPAN:
                m_nRowSpan = o3tl::narrowing<sal_uInt16>(rOption.GetNumber());
                if (m_nRowSpan > 8192 || (m_nRowSpan > 256 && comphelper::IsFuzzing()))
                {
                    SAL_INFO("sw.html", "ignoring huge ROWSPAN " << m_nRowSpan);
                    m_nRowSpan = 1;
                }
                break;
            case HtmlOptionId::ALIGN:
                eAdjust = rOption.GetEnum( aHTMLPAlignTable, eAdjust );
                break;
            case HtmlOptionId::VALIGN:
                m_eVertOri = rOption.GetEnum( aHTMLTableVAlignTable, m_eVertOri );
                break;
            case HtmlOptionId::WIDTH:
                m_nWidth = o3tl::narrowing<sal_uInt16>(rOption.GetNumber());   // Just for Netscape
                m_bPercentWidth = (rOption.GetString().indexOf('%') != -1);
                if( m_bPercentWidth && m_nWidth>100 )
                    m_nWidth = 100;
                break;
            case HtmlOptionId::HEIGHT:
                m_nHeight = o3tl::narrowing<sal_uInt16>(rOption.GetNumber());  // Just for Netscape
                if( rOption.GetString().indexOf('%') != -1)
                    m_nHeight = 0;    // don't consider % attributes
                break;
            case HtmlOptionId::BGCOLOR:
                // Ignore empty BGCOLOR on <TABLE>, <TR> and <TD>/<TH> like Netscape
                // *really* not on other tags
                if( !rOption.GetString().isEmpty() )
                {
                    rOption.GetColor( m_aBGColor );
                    m_bBGColor = true;
                }
                break;
            case HtmlOptionId::BACKGROUND:
                m_aBGImage = rOption.GetString();
                break;
            case HtmlOptionId::STYLE:
                m_aStyle = rOption.GetString();
                break;
            case HtmlOptionId::CLASS:
                m_aClass = rOption.GetString();
                break;
            case HtmlOptionId::LANG:
                aLang = rOption.GetString();
                break;
            case HtmlOptionId::DIR:
                aDir = rOption.GetString();
                break;
            case HtmlOptionId::SDNUM:
                aNumFormat = rOption.GetString();
                m_bHasNumFormat = true;
                break;
            case HtmlOptionId::SDVAL:
                m_bHasValue = true;
                aValue = rOption.GetString();
                break;
            case HtmlOptionId::NOWRAP:
                m_bNoWrap = true;
                break;
            default: break;
            }
        }
 
        if( !m_aId.isEmpty() )
            rParser.InsertBookmark( m_aId );
    }
 
    if( m_bHasNumFormat )
    {
        LanguageType eLang;
        m_nValue = SfxHTMLParser::GetTableDataOptionsValNum(
                            m_nNumFormat, eLang, aValue, aNumFormat,
                            *rParser.m_xDoc->GetNumberFormatter() );
    }
 
    // Create a new context but don't anchor the drawing::Alignment attribute there,
    // since there's no section yet
    HtmlTokenId nToken;
    sal_uInt16 nColl;
    if( m_bHead )
    {
        nToken = HtmlTokenId::TABLEHEADER_ON;
        nColl = RES_POOLCOLL_TABLE_HDLN;
    }
    else
    {
        nToken = HtmlTokenId::TABLEDATA_ON;
        nColl = RES_POOLCOLL_TABLE;
    }
    std::unique_ptr<HTMLAttrContext> xCntxt(new HTMLAttrContext(nToken, nColl, OUString(), true));
    if( SvxAdjust::End != eAdjust )
        rParser.InsertAttr(&rParser.m_xAttrTab->pAdjust, SvxAdjustItem(eAdjust, RES_PARATR_ADJUST),
                           xCntxt.get());
 
    if( SwHTMLParser::HasStyleOptions( m_aStyle, m_aId, m_aClass, &aLang, &aDir ) )
    {
        SfxItemSet aItemSet( rParser.m_xDoc->GetAttrPool(),
                             rParser.m_pCSS1Parser->GetWhichMap() );
        SvxCSS1PropertyInfo aPropInfo;
 
        if( rParser.ParseStyleOptions( m_aStyle, m_aId, m_aClass, aItemSet,
                                       aPropInfo, &aLang, &aDir ) )
        {
            if (SvxBoxItem const* pItem = aItemSet.GetItemIfSet(RES_BOX, false))
            {   // fdo#41796: steal box item to set it in FixFrameFormat later!
                m_xBoxItem.reset(pItem->Clone());
                aItemSet.ClearItem(RES_BOX);
            }
            rParser.InsertAttrs(aItemSet, aPropInfo, xCntxt.get());
        }
    }
 
    rParser.SplitPREListingXMP(xCntxt.get());
 
    rParser.PushContext(xCntxt);
}
 
void CellSaveStruct::AddContents( std::unique_ptr<HTMLTableCnts> pNewCnts )
{
    m_pCurrCnts = pNewCnts.get();
 
    if (m_xCnts)
        m_xCnts->Add( std::move(pNewCnts) );
    else
        m_xCnts = std::move(pNewCnts);
}
 
void CellSaveStruct::InsertCell( SwHTMLParser& rParser,
                                  HTMLTable *pCurTable )
{
#if OSL_DEBUG_LEVEL > 0
    // The attributes need to have been removed when tidying up the context stack,
    // Otherwise something's wrong. Let's check that...
 
    // MIB 8.1.98: When attributes were opened outside of a cell,
    // they're still in the attribute table and will only be deleted at the end
    // through the CleanContext calls in BuildTable. We don't check that there
    // so that we get no assert [violations, by translator]
    // We can see this on nContextStAttrMin: the remembered value of nContextStAttrMinSave
    // is the value that nContextStAttrMin had at the start of the table. And the
    // current value of nContextStAttrMin corresponds to the number of contexts
    // we found at the start of the cell. If the values differ, contexts
    // were created and we don't check anything.
 
    if( rParser.m_nContextStAttrMin == GetContextStAttrMin() )
    {
        HTMLAttr** pTable = reinterpret_cast<HTMLAttr**>(rParser.m_xAttrTab.get());
 
        for( auto nCnt = sizeof( HTMLAttrTable ) / sizeof( HTMLAttr* );
            nCnt--; ++pTable )
        {
            OSL_ENSURE( !*pTable, "The attribute table isn't empty" );
        }
    }
#endif
 
    // we need to add the cell on the current position
    std::shared_ptr<SvxBrushItem> xBrushItem(
        rParser.CreateBrushItem(m_bBGColor ? &m_aBGColor : nullptr, m_aBGImage,
                                m_aStyle, m_aId, m_aClass));
    pCurTable->InsertCell( m_xCnts, m_nRowSpan, m_nColSpan, m_nWidth,
                           m_bPercentWidth, m_nHeight, m_eVertOri, xBrushItem, m_xBoxItem,
                           m_bHasNumFormat, m_nNumFormat, m_bHasValue, m_nValue,
                           m_bNoWrap );
    Restore( rParser );
}
 
void CellSaveStruct::StartNoBreak( const SwPosition& rPos )
{
    if( !m_xCnts ||
        (!rPos.GetContentIndex() && m_pCurrCnts == m_xCnts.get() &&
         m_xCnts->GetStartNode() &&
         m_xCnts->GetStartNode()->GetIndex() + 1 ==
            rPos.GetNodeIndex()) )
    {
        m_bNoBreak = true;
    }
}
 
void CellSaveStruct::EndNoBreak( const SwPosition& rPos )
{
    if( m_bNoBreak )
    {
        m_oNoBreakEndNodeIndex.emplace( rPos.GetNode() );
        m_nNoBreakEndContentPos = rPos.GetContentIndex();
        m_bNoBreak = false;
    }
}
 
void CellSaveStruct::CheckNoBreak( const SwPosition& rPos )
{
    if (!(m_xCnts && m_pCurrCnts == m_xCnts.get()))
        return;
 
    if( m_bNoBreak )
    {
        // <NOBR> wasn't closed
        m_xCnts->SetNoBreak();
    }
    else if( m_oNoBreakEndNodeIndex &&
             m_oNoBreakEndNodeIndex->GetIndex() == rPos.GetNodeIndex() )
    {
        if( m_nNoBreakEndContentPos == rPos.GetContentIndex() )
        {
            // <NOBR> was closed immediately before the cell end
            m_xCnts->SetNoBreak();
        }
        else if( m_nNoBreakEndContentPos + 1 == rPos.GetContentIndex() )
        {
            SwTextNode const*const pTextNd(rPos.GetNode().GetTextNode());
            if( pTextNd )
            {
                sal_Unicode const cLast =
                        pTextNd->GetText()[m_nNoBreakEndContentPos];
                if( ' '==cLast || '\x0a'==cLast )
                {
                    // There's just a blank or a newline between the <NOBR> and the cell end
                    m_xCnts->SetNoBreak();
                }
            }
        }
    }
}
 
std::unique_ptr<HTMLTableCnts> SwHTMLParser::InsertTableContents(
                                        bool bHead )
{
    // create a new section, the PaM is gonna be there
    const SwStartNode *pStNd =
        InsertTableSection( static_cast< sal_uInt16 >(bHead ? RES_POOLCOLL_TABLE_HDLN
                                           : RES_POOLCOLL_TABLE) );
 
    if( GetNumInfo().GetNumRule() )
    {
        // Set the first paragraph to non-enumerated
        sal_uInt8 nLvl = GetNumInfo().GetLevel();
 
        SetNodeNum( nLvl );
    }
 
    // Reset attributation start
    const SwNode& rSttPara = m_pPam->GetPoint()->GetNode();
    sal_Int32 nSttCnt = m_pPam->GetPoint()->GetContentIndex();
 
    HTMLAttr** pHTMLAttributes = reinterpret_cast<HTMLAttr**>(m_xAttrTab.get());
    for (sal_uInt16 nCnt = sizeof(HTMLAttrTable) / sizeof(HTMLAttr*); nCnt--; ++pHTMLAttributes)
    {
        HTMLAttr *pAttr = *pHTMLAttributes;
        while( pAttr )
        {
            OSL_ENSURE( !pAttr->GetPrev(), "Attribute has previous list" );
            pAttr->m_nStartPara = rSttPara;
            pAttr->m_nEndPara = rSttPara;
            pAttr->m_nStartContent = nSttCnt;
            pAttr->m_nEndContent = nSttCnt;
 
            pAttr = pAttr->GetNext();
        }
    }
 
    return std::make_unique<HTMLTableCnts>( pStNd );
}
 
sal_uInt16 SwHTMLParser::IncGrfsThatResizeTable()
{
    return m_xTable ? m_xTable->IncGrfsThatResize() : 0;
}
 
void SwHTMLParser::RegisterDrawObjectToTable( HTMLTable *pCurTable,
                                        SdrObject *pObj, sal_uInt8 nPercentWidth )
{
    pCurTable->RegisterDrawObject( pObj, nPercentWidth );
}
 
void SwHTMLParser::BuildTableCell( HTMLTable *pCurTable, bool bReadOptions,
                                   bool bHead )
{
    if( !IsParserWorking() && m_vPendingStack.empty() )
        return;
 
    ::comphelper::FlagRestorationGuard g(m_isInTableStructure, false);
    std::unique_ptr<CellSaveStruct> xSaveStruct;
 
    HtmlTokenId nToken = HtmlTokenId::NONE;
    bool bPending = false;
    if( !m_vPendingStack.empty() )
    {
        xSaveStruct.reset(static_cast<CellSaveStruct*>(m_vPendingStack.back().pData.release()));
 
        m_vPendingStack.pop_back();
        nToken = !m_vPendingStack.empty() ? m_vPendingStack.back().nToken : GetSaveToken();
        bPending = SvParserState::Error == eState && !m_vPendingStack.empty();
 
        SaveState( nToken );
    }
    else
    {
        // <TH> resp. <TD> were already read
        if (m_xTable->IsOverflowing())
        {
            SaveState( HtmlTokenId::NONE );
            return;
        }
 
        if( !pCurTable->GetContext() )
        {
            bool bTopTable = m_xTable.get() == pCurTable;
 
            // the table has no content yet, this means the actual table needs
            // to be created first
 
            SfxItemSetFixed<
                RES_PARATR_SPLIT,   RES_PARATR_SPLIT,
                RES_PAGEDESC,       RES_PAGEDESC,
                RES_BREAK,          RES_BREAK,
                RES_BACKGROUND,     RES_BACKGROUND,
                RES_KEEP,           RES_KEEP,
                RES_LAYOUT_SPLIT,   RES_LAYOUT_SPLIT,
                RES_FRAMEDIR,       RES_FRAMEDIR
            > aItemSet( m_xDoc->GetAttrPool() );
            SvxCSS1PropertyInfo aPropInfo;
 
            bool bStyleParsed = ParseStyleOptions( pCurTable->GetStyle(),
                                                   pCurTable->GetId(),
                                                   pCurTable->GetClass(),
                                                   aItemSet, aPropInfo,
                                                      nullptr, &pCurTable->GetDirection() );
            if( bStyleParsed )
            {
                if( const SvxBrushItem* pItem = aItemSet.GetItemIfSet(
                                        RES_BACKGROUND, false ) )
                {
                    pCurTable->SetBGBrush( *pItem );
                    aItemSet.ClearItem( RES_BACKGROUND );
                }
                if( const SvxFormatSplitItem* pSplitItem = aItemSet.GetItemIfSet(
                                        RES_PARATR_SPLIT, false ) )
                {
                    aItemSet.Put(
                        SwFormatLayoutSplit( pSplitItem->GetValue() ) );
                    aItemSet.ClearItem( RES_PARATR_SPLIT );
                }
            }
 
            sal_uInt16 nLeftSpace = 0;
            sal_uInt16 nRightSpace = 0;
            short nIndent;
            GetMarginsFromContextWithNumberBullet( nLeftSpace, nRightSpace, nIndent );
 
            // save the current position we'll get back to some time
            SwPosition *pSavePos = nullptr;
            bool bForceFrame = false;
            bool bAppended = false;
            bool bParentLFStripped = false;
            if( bTopTable )
            {
                SvxAdjust eTableAdjust = m_xTable->GetTableAdjust(false);
 
                // If the table is left or right adjusted or should be in a text frame,
                // it'll get one
                bForceFrame = eTableAdjust == SvxAdjust::Left ||
                              eTableAdjust == SvxAdjust::Right ||
                              pCurTable->HasToFly();
 
                // The table either shouldn't get in a text frame and isn't in one
                // (it gets simulated through cells),
                // or there's already content at that position
                OSL_ENSURE( !bForceFrame || pCurTable->HasParentSection(),
                        "table in frame has no parent!" );
 
                bool bAppend = false;
                if( bForceFrame )
                {
                    // If the table gets in a border, we only need to open a new
                    //paragraph if the paragraph has text frames that don't fly
                    bAppend = HasCurrentParaFlys(true);
                }
                else
                {
                    // Otherwise, we need to open a new paragraph if the paragraph
                    // is empty or contains text frames or bookmarks
                    bAppend =
                        m_pPam->GetPoint()->GetContentIndex() ||
                        HasCurrentParaFlys() ||
                        HasCurrentParaBookmarks();
                }
                if( bAppend )
                {
                    if( !m_pPam->GetPoint()->GetContentIndex() )
                    {
                        //Set default to CJK and CTL
                        m_xDoc->SetTextFormatColl( *m_pPam,
                            m_pCSS1Parser->GetTextCollFromPool(RES_POOLCOLL_STANDARD) );
                        SvxFontHeightItem aFontHeight( 40, 100, RES_CHRATR_FONTSIZE );
 
                        HTMLAttr* pTmp =
                            new HTMLAttr( *m_pPam->GetPoint(), aFontHeight, nullptr, std::shared_ptr<HTMLAttrTable>() );
                        m_aSetAttrTab.push_back( pTmp );
 
                        SvxFontHeightItem aFontHeightCJK( 40, 100, RES_CHRATR_CJK_FONTSIZE );
                        pTmp =
                            new HTMLAttr( *m_pPam->GetPoint(), aFontHeightCJK, nullptr, std::shared_ptr<HTMLAttrTable>() );
                        m_aSetAttrTab.push_back( pTmp );
 
                        SvxFontHeightItem aFontHeightCTL( 40, 100, RES_CHRATR_CTL_FONTSIZE );
                        pTmp =
                            new HTMLAttr( *m_pPam->GetPoint(), aFontHeightCTL, nullptr, std::shared_ptr<HTMLAttrTable>() );
                        m_aSetAttrTab.push_back( pTmp );
 
                        pTmp = new HTMLAttr( *m_pPam->GetPoint(),
                                            SvxULSpaceItem( 0, 0, RES_UL_SPACE ), nullptr, std::shared_ptr<HTMLAttrTable>() );
                        m_aSetAttrTab.push_front( pTmp ); // Position 0, since
                                                          // something can be set by
                                                          // the table end before
                    }
                    AppendTextNode( AM_NOSPACE );
                    bAppended = true;
                }
                else if( !m_aParaAttrs.empty() )
                {
                    if( !bForceFrame )
                    {
                        // The paragraph will be moved right behind the table.
                        // That's why we remove all hard attributes of that paragraph
 
                        for(HTMLAttr* i : m_aParaAttrs)
                            i->Invalidate();
                    }
 
                    m_aParaAttrs.clear();
                }
 
                pSavePos = new SwPosition( *m_pPam->GetPoint() );
            }
            else if( pCurTable->HasParentSection() )
            {
                bParentLFStripped = StripTrailingLF() > 0;
 
                // Close paragraph resp. headers
                m_nOpenParaToken = HtmlTokenId::NONE;
                m_nFontStHeadStart = m_nFontStMin;
 
                // The hard attributes on that paragraph are never gonna be invalid anymore
                m_aParaAttrs.clear();
            }
 
            // create a table context
            std::unique_ptr<HTMLTableContext> pTCntxt(
                        new HTMLTableContext( pSavePos, m_nContextStMin,
                                               m_nContextStAttrMin ) );
 
            // end all open attributes and open them again behind the table
            std::optional<std::deque<std::unique_ptr<HTMLAttr>>> pPostIts;
            if( !bForceFrame && (bTopTable || pCurTable->HasParentSection()) )
            {
                SplitAttrTab(pTCntxt->m_xAttrTab, bTopTable);
                // If we reuse an already existing paragraph, we can't add
                // PostIts since the paragraph gets behind that table.
                // They're gonna be moved into the first paragraph of the table
                // If we have tables in tables, we also can't add PostIts to a
                // still empty paragraph, since it's not gonna be deleted that way
                if( (bTopTable && !bAppended) ||
                    (!bTopTable && !bParentLFStripped &&
                     !m_pPam->GetPoint()->GetContentIndex()) )
                    pPostIts.emplace();
                SetAttr( bTopTable, bTopTable, pPostIts ? &*pPostIts : nullptr );
            }
            else
            {
                SaveAttrTab(pTCntxt->m_xAttrTab);
                if( bTopTable && !bAppended )
                {
                    pPostIts.emplace();
                    SetAttr( true, true, &*pPostIts );
                }
            }
            m_bNoParSpace = false;
 
            // Save current numbering and turn it off
            pTCntxt->SetNumInfo( GetNumInfo() );
            GetNumInfo().Clear();
            pTCntxt->SavePREListingXMP( *this );
 
            if( bTopTable )
            {
                if( bForceFrame )
                {
                    // the table should be put in a text frame
 
                    SfxItemSetFixed<RES_FRMATR_BEGIN, RES_FRMATR_END-1>
                        aFrameSet( m_xDoc->GetAttrPool() );
                    if( !pCurTable->IsNewDoc() )
                        Reader::ResetFrameFormatAttrs( aFrameSet );
 
                    css::text::WrapTextMode eSurround = css::text::WrapTextMode_NONE;
                    sal_Int16 eHori;
 
                    switch( pCurTable->GetTableAdjust(true) )
                    {
                    case SvxAdjust::Right:
                        eHori = text::HoriOrientation::RIGHT;
                        eSurround = css::text::WrapTextMode_LEFT;
                        break;
                    case SvxAdjust::Center:
                        eHori = text::HoriOrientation::CENTER;
                        break;
                    case SvxAdjust::Left:
                        eSurround = css::text::WrapTextMode_RIGHT;
                        [[fallthrough]];
                    default:
                        eHori = text::HoriOrientation::LEFT;
                        break;
                    }
                    SetAnchorAndAdjustment( text::VertOrientation::NONE, eHori, aFrameSet,
                                            true );
                    aFrameSet.Put( SwFormatSurround(eSurround) );
 
                    constexpr tools::Long constTwips_100mm = o3tl::convert(tools::Long(100), o3tl::Length::mm, o3tl::Length::twip);
 
                    SwFormatFrameSize aFrameSize( SwFrameSize::Variable, constTwips_100mm, MINLAY );
                    aFrameSize.SetWidthPercent( 100 );
                    aFrameSet.Put( aFrameSize );
 
                    sal_uInt16 nSpace = pCurTable->GetHSpace();
                    if( nSpace )
                        aFrameSet.Put( SvxLRSpaceItem(nSpace, nSpace, 0, RES_LR_SPACE) );
                    nSpace = pCurTable->GetVSpace();
                    if( nSpace )
                        aFrameSet.Put( SvxULSpaceItem(nSpace,nSpace, RES_UL_SPACE) );
 
                    RndStdIds eAnchorId = aFrameSet.
                                                Get( RES_ANCHOR ).
                                                GetAnchorId();
                    SwFrameFormat *pFrameFormat =  m_xDoc->MakeFlySection(
                                eAnchorId, m_pPam->GetPoint(), &aFrameSet );
 
                    pTCntxt->SetFrameFormat( pFrameFormat );
                    const SwFormatContent& rFlyContent = pFrameFormat->GetContent();
                    m_pPam->GetPoint()->Assign( *rFlyContent.GetContentIdx() );
                    SwNodes::GoNext(m_pPam->GetPoint());
                }
 
                // create a SwTable with a box and set the PaM to the content of
                // the box section (the adjustment parameter is a dummy for now
                // and will be corrected later)
                OSL_ENSURE( !m_pPam->GetPoint()->GetContentIndex(),
                        "The paragraph after the table is not empty!" );
                const SwTable* pSwTable = m_xDoc->InsertTable(
                        SwInsertTableOptions( SwInsertTableFlags::HeadlineNoBorder, 1 ),
                        *m_pPam->GetPoint(), 1, 1, text::HoriOrientation::LEFT );
                SwFrameFormat *pFrameFormat = pSwTable ? pSwTable->GetFrameFormat() : nullptr;
 
                if( bForceFrame )
                {
                    SwNodeIndex aDstIdx( m_pPam->GetPoint()->GetNode() );
                    m_pPam->Move( fnMoveBackward );
                    m_xDoc->GetNodes().Delete( aDstIdx );
                }
                else
                {
                    if (bStyleParsed && pFrameFormat)
                    {
                        m_pCSS1Parser->SetFormatBreak( aItemSet, aPropInfo );
                        pFrameFormat->SetFormatAttr( aItemSet );
                    }
                    m_pPam->Move( fnMoveBackward );
                }
 
                SwNode const*const pNd = & m_pPam->GetPoint()->GetNode();
                SwTextNode *const pOldTextNd = (!bAppended && !bForceFrame) ?
                    pSavePos->GetNode().GetTextNode() : nullptr;
 
                if (pFrameFormat && pOldTextNd)
                {
                    const SwFormatPageDesc* pPageDescItem = pOldTextNd->GetSwAttrSet()
                            .GetItemIfSet( RES_PAGEDESC, false );
                    if( pPageDescItem && pPageDescItem->GetPageDesc() )
                    {
                        pFrameFormat->SetFormatAttr( *pPageDescItem );
                        pOldTextNd->ResetAttr( RES_PAGEDESC );
                    }
 
                    if( const SvxFormatBreakItem* pBreakItem = pOldTextNd->GetSwAttrSet()
                            .GetItemIfSet( RES_BREAK ) )
                    {
                        switch( pBreakItem->GetBreak() )
                        {
                        case SvxBreak::PageBefore:
                        case SvxBreak::PageAfter:
                        case SvxBreak::PageBoth:
                            pFrameFormat->SetFormatAttr( *pBreakItem );
                            pOldTextNd->ResetAttr( RES_BREAK );
                            break;
                        default:
                            break;
                        }
                    }
                }
 
                if( !bAppended && pPostIts )
                {
                    // set still-existing PostIts to the first paragraph of the table
                    InsertAttrs( std::move(*pPostIts) );
                    pPostIts.reset();
                }
 
                pTCntxt->SetTableNode( const_cast<SwTableNode *>(pNd->FindTableNode()) );
 
                auto pTableNode = pTCntxt->GetTableNode();
                pCurTable->SetTable( pTableNode, std::move(pTCntxt),
                                     nLeftSpace, nRightSpace,
                                     pSwTable, bForceFrame );
 
                OSL_ENSURE( !pPostIts, "unused PostIts" );
            }
            else
            {
                // still open sections need to be deleted
                if( EndSections( bParentLFStripped ) )
                    bParentLFStripped = false;
 
                if( pCurTable->HasParentSection() )
                {
                    // after that, we remove a possibly redundant empty paragraph,
                    // but only if it was empty before we stripped the LFs
                    if( !bParentLFStripped )
                        StripTrailingPara();
 
                    if( pPostIts )
                    {
                        // move still existing PostIts to the end of the current paragraph
                        InsertAttrs( std::move(*pPostIts) );
                        pPostIts.reset();
                    }
                }
 
                SwNode const*const pNd = & m_pPam->GetPoint()->GetNode();
                const SwStartNode *pStNd = (m_xTable->m_bFirstCell ? pNd->FindTableNode()
                                                            : pNd->FindTableBoxStartNode() );
 
                pCurTable->SetTable( pStNd, std::move(pTCntxt), nLeftSpace, nRightSpace );
            }
 
            // Freeze the context stack, since there could be attributes set
            // outside of cells. Can't happen earlier, since there may be
            // searches in the stack
            m_nContextStMin = m_aContexts.size();
            m_nContextStAttrMin = m_nContextStMin;
        }
 
        xSaveStruct.reset(new CellSaveStruct(*this, pCurTable, bHead, bReadOptions));
 
        // If the first GetNextToken() doesn't succeed (pending input), must re-read from the beginning.
        SaveState( HtmlTokenId::NONE );
    }
 
    if( nToken == HtmlTokenId::NONE )
        nToken = GetNextToken();    // Token after <TABLE>
 
    bool bDone = false;
    while( (IsParserWorking() && !bDone) || bPending )
    {
        SaveState( nToken );
 
        nToken = FilterToken( nToken );
 
        OSL_ENSURE( !m_vPendingStack.empty() || !m_bCallNextToken || xSaveStruct->IsInSection(),
                "Where is the section??" );
        if( m_vPendingStack.empty() && m_bCallNextToken && xSaveStruct->IsInSection() )
        {
            // Call NextToken directly (e.g. ignore the content of floating frames or applets)
            NextToken( nToken );
        }
        else switch( nToken )
        {
        case HtmlTokenId::TABLEHEADER_ON:
        case HtmlTokenId::TABLEDATA_ON:
        case HtmlTokenId::TABLEROW_ON:
        case HtmlTokenId::TABLEROW_OFF:
        case HtmlTokenId::THEAD_ON:
        case HtmlTokenId::THEAD_OFF:
        case HtmlTokenId::TFOOT_ON:
        case HtmlTokenId::TFOOT_OFF:
        case HtmlTokenId::TBODY_ON:
        case HtmlTokenId::TBODY_OFF:
        case HtmlTokenId::TABLE_OFF:
            SkipToken();
            [[fallthrough]];
        case HtmlTokenId::TABLEHEADER_OFF:
        case HtmlTokenId::TABLEDATA_OFF:
            bDone = true;
            break;
        case HtmlTokenId::TABLE_ON:
            {
                bool bHasToFly = false;
                SvxAdjust eTabAdjust = SvxAdjust::End;
                if( m_vPendingStack.empty() )
                {
                    // only if we create a new table, but not if we're still
                    // reading in the table after a Pending
                    xSaveStruct->m_xTable = m_xTable;
 
                    // HACK: create a section for a table that goes in a text frame
                    if( !xSaveStruct->IsInSection() )
                    {
                        // The loop needs to be forward, since the
                        // first option always wins
                        bool bNeedsSection = false;
                        const HTMLOptions& rHTMLOptions = GetOptions();
                        for (const auto & rOption : rHTMLOptions)
                        {
                            if( HtmlOptionId::ALIGN==rOption.GetToken() )
                            {
                                SvxAdjust eAdjust = rOption.GetEnum( aHTMLPAlignTable, SvxAdjust::End );
                                bNeedsSection = SvxAdjust::Left == eAdjust ||
                                                SvxAdjust::Right == eAdjust;
                                break;
                            }
                        }
                        if( bNeedsSection )
                        {
                            xSaveStruct->AddContents(
                                InsertTableContents(bHead  ) );
                        }
                    }
                    else
                    {
                        // If Flys are anchored in the current paragraph,
                        // the table needs to get in a text frame
                        bHasToFly = HasCurrentParaFlys(false,true);
                    }
 
                    // There could be a section in the cell
                    eTabAdjust = m_xAttrTab->pAdjust
                        ? static_cast<const SvxAdjustItem&>(m_xAttrTab->pAdjust->GetItem()).
                                                 GetAdjust()
                        : SvxAdjust::End;
                }
 
                std::shared_ptr<HTMLTable> xSubTable = BuildTable(eTabAdjust,
                                                                  bHead,
                                                                  xSaveStruct->IsInSection(),
                                                                  bHasToFly);
                if( SvParserState::Pending != GetStatus() )
                {
                    // Only if the table is really complete
                    if (xSubTable)
                    {
                        OSL_ENSURE( xSubTable->GetTableAdjust(false)!= SvxAdjust::Left &&
                                xSubTable->GetTableAdjust(false)!= SvxAdjust::Right,
                                "left or right aligned tables belong in frames" );
 
                        auto& rParentContents = xSubTable->GetParentContents();
                        if (rParentContents)
                        {
                            OSL_ENSURE( !xSaveStruct->IsInSection(),
                                    "Where is the section" );
 
                            // If there's no table coming, we have a section
                            xSaveStruct->AddContents(std::move(rParentContents));
                        }
 
                        const SwStartNode *pCapStNd =
                                xSubTable->GetCaptionStartNode();
 
                        if (xSubTable->GetContext())
                        {
                            OSL_ENSURE( !xSubTable->GetContext()->GetFrameFormat(),
                                    "table in frame" );
 
                            if( pCapStNd && xSubTable->IsTopCaption() )
                            {
                                xSaveStruct->AddContents(
                                    std::make_unique<HTMLTableCnts>(pCapStNd) );
                            }
 
                            xSaveStruct->AddContents(
                                std::make_unique<HTMLTableCnts>(xSubTable) );
 
                            if( pCapStNd && !xSubTable->IsTopCaption() )
                            {
                                xSaveStruct->AddContents(
                                    std::make_unique<HTMLTableCnts>(pCapStNd) );
                            }
 
                            // We don't have a section anymore
                            xSaveStruct->ClearIsInSection();
                        }
                        else if( pCapStNd )
                        {
                            // Since we can't delete this section (it might
                            // belong to the first box), we'll add it
                            xSaveStruct->AddContents(
                                std::make_unique<HTMLTableCnts>(pCapStNd) );
 
                            // We don't have a section anymore
                            xSaveStruct->ClearIsInSection();
                        }
                    }
 
                    m_xTable = xSaveStruct->m_xTable;
                }
            }
            break;
 
        case HtmlTokenId::NOBR_ON:
            // HACK for MS: Is the <NOBR> at the start of the cell?
            xSaveStruct->StartNoBreak( *m_pPam->GetPoint() );
            break;
 
        case HtmlTokenId::NOBR_OFF:
                xSaveStruct->EndNoBreak( *m_pPam->GetPoint() );
            break;
 
        case HtmlTokenId::COMMENT:
            // Spaces are not gonna be deleted with comment fields,
            // and we don't want a new cell for a comment
            NextToken( nToken );
            break;
 
        case HtmlTokenId::MARQUEE_ON:
            if( !xSaveStruct->IsInSection() )
            {
                // create a new section, the PaM is gonna be there
                xSaveStruct->AddContents(
                    InsertTableContents( bHead ) );
            }
            m_bCallNextToken = true;
            NewMarquee( pCurTable );
            break;
 
        case HtmlTokenId::TEXTTOKEN:
            // Don't add a section for an empty string
            if( !xSaveStruct->IsInSection() && 1==aToken.getLength() &&
                ' '==aToken[0] )
                break;
            [[fallthrough]];
        default:
            if( !xSaveStruct->IsInSection() )
            {
                // add a new section, the PaM's gonna be there
                xSaveStruct->AddContents(
                    InsertTableContents( bHead ) );
            }
 
            if( IsParserWorking() || bPending )
                NextToken( nToken );
            break;
        }
 
        OSL_ENSURE( !bPending || m_vPendingStack.empty(),
                "SwHTMLParser::BuildTableCell: There is a PendStack again" );
        bPending = false;
        if( IsParserWorking() )
            SaveState( HtmlTokenId::NONE );
 
        if( !bDone )
            nToken = GetNextToken();
    }
 
    if( SvParserState::Pending == GetStatus() )
    {
        m_vPendingStack.emplace_back( bHead ? HtmlTokenId::TABLEHEADER_ON
                                            : HtmlTokenId::TABLEDATA_ON );
        m_vPendingStack.back().pData = std::move(xSaveStruct);
 
        return;
    }
 
    // If the content of the cell was empty, we need to create an empty content
    // We also create an empty content if the cell ended with a table and had no
    // COL tags. Otherwise, it was probably exported by us and we don't
    // want to have an additional paragraph
    if( !xSaveStruct->HasFirstContents() ||
        (!xSaveStruct->IsInSection() && !pCurTable->HasColTags()) )
    {
        OSL_ENSURE( xSaveStruct->HasFirstContents() ||
                !xSaveStruct->IsInSection(),
                "Section or not, that is the question here" );
        const SwStartNode *pStNd =
            InsertTableSection( static_cast< sal_uInt16 >(xSaveStruct->IsHeaderCell()
                                        ? RES_POOLCOLL_TABLE_HDLN
                                        : RES_POOLCOLL_TABLE ));
 
        if (!pStNd)
            eState = SvParserState::Error;
        else
        {
            const SwEndNode *pEndNd = pStNd->EndOfSectionNode();
            SwContentNode *pCNd = m_xDoc->GetNodes()[pEndNd->GetIndex()-1] ->GetContentNode();
            if (!pCNd)
                eState = SvParserState::Error;
            else
            {
                //Added defaults to CJK and CTL
                SvxFontHeightItem aFontHeight( 40, 100, RES_CHRATR_FONTSIZE );
                pCNd->SetAttr( aFontHeight );
                SvxFontHeightItem aFontHeightCJK( 40, 100, RES_CHRATR_CJK_FONTSIZE );
                pCNd->SetAttr( aFontHeightCJK );
                SvxFontHeightItem aFontHeightCTL( 40, 100, RES_CHRATR_CTL_FONTSIZE );
                pCNd->SetAttr( aFontHeightCTL );
            }
        }
 
        xSaveStruct->AddContents( std::make_unique<HTMLTableCnts>(pStNd) );
        xSaveStruct->ClearIsInSection();
    }
 
    if( xSaveStruct->IsInSection() )
    {
        xSaveStruct->CheckNoBreak( *m_pPam->GetPoint() );
 
        // End all open contexts. We'll take AttrMin because nContextStMin might
        // have been modified. Since it's gonna be restored by EndContext, it's okay
        while( m_aContexts.size() > m_nContextStAttrMin+1 )
        {
            std::unique_ptr<HTMLAttrContext> xCntxt(PopContext());
            EndContext(xCntxt.get());
        }
 
        // Remove LFs at the paragraph end
        if (StripTrailingLF() == 0 && !m_pPam->GetPoint()->GetContentIndex())
        {
            HTMLTableContext* pTableContext = m_xTable ? m_xTable->GetContext() : nullptr;
            SwPosition* pSavedPos = pTableContext ? pTableContext->GetPos() : nullptr;
            const bool bDeleteSafe = !pSavedPos || pSavedPos->GetNode() != m_pPam->GetPoint()->GetNode();
            if (bDeleteSafe)
                StripTrailingPara();
        }
 
        // If there was an adjustment set for the cell, we need to close it
        std::unique_ptr<HTMLAttrContext> xCntxt(PopContext());
        if (xCntxt)
            EndContext(xCntxt.get());
    }
    else
    {
        // Close all still open contexts
        while( m_aContexts.size() > m_nContextStAttrMin )
        {
            std::unique_ptr<HTMLAttrContext> xCntxt(PopContext());
            if (!xCntxt)
                break;
            ClearContext(xCntxt.get());
        }
    }
 
    // end an enumeration
    GetNumInfo().Clear();
 
    SetAttr( false );
 
    xSaveStruct->InsertCell( *this, pCurTable );
 
    // we're probably before a <TH>, <TD>, <TR> or </TABLE>
    xSaveStruct.reset();
}
 
namespace {
 
class RowSaveStruct : public SwPendingData
{
public:
    SvxAdjust eAdjust;
    sal_Int16 eVertOri;
    bool bHasCells;
 
    RowSaveStruct() :
        eAdjust( SvxAdjust::End ), eVertOri( text::VertOrientation::TOP ), bHasCells( false )
    {}
};
 
}
 
void SwHTMLParser::BuildTableRow( HTMLTable *pCurTable, bool bReadOptions,
                                  SvxAdjust eGrpAdjust,
                                  sal_Int16 eGrpVertOri )
{
    // <TR> was already read
 
    if( !IsParserWorking() && m_vPendingStack.empty() )
        return;
 
    HtmlTokenId nToken = HtmlTokenId::NONE;
    std::unique_ptr<RowSaveStruct> xSaveStruct;
 
    bool bPending = false;
    if( !m_vPendingStack.empty() )
    {
        xSaveStruct.reset(static_cast<RowSaveStruct*>(m_vPendingStack.back().pData.release()));
 
        m_vPendingStack.pop_back();
        nToken = !m_vPendingStack.empty() ? m_vPendingStack.back().nToken : GetSaveToken();
        bPending = SvParserState::Error == eState && !m_vPendingStack.empty();
 
        SaveState( nToken );
    }
    else
    {
        SvxAdjust eAdjust = eGrpAdjust;
        sal_Int16 eVertOri = eGrpVertOri;
        Color aBGColor;
        OUString aBGImage, aStyle, aId, aClass;
        bool bBGColor = false;
        xSaveStruct.reset(new RowSaveStruct);
 
        if( bReadOptions )
        {
            const HTMLOptions& rHTMLOptions = GetOptions();
            for (size_t i = rHTMLOptions.size(); i; )
            {
                const HTMLOption& rOption = rHTMLOptions[--i];
                switch( rOption.GetToken() )
                {
                case HtmlOptionId::ID:
                    aId = rOption.GetString();
                    break;
                case HtmlOptionId::ALIGN:
                    eAdjust = rOption.GetEnum( aHTMLPAlignTable, eAdjust );
                    break;
                case HtmlOptionId::VALIGN:
                    eVertOri = rOption.GetEnum( aHTMLTableVAlignTable, eVertOri );
                    break;
                case HtmlOptionId::BGCOLOR:
                    // Ignore empty BGCOLOR on <TABLE>, <TR> and <TD>/>TH> like Netscape
                    // *really* not on other tags
                    if( !rOption.GetString().isEmpty() )
                    {
                        rOption.GetColor( aBGColor );
                        bBGColor = true;
                    }
                    break;
                case HtmlOptionId::BACKGROUND:
                    aBGImage = rOption.GetString();
                    break;
                case HtmlOptionId::STYLE:
                    aStyle = rOption.GetString();
                    break;
                case HtmlOptionId::CLASS:
                    aClass= rOption.GetString();
                    break;
                default: break;
                }
            }
        }
 
        if( !aId.isEmpty() )
            InsertBookmark( aId );
 
        std::unique_ptr<SvxBrushItem> xBrushItem(
            CreateBrushItem( bBGColor ? &aBGColor : nullptr, aBGImage, aStyle,
                             aId, aClass ));
        pCurTable->OpenRow(eAdjust, eVertOri, xBrushItem);
        // If the first GetNextToken() doesn't succeed (pending input), must re-read from the beginning.
        SaveState( HtmlTokenId::NONE );
    }
 
    if( nToken == HtmlTokenId::NONE )
        nToken = GetNextToken();
 
    bool bDone = false;
    while( (IsParserWorking() && !bDone) || bPending )
    {
        SaveState( nToken );
 
        nToken = FilterToken( nToken );
 
        OSL_ENSURE( !m_vPendingStack.empty() || !m_bCallNextToken ||
                pCurTable->GetContext() || pCurTable->HasParentSection(),
                "Where is the section??" );
        if( m_vPendingStack.empty() && m_bCallNextToken &&
            (pCurTable->GetContext() || pCurTable->HasParentSection()) )
        {
            /// Call NextToken directly (e.g. ignore the content of floating frames or applets)
            NextToken( nToken );
        }
        else switch( nToken )
        {
        case HtmlTokenId::TABLE_ON:
            if( !pCurTable->GetContext()  )
            {
                SkipToken();
                bDone = true;
            }
 
            break;
        case HtmlTokenId::TABLEROW_ON:
        case HtmlTokenId::THEAD_ON:
        case HtmlTokenId::THEAD_OFF:
        case HtmlTokenId::TBODY_ON:
        case HtmlTokenId::TBODY_OFF:
        case HtmlTokenId::TFOOT_ON:
        case HtmlTokenId::TFOOT_OFF:
        case HtmlTokenId::TABLE_OFF:
            SkipToken();
            [[fallthrough]];
        case HtmlTokenId::TABLEROW_OFF:
            bDone = true;
            break;
        case HtmlTokenId::TABLEHEADER_ON:
        case HtmlTokenId::TABLEDATA_ON:
            BuildTableCell( pCurTable, true, HtmlTokenId::TABLEHEADER_ON==nToken );
            if( SvParserState::Pending != GetStatus() )
            {
                xSaveStruct->bHasCells = true;
                bDone = m_xTable->IsOverflowing();
            }
            break;
        case HtmlTokenId::CAPTION_ON:
            BuildTableCaption( pCurTable );
            bDone = m_xTable->IsOverflowing();
            break;
        case HtmlTokenId::CAPTION_OFF:
        case HtmlTokenId::TABLEHEADER_OFF:
        case HtmlTokenId::TABLEDATA_OFF:
        case HtmlTokenId::COLGROUP_ON:
        case HtmlTokenId::COLGROUP_OFF:
        case HtmlTokenId::COL_ON:
        case HtmlTokenId::COL_OFF:
            // Where no cell started, there can't be a cell ending
            // all the other tokens are bogus anyway and only break the table
            break;
        case HtmlTokenId::MULTICOL_ON:
            // we can't add columned text frames here
            break;
        case HtmlTokenId::FORM_ON:
            NewForm( false );   // don't create a new paragraph
            break;
        case HtmlTokenId::FORM_OFF:
            EndForm( false );   // don't create a new paragraph
            break;
        case HtmlTokenId::COMMENT:
            NextToken( nToken );
            break;
        case HtmlTokenId::MAP_ON:
            // an image map doesn't add anything, so we can parse it without a cell
            NextToken( nToken );
            break;
        case HtmlTokenId::TEXTTOKEN:
            if( (pCurTable->GetContext() ||
                 !pCurTable->HasParentSection()) &&
                1==aToken.getLength() && ' '==aToken[0] )
                break;
            [[fallthrough]];
        default:
            pCurTable->MakeParentContents();
            NextToken( nToken );
            break;
        }
 
        OSL_ENSURE( !bPending || m_vPendingStack.empty(),
                "SwHTMLParser::BuildTableRow: There is a PendStack again" );
        bPending = false;
        if( IsParserWorking() )
            SaveState( HtmlTokenId::NONE );
 
        if( !bDone )
            nToken = GetNextToken();
    }
 
    if( SvParserState::Pending == GetStatus() )
    {
        m_vPendingStack.emplace_back( HtmlTokenId::TABLEROW_ON );
        m_vPendingStack.back().pData = std::move(xSaveStruct);
    }
    else
    {
        pCurTable->CloseRow(!xSaveStruct->bHasCells);
        xSaveStruct.reset();
    }
 
    // we're probably before <TR> or </TABLE>
}
 
void SwHTMLParser::BuildTableSection( HTMLTable *pCurTable,
                                      bool bReadOptions,
                                      bool bHead )
{
    // <THEAD>, <TBODY> resp. <TFOOT> were read already
    if( !IsParserWorking() && m_vPendingStack.empty() )
        return;
 
    HtmlTokenId nToken = HtmlTokenId::NONE;
    bool bPending = false;
    std::unique_ptr<RowSaveStruct> xSaveStruct;
 
    if( !m_vPendingStack.empty() )
    {
        xSaveStruct.reset(static_cast<RowSaveStruct*>(m_vPendingStack.back().pData.release()));
 
        m_vPendingStack.pop_back();
        nToken = !m_vPendingStack.empty() ? m_vPendingStack.back().nToken : GetSaveToken();
        bPending = SvParserState::Error == eState && !m_vPendingStack.empty();
 
        SaveState( nToken );
    }
    else
    {
        xSaveStruct.reset(new RowSaveStruct);
 
        if( bReadOptions )
        {
            const HTMLOptions& rHTMLOptions = GetOptions();
            for (size_t i = rHTMLOptions.size(); i; )
            {
                const HTMLOption& rOption = rHTMLOptions[--i];
                switch( rOption.GetToken() )
                {
                case HtmlOptionId::ID:
                    InsertBookmark( rOption.GetString() );
                    break;
                case HtmlOptionId::ALIGN:
                    xSaveStruct->eAdjust =
                        rOption.GetEnum( aHTMLPAlignTable, xSaveStruct->eAdjust );
                    break;
                case HtmlOptionId::VALIGN:
                    xSaveStruct->eVertOri =
                        rOption.GetEnum( aHTMLTableVAlignTable,
                                          xSaveStruct->eVertOri );
                    break;
                default: break;
                }
            }
        }
 
        // If the first GetNextToken() doesn't succeed (pending input), must re-read from the beginning.
        SaveState( HtmlTokenId::NONE );
    }
 
    if( nToken == HtmlTokenId::NONE )
        nToken = GetNextToken();
 
    bool bDone = false;
    while( (IsParserWorking() && !bDone) || bPending )
    {
        SaveState( nToken );
 
        nToken = FilterToken( nToken );
 
        OSL_ENSURE( !m_vPendingStack.empty() || !m_bCallNextToken ||
                pCurTable->GetContext() || pCurTable->HasParentSection(),
                "Where is the section?" );
        if( m_vPendingStack.empty() && m_bCallNextToken &&
            (pCurTable->GetContext() || pCurTable->HasParentSection()) )
        {
            // Call NextToken directly (e.g. ignore the content of floating frames or applets)
            NextToken( nToken );
        }
        else switch( nToken )
        {
        case HtmlTokenId::TABLE_ON:
            if( !pCurTable->GetContext()  )
            {
                SkipToken();
                bDone = true;
            }
 
            break;
        case HtmlTokenId::THEAD_ON:
        case HtmlTokenId::TFOOT_ON:
        case HtmlTokenId::TBODY_ON:
        case HtmlTokenId::TABLE_OFF:
            SkipToken();
            [[fallthrough]];
        case HtmlTokenId::THEAD_OFF:
        case HtmlTokenId::TBODY_OFF:
        case HtmlTokenId::TFOOT_OFF:
            bDone = true;
            break;
        case HtmlTokenId::CAPTION_ON:
            BuildTableCaption( pCurTable );
            bDone = m_xTable->IsOverflowing();
            break;
        case HtmlTokenId::CAPTION_OFF:
            break;
        case HtmlTokenId::TABLEHEADER_ON:
        case HtmlTokenId::TABLEDATA_ON:
            SkipToken();
            BuildTableRow( pCurTable, false, xSaveStruct->eAdjust,
                           xSaveStruct->eVertOri );
            bDone = m_xTable->IsOverflowing();
            break;
        case HtmlTokenId::TABLEROW_ON:
            BuildTableRow( pCurTable, true, xSaveStruct->eAdjust,
                           xSaveStruct->eVertOri );
            bDone = m_xTable->IsOverflowing();
            break;
        case HtmlTokenId::MULTICOL_ON:
            // we can't add columned text frames here
            break;
        case HtmlTokenId::FORM_ON:
            NewForm( false );   // don't create a new paragraph
            break;
        case HtmlTokenId::FORM_OFF:
            EndForm( false );   // don't create a new paragraph
            break;
        case HtmlTokenId::TEXTTOKEN:
            // blank strings may be a series of CR+LF and no text
            if( (pCurTable->GetContext() ||
                 !pCurTable->HasParentSection()) &&
                1==aToken.getLength() && ' ' == aToken[0] )
                break;
            [[fallthrough]];
        default:
            pCurTable->MakeParentContents();
            NextToken( nToken );
        }
 
        OSL_ENSURE( !bPending || m_vPendingStack.empty(),
                "SwHTMLParser::BuildTableSection: There is a PendStack again" );
        bPending = false;
        if( IsParserWorking() )
            SaveState( HtmlTokenId::NONE );
 
        if( !bDone )
            nToken = GetNextToken();
    }
 
    if( SvParserState::Pending == GetStatus() )
    {
        m_vPendingStack.emplace_back( bHead ? HtmlTokenId::THEAD_ON
                                            : HtmlTokenId::TBODY_ON );
        m_vPendingStack.back().pData = std::move(xSaveStruct);
    }
    else
    {
        pCurTable->CloseSection( bHead );
        xSaveStruct.reset();
    }
 
    // now we stand (perhaps) in front of <TBODY>,... or </TABLE>
}
 
namespace {
 
struct TableColGrpSaveStruct : public SwPendingData
{
    sal_uInt16 nColGrpSpan;
    sal_uInt16 nColGrpWidth;
    bool bRelColGrpWidth;
    SvxAdjust eColGrpAdjust;
    sal_Int16 eColGrpVertOri;
 
    inline TableColGrpSaveStruct();
 
    inline void CloseColGroup( HTMLTable *pTable );
};
 
}
 
inline TableColGrpSaveStruct::TableColGrpSaveStruct() :
    nColGrpSpan( 1 ), nColGrpWidth( 0 ),
    bRelColGrpWidth( false ), eColGrpAdjust( SvxAdjust::End ),
    eColGrpVertOri( text::VertOrientation::TOP )
{}
 
inline void TableColGrpSaveStruct::CloseColGroup( HTMLTable *pTable )
{
    pTable->CloseColGroup( nColGrpSpan, nColGrpWidth,
                            bRelColGrpWidth, eColGrpAdjust, eColGrpVertOri );
}
 
void SwHTMLParser::BuildTableColGroup( HTMLTable *pCurTable,
                                       bool bReadOptions )
{
    // <COLGROUP> was read already if bReadOptions is set
 
    if( !IsParserWorking() && m_vPendingStack.empty() )
        return;
 
    HtmlTokenId nToken = HtmlTokenId::NONE;
    bool bPending = false;
    std::unique_ptr<TableColGrpSaveStruct> pSaveStruct;
 
    if( !m_vPendingStack.empty() )
    {
        pSaveStruct.reset(static_cast<TableColGrpSaveStruct*>(m_vPendingStack.back().pData.release()));
 
 
        m_vPendingStack.pop_back();
        nToken = !m_vPendingStack.empty() ? m_vPendingStack.back().nToken : GetSaveToken();
        bPending = SvParserState::Error == eState && !m_vPendingStack.empty();
 
        SaveState( nToken );
    }
    else
    {
 
        pSaveStruct.reset(new TableColGrpSaveStruct);
        if( bReadOptions )
        {
            const HTMLOptions& rColGrpOptions = GetOptions();
            for (size_t i = rColGrpOptions.size(); i; )
            {
                const HTMLOption& rOption = rColGrpOptions[--i];
                switch( rOption.GetToken() )
                {
                case HtmlOptionId::ID:
                    InsertBookmark( rOption.GetString() );
                    break;
                case HtmlOptionId::SPAN:
                    pSaveStruct->nColGrpSpan = o3tl::narrowing<sal_uInt16>(rOption.GetNumber());
                    if (pSaveStruct->nColGrpSpan > 256)
                    {
                        SAL_INFO("sw.html", "ignoring huge SPAN " << pSaveStruct->nColGrpSpan);
                        pSaveStruct->nColGrpSpan = 1;
                    }
                    break;
                case HtmlOptionId::WIDTH:
                    pSaveStruct->nColGrpWidth = o3tl::narrowing<sal_uInt16>(rOption.GetNumber());
                    pSaveStruct->bRelColGrpWidth =
                        (rOption.GetString().indexOf('*') != -1);
                    break;
                case HtmlOptionId::ALIGN:
                    pSaveStruct->eColGrpAdjust =
                        rOption.GetEnum( aHTMLPAlignTable, pSaveStruct->eColGrpAdjust );
                    break;
                case HtmlOptionId::VALIGN:
                    pSaveStruct->eColGrpVertOri =
                        rOption.GetEnum( aHTMLTableVAlignTable,
                                                pSaveStruct->eColGrpVertOri );
                    break;
                default: break;
                }
            }
        }
        // If the first GetNextToken() doesn't succeed (pending input), must re-read from the beginning.
        SaveState( HtmlTokenId::NONE );
    }
 
    if( nToken == HtmlTokenId::NONE )
        nToken = GetNextToken();    // naechstes Token
 
    bool bDone = false;
    while( (IsParserWorking() && !bDone) || bPending )
    {
        SaveState( nToken );
 
        nToken = FilterToken( nToken );
 
        OSL_ENSURE( !m_vPendingStack.empty() || !m_bCallNextToken ||
                pCurTable->GetContext() || pCurTable->HasParentSection(),
                "Where is the section?" );
        if( m_vPendingStack.empty() && m_bCallNextToken &&
            (pCurTable->GetContext() || pCurTable->HasParentSection()) )
        {
            // Call NextToken directly (e.g. ignore the content of floating frames or applets)
            NextToken( nToken );
        }
        else switch( nToken )
        {
        case HtmlTokenId::TABLE_ON:
            if( !pCurTable->GetContext()  )
            {
                SkipToken();
                bDone = true;
            }
 
            break;
        case HtmlTokenId::COLGROUP_ON:
        case HtmlTokenId::THEAD_ON:
        case HtmlTokenId::TFOOT_ON:
        case HtmlTokenId::TBODY_ON:
        case HtmlTokenId::TABLEROW_ON:
        case HtmlTokenId::TABLE_OFF:
            SkipToken();
            [[fallthrough]];
        case HtmlTokenId::COLGROUP_OFF:
            bDone = true;
            break;
        case HtmlTokenId::COL_ON:
            {
                sal_uInt16 nColSpan = 1;
                sal_uInt16 nColWidth = pSaveStruct->nColGrpWidth;
                bool bRelColWidth = pSaveStruct->bRelColGrpWidth;
                SvxAdjust eColAdjust = pSaveStruct->eColGrpAdjust;
                sal_Int16 eColVertOri = pSaveStruct->eColGrpVertOri;
 
                const HTMLOptions& rColOptions = GetOptions();
                for (size_t i = rColOptions.size(); i; )
                {
                    const HTMLOption& rOption = rColOptions[--i];
                    switch( rOption.GetToken() )
                    {
                    case HtmlOptionId::ID:
                        InsertBookmark( rOption.GetString() );
                        break;
                    case HtmlOptionId::SPAN:
                        nColSpan = o3tl::narrowing<sal_uInt16>(rOption.GetNumber());
                        if (nColSpan > 256)
                        {
                            SAL_INFO("sw.html", "ignoring huge SPAN " << nColSpan);
                            nColSpan = 1;
                        }
                        break;
                    case HtmlOptionId::WIDTH:
                        nColWidth = o3tl::narrowing<sal_uInt16>(rOption.GetNumber());
                        bRelColWidth =
                            (rOption.GetString().indexOf('*') != -1);
                        break;
                    case HtmlOptionId::ALIGN:
                        eColAdjust = rOption.GetEnum( aHTMLPAlignTable, eColAdjust );
                        break;
                    case HtmlOptionId::VALIGN:
                        eColVertOri =
                            rOption.GetEnum( aHTMLTableVAlignTable, eColVertOri );
                        break;
                    default: break;
                    }
                }
                pCurTable->InsertCol( nColSpan, nColWidth, bRelColWidth,
                                      eColAdjust, eColVertOri );
 
                // the attributes in <COLGRP> should be ignored, if there are <COL> elements
                pSaveStruct->nColGrpSpan = 0;
            }
            break;
        case HtmlTokenId::COL_OFF:
            break;      // Ignore
        case HtmlTokenId::MULTICOL_ON:
            // we can't add columned text frames here
            break;
        case HtmlTokenId::TEXTTOKEN:
            if( (pCurTable->GetContext() ||
                 !pCurTable->HasParentSection()) &&
                1==aToken.getLength() && ' '==aToken[0] )
                break;
            [[fallthrough]];
        default:
            pCurTable->MakeParentContents();
            NextToken( nToken );
        }
 
        OSL_ENSURE( !bPending || m_vPendingStack.empty(),
                "SwHTMLParser::BuildTableColGrp: There is a PendStack again" );
        bPending = false;
        if( IsParserWorking() )
            SaveState( HtmlTokenId::NONE );
 
        if( !bDone )
            nToken = GetNextToken();
    }
 
    if( SvParserState::Pending == GetStatus() )
    {
        m_vPendingStack.emplace_back( HtmlTokenId::COL_ON );
        m_vPendingStack.back().pData = std::move(pSaveStruct);
    }
    else
    {
        pSaveStruct->CloseColGroup( pCurTable );
    }
}
 
class CaptionSaveStruct : public SectionSaveStruct
{
    SwPosition m_aSavePos;
    SwHTMLNumRuleInfo m_aNumRuleInfo; // valid numbering
 
public:
 
    std::shared_ptr<HTMLAttrTable> m_xAttrTab;        // attributes
 
    CaptionSaveStruct( SwHTMLParser& rParser, SwPosition aPos ) :
        SectionSaveStruct( rParser ), m_aSavePos(std::move( aPos )),
        m_xAttrTab(std::make_shared<HTMLAttrTable>())
    {
        rParser.SaveAttrTab(m_xAttrTab);
 
        // The current numbering was remembered and just needs to be closed
        m_aNumRuleInfo.Set( rParser.GetNumInfo() );
        rParser.GetNumInfo().Clear();
    }
 
    const SwPosition& GetPos() const { return m_aSavePos; }
 
    void RestoreAll( SwHTMLParser& rParser )
    {
        // Recover the old stack
        Restore( rParser );
 
        // Recover the old attribute tables
        rParser.RestoreAttrTab(m_xAttrTab);
 
        // Re-open the old numbering
        rParser.GetNumInfo().Set( m_aNumRuleInfo );
    }
};
 
void SwHTMLParser::BuildTableCaption( HTMLTable *pCurTable )
{
    // <CAPTION> was read already
 
    if( !IsParserWorking() && m_vPendingStack.empty() )
        return;
 
    HtmlTokenId nToken = HtmlTokenId::NONE;
    std::unique_ptr<CaptionSaveStruct> xSaveStruct;
 
    if( !m_vPendingStack.empty() )
    {
        xSaveStruct.reset(static_cast<CaptionSaveStruct*>(m_vPendingStack.back().pData.release()));
 
        m_vPendingStack.pop_back();
        nToken = !m_vPendingStack.empty() ? m_vPendingStack.back().nToken : GetSaveToken();
        OSL_ENSURE( m_vPendingStack.empty(), "Where does a PendStack coming from?" );
 
        SaveState( nToken );
    }
    else
    {
        if (m_xTable->IsOverflowing())
        {
            SaveState( HtmlTokenId::NONE );
            return;
        }
 
        bool bTop = true;
        const HTMLOptions& rHTMLOptions = GetOptions();
        for ( size_t i = rHTMLOptions.size(); i; )
        {
            const HTMLOption& rOption = rHTMLOptions[--i];
            if( HtmlOptionId::ALIGN == rOption.GetToken() )
            {
                if (rOption.GetString().equalsIgnoreAsciiCase(
                        OOO_STRING_SVTOOLS_HTML_VA_bottom))
                {
                    bTop = false;
                }
            }
        }
 
        // Remember old PaM position
        xSaveStruct.reset(new CaptionSaveStruct(*this, *m_pPam->GetPoint()));
 
        // Add a text section in the icon section as a container for the header
        // and set the PaM there
        const SwStartNode *pStNd;
        if (m_xTable.get() == pCurTable)
            pStNd = InsertTempTableCaptionSection();
        else
            pStNd = InsertTableSection( RES_POOLCOLL_TEXT );
 
        std::unique_ptr<HTMLAttrContext> xCntxt(new HTMLAttrContext(HtmlTokenId::CAPTION_ON));
 
        // Table headers are always centered
        NewAttr(m_xAttrTab, &m_xAttrTab->pAdjust, SvxAdjustItem(SvxAdjust::Center, RES_PARATR_ADJUST));
 
        HTMLAttrs &rAttrs = xCntxt->GetAttrs();
        rAttrs.push_back( m_xAttrTab->pAdjust );
 
        PushContext(xCntxt);
 
        // Remember the start node of the section at the table
        pCurTable->SetCaption( pStNd, bTop );
 
        // If the first GetNextToken() doesn't succeed (pending input), must re-read from the beginning.
        SaveState( HtmlTokenId::NONE );
    }
 
    if( nToken == HtmlTokenId::NONE )
        nToken = GetNextToken();
 
    // </CAPTION> is needed according to DTD
    bool bDone = false;
    while( IsParserWorking() && !bDone )
    {
        SaveState( nToken );
 
        nToken = FilterToken( nToken );
 
        switch( nToken )
        {
        case HtmlTokenId::TABLE_ON:
            if( m_vPendingStack.empty() )
            {
                xSaveStruct->m_xTable = m_xTable;
                bool bHasToFly = xSaveStruct->m_xTable.get() != pCurTable;
                BuildTable( pCurTable->GetTableAdjust( true ),
                            false, true, bHasToFly );
            }
            else
            {
                BuildTable( SvxAdjust::End );
            }
            if( SvParserState::Pending != GetStatus() )
            {
                m_xTable = xSaveStruct->m_xTable;
            }
            break;
        case HtmlTokenId::TABLE_OFF:
        case HtmlTokenId::COLGROUP_ON:
        case HtmlTokenId::THEAD_ON:
        case HtmlTokenId::TFOOT_ON:
        case HtmlTokenId::TBODY_ON:
        case HtmlTokenId::TABLEROW_ON:
            SkipToken();
            bDone = true;
            break;
 
        case HtmlTokenId::CAPTION_OFF:
            bDone = true;
            break;
        default:
            if( !m_vPendingStack.empty() )
            {
                m_vPendingStack.pop_back();
                OSL_ENSURE( m_vPendingStack.empty(), "Further it can't go!" );
            }
 
            if( IsParserWorking() )
                NextToken( nToken );
            break;
        }
 
        if( IsParserWorking() )
            SaveState( HtmlTokenId::NONE );
 
        if( !bDone )
            nToken = GetNextToken();
    }
 
    if( SvParserState::Pending==GetStatus() )
    {
        m_vPendingStack.emplace_back( HtmlTokenId::CAPTION_ON );
        m_vPendingStack.back().pData = std::move(xSaveStruct);
        return;
    }
 
    // end all still open contexts
    while( m_aContexts.size() > m_nContextStAttrMin+1 )
    {
        std::unique_ptr<HTMLAttrContext> xCntxt(PopContext());
        EndContext(xCntxt.get());
    }
 
    bool bLFStripped = StripTrailingLF() > 0;
 
    if (m_xTable.get() == pCurTable)
    {
        // On moving the caption later, the last paragraph isn't moved as well.
        // That means, there has to be an empty paragraph at the end of the section
        if( m_pPam->GetPoint()->GetContentIndex() || bLFStripped )
            AppendTextNode( AM_NOSPACE );
    }
    else
    {
        // Strip LFs at the end of the paragraph
        if( !m_pPam->GetPoint()->GetContentIndex() && !bLFStripped )
            StripTrailingPara();
    }
 
    // If there's an adjustment for the cell, we need to close it
    std::unique_ptr<HTMLAttrContext> xCntxt(PopContext());
    if (xCntxt)
    {
        EndContext(xCntxt.get());
        xCntxt.reset();
    }
 
    SetAttr( false );
 
    // Recover stack and attribute table
    xSaveStruct->RestoreAll(*this);
 
    // Recover PaM
    *m_pPam->GetPoint() = xSaveStruct->GetPos();
}
 
namespace {
 
class TableSaveStruct : public SwPendingData
{
public:
    std::shared_ptr<HTMLTable> m_xCurrentTable;
 
    explicit TableSaveStruct(std::shared_ptr<HTMLTable> xCurTable)
        : m_xCurrentTable(std::move(xCurTable))
    {
    }
 
    // Initiate creation of the table and put the table in a text frame if
    // needed. If it returns true, we need to insert a paragraph.
    void MakeTable( sal_uInt16 nWidth, SwPosition& rPos, SwDoc *pDoc );
};
 
}
 
void TableSaveStruct::MakeTable( sal_uInt16 nWidth, SwPosition& rPos, SwDoc *pDoc )
{
    m_xCurrentTable->MakeTable(nullptr, nWidth);
 
    HTMLTableContext *pTCntxt = m_xCurrentTable->GetContext();
    assert(pTCntxt && "Where is the table context");
 
    SwTableNode *pTableNd = pTCntxt->GetTableNode();
    OSL_ENSURE( pTableNd, "Where is the table node" );
 
    if( pDoc->getIDocumentLayoutAccess().GetCurrentViewShell() && pTableNd )
    {
        // If there's already a layout, the BoxFrames need to be regenerated at this table
 
        if( pTCntxt->GetFrameFormat() )
        {
            pTCntxt->GetFrameFormat()->DelFrames();
            pTableNd->DelFrames();
            pTCntxt->GetFrameFormat()->MakeFrames();
        }
        else
        {
            pTableNd->DelFrames();
            SwNodeIndex aIdx( *pTableNd->EndOfSectionNode(), 1 );
            OSL_ENSURE( aIdx.GetIndex() <= pTCntxt->GetPos()->GetNodeIndex(),
                    "unexpected node for table layout" );
            pTableNd->MakeOwnFrames();
        }
    }
 
    rPos = *pTCntxt->GetPos();
}
 
HTMLTableOptions::HTMLTableOptions( const HTMLOptions& rOptions,
                                    SvxAdjust eParentAdjust ) :
    nCols( 0 ),
    nWidth( 0 ), nHeight( 0 ),
    nCellPadding( USHRT_MAX ), nCellSpacing( USHRT_MAX ),
    nBorder( USHRT_MAX ),
    nHSpace( 0 ), nVSpace( 0 ),
    eAdjust( eParentAdjust ), eVertOri( text::VertOrientation::CENTER ),
    eFrame( HTMLTableFrame::Void ), eRules( HTMLTableRules::NONE ),
    bPercentWidth( false ),
    bTableAdjust( false ),
    bBGColor( false ),
    aBorderColor( COL_GRAY )
{
    bool bBorderColor = false;
    bool bHasFrame = false, bHasRules = false;
 
    for (size_t i = rOptions.size(); i; )
    {
        const HTMLOption& rOption = rOptions[--i];
        switch( rOption.GetToken() )
        {
        case HtmlOptionId::ID:
            aId = rOption.GetString();
            break;
        case HtmlOptionId::COLS:
            nCols = o3tl::narrowing<sal_uInt16>(rOption.GetNumber());
            break;
        case HtmlOptionId::WIDTH:
            nWidth = o3tl::narrowing<sal_uInt16>(rOption.GetNumber());
            bPercentWidth = (rOption.GetString().indexOf('%') != -1);
            if( bPercentWidth && nWidth>100 )
                nWidth = 100;
            break;
        case HtmlOptionId::HEIGHT:
            nHeight = o3tl::narrowing<sal_uInt16>(rOption.GetNumber());
            if( rOption.GetString().indexOf('%') != -1 )
                nHeight = 0;    // don't use % attributes
            break;
        case HtmlOptionId::CELLPADDING:
            nCellPadding = o3tl::narrowing<sal_uInt16>(rOption.GetNumber());
            break;
        case HtmlOptionId::CELLSPACING:
            nCellSpacing = o3tl::narrowing<sal_uInt16>(rOption.GetNumber());
            break;
        case HtmlOptionId::ALIGN:
            {
                if( rOption.GetEnum( eAdjust, aHTMLPAlignTable ) )
                {
                    bTableAdjust = true;
                }
            }
            break;
        case HtmlOptionId::VALIGN:
            eVertOri = rOption.GetEnum( aHTMLTableVAlignTable, eVertOri );
            break;
        case HtmlOptionId::BORDER:
            // Handle BORDER and BORDER=BORDER like BORDER=1
            if (!rOption.GetString().isEmpty() &&
                !rOption.GetString().equalsIgnoreAsciiCase(
                        OOO_STRING_SVTOOLS_HTML_O_border))
            {
                nBorder = o3tl::narrowing<sal_uInt16>(rOption.GetNumber());
            }
            else
                nBorder = 1;
 
            if( !bHasFrame )
                eFrame = ( nBorder ? HTMLTableFrame::Box : HTMLTableFrame::Void );
            if( !bHasRules )
                eRules = ( nBorder ? HTMLTableRules::All : HTMLTableRules::NONE );
            break;
        case HtmlOptionId::FRAME:
            eFrame = rOption.GetTableFrame();
            bHasFrame = true;
            break;
        case HtmlOptionId::RULES:
            eRules = rOption.GetTableRules();
            bHasRules = true;
            break;
        case HtmlOptionId::BGCOLOR:
            // Ignore empty BGCOLOR on <TABLE>, <TR> and <TD>/<TH> like Netscape
            // *really* not on other tags
            if( !rOption.GetString().isEmpty() )
            {
                rOption.GetColor( aBGColor );
                bBGColor = true;
            }
            break;
        case HtmlOptionId::BACKGROUND:
            aBGImage = rOption.GetString();
            break;
        case HtmlOptionId::BORDERCOLOR:
            rOption.GetColor( aBorderColor );
            bBorderColor = true;
            break;
        case HtmlOptionId::BORDERCOLORDARK:
            if( !bBorderColor )
                rOption.GetColor( aBorderColor );
            break;
        case HtmlOptionId::STYLE:
            aStyle = rOption.GetString();
            break;
        case HtmlOptionId::CLASS:
            aClass = rOption.GetString();
            break;
        case HtmlOptionId::DIR:
            aDir = rOption.GetString();
            break;
        case HtmlOptionId::HSPACE:
            nHSpace = o3tl::narrowing<sal_uInt16>(rOption.GetNumber());
            break;
        case HtmlOptionId::VSPACE:
            nVSpace = o3tl::narrowing<sal_uInt16>(rOption.GetNumber());
            break;
        default: break;
        }
    }
 
    if( nCols && !nWidth )
    {
        nWidth = 100;
        bPercentWidth = true;
    }
 
    // If BORDER=0 or no BORDER given, then there shouldn't be a border
    if( 0==nBorder || USHRT_MAX==nBorder )
    {
        eFrame = HTMLTableFrame::Void;
        eRules = HTMLTableRules::NONE;
    }
}
 
void SwHTMLParser::DeleteSection(SwStartNode* pSttNd)
{
    //if section to be deleted contains a pending m_pMarquee, it will be deleted
    //so clear m_pMarquee pointer if that's the case
    SwFrameFormat* pObjectFormat = m_pMarquee ? ::FindFrameFormat(m_pMarquee.get()) : nullptr;
    FrameDeleteWatch aWatch(pObjectFormat);
 
    m_xDoc->getIDocumentContentOperations().DeleteSection(pSttNd);
 
    if (pObjectFormat)
    {
        if (aWatch.WasDeleted())
            m_pMarquee = nullptr;
        else
            aWatch.EndListeningAll();
    }
}
 
std::shared_ptr<HTMLTable> SwHTMLParser::BuildTable(SvxAdjust eParentAdjust,
                                                    bool bIsParentHead,
                                                    bool bHasParentSection,
                                                    bool bHasToFly)
{
    TableDepthGuard aGuard(*this);
    if (aGuard.TooDeep())
        eState = SvParserState::Error;
 
    if (!IsParserWorking() && m_vPendingStack.empty())
        return std::shared_ptr<HTMLTable>();
 
    ::comphelper::FlagRestorationGuard g(m_isInTableStructure, true);
    HtmlTokenId nToken = HtmlTokenId::NONE;
    bool bPending = false;
    std::unique_ptr<TableSaveStruct> xSaveStruct;
 
    if( !m_vPendingStack.empty() )
    {
        xSaveStruct.reset(static_cast<TableSaveStruct*>(m_vPendingStack.back().pData.release()));
 
        m_vPendingStack.pop_back();
        nToken = !m_vPendingStack.empty() ? m_vPendingStack.back().nToken : GetSaveToken();
        bPending = SvParserState::Error == eState && !m_vPendingStack.empty();
 
        SaveState( nToken );
    }
    else
    {
        m_xTable.reset();
 
        // Parse CSS on the table.
        OUString aStyle;
        const HTMLOptions& rHTMLOptions = GetOptions();
        for (size_t i = rHTMLOptions.size(); i;)
        {
            const HTMLOption& rOption = rHTMLOptions[--i];
            if (rOption.GetToken() == HtmlOptionId::STYLE)
            {
                aStyle = rOption.GetString();
            }
        }
        if (!aStyle.isEmpty())
        {
            // Have inline CSS.
            SfxItemSet aItemSet(m_xDoc->GetAttrPool(), m_pCSS1Parser->GetWhichMap());
            SvxCSS1PropertyInfo aPropInfo;
            if (ParseStyleOptions(aStyle, /*aId=*/OUString(), /*aClass=*/OUString(), aItemSet,
                                  aPropInfo))
            {
                if (aPropInfo.m_eLeftMarginType == SVX_CSS1_LTYPE_AUTO
                    && aPropInfo.m_eRightMarginType == SVX_CSS1_LTYPE_AUTO)
                {
                    // Both left & right is set to auto: that's our center.
                    eParentAdjust = SvxAdjust::Center;
                }
            }
        }
 
        HTMLTableOptions aTableOptions(GetOptions(), eParentAdjust);
 
        if (!aTableOptions.aId.isEmpty())
            InsertBookmark(aTableOptions.aId);
 
        std::shared_ptr<HTMLTable> xCurTable(std::make_shared<HTMLTable>(this,
                                              bIsParentHead,
                                              bHasParentSection,
                                              bHasToFly,
                                              aTableOptions));
        m_xTable = xCurTable;
 
        xSaveStruct.reset(new TableSaveStruct(std::move(xCurTable)));
 
        // Is pending on the first GetNextToken, needs to be re-read on each construction
        SaveState( HtmlTokenId::NONE );
    }
 
    std::shared_ptr<HTMLTable> xCurTable = xSaveStruct->m_xCurrentTable;
 
    // </TABLE> is needed according to DTD
    if( nToken == HtmlTokenId::NONE )
        nToken = GetNextToken();
 
    bool bDone = false;
    while( (IsParserWorking() && !bDone) || bPending )
    {
        SaveState( nToken );
 
        nToken = FilterToken( nToken );
 
        OSL_ENSURE( !m_vPendingStack.empty() || !m_bCallNextToken ||
                xCurTable->GetContext() || xCurTable->HasParentSection(),
                "Where is the section?" );
        if( m_vPendingStack.empty() && m_bCallNextToken &&
            (xCurTable->GetContext() || xCurTable->HasParentSection()) )
        {
            /// Call NextToken directly (e.g. ignore the content of floating frames or applets)
            NextToken( nToken );
        }
        else switch( nToken )
        {
        case HtmlTokenId::TABLE_ON:
            if( !xCurTable->GetContext() )
            {
                // If there's no table added, read the next table'
                SkipToken();
                bDone = true;
            }
 
            break;
        case HtmlTokenId::TABLE_OFF:
            bDone = true;
            break;
        case HtmlTokenId::CAPTION_ON:
            BuildTableCaption(xCurTable.get());
            bDone = m_xTable->IsOverflowing();
            break;
        case HtmlTokenId::COL_ON:
            SkipToken();
            BuildTableColGroup(xCurTable.get(), false);
            break;
        case HtmlTokenId::COLGROUP_ON:
            BuildTableColGroup(xCurTable.get(), true);
            break;
        case HtmlTokenId::TABLEROW_ON:
        case HtmlTokenId::TABLEHEADER_ON:
        case HtmlTokenId::TABLEDATA_ON:
            SkipToken();
            BuildTableSection(xCurTable.get(), false, false);
            bDone = m_xTable->IsOverflowing();
            break;
        case HtmlTokenId::THEAD_ON:
        case HtmlTokenId::TFOOT_ON:
        case HtmlTokenId::TBODY_ON:
            BuildTableSection(xCurTable.get(), true, HtmlTokenId::THEAD_ON==nToken);
            bDone = m_xTable->IsOverflowing();
            break;
        case HtmlTokenId::MULTICOL_ON:
            // we can't add columned text frames here
            break;
        case HtmlTokenId::FORM_ON:
            NewForm( false );   // don't add a new paragraph
            break;
        case HtmlTokenId::FORM_OFF:
            EndForm( false );   // don't add a new paragraph
            break;
        case HtmlTokenId::TEXTTOKEN:
            // blank strings may be a series of CR+LF and no text
            if( (xCurTable->GetContext() ||
                 !xCurTable->HasParentSection()) &&
                1==aToken.getLength() && ' '==aToken[0] )
                break;
            [[fallthrough]];
        default:
            xCurTable->MakeParentContents();
            NextToken( nToken );
            break;
        }
 
        OSL_ENSURE( !bPending || m_vPendingStack.empty(),
                "SwHTMLParser::BuildTable: There is a PendStack again" );
        bPending = false;
        if( IsParserWorking() )
            SaveState( HtmlTokenId::NONE );
 
        if( !bDone )
            nToken = GetNextToken();
    }
 
    if( SvParserState::Pending == GetStatus() )
    {
        m_vPendingStack.emplace_back( HtmlTokenId::TABLE_ON );
        m_vPendingStack.back().pData = std::move(xSaveStruct);
        return std::shared_ptr<HTMLTable>();
    }
 
    HTMLTableContext *pTCntxt = xCurTable->GetContext();
    if( pTCntxt )
    {
 
        // Modify table structure
        xCurTable->CloseTable();
 
        // end contexts that began out of cells. Needs to exist before (!) we move the table,
        // since the current one doesn't exist anymore afterwards
        while( m_aContexts.size() > m_nContextStAttrMin )
        {
            std::unique_ptr<HTMLAttrContext> xCntxt(PopContext());
            if (!xCntxt)
                break;
            ClearContext(xCntxt.get());
        }
 
        m_nContextStMin = pTCntxt->GetContextStMin();
        m_nContextStAttrMin = pTCntxt->GetContextStAttrMin();
 
        if (m_xTable == xCurTable)
        {
            // Set table caption
            const SwStartNode *pCapStNd = m_xTable->GetCaptionStartNode();
            if( pCapStNd )
            {
                // The last paragraph of the section is never part of the copy.
                // That's why the section needs to contain at least two paragraphs
 
                if( pCapStNd->EndOfSectionIndex() - pCapStNd->GetIndex() > SwNodeOffset(2) )
                {
                    // Don't copy start node and the last paragraph
                    SwNodeRange aSrcRg( *pCapStNd, SwNodeOffset(1),
                                    *pCapStNd->EndOfSectionNode(), SwNodeOffset(-1) );
 
                    bool bTop = m_xTable->IsTopCaption();
                    SwStartNode *pTableStNd = pTCntxt->GetTableNode();
 
                    OSL_ENSURE( pTableStNd, "Where is the table node" );
                    OSL_ENSURE( pTableStNd == m_pPam->GetPointNode().FindTableNode(),
                            "Are we in the wrong table?" );
 
                    if (pTableStNd)
                    {
                        SwNode* pNd;
                        if( bTop )
                            pNd = pTableStNd;
                        else
                            pNd = pTableStNd->EndOfSectionNode();
                        SwNodeIndex aDstIdx( *pNd, bTop ? 0 : 1 );
 
                        m_xDoc->getIDocumentContentOperations().MoveNodeRange( aSrcRg, aDstIdx.GetNode(),
                            SwMoveFlags::DEFAULT );
 
                        // If the caption was added before the table, a page style on that table
                        // needs to be moved to the first paragraph of the header.
                        // Additionally, all remembered indices that point to the table node
                        // need to be moved
                        if( bTop )
                        {
                            MovePageDescAttrs( pTableStNd, aSrcRg.aStart.GetIndex(),
                                               false );
                        }
                    }
                }
 
                // The section isn't needed anymore
                m_pPam->SetMark();
                m_pPam->DeleteMark();
                DeleteSection(const_cast<SwStartNode*>(pCapStNd));
                m_xTable->SetCaption( nullptr, false );
            }
 
            // Process SwTable
            sal_uInt16 nBrowseWidth = o3tl::narrowing<sal_uInt16>(GetCurrentBrowseWidth());
            xSaveStruct->MakeTable(nBrowseWidth, *m_pPam->GetPoint(), m_xDoc.get());
        }
 
        GetNumInfo().Set( pTCntxt->GetNumInfo() );
        pTCntxt->RestorePREListingXMP( *this );
        RestoreAttrTab(pTCntxt->m_xAttrTab);
 
        if (m_xTable == xCurTable)
        {
            // Set upper paragraph spacing
            m_bUpperSpace = true;
            SetTextCollAttrs();
 
            SwTableNode* pTableNode = pTCntxt->GetTableNode();
            size_t nTableBoxSize = pTableNode ? pTableNode->GetTable().GetTabSortBoxes().size() : 0;
            m_nParaCnt = m_nParaCnt - std::min(m_nParaCnt, nTableBoxSize);
 
            // Jump to a table if needed
            if( JumpToMarks::Table == m_eJumpTo && m_xTable->GetSwTable() &&
                m_xTable->GetSwTable()->GetFrameFormat()->GetName() == m_sJmpMark )
            {
                m_bChkJumpMark = true;
                m_eJumpTo = JumpToMarks::NONE;
            }
 
            // If the import was canceled, don't call Show again here since
            // the SwViewShell was already deleted
            // That's not enough. Even in the ACCEPTING_STATE, a Show mustn't be called
            // because otherwise the parser's gonna be destroyed on the reschedule,
            // if there's still a DataAvailable link coming. So: only in the WORKING state
            if( !m_nParaCnt && SvParserState::Working == GetStatus() )
                Show();
        }
    }
    else if (m_xTable == xCurTable)
    {
        // There was no table read
 
        // We maybe need to delete a read caption
        const SwStartNode *pCapStNd = xCurTable->GetCaptionStartNode();
        if( pCapStNd )
        {
            m_pPam->SetMark();
            m_pPam->DeleteMark();
            DeleteSection(const_cast<SwStartNode*>(pCapStNd));
            xCurTable->SetCaption( nullptr, false );
        }
    }
 
    if (m_xTable == xCurTable)
    {
        xSaveStruct->m_xCurrentTable.reset();
        m_xTable.reset();
    }
 
    std::shared_ptr<HTMLTable> xRetTable = xSaveStruct->m_xCurrentTable;
    xSaveStruct.reset();
 
    return xRetTable;
}
 
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */

V730 Not all members of a class are initialized inside the constructor. Consider inspecting: m_aFrameFormats.

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

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

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

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

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

V1037 Two or more case-branches perform the same actions. Check lines: 4069, 4073

V1051 Consider checking for misprints. It's possible that the 'nEmptyRows' should be checked here.

V1051 Consider checking for misprints. It's possible that the 'm_pCurrCnts' should be checked here.