blob: f98f611a4a1d2737b6c6dd6c126a439359d40137 [file] [log] [blame]
/**************************************************************
*
* 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
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*
*************************************************************/
// MARKER(update_precomp.py): autogen include statement, do not remove
#include "precompiled_sc.hxx"
#include <boost/shared_ptr.hpp>
#define SC_HTMLPARS_CXX
#include "scitems.hxx"
#include <editeng/eeitem.hxx>
#include <svtools/htmlcfg.hxx>
#include <svx/algitem.hxx>
#include <editeng/colritem.hxx>
#include <editeng/brshitem.hxx>
#include <editeng/editeng.hxx>
#include <editeng/fhgtitem.hxx>
#include <editeng/fontitem.hxx>
#include <editeng/postitem.hxx>
#include <editeng/udlnitem.hxx>
#include <editeng/wghtitem.hxx>
#include <editeng/boxitem.hxx>
#include <sfx2/objsh.hxx>
#include <svl/eitem.hxx>
#include <svtools/filter.hxx>
#include <svtools/parhtml.hxx>
#include <svtools/htmlkywd.hxx>
#include <svtools/htmltokn.h>
#include <sfx2/docfile.hxx>
#include <vcl/svapp.hxx>
#include <tools/urlobj.hxx>
#include <tools/tenccvt.hxx>
#include "htmlpars.hxx"
#include "global.hxx"
#include "document.hxx"
#include "rangelst.hxx"
#include <com/sun/star/document/XDocumentProperties.hpp>
#include <com/sun/star/document/XDocumentPropertiesSupplier.hpp>
using namespace ::com::sun::star;
SV_IMPL_VARARR_SORT( ScHTMLColOffset, sal_uLong );
// ============================================================================
// BASE class for HTML parser classes
// ============================================================================
ScHTMLParser::ScHTMLParser( EditEngine* pEditEngine, ScDocument* pDoc ) :
ScEEParser( pEditEngine ),
mpDoc( pDoc )
{
SvxHtmlOptions* pHtmlOptions = SvxHtmlOptions::Get();
for( sal_uInt16 nIndex = 0; nIndex < SC_HTML_FONTSIZES; ++nIndex )
maFontHeights[ nIndex ] = pHtmlOptions->GetFontSize( nIndex ) * 20;
}
ScHTMLParser::~ScHTMLParser()
{
}
// ============================================================================
ScHTMLLayoutParser::ScHTMLLayoutParser( EditEngine* pEditP, const String& rBaseURL, const Size& aPageSizeP, ScDocument* pDocP ) :
ScHTMLParser( pEditP, pDocP ),
aPageSize( aPageSizeP ),
aBaseURL( rBaseURL ),
xLockedList( new ScRangeList ),
pTables( NULL ),
pColOffset( new ScHTMLColOffset ),
pLocalColOffset( new ScHTMLColOffset ),
nFirstTableCell(0),
nTableLevel(0),
nTable(0),
nMaxTable(0),
nColCntStart(0),
nMaxCol(0),
nTableWidth(0),
nColOffset(0),
nColOffsetStart(0),
nMetaCnt(0),
nOffsetTolerance( SC_HTML_OFFSET_TOLERANCE_SMALL ),
bTabInTabCell( sal_False ),
bFirstRow( sal_True ),
bInCell( sal_False ),
bInTitle( sal_False )
{
MakeColNoRef( pLocalColOffset, 0, 0, 0, 0 );
MakeColNoRef( pColOffset, 0, 0, 0, 0 );
}
ScHTMLLayoutParser::~ScHTMLLayoutParser()
{
ScHTMLTableStackEntry* pS;
while ( (pS = aTableStack.Pop()) != 0 )
{
if ( pList->GetPos( pS->pCellEntry ) == LIST_ENTRY_NOTFOUND )
delete pS->pCellEntry;
if ( pS->pLocalColOffset != pLocalColOffset )
delete pS->pLocalColOffset;
delete pS;
}
if ( pLocalColOffset )
delete pLocalColOffset;
if ( pColOffset )
delete pColOffset;
if ( pTables )
{
for ( Table* pT = (Table*) pTables->First(); pT; pT = (Table*) pTables->Next() )
delete pT;
delete pTables;
}
}
sal_uLong ScHTMLLayoutParser::Read( SvStream& rStream, const String& rBaseURL )
{
Link aOldLink = pEdit->GetImportHdl();
pEdit->SetImportHdl( LINK( this, ScHTMLLayoutParser, HTMLImportHdl ) );
SfxObjectShell* pObjSh = mpDoc->GetDocumentShell();
sal_Bool bLoading = pObjSh && pObjSh->IsLoading();
SvKeyValueIteratorRef xValues;
SvKeyValueIterator* pAttributes = NULL;
if ( bLoading )
pAttributes = pObjSh->GetHeaderAttributes();
else
{
// When not loading, set up fake http headers to force the SfxHTMLParser to use UTF8
// (used when pasting from clipboard)
const sal_Char* pCharSet = rtl_getBestMimeCharsetFromTextEncoding( RTL_TEXTENCODING_UTF8 );
if( pCharSet )
{
String aContentType = String::CreateFromAscii( "text/html; charset=" );
aContentType.AppendAscii( pCharSet );
xValues = new SvKeyValueIterator;
xValues->Append( SvKeyValue( String::CreateFromAscii( OOO_STRING_SVTOOLS_HTML_META_content_type ), aContentType ) );
pAttributes = xValues;
}
}
sal_uLong nErr = pEdit->Read( rStream, rBaseURL, EE_FORMAT_HTML, pAttributes );
pEdit->SetImportHdl( aOldLink );
// Spaltenbreiten erzeugen
Adjust();
OutputDevice* pDefaultDev = Application::GetDefaultDevice();
sal_uInt16 nCount = pColOffset->Count();
const sal_uLong* pOff = (const sal_uLong*) pColOffset->GetData();
sal_uLong nOff = *pOff++;
Size aSize;
for ( sal_uInt16 j = 1; j < nCount; j++, pOff++ )
{
aSize.Width() = *pOff - nOff;
aSize = pDefaultDev->PixelToLogic( aSize, MapMode( MAP_TWIP ) );
pColWidths->Insert( j-1, (void*)aSize.Width() );
nOff = *pOff;
}
return nErr;
}
const ScHTMLTable* ScHTMLLayoutParser::GetGlobalTable() const
{
return 0;
}
void ScHTMLLayoutParser::NewActEntry( ScEEParseEntry* pE )
{
ScEEParser::NewActEntry( pE );
if ( pE )
{
if ( !pE->aSel.HasRange() )
{ // komplett leer, nachfolgender Text landet im gleichen Absatz!
pActEntry->aSel.nStartPara = pE->aSel.nEndPara;
pActEntry->aSel.nStartPos = pE->aSel.nEndPos;
}
}
pActEntry->aSel.nEndPara = pActEntry->aSel.nStartPara;
pActEntry->aSel.nEndPos = pActEntry->aSel.nStartPos;
}
void ScHTMLLayoutParser::EntryEnd( ScEEParseEntry* pE, const ESelection& rSel )
{
if ( rSel.nEndPara >= pE->aSel.nStartPara )
{
pE->aSel.nEndPara = rSel.nEndPara;
pE->aSel.nEndPos = rSel.nEndPos;
}
else if ( rSel.nStartPara == pE->aSel.nStartPara - 1 && !pE->aSel.HasRange() )
{ // kein Absatz angehaengt aber leer, nichts tun
}
else
{
DBG_ERRORFILE( "EntryEnd: EditEngine ESelection End < Start" );
}
}
void ScHTMLLayoutParser::NextRow( ImportInfo* pInfo )
{
if ( bInCell )
CloseEntry( pInfo );
if ( nRowMax < ++nRowCnt )
nRowMax = nRowCnt;
nColCnt = nColCntStart;
nColOffset = nColOffsetStart;
bFirstRow = sal_False;
}
sal_Bool ScHTMLLayoutParser::SeekOffset( ScHTMLColOffset* pOffset, sal_uInt16 nOffset,
SCCOL* pCol, sal_uInt16 nOffsetTol )
{
DBG_ASSERT( pOffset, "ScHTMLLayoutParser::SeekOffset - illegal call" );
sal_uInt16 nPos;
sal_Bool bFound = pOffset->Seek_Entry( nOffset, &nPos );
*pCol = static_cast<SCCOL>(nPos);
if ( bFound )
return sal_True;
sal_uInt16 nCount = pOffset->Count();
if ( !nCount )
return sal_False;
// nPos ist Einfuegeposition, da liegt der Naechsthoehere (oder auch nicht)
if ( nPos < nCount && (((*pOffset)[nPos] - nOffsetTol) <= nOffset) )
return sal_True;
// nicht kleiner als alles andere? dann mit Naechstniedrigerem vergleichen
else if ( nPos && (((*pOffset)[nPos-1] + nOffsetTol) >= nOffset) )
{
(*pCol)--;
return sal_True;
}
return sal_False;
}
void ScHTMLLayoutParser::MakeCol( ScHTMLColOffset* pOffset, sal_uInt16& nOffset,
sal_uInt16& nWidth, sal_uInt16 nOffsetTol, sal_uInt16 nWidthTol )
{
DBG_ASSERT( pOffset, "ScHTMLLayoutParser::MakeCol - illegal call" );
SCCOL nPos;
if ( SeekOffset( pOffset, nOffset, &nPos, nOffsetTol ) )
nOffset = (sal_uInt16)(*pOffset)[nPos];
else
pOffset->Insert( nOffset );
if ( nWidth )
{
if ( SeekOffset( pOffset, nOffset + nWidth, &nPos, nWidthTol ) )
nWidth = (sal_uInt16)(*pOffset)[nPos] - nOffset;
else
pOffset->Insert( nOffset + nWidth );
}
}
void ScHTMLLayoutParser::MakeColNoRef( ScHTMLColOffset* pOffset, sal_uInt16 nOffset,
sal_uInt16 nWidth, sal_uInt16 nOffsetTol, sal_uInt16 nWidthTol )
{
DBG_ASSERT( pOffset, "ScHTMLLayoutParser::MakeColNoRef - illegal call" );
SCCOL nPos;
if ( SeekOffset( pOffset, nOffset, &nPos, nOffsetTol ) )
nOffset = (sal_uInt16)(*pOffset)[nPos];
else
pOffset->Insert( nOffset );
if ( nWidth )
{
if ( !SeekOffset( pOffset, nOffset + nWidth, &nPos, nWidthTol ) )
pOffset->Insert( nOffset + nWidth );
}
}
void ScHTMLLayoutParser::ModifyOffset( ScHTMLColOffset* pOffset, sal_uInt16& nOldOffset,
sal_uInt16& nNewOffset, sal_uInt16 nOffsetTol )
{
DBG_ASSERT( pOffset, "ScHTMLLayoutParser::ModifyOffset - illegal call" );
SCCOL nPos;
if ( !SeekOffset( pOffset, nOldOffset, &nPos, nOffsetTol ) )
{
if ( SeekOffset( pOffset, nNewOffset, &nPos, nOffsetTol ) )
nNewOffset = (sal_uInt16)(*pOffset)[nPos];
else
pOffset->Insert( nNewOffset );
return ;
}
nOldOffset = (sal_uInt16)(*pOffset)[nPos];
SCCOL nPos2;
if ( SeekOffset( pOffset, nNewOffset, &nPos2, nOffsetTol ) )
{
nNewOffset = (sal_uInt16)(*pOffset)[nPos2];
return ;
}
sal_uLong* pData = ((sal_uLong*) pOffset->GetData()) + nPos; //! QAD
long nDiff = nNewOffset - nOldOffset;
if ( nDiff < 0 )
{
const sal_uLong* pStop = pOffset->GetData();
do
{
*pData += nDiff;
} while ( pStop < pData-- );
}
else
{
const sal_uLong* pStop = pOffset->GetData() + pOffset->Count();
do
{
*pData += nDiff;
} while ( ++pData < pStop );
}
}
void ScHTMLLayoutParser::SkipLocked( ScEEParseEntry* pE, sal_Bool bJoin )
{
if ( ValidCol(pE->nCol) )
{ // wuerde sonst bei ScAddress falschen Wert erzeugen, evtl. Endlosschleife!
sal_Bool bBadCol = sal_False;
sal_Bool bAgain;
ScRange aRange( pE->nCol, pE->nRow, 0,
pE->nCol + pE->nColOverlap - 1, pE->nRow + pE->nRowOverlap - 1, 0 );
do
{
bAgain = sal_False;
for ( ScRange* pR = xLockedList->First(); pR; pR = xLockedList->Next() )
{
if ( pR->Intersects( aRange ) )
{
pE->nCol = pR->aEnd.Col() + 1;
SCCOL nTmp = pE->nCol + pE->nColOverlap - 1;
if ( pE->nCol > MAXCOL || nTmp > MAXCOL )
bBadCol = sal_True;
else
{
bAgain = sal_True;
aRange.aStart.SetCol( pE->nCol );
aRange.aEnd.SetCol( nTmp );
}
break;
}
}
} while ( bAgain );
if ( bJoin && !bBadCol )
xLockedList->Join( aRange );
}
}
void ScHTMLLayoutParser::Adjust()
{
for ( ScRange* pR = xLockedList->First(); pR; pR = xLockedList->Next() )
delete pR;
xLockedList->Clear();
ScHTMLAdjustStack aStack;
ScHTMLAdjustStackEntry* pS;
sal_uInt16 nTab = 0;
SCCOL nLastCol = SCCOL_MAX;
SCROW nNextRow = 0;
SCROW nCurRow = 0;
sal_uInt16 nPageWidth = (sal_uInt16) aPageSize.Width();
Table* pTab = NULL;
for ( ScEEParseEntry* pE = pList->First(); pE; pE = pList->Next() )
{
if ( pE->nTab < nTab )
{ // Table beendet
if ( (pS = aStack.Pop()) != 0 )
{
nLastCol = pS->nLastCol;
nNextRow = pS->nNextRow;
nCurRow = pS->nCurRow;
}
delete pS;
nTab = pE->nTab;
pTab = (pTables ? (Table*) pTables->Get( nTab ) : NULL);
}
SCROW nRow = pE->nRow;
if ( pE->nCol <= nLastCol )
{ // naechste Zeile
if ( pE->nRow < nNextRow )
pE->nRow = nCurRow = nNextRow;
else
nCurRow = nNextRow = pE->nRow;
SCROW nR;
if ( pTab && ((nR = (SCROW)(sal_uLong)pTab->Get( nCurRow )) != 0) )
nNextRow += nR;
else
nNextRow++;
}
else
pE->nRow = nCurRow;
nLastCol = pE->nCol; // eingelesene Col
if ( pE->nTab > nTab )
{ // neue Table
aStack.Push( new ScHTMLAdjustStackEntry(
nLastCol, nNextRow, nCurRow ) );
nTab = pE->nTab;
pTab = (pTables ? (Table*) pTables->Get( nTab ) : NULL);
// neuer Zeilenabstand
SCROW nR;
if ( pTab && ((nR = (SCROW)(sal_uLong)pTab->Get( nCurRow )) != 0) )
nNextRow = nCurRow + nR;
else
nNextRow = nCurRow + 1;
}
if ( nTab == 0 )
pE->nWidth = nPageWidth;
else
{ // echte Table, keine Absaetze auf der Wiese
if ( pTab )
{
SCROW nRowSpan = pE->nRowOverlap;
for ( SCROW j=0; j < nRowSpan; j++ )
{ // aus merged Zeilen resultierendes RowSpan
SCROW nRows = (SCROW)(sal_uLong)pTab->Get( nRow+j );
if ( nRows > 1 )
{
pE->nRowOverlap += nRows - 1;
if ( j == 0 )
{ // merged Zeilen verschieben die naechste Zeile
SCROW nTmp = nCurRow + nRows;
if ( nNextRow < nTmp )
nNextRow = nTmp;
}
}
}
}
}
// echte Col
SeekOffset( pColOffset, pE->nOffset, &pE->nCol, nOffsetTolerance );
SCCOL nColBeforeSkip = pE->nCol;
SkipLocked( pE, sal_False );
if ( pE->nCol != nColBeforeSkip )
{
SCCOL nCount = (SCCOL)pColOffset->Count();
if ( nCount <= pE->nCol )
{
pE->nOffset = (sal_uInt16) (*pColOffset)[nCount-1];
MakeCol( pColOffset, pE->nOffset, pE->nWidth, nOffsetTolerance, nOffsetTolerance );
}
else
{
pE->nOffset = (sal_uInt16) (*pColOffset)[pE->nCol];
}
}
SCCOL nPos;
if ( pE->nWidth && SeekOffset( pColOffset, pE->nOffset + pE->nWidth, &nPos, nOffsetTolerance ) )
pE->nColOverlap = (nPos > pE->nCol ? nPos - pE->nCol : 1);
else
{
//2do: das muss nicht korrekt sein, ist aber..
pE->nColOverlap = 1;
}
xLockedList->Join( ScRange( pE->nCol, pE->nRow, 0,
pE->nCol + pE->nColOverlap - 1, pE->nRow + pE->nRowOverlap - 1, 0 ) );
// MaxDimensions mitfuehren
SCCOL nColTmp = pE->nCol + pE->nColOverlap;
if ( nColMax < nColTmp )
nColMax = nColTmp;
SCROW nRowTmp = pE->nRow + pE->nRowOverlap;
if ( nRowMax < nRowTmp )
nRowMax = nRowTmp;
}
while ( (pS = aStack.Pop()) != 0 )
delete pS;
}
sal_uInt16 ScHTMLLayoutParser::GetWidth( ScEEParseEntry* pE )
{
if ( pE->nWidth )
return pE->nWidth;
sal_Int32 nTmp = ::std::min( static_cast<sal_Int32>( pE->nCol -
nColCntStart + pE->nColOverlap),
static_cast<sal_Int32>( pLocalColOffset->Count() - 1));
SCCOL nPos = (nTmp < 0 ? 0 : static_cast<SCCOL>(nTmp));
sal_uInt16 nOff2 = (sal_uInt16) (*pLocalColOffset)[nPos];
if ( pE->nOffset < nOff2 )
return nOff2 - pE->nOffset;
return 0;
}
void ScHTMLLayoutParser::SetWidths()
{
ScEEParseEntry* pE;
SCCOL nCol;
if ( !nTableWidth )
nTableWidth = (sal_uInt16) aPageSize.Width();
SCCOL nColsPerRow = nMaxCol - nColCntStart;
if ( nColsPerRow <= 0 )
nColsPerRow = 1;
if ( pLocalColOffset->Count() <= 2 )
{ // nur PageSize, es gab keine Width-Angabe
sal_uInt16 nWidth = nTableWidth / static_cast<sal_uInt16>(nColsPerRow);
sal_uInt16 nOff = nColOffsetStart;
pLocalColOffset->Remove( (sal_uInt16)0, pLocalColOffset->Count() );
for ( nCol = 0; nCol <= nColsPerRow; ++nCol, nOff = nOff + nWidth )
{
MakeColNoRef( pLocalColOffset, nOff, 0, 0, 0 );
}
nTableWidth = (sal_uInt16)((*pLocalColOffset)[pLocalColOffset->Count() -1 ] - (*pLocalColOffset)[0]);
pE = pList->Seek( nFirstTableCell );
while ( pE )
{
if ( pE->nTab == nTable )
{
pE->nOffset = (sal_uInt16) (*pLocalColOffset)[pE->nCol - nColCntStart];
pE->nWidth = 0; // to be recalculated later
}
pE = pList->Next();
}
}
else
{ // einige mit einige ohne Width
pE = pList->Seek( nFirstTableCell );
// #36350# wieso eigentlich kein pE ?!?
if ( pE )
{
sal_uInt16* pOffsets = new sal_uInt16[ nColsPerRow+1 ];
memset( pOffsets, 0, (nColsPerRow+1) * sizeof(sal_uInt16) );
sal_uInt16* pWidths = new sal_uInt16[ nColsPerRow ];
memset( pWidths, 0, nColsPerRow * sizeof(sal_uInt16) );
pOffsets[0] = nColOffsetStart;
while ( pE )
{
if ( pE->nTab == nTable && pE->nWidth )
{
nCol = pE->nCol - nColCntStart;
if ( nCol < nColsPerRow )
{
if ( pE->nColOverlap == 1 )
{
if ( pWidths[nCol] < pE->nWidth )
pWidths[nCol] = pE->nWidth;
}
else
{ // try to find a single undefined width
sal_uInt16 nTotal = 0;
sal_Bool bFound = sal_False;
SCCOL nHere = 0;
SCCOL nStop = Min( static_cast<SCCOL>(nCol + pE->nColOverlap), nColsPerRow );
for ( ; nCol < nStop; nCol++ )
{
if ( pWidths[nCol] )
nTotal = nTotal + pWidths[nCol];
else
{
if ( bFound )
{
bFound = sal_False;
break; // for
}
bFound = sal_True;
nHere = nCol;
}
}
if ( bFound && pE->nWidth > nTotal )
pWidths[nHere] = pE->nWidth - nTotal;
}
}
}
pE = pList->Next();
}
sal_uInt16 nWidths = 0;
sal_uInt16 nUnknown = 0;
for ( nCol = 0; nCol < nColsPerRow; nCol++ )
{
if ( pWidths[nCol] )
nWidths = nWidths + pWidths[nCol];
else
nUnknown++;
}
if ( nUnknown )
{
sal_uInt16 nW = ((nWidths < nTableWidth) ?
((nTableWidth - nWidths) / nUnknown) :
(nTableWidth / nUnknown));
for ( nCol = 0; nCol < nColsPerRow; nCol++ )
{
if ( !pWidths[nCol] )
pWidths[nCol] = nW;
}
}
for ( nCol = 1; nCol <= nColsPerRow; nCol++ )
{
pOffsets[nCol] = pOffsets[nCol-1] + pWidths[nCol-1];
}
pLocalColOffset->Remove( (sal_uInt16)0, pLocalColOffset->Count() );
for ( nCol = 0; nCol <= nColsPerRow; nCol++ )
{
MakeColNoRef( pLocalColOffset, pOffsets[nCol], 0, 0, 0 );
}
nTableWidth = pOffsets[nColsPerRow] - pOffsets[0];
pE = pList->Seek( nFirstTableCell );
while ( pE )
{
if ( pE->nTab == nTable )
{
nCol = pE->nCol - nColCntStart;
DBG_ASSERT( nCol < nColsPerRow, "ScHTMLLayoutParser::SetWidths: column overflow" );
if ( nCol < nColsPerRow )
{
pE->nOffset = pOffsets[nCol];
nCol = nCol + pE->nColOverlap;
if ( nCol > nColsPerRow )
nCol = nColsPerRow;
pE->nWidth = pOffsets[nCol] - pE->nOffset;
}
}
pE = pList->Next();
}
delete [] pWidths;
delete [] pOffsets;
}
}
if ( pLocalColOffset->Count() )
{
sal_uInt16 nMax = (sal_uInt16) (*pLocalColOffset)[pLocalColOffset->Count() - 1];
if ( aPageSize.Width() < nMax )
aPageSize.Width() = nMax;
}
pE = pList->Seek( nFirstTableCell );
while ( pE )
{
if ( pE->nTab == nTable )
{
if ( !pE->nWidth )
{
pE->nWidth = GetWidth( pE );
DBG_ASSERT( pE->nWidth, "SetWidths: pE->nWidth == 0" );
}
MakeCol( pColOffset, pE->nOffset, pE->nWidth, nOffsetTolerance, nOffsetTolerance );
}
pE = pList->Next();
}
}
void ScHTMLLayoutParser::Colonize( ScEEParseEntry* pE )
{
if ( pE->nCol == SCCOL_MAX )
pE->nCol = nColCnt;
if ( pE->nRow == SCROW_MAX )
pE->nRow = nRowCnt;
SCCOL nCol = pE->nCol;
SkipLocked( pE ); // Spaltenverdraengung nach rechts
if ( nCol < pE->nCol )
{ // verdraengt
nCol = pE->nCol - nColCntStart;
SCCOL nCount = static_cast<SCCOL>(pLocalColOffset->Count());
if ( nCol < nCount )
nColOffset = (sal_uInt16) (*pLocalColOffset)[nCol];
else
nColOffset = (sal_uInt16) (*pLocalColOffset)[nCount - 1];
}
pE->nOffset = nColOffset;
sal_uInt16 nWidth = GetWidth( pE );
MakeCol( pLocalColOffset, pE->nOffset, nWidth, nOffsetTolerance, nOffsetTolerance );
if ( pE->nWidth )
pE->nWidth = nWidth;
nColOffset = pE->nOffset + nWidth;
if ( nTableWidth < nColOffset - nColOffsetStart )
nTableWidth = nColOffset - nColOffsetStart;
}
void ScHTMLLayoutParser::CloseEntry( ImportInfo* pInfo )
{
bInCell = sal_False;
if ( bTabInTabCell )
{ // in TableOff vom Stack geholt
bTabInTabCell = sal_False;
if ( pList->GetPos( pActEntry ) == LIST_ENTRY_NOTFOUND )
delete pActEntry;
NewActEntry( pList->Last() ); // neuer freifliegender pActEntry
return ;
}
if ( pActEntry->nTab == 0 )
pActEntry->nWidth = (sal_uInt16) aPageSize.Width();
Colonize( pActEntry );
nColCnt = pActEntry->nCol + pActEntry->nColOverlap;
if ( nMaxCol < nColCnt )
nMaxCol = nColCnt; // TableStack MaxCol
if ( nColMax < nColCnt )
nColMax = nColCnt; // globales MaxCol fuer ScEEParser GetDimensions!
EntryEnd( pActEntry, pInfo->aSelection );
ESelection& rSel = pActEntry->aSel;
while ( rSel.nStartPara < rSel.nEndPara
&& pEdit->GetTextLen( rSel.nStartPara ) == 0 )
{ // vorgehaengte Leerabsaetze strippen
rSel.nStartPara++;
}
while ( rSel.nEndPos == 0 && rSel.nEndPara > rSel.nStartPara )
{ // angehaengte Leerabsaetze strippen
rSel.nEndPara--;
rSel.nEndPos = pEdit->GetTextLen( rSel.nEndPara );
}
if ( rSel.nStartPara > rSel.nEndPara )
{ // gibt GPF in CreateTextObject
DBG_ERRORFILE( "CloseEntry: EditEngine ESelection Start > End" );
rSel.nEndPara = rSel.nStartPara;
}
if ( rSel.HasRange() )
pActEntry->aItemSet.Put( SfxBoolItem( ATTR_LINEBREAK, sal_True ) );
pList->Insert( pActEntry, LIST_APPEND );
NewActEntry( pActEntry ); // neuer freifliegender pActEntry
}
IMPL_LINK( ScHTMLLayoutParser, HTMLImportHdl, ImportInfo*, pInfo )
{
#if defined(erDEBUG) //|| 1
static ESelection aDebugSel;
static String aDebugStr;
static SvFileStream* pDebugStrm = NULL;
static sal_uLong nDebugStrmPos = 0;
static sal_uLong nDebugCount = 0;
static sal_uLong nDebugCountAll = 0;
static const sal_Char* sDebugState[15] = {
"RTFIMP_START", "RTFIMP_END",
"RTFIMP_NEXTTOKEN", "RTFIMP_UNKNOWNATTR",
"RTFIMP_SETATTR",
"RTFIMP_INSERTTEXT",
"RTFIMP_INSERTPARA",
"HTMLIMP_START", "HTMLIMP_END",
"HTMLIMP_NEXTTOKEN", "HTMLIMP_UNKNOWNATTR",
"HTMLIMP_SETATTR",
"HTMLIMP_INSERTTEXT",
"HTMLIMP_INSERTPARA", "HTMLIMP_INSERTFIELD"
};
nDebugCountAll++;
if ( pInfo->eState != HTMLIMP_NEXTTOKEN // not too much
|| pInfo->nToken == HTML_TABLE_ON
|| pInfo->nToken == HTML_TABLE_OFF
|| pInfo->nToken == HTML_TABLEROW_ON
|| pInfo->nToken == HTML_TABLEROW_OFF
|| pInfo->nToken == HTML_TABLEHEADER_ON
|| pInfo->nToken == HTML_TABLEHEADER_OFF
|| pInfo->nToken == HTML_TABLEDATA_ON
|| pInfo->nToken == HTML_TABLEDATA_OFF
|| !aDebugSel.IsEqual( pInfo->aSelection )
|| pInfo->aText.Len() || aDebugStr != pInfo->aText
)
{
aDebugSel = pInfo->aSelection;
aDebugStr = pInfo->aText;
nDebugCount++;
if ( !pDebugStrm )
{
pDebugStrm = new SvFileStream( "d:\\erdbghtm.log",
STREAM_WRITE | STREAM_TRUNC );
}
else
{
pDebugStrm->ReOpen();
pDebugStrm->Seek( nDebugStrmPos );
}
SvFileStream& rS = *pDebugStrm;
rS.WriteNumber( nDebugCountAll ); rS << ".: ";
rS.WriteNumber( nDebugCount ); rS << ". State: ";
rS.WriteNumber( (sal_uInt16) pInfo->eState );
rS << ' ' << sDebugState[pInfo->eState] << endl;
rS << "SPar,SPos EPar,EPos: ";
rS.WriteNumber( aDebugSel.nStartPara ); rS << ',';
rS.WriteNumber( aDebugSel.nStartPos ); rS << ' ';
rS.WriteNumber( aDebugSel.nEndPara ); rS << ',';
rS.WriteNumber( aDebugSel.nEndPos ); rS << endl;
if ( aDebugStr.Len() )
{
rS << "Text: \"" << aDebugStr << '\"' << endl;
}
else
{
rS << "Text:" << endl;
}
rS << "Token: "; rS.WriteNumber( pInfo->nToken );
switch ( pInfo->nToken )
{
case HTML_TABLE_ON:
rS << " HTML_TABLE_ON";
break;
case HTML_TABLE_OFF:
rS << " HTML_TABLE_OFF";
break;
case HTML_TABLEROW_ON:
rS << " HTML_TABLEROW_ON";
break;
case HTML_TABLEROW_OFF:
rS << " HTML_TABLEROW_OFF";
break;
case HTML_TABLEHEADER_ON:
rS << " HTML_TABLEHEADER_ON";
break;
case HTML_TABLEHEADER_OFF:
rS << " HTML_TABLEHEADER_OFF";
break;
case HTML_TABLEDATA_ON:
rS << " HTML_TABLEDATA_ON";
break;
case HTML_TABLEDATA_OFF:
rS << " HTML_TABLEDATA_OFF";
break;
}
rS << " Value: "; rS.WriteNumber( pInfo->nTokenValue );
rS << endl << endl;
nDebugStrmPos = pDebugStrm->Tell();
pDebugStrm->Close();
}
#endif
switch ( pInfo->eState )
{
case HTMLIMP_NEXTTOKEN:
ProcToken( pInfo );
break;
case HTMLIMP_UNKNOWNATTR:
ProcToken( pInfo );
break;
case HTMLIMP_START:
break;
case HTMLIMP_END:
if ( pInfo->aSelection.nEndPos )
{
// If text remains: create paragraph, without calling CloseEntry().
if( bInCell ) // #108269# ...but only in opened table cells.
{
bInCell = sal_False;
NextRow( pInfo );
bInCell = sal_True;
}
CloseEntry( pInfo );
}
while ( nTableLevel > 0 )
TableOff( pInfo ); // close tables, if </TABLE> missing
break;
case HTMLIMP_SETATTR:
break;
case HTMLIMP_INSERTTEXT:
break;
case HTMLIMP_INSERTPARA:
if ( nTableLevel < 1 )
{
CloseEntry( pInfo );
NextRow( pInfo );
}
break;
case HTMLIMP_INSERTFIELD:
break;
default:
DBG_ERRORFILE("HTMLImportHdl: unknown ImportInfo.eState");
}
return 0;
}
// Groesster Gemeinsamer Teiler nach Euklid (Kettendivision)
// Sonderfall: 0 und irgendwas geben 1
SCROW lcl_GGT( SCROW a, SCROW b )
{
if ( !a || !b )
return 1;
do
{
if ( a > b )
a -= SCROW(a / b) * b;
else
b -= SCROW(b / a) * a;
} while ( a && b );
return ((a != 0) ? a : b);
}
// Kleinstes Gemeinsames Vielfaches: a * b / GGT(a,b)
SCROW lcl_KGV( SCROW a, SCROW b )
{
if ( a > b ) // Ueberlauf unwahrscheinlicher machen
return (a / lcl_GGT(a,b)) * b;
else
return (b / lcl_GGT(a,b)) * a;
}
void ScHTMLLayoutParser::TableDataOn( ImportInfo* pInfo )
{
if ( bInCell )
CloseEntry( pInfo );
if ( !nTableLevel )
{
DBG_ERROR( "Dummbatz-Dok! <TH> oder <TD> ohne vorheriges <TABLE>" );
TableOn( pInfo );
}
bInCell = sal_True;
sal_Bool bHorJustifyCenterTH = (pInfo->nToken == HTML_TABLEHEADER_ON);
const HTMLOptions* pOptions = ((HTMLParser*)pInfo->pParser)->GetOptions();
sal_uInt16 nArrLen = pOptions->Count();
for ( sal_uInt16 i = 0; i < nArrLen; i++ )
{
const HTMLOption* pOption = (*pOptions)[i];
switch( pOption->GetToken() )
{
case HTML_O_COLSPAN:
{
pActEntry->nColOverlap = ( SCCOL ) pOption->GetString().ToInt32();
}
break;
case HTML_O_ROWSPAN:
{
pActEntry->nRowOverlap = ( SCROW ) pOption->GetString().ToInt32();
}
break;
case HTML_O_ALIGN:
{
bHorJustifyCenterTH = sal_False;
SvxCellHorJustify eVal;
const String& rOptVal = pOption->GetString();
if ( rOptVal.CompareIgnoreCaseToAscii( OOO_STRING_SVTOOLS_HTML_AL_right ) == COMPARE_EQUAL )
eVal = SVX_HOR_JUSTIFY_RIGHT;
else if ( rOptVal.CompareIgnoreCaseToAscii( OOO_STRING_SVTOOLS_HTML_AL_center ) == COMPARE_EQUAL )
eVal = SVX_HOR_JUSTIFY_CENTER;
else if ( rOptVal.CompareIgnoreCaseToAscii( OOO_STRING_SVTOOLS_HTML_AL_left ) == COMPARE_EQUAL )
eVal = SVX_HOR_JUSTIFY_LEFT;
else
eVal = SVX_HOR_JUSTIFY_STANDARD;
if ( eVal != SVX_HOR_JUSTIFY_STANDARD )
pActEntry->aItemSet.Put( SvxHorJustifyItem( eVal, ATTR_HOR_JUSTIFY) );
}
break;
case HTML_O_VALIGN:
{
SvxCellVerJustify eVal;
const String& rOptVal = pOption->GetString();
if ( rOptVal.CompareIgnoreCaseToAscii( OOO_STRING_SVTOOLS_HTML_VA_top ) == COMPARE_EQUAL )
eVal = SVX_VER_JUSTIFY_TOP;
else if ( rOptVal.CompareIgnoreCaseToAscii( OOO_STRING_SVTOOLS_HTML_VA_middle ) == COMPARE_EQUAL )
eVal = SVX_VER_JUSTIFY_CENTER;
else if ( rOptVal.CompareIgnoreCaseToAscii( OOO_STRING_SVTOOLS_HTML_VA_bottom ) == COMPARE_EQUAL )
eVal = SVX_VER_JUSTIFY_BOTTOM;
else
eVal = SVX_VER_JUSTIFY_STANDARD;
pActEntry->aItemSet.Put( SvxVerJustifyItem( eVal, ATTR_VER_JUSTIFY) );
}
break;
case HTML_O_WIDTH:
{
pActEntry->nWidth = GetWidthPixel( pOption );
}
break;
case HTML_O_BGCOLOR:
{
Color aColor;
pOption->GetColor( aColor );
pActEntry->aItemSet.Put(
SvxBrushItem( aColor, ATTR_BACKGROUND ) );
}
break;
case HTML_O_SDVAL:
{
pActEntry->pValStr = new String( pOption->GetString() );
}
break;
case HTML_O_SDNUM:
{
pActEntry->pNumStr = new String( pOption->GetString() );
}
break;
}
}
pActEntry->nCol = nColCnt;
pActEntry->nRow = nRowCnt;
pActEntry->nTab = nTable;
if ( bHorJustifyCenterTH )
pActEntry->aItemSet.Put(
SvxHorJustifyItem( SVX_HOR_JUSTIFY_CENTER, ATTR_HOR_JUSTIFY) );
}
void ScHTMLLayoutParser::TableRowOn( ImportInfo* pInfo )
{
if ( nColCnt > nColCntStart )
NextRow( pInfo ); // das optionale TableRowOff war nicht
nColOffset = nColOffsetStart;
}
void ScHTMLLayoutParser::TableRowOff( ImportInfo* pInfo )
{
NextRow( pInfo );
}
void ScHTMLLayoutParser::TableDataOff( ImportInfo* pInfo )
{
if ( bInCell )
CloseEntry( pInfo ); // aber nur wenn's auch eine war
}
void ScHTMLLayoutParser::TableOn( ImportInfo* pInfo )
{
String aTabName;
bool bBorderOn = false;
if ( ++nTableLevel > 1 )
{ // Table in Table
sal_uInt16 nTmpColOffset = nColOffset; // wird in Colonize noch angepasst
Colonize( pActEntry );
aTableStack.Push( new ScHTMLTableStackEntry(
pActEntry, xLockedList, pLocalColOffset, nFirstTableCell,
nColCnt, nRowCnt, nColCntStart, nMaxCol, nTable,
nTableWidth, nColOffset, nColOffsetStart,
bFirstRow ) );
sal_uInt16 nLastWidth = nTableWidth;
nTableWidth = GetWidth( pActEntry );
if ( nTableWidth == nLastWidth && nMaxCol - nColCntStart > 1 )
{ // es muss mehr als einen geben, also kann dieser nicht alles sein
nTableWidth = nLastWidth / static_cast<sal_uInt16>((nMaxCol - nColCntStart));
}
nLastWidth = nTableWidth;
if ( pInfo->nToken == HTML_TABLE_ON )
{ // es kann auch TD oder TH sein, wenn es vorher kein TABLE gab
const HTMLOptions* pOptions = ((HTMLParser*)pInfo->pParser)->GetOptions();
sal_uInt16 nArrLen = pOptions->Count();
for ( sal_uInt16 i = 0; i < nArrLen; i++ )
{
const HTMLOption* pOption = (*pOptions)[i];
switch( pOption->GetToken() )
{
case HTML_O_WIDTH:
{ // Prozent: von Dokumentbreite bzw. aeusserer Zelle
nTableWidth = GetWidthPixel( pOption );
}
break;
case HTML_O_BORDER:
bBorderOn = ((pOption->GetString().Len() == 0) || (pOption->GetNumber() != 0));
break;
case HTML_O_ID:
aTabName.Assign( pOption->GetString() );
break;
}
}
}
bInCell = sal_False;
if ( bTabInTabCell && !(nTableWidth < nLastWidth) )
{ // mehrere Tabellen in einer Zelle, untereinander
bTabInTabCell = sal_False;
NextRow( pInfo );
}
else
{ // in dieser Zelle geht's los, oder nebeneinander
bTabInTabCell = sal_False;
nColCntStart = nColCnt;
nColOffset = nTmpColOffset;
nColOffsetStart = nColOffset;
}
ScEEParseEntry* pE = pList->Last();
NewActEntry( pE ); // neuer freifliegender pActEntry
xLockedList = new ScRangeList;
}
else
{ // einfache Table auf Dokumentebene
EntryEnd( pActEntry, pInfo->aSelection );
if ( pActEntry->aSel.HasRange() )
{ // noch fliegender Text
CloseEntry( pInfo );
NextRow( pInfo );
}
aTableStack.Push( new ScHTMLTableStackEntry(
pActEntry, xLockedList, pLocalColOffset, nFirstTableCell,
nColCnt, nRowCnt, nColCntStart, nMaxCol, nTable,
nTableWidth, nColOffset, nColOffsetStart,
bFirstRow ) );
// As soon as we have multiple tables we need to be tolerant with the offsets.
if (nMaxTable > 0)
nOffsetTolerance = SC_HTML_OFFSET_TOLERANCE_LARGE;
nTableWidth = 0;
if ( pInfo->nToken == HTML_TABLE_ON )
{ // es kann auch TD oder TH sein, wenn es vorher kein TABLE gab
const HTMLOptions* pOptions = ((HTMLParser*)pInfo->pParser)->GetOptions();
sal_uInt16 nArrLen = pOptions->Count();
for ( sal_uInt16 i = 0; i < nArrLen; i++ )
{
const HTMLOption* pOption = (*pOptions)[i];
switch( pOption->GetToken() )
{
case HTML_O_WIDTH:
{ // Prozent: von Dokumentbreite bzw. aeusserer Zelle
nTableWidth = GetWidthPixel( pOption );
}
break;
case HTML_O_BORDER:
bBorderOn = ((pOption->GetString().Len() == 0) || (pOption->GetNumber() != 0));
break;
case HTML_O_ID:
aTabName.Assign( pOption->GetString() );
break;
}
}
}
}
nTable = ++nMaxTable;
bFirstRow = sal_True;
nFirstTableCell = pList->Count();
pLocalColOffset = new ScHTMLColOffset;
MakeColNoRef( pLocalColOffset, nColOffsetStart, 0, 0, 0 );
}
void ScHTMLLayoutParser::TableOff( ImportInfo* pInfo )
{
if ( bInCell )
CloseEntry( pInfo );
if ( nColCnt > nColCntStart )
TableRowOff( pInfo ); // das optionale TableRowOff war nicht
if ( !nTableLevel )
{
DBG_ERROR( "Dummbatz-Dok! </TABLE> ohne oeffnendes <TABLE>" );
return ;
}
if ( --nTableLevel > 0 )
{ // Table in Table beendet
ScHTMLTableStackEntry* pS = aTableStack.Pop();
if ( pS )
{
ScEEParseEntry* pE = pS->pCellEntry;
SCROW nRows = nRowCnt - pS->nRowCnt;
if ( nRows > 1 )
{ // Groesse der Tabelle an dieser Position eintragen
SCROW nRow = pS->nRowCnt;
sal_uInt16 nTab = pS->nTable;
if ( !pTables )
pTables = new Table;
// Hoehen der aeusseren Table
Table* pTab1 = (Table*) pTables->Get( nTab );
if ( !pTab1 )
{
pTab1 = new Table;
pTables->Insert( nTab, pTab1 );
}
SCROW nRowSpan = pE->nRowOverlap;
SCROW nRowKGV;
SCROW nRowsPerRow1; // aeussere Table
SCROW nRowsPerRow2; // innere Table
if ( nRowSpan > 1 )
{ // KGV auf das sich aussere und innere Zeilen
// abbilden lassen
nRowKGV = lcl_KGV( nRowSpan, nRows );
nRowsPerRow1 = nRowKGV / nRowSpan;
nRowsPerRow2 = nRowKGV / nRows;
}
else
{
nRowKGV = nRowsPerRow1 = nRows;
nRowsPerRow2 = 1;
}
Table* pTab2 = NULL;
if ( nRowsPerRow2 > 1 )
{ // Hoehen der inneren Table
pTab2 = new Table;
pTables->Insert( nTable, pTab2 );
}
// void* Data-Entry der Table-Class fuer das
// Hoehen-Mapping missbrauchen
if ( nRowKGV > 1 )
{
if ( nRowsPerRow1 > 1 )
{ // aussen
for ( SCROW j=0; j < nRowSpan; j++ )
{
sal_uLong nRowKey = nRow + j;
SCROW nR = (SCROW)(sal_uLong)pTab1->Get( nRowKey );
if ( !nR )
pTab1->Insert( nRowKey, (void*) nRowsPerRow1 );
else if ( nRowsPerRow1 > nR )
pTab1->Replace( nRowKey, (void*) nRowsPerRow1 );
//2do: wie geht das noch besser?
else if ( nRowsPerRow1 < nR && nRowSpan == 1
&& nTable == nMaxTable )
{ // Platz uebrig, evtl. besser mergen
SCROW nAdd = nRowsPerRow1 - (nR % nRowsPerRow1);
nR += nAdd;
if ( (nR % nRows) == 0 )
{ // nur wenn abbildbar
SCROW nR2 = (SCROW)(sal_uLong)pTab1->Get( nRowKey+1 );
if ( nR2 > nAdd )
{ // nur wenn wirklich Platz
pTab1->Replace( nRowKey, (void*) nR );
pTab1->Replace( nRowKey+1, (void*) (nR2 - nAdd) );
nRowsPerRow2 = nR / nRows;
}
}
}
}
}
if ( nRowsPerRow2 > 1 )
{ // innen
if ( !pTab2 )
{ // nRowsPerRow2 kann erhoeht worden sein
pTab2 = new Table;
pTables->Insert( nTable, pTab2 );
}
for ( SCROW j=0; j < nRows; j++ )
{
sal_uLong nRowKey = nRow + j;
SCROW nR = (SCROW)(sal_uLong)pTab2->Get( nRowKey );
if ( !nR )
pTab2->Insert( nRowKey, (void*) nRowsPerRow2 );
else if ( nRowsPerRow2 > nR )
pTab2->Replace( nRowKey, (void*) nRowsPerRow2 );
}
}
}
}
SetWidths();
if ( !pE->nWidth )
pE->nWidth = nTableWidth;
else if ( pE->nWidth < nTableWidth )
{
sal_uInt16 nOldOffset = pE->nOffset + pE->nWidth;
sal_uInt16 nNewOffset = pE->nOffset + nTableWidth;
ModifyOffset( pS->pLocalColOffset, nOldOffset, nNewOffset, nOffsetTolerance );
sal_uInt16 nTmp = nNewOffset - pE->nOffset - pE->nWidth;
pE->nWidth = nNewOffset - pE->nOffset;
pS->nTableWidth = pS->nTableWidth + nTmp;
if ( pS->nColOffset >= nOldOffset )
pS->nColOffset = pS->nColOffset + nTmp;
}
nColCnt = pE->nCol + pE->nColOverlap;
nRowCnt = pS->nRowCnt;
nColCntStart = pS->nColCntStart;
nMaxCol = pS->nMaxCol;
nTable = pS->nTable;
nTableWidth = pS->nTableWidth;
nFirstTableCell = pS->nFirstTableCell;
nColOffset = pS->nColOffset;
nColOffsetStart = pS->nColOffsetStart;
bFirstRow = pS->bFirstRow;
xLockedList = pS->xLockedList;
if ( pLocalColOffset )
delete pLocalColOffset;
pLocalColOffset = pS->pLocalColOffset;
delete pActEntry;
// pActEntry bleibt erstmal erhalten falls da noch 'ne Table in
// der gleichen Zelle aufgemacht werden sollte (in HTML ist ja
// alles moeglich..) und wird in CloseEntry deleted
pActEntry = pE;
delete pS;
}
bTabInTabCell = sal_True;
bInCell = sal_True;
}
else
{ // einfache Table beendet
SetWidths();
ScHTMLTableStackEntry* pS = aTableStack.Pop();
nMaxCol = 0;
nTable = 0;
if ( pS )
{
if ( pLocalColOffset )
delete pLocalColOffset;
pLocalColOffset = pS->pLocalColOffset;
delete pS;
}
}
}
void ScHTMLLayoutParser::Image( ImportInfo* pInfo )
{
if ( !pActEntry->pImageList )
pActEntry->pImageList = new ScHTMLImageList;
ScHTMLImageList* pIL = pActEntry->pImageList;
ScHTMLImage* pImage = new ScHTMLImage;
pIL->Insert( pImage, LIST_APPEND );
const HTMLOptions* pOptions = ((HTMLParser*)pInfo->pParser)->GetOptions();
sal_uInt16 nArrLen = pOptions->Count();
for ( sal_uInt16 i = 0; i < nArrLen; i++ )
{
const HTMLOption* pOption = (*pOptions)[i];
switch( pOption->GetToken() )
{
case HTML_O_SRC:
{
pImage->aURL = INetURLObject::GetAbsURL( aBaseURL, pOption->GetString() );
}
break;
case HTML_O_ALT:
{
if ( !pActEntry->bHasGraphic )
{ // ALT text only if not any image loaded
if ( pActEntry->aAltText.Len() )
pActEntry->aAltText.AppendAscii( "; " );
pActEntry->aAltText += pOption->GetString();
}
}
break;
case HTML_O_WIDTH:
{
pImage->aSize.Width() = (long)pOption->GetNumber();
}
break;
case HTML_O_HEIGHT:
{
pImage->aSize.Height() = (long)pOption->GetNumber();
}
break;
case HTML_O_HSPACE:
{
pImage->aSpace.X() = (long)pOption->GetNumber();
}
break;
case HTML_O_VSPACE:
{
pImage->aSpace.Y() = (long)pOption->GetNumber();
}
break;
}
}
if ( !pImage->aURL.Len() )
{
DBG_ERRORFILE( "Image: Grafik ohne URL ?!?" );
return ;
}
sal_uInt16 nFormat;
Graphic* pGraphic = new Graphic;
GraphicFilter* pFilter = GraphicFilter::GetGraphicFilter();
if ( GRFILTER_OK != GraphicFilter::LoadGraphic( pImage->aURL, pImage->aFilterName,
*pGraphic, pFilter, &nFormat ) )
{
delete pGraphic;
return ; // dumm gelaufen
}
if ( !pActEntry->bHasGraphic )
{ // discard any ALT text in this cell if we have any image
pActEntry->bHasGraphic = sal_True;
pActEntry->aAltText.Erase();
}
pImage->aFilterName = pFilter->GetImportFormatName( nFormat );
pImage->pGraphic = pGraphic;
if ( !(pImage->aSize.Width() && pImage->aSize.Height()) )
{
OutputDevice* pDefaultDev = Application::GetDefaultDevice();
pImage->aSize = pDefaultDev->LogicToPixel( pGraphic->GetPrefSize(),
pGraphic->GetPrefMapMode() );
}
if ( pIL->Count() > 0 )
{
long nWidth = 0;
for ( ScHTMLImage* pI = pIL->First(); pI; pI = pIL->Next() )
{
if ( pI->nDir & nHorizontal )
nWidth += pI->aSize.Width() + 2 * pI->aSpace.X();
else
nWidth = 0;
}
if ( pActEntry->nWidth
&& (nWidth + pImage->aSize.Width() + 2 * pImage->aSpace.X()
>= pActEntry->nWidth) )
pIL->Last()->nDir = nVertical;
}
}
void ScHTMLLayoutParser::ColOn( ImportInfo* pInfo )
{
const HTMLOptions* pOptions = ((HTMLParser*)pInfo->pParser)->GetOptions();
sal_uInt16 nArrLen = pOptions->Count();
for ( sal_uInt16 i = 0; i < nArrLen; i++ )
{
const HTMLOption* pOption = (*pOptions)[i];
switch( pOption->GetToken() )
{
case HTML_O_WIDTH:
{
sal_uInt16 nVal = GetWidthPixel( pOption );
MakeCol( pLocalColOffset, nColOffset, nVal, 0, 0 );
nColOffset = nColOffset + nVal;
}
break;
}
}
}
sal_uInt16 ScHTMLLayoutParser::GetWidthPixel( const HTMLOption* pOption )
{
const String& rOptVal = pOption->GetString();
if ( rOptVal.Search('%') != STRING_NOTFOUND )
{ // Prozent
sal_uInt16 nW = (nTableWidth ? nTableWidth : (sal_uInt16) aPageSize.Width());
return (sal_uInt16)((pOption->GetNumber() * nW) / 100);
}
else
{
if ( rOptVal.Search('*') != STRING_NOTFOUND )
{ // relativ zu was?!?
//2do: ColArray aller relativen Werte sammeln und dann MakeCol
return 0;
}
else
return (sal_uInt16)pOption->GetNumber(); // Pixel
}
}
void ScHTMLLayoutParser::AnchorOn( ImportInfo* pInfo )
{
const HTMLOptions* pOptions = ((HTMLParser*)pInfo->pParser)->GetOptions();
sal_uInt16 nArrLen = pOptions->Count();
for ( sal_uInt16 i = 0; i < nArrLen; i++ )
{
const HTMLOption* pOption = (*pOptions)[i];
switch( pOption->GetToken() )
{
case HTML_O_NAME:
{
pActEntry->pName = new String( pOption->GetString() );
}
break;
}
}
}
sal_Bool ScHTMLLayoutParser::IsAtBeginningOfText( ImportInfo* pInfo )
{
ESelection& rSel = pActEntry->aSel;
return rSel.nStartPara == rSel.nEndPara &&
rSel.nStartPara <= pInfo->aSelection.nEndPara &&
pEdit->GetTextLen( rSel.nStartPara ) == 0;
}
void ScHTMLLayoutParser::FontOn( ImportInfo* pInfo )
{
if ( IsAtBeginningOfText( pInfo ) )
{ // nur am Anfang des Textes, gilt dann fuer gesamte Zelle
const HTMLOptions* pOptions = ((HTMLParser*)pInfo->pParser)->GetOptions();
sal_uInt16 nArrLen = pOptions->Count();
for ( sal_uInt16 i = 0; i < nArrLen; i++ )
{
const HTMLOption* pOption = (*pOptions)[i];
switch( pOption->GetToken() )
{
case HTML_O_FACE :
{
const String& rFace = pOption->GetString();
String aFontName;
xub_StrLen nPos = 0;
while( nPos != STRING_NOTFOUND )
{ // Fontliste, VCL: Semikolon als Separator, HTML: Komma
String aFName = rFace.GetToken( 0, ',', nPos );
aFName.EraseTrailingChars().EraseLeadingChars();
if( aFontName.Len() )
aFontName += ';';
aFontName += aFName;
}
if ( aFontName.Len() )
pActEntry->aItemSet.Put( SvxFontItem( FAMILY_DONTKNOW,
aFontName, EMPTY_STRING, PITCH_DONTKNOW,
RTL_TEXTENCODING_DONTKNOW, ATTR_FONT ) );
}
break;
case HTML_O_SIZE :
{
sal_uInt16 nSize = (sal_uInt16) pOption->GetNumber();
if ( nSize == 0 )
nSize = 1;
else if ( nSize > SC_HTML_FONTSIZES )
nSize = SC_HTML_FONTSIZES;
pActEntry->aItemSet.Put( SvxFontHeightItem(
maFontHeights[nSize-1], 100, ATTR_FONT_HEIGHT ) );
}
break;
case HTML_O_COLOR :
{
Color aColor;
pOption->GetColor( aColor );
pActEntry->aItemSet.Put( SvxColorItem( aColor, ATTR_FONT_COLOR ) );
}
break;
}
}
}
}
void ScHTMLLayoutParser::ProcToken( ImportInfo* pInfo )
{
sal_Bool bSetLastToken = sal_True;
switch ( pInfo->nToken )
{
case HTML_META:
{
HTMLParser* pParser = (HTMLParser*) pInfo->pParser;
uno::Reference<document::XDocumentPropertiesSupplier> xDPS(
mpDoc->GetDocumentShell()->GetModel(), uno::UNO_QUERY_THROW);
pParser->ParseMetaOptions(
xDPS->getDocumentProperties(),
mpDoc->GetDocumentShell()->GetHeaderAttributes() );
}
break;
case HTML_TITLE_ON:
{
bInTitle = sal_True;
aString.Erase();
}
break;
case HTML_TITLE_OFF:
{
if ( bInTitle && aString.Len() )
{
// Leerzeichen von Zeilenumbruechen raus
aString.EraseLeadingChars();
aString.EraseTrailingChars();
uno::Reference<document::XDocumentPropertiesSupplier> xDPS(
mpDoc->GetDocumentShell()->GetModel(),
uno::UNO_QUERY_THROW);
xDPS->getDocumentProperties()->setTitle(aString);
}
bInTitle = sal_False;
}
break;
case HTML_TABLE_ON:
{
TableOn( pInfo );
}
break;
case HTML_COL_ON:
{
ColOn( pInfo );
}
break;
case HTML_TABLEHEADER_ON: // oeffnet Zelle
{
if ( bInCell )
CloseEntry( pInfo );
// bInCell nicht sal_True setzen, das macht TableDataOn
pActEntry->aItemSet.Put(
SvxWeightItem( WEIGHT_BOLD, ATTR_FONT_WEIGHT) );
} // fall thru
case HTML_TABLEDATA_ON: // oeffnet Zelle
{
TableDataOn( pInfo );
}
break;
case HTML_TABLEHEADER_OFF:
case HTML_TABLEDATA_OFF: // schliesst Zelle
{
TableDataOff( pInfo );
}
break;
case HTML_TABLEROW_ON: // vor erster Zelle in Row
{
TableRowOn( pInfo );
}
break;
case HTML_TABLEROW_OFF: // nach letzter Zelle in Row
{
TableRowOff( pInfo );
}
break;
case HTML_TABLE_OFF:
{
TableOff( pInfo );
}
break;
case HTML_IMAGE:
{
Image( pInfo );
}
break;
case HTML_PARABREAK_OFF:
{ // nach einem Image geht es vertikal weiter
if ( pActEntry->pImageList && pActEntry->pImageList->Count() > 0 )
pActEntry->pImageList->Last()->nDir = nVertical;
}
break;
case HTML_ANCHOR_ON:
{
AnchorOn( pInfo );
}
break;
case HTML_FONT_ON :
{
FontOn( pInfo );
}
break;
case HTML_BIGPRINT_ON :
{
//2do: aktuelle Fontgroesse merken und einen groesser
if ( IsAtBeginningOfText( pInfo ) )
pActEntry->aItemSet.Put( SvxFontHeightItem(
maFontHeights[3], 100, ATTR_FONT_HEIGHT ) );
}
break;
case HTML_SMALLPRINT_ON :
{
//2do: aktuelle Fontgroesse merken und einen kleiner
if ( IsAtBeginningOfText( pInfo ) )
pActEntry->aItemSet.Put( SvxFontHeightItem(
maFontHeights[0], 100, ATTR_FONT_HEIGHT ) );
}
break;
case HTML_BOLD_ON :
case HTML_STRONG_ON :
{
if ( IsAtBeginningOfText( pInfo ) )
pActEntry->aItemSet.Put( SvxWeightItem( WEIGHT_BOLD,
ATTR_FONT_WEIGHT ) );
}
break;
case HTML_ITALIC_ON :
case HTML_EMPHASIS_ON :
case HTML_ADDRESS_ON :
case HTML_BLOCKQUOTE_ON :
case HTML_BLOCKQUOTE30_ON :
case HTML_CITIATION_ON :
case HTML_VARIABLE_ON :
{
if ( IsAtBeginningOfText( pInfo ) )
pActEntry->aItemSet.Put( SvxPostureItem( ITALIC_NORMAL,
ATTR_FONT_POSTURE ) );
}
break;
case HTML_DEFINSTANCE_ON :
{
if ( IsAtBeginningOfText( pInfo ) )
{
pActEntry->aItemSet.Put( SvxWeightItem( WEIGHT_BOLD,
ATTR_FONT_WEIGHT ) );
pActEntry->aItemSet.Put( SvxPostureItem( ITALIC_NORMAL,
ATTR_FONT_POSTURE ) );
}
}
break;
case HTML_UNDERLINE_ON :
{
if ( IsAtBeginningOfText( pInfo ) )
pActEntry->aItemSet.Put( SvxUnderlineItem( UNDERLINE_SINGLE,
ATTR_FONT_UNDERLINE ) );
}
break;
case HTML_TEXTTOKEN:
{
if ( bInTitle )
aString += pInfo->aText;
}
break;
default:
{ // nLastToken nicht setzen!
bSetLastToken = sal_False;
}
}
if ( bSetLastToken )
nLastToken = pInfo->nToken;
}
// ============================================================================
// HTML DATA QUERY PARSER
// ============================================================================
template< typename Type >
inline Type getLimitedValue( const Type& rValue, const Type& rMin, const Type& rMax )
{ return ::std::max( ::std::min( rValue, rMax ), rMin ); }
// ============================================================================
/** Iterates through all HTML tag options of the passed ImportInfo struct. */
class ScHTMLOptionIterator
{
private:
const HTMLOptions* mpOptions; /// The options array.
const HTMLOption* mpCurrOption; /// Current option.
sal_uInt16 mnCount; /// Size of the options array.
sal_uInt16 mnIndex; /// Next option to return.
public:
explicit ScHTMLOptionIterator( const ImportInfo& rInfo );
inline bool is() const { return mnIndex < mnCount; }
inline const HTMLOption* operator->() const { return mpCurrOption; }
inline const HTMLOption& operator*() const { return *mpCurrOption; }
ScHTMLOptionIterator& operator++();
};
// ----------------------------------------------------------------------------
ScHTMLOptionIterator::ScHTMLOptionIterator( const ImportInfo& rInfo ) :
mpOptions( 0 ),
mpCurrOption( 0 ),
mnCount( 0 ),
mnIndex( 0 )
{
const HTMLParser* pParser = static_cast< const HTMLParser* >( rInfo.pParser );
if( pParser )
mpOptions = pParser->GetOptions();
if( mpOptions )
mnCount = mpOptions->Count();
if( mnCount )
mpCurrOption = mpOptions->GetObject( 0 );
}
ScHTMLOptionIterator& ScHTMLOptionIterator::operator++()
{
if( mnIndex < mnCount ) ++mnIndex;
mpCurrOption = (mnIndex < mnCount) ? mpOptions->GetObject( mnIndex ) : 0;
return *this;
}
// ============================================================================
ScHTMLEntry::ScHTMLEntry( const SfxItemSet& rItemSet, ScHTMLTableId nTableId ) :
ScEEParseEntry( rItemSet ),
mbImportAlways( false )
{
nTab = nTableId;
bEntirePara = false;
}
bool ScHTMLEntry::HasContents() const
{
return mbImportAlways || aSel.HasRange() || aAltText.Len() || IsTable();
}
void ScHTMLEntry::AdjustStart( const ImportInfo& rInfo )
{
// set start position
aSel.nStartPara = rInfo.aSelection.nStartPara;
aSel.nStartPos = rInfo.aSelection.nStartPos;
// adjust end position
if( (aSel.nEndPara < aSel.nStartPara) || ((aSel.nEndPara == aSel.nStartPara) && (aSel.nEndPos < aSel.nStartPos)) )
{
aSel.nEndPara = aSel.nStartPara;
aSel.nEndPos = aSel.nStartPos;
}
}
void ScHTMLEntry::AdjustEnd( const ImportInfo& rInfo )
{
DBG_ASSERT( (aSel.nEndPara < rInfo.aSelection.nEndPara) ||
((aSel.nEndPara == rInfo.aSelection.nEndPara) && (aSel.nEndPos <= rInfo.aSelection.nEndPos)),
"ScHTMLQueryParser::AdjustEntryEnd - invalid end position" );
// set end position
aSel.nEndPara = rInfo.aSelection.nEndPara;
aSel.nEndPos = rInfo.aSelection.nEndPos;
}
void ScHTMLEntry::Strip( const EditEngine& rEditEngine )
{
// strip leading empty paragraphs
while( (aSel.nStartPara < aSel.nEndPara) && (rEditEngine.GetTextLen( aSel.nStartPara ) <= aSel.nStartPos) )
{
++aSel.nStartPara;
aSel.nStartPos = 0;
}
// strip trailing empty paragraphs
while( (aSel.nStartPara < aSel.nEndPara) && (aSel.nEndPos == 0) )
{
--aSel.nEndPara;
aSel.nEndPos = rEditEngine.GetTextLen( aSel.nEndPara );
}
}
// ============================================================================
/** A map of ScHTMLTable objects.
Organizes the tables with a unique table key. Stores nested tables inside
the parent table and forms in this way a tree structure of tables. An
instance of this class ownes the contained table objects and deletes them
on destruction.
*/
class ScHTMLTableMap
{
private:
typedef ::boost::shared_ptr< ScHTMLTable > ScHTMLTablePtr;
typedef ::std::map< ScHTMLTableId, ScHTMLTablePtr > ScHTMLTableStdMap;
public:
typedef ScHTMLTableStdMap::iterator iterator;
typedef ScHTMLTableStdMap::const_iterator const_iterator;
private:
ScHTMLTable& mrParentTable; /// Reference to parent table.
ScHTMLTableStdMap maTables; /// Container for all table objects.
mutable ScHTMLTable* mpCurrTable; /// Current table, used for fast search.
public:
explicit ScHTMLTableMap( ScHTMLTable& rParentTable );
virtual ~ScHTMLTableMap();
inline iterator begin() { return maTables.begin(); }
inline const_iterator begin() const { return maTables.begin(); }
inline iterator end() { return maTables.end(); }
inline const_iterator end() const { return maTables.end(); }
inline bool empty() const { return maTables.empty(); }
/** Returns the specified table.
@param nTableId Unique identifier of the table.
@param bDeep true = searches deep in all nested table; false = only in this container. */
ScHTMLTable* FindTable( ScHTMLTableId nTableId, bool bDeep = true ) const;
/** Inserts a new table into the container. This container owns the created table.
@param bPreFormText true = New table is based on preformatted text (<pre> tag). */
ScHTMLTable* CreateTable( const ImportInfo& rInfo, bool bPreFormText );
private:
/** Sets a working table with its index for search optimization. */
inline void SetCurrTable( ScHTMLTable* pTable ) const
{ if( pTable ) mpCurrTable = pTable; }
};
// ----------------------------------------------------------------------------
ScHTMLTableMap::ScHTMLTableMap( ScHTMLTable& rParentTable ) :
mrParentTable( rParentTable )
{
}
ScHTMLTableMap::~ScHTMLTableMap()
{
}
ScHTMLTable* ScHTMLTableMap::FindTable( ScHTMLTableId nTableId, bool bDeep ) const
{
ScHTMLTable* pResult = 0;
if( mpCurrTable && (nTableId == mpCurrTable->GetTableId()) )
pResult = mpCurrTable; // cached table
else
{
const_iterator aFind = maTables.find( nTableId );
if( aFind != maTables.end() )
pResult = aFind->second.get(); // table from this container
}
// not found -> search deep in nested tables
if( !pResult && bDeep )
for( const_iterator aIter = begin(), aEnd = end(); !pResult && (aIter != aEnd); ++aIter )
pResult = aIter->second->FindNestedTable( nTableId );
SetCurrTable( pResult );
return pResult;
}
ScHTMLTable* ScHTMLTableMap::CreateTable( const ImportInfo& rInfo, bool bPreFormText )
{
ScHTMLTable* pTable = new ScHTMLTable( mrParentTable, rInfo, bPreFormText );
maTables[ pTable->GetTableId() ].reset( pTable );
SetCurrTable( pTable );
return pTable;
}
// ============================================================================
ScHTMLTableAutoId::ScHTMLTableAutoId( ScHTMLTableId& rnUnusedId ) :
mnTableId( rnUnusedId ),
mrnUnusedId( rnUnusedId )
{
++mrnUnusedId;
}
// ----------------------------------------------------------------------------
ScHTMLTable::ScHTMLTable( ScHTMLTable& rParentTable, const ImportInfo& rInfo, bool bPreFormText ) :
mpParentTable( &rParentTable ),
maTableId( rParentTable.maTableId.mrnUnusedId ),
maTableItemSet( rParentTable.GetCurrItemSet() ),
mrEditEngine( rParentTable.mrEditEngine ),
mrEEParseList( rParentTable.mrEEParseList ),
mpCurrEntryList( 0 ),
maSize( 1, 1 ),
mbBorderOn( false ),
mbPreFormText( bPreFormText ),
mbRowOn( false ),
mbDataOn( false ),
mbPushEmptyLine( false )
{
if( mbPreFormText )
{
ImplRowOn();
ImplDataOn( ScHTMLSize( 1, 1 ) );
}
else
{
ProcessFormatOptions( maTableItemSet, rInfo );
for( ScHTMLOptionIterator aIter( rInfo ); aIter.is(); ++aIter )
{
switch( aIter->GetToken() )
{
case HTML_O_BORDER:
mbBorderOn = ((aIter->GetString().Len() == 0) || (aIter->GetNumber() != 0));
break;
case HTML_O_ID:
maTableName = aIter->GetString();
break;
}
}
}
CreateNewEntry( rInfo );
}
ScHTMLTable::ScHTMLTable( SfxItemPool& rPool, EditEngine& rEditEngine, ScEEParseList& rEEParseList, ScHTMLTableId& rnUnusedId ) :
mpParentTable( 0 ),
maTableId( rnUnusedId ),
maTableItemSet( rPool ),
mrEditEngine( rEditEngine ),
mrEEParseList( rEEParseList ),
mpCurrEntryList( 0 ),
maSize( 1, 1 ),
mbBorderOn( false ),
mbPreFormText( false ),
mbRowOn( false ),
mbDataOn( false ),
mbPushEmptyLine( false )
{
// open the first "cell" of the document
ImplRowOn();
ImplDataOn( ScHTMLSize( 1, 1 ) );
mxCurrEntry = CreateEntry();
}
ScHTMLTable::~ScHTMLTable()
{
}
const SfxItemSet& ScHTMLTable::GetCurrItemSet() const
{
// first try cell item set, then row item set, then table item set
return mxDataItemSet.get() ? *mxDataItemSet : (mxRowItemSet.get() ? *mxRowItemSet : maTableItemSet);
}
ScHTMLSize ScHTMLTable::GetSpan( const ScHTMLPos& rCellPos ) const
{
ScHTMLSize aSpan( 1, 1 );
ScRange* pRange = 0;
if( ((pRange = maVMergedCells.Find( rCellPos.MakeAddr() )) != 0) || ((pRange = maHMergedCells.Find( rCellPos.MakeAddr() )) != 0) )
aSpan.Set( pRange->aEnd.Col() - pRange->aStart.Col() + 1, pRange->aEnd.Row() - pRange->aStart.Row() + 1 );
return aSpan;
}
ScHTMLTable* ScHTMLTable::FindNestedTable( ScHTMLTableId nTableId ) const
{
return mxNestedTables.get() ? mxNestedTables->FindTable( nTableId, true ) : 0;
}
void ScHTMLTable::PutItem( const SfxPoolItem& rItem )
{
DBG_ASSERT( mxCurrEntry.get(), "ScHTMLTable::PutItem - no current entry" );
if( mxCurrEntry.get() && mxCurrEntry->IsEmpty() )
mxCurrEntry->GetItemSet().Put( rItem );
}
void ScHTMLTable::PutText( const ImportInfo& rInfo )
{
DBG_ASSERT( mxCurrEntry.get(), "ScHTMLTable::PutText - no current entry" );
if( mxCurrEntry.get() )
{
if( !mxCurrEntry->HasContents() && IsSpaceCharInfo( rInfo ) )
mxCurrEntry->AdjustStart( rInfo );
else
mxCurrEntry->AdjustEnd( rInfo );
}
}
void ScHTMLTable::InsertPara( const ImportInfo& rInfo )
{
if( mxCurrEntry.get() && mbDataOn && !IsEmptyCell() )
mxCurrEntry->SetImportAlways();
PushEntry( rInfo );
CreateNewEntry( rInfo );
InsertLeadingEmptyLine();
}
void ScHTMLTable::BreakOn()
{
// empty line, if <br> is at start of cell
mbPushEmptyLine = !mbPreFormText && mbDataOn && IsEmptyCell();
}
void ScHTMLTable::HeadingOn()
{
// call directly, InsertPara() has not been called before
InsertLeadingEmptyLine();
}
void ScHTMLTable::InsertLeadingEmptyLine()
{
// empty line, if <p>, </p>, <h?>, or </h*> are not at start of cell
mbPushEmptyLine = !mbPreFormText && mbDataOn && !IsEmptyCell();
}
void ScHTMLTable::AnchorOn()
{
DBG_ASSERT( mxCurrEntry.get(), "ScHTMLTable::AnchorOn - no current entry" );
// don't skip entries with single hyperlinks
if( mxCurrEntry.get() )
mxCurrEntry->SetImportAlways();
}
ScHTMLTable* ScHTMLTable::TableOn( const ImportInfo& rInfo )
{
PushEntry( rInfo );
return InsertNestedTable( rInfo, false );
}
ScHTMLTable* ScHTMLTable::TableOff( const ImportInfo& rInfo )
{
return mbPreFormText ? this : CloseTable( rInfo );
}
ScHTMLTable* ScHTMLTable::PreOn( const ImportInfo& rInfo )
{
PushEntry( rInfo );
return InsertNestedTable( rInfo, true );
}
ScHTMLTable* ScHTMLTable::PreOff( const ImportInfo& rInfo )
{
return mbPreFormText ? CloseTable( rInfo ) : this;
}
void ScHTMLTable::RowOn( const ImportInfo& rInfo )
{
PushEntry( rInfo, true );
if( mpParentTable && !mbPreFormText ) // no rows allowed in global and preformatted tables
{
ImplRowOn();
ProcessFormatOptions( *mxRowItemSet, rInfo );
}
CreateNewEntry( rInfo );
}
void ScHTMLTable::RowOff( const ImportInfo& rInfo )
{
PushEntry( rInfo, true );
if( mpParentTable && !mbPreFormText ) // no rows allowed in global and preformatted tables
ImplRowOff();
CreateNewEntry( rInfo );
}
void ScHTMLTable::DataOn( const ImportInfo& rInfo )
{
PushEntry( rInfo, true );
if( mpParentTable && !mbPreFormText ) // no cells allowed in global and preformatted tables
{
// read needed options from the <td> tag
ScHTMLSize aSpanSize( 1, 1 );
::std::auto_ptr< String > pValStr, pNumStr;
for( ScHTMLOptionIterator aIter( rInfo ); aIter.is(); ++aIter )
{
switch( aIter->GetToken() )
{
case HTML_O_COLSPAN:
aSpanSize.mnCols = static_cast< SCCOL >( getLimitedValue< sal_Int32 >( aIter->GetString().ToInt32(), 1, 256 ) );
break;
case HTML_O_ROWSPAN:
aSpanSize.mnRows = static_cast< SCROW >( getLimitedValue< sal_Int32 >( aIter->GetString().ToInt32(), 1, 256 ) );
break;
case HTML_O_SDVAL:
pValStr.reset( new String( aIter->GetString() ) );
break;
case HTML_O_SDNUM:
pNumStr.reset( new String( aIter->GetString() ) );
break;
}
}
ImplDataOn( aSpanSize );
ProcessFormatOptions( *mxDataItemSet, rInfo );
CreateNewEntry( rInfo );
mxCurrEntry->pValStr = pValStr.release();
mxCurrEntry->pNumStr = pNumStr.release();
}
else
CreateNewEntry( rInfo );
}
void ScHTMLTable::DataOff( const ImportInfo& rInfo )
{
PushEntry( rInfo, true );
if( mpParentTable && !mbPreFormText ) // no cells allowed in global and preformatted tables
ImplDataOff();
CreateNewEntry( rInfo );
}
void ScHTMLTable::BodyOn( const ImportInfo& rInfo )
{
bool bPushed = PushEntry( rInfo );
if( !mpParentTable )
{
// #108269# do not start new row, if nothing (no title) precedes the body.
if( bPushed || !mbRowOn )
ImplRowOn();
if( bPushed || !mbDataOn )
ImplDataOn( ScHTMLSize( 1, 1 ) );
ProcessFormatOptions( *mxDataItemSet, rInfo );
}
CreateNewEntry( rInfo );
}
void ScHTMLTable::BodyOff( const ImportInfo& rInfo )
{
PushEntry( rInfo );
if( !mpParentTable )
{
ImplDataOff();
ImplRowOff();
}
CreateNewEntry( rInfo );
}
ScHTMLTable* ScHTMLTable::CloseTable( const ImportInfo& rInfo )
{
if( mpParentTable ) // not allowed to close global table
{
PushEntry( rInfo, mbDataOn );
ImplDataOff();
ImplRowOff();
mpParentTable->PushTableEntry( GetTableId() );
mpParentTable->CreateNewEntry( rInfo );
if( mbPreFormText ) // enclose preformatted table with empty lines in parent table
mpParentTable->InsertLeadingEmptyLine();
return mpParentTable;
}
return this;
}
SCCOLROW ScHTMLTable::GetDocSize( ScHTMLOrient eOrient, SCCOLROW nCellPos ) const
{
const ScSizeVec& rSizes = maCumSizes[ eOrient ];
size_t nIndex = static_cast< size_t >( nCellPos );
if( nIndex >= rSizes.size() ) return 0;
return (nIndex == 0) ? rSizes.front() : (rSizes[ nIndex ] - rSizes[ nIndex - 1 ]);
}
SCCOLROW ScHTMLTable::GetDocSize( ScHTMLOrient eOrient, SCCOLROW nCellBegin, SCCOLROW nCellEnd ) const
{
const ScSizeVec& rSizes = maCumSizes[ eOrient ];
size_t nBeginIdx = static_cast< size_t >( ::std::max< SCCOLROW >( nCellBegin, 0 ) );
size_t nEndIdx = static_cast< size_t >( ::std::min< SCCOLROW >( nCellEnd, static_cast< SCCOLROW >( rSizes.size() ) ) );
if (nBeginIdx >= nEndIdx ) return 0;
return rSizes[ nEndIdx - 1 ] - ((nBeginIdx == 0) ? 0 : rSizes[ nBeginIdx - 1 ]);
}
SCCOLROW ScHTMLTable::GetDocSize( ScHTMLOrient eOrient ) const
{
const ScSizeVec& rSizes = maCumSizes[ eOrient ];
return rSizes.empty() ? 0 : rSizes.back();
}
ScHTMLSize ScHTMLTable::GetDocSize( const ScHTMLPos& rCellPos ) const
{
ScHTMLSize aCellSpan = GetSpan( rCellPos );
return ScHTMLSize(
static_cast< SCCOL >( GetDocSize( tdCol, rCellPos.mnCol, rCellPos.mnCol + aCellSpan.mnCols ) ),
static_cast< SCROW >( GetDocSize( tdRow, rCellPos.mnRow, rCellPos.mnRow + aCellSpan.mnRows ) ) );
}
SCCOLROW ScHTMLTable::GetDocPos( ScHTMLOrient eOrient, SCCOLROW nCellPos ) const
{
return maDocBasePos.Get( eOrient ) + GetDocSize( eOrient, 0, nCellPos );
}
ScHTMLPos ScHTMLTable::GetDocPos( const ScHTMLPos& rCellPos ) const
{
return ScHTMLPos(
static_cast< SCCOL >( GetDocPos( tdCol, rCellPos.mnCol ) ),
static_cast< SCROW >( GetDocPos( tdRow, rCellPos.mnRow ) ) );
}
void ScHTMLTable::GetDocRange( ScRange& rRange ) const
{
rRange.aStart = rRange.aEnd = maDocBasePos.MakeAddr();
rRange.aEnd.Move( static_cast< SCsCOL >( GetDocSize( tdCol ) ) - 1, static_cast< SCsROW >( GetDocSize( tdRow ) ) - 1, 0 );
}
void ScHTMLTable::ApplyCellBorders( ScDocument* pDoc, const ScAddress& rFirstPos ) const
{
DBG_ASSERT( pDoc, "ScHTMLTable::ApplyCellBorders - no document" );
if( pDoc && mbBorderOn )
{
const SCCOL nLastCol = maSize.mnCols - 1;
const SCROW nLastRow = maSize.mnRows - 1;
const sal_uInt16 nOuterLine = DEF_LINE_WIDTH_2;
const sal_uInt16 nInnerLine = DEF_LINE_WIDTH_0;
SvxBorderLine aOuterLine, aInnerLine;
aOuterLine.SetColor( Color( COL_BLACK ) );
aOuterLine.SetOutWidth( nOuterLine );
aInnerLine.SetColor( Color( COL_BLACK ) );
aInnerLine.SetOutWidth( nInnerLine );
SvxBoxItem aBorderItem( ATTR_BORDER );
for( SCCOL nCol = 0; nCol <= nLastCol; ++nCol )
{
SvxBorderLine* pLeftLine = (nCol == 0) ? &aOuterLine : &aInnerLine;
SvxBorderLine* pRightLine = (nCol == nLastCol) ? &aOuterLine : &aInnerLine;
SCCOL nCellCol1 = static_cast< SCCOL >( GetDocPos( tdCol, nCol ) ) + rFirstPos.Col();
SCCOL nCellCol2 = nCellCol1 + static_cast< SCCOL >( GetDocSize( tdCol, nCol ) ) - 1;
for( SCROW nRow = 0; nRow <= nLastRow; ++nRow )
{
SvxBorderLine* pTopLine = (nRow == 0) ? &aOuterLine : &aInnerLine;
SvxBorderLine* pBottomLine = (nRow == nLastRow) ? &aOuterLine : &aInnerLine;
SCROW nCellRow1 = GetDocPos( tdRow, nRow ) + rFirstPos.Row();
SCROW nCellRow2 = nCellRow1 + GetDocSize( tdRow, nRow ) - 1;
for( SCCOL nCellCol = nCellCol1; nCellCol <= nCellCol2; ++nCellCol )
{
aBorderItem.SetLine( (nCellCol == nCellCol1) ? pLeftLine : 0, BOX_LINE_LEFT );
aBorderItem.SetLine( (nCellCol == nCellCol2) ? pRightLine : 0, BOX_LINE_RIGHT );
for( SCROW nCellRow = nCellRow1; nCellRow <= nCellRow2; ++nCellRow )
{
aBorderItem.SetLine( (nCellRow == nCellRow1) ? pTopLine : 0, BOX_LINE_TOP );
aBorderItem.SetLine( (nCellRow == nCellRow2) ? pBottomLine : 0, BOX_LINE_BOTTOM );
pDoc->ApplyAttr( nCellCol, nCellRow, rFirstPos.Tab(), aBorderItem );
}
}
}
}
}
if ( mxNestedTables.get() )
{
for ( ScHTMLTableMap::const_iterator aIter( mxNestedTables->begin() );
aIter != mxNestedTables->end(); ++aIter )
{
if ( aIter->second.get() )
aIter->second->ApplyCellBorders( pDoc, rFirstPos );
}
}
}
// ----------------------------------------------------------------------------
bool ScHTMLTable::IsEmptyCell() const
{
return mpCurrEntryList && mpCurrEntryList->empty();
}
bool ScHTMLTable::IsSpaceCharInfo( const ImportInfo& rInfo )
{
return (rInfo.nToken == HTML_TEXTTOKEN) && (rInfo.aText.Len() == 1) && (rInfo.aText.GetChar( 0 ) == ' ');
}
ScHTMLTable::ScHTMLEntryPtr ScHTMLTable::CreateEntry() const
{
return ScHTMLEntryPtr( new ScHTMLEntry( GetCurrItemSet() ) );
}
void ScHTMLTable::CreateNewEntry( const ImportInfo& rInfo )
{
DBG_ASSERT( !mxCurrEntry.get(), "ScHTMLTable::CreateNewEntry - old entry still present" );
mxCurrEntry = CreateEntry();
mxCurrEntry->aSel = rInfo.aSelection;
}
void ScHTMLTable::ImplPushEntryToList( ScHTMLEntryList& rEntryList, ScHTMLEntryPtr& rxEntry )
{
// HTML entry list does not own the entries
rEntryList.push_back( rxEntry.get() );
// mrEEParseList (reference to member of ScEEParser) owns the entries
mrEEParseList.Insert( rxEntry.release(), LIST_APPEND );
}
bool ScHTMLTable::PushEntry( ScHTMLEntryPtr& rxEntry )
{
bool bPushed = false;
if( rxEntry.get() && rxEntry->HasContents() )
{
if( mpCurrEntryList )
{
if( mbPushEmptyLine )
{
ScHTMLEntryPtr xEmptyEntry = CreateEntry();
ImplPushEntryToList( *mpCurrEntryList, xEmptyEntry );
mbPushEmptyLine = false;
}
ImplPushEntryToList( *mpCurrEntryList, rxEntry );
bPushed = true;
}
else if( mpParentTable )
{
bPushed = mpParentTable->PushEntry( rxEntry );
}
else
{
DBG_ERRORFILE( "ScHTMLTable::PushEntry - cannot push entry, no parent found" );
}
}
return bPushed;
}
bool ScHTMLTable::PushEntry( const ImportInfo& rInfo, bool bLastInCell )
{
DBG_ASSERT( mxCurrEntry.get(), "ScHTMLTable::PushEntry - no current entry" );
bool bPushed = false;
if( mxCurrEntry.get() )
{
mxCurrEntry->AdjustEnd( rInfo );
mxCurrEntry->Strip( mrEditEngine );
// import entry always, if it is the last in cell, and cell is still empty
if( bLastInCell && IsEmptyCell() )
{
mxCurrEntry->SetImportAlways();
// don't insert empty lines before single empty entries
if( mxCurrEntry->IsEmpty() )
mbPushEmptyLine = false;
}
bPushed = PushEntry( mxCurrEntry );
mxCurrEntry.reset();
}
return bPushed;
}
bool ScHTMLTable::PushTableEntry( ScHTMLTableId nTableId )
{
DBG_ASSERT( nTableId != SC_HTML_GLOBAL_TABLE, "ScHTMLTable::PushTableEntry - cannot push global table" );
bool bPushed = false;
if( nTableId != SC_HTML_GLOBAL_TABLE )
{
ScHTMLEntryPtr xEntry( new ScHTMLEntry( maTableItemSet, nTableId ) );
bPushed = PushEntry( xEntry );
}
return bPushed;
}
ScHTMLTable* ScHTMLTable::GetExistingTable( ScHTMLTableId nTableId ) const
{
ScHTMLTable* pTable = ((nTableId != SC_HTML_GLOBAL_TABLE) && mxNestedTables.get()) ?
mxNestedTables->FindTable( nTableId, false ) : 0;
DBG_ASSERT( pTable || (nTableId == SC_HTML_GLOBAL_TABLE), "ScHTMLTable::GetExistingTable - table not found" );
return pTable;
}
ScHTMLTable* ScHTMLTable::InsertNestedTable( const ImportInfo& rInfo, bool bPreFormText )
{
if( !mxNestedTables.get() )
mxNestedTables.reset( new ScHTMLTableMap( *this ) );
if( bPreFormText ) // enclose new preformatted table with empty lines
InsertLeadingEmptyLine();
return mxNestedTables->CreateTable( rInfo, bPreFormText );
}
void ScHTMLTable::InsertNewCell( const ScHTMLSize& rSpanSize )
{
ScRange* pRange;
/* Find an unused cell by skipping all merged ranges that cover the
current cell position stored in maCurrCell. */
while( ((pRange = maVMergedCells.Find( maCurrCell.MakeAddr() )) != 0) || ((pRange = maHMergedCells.Find( maCurrCell.MakeAddr() )) != 0) )
maCurrCell.mnCol = pRange->aEnd.Col() + 1;
mpCurrEntryList = &maEntryMap[ maCurrCell ];
/* If the new cell is merged horizontally, try to find collisions with
other vertically merged ranges. In this case, shrink existing
vertically merged ranges (do not shrink the new cell). */
SCCOL nColEnd = maCurrCell.mnCol + rSpanSize.mnCols;
for( ScAddress aAddr( maCurrCell.MakeAddr() ); aAddr.Col() < nColEnd; aAddr.IncCol() )
if( (pRange = maVMergedCells.Find( aAddr )) != 0 )
pRange->aEnd.SetRow( maCurrCell.mnRow - 1 );
// insert the new range into the cell lists
ScRange aNewRange( maCurrCell.MakeAddr() );
aNewRange.aEnd.Move( rSpanSize.mnCols - 1, rSpanSize.mnRows - 1, 0 );
if( rSpanSize.mnRows > 1 )
{
maVMergedCells.Append( aNewRange );
/* Do not insert vertically merged ranges into maUsedCells yet,
because they may be shrunken (see above). The final vertically
merged ranges are inserted in FillEmptyCells(). */
}
else
{
if( rSpanSize.mnCols > 1 )
maHMergedCells.Append( aNewRange );
/* Insert horizontally merged ranges and single cells into
maUsedCells, they will not be changed anymore. */
maUsedCells.Join( aNewRange );
}
// adjust table size
maSize.mnCols = ::std::max< SCCOL >( maSize.mnCols, aNewRange.aEnd.Col() + 1 );
maSize.mnRows = ::std::max< SCROW >( maSize.mnRows, aNewRange.aEnd.Row() + 1 );
}
void ScHTMLTable::ImplRowOn()
{
if( mbRowOn )
ImplRowOff();
mxRowItemSet.reset( new SfxItemSet( maTableItemSet ) );
maCurrCell.mnCol = 0;
mbRowOn = true;
mbDataOn = false;
}
void ScHTMLTable::ImplRowOff()
{
if( mbDataOn )
ImplDataOff();
if( mbRowOn )
{
mxRowItemSet.reset();
++maCurrCell.mnRow;
mbRowOn = mbDataOn = false;
}
}
void ScHTMLTable::ImplDataOn( const ScHTMLSize& rSpanSize )
{
if( mbDataOn )
ImplDataOff();
if( !mbRowOn )
ImplRowOn();
mxDataItemSet.reset( new SfxItemSet( *mxRowItemSet ) );
InsertNewCell( rSpanSize );
mbDataOn = true;
mbPushEmptyLine = false;
}
void ScHTMLTable::ImplDataOff()
{
if( mbDataOn )
{
mxDataItemSet.reset();
++maCurrCell.mnCol;
mpCurrEntryList = 0;
mbDataOn = false;
}
}
void ScHTMLTable::ProcessFormatOptions( SfxItemSet& rItemSet, const ImportInfo& rInfo )
{
// special handling for table header cells
if( rInfo.nToken == HTML_TABLEHEADER_ON )
{
rItemSet.Put( SvxWeightItem( WEIGHT_BOLD, ATTR_FONT_WEIGHT ) );
rItemSet.Put( SvxHorJustifyItem( SVX_HOR_JUSTIFY_CENTER, ATTR_HOR_JUSTIFY ) );
}
for( ScHTMLOptionIterator aIter( rInfo ); aIter.is(); ++aIter )
{
switch( aIter->GetToken() )
{
case HTML_O_ALIGN:
{
SvxCellHorJustify eVal = SVX_HOR_JUSTIFY_STANDARD;
const String& rOptVal = aIter->GetString();
if( rOptVal.EqualsIgnoreCaseAscii( OOO_STRING_SVTOOLS_HTML_AL_right ) )
eVal = SVX_HOR_JUSTIFY_RIGHT;
else if( rOptVal.EqualsIgnoreCaseAscii( OOO_STRING_SVTOOLS_HTML_AL_center ) )
eVal = SVX_HOR_JUSTIFY_CENTER;
else if( rOptVal.EqualsIgnoreCaseAscii( OOO_STRING_SVTOOLS_HTML_AL_left ) )
eVal = SVX_HOR_JUSTIFY_LEFT;
if( eVal != SVX_HOR_JUSTIFY_STANDARD )
rItemSet.Put( SvxHorJustifyItem( eVal, ATTR_HOR_JUSTIFY ) );
}
break;
case HTML_O_VALIGN:
{
SvxCellVerJustify eVal = SVX_VER_JUSTIFY_STANDARD;
const String& rOptVal = aIter->GetString();
if( rOptVal.EqualsIgnoreCaseAscii( OOO_STRING_SVTOOLS_HTML_VA_top ) )
eVal = SVX_VER_JUSTIFY_TOP;
else if( rOptVal.EqualsIgnoreCaseAscii( OOO_STRING_SVTOOLS_HTML_VA_middle ) )
eVal = SVX_VER_JUSTIFY_CENTER;
else if( rOptVal.EqualsIgnoreCaseAscii( OOO_STRING_SVTOOLS_HTML_VA_bottom ) )
eVal = SVX_VER_JUSTIFY_BOTTOM;
if( eVal != SVX_VER_JUSTIFY_STANDARD )
rItemSet.Put( SvxVerJustifyItem( eVal, ATTR_VER_JUSTIFY ) );
}
break;
case HTML_O_BGCOLOR:
{
Color aColor;
aIter->GetColor( aColor );
rItemSet.Put( SvxBrushItem( aColor, ATTR_BACKGROUND ) );
}
break;
}
}
}
void ScHTMLTable::SetDocSize( ScHTMLOrient eOrient, SCCOLROW nCellPos, SCCOLROW nSize )
{
DBG_ASSERT( nCellPos >= 0, "ScHTMLTable::SetDocSize - unexpected negative position" );
ScSizeVec& rSizes = maCumSizes[ eOrient ];
size_t nIndex = static_cast< size_t >( nCellPos );
// expand with height/width == 1
while( nIndex >= rSizes.size() )
rSizes.push_back( rSizes.empty() ? 1 : (rSizes.back() + 1) );
// update size of passed position and all following
// #i109987# only grow, don't shrink - use the largest needed size
SCsCOLROW nDiff = nSize - ((nIndex == 0) ? rSizes.front() : (rSizes[ nIndex ] - rSizes[ nIndex - 1 ]));
if( nDiff > 0 )
for( ScSizeVec::iterator aIt = rSizes.begin() + nIndex, aEnd = rSizes.end(); aIt != aEnd; ++aIt )
*aIt += nDiff;
}
void ScHTMLTable::CalcNeededDocSize(
ScHTMLOrient eOrient, SCCOLROW nCellPos, SCCOLROW nCellSpan, SCCOLROW nRealDocSize )
{
SCCOLROW nDiffSize = 0;
// in merged columns/rows: reduce needed size by size of leading columns
while( nCellSpan > 1 )
{
nDiffSize += GetDocSize( eOrient, nCellPos );
--nCellSpan;
++nCellPos;
}
// set remaining needed size to last column/row
nRealDocSize -= ::std::min< SCCOLROW >( nRealDocSize - 1, nDiffSize );
SetDocSize( eOrient, nCellPos, nRealDocSize );
}
// ----------------------------------------------------------------------------
void ScHTMLTable::FillEmptyCells()
{
if ( mxNestedTables.get() )
{
for ( ScHTMLTableMap::const_iterator aIter( mxNestedTables->begin() );
aIter != mxNestedTables->end(); ++aIter )
{
if ( aIter->second.get() )
aIter->second->FillEmptyCells();
}
}
// insert the final vertically merged ranges into maUsedCells
for( const ScRange* pRange = maVMergedCells.First(); pRange; pRange = maVMergedCells.Next() )
maUsedCells.Join( *pRange );
for( ScAddress aAddr; aAddr.Row() < maSize.mnRows; aAddr.IncRow() )
{
for( aAddr.SetCol( 0 ); aAddr.Col() < maSize.mnCols; aAddr.IncCol() )
{
if( !maUsedCells.Find( aAddr ) )
{
// create a range for the lock list (used to calc. cell span)
ScRange aRange( aAddr );
do
{
aRange.aEnd.IncCol();
}
while( (aRange.aEnd.Col() < maSize.mnCols) && !maUsedCells.Find( aRange.aEnd ) );
aRange.aEnd.IncCol( -1 );
maUsedCells.Join( aRange );
// insert a dummy entry
ScHTMLEntryPtr xEntry = CreateEntry();
ImplPushEntryToList( maEntryMap[ ScHTMLPos( aAddr ) ], xEntry );
}
}
}
}
void ScHTMLTable::RecalcDocSize()
{
// recalc table sizes recursively from inner to outer
if ( mxNestedTables.get() )
{
for ( ScHTMLTableMap::const_iterator aIter( mxNestedTables->begin() );
aIter != mxNestedTables->end(); ++aIter )
{
if ( aIter->second.get() )
aIter->second->RecalcDocSize();
}
}
/* Two passes: first calculates the sizes of single columns/rows, then
the sizes of spanned columns/rows. This allows to fill nested tables
into merged cells optimally. */
static const sal_uInt16 PASS_SINGLE = 0;
static const sal_uInt16 PASS_SPANNED = 1;
for( sal_uInt16 nPass = PASS_SINGLE; nPass <= PASS_SPANNED; ++nPass )
{
// iterate through every table cell
ScHTMLEntryMap::const_iterator aMapIterEnd = maEntryMap.end();
for( ScHTMLEntryMap::const_iterator aMapIter = maEntryMap.begin(); aMapIter != aMapIterEnd; ++aMapIter )
{
const ScHTMLPos& rCellPos = aMapIter->first;
ScHTMLSize aCellSpan = GetSpan( rCellPos );
const ScHTMLEntryList& rEntryList = aMapIter->second;
ScHTMLEntryList::const_iterator aListIter;
ScHTMLEntryList::const_iterator aListIterEnd = rEntryList.end();
// process the dimension of the current cell in this pass?
// (pass is single and span is 1) or (pass is not single and span is not 1)
bool bProcessColWidth = ((nPass == PASS_SINGLE) == (aCellSpan.mnCols == 1));
bool bProcessRowHeight = ((nPass == PASS_SINGLE) == (aCellSpan.mnRows == 1));
if( bProcessColWidth || bProcessRowHeight )
{
ScHTMLSize aDocSize( 1, 0 ); // resulting size of the cell in document
// expand the cell size for each cell parse entry
for( aListIter = rEntryList.begin(); aListIter != aListIterEnd; ++aListIter )
{
ScHTMLTable* pTable = GetExistingTable( (*aListIter)->GetTableId() );
// find entry with maximum width
if( bProcessColWidth && pTable )
aDocSize.mnCols = ::std::max( aDocSize.mnCols, static_cast< SCCOL >( pTable->GetDocSize( tdCol ) ) );
// add up height of each entry
if( bProcessRowHeight )
aDocSize.mnRows += pTable ? pTable->GetDocSize( tdRow ) : 1;
}
if( !aDocSize.mnRows )
aDocSize.mnRows = 1;
if( bProcessColWidth )
CalcNeededDocSize( tdCol, rCellPos.mnCol, aCellSpan.mnCols, aDocSize.mnCols );
if( bProcessRowHeight )
CalcNeededDocSize( tdRow, rCellPos.mnRow, aCellSpan.mnRows, aDocSize.mnRows );
}
}
}
}
void ScHTMLTable::RecalcDocPos( const ScHTMLPos& rBasePos )
{
maDocBasePos = rBasePos;
// after the previous assignment it is allowed to call GetDocPos() methods
// iterate through every table cell
ScHTMLEntryMap::iterator aMapIterEnd = maEntryMap.end();
for( ScHTMLEntryMap::iterator aMapIter = maEntryMap.begin(); aMapIter != aMapIterEnd; ++aMapIter )
{
// fixed doc position of the entire cell (first entry)
const ScHTMLPos aCellDocPos( GetDocPos( aMapIter->first ) );
// fixed doc size of the entire cell
const ScHTMLSize aCellDocSize( GetDocSize( aMapIter->first ) );
// running doc position for single entries
ScHTMLPos aEntryDocPos( aCellDocPos );
ScHTMLEntryList& rEntryList = aMapIter->second;
ScHTMLEntry* pEntry = 0;
ScHTMLEntryList::iterator aListIterEnd = rEntryList.end();
for( ScHTMLEntryList::iterator aListIter = rEntryList.begin(); aListIter != aListIterEnd; ++aListIter )
{
pEntry = *aListIter;
if( ScHTMLTable* pTable = GetExistingTable( pEntry->GetTableId() ) )
{
pTable->RecalcDocPos( aEntryDocPos ); // recalc nested table
pEntry->nCol = SCCOL_MAX;
pEntry->nRow = SCROW_MAX;
SCROW nTableRows = static_cast< SCROW >( pTable->GetDocSize( tdRow ) );
// use this entry to pad empty space right of table
if( mpParentTable ) // ... but not in global table
{
SCCOL nStartCol = aEntryDocPos.mnCol + static_cast< SCCOL >( pTable->GetDocSize( tdCol ) );
SCCOL nNextCol = aEntryDocPos.mnCol + aCellDocSize.mnCols;
if( nStartCol < nNextCol )
{
pEntry->nCol = nStartCol;
pEntry->nRow = aEntryDocPos.mnRow;
pEntry->nColOverlap = nNextCol - nStartCol;
pEntry->nRowOverlap = nTableRows;
}
}
aEntryDocPos.mnRow += nTableRows;
}
else
{
pEntry->nCol = aEntryDocPos.mnCol;
pEntry->nRow = aEntryDocPos.mnRow;
if( mpParentTable ) // do not merge in global table
pEntry->nColOverlap = aCellDocSize.mnCols;
++aEntryDocPos.mnRow;
}
}
// pEntry points now to last entry.
if( pEntry )
{
if( (pEntry == rEntryList.front()) && (pEntry->GetTableId() == SC_HTML_NO_TABLE) )
{
// pEntry is the only entry in this cell - merge rows of cell with single non-table entry.
pEntry->nRowOverlap = aCellDocSize.mnRows;
}
else
{
// #111667# fill up incomplete entry lists
SCROW nFirstUnusedRow = aCellDocPos.mnRow + aCellDocSize.mnRows;
while( aEntryDocPos.mnRow < nFirstUnusedRow )
{
ScHTMLEntryPtr xDummyEntry( new ScHTMLEntry( pEntry->GetItemSet() ) );
xDummyEntry->nCol = aEntryDocPos.mnCol;
xDummyEntry->nRow = aEntryDocPos.mnRow;
xDummyEntry->nColOverlap = aCellDocSize.mnCols;
ImplPushEntryToList( rEntryList, xDummyEntry );
++aEntryDocPos.mnRow;
}
}
}
}
}
// ============================================================================
ScHTMLGlobalTable::ScHTMLGlobalTable( SfxItemPool& rPool, EditEngine& rEditEngine, ScEEParseList& rEEParseList, ScHTMLTableId& rnUnusedId ) :
ScHTMLTable( rPool, rEditEngine, rEEParseList, rnUnusedId )
{
}
ScHTMLGlobalTable::~ScHTMLGlobalTable()
{
}
void ScHTMLGlobalTable::Recalc()
{
// Fills up empty cells with a dummy entry. */
FillEmptyCells();
// recalc table sizes of all nested tables and this table
RecalcDocSize();
// recalc document positions of all entries in this table and in nested tables
RecalcDocPos( GetDocPos() );
}
// ============================================================================
ScHTMLQueryParser::ScHTMLQueryParser( EditEngine* pEditEngine, ScDocument* pDoc ) :
ScHTMLParser( pEditEngine, pDoc ),
mnUnusedId( SC_HTML_GLOBAL_TABLE ),
mbTitleOn( false )
{
mxGlobTable.reset( new ScHTMLGlobalTable( *pPool, *pEdit, *pList, mnUnusedId ) );
mpCurrTable = mxGlobTable.get();
}
ScHTMLQueryParser::~ScHTMLQueryParser()
{
}
sal_uLong ScHTMLQueryParser::Read( SvStream& rStrm, const String& rBaseURL )
{
SvKeyValueIteratorRef xValues;
SvKeyValueIterator* pAttributes = 0;
SfxObjectShell* pObjSh = mpDoc->GetDocumentShell();
if( pObjSh && pObjSh->IsLoading() )
{
pAttributes = pObjSh->GetHeaderAttributes();
}
else
{
/* When not loading, set up fake HTTP headers to force the SfxHTMLParser
to use UTF8 (used when pasting from clipboard) */
const sal_Char* pCharSet = rtl_getBestMimeCharsetFromTextEncoding( RTL_TEXTENCODING_UTF8 );
if( pCharSet )
{
String aContentType = String::CreateFromAscii( "text/html; charset=" );
aContentType.AppendAscii( pCharSet );
xValues = new SvKeyValueIterator;
xValues->Append( SvKeyValue( String::CreateFromAscii( OOO_STRING_SVTOOLS_HTML_META_content_type ), aContentType ) );
pAttributes = xValues;
}
}
Link aOldLink = pEdit->GetImportHdl();
pEdit->SetImportHdl( LINK( this, ScHTMLQueryParser, HTMLImportHdl ) );
sal_uLong nErr = pEdit->Read( rStrm, rBaseURL, EE_FORMAT_HTML, pAttributes );
pEdit->SetImportHdl( aOldLink );
mxGlobTable->Recalc();
nColMax = static_cast< SCCOL >( mxGlobTable->GetDocSize( tdCol ) - 1 );
nRowMax = static_cast< SCROW >( mxGlobTable->GetDocSize( tdRow ) - 1 );
return nErr;
}
const ScHTMLTable* ScHTMLQueryParser::GetGlobalTable() const
{
return mxGlobTable.get();
}
void ScHTMLQueryParser::ProcessToken( const ImportInfo& rInfo )
{
switch( rInfo.nToken )
{
// --- meta data ---
case HTML_META: MetaOn( rInfo ); break; // <meta>
// --- title handling ---
case HTML_TITLE_ON: TitleOn( rInfo ); break; // <title>
case HTML_TITLE_OFF: TitleOff( rInfo ); break; // </title>
// --- body handling ---
case HTML_BODY_ON: mpCurrTable->BodyOn( rInfo ); break; // <body>
case HTML_BODY_OFF: mpCurrTable->BodyOff( rInfo ); break; // </body>
// --- insert text ---
case HTML_TEXTTOKEN: InsertText( rInfo ); break; // any text
case HTML_LINEBREAK: mpCurrTable->BreakOn(); break; // <br>
case HTML_HEAD1_ON: // <h1>
case HTML_HEAD2_ON: // <h2>
case HTML_HEAD3_ON: // <h3>
case HTML_HEAD4_ON: // <h4>
case HTML_HEAD5_ON: // <h5>
case HTML_HEAD6_ON: // <h6>
case HTML_PARABREAK_ON: mpCurrTable->HeadingOn(); break; // <p>
// --- misc. contents ---
case HTML_ANCHOR_ON: mpCurrTable->AnchorOn(); break; // <a>
// --- table handling ---
case HTML_TABLE_ON: TableOn( rInfo ); break; // <table>
case HTML_TABLE_OFF: TableOff( rInfo ); break; // </table>
case HTML_TABLEROW_ON: mpCurrTable->RowOn( rInfo ); break; // <tr>
case HTML_TABLEROW_OFF: mpCurrTable->RowOff( rInfo ); break; // </tr>
case HTML_TABLEHEADER_ON: // <th>
case HTML_TABLEDATA_ON: mpCurrTable->DataOn( rInfo ); break; // <td>
case HTML_TABLEHEADER_OFF: // </th>
case HTML_TABLEDATA_OFF: mpCurrTable->DataOff( rInfo ); break; // </td>
case HTML_PREFORMTXT_ON: PreOn( rInfo ); break; // <pre>
case HTML_PREFORMTXT_OFF: PreOff( rInfo ); break; // </pre>
// --- formatting ---
case HTML_FONT_ON: FontOn( rInfo ); break; // <font>
case HTML_BIGPRINT_ON: // <big>
//! TODO: store current font size, use following size
mpCurrTable->PutItem( SvxFontHeightItem( maFontHeights[ 3 ], 100, ATTR_FONT_HEIGHT ) );
break;
case HTML_SMALLPRINT_ON: // <small>
//! TODO: store current font size, use preceding size
mpCurrTable->PutItem( SvxFontHeightItem( maFontHeights[ 0 ], 100, ATTR_FONT_HEIGHT ) );
break;
case HTML_BOLD_ON: // <b>
case HTML_STRONG_ON: // <strong>
mpCurrTable->PutItem( SvxWeightItem( WEIGHT_BOLD, ATTR_FONT_WEIGHT ) );
break;
case HTML_ITALIC_ON: // <i>
case HTML_EMPHASIS_ON: // <em>
case HTML_ADDRESS_ON: // <address>
case HTML_BLOCKQUOTE_ON: // <blockquote>
case HTML_BLOCKQUOTE30_ON: // <bq>
case HTML_CITIATION_ON: // <cite>
case HTML_VARIABLE_ON: // <var>
mpCurrTable->PutItem( SvxPostureItem( ITALIC_NORMAL, ATTR_FONT_POSTURE ) );
break;
case HTML_DEFINSTANCE_ON: // <dfn>
mpCurrTable->PutItem( SvxWeightItem( WEIGHT_BOLD, ATTR_FONT_WEIGHT ) );
mpCurrTable->PutItem( SvxPostureItem( ITALIC_NORMAL, ATTR_FONT_POSTURE ) );
break;
case HTML_UNDERLINE_ON: // <u>
mpCurrTable->PutItem( SvxUnderlineItem( UNDERLINE_SINGLE, ATTR_FONT_UNDERLINE ) );
break;
}
}
void ScHTMLQueryParser::InsertText( const ImportInfo& rInfo )
{
mpCurrTable->PutText( rInfo );
if( mbTitleOn )
maTitle.Append( rInfo.aText );
}
void ScHTMLQueryParser::FontOn( const ImportInfo& rInfo )
{
for( ScHTMLOptionIterator aIter( rInfo ); aIter.is(); ++aIter )
{
switch( aIter->GetToken() )
{
case HTML_O_FACE :
{
const String& rFace = aIter->GetString();
String aFontName;
xub_StrLen nPos = 0;
while( nPos != STRING_NOTFOUND )
{
// font list separator: VCL = ';' HTML = ','
String aFName = rFace.GetToken( 0, ',', nPos );
aFName.EraseLeadingAndTrailingChars();
ScGlobal::AddToken( aFontName, aFName, ';' );
}
if ( aFontName.Len() )
mpCurrTable->PutItem( SvxFontItem( FAMILY_DONTKNOW,
aFontName, EMPTY_STRING, PITCH_DONTKNOW,
RTL_TEXTENCODING_DONTKNOW, ATTR_FONT ) );
}
break;
case HTML_O_SIZE :
{
sal_uInt32 nSize = getLimitedValue< sal_uInt32 >( aIter->GetNumber(), 1, SC_HTML_FONTSIZES );
mpCurrTable->PutItem( SvxFontHeightItem( maFontHeights[ nSize - 1 ], 100, ATTR_FONT_HEIGHT ) );
}
break;
case HTML_O_COLOR :
{
Color aColor;
aIter->GetColor( aColor );
mpCurrTable->PutItem( SvxColorItem( aColor, ATTR_FONT_COLOR ) );
}
break;
}
}
}
void ScHTMLQueryParser::MetaOn( const ImportInfo& rInfo )
{
if( mpDoc->GetDocumentShell() )
{
HTMLParser* pParser = static_cast< HTMLParser* >( rInfo.pParser );
uno::Reference<document::XDocumentPropertiesSupplier> xDPS(
mpDoc->GetDocumentShell()->GetModel(), uno::UNO_QUERY_THROW);
pParser->ParseMetaOptions(
xDPS->getDocumentProperties(),
mpDoc->GetDocumentShell()->GetHeaderAttributes() );
}
}
void ScHTMLQueryParser::TitleOn( const ImportInfo& /*rInfo*/ )
{
mbTitleOn = true;
maTitle.Erase();
}
void ScHTMLQueryParser::TitleOff( const ImportInfo& rInfo )
{
if( mbTitleOn )
{
maTitle.EraseLeadingAndTrailingChars();
if( maTitle.Len() && mpDoc->GetDocumentShell() ) {
uno::Reference<document::XDocumentPropertiesSupplier> xDPS(
mpDoc->GetDocumentShell()->GetModel(), uno::UNO_QUERY_THROW);
xDPS->getDocumentProperties()->setTitle(maTitle);
}
InsertText( rInfo );
mbTitleOn = false;
}
}
void ScHTMLQueryParser::TableOn( const ImportInfo& rInfo )
{
mpCurrTable = mpCurrTable->TableOn( rInfo );
}
void ScHTMLQueryParser::TableOff( const ImportInfo& rInfo )
{
mpCurrTable = mpCurrTable->TableOff( rInfo );
}
void ScHTMLQueryParser::PreOn( const ImportInfo& rInfo )
{
mpCurrTable = mpCurrTable->PreOn( rInfo );
}
void ScHTMLQueryParser::PreOff( const ImportInfo& rInfo )
{
mpCurrTable = mpCurrTable->PreOff( rInfo );
}
void ScHTMLQueryParser::CloseTable( const ImportInfo& rInfo )
{
mpCurrTable = mpCurrTable->CloseTable( rInfo );
}
// ----------------------------------------------------------------------------
IMPL_LINK( ScHTMLQueryParser, HTMLImportHdl, const ImportInfo*, pInfo )
{
switch( pInfo->eState )
{
case HTMLIMP_START:
break;
case HTMLIMP_NEXTTOKEN:
case HTMLIMP_UNKNOWNATTR:
ProcessToken( *pInfo );
break;
case HTMLIMP_INSERTPARA:
mpCurrTable->InsertPara( *pInfo );
break;
case HTMLIMP_SETATTR:
case HTMLIMP_INSERTTEXT:
case HTMLIMP_INSERTFIELD:
break;
case HTMLIMP_END:
while( mpCurrTable->GetTableId() != SC_HTML_GLOBAL_TABLE )
CloseTable( *pInfo );
break;
default:
DBG_ERRORFILE( "ScHTMLQueryParser::HTMLImportHdl - unknown ImportInfo::eState" );
}
return 0;
}
// ============================================================================