1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
3 * This file is part of the LibreOffice project.
5 * This Source Code Form is subject to the terms of the Mozilla Public
6 * License, v. 2.0. If a copy of the MPL was not distributed with this
7 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
9 * This file incorporates work covered by the following license notice:
11 * Licensed to the Apache Software Foundation (ASF) under one or more
12 * contributor license agreements. See the NOTICE file distributed
13 * with this work for additional information regarding copyright
14 * ownership. The ASF licenses this file to you under the Apache
15 * License, Version 2.0 (the "License"); you may not use this file
16 * except in compliance with the License. You may obtain a copy of
17 * the License at http://www.apache.org/licenses/LICENSE-2.0 .
21 #include <sal/config.h>
23 #include <comphelper/string.hxx>
25 #include <scitems.hxx>
27 #include <svtools/htmlcfg.hxx>
28 #include <editeng/colritem.hxx>
29 #include <editeng/brushitem.hxx>
30 #include <editeng/editeng.hxx>
31 #include <editeng/fhgtitem.hxx>
32 #include <editeng/fontitem.hxx>
33 #include <editeng/postitem.hxx>
34 #include <editeng/udlnitem.hxx>
35 #include <editeng/wghtitem.hxx>
36 #include <editeng/borderline.hxx>
37 #include <editeng/boxitem.hxx>
38 #include <editeng/justifyitem.hxx>
39 #include <sfx2/objsh.hxx>
40 #include <svl/numformat.hxx>
41 #include <svl/intitem.hxx>
42 #include <vcl/graphicfilter.hxx>
43 #include <svtools/parhtml.hxx>
44 #include <svtools/htmlkywd.hxx>
45 #include <svtools/htmltokn.h>
47 #include <vcl/outdev.hxx>
48 #include <vcl/svapp.hxx>
49 #include <tools/urlobj.hxx>
50 #include <osl/diagnose.h>
52 #include <rtl/tencinfo.h>
55 #include <htmlpars.hxx>
57 #include <document.hxx>
58 #include <rangelst.hxx>
60 #include <orcus/css_parser.hpp>
62 #include <com/sun/star/document/XDocumentProperties.hpp>
63 #include <com/sun/star/document/XDocumentPropertiesSupplier.hpp>
64 #include <com/sun/star/frame/XModel.hpp>
67 #include <officecfg/Office/Common.hxx>
69 using ::editeng::SvxBorderLine
;
70 using namespace ::com::sun::star
;
72 ScHTMLStyles::ScHTMLStyles() : maEmpty() {}
74 void ScHTMLStyles::add(const char* pElemName
, size_t nElemName
, const char* pClassName
, size_t nClassName
,
75 const OUString
& aProp
, const OUString
& aValue
)
79 OUString
aElem(pElemName
, nElemName
, RTL_TEXTENCODING_UTF8
);
80 aElem
= aElem
.toAsciiLowerCase();
83 // Both element and class names given.
84 ElemsType::iterator itrElem
= m_ElemProps
.find(aElem
);
85 if (itrElem
== m_ElemProps
.end())
88 std::pair
<ElemsType::iterator
, bool> r
=
89 m_ElemProps
.insert(std::make_pair(aElem
, NamePropsType()));
96 NamePropsType
& rClsProps
= itrElem
->second
;
97 OUString
aClass(pClassName
, nClassName
, RTL_TEXTENCODING_UTF8
);
98 aClass
= aClass
.toAsciiLowerCase();
99 insertProp(rClsProps
, aClass
, aProp
, aValue
);
103 // Element name only. Add it to the element global.
104 insertProp(m_ElemGlobalProps
, aElem
, aProp
, aValue
);
111 // Class name only. Add it to the global.
112 OUString
aClass(pClassName
, nClassName
, RTL_TEXTENCODING_UTF8
);
113 aClass
= aClass
.toAsciiLowerCase();
114 insertProp(m_GlobalProps
, aClass
, aProp
, aValue
);
119 const OUString
& ScHTMLStyles::getPropertyValue(
120 const OUString
& rElem
, const OUString
& rClass
, const OUString
& rPropName
) const
122 // First, look into the element-class storage.
124 auto const itr
= m_ElemProps
.find(rElem
);
125 if (itr
!= m_ElemProps
.end())
127 const NamePropsType
& rClasses
= itr
->second
;
128 NamePropsType::const_iterator itr2
= rClasses
.find(rClass
);
129 if (itr2
!= rClasses
.end())
131 const PropsType
& rProps
= itr2
->second
;
132 PropsType::const_iterator itr3
= rProps
.find(rPropName
);
133 if (itr3
!= rProps
.end())
138 // Next, look into the class global storage.
140 auto const itr
= m_GlobalProps
.find(rClass
);
141 if (itr
!= m_GlobalProps
.end())
143 const PropsType
& rProps
= itr
->second
;
144 PropsType::const_iterator itr2
= rProps
.find(rPropName
);
145 if (itr2
!= rProps
.end())
149 // As the last resort, look into the element global storage.
151 auto const itr
= m_ElemGlobalProps
.find(rClass
);
152 if (itr
!= m_ElemGlobalProps
.end())
154 const PropsType
& rProps
= itr
->second
;
155 PropsType::const_iterator itr2
= rProps
.find(rPropName
);
156 if (itr2
!= rProps
.end())
161 return maEmpty
; // nothing found.
164 void ScHTMLStyles::insertProp(
165 NamePropsType
& rStore
, const OUString
& aName
,
166 const OUString
& aProp
, const OUString
& aValue
)
168 NamePropsType::iterator itr
= rStore
.find(aName
);
169 if (itr
== rStore
.end())
172 std::pair
<NamePropsType::iterator
, bool> r
=
173 rStore
.insert(std::make_pair(aName
, PropsType()));
181 PropsType
& rProps
= itr
->second
;
182 rProps
.emplace(aProp
, aValue
);
185 // BASE class for HTML parser classes
187 ScHTMLParser::ScHTMLParser( EditEngine
* pEditEngine
, ScDocument
* pDoc
) :
188 ScEEParser( pEditEngine
),
191 maFontHeights
[0] = officecfg::Office::Common::Filter::HTML::Import::FontSize::Size_1::get() * 20;
192 maFontHeights
[1] = officecfg::Office::Common::Filter::HTML::Import::FontSize::Size_2::get() * 20;
193 maFontHeights
[2] = officecfg::Office::Common::Filter::HTML::Import::FontSize::Size_3::get() * 20;
194 maFontHeights
[3] = officecfg::Office::Common::Filter::HTML::Import::FontSize::Size_4::get() * 20;
195 maFontHeights
[4] = officecfg::Office::Common::Filter::HTML::Import::FontSize::Size_5::get() * 20;
196 maFontHeights
[5] = officecfg::Office::Common::Filter::HTML::Import::FontSize::Size_6::get() * 20;
197 maFontHeights
[6] = officecfg::Office::Common::Filter::HTML::Import::FontSize::Size_7::get() * 20;
200 ScHTMLParser::~ScHTMLParser()
204 ScHTMLLayoutParser::ScHTMLLayoutParser(
205 EditEngine
* pEditP
, const OUString
& rBaseURL
, const Size
& aPageSizeP
,
206 ScDocument
* pDocP
) :
207 ScHTMLParser( pEditP
, pDocP
),
208 aPageSize( aPageSizeP
),
209 aBaseURL( rBaseURL
),
210 xLockedList( new ScRangeList
),
211 pLocalColOffset( new ScHTMLColOffset
),
221 nOffsetTolerance( SC_HTML_OFFSET_TOLERANCE_SMALL
),
223 bTabInTabCell( false ),
227 MakeColNoRef( pLocalColOffset
, 0, 0, 0, 0 );
228 MakeColNoRef( &maColOffset
, 0, 0, 0, 0 );
231 ScHTMLLayoutParser::~ScHTMLLayoutParser()
233 while ( !aTableStack
.empty() )
235 ScHTMLTableStackEntry
* pS
= aTableStack
.top().get();
236 if ( pS
->pLocalColOffset
!= pLocalColOffset
)
237 delete pS
->pLocalColOffset
;
240 delete pLocalColOffset
;
243 for( const auto& rEntry
: *pTables
)
244 delete rEntry
.second
;
249 ErrCode
ScHTMLLayoutParser::Read( SvStream
& rStream
, const OUString
& rBaseURL
)
251 Link
<HtmlImportInfo
&,void> aOldLink
= pEdit
->GetHtmlImportHdl();
252 pEdit
->SetHtmlImportHdl( LINK( this, ScHTMLLayoutParser
, HTMLImportHdl
) );
254 SfxObjectShell
* pObjSh
= mpDoc
->GetDocumentShell();
255 bool bLoading
= pObjSh
&& pObjSh
->IsLoading();
257 SvKeyValueIteratorRef xValues
;
258 SvKeyValueIterator
* pAttributes
= nullptr;
260 pAttributes
= pObjSh
->GetHeaderAttributes();
263 // When not loading, set up fake http headers to force the SfxHTMLParser to use UTF8
264 // (used when pasting from clipboard)
265 const char* pCharSet
= rtl_getBestMimeCharsetFromTextEncoding( RTL_TEXTENCODING_UTF8
);
268 OUString aContentType
= "text/html; charset=" +
269 OUString::createFromAscii( pCharSet
);
271 xValues
= new SvKeyValueIterator
;
272 xValues
->Append( SvKeyValue( OOO_STRING_SVTOOLS_HTML_META_content_type
, aContentType
) );
273 pAttributes
= xValues
.get();
277 ErrCode nErr
= pEdit
->Read( rStream
, rBaseURL
, EETextFormat::Html
, pAttributes
);
279 pEdit
->SetHtmlImportHdl( aOldLink
);
280 // Create column width
282 OutputDevice
* pDefaultDev
= Application::GetDefaultDevice();
283 sal_uInt16 nCount
= maColOffset
.size();
284 sal_uLong nOff
= maColOffset
[0];
286 for ( sal_uInt16 j
= 1; j
< nCount
; j
++ )
288 aSize
.setWidth( maColOffset
[j
] - nOff
);
289 aSize
= pDefaultDev
->PixelToLogic( aSize
, MapMode( MapUnit::MapTwip
) );
290 maColWidths
[ j
-1 ] = aSize
.Width();
291 nOff
= maColOffset
[j
];
296 const ScHTMLTable
* ScHTMLLayoutParser::GetGlobalTable() const
301 void ScHTMLLayoutParser::NewActEntry( const ScEEParseEntry
* pE
)
303 ScEEParser::NewActEntry( pE
);
306 if ( !pE
->aSel
.HasRange() )
307 { // Completely empty, following text ends up in the same paragraph!
308 mxActEntry
->aSel
.nStartPara
= pE
->aSel
.nEndPara
;
309 mxActEntry
->aSel
.nStartPos
= pE
->aSel
.nEndPos
;
312 mxActEntry
->aSel
.nEndPara
= mxActEntry
->aSel
.nStartPara
;
313 mxActEntry
->aSel
.nEndPos
= mxActEntry
->aSel
.nStartPos
;
316 void ScHTMLLayoutParser::EntryEnd( ScEEParseEntry
* pE
, const ESelection
& rSel
)
318 if ( rSel
.nEndPara
>= pE
->aSel
.nStartPara
)
320 pE
->aSel
.nEndPara
= rSel
.nEndPara
;
321 pE
->aSel
.nEndPos
= rSel
.nEndPos
;
323 else if ( rSel
.nStartPara
== pE
->aSel
.nStartPara
- 1 && !pE
->aSel
.HasRange() )
324 { // Did not attach a paragraph, but empty, do nothing
328 OSL_FAIL( "EntryEnd: EditEngine ESelection End < Start" );
332 void ScHTMLLayoutParser::NextRow( const HtmlImportInfo
* pInfo
)
336 if ( nRowMax
< ++nRowCnt
)
338 nColCnt
= nColCntStart
;
339 nColOffset
= nColOffsetStart
;
343 bool ScHTMLLayoutParser::SeekOffset( const ScHTMLColOffset
* pOffset
, sal_uInt16 nOffset
,
344 SCCOL
* pCol
, sal_uInt16 nOffsetTol
)
346 OSL_ENSURE( pOffset
, "ScHTMLLayoutParser::SeekOffset - illegal call" );
347 ScHTMLColOffset::const_iterator it
= pOffset
->find( nOffset
);
348 bool bFound
= it
!= pOffset
->end();
349 sal_uInt16 nPos
= it
- pOffset
->begin();
350 *pCol
= static_cast<SCCOL
>(nPos
);
353 sal_uInt16 nCount
= pOffset
->size();
356 // nPos is the position of insertion, that's where the next higher one is (or isn't)
357 if ( nPos
< nCount
&& (((*pOffset
)[nPos
] - nOffsetTol
) <= nOffset
) )
359 // Not smaller than everything else? Then compare with the next lower one
360 else if ( nPos
&& (((*pOffset
)[nPos
-1] + nOffsetTol
) >= nOffset
) )
368 void ScHTMLLayoutParser::MakeCol( ScHTMLColOffset
* pOffset
, sal_uInt16
& nOffset
,
369 sal_uInt16
& nWidth
, sal_uInt16 nOffsetTol
, sal_uInt16 nWidthTol
)
371 OSL_ENSURE( pOffset
, "ScHTMLLayoutParser::MakeCol - illegal call" );
373 if ( SeekOffset( pOffset
, nOffset
, &nPos
, nOffsetTol
) )
374 nOffset
= static_cast<sal_uInt16
>((*pOffset
)[nPos
]);
376 pOffset
->insert( nOffset
);
379 if ( SeekOffset( pOffset
, nOffset
+ nWidth
, &nPos
, nWidthTol
) )
380 nWidth
= static_cast<sal_uInt16
>((*pOffset
)[nPos
]) - nOffset
;
382 pOffset
->insert( nOffset
+ nWidth
);
386 void ScHTMLLayoutParser::MakeColNoRef( ScHTMLColOffset
* pOffset
, sal_uInt16 nOffset
,
387 sal_uInt16 nWidth
, sal_uInt16 nOffsetTol
, sal_uInt16 nWidthTol
)
389 OSL_ENSURE( pOffset
, "ScHTMLLayoutParser::MakeColNoRef - illegal call" );
391 if ( SeekOffset( pOffset
, nOffset
, &nPos
, nOffsetTol
) )
392 nOffset
= static_cast<sal_uInt16
>((*pOffset
)[nPos
]);
394 pOffset
->insert( nOffset
);
397 if ( !SeekOffset( pOffset
, nOffset
+ nWidth
, &nPos
, nWidthTol
) )
398 pOffset
->insert( nOffset
+ nWidth
);
402 void ScHTMLLayoutParser::ModifyOffset( ScHTMLColOffset
* pOffset
, sal_uInt16
& nOldOffset
,
403 sal_uInt16
& nNewOffset
, sal_uInt16 nOffsetTol
)
405 OSL_ENSURE( pOffset
, "ScHTMLLayoutParser::ModifyOffset - illegal call" );
407 if ( !SeekOffset( pOffset
, nOldOffset
, &nPos
, nOffsetTol
) )
409 if ( SeekOffset( pOffset
, nNewOffset
, &nPos
, nOffsetTol
) )
410 nNewOffset
= static_cast<sal_uInt16
>((*pOffset
)[nPos
]);
412 pOffset
->insert( nNewOffset
);
415 nOldOffset
= static_cast<sal_uInt16
>((*pOffset
)[nPos
]);
417 if ( SeekOffset( pOffset
, nNewOffset
, &nPos2
, nOffsetTol
) )
419 nNewOffset
= static_cast<sal_uInt16
>((*pOffset
)[nPos2
]);
422 tools::Long nDiff
= nNewOffset
- nOldOffset
;
427 const_cast<sal_uLong
&>((*pOffset
)[nPos
]) += nDiff
;
434 const_cast<sal_uLong
&>((*pOffset
)[nPos
]) += nDiff
;
435 } while ( ++nPos
< static_cast<sal_uInt16
>(pOffset
->size()) );
439 void ScHTMLLayoutParser::SkipLocked( ScEEParseEntry
* pE
, bool bJoin
)
441 if ( !mpDoc
->ValidCol(pE
->nCol
) )
444 // Or else this would create a wrong value at ScAddress (chance for an infinite loop)!
445 bool bBadCol
= false;
447 ScRange
aRange( pE
->nCol
, pE
->nRow
, 0,
448 pE
->nCol
+ pE
->nColOverlap
- 1, pE
->nRow
+ pE
->nRowOverlap
- 1, 0 );
452 for ( size_t i
= 0, nRanges
= xLockedList
->size(); i
< nRanges
; ++i
)
454 ScRange
& rR
= (*xLockedList
)[i
];
455 if ( rR
.Intersects( aRange
) )
457 pE
->nCol
= rR
.aEnd
.Col() + 1;
458 SCCOL nTmp
= pE
->nCol
+ pE
->nColOverlap
- 1;
459 if ( pE
->nCol
> mpDoc
->MaxCol() || nTmp
> mpDoc
->MaxCol() )
464 aRange
.aStart
.SetCol( pE
->nCol
);
465 aRange
.aEnd
.SetCol( nTmp
);
471 if ( bJoin
&& !bBadCol
)
472 xLockedList
->Join( aRange
);
475 void ScHTMLLayoutParser::Adjust()
477 xLockedList
->RemoveAll();
479 std::stack
< std::unique_ptr
<ScHTMLAdjustStackEntry
> > aStack
;
481 SCCOL nLastCol
= SCCOL_MAX
;
484 sal_uInt16 nPageWidth
= static_cast<sal_uInt16
>(aPageSize
.Width());
485 InnerMap
* pTab
= nullptr;
486 for (auto& pE
: maList
)
488 if ( pE
->nTab
< nTab
)
490 if ( !aStack
.empty() )
492 std::unique_ptr
<ScHTMLAdjustStackEntry
> pS
= std::move(aStack
.top());
495 nLastCol
= pS
->nLastCol
;
496 nNextRow
= pS
->nNextRow
;
497 nCurRow
= pS
->nCurRow
;
502 OuterMap::const_iterator it
= pTables
->find( nTab
);
503 if ( it
!= pTables
->end() )
508 SCROW nRow
= pE
->nRow
;
509 if ( pE
->nCol
<= nLastCol
)
511 if ( pE
->nRow
< nNextRow
)
512 pE
->nRow
= nCurRow
= nNextRow
;
514 nCurRow
= nNextRow
= pE
->nRow
;
518 InnerMap::const_iterator it
= pTab
->find( nCurRow
);
519 if ( it
!= pTab
->end() )
529 nLastCol
= pE
->nCol
; // Read column
530 if ( pE
->nTab
> nTab
)
532 aStack
.push( std::make_unique
<ScHTMLAdjustStackEntry
>(
533 nLastCol
, nNextRow
, nCurRow
) );
537 OuterMap::const_iterator it
= pTables
->find( nTab
);
538 if ( it
!= pTables
->end() )
545 InnerMap::const_iterator it
= pTab
->find( nCurRow
);
546 if ( it
!= pTab
->end() )
550 nNextRow
= nCurRow
+ nR
;
552 nNextRow
= nCurRow
+ 1;
555 pE
->nWidth
= nPageWidth
;
557 { // Real table, no paragraphs on the field
560 SCROW nRowSpan
= pE
->nRowOverlap
;
561 for ( SCROW j
=0; j
< nRowSpan
; j
++ )
562 { // RowSpan resulting from merged rows
564 InnerMap::const_iterator it
= pTab
->find( nRow
+j
);
565 if ( it
!= pTab
->end() )
569 pE
->nRowOverlap
+= nRows
- 1;
571 { // Merged rows move the next row
572 SCROW nTmp
= nCurRow
+ nRows
;
573 if ( nNextRow
< nTmp
)
581 (void)SeekOffset( &maColOffset
, pE
->nOffset
, &pE
->nCol
, nOffsetTolerance
);
582 SCCOL nColBeforeSkip
= pE
->nCol
;
583 SkipLocked(pE
.get(), false);
584 if ( pE
->nCol
!= nColBeforeSkip
)
586 SCCOL nCount
= static_cast<SCCOL
>(maColOffset
.size());
587 if ( nCount
<= pE
->nCol
)
589 pE
->nOffset
= static_cast<sal_uInt16
>(maColOffset
[nCount
-1]);
590 MakeCol( &maColOffset
, pE
->nOffset
, pE
->nWidth
, nOffsetTolerance
, nOffsetTolerance
);
594 pE
->nOffset
= static_cast<sal_uInt16
>(maColOffset
[pE
->nCol
]);
598 if ( pE
->nWidth
&& SeekOffset( &maColOffset
, pE
->nOffset
+ pE
->nWidth
, &nPos
, nOffsetTolerance
) )
599 pE
->nColOverlap
= (nPos
> pE
->nCol
? nPos
- pE
->nCol
: 1);
602 //FIXME: This may not be correct, but works anyway ...
605 xLockedList
->Join( ScRange( pE
->nCol
, pE
->nRow
, 0,
606 pE
->nCol
+ pE
->nColOverlap
- 1, pE
->nRow
+ pE
->nRowOverlap
- 1, 0 ) );
607 // Take over MaxDimensions
608 SCCOL nColTmp
= pE
->nCol
+ pE
->nColOverlap
;
609 if ( nColMax
< nColTmp
)
611 SCROW nRowTmp
= pE
->nRow
+ pE
->nRowOverlap
;
612 if ( nRowMax
< nRowTmp
)
617 sal_uInt16
ScHTMLLayoutParser::GetWidth( const ScEEParseEntry
* pE
)
621 sal_Int32 nTmp
= std::min( static_cast<sal_Int32
>( pE
->nCol
-
622 nColCntStart
+ pE
->nColOverlap
),
623 static_cast<sal_Int32
>( pLocalColOffset
->size() - 1));
624 SCCOL nPos
= (nTmp
< 0 ? 0 : static_cast<SCCOL
>(nTmp
));
625 sal_uInt16 nOff2
= static_cast<sal_uInt16
>((*pLocalColOffset
)[nPos
]);
626 if ( pE
->nOffset
< nOff2
)
627 return nOff2
- pE
->nOffset
;
631 void ScHTMLLayoutParser::SetWidths()
635 nTableWidth
= static_cast<sal_uInt16
>(aPageSize
.Width());
636 SCCOL nColsPerRow
= nMaxCol
- nColCntStart
;
637 if ( nColsPerRow
<= 0 )
639 if ( pLocalColOffset
->size() <= 2 )
640 { // Only PageSize, there was no width setting
641 sal_uInt16 nWidth
= nTableWidth
/ static_cast<sal_uInt16
>(nColsPerRow
);
642 sal_uInt16 nOff
= nColOffsetStart
;
643 pLocalColOffset
->clear();
644 for ( nCol
= 0; nCol
<= nColsPerRow
; ++nCol
, nOff
= nOff
+ nWidth
)
646 MakeColNoRef( pLocalColOffset
, nOff
, 0, 0, 0 );
648 nTableWidth
= static_cast<sal_uInt16
>(pLocalColOffset
->back() - pLocalColOffset
->front());
649 for ( size_t i
= nFirstTableCell
, nListSize
= maList
.size(); i
< nListSize
; ++i
)
651 auto& pE
= maList
[ i
];
652 if ( pE
->nTab
== nTable
)
654 pE
->nOffset
= static_cast<sal_uInt16
>((*pLocalColOffset
)[pE
->nCol
- nColCntStart
]);
655 pE
->nWidth
= 0; // to be recalculated later
660 { // Some without width
661 // Why actually no pE?
662 if ( nFirstTableCell
< maList
.size() )
664 std::unique_ptr
<sal_uInt16
[]> pOffsets(new sal_uInt16
[ nColsPerRow
+1 ]);
665 memset( pOffsets
.get(), 0, (nColsPerRow
+1) * sizeof(sal_uInt16
) );
666 std::unique_ptr
<sal_uInt16
[]> pWidths(new sal_uInt16
[ nColsPerRow
]);
667 memset( pWidths
.get(), 0, nColsPerRow
* sizeof(sal_uInt16
) );
668 pOffsets
[0] = nColOffsetStart
;
669 for ( size_t i
= nFirstTableCell
, nListSize
= maList
.size(); i
< nListSize
; ++i
)
671 auto& pE
= maList
[ i
];
672 if ( pE
->nTab
== nTable
&& pE
->nWidth
)
674 nCol
= pE
->nCol
- nColCntStart
;
675 if ( nCol
< nColsPerRow
)
677 if ( pE
->nColOverlap
== 1 )
679 if ( pWidths
[nCol
] < pE
->nWidth
)
680 pWidths
[nCol
] = pE
->nWidth
;
683 { // try to find a single undefined width
684 sal_uInt16 nTotal
= 0;
687 SCCOL nStop
= std::min( static_cast<SCCOL
>(nCol
+ pE
->nColOverlap
), nColsPerRow
);
688 for ( ; nCol
< nStop
; nCol
++ )
691 nTotal
= nTotal
+ pWidths
[nCol
];
703 if ( bFound
&& pE
->nWidth
> nTotal
)
704 pWidths
[nHere
] = pE
->nWidth
- nTotal
;
709 sal_uInt16 nWidths
= 0;
710 sal_uInt16 nUnknown
= 0;
711 for ( nCol
= 0; nCol
< nColsPerRow
; nCol
++ )
714 nWidths
= nWidths
+ pWidths
[nCol
];
720 sal_uInt16 nW
= ((nWidths
< nTableWidth
) ?
721 ((nTableWidth
- nWidths
) / nUnknown
) :
722 (nTableWidth
/ nUnknown
));
723 for ( nCol
= 0; nCol
< nColsPerRow
; nCol
++ )
725 if ( !pWidths
[nCol
] )
729 for ( nCol
= 1; nCol
<= nColsPerRow
; nCol
++ )
731 pOffsets
[nCol
] = pOffsets
[nCol
-1] + pWidths
[nCol
-1];
733 pLocalColOffset
->clear();
734 for ( nCol
= 0; nCol
<= nColsPerRow
; nCol
++ )
736 MakeColNoRef( pLocalColOffset
, pOffsets
[nCol
], 0, 0, 0 );
738 nTableWidth
= pOffsets
[nColsPerRow
] - pOffsets
[0];
740 for ( size_t i
= nFirstTableCell
, nListSize
= maList
.size(); i
< nListSize
; ++i
)
742 auto& pE
= maList
[ i
];
743 if ( pE
->nTab
== nTable
)
745 nCol
= pE
->nCol
- nColCntStart
;
746 OSL_ENSURE( nCol
< nColsPerRow
, "ScHTMLLayoutParser::SetWidths: column overflow" );
747 if ( nCol
< nColsPerRow
)
749 pE
->nOffset
= pOffsets
[nCol
];
750 nCol
= nCol
+ pE
->nColOverlap
;
751 if ( nCol
> nColsPerRow
)
753 pE
->nWidth
= pOffsets
[nCol
] - pE
->nOffset
;
759 if ( !pLocalColOffset
->empty() )
761 sal_uInt16 nMax
= static_cast<sal_uInt16
>(pLocalColOffset
->back());
762 if ( aPageSize
.Width() < nMax
)
763 aPageSize
.setWidth( nMax
);
764 if (nTableLevel
== 0)
766 // Local table is very outer table, create missing offsets.
767 for (auto it
= pLocalColOffset
->begin(); it
!= pLocalColOffset
->end(); ++it
)
769 // Only exact offsets, do not use MakeColNoRef().
770 if (maColOffset
.find(*it
) == maColOffset
.end())
771 maColOffset
.insert(*it
);
775 for ( size_t i
= nFirstTableCell
, nListSize
= maList
.size(); i
< nListSize
; ++i
)
777 auto& pE
= maList
[ i
];
778 if ( pE
->nTab
== nTable
)
782 pE
->nWidth
= GetWidth(pE
.get());
783 OSL_ENSURE( pE
->nWidth
, "SetWidths: pE->nWidth == 0" );
785 MakeCol( &maColOffset
, pE
->nOffset
, pE
->nWidth
, nOffsetTolerance
, nOffsetTolerance
);
790 void ScHTMLLayoutParser::Colonize( ScEEParseEntry
* pE
)
792 if ( pE
->nCol
== SCCOL_MAX
)
794 if ( pE
->nRow
== SCROW_MAX
)
796 SCCOL nCol
= pE
->nCol
;
797 SkipLocked( pE
); // Change of columns to the right
799 if ( nCol
< pE
->nCol
)
801 nCol
= pE
->nCol
- nColCntStart
;
802 SCCOL nCount
= static_cast<SCCOL
>(pLocalColOffset
->size());
804 nColOffset
= static_cast<sal_uInt16
>((*pLocalColOffset
)[nCol
]);
806 nColOffset
= static_cast<sal_uInt16
>((*pLocalColOffset
)[nCount
- 1]);
808 pE
->nOffset
= nColOffset
;
809 sal_uInt16 nWidth
= GetWidth( pE
);
810 MakeCol( pLocalColOffset
, pE
->nOffset
, nWidth
, nOffsetTolerance
, nOffsetTolerance
);
813 nColOffset
= pE
->nOffset
+ nWidth
;
814 if ( nTableWidth
< nColOffset
- nColOffsetStart
)
815 nTableWidth
= nColOffset
- nColOffsetStart
;
818 void ScHTMLLayoutParser::CloseEntry( const HtmlImportInfo
* pInfo
)
822 { // From the stack in TableOff
823 bTabInTabCell
= false;
824 NewActEntry(maList
.back().get()); // New free flying mxActEntry
827 if (mxActEntry
->nTab
== 0)
828 mxActEntry
->nWidth
= static_cast<sal_uInt16
>(aPageSize
.Width());
829 Colonize(mxActEntry
.get());
830 nColCnt
= mxActEntry
->nCol
+ mxActEntry
->nColOverlap
;
831 if ( nMaxCol
< nColCnt
)
832 nMaxCol
= nColCnt
; // TableStack MaxCol
833 if ( nColMax
< nColCnt
)
834 nColMax
= nColCnt
; // Global MaxCol for ScEEParser GetDimensions!
835 EntryEnd(mxActEntry
.get(), pInfo
->aSelection
);
836 ESelection
& rSel
= mxActEntry
->aSel
;
837 while ( rSel
.nStartPara
< rSel
.nEndPara
838 && pEdit
->GetTextLen( rSel
.nStartPara
) == 0 )
839 { // Strip preceding empty paragraphs
842 while ( rSel
.nEndPos
== 0 && rSel
.nEndPara
> rSel
.nStartPara
)
843 { // Strip successive empty paragraphs
845 rSel
.nEndPos
= pEdit
->GetTextLen( rSel
.nEndPara
);
847 if ( rSel
.nStartPara
> rSel
.nEndPara
)
848 { // Gives GPF in CreateTextObject
849 OSL_FAIL( "CloseEntry: EditEngine ESelection Start > End" );
850 rSel
.nEndPara
= rSel
.nStartPara
;
852 if ( rSel
.HasRange() )
853 mxActEntry
->aItemSet
.Put( ScLineBreakCell(true) );
854 maList
.push_back(mxActEntry
);
855 NewActEntry(mxActEntry
.get()); // New free flying mxActEntry
858 IMPL_LINK( ScHTMLLayoutParser
, HTMLImportHdl
, HtmlImportInfo
&, rInfo
, void )
860 switch ( rInfo
.eState
)
862 case HtmlImportState::NextToken
:
865 case HtmlImportState::Start
:
867 case HtmlImportState::End
:
868 if ( rInfo
.aSelection
.nEndPos
)
870 // If text remains: create paragraph, without calling CloseEntry().
871 if( bInCell
) // ...but only in opened table cells.
877 CloseEntry( &rInfo
);
879 while ( nTableLevel
> 0 )
880 TableOff( &rInfo
); // close tables, if </TABLE> missing
882 case HtmlImportState::SetAttr
:
884 case HtmlImportState::InsertText
:
886 case HtmlImportState::InsertPara
:
887 if ( nTableLevel
< 1 )
889 CloseEntry( &rInfo
);
893 case HtmlImportState::InsertField
:
896 OSL_FAIL("HTMLImportHdl: unknown ImportInfo.eState");
900 void ScHTMLLayoutParser::TableDataOn( HtmlImportInfo
* pInfo
)
906 OSL_FAIL( "dumbo doc! <TH> or <TD> without previous <TABLE>" );
910 bool bHorJustifyCenterTH
= (pInfo
->nToken
== HtmlTokenId::TABLEHEADER_ON
);
911 const HTMLOptions
& rOptions
= static_cast<HTMLParser
*>(pInfo
->pParser
)->GetOptions();
912 for (const auto & rOption
: rOptions
)
914 switch( rOption
.GetToken() )
916 case HtmlOptionId::COLSPAN
:
918 mxActEntry
->nColOverlap
= static_cast<SCCOL
>(rOption
.GetString().toInt32());
921 case HtmlOptionId::ROWSPAN
:
923 mxActEntry
->nRowOverlap
= static_cast<SCROW
>(rOption
.GetString().toInt32());
926 case HtmlOptionId::ALIGN
:
928 bHorJustifyCenterTH
= false;
929 SvxCellHorJustify eVal
;
930 const OUString
& rOptVal
= rOption
.GetString();
931 if ( rOptVal
.equalsIgnoreAsciiCase( OOO_STRING_SVTOOLS_HTML_AL_right
) )
932 eVal
= SvxCellHorJustify::Right
;
933 else if ( rOptVal
.equalsIgnoreAsciiCase( OOO_STRING_SVTOOLS_HTML_AL_center
) )
934 eVal
= SvxCellHorJustify::Center
;
935 else if ( rOptVal
.equalsIgnoreAsciiCase( OOO_STRING_SVTOOLS_HTML_AL_left
) )
936 eVal
= SvxCellHorJustify::Left
;
938 eVal
= SvxCellHorJustify::Standard
;
939 if ( eVal
!= SvxCellHorJustify::Standard
)
940 mxActEntry
->aItemSet
.Put(SvxHorJustifyItem(eVal
, ATTR_HOR_JUSTIFY
));
943 case HtmlOptionId::VALIGN
:
945 SvxCellVerJustify eVal
;
946 const OUString
& rOptVal
= rOption
.GetString();
947 if ( rOptVal
.equalsIgnoreAsciiCase( OOO_STRING_SVTOOLS_HTML_VA_top
) )
948 eVal
= SvxCellVerJustify::Top
;
949 else if ( rOptVal
.equalsIgnoreAsciiCase( OOO_STRING_SVTOOLS_HTML_VA_middle
) )
950 eVal
= SvxCellVerJustify::Center
;
951 else if ( rOptVal
.equalsIgnoreAsciiCase( OOO_STRING_SVTOOLS_HTML_VA_bottom
) )
952 eVal
= SvxCellVerJustify::Bottom
;
954 eVal
= SvxCellVerJustify::Standard
;
955 mxActEntry
->aItemSet
.Put(SvxVerJustifyItem(eVal
, ATTR_VER_JUSTIFY
));
958 case HtmlOptionId::WIDTH
:
960 mxActEntry
->nWidth
= GetWidthPixel(rOption
);
963 case HtmlOptionId::BGCOLOR
:
966 rOption
.GetColor( aColor
);
967 mxActEntry
->aItemSet
.Put(SvxBrushItem(aColor
, ATTR_BACKGROUND
));
970 case HtmlOptionId::SDVAL
:
972 mxActEntry
->pValStr
= rOption
.GetString();
975 case HtmlOptionId::SDNUM
:
977 mxActEntry
->pNumStr
= rOption
.GetString();
984 mxActEntry
->nCol
= nColCnt
;
985 mxActEntry
->nRow
= nRowCnt
;
986 mxActEntry
->nTab
= nTable
;
988 if ( bHorJustifyCenterTH
)
989 mxActEntry
->aItemSet
.Put(
990 SvxHorJustifyItem( SvxCellHorJustify::Center
, ATTR_HOR_JUSTIFY
) );
993 void ScHTMLLayoutParser::TableRowOn( const HtmlImportInfo
* pInfo
)
995 if ( nColCnt
> nColCntStart
)
996 NextRow( pInfo
); // The optional TableRowOff wasn't there
997 nColOffset
= nColOffsetStart
;
1000 void ScHTMLLayoutParser::TableRowOff( const HtmlImportInfo
* pInfo
)
1005 void ScHTMLLayoutParser::TableDataOff( const HtmlImportInfo
* pInfo
)
1008 CloseEntry( pInfo
); // Only if it really was one
1011 void ScHTMLLayoutParser::TableOn( HtmlImportInfo
* pInfo
)
1013 if ( ++nTableLevel
> 1 )
1015 sal_uInt16 nTmpColOffset
= nColOffset
; // Will be changed in Colonize()
1016 Colonize(mxActEntry
.get());
1017 aTableStack
.push( std::make_unique
<ScHTMLTableStackEntry
>(
1018 mxActEntry
, xLockedList
, pLocalColOffset
, nFirstTableCell
,
1019 nRowCnt
, nColCntStart
, nMaxCol
, nTable
,
1020 nTableWidth
, nColOffset
, nColOffsetStart
,
1022 sal_uInt16 nLastWidth
= nTableWidth
;
1023 nTableWidth
= GetWidth(mxActEntry
.get());
1024 if ( nTableWidth
== nLastWidth
&& nMaxCol
- nColCntStart
> 1 )
1025 { // There must be more than one, so this one cannot be enough
1026 nTableWidth
= nLastWidth
/ static_cast<sal_uInt16
>((nMaxCol
- nColCntStart
));
1028 nLastWidth
= nTableWidth
;
1029 if ( pInfo
->nToken
== HtmlTokenId::TABLE_ON
)
1030 { // It can still be TD or TH, if we didn't have a TABLE earlier
1031 const HTMLOptions
& rOptions
= static_cast<HTMLParser
*>(pInfo
->pParser
)->GetOptions();
1032 for (const auto & rOption
: rOptions
)
1034 switch( rOption
.GetToken() )
1036 case HtmlOptionId::WIDTH
:
1037 { // Percent: of document width or outer cell
1038 nTableWidth
= GetWidthPixel( rOption
);
1041 case HtmlOptionId::BORDER
:
1042 // Border is: ((pOption->GetString().Len() == 0) || (pOption->GetNumber() != 0));
1049 if ( bTabInTabCell
&& (nTableWidth
>= nLastWidth
) )
1050 { // Multiple tables in one cell, underneath each other
1051 bTabInTabCell
= false;
1055 { // It start's in this cell or next to each other
1056 bTabInTabCell
= false;
1057 nColCntStart
= nColCnt
;
1058 nColOffset
= nTmpColOffset
;
1059 nColOffsetStart
= nColOffset
;
1062 NewActEntry(!maList
.empty() ? maList
.back().get() : nullptr); // New free flying mxActEntry
1063 xLockedList
= new ScRangeList
;
1066 { // Simple table at the document level
1067 EntryEnd(mxActEntry
.get(), pInfo
->aSelection
);
1068 if (mxActEntry
->aSel
.HasRange())
1069 { // Flying text left
1070 CloseEntry( pInfo
);
1073 aTableStack
.push( std::make_unique
<ScHTMLTableStackEntry
>(
1074 mxActEntry
, xLockedList
, pLocalColOffset
, nFirstTableCell
,
1075 nRowCnt
, nColCntStart
, nMaxCol
, nTable
,
1076 nTableWidth
, nColOffset
, nColOffsetStart
,
1078 // As soon as we have multiple tables we need to be tolerant with the offsets.
1080 nOffsetTolerance
= SC_HTML_OFFSET_TOLERANCE_LARGE
;
1082 if ( pInfo
->nToken
== HtmlTokenId::TABLE_ON
)
1084 // It can still be TD or TH, if we didn't have a TABLE earlier
1085 const HTMLOptions
& rOptions
= static_cast<HTMLParser
*>(pInfo
->pParser
)->GetOptions();
1086 for (const auto & rOption
: rOptions
)
1088 switch( rOption
.GetToken() )
1090 case HtmlOptionId::WIDTH
:
1091 { // Percent: of document width or outer cell
1092 nTableWidth
= GetWidthPixel( rOption
);
1095 case HtmlOptionId::BORDER
:
1096 //BorderOn is: ((pOption->GetString().Len() == 0) || (pOption->GetNumber() != 0));
1103 nTable
= ++nMaxTable
;
1105 nFirstTableCell
= maList
.size();
1107 pLocalColOffset
= new ScHTMLColOffset
;
1108 MakeColNoRef( pLocalColOffset
, nColOffsetStart
, 0, 0, 0 );
1111 void ScHTMLLayoutParser::TableOff( const HtmlImportInfo
* pInfo
)
1114 CloseEntry( pInfo
);
1115 if ( nColCnt
> nColCntStart
)
1116 TableRowOff( pInfo
); // The optional TableRowOff wasn't
1119 OSL_FAIL( "dumbo doc! </TABLE> without opening <TABLE>" );
1122 if ( --nTableLevel
> 0 )
1123 { // Table in Table done
1124 if ( !aTableStack
.empty() )
1126 std::unique_ptr
<ScHTMLTableStackEntry
> pS
= std::move(aTableStack
.top());
1129 auto& pE
= pS
->xCellEntry
;
1130 SCROW nRows
= nRowCnt
- pS
->nRowCnt
;
1132 { // Insert size of table at this position
1133 SCROW nRow
= pS
->nRowCnt
;
1134 sal_uInt16 nTab
= pS
->nTable
;
1136 pTables
.reset( new OuterMap
);
1137 // Height of outer table
1138 OuterMap::const_iterator it
= pTables
->find( nTab
);
1140 if ( it
== pTables
->end() )
1142 pTab1
= new InnerMap
;
1143 (*pTables
)[ nTab
] = pTab1
;
1147 SCROW nRowSpan
= pE
->nRowOverlap
;
1149 SCROW nRowsPerRow1
; // Outer table
1150 SCROW nRowsPerRow2
; // Inner table
1152 { // LCM to which we can map the inner and outer rows
1153 nRowKGV
= std::lcm( nRowSpan
, nRows
);
1154 nRowsPerRow1
= nRowKGV
/ nRowSpan
;
1155 nRowsPerRow2
= nRowKGV
/ nRows
;
1159 nRowKGV
= nRowsPerRow1
= nRows
;
1162 InnerMap
* pTab2
= nullptr;
1163 if ( nRowsPerRow2
> 1 )
1164 { // Height of the inner table
1165 pTab2
= new InnerMap
;
1166 (*pTables
)[ nTable
] = pTab2
;
1168 // Abuse void* Data entry of the Table class for height mapping
1171 if ( nRowsPerRow1
> 1 )
1173 for ( SCROW j
=0; j
< nRowSpan
; j
++ )
1175 sal_uLong nRowKey
= nRow
+ j
;
1176 SCROW nR
= (*pTab1
)[ nRowKey
];
1178 (*pTab1
)[ nRowKey
] = nRowsPerRow1
;
1179 else if ( nRowsPerRow1
> nR
)
1180 (*pTab1
)[ nRowKey
] = nRowsPerRow1
;
1181 //TODO: How can we improve on this?
1182 else if ( nRowsPerRow1
< nR
&& nRowSpan
== 1
1183 && nTable
== nMaxTable
)
1184 { // Still some space left, merge in a better way (if possible)
1185 SCROW nAdd
= nRowsPerRow1
- (nR
% nRowsPerRow1
);
1187 if ( (nR
% nRows
) == 0 )
1188 { // Only if representable
1189 SCROW nR2
= (*pTab1
)[ nRowKey
+1 ];
1191 { // Only if we really have enough space
1192 (*pTab1
)[ nRowKey
] = nR
;
1193 (*pTab1
)[ nRowKey
+1 ] = nR2
- nAdd
;
1194 nRowsPerRow2
= nR
/ nRows
;
1200 if ( nRowsPerRow2
> 1 )
1203 { // nRowsPerRow2 could be've been incremented
1204 pTab2
= new InnerMap
;
1205 (*pTables
)[ nTable
] = pTab2
;
1207 for ( SCROW j
=0; j
< nRows
; j
++ )
1209 sal_uLong nRowKey
= nRow
+ j
;
1210 (*pTab2
)[ nRowKey
] = nRowsPerRow2
;
1219 pE
->nWidth
= nTableWidth
;
1220 else if ( pE
->nWidth
< nTableWidth
)
1222 sal_uInt16 nOldOffset
= pE
->nOffset
+ pE
->nWidth
;
1223 sal_uInt16 nNewOffset
= pE
->nOffset
+ nTableWidth
;
1224 ModifyOffset( pS
->pLocalColOffset
, nOldOffset
, nNewOffset
, nOffsetTolerance
);
1225 sal_uInt16 nTmp
= nNewOffset
- pE
->nOffset
- pE
->nWidth
;
1226 pE
->nWidth
= nNewOffset
- pE
->nOffset
;
1227 pS
->nTableWidth
= pS
->nTableWidth
+ nTmp
;
1228 if ( pS
->nColOffset
>= nOldOffset
)
1229 pS
->nColOffset
= pS
->nColOffset
+ nTmp
;
1232 nColCnt
= pE
->nCol
+ pE
->nColOverlap
;
1233 nRowCnt
= pS
->nRowCnt
;
1234 nColCntStart
= pS
->nColCntStart
;
1235 nMaxCol
= pS
->nMaxCol
;
1236 nTable
= pS
->nTable
;
1237 nTableWidth
= pS
->nTableWidth
;
1238 nFirstTableCell
= pS
->nFirstTableCell
;
1239 nColOffset
= pS
->nColOffset
;
1240 nColOffsetStart
= pS
->nColOffsetStart
;
1241 bFirstRow
= pS
->bFirstRow
;
1242 xLockedList
= pS
->xLockedList
;
1243 pLocalColOffset
= pS
->pLocalColOffset
;
1244 // mxActEntry is kept around if a table is started in the same row
1245 // (anything's possible in HTML); will be deleted by CloseEntry
1248 bTabInTabCell
= true;
1252 { // Simple table finished
1256 if ( !aTableStack
.empty() )
1258 ScHTMLTableStackEntry
* pS
= aTableStack
.top().get();
1259 delete pLocalColOffset
;
1260 pLocalColOffset
= pS
->pLocalColOffset
;
1266 void ScHTMLLayoutParser::Image( HtmlImportInfo
* pInfo
)
1268 mxActEntry
->maImageList
.push_back(std::make_unique
<ScHTMLImage
>());
1269 ScHTMLImage
* pImage
= mxActEntry
->maImageList
.back().get();
1270 const HTMLOptions
& rOptions
= static_cast<HTMLParser
*>(pInfo
->pParser
)->GetOptions();
1271 for (const auto & rOption
: rOptions
)
1273 switch( rOption
.GetToken() )
1275 case HtmlOptionId::SRC
:
1277 pImage
->aURL
= INetURLObject::GetAbsURL( aBaseURL
, rOption
.GetString() );
1280 case HtmlOptionId::ALT
:
1282 if (!mxActEntry
->bHasGraphic
)
1283 { // ALT text only if not any image loaded
1284 if (!mxActEntry
->aAltText
.isEmpty())
1285 mxActEntry
->aAltText
+= "; ";
1287 mxActEntry
->aAltText
+= rOption
.GetString();
1291 case HtmlOptionId::WIDTH
:
1293 pImage
->aSize
.setWidth( static_cast<tools::Long
>(rOption
.GetNumber()) );
1296 case HtmlOptionId::HEIGHT
:
1298 pImage
->aSize
.setHeight( static_cast<tools::Long
>(rOption
.GetNumber()) );
1301 case HtmlOptionId::HSPACE
:
1303 pImage
->aSpace
.setX( static_cast<tools::Long
>(rOption
.GetNumber()) );
1306 case HtmlOptionId::VSPACE
:
1308 pImage
->aSpace
.setY( static_cast<tools::Long
>(rOption
.GetNumber()) );
1314 if (pImage
->aURL
.isEmpty())
1316 OSL_FAIL( "Image: graphic without URL ?!?" );
1321 std::unique_ptr
<Graphic
> pGraphic(new Graphic
);
1322 GraphicFilter
& rFilter
= GraphicFilter::GetGraphicFilter();
1323 if ( ERRCODE_NONE
!= GraphicFilter::LoadGraphic( pImage
->aURL
, pImage
->aFilterName
,
1324 *pGraphic
, &rFilter
, &nFormat
) )
1326 return ; // Bad luck
1328 if (!mxActEntry
->bHasGraphic
)
1329 { // discard any ALT text in this cell if we have any image
1330 mxActEntry
->bHasGraphic
= true;
1331 mxActEntry
->aAltText
.clear();
1333 pImage
->aFilterName
= rFilter
.GetImportFormatName( nFormat
);
1334 pImage
->pGraphic
= std::move( pGraphic
);
1335 if ( !(pImage
->aSize
.Width() && pImage
->aSize
.Height()) )
1337 OutputDevice
* pDefaultDev
= Application::GetDefaultDevice();
1338 pImage
->aSize
= pDefaultDev
->LogicToPixel( pImage
->pGraphic
->GetPrefSize(),
1339 pImage
->pGraphic
->GetPrefMapMode() );
1341 if (mxActEntry
->maImageList
.empty())
1344 tools::Long nWidth
= 0;
1345 for (const std::unique_ptr
<ScHTMLImage
> & pI
: mxActEntry
->maImageList
)
1347 if ( pI
->nDir
& nHorizontal
)
1348 nWidth
+= pI
->aSize
.Width() + 2 * pI
->aSpace
.X();
1352 if ( mxActEntry
->nWidth
1353 && (nWidth
+ pImage
->aSize
.Width() + 2 * pImage
->aSpace
.X()
1354 >= mxActEntry
->nWidth
) )
1355 mxActEntry
->maImageList
.back()->nDir
= nVertical
;
1358 void ScHTMLLayoutParser::ColOn( HtmlImportInfo
* pInfo
)
1360 const HTMLOptions
& rOptions
= static_cast<HTMLParser
*>(pInfo
->pParser
)->GetOptions();
1361 for (const auto & rOption
: rOptions
)
1363 if( rOption
.GetToken() == HtmlOptionId::WIDTH
)
1365 sal_uInt16 nVal
= GetWidthPixel( rOption
);
1366 MakeCol( pLocalColOffset
, nColOffset
, nVal
, 0, 0 );
1367 nColOffset
= nColOffset
+ nVal
;
1372 sal_uInt16
ScHTMLLayoutParser::GetWidthPixel( const HTMLOption
& rOption
)
1374 const OUString
& rOptVal
= rOption
.GetString();
1375 if ( rOptVal
.indexOf('%') != -1 )
1377 sal_uInt16 nW
= (nTableWidth
? nTableWidth
: static_cast<sal_uInt16
>(aPageSize
.Width()));
1378 return static_cast<sal_uInt16
>((rOption
.GetNumber() * nW
) / 100);
1382 if ( rOptVal
.indexOf('*') != -1 )
1383 { // Relative to what?
1384 // TODO: Collect all relative values in ColArray and then MakeCol
1388 return static_cast<sal_uInt16
>(rOption
.GetNumber()); // Pixel
1392 void ScHTMLLayoutParser::AnchorOn( HtmlImportInfo
* pInfo
)
1394 const HTMLOptions
& rOptions
= static_cast<HTMLParser
*>(pInfo
->pParser
)->GetOptions();
1395 for (const auto & rOption
: rOptions
)
1397 if( rOption
.GetToken() == HtmlOptionId::NAME
)
1398 mxActEntry
->pName
= rOption
.GetString();
1402 bool ScHTMLLayoutParser::IsAtBeginningOfText( const HtmlImportInfo
* pInfo
)
1404 ESelection
& rSel
= mxActEntry
->aSel
;
1405 return rSel
.nStartPara
== rSel
.nEndPara
&&
1406 rSel
.nStartPara
<= pInfo
->aSelection
.nEndPara
&&
1407 pEdit
->GetTextLen( rSel
.nStartPara
) == 0;
1410 void ScHTMLLayoutParser::FontOn( HtmlImportInfo
* pInfo
)
1412 if ( !IsAtBeginningOfText( pInfo
) )
1415 // Only at the start of the text; applies to whole line
1416 const HTMLOptions
& rOptions
= static_cast<HTMLParser
*>(pInfo
->pParser
)->GetOptions();
1417 for (const auto & rOption
: rOptions
)
1419 switch( rOption
.GetToken() )
1421 case HtmlOptionId::FACE
:
1423 const OUString
& rFace
= rOption
.GetString();
1424 OUStringBuffer aFontName
;
1428 // Font list, VCL uses the semicolon as separator
1429 // HTML uses the comma
1430 OUString aFName
= rFace
.getToken( 0, ',', nPos
);
1431 aFName
= comphelper::string::strip(aFName
, ' ');
1432 if( !aFontName
.isEmpty() )
1433 aFontName
.append(";");
1434 aFontName
.append(aFName
);
1436 if ( !aFontName
.isEmpty() )
1437 mxActEntry
->aItemSet
.Put( SvxFontItem( FAMILY_DONTKNOW
,
1438 aFontName
.makeStringAndClear(), OUString(), PITCH_DONTKNOW
,
1439 RTL_TEXTENCODING_DONTKNOW
, ATTR_FONT
) );
1442 case HtmlOptionId::SIZE
:
1444 sal_uInt16 nSize
= static_cast<sal_uInt16
>(rOption
.GetNumber());
1447 else if ( nSize
> SC_HTML_FONTSIZES
)
1448 nSize
= SC_HTML_FONTSIZES
;
1449 mxActEntry
->aItemSet
.Put( SvxFontHeightItem(
1450 maFontHeights
[nSize
-1], 100, ATTR_FONT_HEIGHT
) );
1453 case HtmlOptionId::COLOR
:
1456 rOption
.GetColor( aColor
);
1457 mxActEntry
->aItemSet
.Put( SvxColorItem( aColor
, ATTR_FONT_COLOR
) );
1465 void ScHTMLLayoutParser::ProcToken( HtmlImportInfo
* pInfo
)
1467 switch ( pInfo
->nToken
)
1469 case HtmlTokenId::META
:
1471 HTMLParser
* pParser
= static_cast<HTMLParser
*>(pInfo
->pParser
);
1472 uno::Reference
<document::XDocumentPropertiesSupplier
> xDPS(
1473 mpDoc
->GetDocumentShell()->GetModel(), uno::UNO_QUERY_THROW
);
1474 pParser
->ParseMetaOptions(
1475 xDPS
->getDocumentProperties(),
1476 mpDoc
->GetDocumentShell()->GetHeaderAttributes() );
1479 case HtmlTokenId::TITLE_ON
:
1485 case HtmlTokenId::TITLE_OFF
:
1487 if ( bInTitle
&& !aString
.isEmpty() )
1489 // Remove blanks from line breaks
1490 aString
= aString
.trim();
1491 uno::Reference
<document::XDocumentPropertiesSupplier
> xDPS(
1492 mpDoc
->GetDocumentShell()->GetModel(),
1493 uno::UNO_QUERY_THROW
);
1494 xDPS
->getDocumentProperties()->setTitle(aString
);
1499 case HtmlTokenId::TABLE_ON
:
1504 case HtmlTokenId::COL_ON
:
1509 case HtmlTokenId::TABLEHEADER_ON
: // Opens row
1512 CloseEntry( pInfo
);
1513 // Do not set bInCell to true, TableDataOn does that
1514 mxActEntry
->aItemSet
.Put(
1515 SvxWeightItem( WEIGHT_BOLD
, ATTR_FONT_WEIGHT
) );
1518 case HtmlTokenId::TABLEDATA_ON
: // Opens cell
1520 TableDataOn( pInfo
);
1523 case HtmlTokenId::TABLEHEADER_OFF
:
1524 case HtmlTokenId::TABLEDATA_OFF
: // Closes cell
1526 TableDataOff( pInfo
);
1529 case HtmlTokenId::TABLEROW_ON
: // Before first cell in row
1531 TableRowOn( pInfo
);
1534 case HtmlTokenId::TABLEROW_OFF
: // After last cell in row
1536 TableRowOff( pInfo
);
1539 case HtmlTokenId::TABLE_OFF
:
1544 case HtmlTokenId::IMAGE
:
1549 case HtmlTokenId::PARABREAK_OFF
:
1550 { // We continue vertically after an image
1551 if (!mxActEntry
->maImageList
.empty())
1552 mxActEntry
->maImageList
.back()->nDir
= nVertical
;
1555 case HtmlTokenId::ANCHOR_ON
:
1560 case HtmlTokenId::FONT_ON
:
1565 case HtmlTokenId::BIGPRINT_ON
:
1567 // TODO: Remember current font size and increase by 1
1568 if ( IsAtBeginningOfText( pInfo
) )
1569 mxActEntry
->aItemSet
.Put( SvxFontHeightItem(
1570 maFontHeights
[3], 100, ATTR_FONT_HEIGHT
) );
1573 case HtmlTokenId::SMALLPRINT_ON
:
1575 // TODO: Remember current font size and decrease by 1
1576 if ( IsAtBeginningOfText( pInfo
) )
1577 mxActEntry
->aItemSet
.Put( SvxFontHeightItem(
1578 maFontHeights
[0], 100, ATTR_FONT_HEIGHT
) );
1581 case HtmlTokenId::BOLD_ON
:
1582 case HtmlTokenId::STRONG_ON
:
1584 if ( IsAtBeginningOfText( pInfo
) )
1585 mxActEntry
->aItemSet
.Put( SvxWeightItem( WEIGHT_BOLD
,
1586 ATTR_FONT_WEIGHT
) );
1589 case HtmlTokenId::ITALIC_ON
:
1590 case HtmlTokenId::EMPHASIS_ON
:
1591 case HtmlTokenId::ADDRESS_ON
:
1592 case HtmlTokenId::BLOCKQUOTE_ON
:
1593 case HtmlTokenId::BLOCKQUOTE30_ON
:
1594 case HtmlTokenId::CITATION_ON
:
1595 case HtmlTokenId::VARIABLE_ON
:
1597 if ( IsAtBeginningOfText( pInfo
) )
1598 mxActEntry
->aItemSet
.Put( SvxPostureItem( ITALIC_NORMAL
,
1599 ATTR_FONT_POSTURE
) );
1602 case HtmlTokenId::DEFINSTANCE_ON
:
1604 if ( IsAtBeginningOfText( pInfo
) )
1606 mxActEntry
->aItemSet
.Put( SvxWeightItem( WEIGHT_BOLD
,
1607 ATTR_FONT_WEIGHT
) );
1608 mxActEntry
->aItemSet
.Put( SvxPostureItem( ITALIC_NORMAL
,
1609 ATTR_FONT_POSTURE
) );
1613 case HtmlTokenId::UNDERLINE_ON
:
1615 if ( IsAtBeginningOfText( pInfo
) )
1616 mxActEntry
->aItemSet
.Put( SvxUnderlineItem( LINESTYLE_SINGLE
,
1617 ATTR_FONT_UNDERLINE
) );
1620 case HtmlTokenId::TEXTTOKEN
:
1623 aString
+= pInfo
->aText
;
1630 // HTML DATA QUERY PARSER
1632 template< typename Type
>
1633 static Type
getLimitedValue( const Type
& rValue
, const Type
& rMin
, const Type
& rMax
)
1634 { return std::clamp( rValue
, rMin
, rMax
); }
1636 ScHTMLEntry::ScHTMLEntry( const SfxItemSet
& rItemSet
, ScHTMLTableId nTableId
) :
1637 ScEEParseEntry( rItemSet
),
1638 mbImportAlways( false )
1641 bEntirePara
= false;
1644 bool ScHTMLEntry::HasContents() const
1646 return mbImportAlways
|| aSel
.HasRange() || !aAltText
.isEmpty() || IsTable();
1649 void ScHTMLEntry::AdjustStart( const HtmlImportInfo
& rInfo
)
1651 // set start position
1652 aSel
.nStartPara
= rInfo
.aSelection
.nStartPara
;
1653 aSel
.nStartPos
= rInfo
.aSelection
.nStartPos
;
1654 // adjust end position
1655 if( (aSel
.nEndPara
< aSel
.nStartPara
) || ((aSel
.nEndPara
== aSel
.nStartPara
) && (aSel
.nEndPos
< aSel
.nStartPos
)) )
1657 aSel
.nEndPara
= aSel
.nStartPara
;
1658 aSel
.nEndPos
= aSel
.nStartPos
;
1662 void ScHTMLEntry::AdjustEnd( const HtmlImportInfo
& rInfo
)
1664 OSL_ENSURE( (aSel
.nEndPara
< rInfo
.aSelection
.nEndPara
) ||
1665 ((aSel
.nEndPara
== rInfo
.aSelection
.nEndPara
) && (aSel
.nEndPos
<= rInfo
.aSelection
.nEndPos
)),
1666 "ScHTMLQueryParser::AdjustEntryEnd - invalid end position" );
1668 aSel
.nEndPara
= rInfo
.aSelection
.nEndPara
;
1669 aSel
.nEndPos
= rInfo
.aSelection
.nEndPos
;
1672 void ScHTMLEntry::Strip( const EditEngine
& rEditEngine
)
1674 // strip leading empty paragraphs
1675 while( (aSel
.nStartPara
< aSel
.nEndPara
) && (rEditEngine
.GetTextLen( aSel
.nStartPara
) <= aSel
.nStartPos
) )
1680 // strip trailing empty paragraphs
1681 while( (aSel
.nStartPara
< aSel
.nEndPara
) && (aSel
.nEndPos
== 0) )
1684 aSel
.nEndPos
= rEditEngine
.GetTextLen( aSel
.nEndPara
);
1688 /** A map of ScHTMLTable objects.
1690 Organizes the tables with a unique table key. Stores nested tables inside
1691 the parent table and forms in this way a tree structure of tables. An
1692 instance of this class owns the contained table objects and deletes them
1695 class ScHTMLTableMap final
1698 typedef std::shared_ptr
< ScHTMLTable
> ScHTMLTablePtr
;
1699 typedef std::map
< ScHTMLTableId
, ScHTMLTablePtr
> ScHTMLTableStdMap
;
1702 typedef ScHTMLTableStdMap::iterator iterator
;
1703 typedef ScHTMLTableStdMap::const_iterator const_iterator
;
1706 ScHTMLTable
& mrParentTable
; /// Reference to parent table.
1707 ScHTMLTableStdMap maTables
; /// Container for all table objects.
1708 mutable ScHTMLTable
* mpCurrTable
; /// Current table, used for fast search.
1711 explicit ScHTMLTableMap( ScHTMLTable
& rParentTable
);
1713 const_iterator
begin() const { return maTables
.begin(); }
1714 const_iterator
end() const { return maTables
.end(); }
1716 /** Returns the specified table.
1717 @param nTableId Unique identifier of the table.
1718 @param bDeep true = searches deep in all nested table; false = only in this container. */
1719 ScHTMLTable
* FindTable( ScHTMLTableId nTableId
, bool bDeep
= true ) const;
1721 /** Inserts a new table into the container. This container owns the created table.
1722 @param bPreFormText true = New table is based on preformatted text (<pre> tag). */
1723 ScHTMLTable
* CreateTable( const HtmlImportInfo
& rInfo
, bool bPreFormText
, const ScDocument
& rDoc
);
1726 /** Sets a working table with its index for search optimization. */
1727 void SetCurrTable( ScHTMLTable
* pTable
) const
1728 { if( pTable
) mpCurrTable
= pTable
; }
1731 ScHTMLTableMap::ScHTMLTableMap( ScHTMLTable
& rParentTable
) :
1732 mrParentTable(rParentTable
),
1733 mpCurrTable(nullptr)
1737 ScHTMLTable
* ScHTMLTableMap::FindTable( ScHTMLTableId nTableId
, bool bDeep
) const
1739 ScHTMLTable
* pResult
= nullptr;
1740 if( mpCurrTable
&& (nTableId
== mpCurrTable
->GetTableId()) )
1741 pResult
= mpCurrTable
; // cached table
1744 const_iterator aFind
= maTables
.find( nTableId
);
1745 if( aFind
!= maTables
.end() )
1746 pResult
= aFind
->second
.get(); // table from this container
1749 // not found -> search deep in nested tables
1750 if( !pResult
&& bDeep
)
1751 for( const_iterator aIter
= begin(), aEnd
= end(); !pResult
&& (aIter
!= aEnd
); ++aIter
)
1752 pResult
= aIter
->second
->FindNestedTable( nTableId
);
1754 SetCurrTable( pResult
);
1758 ScHTMLTable
* ScHTMLTableMap::CreateTable( const HtmlImportInfo
& rInfo
, bool bPreFormText
, const ScDocument
& rDoc
)
1760 ScHTMLTable
* pTable
= new ScHTMLTable( mrParentTable
, rInfo
, bPreFormText
, rDoc
);
1761 maTables
[ pTable
->GetTableId() ].reset( pTable
);
1762 SetCurrTable( pTable
);
1768 /** Simplified forward iterator for convenience.
1770 Before the iterator can be dereferenced, it must be tested with the is()
1771 method. The iterator may be invalid directly after construction (e.g. empty
1774 class ScHTMLTableIterator
1777 /** Constructs the iterator for the passed table map.
1778 @param pTableMap Pointer to the table map (is allowed to be NULL). */
1779 explicit ScHTMLTableIterator( const ScHTMLTableMap
* pTableMap
);
1781 bool is() const { return mpTableMap
&& maIter
!= maEnd
; }
1782 ScHTMLTable
* operator->() { return maIter
->second
.get(); }
1783 ScHTMLTableIterator
& operator++() { ++maIter
; return *this; }
1786 ScHTMLTableMap::const_iterator maIter
;
1787 ScHTMLTableMap::const_iterator maEnd
;
1788 const ScHTMLTableMap
* mpTableMap
;
1793 ScHTMLTableIterator::ScHTMLTableIterator( const ScHTMLTableMap
* pTableMap
) :
1794 mpTableMap(pTableMap
)
1798 maIter
= pTableMap
->begin();
1799 maEnd
= pTableMap
->end();
1803 ScHTMLTableAutoId::ScHTMLTableAutoId( ScHTMLTableId
& rnUnusedId
) :
1804 mnTableId( rnUnusedId
),
1805 mrnUnusedId( rnUnusedId
)
1810 ScHTMLTable::ScHTMLTable( ScHTMLTable
& rParentTable
, const HtmlImportInfo
& rInfo
, bool bPreFormText
, const ScDocument
& rDoc
) :
1811 mpParentTable( &rParentTable
),
1812 maTableId( rParentTable
.maTableId
.mrnUnusedId
),
1813 maTableItemSet( rParentTable
.GetCurrItemSet() ),
1814 mrEditEngine( rParentTable
.mrEditEngine
),
1815 mrEEParseList( rParentTable
.mrEEParseList
),
1816 mpCurrEntryVector( nullptr ),
1818 mpParser(rParentTable
.mpParser
),
1820 mbBorderOn( false ),
1821 mbPreFormText( bPreFormText
),
1824 mbPushEmptyLine( false ),
1825 mbCaptionOn ( false )
1830 ImplDataOn( ScHTMLSize( 1, 1 ) );
1834 ProcessFormatOptions( maTableItemSet
, rInfo
);
1835 const HTMLOptions
& rOptions
= static_cast<HTMLParser
*>(rInfo
.pParser
)->GetOptions();
1836 for (const auto& rOption
: rOptions
)
1838 switch( rOption
.GetToken() )
1840 case HtmlOptionId::BORDER
:
1841 mbBorderOn
= rOption
.GetString().isEmpty() || (rOption
.GetNumber() != 0);
1843 case HtmlOptionId::ID
:
1844 maTableName
= rOption
.GetString();
1851 CreateNewEntry( rInfo
);
1854 ScHTMLTable::ScHTMLTable(
1856 EditEngine
& rEditEngine
,
1857 std::vector
<std::shared_ptr
<ScEEParseEntry
>>& rEEParseList
,
1858 ScHTMLTableId
& rnUnusedId
, ScHTMLParser
* pParser
, const ScDocument
& rDoc
1860 mpParentTable( nullptr ),
1861 maTableId( rnUnusedId
),
1862 maTableItemSet( rPool
),
1863 mrEditEngine( rEditEngine
),
1864 mrEEParseList( rEEParseList
),
1865 mpCurrEntryVector( nullptr ),
1869 mbBorderOn( false ),
1870 mbPreFormText( false ),
1873 mbPushEmptyLine( false ),
1874 mbCaptionOn ( false )
1876 // open the first "cell" of the document
1878 ImplDataOn( ScHTMLSize( 1, 1 ) );
1879 mxCurrEntry
= CreateEntry();
1882 ScHTMLTable::~ScHTMLTable()
1886 const SfxItemSet
& ScHTMLTable::GetCurrItemSet() const
1888 // first try cell item set, then row item set, then table item set
1889 return moDataItemSet
? *moDataItemSet
: (moRowItemSet
? *moRowItemSet
: maTableItemSet
);
1892 ScHTMLSize
ScHTMLTable::GetSpan( const ScHTMLPos
& rCellPos
) const
1894 ScHTMLSize
aSpan( 1, 1 );
1895 const ScRange
* pRange
= maVMergedCells
.Find( rCellPos
.MakeAddr() );
1897 pRange
= maHMergedCells
.Find( rCellPos
.MakeAddr() );
1899 aSpan
.Set( pRange
->aEnd
.Col() - pRange
->aStart
.Col() + 1, pRange
->aEnd
.Row() - pRange
->aStart
.Row() + 1 );
1903 ScHTMLTable
* ScHTMLTable::FindNestedTable( ScHTMLTableId nTableId
) const
1905 return mxNestedTables
? mxNestedTables
->FindTable( nTableId
) : nullptr;
1908 void ScHTMLTable::PutItem( const SfxPoolItem
& rItem
)
1910 OSL_ENSURE( mxCurrEntry
, "ScHTMLTable::PutItem - no current entry" );
1911 if( mxCurrEntry
&& mxCurrEntry
->IsEmpty() )
1912 mxCurrEntry
->GetItemSet().Put( rItem
);
1915 void ScHTMLTable::PutText( const HtmlImportInfo
& rInfo
)
1917 OSL_ENSURE( mxCurrEntry
, "ScHTMLTable::PutText - no current entry" );
1920 if( !mxCurrEntry
->HasContents() && IsSpaceCharInfo( rInfo
) )
1921 mxCurrEntry
->AdjustStart( rInfo
);
1923 mxCurrEntry
->AdjustEnd( rInfo
);
1925 maCaptionBuffer
.append(rInfo
.aText
);
1930 void ScHTMLTable::InsertPara( const HtmlImportInfo
& rInfo
)
1932 if( mxCurrEntry
&& mbDataOn
&& !IsEmptyCell() )
1933 mxCurrEntry
->SetImportAlways();
1935 CreateNewEntry( rInfo
);
1936 InsertLeadingEmptyLine();
1939 void ScHTMLTable::BreakOn()
1941 // empty line, if <br> is at start of cell
1942 mbPushEmptyLine
= !mbPreFormText
&& mbDataOn
&& IsEmptyCell();
1945 void ScHTMLTable::HeadingOn()
1947 // call directly, InsertPara() has not been called before
1948 InsertLeadingEmptyLine();
1951 void ScHTMLTable::InsertLeadingEmptyLine()
1953 // empty line, if <p>, </p>, <h?>, or </h*> are not at start of cell
1954 mbPushEmptyLine
= !mbPreFormText
&& mbDataOn
&& !IsEmptyCell();
1957 void ScHTMLTable::AnchorOn()
1959 OSL_ENSURE( mxCurrEntry
, "ScHTMLTable::AnchorOn - no current entry" );
1960 // don't skip entries with single hyperlinks
1962 mxCurrEntry
->SetImportAlways();
1965 ScHTMLTable
* ScHTMLTable::TableOn( const HtmlImportInfo
& rInfo
)
1968 return InsertNestedTable( rInfo
, false );
1971 ScHTMLTable
* ScHTMLTable::TableOff( const HtmlImportInfo
& rInfo
)
1973 return mbPreFormText
? this : CloseTable( rInfo
);
1976 void ScHTMLTable::CaptionOn()
1979 maCaptionBuffer
.setLength(0);
1982 void ScHTMLTable::CaptionOff()
1986 maCaption
= maCaptionBuffer
.makeStringAndClear().trim();
1987 mbCaptionOn
= false;
1990 ScHTMLTable
* ScHTMLTable::PreOn( const HtmlImportInfo
& rInfo
)
1993 return InsertNestedTable( rInfo
, true );
1996 ScHTMLTable
* ScHTMLTable::PreOff( const HtmlImportInfo
& rInfo
)
1998 return mbPreFormText
? CloseTable( rInfo
) : this;
2001 void ScHTMLTable::RowOn( const HtmlImportInfo
& rInfo
)
2003 PushEntry( rInfo
, true );
2004 if( mpParentTable
&& !mbPreFormText
) // no rows allowed in global and preformatted tables
2007 ProcessFormatOptions( *moRowItemSet
, rInfo
);
2009 CreateNewEntry( rInfo
);
2012 void ScHTMLTable::RowOff( const HtmlImportInfo
& rInfo
)
2014 PushEntry( rInfo
, true );
2015 if( mpParentTable
&& !mbPreFormText
) // no rows allowed in global and preformatted tables
2017 CreateNewEntry( rInfo
);
2023 * Decode a number format string stored in Excel-generated HTML's CSS
2026 OUString
decodeNumberFormat(const OUString
& rFmt
)
2028 OUStringBuffer aBuf
;
2029 const sal_Unicode
* p
= rFmt
.getStr();
2030 sal_Int32 n
= rFmt
.getLength();
2031 for (sal_Int32 i
= 0; i
< n
; ++i
, ++p
)
2039 // Parse all subsequent digits until first non-digit is found.
2040 sal_Int32 nDigitCount
= 0;
2041 const sal_Unicode
* p1
= p
;
2042 for (; i
< n
; ++i
, ++p
, ++nDigitCount
)
2044 if (*p
< '0' || '9' < *p
)
2054 // Hex-encoded character found. Decode it back into its
2055 // original character. An example of number format with
2056 // hex-encoded chars: "\0022$\0022\#\,\#\#0\.00"
2057 sal_uInt32 nVal
= OUString(p1
, nDigitCount
).toUInt32(16);
2058 aBuf
.append(static_cast<sal_Unicode
>(nVal
));
2064 return aBuf
.makeStringAndClear();
2069 void ScHTMLTable::DataOn( const HtmlImportInfo
& rInfo
)
2071 PushEntry( rInfo
, true );
2072 if( mpParentTable
&& !mbPreFormText
) // no cells allowed in global and preformatted tables
2074 // read needed options from the <td> tag
2075 ScHTMLSize
aSpanSize( 1, 1 );
2076 std::optional
<OUString
> pValStr
, pNumStr
;
2077 const HTMLOptions
& rOptions
= static_cast<HTMLParser
*>(rInfo
.pParser
)->GetOptions();
2078 sal_uInt32 nNumberFormat
= NUMBERFORMAT_ENTRY_NOT_FOUND
;
2079 for (const auto& rOption
: rOptions
)
2081 switch (rOption
.GetToken())
2083 case HtmlOptionId::COLSPAN
:
2084 aSpanSize
.mnCols
= static_cast<SCCOL
>( getLimitedValue
<sal_Int32
>( rOption
.GetString().toInt32(), 1, 256 ) );
2086 case HtmlOptionId::ROWSPAN
:
2087 aSpanSize
.mnRows
= static_cast<SCROW
>( getLimitedValue
<sal_Int32
>( rOption
.GetString().toInt32(), 1, 256 ) );
2089 case HtmlOptionId::SDVAL
:
2090 pValStr
= rOption
.GetString();
2092 case HtmlOptionId::SDNUM
:
2093 pNumStr
= rOption
.GetString();
2095 case HtmlOptionId::CLASS
:
2097 // Pick up the number format associated with this class (if
2099 OUString aClass
= rOption
.GetString();
2100 const ScHTMLStyles
& rStyles
= mpParser
->GetStyles();
2101 const OUString
& rVal
= rStyles
.getPropertyValue("td", aClass
, "mso-number-format");
2102 if (!rVal
.isEmpty())
2104 OUString aNumFmt
= decodeNumberFormat(rVal
);
2106 nNumberFormat
= GetFormatTable()->GetEntryKey(aNumFmt
);
2107 if (nNumberFormat
== NUMBERFORMAT_ENTRY_NOT_FOUND
)
2109 sal_Int32 nErrPos
= 0;
2110 SvNumFormatType nDummy
;
2111 bool bValidFmt
= GetFormatTable()->PutEntry(aNumFmt
, nErrPos
, nDummy
, nNumberFormat
);
2113 nNumberFormat
= NUMBERFORMAT_ENTRY_NOT_FOUND
;
2122 ImplDataOn( aSpanSize
);
2124 if (nNumberFormat
!= NUMBERFORMAT_ENTRY_NOT_FOUND
)
2125 moDataItemSet
->Put( SfxUInt32Item(ATTR_VALUE_FORMAT
, nNumberFormat
) );
2127 ProcessFormatOptions( *moDataItemSet
, rInfo
);
2128 CreateNewEntry( rInfo
);
2129 mxCurrEntry
->pValStr
= std::move(pValStr
);
2130 mxCurrEntry
->pNumStr
= std::move(pNumStr
);
2133 CreateNewEntry( rInfo
);
2136 void ScHTMLTable::DataOff( const HtmlImportInfo
& rInfo
)
2138 PushEntry( rInfo
, true );
2139 if( mpParentTable
&& !mbPreFormText
) // no cells allowed in global and preformatted tables
2141 CreateNewEntry( rInfo
);
2144 void ScHTMLTable::BodyOn( const HtmlImportInfo
& rInfo
)
2146 bool bPushed
= PushEntry( rInfo
);
2147 if( !mpParentTable
)
2149 // do not start new row, if nothing (no title) precedes the body.
2150 if( bPushed
|| !mbRowOn
)
2152 if( bPushed
|| !mbDataOn
)
2153 ImplDataOn( ScHTMLSize( 1, 1 ) );
2154 ProcessFormatOptions( *moDataItemSet
, rInfo
);
2156 CreateNewEntry( rInfo
);
2159 void ScHTMLTable::BodyOff( const HtmlImportInfo
& rInfo
)
2162 if( !mpParentTable
)
2167 CreateNewEntry( rInfo
);
2170 ScHTMLTable
* ScHTMLTable::CloseTable( const HtmlImportInfo
& rInfo
)
2172 if( mpParentTable
) // not allowed to close global table
2174 PushEntry( rInfo
, mbDataOn
);
2177 mpParentTable
->PushTableEntry( GetTableId() );
2178 mpParentTable
->CreateNewEntry( rInfo
);
2179 if( mbPreFormText
) // enclose preformatted table with empty lines in parent table
2180 mpParentTable
->InsertLeadingEmptyLine();
2181 return mpParentTable
;
2186 SCCOLROW
ScHTMLTable::GetDocSize( ScHTMLOrient eOrient
, SCCOLROW nCellPos
) const
2188 const ScSizeVec
& rSizes
= maCumSizes
[ eOrient
];
2189 size_t nIndex
= static_cast< size_t >( nCellPos
);
2190 if( nIndex
>= rSizes
.size() ) return 0;
2191 return (nIndex
== 0) ? rSizes
.front() : (rSizes
[ nIndex
] - rSizes
[ nIndex
- 1 ]);
2194 SCCOLROW
ScHTMLTable::GetDocSize( ScHTMLOrient eOrient
, SCCOLROW nCellBegin
, SCCOLROW nCellEnd
) const
2196 const ScSizeVec
& rSizes
= maCumSizes
[ eOrient
];
2197 size_t nBeginIdx
= static_cast< size_t >( std::max
< SCCOLROW
>( nCellBegin
, 0 ) );
2198 size_t nEndIdx
= static_cast< size_t >( std::min
< SCCOLROW
>( nCellEnd
, static_cast< SCCOLROW
>( rSizes
.size() ) ) );
2199 if (nBeginIdx
>= nEndIdx
) return 0;
2200 return rSizes
[ nEndIdx
- 1 ] - ((nBeginIdx
== 0) ? 0 : rSizes
[ nBeginIdx
- 1 ]);
2203 SCCOLROW
ScHTMLTable::GetDocSize( ScHTMLOrient eOrient
) const
2205 const ScSizeVec
& rSizes
= maCumSizes
[ eOrient
];
2206 return rSizes
.empty() ? 0 : rSizes
.back();
2209 ScHTMLSize
ScHTMLTable::GetDocSize( const ScHTMLPos
& rCellPos
) const
2211 ScHTMLSize aCellSpan
= GetSpan( rCellPos
);
2213 static_cast< SCCOL
>( GetDocSize( tdCol
, rCellPos
.mnCol
, rCellPos
.mnCol
+ aCellSpan
.mnCols
) ),
2214 static_cast< SCROW
>( GetDocSize( tdRow
, rCellPos
.mnRow
, rCellPos
.mnRow
+ aCellSpan
.mnRows
) ) );
2217 SCCOLROW
ScHTMLTable::GetDocPos( ScHTMLOrient eOrient
, SCCOLROW nCellPos
) const
2219 return maDocBasePos
.Get( eOrient
) + GetDocSize( eOrient
, 0, nCellPos
);
2222 ScHTMLPos
ScHTMLTable::GetDocPos( const ScHTMLPos
& rCellPos
) const
2225 static_cast< SCCOL
>( GetDocPos( tdCol
, rCellPos
.mnCol
) ),
2226 static_cast< SCROW
>( GetDocPos( tdRow
, rCellPos
.mnRow
) ) );
2229 void ScHTMLTable::GetDocRange( ScRange
& rRange
) const
2231 rRange
.aStart
= rRange
.aEnd
= maDocBasePos
.MakeAddr();
2232 ScAddress
aErrorPos( ScAddress::UNINITIALIZED
);
2233 if (!rRange
.aEnd
.Move( static_cast< SCCOL
>( GetDocSize( tdCol
) ) - 1,
2234 static_cast< SCROW
>( GetDocSize( tdRow
) ) - 1, 0, aErrorPos
, mrDoc
))
2236 assert(!"can't move");
2240 void ScHTMLTable::ApplyCellBorders( ScDocument
* pDoc
, const ScAddress
& rFirstPos
) const
2242 OSL_ENSURE( pDoc
, "ScHTMLTable::ApplyCellBorders - no document" );
2243 if( pDoc
&& mbBorderOn
)
2245 const SCCOL nLastCol
= maSize
.mnCols
- 1;
2246 const SCROW nLastRow
= maSize
.mnRows
- 1;
2247 const tools::Long nOuterLine
= SvxBorderLineWidth::Medium
;
2248 const tools::Long nInnerLine
= SvxBorderLineWidth::Hairline
;
2249 SvxBorderLine
aOuterLine(nullptr, nOuterLine
, SvxBorderLineStyle::SOLID
);
2250 SvxBorderLine
aInnerLine(nullptr, nInnerLine
, SvxBorderLineStyle::SOLID
);
2251 SvxBoxItem
aBorderItem( ATTR_BORDER
);
2253 for( SCCOL nCol
= 0; nCol
<= nLastCol
; ++nCol
)
2255 SvxBorderLine
* pLeftLine
= (nCol
== 0) ? &aOuterLine
: &aInnerLine
;
2256 SvxBorderLine
* pRightLine
= (nCol
== nLastCol
) ? &aOuterLine
: &aInnerLine
;
2257 SCCOL nCellCol1
= static_cast< SCCOL
>( GetDocPos( tdCol
, nCol
) ) + rFirstPos
.Col();
2258 SCCOL nCellCol2
= nCellCol1
+ static_cast< SCCOL
>( GetDocSize( tdCol
, nCol
) ) - 1;
2259 for( SCROW nRow
= 0; nRow
<= nLastRow
; ++nRow
)
2261 SvxBorderLine
* pTopLine
= (nRow
== 0) ? &aOuterLine
: &aInnerLine
;
2262 SvxBorderLine
* pBottomLine
= (nRow
== nLastRow
) ? &aOuterLine
: &aInnerLine
;
2263 SCROW nCellRow1
= GetDocPos( tdRow
, nRow
) + rFirstPos
.Row();
2264 SCROW nCellRow2
= nCellRow1
+ GetDocSize( tdRow
, nRow
) - 1;
2265 for( SCCOL nCellCol
= nCellCol1
; nCellCol
<= nCellCol2
; ++nCellCol
)
2267 aBorderItem
.SetLine( (nCellCol
== nCellCol1
) ? pLeftLine
: nullptr, SvxBoxItemLine::LEFT
);
2268 aBorderItem
.SetLine( (nCellCol
== nCellCol2
) ? pRightLine
: nullptr, SvxBoxItemLine::RIGHT
);
2269 for( SCROW nCellRow
= nCellRow1
; nCellRow
<= nCellRow2
; ++nCellRow
)
2271 aBorderItem
.SetLine( (nCellRow
== nCellRow1
) ? pTopLine
: nullptr, SvxBoxItemLine::TOP
);
2272 aBorderItem
.SetLine( (nCellRow
== nCellRow2
) ? pBottomLine
: nullptr, SvxBoxItemLine::BOTTOM
);
2273 pDoc
->ApplyAttr( nCellCol
, nCellRow
, rFirstPos
.Tab(), aBorderItem
);
2280 for( ScHTMLTableIterator
aIter( mxNestedTables
.get() ); aIter
.is(); ++aIter
)
2281 aIter
->ApplyCellBorders( pDoc
, rFirstPos
);
2284 SvNumberFormatter
* ScHTMLTable::GetFormatTable()
2286 return mpParser
->GetDoc().GetFormatTable();
2289 bool ScHTMLTable::IsEmptyCell() const
2291 return mpCurrEntryVector
&& mpCurrEntryVector
->empty();
2294 bool ScHTMLTable::IsSpaceCharInfo( const HtmlImportInfo
& rInfo
)
2296 return (rInfo
.nToken
== HtmlTokenId::TEXTTOKEN
) && (rInfo
.aText
.getLength() == 1) && (rInfo
.aText
[ 0 ] == ' ');
2299 ScHTMLTable::ScHTMLEntryPtr
ScHTMLTable::CreateEntry() const
2301 return std::make_unique
<ScHTMLEntry
>( GetCurrItemSet() );
2304 void ScHTMLTable::CreateNewEntry( const HtmlImportInfo
& rInfo
)
2306 OSL_ENSURE( !mxCurrEntry
, "ScHTMLTable::CreateNewEntry - old entry still present" );
2307 mxCurrEntry
= CreateEntry();
2308 mxCurrEntry
->aSel
= rInfo
.aSelection
;
2311 void ScHTMLTable::ImplPushEntryToVector( ScHTMLEntryVector
& rEntryVector
, ScHTMLEntryPtr
& rxEntry
)
2313 // HTML entry list does not own the entries
2314 rEntryVector
.push_back( rxEntry
.get() );
2315 // mrEEParseList (reference to member of ScEEParser) owns the entries
2316 mrEEParseList
.push_back(std::shared_ptr
<ScEEParseEntry
>(rxEntry
.release()));
2319 bool ScHTMLTable::PushEntry( ScHTMLEntryPtr
& rxEntry
)
2321 bool bPushed
= false;
2322 if( rxEntry
&& rxEntry
->HasContents() )
2324 if( mpCurrEntryVector
)
2326 if( mbPushEmptyLine
)
2328 ScHTMLEntryPtr xEmptyEntry
= CreateEntry();
2329 ImplPushEntryToVector( *mpCurrEntryVector
, xEmptyEntry
);
2330 mbPushEmptyLine
= false;
2332 ImplPushEntryToVector( *mpCurrEntryVector
, rxEntry
);
2335 else if( mpParentTable
)
2337 bPushed
= mpParentTable
->PushEntry( rxEntry
);
2341 OSL_FAIL( "ScHTMLTable::PushEntry - cannot push entry, no parent found" );
2347 bool ScHTMLTable::PushEntry( const HtmlImportInfo
& rInfo
, bool bLastInCell
)
2349 OSL_ENSURE( mxCurrEntry
, "ScHTMLTable::PushEntry - no current entry" );
2350 bool bPushed
= false;
2353 mxCurrEntry
->AdjustEnd( rInfo
);
2354 mxCurrEntry
->Strip( mrEditEngine
);
2356 // import entry always, if it is the last in cell, and cell is still empty
2357 if( bLastInCell
&& IsEmptyCell() )
2359 mxCurrEntry
->SetImportAlways();
2360 // don't insert empty lines before single empty entries
2361 if( mxCurrEntry
->IsEmpty() )
2362 mbPushEmptyLine
= false;
2365 bPushed
= PushEntry( mxCurrEntry
);
2366 mxCurrEntry
.reset();
2371 void ScHTMLTable::PushTableEntry( ScHTMLTableId nTableId
)
2373 OSL_ENSURE( nTableId
!= SC_HTML_GLOBAL_TABLE
, "ScHTMLTable::PushTableEntry - cannot push global table" );
2374 if( nTableId
!= SC_HTML_GLOBAL_TABLE
)
2376 ScHTMLEntryPtr
xEntry( new ScHTMLEntry( maTableItemSet
, nTableId
) );
2377 PushEntry( xEntry
);
2381 ScHTMLTable
* ScHTMLTable::GetExistingTable( ScHTMLTableId nTableId
) const
2383 ScHTMLTable
* pTable
= ((nTableId
!= SC_HTML_GLOBAL_TABLE
) && mxNestedTables
) ?
2384 mxNestedTables
->FindTable( nTableId
, false ) : nullptr;
2385 OSL_ENSURE( pTable
|| (nTableId
== SC_HTML_GLOBAL_TABLE
), "ScHTMLTable::GetExistingTable - table not found" );
2389 ScHTMLTable
* ScHTMLTable::InsertNestedTable( const HtmlImportInfo
& rInfo
, bool bPreFormText
)
2391 if( !mxNestedTables
)
2392 mxNestedTables
.reset( new ScHTMLTableMap( *this ) );
2393 if( bPreFormText
) // enclose new preformatted table with empty lines
2394 InsertLeadingEmptyLine();
2395 return mxNestedTables
->CreateTable( rInfo
, bPreFormText
, mrDoc
);
2398 void ScHTMLTable::InsertNewCell( const ScHTMLSize
& rSpanSize
)
2402 /* Find an unused cell by skipping all merged ranges that cover the
2403 current cell position stored in maCurrCell. */
2406 pRange
= maVMergedCells
.Find( maCurrCell
.MakeAddr() );
2408 pRange
= maHMergedCells
.Find( maCurrCell
.MakeAddr() );
2411 maCurrCell
.mnCol
= pRange
->aEnd
.Col() + 1;
2413 mpCurrEntryVector
= &maEntryMap
[ maCurrCell
];
2415 /* If the new cell is merged horizontally, try to find collisions with
2416 other vertically merged ranges. In this case, shrink existing
2417 vertically merged ranges (do not shrink the new cell). */
2418 SCCOL nColEnd
= maCurrCell
.mnCol
+ rSpanSize
.mnCols
;
2419 for( ScAddress
aAddr( maCurrCell
.MakeAddr() ); aAddr
.Col() < nColEnd
; aAddr
.IncCol() )
2420 if( (pRange
= maVMergedCells
.Find( aAddr
)) != nullptr )
2421 pRange
->aEnd
.SetRow( maCurrCell
.mnRow
- 1 );
2423 // insert the new range into the cell lists
2424 ScRange
aNewRange( maCurrCell
.MakeAddr() );
2425 ScAddress
aErrorPos( ScAddress::UNINITIALIZED
);
2426 if (!aNewRange
.aEnd
.Move( rSpanSize
.mnCols
- 1, rSpanSize
.mnRows
- 1, 0, aErrorPos
, mrDoc
))
2428 assert(!"can't move");
2430 if( rSpanSize
.mnRows
> 1 )
2432 maVMergedCells
.push_back( aNewRange
);
2433 /* Do not insert vertically merged ranges into maUsedCells yet,
2434 because they may be shrunken (see above). The final vertically
2435 merged ranges are inserted in FillEmptyCells(). */
2439 if( rSpanSize
.mnCols
> 1 )
2440 maHMergedCells
.push_back( aNewRange
);
2441 /* Insert horizontally merged ranges and single cells into
2442 maUsedCells, they will not be changed anymore. */
2443 maUsedCells
.Join( aNewRange
);
2446 // adjust table size
2447 maSize
.mnCols
= std::max
< SCCOL
>( maSize
.mnCols
, aNewRange
.aEnd
.Col() + 1 );
2448 maSize
.mnRows
= std::max
< SCROW
>( maSize
.mnRows
, aNewRange
.aEnd
.Row() + 1 );
2451 void ScHTMLTable::ImplRowOn()
2455 moRowItemSet
.emplace( maTableItemSet
);
2456 maCurrCell
.mnCol
= 0;
2461 void ScHTMLTable::ImplRowOff()
2467 moRowItemSet
.reset();
2469 mbRowOn
= mbDataOn
= false;
2473 void ScHTMLTable::ImplDataOn( const ScHTMLSize
& rSpanSize
)
2479 moDataItemSet
.emplace( *moRowItemSet
);
2480 InsertNewCell( rSpanSize
);
2482 mbPushEmptyLine
= false;
2485 void ScHTMLTable::ImplDataOff()
2489 moDataItemSet
.reset();
2491 mpCurrEntryVector
= nullptr;
2496 void ScHTMLTable::ProcessFormatOptions( SfxItemSet
& rItemSet
, const HtmlImportInfo
& rInfo
)
2498 // special handling for table header cells
2499 if( rInfo
.nToken
== HtmlTokenId::TABLEHEADER_ON
)
2501 rItemSet
.Put( SvxWeightItem( WEIGHT_BOLD
, ATTR_FONT_WEIGHT
) );
2502 rItemSet
.Put( SvxHorJustifyItem( SvxCellHorJustify::Center
, ATTR_HOR_JUSTIFY
) );
2505 const HTMLOptions
& rOptions
= static_cast<HTMLParser
*>(rInfo
.pParser
)->GetOptions();
2506 for (const auto& rOption
: rOptions
)
2508 switch( rOption
.GetToken() )
2510 case HtmlOptionId::ALIGN
:
2512 SvxCellHorJustify eVal
= SvxCellHorJustify::Standard
;
2513 const OUString
& rOptVal
= rOption
.GetString();
2514 if( rOptVal
.equalsIgnoreAsciiCase( OOO_STRING_SVTOOLS_HTML_AL_right
) )
2515 eVal
= SvxCellHorJustify::Right
;
2516 else if( rOptVal
.equalsIgnoreAsciiCase( OOO_STRING_SVTOOLS_HTML_AL_center
) )
2517 eVal
= SvxCellHorJustify::Center
;
2518 else if( rOptVal
.equalsIgnoreAsciiCase( OOO_STRING_SVTOOLS_HTML_AL_left
) )
2519 eVal
= SvxCellHorJustify::Left
;
2520 if( eVal
!= SvxCellHorJustify::Standard
)
2521 rItemSet
.Put( SvxHorJustifyItem( eVal
, ATTR_HOR_JUSTIFY
) );
2525 case HtmlOptionId::VALIGN
:
2527 SvxCellVerJustify eVal
= SvxCellVerJustify::Standard
;
2528 const OUString
& rOptVal
= rOption
.GetString();
2529 if( rOptVal
.equalsIgnoreAsciiCase( OOO_STRING_SVTOOLS_HTML_VA_top
) )
2530 eVal
= SvxCellVerJustify::Top
;
2531 else if( rOptVal
.equalsIgnoreAsciiCase( OOO_STRING_SVTOOLS_HTML_VA_middle
) )
2532 eVal
= SvxCellVerJustify::Center
;
2533 else if( rOptVal
.equalsIgnoreAsciiCase( OOO_STRING_SVTOOLS_HTML_VA_bottom
) )
2534 eVal
= SvxCellVerJustify::Bottom
;
2535 if( eVal
!= SvxCellVerJustify::Standard
)
2536 rItemSet
.Put( SvxVerJustifyItem( eVal
, ATTR_VER_JUSTIFY
) );
2540 case HtmlOptionId::BGCOLOR
:
2543 rOption
.GetColor( aColor
);
2544 rItemSet
.Put( SvxBrushItem( aColor
, ATTR_BACKGROUND
) );
2552 void ScHTMLTable::SetDocSize( ScHTMLOrient eOrient
, SCCOLROW nCellPos
, SCCOLROW nSize
)
2554 OSL_ENSURE( nCellPos
>= 0, "ScHTMLTable::SetDocSize - unexpected negative position" );
2555 ScSizeVec
& rSizes
= maCumSizes
[ eOrient
];
2556 size_t nIndex
= static_cast< size_t >( nCellPos
);
2557 // expand with height/width == 1
2558 while( nIndex
>= rSizes
.size() )
2559 rSizes
.push_back( rSizes
.empty() ? 1 : (rSizes
.back() + 1) );
2560 // update size of passed position and all following
2561 // #i109987# only grow, don't shrink - use the largest needed size
2562 SCCOLROW nDiff
= nSize
- ((nIndex
== 0) ? rSizes
.front() : (rSizes
[ nIndex
] - rSizes
[ nIndex
- 1 ]));
2564 std::for_each(rSizes
.begin() + nIndex
, rSizes
.end(), [&nDiff
](SCCOLROW
& rSize
) { rSize
+= nDiff
; });
2567 void ScHTMLTable::CalcNeededDocSize(
2568 ScHTMLOrient eOrient
, SCCOLROW nCellPos
, SCCOLROW nCellSpan
, SCCOLROW nRealDocSize
)
2570 SCCOLROW nDiffSize
= 0;
2571 // in merged columns/rows: reduce needed size by size of leading columns
2572 while( nCellSpan
> 1 )
2574 nDiffSize
+= GetDocSize( eOrient
, nCellPos
);
2578 // set remaining needed size to last column/row
2579 nRealDocSize
-= std::min
< SCCOLROW
>( nRealDocSize
- 1, nDiffSize
);
2580 SetDocSize( eOrient
, nCellPos
, nRealDocSize
);
2583 void ScHTMLTable::FillEmptyCells()
2585 for( ScHTMLTableIterator
aIter( mxNestedTables
.get() ); aIter
.is(); ++aIter
)
2586 aIter
->FillEmptyCells();
2588 // insert the final vertically merged ranges into maUsedCells
2589 for ( size_t i
= 0, nRanges
= maVMergedCells
.size(); i
< nRanges
; ++i
)
2591 ScRange
& rRange
= maVMergedCells
[ i
];
2592 maUsedCells
.Join( rRange
);
2595 for( ScAddress aAddr
; aAddr
.Row() < maSize
.mnRows
; aAddr
.IncRow() )
2597 for( aAddr
.SetCol( 0 ); aAddr
.Col() < maSize
.mnCols
; aAddr
.IncCol() )
2599 if( !maUsedCells
.Find( aAddr
) )
2601 // create a range for the lock list (used to calc. cell span)
2602 ScRange
aRange( aAddr
);
2605 aRange
.aEnd
.IncCol();
2607 while( (aRange
.aEnd
.Col() < maSize
.mnCols
) && !maUsedCells
.Find( aRange
.aEnd
) );
2608 aRange
.aEnd
.IncCol( -1 );
2609 maUsedCells
.Join( aRange
);
2611 // insert a dummy entry
2612 ScHTMLEntryPtr xEntry
= CreateEntry();
2613 ImplPushEntryToVector( maEntryMap
[ ScHTMLPos( aAddr
) ], xEntry
);
2619 void ScHTMLTable::RecalcDocSize()
2621 // recalc table sizes recursively from inner to outer
2622 for( ScHTMLTableIterator
aIter( mxNestedTables
.get() ); aIter
.is(); ++aIter
)
2623 aIter
->RecalcDocSize();
2625 /* Two passes: first calculates the sizes of single columns/rows, then
2626 the sizes of spanned columns/rows. This allows to fill nested tables
2627 into merged cells optimally. */
2628 static const sal_uInt16 PASS_SINGLE
= 0;
2629 static const sal_uInt16 PASS_SPANNED
= 1;
2630 for( sal_uInt16 nPass
= PASS_SINGLE
; nPass
<= PASS_SPANNED
; ++nPass
)
2632 // iterate through every table cell
2633 for( const auto& [rCellPos
, rEntryVector
] : maEntryMap
)
2635 ScHTMLSize aCellSpan
= GetSpan( rCellPos
);
2637 // process the dimension of the current cell in this pass?
2638 // (pass is single and span is 1) or (pass is not single and span is not 1)
2639 bool bProcessColWidth
= ((nPass
== PASS_SINGLE
) == (aCellSpan
.mnCols
== 1));
2640 bool bProcessRowHeight
= ((nPass
== PASS_SINGLE
) == (aCellSpan
.mnRows
== 1));
2641 if( bProcessColWidth
|| bProcessRowHeight
)
2643 ScHTMLSize
aDocSize( 1, 0 ); // resulting size of the cell in document
2645 // expand the cell size for each cell parse entry
2646 for( const auto& rpEntry
: rEntryVector
)
2648 ScHTMLTable
* pTable
= GetExistingTable( rpEntry
->GetTableId() );
2649 // find entry with maximum width
2650 if( bProcessColWidth
&& pTable
)
2651 aDocSize
.mnCols
= std::max( aDocSize
.mnCols
, static_cast< SCCOL
>( pTable
->GetDocSize( tdCol
) ) );
2652 // add up height of each entry
2653 if( bProcessRowHeight
)
2654 aDocSize
.mnRows
+= pTable
? pTable
->GetDocSize( tdRow
) : 1;
2656 if( !aDocSize
.mnRows
)
2657 aDocSize
.mnRows
= 1;
2659 if( bProcessColWidth
)
2660 CalcNeededDocSize( tdCol
, rCellPos
.mnCol
, aCellSpan
.mnCols
, aDocSize
.mnCols
);
2661 if( bProcessRowHeight
)
2662 CalcNeededDocSize( tdRow
, rCellPos
.mnRow
, aCellSpan
.mnRows
, aDocSize
.mnRows
);
2668 void ScHTMLTable::RecalcDocPos( const ScHTMLPos
& rBasePos
)
2670 maDocBasePos
= rBasePos
;
2671 // after the previous assignment it is allowed to call GetDocPos() methods
2673 // iterate through every table cell
2674 for( auto& [rCellPos
, rEntryVector
] : maEntryMap
)
2676 // fixed doc position of the entire cell (first entry)
2677 const ScHTMLPos
aCellDocPos( GetDocPos( rCellPos
) );
2678 // fixed doc size of the entire cell
2679 const ScHTMLSize
aCellDocSize( GetDocSize( rCellPos
) );
2681 // running doc position for single entries
2682 ScHTMLPos
aEntryDocPos( aCellDocPos
);
2684 ScHTMLEntry
* pEntry
= nullptr;
2685 for( const auto& rpEntry
: rEntryVector
)
2688 if( ScHTMLTable
* pTable
= GetExistingTable( pEntry
->GetTableId() ) )
2690 pTable
->RecalcDocPos( aEntryDocPos
); // recalc nested table
2691 pEntry
->nCol
= SCCOL_MAX
;
2692 pEntry
->nRow
= SCROW_MAX
;
2693 SCROW nTableRows
= static_cast< SCROW
>( pTable
->GetDocSize( tdRow
) );
2695 // use this entry to pad empty space right of table
2696 if( mpParentTable
) // ... but not in global table
2698 SCCOL nStartCol
= aEntryDocPos
.mnCol
+ static_cast< SCCOL
>( pTable
->GetDocSize( tdCol
) );
2699 SCCOL nNextCol
= aEntryDocPos
.mnCol
+ aCellDocSize
.mnCols
;
2700 if( nStartCol
< nNextCol
)
2702 pEntry
->nCol
= nStartCol
;
2703 pEntry
->nRow
= aEntryDocPos
.mnRow
;
2704 pEntry
->nColOverlap
= nNextCol
- nStartCol
;
2705 pEntry
->nRowOverlap
= nTableRows
;
2708 aEntryDocPos
.mnRow
+= nTableRows
;
2712 pEntry
->nCol
= aEntryDocPos
.mnCol
;
2713 pEntry
->nRow
= aEntryDocPos
.mnRow
;
2714 if( mpParentTable
) // do not merge in global table
2715 pEntry
->nColOverlap
= aCellDocSize
.mnCols
;
2716 ++aEntryDocPos
.mnRow
;
2720 // pEntry points now to last entry.
2723 if( (pEntry
== rEntryVector
.front()) && (pEntry
->GetTableId() == SC_HTML_NO_TABLE
) )
2725 // pEntry is the only entry in this cell - merge rows of cell with single non-table entry.
2726 pEntry
->nRowOverlap
= aCellDocSize
.mnRows
;
2730 // fill up incomplete entry lists
2731 SCROW nFirstUnusedRow
= aCellDocPos
.mnRow
+ aCellDocSize
.mnRows
;
2732 while( aEntryDocPos
.mnRow
< nFirstUnusedRow
)
2734 ScHTMLEntryPtr
xDummyEntry( new ScHTMLEntry( pEntry
->GetItemSet() ) );
2735 xDummyEntry
->nCol
= aEntryDocPos
.mnCol
;
2736 xDummyEntry
->nRow
= aEntryDocPos
.mnRow
;
2737 xDummyEntry
->nColOverlap
= aCellDocSize
.mnCols
;
2738 ImplPushEntryToVector( rEntryVector
, xDummyEntry
);
2739 ++aEntryDocPos
.mnRow
;
2746 ScHTMLGlobalTable::ScHTMLGlobalTable(
2748 EditEngine
& rEditEngine
,
2749 std::vector
<std::shared_ptr
<ScEEParseEntry
>>& rEEParseVector
,
2750 ScHTMLTableId
& rnUnusedId
,
2751 ScHTMLParser
* pParser
,
2752 const ScDocument
& rDoc
2754 ScHTMLTable( rPool
, rEditEngine
, rEEParseVector
, rnUnusedId
, pParser
, rDoc
)
2758 ScHTMLGlobalTable::~ScHTMLGlobalTable()
2762 void ScHTMLGlobalTable::Recalc()
2764 // Fills up empty cells with a dummy entry. */
2766 // recalc table sizes of all nested tables and this table
2768 // recalc document positions of all entries in this table and in nested tables
2769 RecalcDocPos( GetDocPos() );
2772 ScHTMLQueryParser::ScHTMLQueryParser( EditEngine
* pEditEngine
, ScDocument
* pDoc
) :
2773 ScHTMLParser( pEditEngine
, pDoc
),
2774 mnUnusedId( SC_HTML_GLOBAL_TABLE
),
2778 new ScHTMLGlobalTable(*pPool
, *pEdit
, maList
, mnUnusedId
, this, *pDoc
));
2779 mpCurrTable
= mxGlobTable
.get();
2782 ScHTMLQueryParser::~ScHTMLQueryParser()
2786 ErrCode
ScHTMLQueryParser::Read( SvStream
& rStrm
, const OUString
& rBaseURL
)
2788 SvKeyValueIteratorRef xValues
;
2789 SvKeyValueIterator
* pAttributes
= nullptr;
2791 SfxObjectShell
* pObjSh
= mpDoc
->GetDocumentShell();
2792 if( pObjSh
&& pObjSh
->IsLoading() )
2794 pAttributes
= pObjSh
->GetHeaderAttributes();
2798 /* When not loading, set up fake HTTP headers to force the SfxHTMLParser
2799 to use UTF8 (used when pasting from clipboard) */
2800 const char* pCharSet
= rtl_getBestMimeCharsetFromTextEncoding( RTL_TEXTENCODING_UTF8
);
2803 OUString aContentType
= "text/html; charset=" +
2804 OUString::createFromAscii( pCharSet
);
2806 xValues
= new SvKeyValueIterator
;
2807 xValues
->Append( SvKeyValue( OOO_STRING_SVTOOLS_HTML_META_content_type
, aContentType
) );
2808 pAttributes
= xValues
.get();
2812 Link
<HtmlImportInfo
&,void> aOldLink
= pEdit
->GetHtmlImportHdl();
2813 pEdit
->SetHtmlImportHdl( LINK( this, ScHTMLQueryParser
, HTMLImportHdl
) );
2814 ErrCode nErr
= pEdit
->Read( rStrm
, rBaseURL
, EETextFormat::Html
, pAttributes
);
2815 pEdit
->SetHtmlImportHdl( aOldLink
);
2817 mxGlobTable
->Recalc();
2818 nColMax
= static_cast< SCCOL
>( mxGlobTable
->GetDocSize( tdCol
) - 1 );
2819 nRowMax
= static_cast< SCROW
>( mxGlobTable
->GetDocSize( tdRow
) - 1 );
2824 const ScHTMLTable
* ScHTMLQueryParser::GetGlobalTable() const
2826 return mxGlobTable
.get();
2829 void ScHTMLQueryParser::ProcessToken( const HtmlImportInfo
& rInfo
)
2831 switch( rInfo
.nToken
)
2833 // --- meta data ---
2834 case HtmlTokenId::META
: MetaOn( rInfo
); break; // <meta>
2836 // --- title handling ---
2837 case HtmlTokenId::TITLE_ON
: TitleOn(); break; // <title>
2838 case HtmlTokenId::TITLE_OFF
: TitleOff( rInfo
); break; // </title>
2840 case HtmlTokenId::STYLE_ON
: break;
2841 case HtmlTokenId::STYLE_OFF
: ParseStyle(rInfo
.aText
); break;
2843 // --- body handling ---
2844 case HtmlTokenId::BODY_ON
: mpCurrTable
->BodyOn( rInfo
); break; // <body>
2845 case HtmlTokenId::BODY_OFF
: mpCurrTable
->BodyOff( rInfo
); break; // </body>
2847 // --- insert text ---
2848 case HtmlTokenId::TEXTTOKEN
: InsertText( rInfo
); break; // any text
2849 case HtmlTokenId::LINEBREAK
: mpCurrTable
->BreakOn(); break; // <br>
2850 case HtmlTokenId::HEAD1_ON
: // <h1>
2851 case HtmlTokenId::HEAD2_ON
: // <h2>
2852 case HtmlTokenId::HEAD3_ON
: // <h3>
2853 case HtmlTokenId::HEAD4_ON
: // <h4>
2854 case HtmlTokenId::HEAD5_ON
: // <h5>
2855 case HtmlTokenId::HEAD6_ON
: // <h6>
2856 case HtmlTokenId::PARABREAK_ON
: mpCurrTable
->HeadingOn(); break; // <p>
2858 // --- misc. contents ---
2859 case HtmlTokenId::ANCHOR_ON
: mpCurrTable
->AnchorOn(); break; // <a>
2861 // --- table handling ---
2862 case HtmlTokenId::TABLE_ON
: TableOn( rInfo
); break; // <table>
2863 case HtmlTokenId::TABLE_OFF
: TableOff( rInfo
); break; // </table>
2864 case HtmlTokenId::CAPTION_ON
: mpCurrTable
->CaptionOn(); break; // <caption>
2865 case HtmlTokenId::CAPTION_OFF
: mpCurrTable
->CaptionOff(); break; // </caption>
2866 case HtmlTokenId::TABLEROW_ON
: mpCurrTable
->RowOn( rInfo
); break; // <tr>
2867 case HtmlTokenId::TABLEROW_OFF
: mpCurrTable
->RowOff( rInfo
); break; // </tr>
2868 case HtmlTokenId::TABLEHEADER_ON
: // <th>
2869 case HtmlTokenId::TABLEDATA_ON
: mpCurrTable
->DataOn( rInfo
); break; // <td>
2870 case HtmlTokenId::TABLEHEADER_OFF
: // </th>
2871 case HtmlTokenId::TABLEDATA_OFF
: mpCurrTable
->DataOff( rInfo
); break; // </td>
2872 case HtmlTokenId::PREFORMTXT_ON
: PreOn( rInfo
); break; // <pre>
2873 case HtmlTokenId::PREFORMTXT_OFF
: PreOff( rInfo
); break; // </pre>
2875 // --- formatting ---
2876 case HtmlTokenId::FONT_ON
: FontOn( rInfo
); break; // <font>
2878 case HtmlTokenId::BIGPRINT_ON
: // <big>
2879 //! TODO: store current font size, use following size
2880 mpCurrTable
->PutItem( SvxFontHeightItem( maFontHeights
[ 3 ], 100, ATTR_FONT_HEIGHT
) );
2882 case HtmlTokenId::SMALLPRINT_ON
: // <small>
2883 //! TODO: store current font size, use preceding size
2884 mpCurrTable
->PutItem( SvxFontHeightItem( maFontHeights
[ 0 ], 100, ATTR_FONT_HEIGHT
) );
2887 case HtmlTokenId::BOLD_ON
: // <b>
2888 case HtmlTokenId::STRONG_ON
: // <strong>
2889 mpCurrTable
->PutItem( SvxWeightItem( WEIGHT_BOLD
, ATTR_FONT_WEIGHT
) );
2892 case HtmlTokenId::ITALIC_ON
: // <i>
2893 case HtmlTokenId::EMPHASIS_ON
: // <em>
2894 case HtmlTokenId::ADDRESS_ON
: // <address>
2895 case HtmlTokenId::BLOCKQUOTE_ON
: // <blockquote>
2896 case HtmlTokenId::BLOCKQUOTE30_ON
: // <bq>
2897 case HtmlTokenId::CITATION_ON
: // <cite>
2898 case HtmlTokenId::VARIABLE_ON
: // <var>
2899 mpCurrTable
->PutItem( SvxPostureItem( ITALIC_NORMAL
, ATTR_FONT_POSTURE
) );
2902 case HtmlTokenId::DEFINSTANCE_ON
: // <dfn>
2903 mpCurrTable
->PutItem( SvxWeightItem( WEIGHT_BOLD
, ATTR_FONT_WEIGHT
) );
2904 mpCurrTable
->PutItem( SvxPostureItem( ITALIC_NORMAL
, ATTR_FONT_POSTURE
) );
2907 case HtmlTokenId::UNDERLINE_ON
: // <u>
2908 mpCurrTable
->PutItem( SvxUnderlineItem( LINESTYLE_SINGLE
, ATTR_FONT_UNDERLINE
) );
2914 void ScHTMLQueryParser::InsertText( const HtmlImportInfo
& rInfo
)
2916 mpCurrTable
->PutText( rInfo
);
2918 maTitle
.append(rInfo
.aText
);
2921 void ScHTMLQueryParser::FontOn( const HtmlImportInfo
& rInfo
)
2923 const HTMLOptions
& rOptions
= static_cast<HTMLParser
*>(rInfo
.pParser
)->GetOptions();
2924 for (const auto& rOption
: rOptions
)
2926 switch( rOption
.GetToken() )
2928 case HtmlOptionId::FACE
:
2930 const OUString
& rFace
= rOption
.GetString();
2935 // font list separator: VCL = ';' HTML = ','
2936 OUString aFName
= comphelper::string::strip(rFace
.getToken(0, ',', nPos
), ' ');
2937 aFontName
= ScGlobal::addToken(aFontName
, aFName
, ';');
2939 if ( !aFontName
.isEmpty() )
2940 mpCurrTable
->PutItem( SvxFontItem( FAMILY_DONTKNOW
,
2941 aFontName
, OUString(), PITCH_DONTKNOW
,
2942 RTL_TEXTENCODING_DONTKNOW
, ATTR_FONT
) );
2945 case HtmlOptionId::SIZE
:
2947 sal_uInt32 nSize
= getLimitedValue
< sal_uInt32
>( rOption
.GetNumber(), 1, SC_HTML_FONTSIZES
);
2948 mpCurrTable
->PutItem( SvxFontHeightItem( maFontHeights
[ nSize
- 1 ], 100, ATTR_FONT_HEIGHT
) );
2951 case HtmlOptionId::COLOR
:
2954 rOption
.GetColor( aColor
);
2955 mpCurrTable
->PutItem( SvxColorItem( aColor
, ATTR_FONT_COLOR
) );
2963 void ScHTMLQueryParser::MetaOn( const HtmlImportInfo
& rInfo
)
2965 if( mpDoc
->GetDocumentShell() )
2967 HTMLParser
* pParser
= static_cast< HTMLParser
* >( rInfo
.pParser
);
2969 uno::Reference
<document::XDocumentPropertiesSupplier
> xDPS(
2970 mpDoc
->GetDocumentShell()->GetModel(), uno::UNO_QUERY_THROW
);
2971 pParser
->ParseMetaOptions(
2972 xDPS
->getDocumentProperties(),
2973 mpDoc
->GetDocumentShell()->GetHeaderAttributes() );
2977 void ScHTMLQueryParser::TitleOn()
2980 maTitle
.setLength(0);
2983 void ScHTMLQueryParser::TitleOff( const HtmlImportInfo
& rInfo
)
2988 OUString aTitle
= maTitle
.makeStringAndClear().trim();
2989 if (!aTitle
.isEmpty() && mpDoc
->GetDocumentShell())
2991 uno::Reference
<document::XDocumentPropertiesSupplier
> xDPS(
2992 mpDoc
->GetDocumentShell()->GetModel(), uno::UNO_QUERY_THROW
);
2994 xDPS
->getDocumentProperties()->setTitle(aTitle
);
2996 InsertText( rInfo
);
3000 void ScHTMLQueryParser::TableOn( const HtmlImportInfo
& rInfo
)
3002 mpCurrTable
= mpCurrTable
->TableOn( rInfo
);
3005 void ScHTMLQueryParser::TableOff( const HtmlImportInfo
& rInfo
)
3007 mpCurrTable
= mpCurrTable
->TableOff( rInfo
);
3010 void ScHTMLQueryParser::PreOn( const HtmlImportInfo
& rInfo
)
3012 mpCurrTable
= mpCurrTable
->PreOn( rInfo
);
3015 void ScHTMLQueryParser::PreOff( const HtmlImportInfo
& rInfo
)
3017 mpCurrTable
= mpCurrTable
->PreOff( rInfo
);
3020 void ScHTMLQueryParser::CloseTable( const HtmlImportInfo
& rInfo
)
3022 mpCurrTable
= mpCurrTable
->CloseTable( rInfo
);
3028 * Handler class for the CSS parser.
3030 class CSSHandler
: public orcus::css_handler
3037 MemStr() : mp(nullptr), mn(0) {}
3038 MemStr(const char* p
, size_t n
) : mp(p
), mn(n
) {}
3039 MemStr(const MemStr
& r
) : mp(r
.mp
), mn(r
.mn
) {}
3040 MemStr
& operator=(const MemStr
& r
) = default;
3043 typedef std::pair
<MemStr
, MemStr
> SelectorName
; // element : class
3044 typedef std::vector
<SelectorName
> SelectorNames
;
3046 SelectorNames maSelectorNames
; // current selector names
3047 MemStr maPropName
; // current property name.
3048 MemStr maPropValue
; // current property value.
3049 ScHTMLStyles
& mrStyles
;
3052 explicit CSSHandler(ScHTMLStyles
& rStyles
):
3058 // selector name starting with "@"
3059 static void at_rule_name(const char* /*p*/, size_t /*n*/)
3061 // TODO: For now, we ignore at-rule properties
3064 // selector name not starting with "." or "#" (i.e. element selectors)
3065 void simple_selector_type(const char* pElem
, size_t nElem
)
3067 MemStr
aElem(pElem
, nElem
); // element given
3068 MemStr
aClass(nullptr, 0); // class name not given - to be added in the "element global" storage
3069 SelectorName
aName(aElem
, aClass
);
3071 maSelectorNames
.push_back(aName
);
3074 // selector names starting with a "." (i.e. class selector)
3075 void simple_selector_class(const char* pClassName
, size_t nClassName
)
3077 MemStr
aElem(nullptr, 0); // no element given - should be added in the "global" storage
3078 MemStr
aClass(pClassName
, nClassName
);
3079 SelectorName
aName(aElem
, aClass
);
3081 maSelectorNames
.push_back(aName
);
3084 // TODO: Add other selectors
3086 void property_name(const char* p
, size_t n
)
3088 maPropName
= MemStr(p
, n
);
3091 void value(const char* p
, size_t n
)
3093 maPropValue
= MemStr(p
, n
);
3097 maSelectorNames
.clear();
3102 SelectorNames::const_iterator itr
= maSelectorNames
.begin(), itrEnd
= maSelectorNames
.end();
3103 for (; itr
!= itrEnd
; ++itr
)
3105 // Add this property to the collection for each selector.
3106 const SelectorName
& rSelName
= *itr
;
3107 const MemStr
& rElem
= rSelName
.first
;
3108 const MemStr
& rClass
= rSelName
.second
;
3109 OUString
aName(maPropName
.mp
, maPropName
.mn
, RTL_TEXTENCODING_UTF8
);
3110 OUString
aValue(maPropValue
.mp
, maPropValue
.mn
, RTL_TEXTENCODING_UTF8
);
3111 mrStyles
.add(rElem
.mp
, rElem
.mn
, rClass
.mp
, rClass
.mn
, aName
, aValue
);
3113 maPropName
= MemStr();
3114 maPropValue
= MemStr();
3121 void ScHTMLQueryParser::ParseStyle(std::u16string_view rStrm
)
3123 OString aStr
= OUStringToOString(rStrm
, RTL_TEXTENCODING_UTF8
);
3124 CSSHandler
aHdl(GetStyles());
3125 orcus::css_parser
<CSSHandler
> aParser(aStr
.getStr(), aStr
.getLength(), aHdl
);
3130 catch (const orcus::css::parse_error
& rOrcusParseError
)
3132 SAL_WARN("sc", "ScHTMLQueryParser::ParseStyle: " << rOrcusParseError
.what());
3133 // TODO: Parsing of CSS failed. Do nothing for now.
3137 IMPL_LINK( ScHTMLQueryParser
, HTMLImportHdl
, HtmlImportInfo
&, rInfo
, void )
3139 switch( rInfo
.eState
)
3141 case HtmlImportState::Start
:
3144 case HtmlImportState::NextToken
:
3145 ProcessToken( rInfo
);
3148 case HtmlImportState::InsertPara
:
3149 mpCurrTable
->InsertPara( rInfo
);
3152 case HtmlImportState::SetAttr
:
3153 case HtmlImportState::InsertText
:
3154 case HtmlImportState::InsertField
:
3157 case HtmlImportState::End
:
3158 while( mpCurrTable
->GetTableId() != SC_HTML_GLOBAL_TABLE
)
3159 CloseTable( rInfo
);
3163 OSL_FAIL( "ScHTMLQueryParser::HTMLImportHdl - unknown ImportInfo::eState" );
3167 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */