/* -*- 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include using ::editeng::SvxBorderLine; using namespace ::com::sun::star; ScHTMLStyles::ScHTMLStyles() : maEmpty() {} void ScHTMLStyles::add(const char* pElemName, size_t nElemName, const char* pClassName, size_t nClassName, const OUString& aProp, const OUString& aValue) { if (pElemName) { OUString aElem(pElemName, nElemName, RTL_TEXTENCODING_UTF8); aElem = aElem.toAsciiLowerCase(); if (pClassName) { // Both element and class names given. ElemsType::iterator itrElem = m_ElemProps.find(aElem); if (itrElem == m_ElemProps.end()) { // new element std::pair r = m_ElemProps.insert(std::make_pair(aElem, std::make_unique())); if (!r.second) // insertion failed. return; itrElem = r.first; } NamePropsType *const pClsProps = itrElem->second.get(); OUString aClass(pClassName, nClassName, RTL_TEXTENCODING_UTF8); aClass = aClass.toAsciiLowerCase(); insertProp(*pClsProps, aClass, aProp, aValue); } else { // Element name only. Add it to the element global. insertProp(m_ElemGlobalProps, aElem, aProp, aValue); } } else { if (pClassName) { // Class name only. Add it to the global. OUString aClass(pClassName, nClassName, RTL_TEXTENCODING_UTF8); aClass = aClass.toAsciiLowerCase(); insertProp(m_GlobalProps, aClass, aProp, aValue); } } } const OUString& ScHTMLStyles::getPropertyValue( const OUString& rElem, const OUString& rClass, const OUString& rPropName) const { // First, look into the element-class storage. { auto const itr = m_ElemProps.find(rElem); if (itr != m_ElemProps.end()) { const NamePropsType *const pClasses = itr->second.get(); NamePropsType::const_iterator itr2 = pClasses->find(rClass); if (itr2 != pClasses->end()) { const PropsType *const pProps = itr2->second.get(); PropsType::const_iterator itr3 = pProps->find(rPropName); if (itr3 != pProps->end()) return itr3->second; } } } // Next, look into the class global storage. { auto const itr = m_GlobalProps.find(rClass); if (itr != m_GlobalProps.end()) { const PropsType *const pProps = itr->second.get(); PropsType::const_iterator itr2 = pProps->find(rPropName); if (itr2 != pProps->end()) return itr2->second; } } // As the last resort, look into the element global storage. { auto const itr = m_ElemGlobalProps.find(rClass); if (itr != m_ElemGlobalProps.end()) { const PropsType *const pProps = itr->second.get(); PropsType::const_iterator itr2 = pProps->find(rPropName); if (itr2 != pProps->end()) return itr2->second; } } return maEmpty; // nothing found. } void ScHTMLStyles::insertProp( NamePropsType& rStore, const OUString& aName, const OUString& aProp, const OUString& aValue) { NamePropsType::iterator itr = rStore.find(aName); if (itr == rStore.end()) { // new element std::pair r = rStore.insert(std::make_pair(aName, std::make_unique())); if (!r.second) // insertion failed. return; itr = r.first; } PropsType *const pProps = itr->second.get(); pProps->emplace(aProp, aValue); } // BASE class for HTML parser classes ScHTMLParser::ScHTMLParser( EditEngine* pEditEngine, ScDocument* pDoc ) : ScEEParser( pEditEngine ), mpDoc( pDoc ) { SvxHtmlOptions& rHtmlOptions = SvxHtmlOptions::Get(); for( sal_uInt16 nIndex = 0; nIndex < SC_HTML_FONTSIZES; ++nIndex ) maFontHeights[ nIndex ] = rHtmlOptions.GetFontSize( nIndex ) * 20; } ScHTMLParser::~ScHTMLParser() { } ScHTMLLayoutParser::ScHTMLLayoutParser( EditEngine* pEditP, const OUString& rBaseURL, const Size& aPageSizeP, ScDocument* pDocP ) : ScHTMLParser( pEditP, pDocP ), aPageSize( aPageSizeP ), aBaseURL( rBaseURL ), xLockedList( new ScRangeList ), pLocalColOffset( new ScHTMLColOffset ), nFirstTableCell(0), nTableLevel(0), nTable(0), nMaxTable(0), nColCntStart(0), nMaxCol(0), nTableWidth(0), nColOffset(0), nColOffsetStart(0), nOffsetTolerance( SC_HTML_OFFSET_TOLERANCE_SMALL ), bFirstRow( true ), bTabInTabCell( false ), bInCell( false ), bInTitle( false ) { MakeColNoRef( pLocalColOffset, 0, 0, 0, 0 ); MakeColNoRef( &maColOffset, 0, 0, 0, 0 ); } ScHTMLLayoutParser::~ScHTMLLayoutParser() { while ( !aTableStack.empty() ) { ScHTMLTableStackEntry * pS = aTableStack.top().get(); if ( pS->pLocalColOffset != pLocalColOffset ) delete pS->pLocalColOffset; aTableStack.pop(); } delete pLocalColOffset; if ( pTables ) { for( const auto& rEntry : *pTables) delete rEntry.second; pTables.reset(); } } ErrCode ScHTMLLayoutParser::Read( SvStream& rStream, const OUString& rBaseURL ) { Link aOldLink = pEdit->GetHtmlImportHdl(); pEdit->SetHtmlImportHdl( LINK( this, ScHTMLLayoutParser, HTMLImportHdl ) ); SfxObjectShell* pObjSh = mpDoc->GetDocumentShell(); bool bLoading = pObjSh && pObjSh->IsLoading(); SvKeyValueIteratorRef xValues; SvKeyValueIterator* pAttributes = nullptr; 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 char* pCharSet = rtl_getBestMimeCharsetFromTextEncoding( RTL_TEXTENCODING_UTF8 ); if( pCharSet ) { OUString aContentType = "text/html; charset=" + OUString::createFromAscii( pCharSet ); xValues = new SvKeyValueIterator; xValues->Append( SvKeyValue( OOO_STRING_SVTOOLS_HTML_META_content_type, aContentType ) ); pAttributes = xValues.get(); } } ErrCode nErr = pEdit->Read( rStream, rBaseURL, EETextFormat::Html, pAttributes ); pEdit->SetHtmlImportHdl( aOldLink ); // Create column width Adjust(); OutputDevice* pDefaultDev = Application::GetDefaultDevice(); sal_uInt16 nCount = maColOffset.size(); sal_uLong nOff = maColOffset[0]; Size aSize; for ( sal_uInt16 j = 1; j < nCount; j++ ) { aSize.setWidth( maColOffset[j] - nOff ); aSize = pDefaultDev->PixelToLogic( aSize, MapMode( MapUnit::MapTwip ) ); maColWidths[ j-1 ] = aSize.Width(); nOff = maColOffset[j]; } return nErr; } const ScHTMLTable* ScHTMLLayoutParser::GetGlobalTable() const { return nullptr; } void ScHTMLLayoutParser::NewActEntry( const ScEEParseEntry* pE ) { ScEEParser::NewActEntry( pE ); if ( pE ) { if ( !pE->aSel.HasRange() ) { // Completely empty, following text ends up in the same paragraph! mxActEntry->aSel.nStartPara = pE->aSel.nEndPara; mxActEntry->aSel.nStartPos = pE->aSel.nEndPos; } } mxActEntry->aSel.nEndPara = mxActEntry->aSel.nStartPara; mxActEntry->aSel.nEndPos = mxActEntry->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() ) { // Did not attach a paragraph, but empty, do nothing } else { OSL_FAIL( "EntryEnd: EditEngine ESelection End < Start" ); } } void ScHTMLLayoutParser::NextRow( const HtmlImportInfo* pInfo ) { if ( bInCell ) CloseEntry( pInfo ); if ( nRowMax < ++nRowCnt ) nRowMax = nRowCnt; nColCnt = nColCntStart; nColOffset = nColOffsetStart; bFirstRow = false; } bool ScHTMLLayoutParser::SeekOffset( const ScHTMLColOffset* pOffset, sal_uInt16 nOffset, SCCOL* pCol, sal_uInt16 nOffsetTol ) { OSL_ENSURE( pOffset, "ScHTMLLayoutParser::SeekOffset - illegal call" ); ScHTMLColOffset::const_iterator it = pOffset->find( nOffset ); bool bFound = it != pOffset->end(); sal_uInt16 nPos = it - pOffset->begin(); *pCol = static_cast(nPos); if ( bFound ) return true; sal_uInt16 nCount = pOffset->size(); if ( !nCount ) return false; // nPos is the position of insertion, that's where the next higher one is (or isn't) if ( nPos < nCount && (((*pOffset)[nPos] - nOffsetTol) <= nOffset) ) return true; // Not smaller than everything else? Then compare with the next lower one else if ( nPos && (((*pOffset)[nPos-1] + nOffsetTol) >= nOffset) ) { (*pCol)--; return true; } return false; } void ScHTMLLayoutParser::MakeCol( ScHTMLColOffset* pOffset, sal_uInt16& nOffset, sal_uInt16& nWidth, sal_uInt16 nOffsetTol, sal_uInt16 nWidthTol ) { OSL_ENSURE( pOffset, "ScHTMLLayoutParser::MakeCol - illegal call" ); SCCOL nPos; if ( SeekOffset( pOffset, nOffset, &nPos, nOffsetTol ) ) nOffset = static_cast((*pOffset)[nPos]); else pOffset->insert( nOffset ); if ( nWidth ) { if ( SeekOffset( pOffset, nOffset + nWidth, &nPos, nWidthTol ) ) nWidth = static_cast((*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 ) { OSL_ENSURE( pOffset, "ScHTMLLayoutParser::MakeColNoRef - illegal call" ); SCCOL nPos; if ( SeekOffset( pOffset, nOffset, &nPos, nOffsetTol ) ) nOffset = static_cast((*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 ) { OSL_ENSURE( pOffset, "ScHTMLLayoutParser::ModifyOffset - illegal call" ); SCCOL nPos; if ( !SeekOffset( pOffset, nOldOffset, &nPos, nOffsetTol ) ) { if ( SeekOffset( pOffset, nNewOffset, &nPos, nOffsetTol ) ) nNewOffset = static_cast((*pOffset)[nPos]); else pOffset->insert( nNewOffset ); return ; } nOldOffset = static_cast((*pOffset)[nPos]); SCCOL nPos2; if ( SeekOffset( pOffset, nNewOffset, &nPos2, nOffsetTol ) ) { nNewOffset = static_cast((*pOffset)[nPos2]); return ; } tools::Long nDiff = nNewOffset - nOldOffset; if ( nDiff < 0 ) { do { const_cast((*pOffset)[nPos]) += nDiff; } while ( nPos-- ); } else { do { const_cast((*pOffset)[nPos]) += nDiff; } while ( ++nPos < static_cast(pOffset->size()) ); } } void ScHTMLLayoutParser::SkipLocked( ScEEParseEntry* pE, bool bJoin ) { if ( !mpDoc->ValidCol(pE->nCol) ) return; // Or else this would create a wrong value at ScAddress (chance for an infinite loop)! bool bBadCol = false; bool bAgain; ScRange aRange( pE->nCol, pE->nRow, 0, pE->nCol + pE->nColOverlap - 1, pE->nRow + pE->nRowOverlap - 1, 0 ); do { bAgain = false; for ( size_t i = 0, nRanges = xLockedList->size(); i < nRanges; ++i ) { ScRange & rR = (*xLockedList)[i]; if ( rR.Intersects( aRange ) ) { pE->nCol = rR.aEnd.Col() + 1; SCCOL nTmp = pE->nCol + pE->nColOverlap - 1; if ( pE->nCol > mpDoc->MaxCol() || nTmp > mpDoc->MaxCol() ) bBadCol = true; else { bAgain = true; aRange.aStart.SetCol( pE->nCol ); aRange.aEnd.SetCol( nTmp ); } break; } } } while ( bAgain ); if ( bJoin && !bBadCol ) xLockedList->Join( aRange ); } void ScHTMLLayoutParser::Adjust() { xLockedList->RemoveAll(); std::stack< std::unique_ptr > aStack; sal_uInt16 nTab = 0; SCCOL nLastCol = SCCOL_MAX; SCROW nNextRow = 0; SCROW nCurRow = 0; sal_uInt16 nPageWidth = static_cast(aPageSize.Width()); InnerMap* pTab = nullptr; for (auto& pE : maList) { if ( pE->nTab < nTab ) { // Table finished if ( !aStack.empty() ) { std::unique_ptr pS = std::move(aStack.top()); aStack.pop(); nLastCol = pS->nLastCol; nNextRow = pS->nNextRow; nCurRow = pS->nCurRow; } nTab = pE->nTab; if (pTables) { OuterMap::const_iterator it = pTables->find( nTab ); if ( it != pTables->end() ) pTab = it->second; } } SCROW nRow = pE->nRow; if ( pE->nCol <= nLastCol ) { // Next row if ( pE->nRow < nNextRow ) pE->nRow = nCurRow = nNextRow; else nCurRow = nNextRow = pE->nRow; SCROW nR = 0; if ( pTab ) { InnerMap::const_iterator it = pTab->find( nCurRow ); if ( it != pTab->end() ) nR = it->second; } if ( nR ) nNextRow += nR; else nNextRow++; } else pE->nRow = nCurRow; nLastCol = pE->nCol; // Read column if ( pE->nTab > nTab ) { // New table aStack.push( std::make_unique( nLastCol, nNextRow, nCurRow ) ); nTab = pE->nTab; if ( pTables ) { OuterMap::const_iterator it = pTables->find( nTab ); if ( it != pTables->end() ) pTab = it->second; } // New line spacing SCROW nR = 0; if ( pTab ) { InnerMap::const_iterator it = pTab->find( nCurRow ); if ( it != pTab->end() ) nR = it->second; } if ( nR ) nNextRow = nCurRow + nR; else nNextRow = nCurRow + 1; } if ( nTab == 0 ) pE->nWidth = nPageWidth; else { // Real table, no paragraphs on the field if ( pTab ) { SCROW nRowSpan = pE->nRowOverlap; for ( SCROW j=0; j < nRowSpan; j++ ) { // RowSpan resulting from merged rows SCROW nRows = 0; InnerMap::const_iterator it = pTab->find( nRow+j ); if ( it != pTab->end() ) nRows = it->second; if ( nRows > 1 ) { pE->nRowOverlap += nRows - 1; if ( j == 0 ) { // Merged rows move the next row SCROW nTmp = nCurRow + nRows; if ( nNextRow < nTmp ) nNextRow = nTmp; } } } } } // Real column (void)SeekOffset( &maColOffset, pE->nOffset, &pE->nCol, nOffsetTolerance ); SCCOL nColBeforeSkip = pE->nCol; SkipLocked(pE.get(), false); if ( pE->nCol != nColBeforeSkip ) { SCCOL nCount = static_cast(maColOffset.size()); if ( nCount <= pE->nCol ) { pE->nOffset = static_cast(maColOffset[nCount-1]); MakeCol( &maColOffset, pE->nOffset, pE->nWidth, nOffsetTolerance, nOffsetTolerance ); } else { pE->nOffset = static_cast(maColOffset[pE->nCol]); } } SCCOL nPos; if ( pE->nWidth && SeekOffset( &maColOffset, pE->nOffset + pE->nWidth, &nPos, nOffsetTolerance ) ) pE->nColOverlap = (nPos > pE->nCol ? nPos - pE->nCol : 1); else { //FIXME: This may not be correct, but works anyway ... pE->nColOverlap = 1; } xLockedList->Join( ScRange( pE->nCol, pE->nRow, 0, pE->nCol + pE->nColOverlap - 1, pE->nRow + pE->nRowOverlap - 1, 0 ) ); // Take over MaxDimensions SCCOL nColTmp = pE->nCol + pE->nColOverlap; if ( nColMax < nColTmp ) nColMax = nColTmp; SCROW nRowTmp = pE->nRow + pE->nRowOverlap; if ( nRowMax < nRowTmp ) nRowMax = nRowTmp; } } sal_uInt16 ScHTMLLayoutParser::GetWidth( const ScEEParseEntry* pE ) { if ( pE->nWidth ) return pE->nWidth; sal_Int32 nTmp = std::min( static_cast( pE->nCol - nColCntStart + pE->nColOverlap), static_cast( pLocalColOffset->size() - 1)); SCCOL nPos = (nTmp < 0 ? 0 : static_cast(nTmp)); sal_uInt16 nOff2 = static_cast((*pLocalColOffset)[nPos]); if ( pE->nOffset < nOff2 ) return nOff2 - pE->nOffset; return 0; } void ScHTMLLayoutParser::SetWidths() { SCCOL nCol; if ( !nTableWidth ) nTableWidth = static_cast(aPageSize.Width()); SCCOL nColsPerRow = nMaxCol - nColCntStart; if ( nColsPerRow <= 0 ) nColsPerRow = 1; if ( pLocalColOffset->size() <= 2 ) { // Only PageSize, there was no width setting sal_uInt16 nWidth = nTableWidth / static_cast(nColsPerRow); sal_uInt16 nOff = nColOffsetStart; pLocalColOffset->clear(); for ( nCol = 0; nCol <= nColsPerRow; ++nCol, nOff = nOff + nWidth ) { MakeColNoRef( pLocalColOffset, nOff, 0, 0, 0 ); } nTableWidth = static_cast(pLocalColOffset->back() - pLocalColOffset->front()); for ( size_t i = nFirstTableCell, nListSize = maList.size(); i < nListSize; ++i ) { auto& pE = maList[ i ]; if ( pE->nTab == nTable ) { pE->nOffset = static_cast((*pLocalColOffset)[pE->nCol - nColCntStart]); pE->nWidth = 0; // to be recalculated later } } } else { // Some without width // Why actually no pE? if ( nFirstTableCell < maList.size() ) { std::unique_ptr pOffsets(new sal_uInt16[ nColsPerRow+1 ]); memset( pOffsets.get(), 0, (nColsPerRow+1) * sizeof(sal_uInt16) ); std::unique_ptr pWidths(new sal_uInt16[ nColsPerRow ]); memset( pWidths.get(), 0, nColsPerRow * sizeof(sal_uInt16) ); pOffsets[0] = nColOffsetStart; for ( size_t i = nFirstTableCell, nListSize = maList.size(); i < nListSize; ++i ) { auto& pE = maList[ i ]; 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; bool bFound = false; SCCOL nHere = 0; SCCOL nStop = std::min( static_cast(nCol + pE->nColOverlap), nColsPerRow ); for ( ; nCol < nStop; nCol++ ) { if ( pWidths[nCol] ) nTotal = nTotal + pWidths[nCol]; else { if ( bFound ) { bFound = false; break; // for } bFound = true; nHere = nCol; } } if ( bFound && pE->nWidth > nTotal ) pWidths[nHere] = pE->nWidth - nTotal; } } } } 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->clear(); for ( nCol = 0; nCol <= nColsPerRow; nCol++ ) { MakeColNoRef( pLocalColOffset, pOffsets[nCol], 0, 0, 0 ); } nTableWidth = pOffsets[nColsPerRow] - pOffsets[0]; for ( size_t i = nFirstTableCell, nListSize = maList.size(); i < nListSize; ++i ) { auto& pE = maList[ i ]; if ( pE->nTab == nTable ) { nCol = pE->nCol - nColCntStart; OSL_ENSURE( 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; } } } } } if ( !pLocalColOffset->empty() ) { sal_uInt16 nMax = static_cast(pLocalColOffset->back()); if ( aPageSize.Width() < nMax ) aPageSize.setWidth( nMax ); if (nTableLevel == 0) { // Local table is very outer table, create missing offsets. for (auto it = pLocalColOffset->begin(); it != pLocalColOffset->end(); ++it) { // Only exact offsets, do not use MakeColNoRef(). if (maColOffset.find(*it) == maColOffset.end()) maColOffset.insert(*it); } } } for ( size_t i = nFirstTableCell, nListSize = maList.size(); i < nListSize; ++i ) { auto& pE = maList[ i ]; if ( pE->nTab == nTable ) { if ( !pE->nWidth ) { pE->nWidth = GetWidth(pE.get()); OSL_ENSURE( pE->nWidth, "SetWidths: pE->nWidth == 0" ); } MakeCol( &maColOffset, pE->nOffset, pE->nWidth, nOffsetTolerance, nOffsetTolerance ); } } } 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 ); // Change of columns to the right if ( nCol < pE->nCol ) { // Replaced nCol = pE->nCol - nColCntStart; SCCOL nCount = static_cast(pLocalColOffset->size()); if ( nCol < nCount ) nColOffset = static_cast((*pLocalColOffset)[nCol]); else nColOffset = static_cast((*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( const HtmlImportInfo* pInfo ) { bInCell = false; if ( bTabInTabCell ) { // From the stack in TableOff bTabInTabCell = false; NewActEntry(maList.back().get()); // New free flying mxActEntry return ; } if (mxActEntry->nTab == 0) mxActEntry->nWidth = static_cast(aPageSize.Width()); Colonize(mxActEntry.get()); nColCnt = mxActEntry->nCol + mxActEntry->nColOverlap; if ( nMaxCol < nColCnt ) nMaxCol = nColCnt; // TableStack MaxCol if ( nColMax < nColCnt ) nColMax = nColCnt; // Global MaxCol for ScEEParser GetDimensions! EntryEnd(mxActEntry.get(), pInfo->aSelection); ESelection& rSel = mxActEntry->aSel; while ( rSel.nStartPara < rSel.nEndPara && pEdit->GetTextLen( rSel.nStartPara ) == 0 ) { // Strip preceding empty paragraphs rSel.nStartPara++; } while ( rSel.nEndPos == 0 && rSel.nEndPara > rSel.nStartPara ) { // Strip successive empty paragraphs rSel.nEndPara--; rSel.nEndPos = pEdit->GetTextLen( rSel.nEndPara ); } if ( rSel.nStartPara > rSel.nEndPara ) { // Gives GPF in CreateTextObject OSL_FAIL( "CloseEntry: EditEngine ESelection Start > End" ); rSel.nEndPara = rSel.nStartPara; } if ( rSel.HasRange() ) mxActEntry->aItemSet.Put( ScLineBreakCell(true) ); maList.push_back(mxActEntry); NewActEntry(mxActEntry.get()); // New free flying mxActEntry } IMPL_LINK( ScHTMLLayoutParser, HTMLImportHdl, HtmlImportInfo&, rInfo, void ) { switch ( rInfo.eState ) { case HtmlImportState::NextToken: ProcToken( &rInfo ); break; case HtmlImportState::Start: break; case HtmlImportState::End: if ( rInfo.aSelection.nEndPos ) { // If text remains: create paragraph, without calling CloseEntry(). if( bInCell ) // ...but only in opened table cells. { bInCell = false; NextRow( &rInfo ); bInCell = true; } CloseEntry( &rInfo ); } while ( nTableLevel > 0 ) TableOff( &rInfo ); // close tables, if missing break; case HtmlImportState::SetAttr: break; case HtmlImportState::InsertText: break; case HtmlImportState::InsertPara: if ( nTableLevel < 1 ) { CloseEntry( &rInfo ); NextRow( &rInfo ); } break; case HtmlImportState::InsertField: break; default: OSL_FAIL("HTMLImportHdl: unknown ImportInfo.eState"); } } void ScHTMLLayoutParser::TableDataOn( HtmlImportInfo* pInfo ) { if ( bInCell ) CloseEntry( pInfo ); if ( !nTableLevel ) { OSL_FAIL( "dumbo doc! or without previous " ); TableOn( pInfo ); } bInCell = true; bool bHorJustifyCenterTH = (pInfo->nToken == HtmlTokenId::TABLEHEADER_ON); const HTMLOptions& rOptions = static_cast(pInfo->pParser)->GetOptions(); for (const auto & rOption : rOptions) { switch( rOption.GetToken() ) { case HtmlOptionId::COLSPAN: { mxActEntry->nColOverlap = static_cast(rOption.GetString().toInt32()); } break; case HtmlOptionId::ROWSPAN: { mxActEntry->nRowOverlap = static_cast(rOption.GetString().toInt32()); } break; case HtmlOptionId::ALIGN: { bHorJustifyCenterTH = false; SvxCellHorJustify eVal; const OUString& rOptVal = rOption.GetString(); if ( rOptVal.equalsIgnoreAsciiCase( OOO_STRING_SVTOOLS_HTML_AL_right ) ) eVal = SvxCellHorJustify::Right; else if ( rOptVal.equalsIgnoreAsciiCase( OOO_STRING_SVTOOLS_HTML_AL_center ) ) eVal = SvxCellHorJustify::Center; else if ( rOptVal.equalsIgnoreAsciiCase( OOO_STRING_SVTOOLS_HTML_AL_left ) ) eVal = SvxCellHorJustify::Left; else eVal = SvxCellHorJustify::Standard; if ( eVal != SvxCellHorJustify::Standard ) mxActEntry->aItemSet.Put(SvxHorJustifyItem(eVal, ATTR_HOR_JUSTIFY)); } break; case HtmlOptionId::VALIGN: { SvxCellVerJustify eVal; const OUString& rOptVal = rOption.GetString(); if ( rOptVal.equalsIgnoreAsciiCase( OOO_STRING_SVTOOLS_HTML_VA_top ) ) eVal = SvxCellVerJustify::Top; else if ( rOptVal.equalsIgnoreAsciiCase( OOO_STRING_SVTOOLS_HTML_VA_middle ) ) eVal = SvxCellVerJustify::Center; else if ( rOptVal.equalsIgnoreAsciiCase( OOO_STRING_SVTOOLS_HTML_VA_bottom ) ) eVal = SvxCellVerJustify::Bottom; else eVal = SvxCellVerJustify::Standard; mxActEntry->aItemSet.Put(SvxVerJustifyItem(eVal, ATTR_VER_JUSTIFY)); } break; case HtmlOptionId::WIDTH: { mxActEntry->nWidth = GetWidthPixel(rOption); } break; case HtmlOptionId::BGCOLOR: { Color aColor; rOption.GetColor( aColor ); mxActEntry->aItemSet.Put(SvxBrushItem(aColor, ATTR_BACKGROUND)); } break; case HtmlOptionId::SDVAL: { mxActEntry->pValStr = rOption.GetString(); } break; case HtmlOptionId::SDNUM: { mxActEntry->pNumStr = rOption.GetString(); } break; default: break; } } mxActEntry->nCol = nColCnt; mxActEntry->nRow = nRowCnt; mxActEntry->nTab = nTable; if ( bHorJustifyCenterTH ) mxActEntry->aItemSet.Put( SvxHorJustifyItem( SvxCellHorJustify::Center, ATTR_HOR_JUSTIFY) ); } void ScHTMLLayoutParser::TableRowOn( const HtmlImportInfo* pInfo ) { if ( nColCnt > nColCntStart ) NextRow( pInfo ); // The optional TableRowOff wasn't there nColOffset = nColOffsetStart; } void ScHTMLLayoutParser::TableRowOff( const HtmlImportInfo* pInfo ) { NextRow( pInfo ); } void ScHTMLLayoutParser::TableDataOff( const HtmlImportInfo* pInfo ) { if ( bInCell ) CloseEntry( pInfo ); // Only if it really was one } void ScHTMLLayoutParser::TableOn( HtmlImportInfo* pInfo ) { if ( ++nTableLevel > 1 ) { // Table in Table sal_uInt16 nTmpColOffset = nColOffset; // Will be changed in Colonize() Colonize(mxActEntry.get()); aTableStack.push( std::make_unique( mxActEntry, xLockedList, pLocalColOffset, nFirstTableCell, nRowCnt, nColCntStart, nMaxCol, nTable, nTableWidth, nColOffset, nColOffsetStart, bFirstRow ) ); sal_uInt16 nLastWidth = nTableWidth; nTableWidth = GetWidth(mxActEntry.get()); if ( nTableWidth == nLastWidth && nMaxCol - nColCntStart > 1 ) { // There must be more than one, so this one cannot be enough nTableWidth = nLastWidth / static_cast((nMaxCol - nColCntStart)); } nLastWidth = nTableWidth; if ( pInfo->nToken == HtmlTokenId::TABLE_ON ) { // It can still be TD or TH, if we didn't have a TABLE earlier const HTMLOptions& rOptions = static_cast(pInfo->pParser)->GetOptions(); for (const auto & rOption : rOptions) { switch( rOption.GetToken() ) { case HtmlOptionId::WIDTH: { // Percent: of document width or outer cell nTableWidth = GetWidthPixel( rOption ); } break; case HtmlOptionId::BORDER: // Border is: ((pOption->GetString().Len() == 0) || (pOption->GetNumber() != 0)); break; default: break; } } } bInCell = false; if ( bTabInTabCell && (nTableWidth >= nLastWidth) ) { // Multiple tables in one cell, underneath each other bTabInTabCell = false; NextRow( pInfo ); } else { // It start's in this cell or next to each other bTabInTabCell = false; nColCntStart = nColCnt; nColOffset = nTmpColOffset; nColOffsetStart = nColOffset; } NewActEntry(!maList.empty() ? maList.back().get() : nullptr); // New free flying mxActEntry xLockedList = new ScRangeList; } else { // Simple table at the document level EntryEnd(mxActEntry.get(), pInfo->aSelection); if (mxActEntry->aSel.HasRange()) { // Flying text left CloseEntry( pInfo ); NextRow( pInfo ); } aTableStack.push( std::make_unique( mxActEntry, xLockedList, pLocalColOffset, nFirstTableCell, 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 == HtmlTokenId::TABLE_ON ) { // It can still be TD or TH, if we didn't have a TABLE earlier const HTMLOptions& rOptions = static_cast(pInfo->pParser)->GetOptions(); for (const auto & rOption : rOptions) { switch( rOption.GetToken() ) { case HtmlOptionId::WIDTH: { // Percent: of document width or outer cell nTableWidth = GetWidthPixel( rOption ); } break; case HtmlOptionId::BORDER: //BorderOn is: ((pOption->GetString().Len() == 0) || (pOption->GetNumber() != 0)); break; default: break; } } } } nTable = ++nMaxTable; bFirstRow = true; nFirstTableCell = maList.size(); pLocalColOffset = new ScHTMLColOffset; MakeColNoRef( pLocalColOffset, nColOffsetStart, 0, 0, 0 ); } void ScHTMLLayoutParser::TableOff( const HtmlImportInfo* pInfo ) { if ( bInCell ) CloseEntry( pInfo ); if ( nColCnt > nColCntStart ) TableRowOff( pInfo ); // The optional TableRowOff wasn't if ( !nTableLevel ) { OSL_FAIL( "dumbo doc!
without opening " ); return ; } if ( --nTableLevel > 0 ) { // Table in Table done if ( !aTableStack.empty() ) { std::unique_ptr pS = std::move(aTableStack.top()); aTableStack.pop(); auto& pE = pS->xCellEntry; SCROW nRows = nRowCnt - pS->nRowCnt; if ( nRows > 1 ) { // Insert size of table at this position SCROW nRow = pS->nRowCnt; sal_uInt16 nTab = pS->nTable; if ( !pTables ) pTables.reset( new OuterMap ); // Height of outer table OuterMap::const_iterator it = pTables->find( nTab ); InnerMap* pTab1; if ( it == pTables->end() ) { pTab1 = new InnerMap; (*pTables)[ nTab ] = pTab1; } else pTab1 = it->second; SCROW nRowSpan = pE->nRowOverlap; SCROW nRowKGV; SCROW nRowsPerRow1; // Outer table SCROW nRowsPerRow2; // Inner table if ( nRowSpan > 1 ) { // LCM to which we can map the inner and outer rows nRowKGV = std::lcm( nRowSpan, nRows ); nRowsPerRow1 = nRowKGV / nRowSpan; nRowsPerRow2 = nRowKGV / nRows; } else { nRowKGV = nRowsPerRow1 = nRows; nRowsPerRow2 = 1; } InnerMap* pTab2 = nullptr; if ( nRowsPerRow2 > 1 ) { // Height of the inner table pTab2 = new InnerMap; (*pTables)[ nTable ] = pTab2; } // Abuse void* Data entry of the Table class for height mapping if ( nRowKGV > 1 ) { if ( nRowsPerRow1 > 1 ) { // Outer for ( SCROW j=0; j < nRowSpan; j++ ) { sal_uLong nRowKey = nRow + j; SCROW nR = (*pTab1)[ nRowKey ]; if ( !nR ) (*pTab1)[ nRowKey ] = nRowsPerRow1; else if ( nRowsPerRow1 > nR ) (*pTab1)[ nRowKey ] = nRowsPerRow1; //TODO: How can we improve on this? else if ( nRowsPerRow1 < nR && nRowSpan == 1 && nTable == nMaxTable ) { // Still some space left, merge in a better way (if possible) SCROW nAdd = nRowsPerRow1 - (nR % nRowsPerRow1); nR += nAdd; if ( (nR % nRows) == 0 ) { // Only if representable SCROW nR2 = (*pTab1)[ nRowKey+1 ]; if ( nR2 > nAdd ) { // Only if we really have enough space (*pTab1)[ nRowKey ] = nR; (*pTab1)[ nRowKey+1 ] = nR2 - nAdd; nRowsPerRow2 = nR / nRows; } } } } } if ( nRowsPerRow2 > 1 ) { // Inner if ( !pTab2 ) { // nRowsPerRow2 could be've been incremented pTab2 = new InnerMap; (*pTables)[ nTable ] = pTab2; } for ( SCROW j=0; j < nRows; j++ ) { sal_uLong nRowKey = nRow + j; (*pTab2)[ nRowKey ] = 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; pLocalColOffset = pS->pLocalColOffset; // mxActEntry is kept around if a table is started in the same row // (anything's possible in HTML); will be deleted by CloseEntry mxActEntry = pE; } bTabInTabCell = true; bInCell = true; } else { // Simple table finished SetWidths(); nMaxCol = 0; nTable = 0; if ( !aTableStack.empty() ) { ScHTMLTableStackEntry* pS = aTableStack.top().get(); delete pLocalColOffset; pLocalColOffset = pS->pLocalColOffset; aTableStack.pop(); } } } void ScHTMLLayoutParser::Image( HtmlImportInfo* pInfo ) { mxActEntry->maImageList.push_back(std::make_unique()); ScHTMLImage* pImage = mxActEntry->maImageList.back().get(); const HTMLOptions& rOptions = static_cast(pInfo->pParser)->GetOptions(); for (const auto & rOption : rOptions) { switch( rOption.GetToken() ) { case HtmlOptionId::SRC: { pImage->aURL = INetURLObject::GetAbsURL( aBaseURL, rOption.GetString() ); } break; case HtmlOptionId::ALT: { if (!mxActEntry->bHasGraphic) { // ALT text only if not any image loaded if (!mxActEntry->aAltText.isEmpty()) mxActEntry->aAltText += "; "; mxActEntry->aAltText += rOption.GetString(); } } break; case HtmlOptionId::WIDTH: { pImage->aSize.setWidth( static_cast(rOption.GetNumber()) ); } break; case HtmlOptionId::HEIGHT: { pImage->aSize.setHeight( static_cast(rOption.GetNumber()) ); } break; case HtmlOptionId::HSPACE: { pImage->aSpace.setX( static_cast(rOption.GetNumber()) ); } break; case HtmlOptionId::VSPACE: { pImage->aSpace.setY( static_cast(rOption.GetNumber()) ); } break; default: break; } } if (pImage->aURL.isEmpty()) { OSL_FAIL( "Image: graphic without URL ?!?" ); return ; } sal_uInt16 nFormat; std::unique_ptr pGraphic(new Graphic); GraphicFilter& rFilter = GraphicFilter::GetGraphicFilter(); if ( ERRCODE_NONE != GraphicFilter::LoadGraphic( pImage->aURL, pImage->aFilterName, *pGraphic, &rFilter, &nFormat ) ) { return ; // Bad luck } if (!mxActEntry->bHasGraphic) { // discard any ALT text in this cell if we have any image mxActEntry->bHasGraphic = true; mxActEntry->aAltText.clear(); } pImage->aFilterName = rFilter.GetImportFormatName( nFormat ); pImage->pGraphic = std::move( pGraphic ); if ( !(pImage->aSize.Width() && pImage->aSize.Height()) ) { OutputDevice* pDefaultDev = Application::GetDefaultDevice(); pImage->aSize = pDefaultDev->LogicToPixel( pImage->pGraphic->GetPrefSize(), pImage->pGraphic->GetPrefMapMode() ); } if (mxActEntry->maImageList.empty()) return; tools::Long nWidth = 0; for (const std::unique_ptr & pI : mxActEntry->maImageList) { if ( pI->nDir & nHorizontal ) nWidth += pI->aSize.Width() + 2 * pI->aSpace.X(); else nWidth = 0; } if ( mxActEntry->nWidth && (nWidth + pImage->aSize.Width() + 2 * pImage->aSpace.X() >= mxActEntry->nWidth) ) mxActEntry->maImageList.back()->nDir = nVertical; } void ScHTMLLayoutParser::ColOn( HtmlImportInfo* pInfo ) { const HTMLOptions& rOptions = static_cast(pInfo->pParser)->GetOptions(); for (const auto & rOption : rOptions) { if( rOption.GetToken() == HtmlOptionId::WIDTH ) { sal_uInt16 nVal = GetWidthPixel( rOption ); MakeCol( pLocalColOffset, nColOffset, nVal, 0, 0 ); nColOffset = nColOffset + nVal; } } } sal_uInt16 ScHTMLLayoutParser::GetWidthPixel( const HTMLOption& rOption ) { const OUString& rOptVal = rOption.GetString(); if ( rOptVal.indexOf('%') != -1 ) { // Percent sal_uInt16 nW = (nTableWidth ? nTableWidth : static_cast(aPageSize.Width())); return static_cast((rOption.GetNumber() * nW) / 100); } else { if ( rOptVal.indexOf('*') != -1 ) { // Relative to what? // TODO: Collect all relative values in ColArray and then MakeCol return 0; } else return static_cast(rOption.GetNumber()); // Pixel } } void ScHTMLLayoutParser::AnchorOn( HtmlImportInfo* pInfo ) { const HTMLOptions& rOptions = static_cast(pInfo->pParser)->GetOptions(); for (const auto & rOption : rOptions) { if( rOption.GetToken() == HtmlOptionId::NAME ) mxActEntry->pName = rOption.GetString(); } } bool ScHTMLLayoutParser::IsAtBeginningOfText( const HtmlImportInfo* pInfo ) { ESelection& rSel = mxActEntry->aSel; return rSel.nStartPara == rSel.nEndPara && rSel.nStartPara <= pInfo->aSelection.nEndPara && pEdit->GetTextLen( rSel.nStartPara ) == 0; } void ScHTMLLayoutParser::FontOn( HtmlImportInfo* pInfo ) { if ( !IsAtBeginningOfText( pInfo ) ) return; // Only at the start of the text; applies to whole line const HTMLOptions& rOptions = static_cast(pInfo->pParser)->GetOptions(); for (const auto & rOption : rOptions) { switch( rOption.GetToken() ) { case HtmlOptionId::FACE : { const OUString& rFace = rOption.GetString(); OUStringBuffer aFontName; sal_Int32 nPos = 0; while( nPos != -1 ) { // Font list, VCL uses the semicolon as separator // HTML uses the comma OUString aFName = rFace.getToken( 0, ',', nPos ); aFName = comphelper::string::strip(aFName, ' '); if( !aFontName.isEmpty() ) aFontName.append(";"); aFontName.append(aFName); } if ( !aFontName.isEmpty() ) mxActEntry->aItemSet.Put( SvxFontItem( FAMILY_DONTKNOW, aFontName.makeStringAndClear(), EMPTY_OUSTRING, PITCH_DONTKNOW, RTL_TEXTENCODING_DONTKNOW, ATTR_FONT ) ); } break; case HtmlOptionId::SIZE : { sal_uInt16 nSize = static_cast(rOption.GetNumber()); if ( nSize == 0 ) nSize = 1; else if ( nSize > SC_HTML_FONTSIZES ) nSize = SC_HTML_FONTSIZES; mxActEntry->aItemSet.Put( SvxFontHeightItem( maFontHeights[nSize-1], 100, ATTR_FONT_HEIGHT ) ); } break; case HtmlOptionId::COLOR : { Color aColor; rOption.GetColor( aColor ); mxActEntry->aItemSet.Put( SvxColorItem( aColor, ATTR_FONT_COLOR ) ); } break; default: break; } } } void ScHTMLLayoutParser::ProcToken( HtmlImportInfo* pInfo ) { switch ( pInfo->nToken ) { case HtmlTokenId::META: { HTMLParser* pParser = static_cast(pInfo->pParser); uno::Reference xDPS( mpDoc->GetDocumentShell()->GetModel(), uno::UNO_QUERY_THROW); pParser->ParseMetaOptions( xDPS->getDocumentProperties(), mpDoc->GetDocumentShell()->GetHeaderAttributes() ); } break; case HtmlTokenId::TITLE_ON: { bInTitle = true; aString.clear(); } break; case HtmlTokenId::TITLE_OFF: { if ( bInTitle && !aString.isEmpty() ) { // Remove blanks from line brakes aString = aString.trim(); uno::Reference xDPS( mpDoc->GetDocumentShell()->GetModel(), uno::UNO_QUERY_THROW); xDPS->getDocumentProperties()->setTitle(aString); } bInTitle = false; } break; case HtmlTokenId::TABLE_ON: { TableOn( pInfo ); } break; case HtmlTokenId::COL_ON: { ColOn( pInfo ); } break; case HtmlTokenId::TABLEHEADER_ON: // Opens row { if ( bInCell ) CloseEntry( pInfo ); // Do not set bInCell to true, TableDataOn does that mxActEntry->aItemSet.Put( SvxWeightItem( WEIGHT_BOLD, ATTR_FONT_WEIGHT) ); [[fallthrough]]; } case HtmlTokenId::TABLEDATA_ON: // Opens cell { TableDataOn( pInfo ); } break; case HtmlTokenId::TABLEHEADER_OFF: case HtmlTokenId::TABLEDATA_OFF: // Closes cell { TableDataOff( pInfo ); } break; case HtmlTokenId::TABLEROW_ON: // Before first cell in row { TableRowOn( pInfo ); } break; case HtmlTokenId::TABLEROW_OFF: // After last cell in row { TableRowOff( pInfo ); } break; case HtmlTokenId::TABLE_OFF: { TableOff( pInfo ); } break; case HtmlTokenId::IMAGE: { Image( pInfo ); } break; case HtmlTokenId::PARABREAK_OFF: { // We continue vertically after an image if (!mxActEntry->maImageList.empty()) mxActEntry->maImageList.back()->nDir = nVertical; } break; case HtmlTokenId::ANCHOR_ON: { AnchorOn( pInfo ); } break; case HtmlTokenId::FONT_ON : { FontOn( pInfo ); } break; case HtmlTokenId::BIGPRINT_ON : { // TODO: Remember current font size and increase by 1 if ( IsAtBeginningOfText( pInfo ) ) mxActEntry->aItemSet.Put( SvxFontHeightItem( maFontHeights[3], 100, ATTR_FONT_HEIGHT ) ); } break; case HtmlTokenId::SMALLPRINT_ON : { // TODO: Remember current font size and decrease by 1 if ( IsAtBeginningOfText( pInfo ) ) mxActEntry->aItemSet.Put( SvxFontHeightItem( maFontHeights[0], 100, ATTR_FONT_HEIGHT ) ); } break; case HtmlTokenId::BOLD_ON : case HtmlTokenId::STRONG_ON : { if ( IsAtBeginningOfText( pInfo ) ) mxActEntry->aItemSet.Put( SvxWeightItem( WEIGHT_BOLD, ATTR_FONT_WEIGHT ) ); } break; case HtmlTokenId::ITALIC_ON : case HtmlTokenId::EMPHASIS_ON : case HtmlTokenId::ADDRESS_ON : case HtmlTokenId::BLOCKQUOTE_ON : case HtmlTokenId::BLOCKQUOTE30_ON : case HtmlTokenId::CITATION_ON : case HtmlTokenId::VARIABLE_ON : { if ( IsAtBeginningOfText( pInfo ) ) mxActEntry->aItemSet.Put( SvxPostureItem( ITALIC_NORMAL, ATTR_FONT_POSTURE ) ); } break; case HtmlTokenId::DEFINSTANCE_ON : { if ( IsAtBeginningOfText( pInfo ) ) { mxActEntry->aItemSet.Put( SvxWeightItem( WEIGHT_BOLD, ATTR_FONT_WEIGHT ) ); mxActEntry->aItemSet.Put( SvxPostureItem( ITALIC_NORMAL, ATTR_FONT_POSTURE ) ); } } break; case HtmlTokenId::UNDERLINE_ON : { if ( IsAtBeginningOfText( pInfo ) ) mxActEntry->aItemSet.Put( SvxUnderlineItem( LINESTYLE_SINGLE, ATTR_FONT_UNDERLINE ) ); } break; case HtmlTokenId::TEXTTOKEN: { if ( bInTitle ) aString += pInfo->aText; } break; default: ; } } // HTML DATA QUERY PARSER template< typename Type > static Type getLimitedValue( const Type& rValue, const Type& rMin, const Type& rMax ) { return std::clamp( rValue, rMin, rMax ); } ScHTMLEntry::ScHTMLEntry( const SfxItemSet& rItemSet, ScHTMLTableId nTableId ) : ScEEParseEntry( rItemSet ), mbImportAlways( false ) { nTab = nTableId; bEntirePara = false; } bool ScHTMLEntry::HasContents() const { return mbImportAlways || aSel.HasRange() || !aAltText.isEmpty() || IsTable(); } void ScHTMLEntry::AdjustStart( const HtmlImportInfo& 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 HtmlImportInfo& rInfo ) { OSL_ENSURE( (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 owns the contained table objects and deletes them on destruction. */ class ScHTMLTableMap final { private: typedef std::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 ); const_iterator begin() const { return maTables.begin(); } const_iterator end() const { return maTables.end(); } /** 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 (
 tag). */
    ScHTMLTable*        CreateTable( const HtmlImportInfo& rInfo, bool bPreFormText );

