/* -*- 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 <vector>
#include <com/sun/star/table/XTable.hpp>
#include <com/sun/star/table/XMergeableCellRange.hpp>
#include <tools/stream.hxx>
#include <tools/UnitConversion.hxx>
#include <svx/svdetc.hxx>
#include <editeng/outlobj.hxx>
#include <cell.hxx>
#include <svx/svdotable.hxx>
#include <svx/svdoutl.hxx>
#include <editeng/editeng.hxx>
#include <editeng/editdata.hxx>
#include <svx/svdmodel.hxx>
#include <editeng/editids.hrc>
#include <sal/log.hxx>
#include <tools/debug.hxx>
#include <comphelper/diagnose_ex.hxx>
#include <svtools/htmltokn.h>
#include <svtools/parhtml.hxx>
using namespace ::com::sun::star::uno;
using namespace ::com::sun::star::table;
using namespace ::com::sun::star::container;
using namespace ::com::sun::star::beans;
namespace sdr::table
{
namespace
{
struct RowColSpan
{
sal_Int32 mnRowSpan;
sal_Int32 mnColSpan;
explicit RowColSpan()
: mnRowSpan(1)
, mnColSpan(1)
{
}
};
struct HTMLCellDefault
{
sal_Int32 mnRowSpan;
sal_Int32 mnColSpan; // MergeCell if >1, merged cells if 0
sal_Int32 mnCellX;
explicit HTMLCellDefault()
: mnRowSpan(1)
, mnColSpan(1)
, mnCellX(0)
{
}
};
}
typedef std::vector<std::shared_ptr<HTMLCellDefault>> HTMLCellDefaultVector;
namespace
{
struct HTMLCellInfo
{
SfxItemSet maItemSet;
sal_Int32 mnStartPara;
sal_Int32 mnParaCount;
sal_Int32 mnCellX;
sal_Int32 mnRowSpan;
std::shared_ptr<HTMLCellInfo> mxVMergeCell;
explicit HTMLCellInfo(SfxItemPool& rPool)
: maItemSet(rPool)
, mnStartPara(0)
, mnParaCount(0)
, mnCellX(0)
, mnRowSpan(1)
{
}
};
}
typedef std::shared_ptr<HTMLCellInfo> HTMLCellInfoPtr;
typedef std::vector<HTMLCellInfoPtr> HTMLColumnVector;
typedef std::shared_ptr<HTMLColumnVector> HTMLColumnVectorPtr;
class SdrTableHTMLParser
{
public:
explicit SdrTableHTMLParser(SdrTableObj& rTableObj);
void Read(SvStream& rStream);
void ProcToken(HtmlImportInfo* pInfo);
void NextRow();
void NextColumn();
void NewCellRow();
void InsertCell(sal_Int32 nStartPara, sal_Int32 nEndPara);
void InsertColumnEdge(sal_Int32 nEdge);
void FillTable();
DECL_LINK(HTMLImportHdl, HtmlImportInfo&, void);
private:
SdrTableObj& mrTableObj;
std::unique_ptr<SdrOutliner> mpOutliner;
SfxItemPool& mrItemPool;
HTMLCellDefaultVector maDefaultList;
HTMLCellDefaultVector::iterator maDefaultIterator;
bool mbNewDef;
sal_Int32 mnCellStartPara;
sal_Int32 mnRowCnt;
sal_Int32 mnLastEdge;
sal_Int32 mnVMergeIdx;
std::vector<sal_Int32> maColumnEdges;
std::vector<sal_Int32>::iterator maLastEdge;
std::vector<HTMLColumnVectorPtr> maRows;
std::unique_ptr<HTMLCellDefault> mpInsDefault;
HTMLCellDefault* mpActDefault;
sal_Int32 mnCellInRow;
rtl::Reference<TableModel> mxTable;
HTMLColumnVectorPtr mxLastRow;
// Copy assignment is forbidden and not implemented.
SdrTableHTMLParser(const SdrTableHTMLParser&) = delete;
SdrTableHTMLParser& operator=(const SdrTableHTMLParser&) = delete;
};
SdrTableHTMLParser::SdrTableHTMLParser(SdrTableObj& rTableObj)
: mrTableObj(rTableObj)
, mpOutliner(SdrMakeOutliner(OutlinerMode::TextObject, rTableObj.getSdrModelFromSdrObject()))
, mrItemPool(rTableObj.getSdrModelFromSdrObject().GetItemPool())
, mbNewDef(false)
, mnCellStartPara(0)
, mnRowCnt(0)
, mnLastEdge(0)
, mnVMergeIdx(0)
, mpActDefault(nullptr)
, mnCellInRow(-1)
, mxTable(rTableObj.getUnoTable())
{
mpOutliner->SetUpdateLayout(true);
mpOutliner->SetStyleSheet(0, mrTableObj.GetStyleSheet());
mpInsDefault.reset(new HTMLCellDefault());
}
void SdrTableHTMLParser::Read(SvStream& rStream)
{
EditEngine& rEdit = const_cast<EditEngine&>(mpOutliner->GetEditEngine());
Link<HtmlImportInfo&, void> aOldLink(rEdit.GetHtmlImportHdl());
rEdit.SetHtmlImportHdl(LINK(this, SdrTableHTMLParser, HTMLImportHdl));
mpOutliner->Read(rStream, OUString(), EETextFormat::Html);
rEdit.SetHtmlImportHdl(aOldLink);
FillTable();
}
IMPL_LINK(SdrTableHTMLParser, HTMLImportHdl, HtmlImportInfo&, rInfo, void)
{
switch (rInfo.eState)
{
case HtmlImportState::NextToken:
ProcToken(&rInfo);
break;
case HtmlImportState::End:
if (rInfo.aSelection.nEndPos)
{
mpActDefault = nullptr;
//TODO: ??
// rInfo.nToken = RTF_PAR;
rInfo.aSelection.nEndPara++;
ProcToken(&rInfo);
}
break;
case HtmlImportState::SetAttr:
case HtmlImportState::InsertText:
case HtmlImportState::InsertPara:
break;
default:
SAL_WARN("svx.table", "unknown ImportInfo.eState");
}
}
void SdrTableHTMLParser::NextRow()
{
mxLastRow = maRows.back();
mnVMergeIdx = 0;
++mnRowCnt;
}
void SdrTableHTMLParser::InsertCell(sal_Int32 nStartPara, sal_Int32 nEndPara)
{
HTMLCellInfoPtr xCellInfo = std::make_shared<HTMLCellInfo>(mrItemPool);
xCellInfo->mnStartPara = nStartPara;
xCellInfo->mnParaCount = nEndPara - nStartPara;
xCellInfo->mnCellX = mpActDefault->mnCellX;
xCellInfo->mnRowSpan = mpActDefault->mnRowSpan;
if (mxLastRow != nullptr)
{
sal_Int32 nSize = mxLastRow->size();
while (mnVMergeIdx < nSize && (*mxLastRow)[mnVMergeIdx]->mnCellX < xCellInfo->mnCellX)
++mnVMergeIdx;
if (xCellInfo->mnRowSpan == 0 && mnVMergeIdx < nSize)
{
HTMLCellInfoPtr xLastCell((*mxLastRow)[mnVMergeIdx]);
if (xLastCell->mnRowSpan)
xCellInfo->mxVMergeCell = std::move(xLastCell);
else
xCellInfo->mxVMergeCell = xLastCell->mxVMergeCell;
}
}
if (!maRows.empty())
{
HTMLColumnVectorPtr xColumn(maRows.back());
if (xCellInfo->mxVMergeCell)
{
if (xColumn->empty() || xColumn->back()->mxVMergeCell != xCellInfo->mxVMergeCell)
xCellInfo->mxVMergeCell->mnRowSpan++;
}
xColumn->push_back(xCellInfo);
}
}
void SdrTableHTMLParser::InsertColumnEdge(sal_Int32 nEdge)
{
auto aNextEdge = std::lower_bound(maLastEdge, maColumnEdges.end(), nEdge);
if (aNextEdge == maColumnEdges.end() || nEdge != *aNextEdge)
{
maLastEdge = maColumnEdges.insert(aNextEdge, nEdge);
mnLastEdge = nEdge;
}
}
void SdrTableHTMLParser::FillTable()
{
try
{
sal_Int32 nColCount = mxTable->getColumnCount();
Reference<XTableColumns> xCols(mxTable->getColumns(), UNO_SET_THROW);
sal_Int32 nColMax = maColumnEdges.size();
if (nColCount < nColMax)
{
xCols->insertByIndex(nColCount, nColMax - nColCount);
nColCount = mxTable->getColumnCount();
}
static constexpr OUStringLiteral sWidth(u"Width");
sal_Int32 nCol, nLastEdge = 0;
for (nCol = 0; nCol < nColCount; nCol++)
{
Reference<XPropertySet> xSet(xCols->getByIndex(nCol), UNO_QUERY_THROW);
sal_Int32 nWidth = maColumnEdges[nCol] - nLastEdge;
xSet->setPropertyValue(sWidth, Any(nWidth));
nLastEdge += nWidth;
}
const sal_Int32 nRowCount = mxTable->getRowCount();
if (nRowCount < mnRowCnt)
{
Reference<XTableRows> xRows(mxTable->getRows(), UNO_SET_THROW);
xRows->insertByIndex(nRowCount, mnRowCnt - nRowCount);
}
for (sal_Int32 nRow = 0; nRow < static_cast<sal_Int32>(maRows.size()); nRow++)
{
HTMLColumnVectorPtr xColumn(maRows[nRow]);
nCol = 0;
auto aEdge = maColumnEdges.begin();
for (sal_Int32 nIdx = 0;
nCol < nColMax && nIdx < static_cast<sal_Int32>(xColumn->size()); nIdx++)
{
HTMLCellInfoPtr xCellInfo((*xColumn)[nIdx]);
CellRef xCell(mxTable->getCell(nCol, nRow));
if (xCell.is() && xCellInfo)
{
const SfxPoolItem* pPoolItem = nullptr;
if (xCellInfo->maItemSet.GetItemState(SDRATTR_TABLE_BORDER, false, &pPoolItem)
== SfxItemState::SET)
xCell->SetMergedItem(*pPoolItem);
std::optional<OutlinerParaObject> pTextObject(mpOutliner->CreateParaObject(
xCellInfo->mnStartPara, xCellInfo->mnParaCount));
if (pTextObject)
{
SdrOutliner& rOutliner = mrTableObj.ImpGetDrawOutliner();
rOutliner.SetUpdateLayout(true);
rOutliner.SetText(*pTextObject);
mrTableObj.NbcSetOutlinerParaObjectForText(rOutliner.CreateParaObject(),
xCell.get());
}
sal_Int32 nLastRow = nRow;
if (xCellInfo->mnRowSpan)
nLastRow += xCellInfo->mnRowSpan - 1;
aEdge = std::lower_bound(aEdge, maColumnEdges.end(), xCellInfo->mnCellX);
sal_Int32 nLastCol = nCol;
if (aEdge != maColumnEdges.end())
{
nLastCol = std::distance(maColumnEdges.begin(), aEdge);
++aEdge;
}
if (nLastCol > nCol || nLastRow > nRow)
{
Reference<XMergeableCellRange> xRange(
mxTable->createCursorByRange(
mxTable->getCellRangeByPosition(nCol, nRow, nLastCol, nLastRow)),
UNO_QUERY_THROW);
if (xRange->isMergeable())
xRange->merge();
}
nCol = nLastCol + 1;
}
}
}
tools::Rectangle aRect(mrTableObj.GetSnapRect());
aRect.SetRight(aRect.Left() + nLastEdge);
mrTableObj.NbcSetSnapRect(aRect);
}
catch (Exception&)
{
TOOLS_WARN_EXCEPTION("svx", "");
}
}
void SdrTableHTMLParser::NewCellRow()
{
if (mbNewDef)
{
mbNewDef = false;
maRows.push_back(std::make_shared<std::vector<std::shared_ptr<HTMLCellInfo>>>());
}
maDefaultIterator = maDefaultList.begin();
NextColumn();
DBG_ASSERT(mpActDefault, "NewCellRow: pActDefault==0");
}
void SdrTableHTMLParser::NextColumn()
{
if (maDefaultIterator != maDefaultList.end())
mpActDefault = (*maDefaultIterator++).get();
else
mpActDefault = nullptr;
}
static RowColSpan lcl_GetRowColSpan(const HTMLOptions& options)
{
RowColSpan aRowColSpan;
for (HTMLOptions::const_iterator optionIt = options.begin(); optionIt != options.end();
++optionIt)
{
if (optionIt->GetToken() == HtmlOptionId::COLSPAN)
{
aRowColSpan.mnColSpan = optionIt->GetNumber();
}
else if (optionIt->GetToken() == HtmlOptionId::ROWSPAN)
{
aRowColSpan.mnRowSpan = optionIt->GetNumber();
}
}
return aRowColSpan;
}
//TODO: width is pixel - detect document pixel with to determine real width
static sal_Int32 lcl_GetWidth(const HTMLOptions& options)
{
for (HTMLOptions::const_iterator optionIt = options.begin(); optionIt != options.end();
++optionIt)
{
if (optionIt->GetToken() == HtmlOptionId::WIDTH)
{
//const OUString& value = optionIt->GetString();
//TODO: Which conversion is required?
return 1000;
}
}
return 1000;
}
void SdrTableHTMLParser::ProcToken(HtmlImportInfo* pInfo)
{
HTMLParser* pHtmlParser = static_cast<HTMLParser*>(pInfo->pParser);
const HTMLOptions& options = pHtmlParser->GetOptions();
switch (pInfo->nToken)
{
case HtmlTokenId::TABLE_ON:
maDefaultList.clear();
maLastEdge = maColumnEdges.begin();
mnLastEdge = 0;
break;
case HtmlTokenId::TABLE_OFF:
break;
case HtmlTokenId::TABLEHEADER_ON:
case HtmlTokenId::TABLEDATA_ON:
{
++mnCellInRow;
assert(mpActDefault);
RowColSpan aRowColSpan = lcl_GetRowColSpan(options);
mpActDefault->mnColSpan = aRowColSpan.mnColSpan;
mpActDefault->mnRowSpan = aRowColSpan.mnRowSpan;
mnCellStartPara = pInfo->aSelection.nStartPara;
}
break;
case HtmlTokenId::TABLEDATA_OFF:
case HtmlTokenId::TABLEHEADER_OFF:
{
DBG_ASSERT(mpActDefault, "TABLEDATA_OFF: pActDefault==0");
if (mbNewDef || !mpActDefault)
NewCellRow();
if (!mpActDefault)
mpActDefault = mpInsDefault.get();
if (mpActDefault->mnColSpan > 0)
{
mpActDefault->mnCellX = maColumnEdges[mnCellInRow + mpActDefault->mnColSpan - 1];
InsertCell(mnCellStartPara, pInfo->aSelection.nEndPara);
}
NextColumn();
}
break;
case HtmlTokenId::TABLEROW_ON:
mbNewDef = true;
NewCellRow();
break;
case HtmlTokenId::TABLEROW_OFF:
{
NextRow();
mnCellInRow = -1;
}
break;
case HtmlTokenId::COL_ON:
{
std::shared_ptr<HTMLCellDefault> pDefault(mpInsDefault.release());
maDefaultList.push_back(pDefault);
const sal_Int32 nSize = lcl_GetWidth(options) + mnLastEdge;
if (nSize > mnLastEdge)
InsertColumnEdge(nSize);
mpInsDefault.reset(new HTMLCellDefault());
mnLastEdge = nSize;
}
break;
case HtmlTokenId::COL_OFF:
break;
default:
break;
}
}
void ImportAsHTML(SvStream& rStream, SdrTableObj& rObj)
{
SdrTableHTMLParser aParser(rObj);
aParser.Read(rStream);
}
}
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
↑ V530 The return value of function 'Read' is required to be utilized.