diff options
Diffstat (limited to 'sw/source/filter/html/htmltab.cxx')
-rw-r--r-- | sw/source/filter/html/htmltab.cxx | 5220 |
1 files changed, 5220 insertions, 0 deletions
diff --git a/sw/source/filter/html/htmltab.cxx b/sw/source/filter/html/htmltab.cxx new file mode 100644 index 0000000000..424644378b --- /dev/null +++ b/sw/source/filter/html/htmltab.cxx @@ -0,0 +1,5220 @@ +/* -*- 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/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(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 = static_cast<SwTableLineFormat*>(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 = static_cast<SwTableLineFormat*>(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 = static_cast<SwTableLineFormat*>(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 (!utl::ConfigManager::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 = static_cast<SwTableLineFormat*>(pLine1->GetFrameFormat()); + m_pBoxFormat = static_cast<SwTableBoxFormat*>(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 && utl::ConfigManager::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() ); + m_xDoc->GetNodes().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(); + OSL_ENSURE( 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(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: */ |