private:
    /** Sets a working table with its index for search optimization. */
    void         SetCurrTable( ScHTMLTable* pTable ) const
                            { if( pTable ) mpCurrTable = pTable; }
};

ScHTMLTableMap::ScHTMLTableMap( ScHTMLTable& rParentTable ) :
    mrParentTable(rParentTable),
    mpCurrTable(nullptr)
{
}

ScHTMLTable* ScHTMLTableMap::FindTable( ScHTMLTableId nTableId, bool bDeep ) const
{
    ScHTMLTable* pResult = nullptr;
    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 HtmlImportInfo& rInfo, bool bPreFormText )
{
    ScHTMLTable* pTable = new ScHTMLTable( mrParentTable, rInfo, bPreFormText );
    maTables[ pTable->GetTableId() ].reset( pTable );
    SetCurrTable( pTable );
    return pTable;
}

namespace {

/** Simplified forward iterator for convenience.

    Before the iterator can be dereferenced, it must be tested with the is()
    method. The iterator may be invalid directly after construction (e.g. empty
    container).
 */
class ScHTMLTableIterator
{
public:
    /** Constructs the iterator for the passed table map.
        @param pTableMap  Pointer to the table map (is allowed to be NULL). */
    explicit            ScHTMLTableIterator( const ScHTMLTableMap* pTableMap );

    bool         is() const { return mpTableMap && maIter != maEnd; }
    ScHTMLTable* operator->() { return maIter->second.get(); }
    ScHTMLTableIterator& operator++() { ++maIter; return *this; }

private:
    ScHTMLTableMap::const_iterator maIter;
    ScHTMLTableMap::const_iterator maEnd;
    const ScHTMLTableMap* mpTableMap;
};

}

ScHTMLTableIterator::ScHTMLTableIterator( const ScHTMLTableMap* pTableMap ) :
    mpTableMap(pTableMap)
{
    if( pTableMap )
    {
        maIter = pTableMap->begin();
        maEnd = pTableMap->end();
    }
}

ScHTMLTableAutoId::ScHTMLTableAutoId( ScHTMLTableId& rnUnusedId ) :
    mnTableId( rnUnusedId ),
    mrnUnusedId( rnUnusedId )
{
    ++mrnUnusedId;
}

ScHTMLTable::ScHTMLTable( ScHTMLTable& rParentTable, const HtmlImportInfo& rInfo, bool bPreFormText ) :
    mpParentTable( &rParentTable ),
    maTableId( rParentTable.maTableId.mrnUnusedId ),
    maTableItemSet( rParentTable.GetCurrItemSet() ),
    mrEditEngine( rParentTable.mrEditEngine ),
    mrEEParseList( rParentTable.mrEEParseList ),
    mpCurrEntryVector( nullptr ),
    maSize( 1, 1 ),
    mpParser(rParentTable.mpParser),
    mbBorderOn( false ),
    mbPreFormText( bPreFormText ),
    mbRowOn( false ),
    mbDataOn( false ),
    mbPushEmptyLine( false ),
    mbCaptionOn ( false )
{
    if( mbPreFormText )
    {
        ImplRowOn();
        ImplDataOn( ScHTMLSize( 1, 1 ) );
    }
    else
    {
        ProcessFormatOptions( maTableItemSet, rInfo );
        const HTMLOptions& rOptions = static_cast(rInfo.pParser)->GetOptions();
        for (const auto& rOption : rOptions)
        {
            switch( rOption.GetToken() )
            {
                case HtmlOptionId::BORDER:
                    mbBorderOn = rOption.GetString().isEmpty() || (rOption.GetNumber() != 0);
                break;
                case HtmlOptionId::ID:
                    maTableName = rOption.GetString();
                break;
                default: break;
            }
        }
    }

    CreateNewEntry( rInfo );
}

