summaryrefslogtreecommitdiffstats
path: root/sc/source/filter/rtf/rtfparse.cxx
diff options
context:
space:
mode:
Diffstat (limited to 'sc/source/filter/rtf/rtfparse.cxx')
-rw-r--r--sc/source/filter/rtf/rtfparse.cxx403
1 files changed, 403 insertions, 0 deletions
diff --git a/sc/source/filter/rtf/rtfparse.cxx b/sc/source/filter/rtf/rtfparse.cxx
new file mode 100644
index 000000000..b2d2b8c25
--- /dev/null
+++ b/sc/source/filter/rtf/rtfparse.cxx
@@ -0,0 +1,403 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <memory>
+#include <scitems.hxx>
+#include <editeng/eeitem.hxx>
+#include <editeng/editeng.hxx>
+#include <editeng/editids.hrc>
+#include <editeng/fhgtitem.hxx>
+#include <editeng/svxrtf.hxx>
+#include <svtools/rtftoken.h>
+#include <osl/diagnose.h>
+#include <svl/itempool.hxx>
+
+#include <rtfparse.hxx>
+
+#define SC_RTFTWIPTOL 10 // 10 Twips tolerance when determining columns
+
+ScRTFParser::ScRTFParser( EditEngine* pEditP ) :
+ ScEEParser( pEditP ),
+ mnCurPos(0),
+ pActDefault( nullptr ),
+ pDefMerge( nullptr ),
+ nStartAdjust( sal_uLong(~0) ),
+ nLastWidth(0),
+ bNewDef( false )
+{
+ // RTF default FontSize 12Pt
+ tools::Long nMM = o3tl::convert(12, o3tl::Length::pt, o3tl::Length::mm100);
+
+ pPool->SetPoolDefaultItem( SvxFontHeightItem( nMM, 100, EE_CHAR_FONTHEIGHT ) );
+ // Free-flying pInsDefault
+ pInsDefault.reset( new ScRTFCellDefault( pPool.get() ) );
+}
+
+ScRTFParser::~ScRTFParser()
+{
+ pInsDefault.reset();
+ maDefaultList.clear();
+}
+
+ErrCode ScRTFParser::Read( SvStream& rStream, const OUString& rBaseURL )
+{
+ Link<RtfImportInfo&,void> aOldLink = pEdit->GetRtfImportHdl();
+ pEdit->SetRtfImportHdl( LINK( this, ScRTFParser, RTFImportHdl ) );
+ ErrCode nErr = pEdit->Read( rStream, rBaseURL, EETextFormat::Rtf );
+ if ( nRtfLastToken == RTF_PAR )
+ {
+ if ( !maList.empty() )
+ {
+ auto& pE = maList.back();
+ if ( // Completely empty
+ ( pE->aSel.nStartPara == pE->aSel.nEndPara
+ && pE->aSel.nStartPos == pE->aSel.nEndPos
+ )
+ || // Empty paragraph
+ ( pE->aSel.nStartPara + 1 == pE->aSel.nEndPara
+ && pE->aSel.nStartPos == pEdit->GetTextLen( pE->aSel.nStartPara )
+ && pE->aSel.nEndPos == 0
+ )
+ )
+ { // Don't take over the last paragraph
+ maList.pop_back();
+ }
+ }
+ }
+ ColAdjust();
+ pEdit->SetRtfImportHdl( aOldLink );
+ return nErr;
+}
+
+void ScRTFParser::EntryEnd( ScEEParseEntry* pE, const ESelection& aSel )
+{
+ // Paragraph -2 strips the attached empty paragraph
+ pE->aSel.nEndPara = aSel.nEndPara - 2;
+ // Although it's called nEndPos, the last one is position + 1
+ pE->aSel.nEndPos = pEdit->GetTextLen( aSel.nEndPara - 1 );
+}
+
+inline void ScRTFParser::NextRow()
+{
+ if ( nRowMax < ++nRowCnt )
+ nRowMax = nRowCnt;
+}
+
+bool ScRTFParser::SeekTwips( sal_uInt16 nTwips, SCCOL* pCol )
+{
+ ScRTFColTwips::const_iterator it = aColTwips.find( nTwips );
+ bool bFound = it != aColTwips.end();
+ sal_uInt16 nPos = it - aColTwips.begin();
+ *pCol = static_cast<SCCOL>(nPos);
+ if ( bFound )
+ return true;
+ sal_uInt16 nCount = aColTwips.size();
+ if ( !nCount )
+ return false;
+ SCCOL nCol = *pCol;
+ // nCol is insertion position; the next one higher up is there (or not)
+ if ( nCol < static_cast<SCCOL>(nCount) && ((aColTwips[nCol] - SC_RTFTWIPTOL) <= nTwips) )
+ return true;
+ // Not smaller than everything else? Then compare with the next lower one
+ else if ( nCol != 0 && ((aColTwips[nCol-1] + SC_RTFTWIPTOL) >= nTwips) )
+ {
+ (*pCol)--;
+ return true;
+ }
+ return false;
+}
+
+void ScRTFParser::ColAdjust()
+{
+ if ( nStartAdjust == sal_uLong(~0) )
+ return;
+
+ SCCOL nCol = 0;
+ for (size_t i = nStartAdjust, nListSize = maList.size(); i < nListSize; ++i)
+ {
+ auto& pE = maList[i];
+ if ( pE->nCol == 0 )
+ nCol = 0;
+ pE->nCol = nCol;
+ if ( pE->nColOverlap > 1 )
+ nCol = nCol + pE->nColOverlap; // Merged cells with \clmrg
+ else
+ {
+ SeekTwips( pE->nTwips, &nCol );
+ if ( ++nCol <= pE->nCol )
+ nCol = pE->nCol + 1; // Moved cell X
+ pE->nColOverlap = nCol - pE->nCol; // Merged cells without \clmrg
+ }
+ if ( nCol > nColMax )
+ nColMax = nCol;
+ }
+ nStartAdjust = sal_uLong(~0);
+ aColTwips.clear();
+}
+
+IMPL_LINK( ScRTFParser, RTFImportHdl, RtfImportInfo&, rInfo, void )
+{
+ switch ( rInfo.eState )
+ {
+ case RtfImportState::NextToken:
+ ProcToken( &rInfo );
+ break;
+ case RtfImportState::UnknownAttr:
+ ProcToken( &rInfo );
+ break;
+ case RtfImportState::Start:
+ {
+ SvxRTFParser* pParser = static_cast<SvxRTFParser*>(rInfo.pParser);
+ pParser->SetAttrPool( pPool.get() );
+ pParser->SetPardMap(SID_ATTR_BRUSH, ATTR_BACKGROUND);
+ pParser->SetPardMap(SID_ATTR_BORDER_OUTER, ATTR_BORDER);
+ pParser->SetPardMap(SID_ATTR_BORDER_SHADOW, ATTR_SHADOW);
+ }
+ break;
+ case RtfImportState::End:
+ if ( rInfo.aSelection.nEndPos )
+ { // If still text: create last paragraph
+ pActDefault = nullptr;
+ rInfo.nToken = RTF_PAR;
+ // EditEngine did not attach an empty paragraph anymore
+ // which EntryEnd could strip
+ rInfo.aSelection.nEndPara++;
+ ProcToken( &rInfo );
+ }
+ break;
+ case RtfImportState::SetAttr:
+ break;
+ case RtfImportState::InsertText:
+ break;
+ case RtfImportState::InsertPara:
+ break;
+ default:
+ OSL_FAIL("unknown ImportInfo.eState");
+ }
+}
+
+// Bad behavior:
+// For RTF_INTBL or respectively at the start of the first RTF_CELL
+// after RTF_CELLX if there was no RTF_INTBL
+void ScRTFParser::NewCellRow()
+{
+ if ( bNewDef )
+ {
+ bNewDef = false;
+ // Not flush on the right? => new table
+ if ( nLastWidth && !maDefaultList.empty() )
+ {
+ const ScRTFCellDefault& rD = *maDefaultList.back();
+ if (rD.nTwips != nLastWidth)
+ {
+ SCCOL n1, n2;
+ if ( !( SeekTwips( nLastWidth, &n1 )
+ && SeekTwips( rD.nTwips, &n2 )
+ && n1 == n2
+ )
+ )
+ {
+ ColAdjust();
+ }
+ }
+ }
+ // Build up TwipCols only after nLastWidth comparison!
+ for (const std::unique_ptr<ScRTFCellDefault> & pCellDefault : maDefaultList)
+ {
+ const ScRTFCellDefault& rD = *pCellDefault;
+ SCCOL nCol;
+ if ( !SeekTwips(rD.nTwips, &nCol) )
+ aColTwips.insert( rD.nTwips );
+ }
+ }
+ pDefMerge = nullptr;
+ pActDefault = maDefaultList.empty() ? nullptr : maDefaultList[0].get();
+ mnCurPos = 0;
+ OSL_ENSURE( pActDefault, "NewCellRow: pActDefault==0" );
+}
+
+/*
+ SW:
+ ~~~
+ [\par]
+ \trowd \cellx \cellx ...
+ \intbl \cell \cell ...
+ \row
+ [\par]
+ [\trowd \cellx \cellx ...]
+ \intbl \cell \cell ...
+ \row
+ [\par]
+
+ M$-Word:
+ ~~~~~~~~
+ [\par]
+ \trowd \cellx \cellx ...
+ \intbl \cell \cell ...
+ \intbl \row
+ [\par]
+ [\trowd \cellx \cellx ...]
+ \intbl \cell \cell ...
+ \intbl \row
+ [\par]
+
+ */
+
+void ScRTFParser::ProcToken( RtfImportInfo* pInfo )
+{
+ switch ( pInfo->nToken )
+ {
+ case RTF_TROWD: // denotes table row default, before RTF_CELLX
+ {
+ if (!maDefaultList.empty())
+ nLastWidth = maDefaultList.back()->nTwips;
+
+ nColCnt = 0;
+ if (pActDefault != pInsDefault.get())
+ pActDefault = nullptr;
+ maDefaultList.clear();
+ pDefMerge = nullptr;
+ nRtfLastToken = pInfo->nToken;
+ mnCurPos = 0;
+ }
+ break;
+ case RTF_CLMGF: // The first cell of cells to be merged
+ {
+ pDefMerge = pInsDefault.get();
+ nRtfLastToken = pInfo->nToken;
+ }
+ break;
+ case RTF_CLMRG: // A cell to be merged with the preceding cell
+ {
+ if (!pDefMerge && !maDefaultList.empty())
+ {
+ pDefMerge = maDefaultList.back().get();
+ mnCurPos = maDefaultList.size() - 1;
+ }
+ OSL_ENSURE( pDefMerge, "RTF_CLMRG: pDefMerge==0" );
+ if ( pDefMerge ) // Else broken RTF
+ pDefMerge->nColOverlap++; // multiple successive ones possible
+ pInsDefault->nColOverlap = 0; // Flag: ignore these
+ nRtfLastToken = pInfo->nToken;
+ }
+ break;
+ case RTF_CELLX: // closes cell default
+ {
+ bNewDef = true;
+ pInsDefault->nCol = nColCnt;
+ pInsDefault->nTwips = pInfo->nTokenValue; // Right cell border
+ maDefaultList.push_back( std::move(pInsDefault) );
+ // New free-flying pInsDefault
+ pInsDefault.reset( new ScRTFCellDefault( pPool.get() ) );
+ if ( ++nColCnt > nColMax )
+ nColMax = nColCnt;
+ nRtfLastToken = pInfo->nToken;
+ }
+ break;
+ case RTF_INTBL: // before the first RTF_CELL
+ {
+ // Once over NextToken and once over UnknownAttrToken
+ // or e.g. \intbl ... \cell \pard \intbl ... \cell
+ if ( nRtfLastToken != RTF_INTBL && nRtfLastToken != RTF_CELL && nRtfLastToken != RTF_PAR )
+ {
+ NewCellRow();
+ nRtfLastToken = pInfo->nToken;
+ }
+ }
+ break;
+ case RTF_CELL: // denotes the end of a cell.
+ {
+ OSL_ENSURE( pActDefault, "RTF_CELL: pActDefault==0" );
+ if ( bNewDef || !pActDefault )
+ NewCellRow(); // before was no \intbl, bad behavior
+ // Broken RTF? Let's save what we can
+ if ( !pActDefault )
+ pActDefault = pInsDefault.get();
+ if ( pActDefault->nColOverlap > 0 )
+ { // Not merged with preceding
+ mxActEntry->nCol = pActDefault->nCol;
+ mxActEntry->nColOverlap = pActDefault->nColOverlap;
+ mxActEntry->nTwips = pActDefault->nTwips;
+ mxActEntry->nRow = nRowCnt;
+ mxActEntry->aItemSet.Set(pActDefault->aItemSet);
+ EntryEnd(mxActEntry.get(), pInfo->aSelection);
+
+ if ( nStartAdjust == sal_uLong(~0) )
+ nStartAdjust = maList.size();
+ maList.push_back(mxActEntry);
+ NewActEntry(mxActEntry.get()); // New free-flying mxActEntry
+ }
+ else
+ { // Assign current Twips to MergeCell
+ if ( !maList.empty() )
+ {
+ auto& pE = maList.back();
+ pE->nTwips = pActDefault->nTwips;
+ }
+ // Adjust selection of free-flying mxActEntry
+ // Paragraph -1 due to separated text in EditEngine during parsing
+ mxActEntry->aSel.nStartPara = pInfo->aSelection.nEndPara - 1;
+ }
+
+ pActDefault = nullptr;
+ if (!maDefaultList.empty() && (mnCurPos+1) < maDefaultList.size())
+ pActDefault = maDefaultList[++mnCurPos].get();
+
+ nRtfLastToken = pInfo->nToken;
+ }
+ break;
+ case RTF_ROW: // denotes the end of a row
+ {
+ NextRow();
+ nRtfLastToken = pInfo->nToken;
+ }
+ break;
+ case RTF_PAR: // Paragraph
+ {
+ if ( !pActDefault )
+ { // text not in table
+ ColAdjust(); // close the processing table
+ mxActEntry->nCol = 0;
+ mxActEntry->nRow = nRowCnt;
+ EntryEnd(mxActEntry.get(), pInfo->aSelection);
+ maList.push_back(mxActEntry);
+ NewActEntry(mxActEntry.get()); // new mxActEntry
+ NextRow();
+ }
+ nRtfLastToken = pInfo->nToken;
+ }
+ break;
+ default:
+ { // do not set nRtfLastToken
+ switch ( pInfo->nToken & ~(0xff | RTF_TABLEDEF) )
+ {
+ case RTF_SHADINGDEF:
+ static_cast<SvxRTFParser*>(pInfo->pParser)->ReadBackgroundAttr(
+ pInfo->nToken, pInsDefault->aItemSet, true );
+ break;
+ case RTF_BRDRDEF:
+ static_cast<SvxRTFParser*>(pInfo->pParser)->ReadBorderAttr(
+ pInfo->nToken, pInsDefault->aItemSet, true );
+ break;
+ }
+ }
+ }
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */