529 lines
16 KiB
C++
529 lines
16 KiB
C++
/* -*- 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.end.nIndex)
|
|
{
|
|
mpActDefault = nullptr;
|
|
//TODO: ??
|
|
// rInfo.nToken = RTF_PAR;
|
|
rInfo.aSelection.end.nPara++;
|
|
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;
|
|
}
|
|
static sal_Int32 lcl_GetSpan(const HTMLOptions& options)
|
|
{
|
|
for (HTMLOptions::const_iterator optionIt = options.begin(); optionIt != options.end();
|
|
++optionIt)
|
|
{
|
|
if (optionIt->GetToken() == HtmlOptionId::SPAN)
|
|
return optionIt->GetNumber();
|
|
}
|
|
return 1;
|
|
}
|
|
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.start.nPara;
|
|
}
|
|
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.end.nPara);
|
|
}
|
|
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;
|
|
case HtmlTokenId::COLGROUP_ON:
|
|
{
|
|
const sal_Int32 nSpan = lcl_GetSpan(options);
|
|
for (sal_Int32 nCol = 0; nCol < nSpan; ++nCol)
|
|
{
|
|
std::shared_ptr<HTMLCellDefault> pDefault(mpInsDefault.release());
|
|
maDefaultList.push_back(pDefault);
|
|
const sal_Int32 nSize = lcl_GetWidth(options) + mnLastEdge;
|
|
if (nSize > mnLastEdge)
|
|
InsertColumnEdge(nSize);
|
|
mnLastEdge = nSize;
|
|
mpInsDefault.reset(new HTMLCellDefault());
|
|
}
|
|
}
|
|
break;
|
|
case HtmlTokenId::COLGROUP_OFF:
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
void ImportAsHTML(SvStream& rStream, SdrTableObj& rObj)
|
|
{
|
|
SdrTableHTMLParser aParser(rObj);
|
|
aParser.Read(rStream);
|
|
}
|
|
}
|
|
|
|
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
|