ScHTMLTable::ScHTMLTable(
    SfxItemPool& rPool,
    EditEngine& rEditEngine,
    std::vector>& rEEParseList,
    ScHTMLTableId& rnUnusedId, ScHTMLParser* pParser
) :
    mpParentTable( nullptr ),
    maTableId( rnUnusedId ),
    maTableItemSet( rPool ),
    mrEditEngine( rEditEngine ),
    mrEEParseList( rEEParseList ),
    mpCurrEntryVector( nullptr ),
    maSize( 1, 1 ),
    mpParser(pParser),
    mbBorderOn( false ),
    mbPreFormText( false ),
    mbRowOn( false ),
    mbDataOn( false ),
    mbPushEmptyLine( false ),
    mbCaptionOn ( 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 ? *mxDataItemSet : (mxRowItemSet ? *mxRowItemSet : maTableItemSet);
}

ScHTMLSize ScHTMLTable::GetSpan( const ScHTMLPos& rCellPos ) const
{
    ScHTMLSize aSpan( 1, 1 );
    const ScRange* pRange = maVMergedCells.Find( rCellPos.MakeAddr() );
    if (!pRange)
        pRange = maHMergedCells.Find( rCellPos.MakeAddr() );
    if (pRange)
        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 ? mxNestedTables->FindTable( nTableId ) : nullptr;
}

void ScHTMLTable::PutItem( const SfxPoolItem& rItem )
{
    OSL_ENSURE( mxCurrEntry, "ScHTMLTable::PutItem - no current entry" );
    if( mxCurrEntry && mxCurrEntry->IsEmpty() )
        mxCurrEntry->GetItemSet().Put( rItem );
}

void ScHTMLTable::PutText( const HtmlImportInfo& rInfo )
{
    OSL_ENSURE( mxCurrEntry, "ScHTMLTable::PutText - no current entry" );
    if( mxCurrEntry )
    {
        if( !mxCurrEntry->HasContents() && IsSpaceCharInfo( rInfo ) )
            mxCurrEntry->AdjustStart( rInfo );
        else
            mxCurrEntry->AdjustEnd( rInfo );
        if (mbCaptionOn)
            maCaptionBuffer.append(rInfo.aText);

    }
}

void ScHTMLTable::InsertPara( const HtmlImportInfo& rInfo )
{
    if( mxCurrEntry && mbDataOn && !IsEmptyCell() )
        mxCurrEntry->SetImportAlways();
    PushEntry( rInfo );
    CreateNewEntry( rInfo );
    InsertLeadingEmptyLine();
}

void ScHTMLTable::BreakOn()
{
    // empty line, if 
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

,

, , or are not at start of cell mbPushEmptyLine = !mbPreFormText && mbDataOn && !IsEmptyCell(); } void ScHTMLTable::AnchorOn() { OSL_ENSURE( mxCurrEntry, "ScHTMLTable::AnchorOn - no current entry" ); // don't skip entries with single hyperlinks if( mxCurrEntry ) mxCurrEntry->SetImportAlways(); } ScHTMLTable* ScHTMLTable::TableOn( const HtmlImportInfo& rInfo ) { PushEntry( rInfo ); return InsertNestedTable( rInfo, false ); } ScHTMLTable* ScHTMLTable::TableOff( const HtmlImportInfo& rInfo ) { return mbPreFormText ? this : CloseTable( rInfo ); } void ScHTMLTable::CaptionOn() { mbCaptionOn = true; maCaptionBuffer.setLength(0); } void ScHTMLTable::CaptionOff() { if (!mbCaptionOn) return; maCaption = maCaptionBuffer.makeStringAndClear().trim(); mbCaptionOn = false; } ScHTMLTable* ScHTMLTable::PreOn( const HtmlImportInfo& rInfo ) { PushEntry( rInfo ); return InsertNestedTable( rInfo, true ); } ScHTMLTable* ScHTMLTable::PreOff( const HtmlImportInfo& rInfo ) { return mbPreFormText ? CloseTable( rInfo ) : this; } void ScHTMLTable::RowOn( const HtmlImportInfo& 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 HtmlImportInfo& rInfo ) { PushEntry( rInfo, true ); if( mpParentTable && !mbPreFormText ) // no rows allowed in global and preformatted tables ImplRowOff(); CreateNewEntry( rInfo ); } namespace { /** * Decode a number format string stored in Excel-generated HTML's CSS * region. */ OUString decodeNumberFormat(const OUString& rFmt) { OUStringBuffer aBuf; const sal_Unicode* p = rFmt.getStr(); sal_Int32 n = rFmt.getLength(); for (sal_Int32 i = 0; i < n; ++i, ++p) { if (*p == '\\') { // Skip '\'. ++i; ++p; // Parse all subsequent digits until first non-digit is found. sal_Int32 nDigitCount = 0; const sal_Unicode* p1 = p; for (; i < n; ++i, ++p, ++nDigitCount) { if (*p < '0' || '9' < *p) { --i; --p; break; } } if (nDigitCount) { // Hex-encoded character found. Decode it back into its // original character. An example of number format with // hex-encoded chars: "\0022$\0022\#\,\#\#0\.00" sal_uInt32 nVal = OUString(p1, nDigitCount).toUInt32(16); aBuf.append(static_cast(nVal)); } } else aBuf.append(*p); } return aBuf.makeStringAndClear(); } } void ScHTMLTable::DataOn( const HtmlImportInfo& rInfo ) { PushEntry( rInfo, true ); if( mpParentTable && !mbPreFormText ) // no cells allowed in global and preformatted tables { // read needed options from the
case HtmlTokenId::TABLEROW_ON: mpCurrTable->RowOn( rInfo ); break; // case HtmlTokenId::TABLEROW_OFF: mpCurrTable->RowOff( rInfo ); break; // case HtmlTokenId::TABLEHEADER_ON: // case HtmlTokenId::PREFORMTXT_ON: PreOn( rInfo ); break; //
        case HtmlTokenId::PREFORMTXT_OFF:   PreOff( rInfo );                break;  // 
// --- formatting --- case HtmlTokenId::FONT_ON: FontOn( rInfo ); break; // case HtmlTokenId::BIGPRINT_ON: // //! TODO: store current font size, use following size mpCurrTable->PutItem( SvxFontHeightItem( maFontHeights[ 3 ], 100, ATTR_FONT_HEIGHT ) ); break; case HtmlTokenId::SMALLPRINT_ON: // //! TODO: store current font size, use preceding size mpCurrTable->PutItem( SvxFontHeightItem( maFontHeights[ 0 ], 100, ATTR_FONT_HEIGHT ) ); break; case HtmlTokenId::BOLD_ON: // case HtmlTokenId::STRONG_ON: // mpCurrTable->PutItem( SvxWeightItem( WEIGHT_BOLD, ATTR_FONT_WEIGHT ) ); break; case HtmlTokenId::ITALIC_ON: // case HtmlTokenId::EMPHASIS_ON: // case HtmlTokenId::ADDRESS_ON: //
case HtmlTokenId::BLOCKQUOTE_ON: //
case HtmlTokenId::BLOCKQUOTE30_ON: // case HtmlTokenId::CITATION_ON: // case HtmlTokenId::VARIABLE_ON: // mpCurrTable->PutItem( SvxPostureItem( ITALIC_NORMAL, ATTR_FONT_POSTURE ) ); break; case HtmlTokenId::DEFINSTANCE_ON: // mpCurrTable->PutItem( SvxWeightItem( WEIGHT_BOLD, ATTR_FONT_WEIGHT ) ); mpCurrTable->PutItem( SvxPostureItem( ITALIC_NORMAL, ATTR_FONT_POSTURE ) ); break; case HtmlTokenId::UNDERLINE_ON: // mpCurrTable->PutItem( SvxUnderlineItem( LINESTYLE_SINGLE, ATTR_FONT_UNDERLINE ) ); break; default: break; } } void ScHTMLQueryParser::InsertText( const HtmlImportInfo& rInfo ) { mpCurrTable->PutText( rInfo ); if( mbTitleOn ) maTitle.append(rInfo.aText); } void ScHTMLQueryParser::FontOn( const HtmlImportInfo& rInfo ) { const HTMLOptions& rOptions = static_cast(rInfo.pParser)->GetOptions(); for (const auto& rOption : rOptions) { switch( rOption.GetToken() ) { case HtmlOptionId::FACE : { const OUString& rFace = rOption.GetString(); OUString aFontName; sal_Int32 nPos = 0; while( nPos != -1 ) { // font list separator: VCL = ';' HTML = ',' OUString aFName = comphelper::string::strip(rFace.getToken(0, ',', nPos), ' '); aFontName = ScGlobal::addToken(aFontName, aFName, ';'); } if ( !aFontName.isEmpty() ) mpCurrTable->PutItem( SvxFontItem( FAMILY_DONTKNOW, aFontName, EMPTY_OUSTRING, PITCH_DONTKNOW, RTL_TEXTENCODING_DONTKNOW, ATTR_FONT ) ); } break; case HtmlOptionId::SIZE : { sal_uInt32 nSize = getLimitedValue< sal_uInt32 >( rOption.GetNumber(), 1, SC_HTML_FONTSIZES ); mpCurrTable->PutItem( SvxFontHeightItem( maFontHeights[ nSize - 1 ], 100, ATTR_FONT_HEIGHT ) ); } break; case HtmlOptionId::COLOR : { Color aColor; rOption.GetColor( aColor ); mpCurrTable->PutItem( SvxColorItem( aColor, ATTR_FONT_COLOR ) ); } break; default: break; } } } void ScHTMLQueryParser::MetaOn( const HtmlImportInfo& rInfo ) { if( mpDoc->GetDocumentShell() ) { HTMLParser* pParser = static_cast< HTMLParser* >( rInfo.pParser ); uno::Reference xDPS( mpDoc->GetDocumentShell()->GetModel(), uno::UNO_QUERY_THROW); pParser->ParseMetaOptions( xDPS->getDocumentProperties(), mpDoc->GetDocumentShell()->GetHeaderAttributes() ); } } void ScHTMLQueryParser::TitleOn() { mbTitleOn = true; maTitle.setLength(0); } void ScHTMLQueryParser::TitleOff( const HtmlImportInfo& rInfo ) { if( !mbTitleOn ) return; OUString aTitle = maTitle.makeStringAndClear().trim(); if (!aTitle.isEmpty() && mpDoc->GetDocumentShell()) { uno::Reference xDPS( mpDoc->GetDocumentShell()->GetModel(), uno::UNO_QUERY_THROW); xDPS->getDocumentProperties()->setTitle(aTitle); } InsertText( rInfo ); mbTitleOn = false; } void ScHTMLQueryParser::TableOn( const HtmlImportInfo& rInfo ) { mpCurrTable = mpCurrTable->TableOn( rInfo ); } void ScHTMLQueryParser::TableOff( const HtmlImportInfo& rInfo ) { mpCurrTable = mpCurrTable->TableOff( rInfo ); } void ScHTMLQueryParser::PreOn( const HtmlImportInfo& rInfo ) { mpCurrTable = mpCurrTable->PreOn( rInfo ); } void ScHTMLQueryParser::PreOff( const HtmlImportInfo& rInfo ) { mpCurrTable = mpCurrTable->PreOff( rInfo ); } void ScHTMLQueryParser::CloseTable( const HtmlImportInfo& rInfo ) { mpCurrTable = mpCurrTable->CloseTable( rInfo ); } namespace { /** * Handler class for the CSS parser. */ class CSSHandler: public orcus::css_handler { struct MemStr { const char* mp; size_t mn; MemStr() : mp(nullptr), mn(0) {} MemStr(const char* p, size_t n) : mp(p), mn(n) {} MemStr(const MemStr& r) : mp(r.mp), mn(r.mn) {} MemStr& operator=(const MemStr& r) = default; }; typedef std::pair SelectorName; // element : class typedef std::vector SelectorNames; SelectorNames maSelectorNames; // current selector names MemStr maPropName; // current property name. MemStr maPropValue; // current property value. ScHTMLStyles& mrStyles; public: explicit CSSHandler(ScHTMLStyles& rStyles): maPropName(), maPropValue(), mrStyles(rStyles) {} // selector name starting with "@" static void at_rule_name(const char* /*p*/, size_t /*n*/) { // TODO: For now, we ignore at-rule properties } // selector name not starting with "." or "#" (i.e. element selectors) void simple_selector_type(const char* pElem, size_t nElem) { MemStr aElem(pElem, nElem); // element given MemStr aClass(nullptr, 0); // class name not given - to be added in the "element global" storage SelectorName aName(aElem, aClass); maSelectorNames.push_back(aName); } // selector names starting with a "." (i.e. class selector) void simple_selector_class(const char* pClassName, size_t nClassName) { MemStr aElem(nullptr, 0); // no element given - should be added in the "global" storage MemStr aClass(pClassName, nClassName); SelectorName aName(aElem, aClass); maSelectorNames.push_back(aName); } // TODO: Add other selectors void property_name(const char* p, size_t n) { maPropName = MemStr(p, n); } void value(const char* p, size_t n) { maPropValue = MemStr(p, n); } void end_block() { maSelectorNames.clear(); } void end_property() { SelectorNames::const_iterator itr = maSelectorNames.begin(), itrEnd = maSelectorNames.end(); for (; itr != itrEnd; ++itr) { // Add this property to the collection for each selector. const SelectorName& rSelName = *itr; const MemStr& rElem = rSelName.first; const MemStr& rClass = rSelName.second; OUString aName(maPropName.mp, maPropName.mn, RTL_TEXTENCODING_UTF8); OUString aValue(maPropValue.mp, maPropValue.mn, RTL_TEXTENCODING_UTF8); mrStyles.add(rElem.mp, rElem.mn, rClass.mp, rClass.mn, aName, aValue); } maPropName = MemStr(); maPropValue = MemStr(); } }; } void ScHTMLQueryParser::ParseStyle(std::u16string_view rStrm) { OString aStr = OUStringToOString(rStrm, RTL_TEXTENCODING_UTF8); CSSHandler aHdl(GetStyles()); orcus::css_parser aParser(aStr.getStr(), aStr.getLength(), aHdl); try { aParser.parse(); } catch (const orcus::css::parse_error& rOrcusParseError) { SAL_WARN("sc", "ScHTMLQueryParser::ParseStyle: " << rOrcusParseError.what()); // TODO: Parsing of CSS failed. Do nothing for now. } } IMPL_LINK( ScHTMLQueryParser, HTMLImportHdl, HtmlImportInfo&, rInfo, void ) { switch( rInfo.eState ) { case HtmlImportState::Start: break; case HtmlImportState::NextToken: ProcessToken( rInfo ); break; case HtmlImportState::InsertPara: mpCurrTable->InsertPara( rInfo ); break; case HtmlImportState::SetAttr: case HtmlImportState::InsertText: case HtmlImportState::InsertField: break; case HtmlImportState::End: while( mpCurrTable->GetTableId() != SC_HTML_GLOBAL_TABLE ) CloseTable( rInfo ); break; default: OSL_FAIL( "ScHTMLQueryParser::HTMLImportHdl - unknown ImportInfo::eState" ); } } /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
tag ScHTMLSize aSpanSize( 1, 1 ); std::optional pValStr, pNumStr; const HTMLOptions& rOptions = static_cast(rInfo.pParser)->GetOptions(); sal_uInt32 nNumberFormat = NUMBERFORMAT_ENTRY_NOT_FOUND; for (const auto& rOption : rOptions) { switch (rOption.GetToken()) { case HtmlOptionId::COLSPAN: aSpanSize.mnCols = static_cast( getLimitedValue( rOption.GetString().toInt32(), 1, 256 ) ); break; case HtmlOptionId::ROWSPAN: aSpanSize.mnRows = static_cast( getLimitedValue( rOption.GetString().toInt32(), 1, 256 ) ); break; case HtmlOptionId::SDVAL: pValStr = rOption.GetString(); break; case HtmlOptionId::SDNUM: pNumStr = rOption.GetString(); break; case HtmlOptionId::CLASS: { // Pick up the number format associated with this class (if // any). OUString aClass = rOption.GetString(); const ScHTMLStyles& rStyles = mpParser->GetStyles(); const OUString& rVal = rStyles.getPropertyValue("td", aClass, "mso-number-format"); if (!rVal.isEmpty()) { OUString aNumFmt = decodeNumberFormat(rVal); nNumberFormat = GetFormatTable()->GetEntryKey(aNumFmt); if (nNumberFormat == NUMBERFORMAT_ENTRY_NOT_FOUND) { sal_Int32 nErrPos = 0; SvNumFormatType nDummy; bool bValidFmt = GetFormatTable()->PutEntry(aNumFmt, nErrPos, nDummy, nNumberFormat); if (!bValidFmt) nNumberFormat = NUMBERFORMAT_ENTRY_NOT_FOUND; } } } break; default: break; } } ImplDataOn( aSpanSize ); if (nNumberFormat != NUMBERFORMAT_ENTRY_NOT_FOUND) mxDataItemSet->Put( SfxUInt32Item(ATTR_VALUE_FORMAT, nNumberFormat) ); ProcessFormatOptions( *mxDataItemSet, rInfo ); CreateNewEntry( rInfo ); mxCurrEntry->pValStr = std::move(pValStr); mxCurrEntry->pNumStr = std::move(pNumStr); } else CreateNewEntry( rInfo ); } void ScHTMLTable::DataOff( const HtmlImportInfo& rInfo ) { PushEntry( rInfo, true ); if( mpParentTable && !mbPreFormText ) // no cells allowed in global and preformatted tables ImplDataOff(); CreateNewEntry( rInfo ); } void ScHTMLTable::BodyOn( const HtmlImportInfo& rInfo ) { bool bPushed = PushEntry( rInfo ); if( !mpParentTable ) { // 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 HtmlImportInfo& rInfo ) { PushEntry( rInfo ); if( !mpParentTable ) { ImplDataOff(); ImplRowOff(); } CreateNewEntry( rInfo ); } ScHTMLTable* ScHTMLTable::CloseTable( const HtmlImportInfo& 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(); ScAddress aErrorPos( ScAddress::UNINITIALIZED ); if (!rRange.aEnd.Move( static_cast< SCCOL >( GetDocSize( tdCol ) ) - 1, static_cast< SCROW >( GetDocSize( tdRow ) ) - 1, 0, aErrorPos)) { assert(!"can't move"); } } void ScHTMLTable::ApplyCellBorders( ScDocument* pDoc, const ScAddress& rFirstPos ) const { OSL_ENSURE( pDoc, "ScHTMLTable::ApplyCellBorders - no document" ); if( pDoc && mbBorderOn ) { const SCCOL nLastCol = maSize.mnCols - 1; const SCROW nLastRow = maSize.mnRows - 1; const tools::Long nOuterLine = DEF_LINE_WIDTH_2; const tools::Long nInnerLine = DEF_LINE_WIDTH_0; SvxBorderLine aOuterLine(nullptr, nOuterLine, SvxBorderLineStyle::SOLID); SvxBorderLine aInnerLine(nullptr, nInnerLine, SvxBorderLineStyle::SOLID); 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 : nullptr, SvxBoxItemLine::LEFT ); aBorderItem.SetLine( (nCellCol == nCellCol2) ? pRightLine : nullptr, SvxBoxItemLine::RIGHT ); for( SCROW nCellRow = nCellRow1; nCellRow <= nCellRow2; ++nCellRow ) { aBorderItem.SetLine( (nCellRow == nCellRow1) ? pTopLine : nullptr, SvxBoxItemLine::TOP ); aBorderItem.SetLine( (nCellRow == nCellRow2) ? pBottomLine : nullptr, SvxBoxItemLine::BOTTOM ); pDoc->ApplyAttr( nCellCol, nCellRow, rFirstPos.Tab(), aBorderItem ); } } } } } for( ScHTMLTableIterator aIter( mxNestedTables.get() ); aIter.is(); ++aIter ) aIter->ApplyCellBorders( pDoc, rFirstPos ); } SvNumberFormatter* ScHTMLTable::GetFormatTable() { return mpParser->GetDoc().GetFormatTable(); } bool ScHTMLTable::IsEmptyCell() const { return mpCurrEntryVector && mpCurrEntryVector->empty(); } bool ScHTMLTable::IsSpaceCharInfo( const HtmlImportInfo& rInfo ) { return (rInfo.nToken == HtmlTokenId::TEXTTOKEN) && (rInfo.aText.getLength() == 1) && (rInfo.aText[ 0 ] == ' '); } ScHTMLTable::ScHTMLEntryPtr ScHTMLTable::CreateEntry() const { return std::make_unique( GetCurrItemSet() ); } void ScHTMLTable::CreateNewEntry( const HtmlImportInfo& rInfo ) { OSL_ENSURE( !mxCurrEntry, "ScHTMLTable::CreateNewEntry - old entry still present" ); mxCurrEntry = CreateEntry(); mxCurrEntry->aSel = rInfo.aSelection; } void ScHTMLTable::ImplPushEntryToVector( ScHTMLEntryVector& rEntryVector, ScHTMLEntryPtr& rxEntry ) { // HTML entry list does not own the entries rEntryVector.push_back( rxEntry.get() ); // mrEEParseList (reference to member of ScEEParser) owns the entries mrEEParseList.push_back(std::shared_ptr(rxEntry.release())); } bool ScHTMLTable::PushEntry( ScHTMLEntryPtr& rxEntry ) { bool bPushed = false; if( rxEntry && rxEntry->HasContents() ) { if( mpCurrEntryVector ) { if( mbPushEmptyLine ) { ScHTMLEntryPtr xEmptyEntry = CreateEntry(); ImplPushEntryToVector( *mpCurrEntryVector, xEmptyEntry ); mbPushEmptyLine = false; } ImplPushEntryToVector( *mpCurrEntryVector, rxEntry ); bPushed = true; } else if( mpParentTable ) { bPushed = mpParentTable->PushEntry( rxEntry ); } else { OSL_FAIL( "ScHTMLTable::PushEntry - cannot push entry, no parent found" ); } } return bPushed; } bool ScHTMLTable::PushEntry( const HtmlImportInfo& rInfo, bool bLastInCell ) { OSL_ENSURE( mxCurrEntry, "ScHTMLTable::PushEntry - no current entry" ); bool bPushed = false; if( mxCurrEntry ) { 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; } void ScHTMLTable::PushTableEntry( ScHTMLTableId nTableId ) { OSL_ENSURE( nTableId != SC_HTML_GLOBAL_TABLE, "ScHTMLTable::PushTableEntry - cannot push global table" ); if( nTableId != SC_HTML_GLOBAL_TABLE ) { ScHTMLEntryPtr xEntry( new ScHTMLEntry( maTableItemSet, nTableId ) ); PushEntry( xEntry ); } } ScHTMLTable* ScHTMLTable::GetExistingTable( ScHTMLTableId nTableId ) const { ScHTMLTable* pTable = ((nTableId != SC_HTML_GLOBAL_TABLE) && mxNestedTables) ? mxNestedTables->FindTable( nTableId, false ) : nullptr; OSL_ENSURE( pTable || (nTableId == SC_HTML_GLOBAL_TABLE), "ScHTMLTable::GetExistingTable - table not found" ); return pTable; } ScHTMLTable* ScHTMLTable::InsertNestedTable( const HtmlImportInfo& rInfo, bool bPreFormText ) { if( !mxNestedTables ) 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. */ for (;;) { pRange = maVMergedCells.Find( maCurrCell.MakeAddr() ); if (!pRange) pRange = maHMergedCells.Find( maCurrCell.MakeAddr() ); if (!pRange) break; maCurrCell.mnCol = pRange->aEnd.Col() + 1; } mpCurrEntryVector = &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 )) != nullptr ) pRange->aEnd.SetRow( maCurrCell.mnRow - 1 ); // insert the new range into the cell lists ScRange aNewRange( maCurrCell.MakeAddr() ); ScAddress aErrorPos( ScAddress::UNINITIALIZED ); if (!aNewRange.aEnd.Move( rSpanSize.mnCols - 1, rSpanSize.mnRows - 1, 0, aErrorPos)) { assert(!"can't move"); } if( rSpanSize.mnRows > 1 ) { maVMergedCells.push_back( 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.push_back( 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; mpCurrEntryVector = nullptr; mbDataOn = false; } } void ScHTMLTable::ProcessFormatOptions( SfxItemSet& rItemSet, const HtmlImportInfo& rInfo ) { // special handling for table header cells if( rInfo.nToken == HtmlTokenId::TABLEHEADER_ON ) { rItemSet.Put( SvxWeightItem( WEIGHT_BOLD, ATTR_FONT_WEIGHT ) ); rItemSet.Put( SvxHorJustifyItem( SvxCellHorJustify::Center, ATTR_HOR_JUSTIFY ) ); } const HTMLOptions& rOptions = static_cast(rInfo.pParser)->GetOptions(); for (const auto& rOption : rOptions) { switch( rOption.GetToken() ) { case HtmlOptionId::ALIGN: { SvxCellHorJustify eVal = SvxCellHorJustify::Standard; const OUString& rOptVal = rOption.GetString(); if( rOptVal.equalsIgnoreAsciiCase( OOO_STRING_SVTOOLS_HTML_AL_right ) ) eVal = SvxCellHorJustify::Right; else if( rOptVal.equalsIgnoreAsciiCase( OOO_STRING_SVTOOLS_HTML_AL_center ) ) eVal = SvxCellHorJustify::Center; else if( rOptVal.equalsIgnoreAsciiCase( OOO_STRING_SVTOOLS_HTML_AL_left ) ) eVal = SvxCellHorJustify::Left; if( eVal != SvxCellHorJustify::Standard ) rItemSet.Put( SvxHorJustifyItem( eVal, ATTR_HOR_JUSTIFY ) ); } break; case HtmlOptionId::VALIGN: { SvxCellVerJustify eVal = SvxCellVerJustify::Standard; const OUString& rOptVal = rOption.GetString(); if( rOptVal.equalsIgnoreAsciiCase( OOO_STRING_SVTOOLS_HTML_VA_top ) ) eVal = SvxCellVerJustify::Top; else if( rOptVal.equalsIgnoreAsciiCase( OOO_STRING_SVTOOLS_HTML_VA_middle ) ) eVal = SvxCellVerJustify::Center; else if( rOptVal.equalsIgnoreAsciiCase( OOO_STRING_SVTOOLS_HTML_VA_bottom ) ) eVal = SvxCellVerJustify::Bottom; if( eVal != SvxCellVerJustify::Standard ) rItemSet.Put( SvxVerJustifyItem( eVal, ATTR_VER_JUSTIFY ) ); } break; case HtmlOptionId::BGCOLOR: { Color aColor; rOption.GetColor( aColor ); rItemSet.Put( SvxBrushItem( aColor, ATTR_BACKGROUND ) ); } break; default: break; } } } void ScHTMLTable::SetDocSize( ScHTMLOrient eOrient, SCCOLROW nCellPos, SCCOLROW nSize ) { OSL_ENSURE( 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 SCCOLROW nDiff = nSize - ((nIndex == 0) ? rSizes.front() : (rSizes[ nIndex ] - rSizes[ nIndex - 1 ])); if( nDiff > 0 ) std::for_each(rSizes.begin() + nIndex, rSizes.end(), [&nDiff](SCCOLROW& rSize) { rSize += 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() { for( ScHTMLTableIterator aIter( mxNestedTables.get() ); aIter.is(); ++aIter ) aIter->FillEmptyCells(); // insert the final vertically merged ranges into maUsedCells for ( size_t i = 0, nRanges = maVMergedCells.size(); i < nRanges; ++i ) { ScRange & rRange = maVMergedCells[ i ]; maUsedCells.Join( rRange ); } 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(); ImplPushEntryToVector( maEntryMap[ ScHTMLPos( aAddr ) ], xEntry ); } } } } void ScHTMLTable::RecalcDocSize() { // recalc table sizes recursively from inner to outer for( ScHTMLTableIterator aIter( mxNestedTables.get() ); aIter.is(); ++aIter ) aIter->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 for( const auto& [rCellPos, rEntryVector] : maEntryMap ) { ScHTMLSize aCellSpan = GetSpan( rCellPos ); // 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( const auto& rpEntry : rEntryVector ) { ScHTMLTable* pTable = GetExistingTable( rpEntry->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 for( auto& [rCellPos, rEntryVector] : maEntryMap ) { // fixed doc position of the entire cell (first entry) const ScHTMLPos aCellDocPos( GetDocPos( rCellPos ) ); // fixed doc size of the entire cell const ScHTMLSize aCellDocSize( GetDocSize( rCellPos ) ); // running doc position for single entries ScHTMLPos aEntryDocPos( aCellDocPos ); ScHTMLEntry* pEntry = nullptr; for( const auto& rpEntry : rEntryVector ) { pEntry = rpEntry; 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 == rEntryVector.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 { // 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; ImplPushEntryToVector( rEntryVector, xDummyEntry ); ++aEntryDocPos.mnRow; } } } } } ScHTMLGlobalTable::ScHTMLGlobalTable( SfxItemPool& rPool, EditEngine& rEditEngine, std::vector>& rEEParseVector, ScHTMLTableId& rnUnusedId, ScHTMLParser* pParser ) : ScHTMLTable( rPool, rEditEngine, rEEParseVector, rnUnusedId, pParser ) { } 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, maList, mnUnusedId, this)); mpCurrTable = mxGlobTable.get(); } ScHTMLQueryParser::~ScHTMLQueryParser() { } ErrCode ScHTMLQueryParser::Read( SvStream& rStrm, const OUString& rBaseURL ) { SvKeyValueIteratorRef xValues; SvKeyValueIterator* pAttributes = nullptr; 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 char* pCharSet = rtl_getBestMimeCharsetFromTextEncoding( RTL_TEXTENCODING_UTF8 ); if( pCharSet ) { OUString aContentType = "text/html; charset=" + OUString::createFromAscii( pCharSet ); xValues = new SvKeyValueIterator; xValues->Append( SvKeyValue( OOO_STRING_SVTOOLS_HTML_META_content_type, aContentType ) ); pAttributes = xValues.get(); } } Link aOldLink = pEdit->GetHtmlImportHdl(); pEdit->SetHtmlImportHdl( LINK( this, ScHTMLQueryParser, HTMLImportHdl ) ); ErrCode nErr = pEdit->Read( rStrm, rBaseURL, EETextFormat::Html, pAttributes ); pEdit->SetHtmlImportHdl( 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 HtmlImportInfo& rInfo ) { switch( rInfo.nToken ) { // --- meta data --- case HtmlTokenId::META: MetaOn( rInfo ); break; // // --- title handling --- case HtmlTokenId::TITLE_ON: TitleOn(); break; // case HtmlTokenId::TITLE_OFF: TitleOff( rInfo ); break; // case HtmlTokenId::STYLE_ON: break; case HtmlTokenId::STYLE_OFF: ParseStyle(rInfo.aText); break; // --- body handling --- case HtmlTokenId::BODY_ON: mpCurrTable->BodyOn( rInfo ); break; // case HtmlTokenId::BODY_OFF: mpCurrTable->BodyOff( rInfo ); break; // // --- insert text --- case HtmlTokenId::TEXTTOKEN: InsertText( rInfo ); break; // any text case HtmlTokenId::LINEBREAK: mpCurrTable->BreakOn(); break; //
case HtmlTokenId::HEAD1_ON: //

case HtmlTokenId::HEAD2_ON: //

case HtmlTokenId::HEAD3_ON: //

case HtmlTokenId::HEAD4_ON: //

case HtmlTokenId::HEAD5_ON: //

case HtmlTokenId::HEAD6_ON: //
case HtmlTokenId::PARABREAK_ON: mpCurrTable->HeadingOn(); break; //

// --- misc. contents --- case HtmlTokenId::ANCHOR_ON: mpCurrTable->AnchorOn(); break; // // --- table handling --- case HtmlTokenId::TABLE_ON: TableOn( rInfo ); break; // case HtmlTokenId::TABLE_OFF: TableOff( rInfo ); break; //
case HtmlTokenId::CAPTION_ON: mpCurrTable->CaptionOn(); break; //

case HtmlTokenId::CAPTION_OFF: mpCurrTable->CaptionOff(); break; //
case HtmlTokenId::TABLEDATA_ON: mpCurrTable->DataOn( rInfo ); break; // case HtmlTokenId::TABLEHEADER_OFF: // case HtmlTokenId::TABLEDATA_OFF: mpCurrTable->DataOff( rInfo ); break; //