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 .
20 #include <com/sun/star/text/VertOrientation.hpp>
21 #include <hintids.hxx>
22 #include <svtools/htmltokn.h>
23 #include <svtools/htmlkywd.hxx>
24 #include <svl/urihelper.hxx>
25 #include <editeng/brushitem.hxx>
26 #include <editeng/lrspitem.hxx>
27 #include <vcl/svapp.hxx>
28 #include <sal/log.hxx>
29 #include <osl/diagnose.h>
30 #include <numrule.hxx>
33 #include <poolfmt.hxx>
37 #include "htmlnum.hxx"
44 HTMLOptionEnum
<sal_UCS4
> const aHTMLULTypeTable
[] =
46 { OOO_STRING_SVTOOLS_HTML_ULTYPE_disc
, HTML_BULLETCHAR_DISC
},
47 { OOO_STRING_SVTOOLS_HTML_ULTYPE_circle
, HTML_BULLETCHAR_CIRCLE
},
48 { OOO_STRING_SVTOOLS_HTML_ULTYPE_square
, HTML_BULLETCHAR_SQUARE
},
53 void SwHTMLParser::NewNumberBulletList( HtmlTokenId nToken
)
55 SwHTMLNumRuleInfo
& rInfo
= GetNumInfo();
57 // Create a new paragraph
58 bool bSpace
= (rInfo
.GetDepth() + m_nDefListDeep
) == 0;
59 if( m_pPam
->GetPoint()->GetContentIndex() )
60 AppendTextNode( bSpace
? AM_SPACE
: AM_NOSPACE
, false );
64 // Increment the numbering depth
66 sal_uInt8 nLevel
= static_cast<sal_uInt8
>( (rInfo
.GetDepth() <= MAXLEVEL
? rInfo
.GetDepth()
69 // Create rules if needed
70 if( !rInfo
.GetNumRule() )
72 sal_uInt16 nPos
= m_xDoc
->MakeNumRule( m_xDoc
->GetUniqueNumRuleName() );
73 rInfo
.SetNumRule( m_xDoc
->GetNumRuleTable()[nPos
] );
76 // Change the format for this level if that hasn't happened yet for this level
77 bool bNewNumFormat
= rInfo
.GetNumRule()->GetNumFormat( nLevel
) == nullptr;
78 bool bChangeNumFormat
= false;
80 // Create the default numbering format
81 SwNumFormat
aNumFormat( rInfo
.GetNumRule()->Get(nLevel
) );
82 rInfo
.SetNodeStartValue( nLevel
);
85 sal_uInt16 nChrFormatPoolId
= 0;
86 if( HtmlTokenId::ORDERLIST_ON
== nToken
)
88 aNumFormat
.SetNumberingType(SVX_NUM_ARABIC
);
89 nChrFormatPoolId
= RES_POOLCHR_NUM_LEVEL
;
93 // We'll set a default style because the UI does the same. This meant a 9pt font, which
94 // was not the case in Netscape. That didn't bother anyone so far
95 // #i63395# - Only apply user defined default bullet font
96 if ( numfunc::IsDefBulletFontUserDefined() )
98 aNumFormat
.SetBulletFont( &numfunc::GetDefBulletFont() );
100 aNumFormat
.SetNumberingType(SVX_NUM_CHAR_SPECIAL
);
101 aNumFormat
.SetBulletChar( cBulletChar
);
102 nChrFormatPoolId
= RES_POOLCHR_BULLET_LEVEL
;
105 sal_Int32 nAbsLSpace
= HTML_NUMBER_BULLET_MARGINLEFT
;
107 sal_Int32 nFirstLineIndent
= HTML_NUMBER_BULLET_INDENT
;
110 const SwNumFormat
& rPrevNumFormat
= rInfo
.GetNumRule()->Get( nLevel
-1 );
111 nAbsLSpace
= nAbsLSpace
+ rPrevNumFormat
.GetAbsLSpace();
112 nFirstLineIndent
= rPrevNumFormat
.GetFirstLineOffset();
114 aNumFormat
.SetAbsLSpace( nAbsLSpace
);
115 aNumFormat
.SetFirstLineOffset( nFirstLineIndent
);
116 aNumFormat
.SetCharFormat( m_pCSS1Parser
->GetCharFormatFromPool(nChrFormatPoolId
) );
118 bChangeNumFormat
= true;
120 else if( 1 != aNumFormat
.GetStart() )
122 // If the layer has already been used, the start value may need to be set hard to the paragraph.
123 rInfo
.SetNodeStartValue( nLevel
, 1 );
126 // and set that in the options
127 OUString aId
, aStyle
, aClass
, aLang
, aDir
;
129 sal_Int16 eVertOri
= text::VertOrientation::NONE
;
130 sal_uInt16 nWidth
=USHRT_MAX
, nHeight
=USHRT_MAX
;
131 const HTMLOptions
& rHTMLOptions
= GetOptions();
132 for (size_t i
= rHTMLOptions
.size(); i
; )
134 const HTMLOption
& rOption
= rHTMLOptions
[--i
];
135 switch( rOption
.GetToken() )
137 case HtmlOptionId::ID
:
138 aId
= rOption
.GetString();
140 case HtmlOptionId::TYPE
:
141 if( bNewNumFormat
&& !rOption
.GetString().isEmpty() )
145 case HtmlTokenId::ORDERLIST_ON
:
146 bChangeNumFormat
= true;
147 switch( rOption
.GetString()[0] )
149 case 'A': aNumFormat
.SetNumberingType(SVX_NUM_CHARS_UPPER_LETTER
); break;
150 case 'a': aNumFormat
.SetNumberingType(SVX_NUM_CHARS_LOWER_LETTER
); break;
151 case 'I': aNumFormat
.SetNumberingType(SVX_NUM_ROMAN_UPPER
); break;
152 case 'i': aNumFormat
.SetNumberingType(SVX_NUM_ROMAN_LOWER
); break;
153 default: bChangeNumFormat
= false;
157 case HtmlTokenId::UNORDERLIST_ON
:
158 aNumFormat
.SetBulletChar( rOption
.GetEnum(
159 aHTMLULTypeTable
,aNumFormat
.GetBulletChar() ) );
160 bChangeNumFormat
= true;
166 case HtmlOptionId::START
:
168 sal_uInt16 nStart
= o3tl::narrowing
<sal_uInt16
>(rOption
.GetNumber());
171 aNumFormat
.SetStart( nStart
);
172 bChangeNumFormat
= true;
176 rInfo
.SetNodeStartValue( nLevel
, nStart
);
180 case HtmlOptionId::STYLE
:
181 aStyle
= rOption
.GetString();
183 case HtmlOptionId::CLASS
:
184 aClass
= rOption
.GetString();
186 case HtmlOptionId::LANG
:
187 aLang
= rOption
.GetString();
189 case HtmlOptionId::DIR:
190 aDir
= rOption
.GetString();
192 case HtmlOptionId::SRC
:
195 aBulletSrc
= rOption
.GetString();
196 if( !InternalImgToPrivateURL(aBulletSrc
) )
197 aBulletSrc
= URIHelper::SmartRel2Abs( INetURLObject( m_sBaseURL
), aBulletSrc
, Link
<OUString
*, bool>(), false );
200 case HtmlOptionId::WIDTH
:
201 nWidth
= o3tl::narrowing
<sal_uInt16
>(rOption
.GetNumber());
203 case HtmlOptionId::HEIGHT
:
204 nHeight
= o3tl::narrowing
<sal_uInt16
>(rOption
.GetNumber());
206 case HtmlOptionId::ALIGN
:
207 eVertOri
= rOption
.GetEnum( aHTMLImgVAlignTable
, eVertOri
);
213 if( !aBulletSrc
.isEmpty() )
215 // A bullet list with graphics
216 aNumFormat
.SetNumberingType(SVX_NUM_BITMAP
);
218 // Create the graphic as a brush
219 SvxBrushItem
aBrushItem( RES_BACKGROUND
);
220 aBrushItem
.SetGraphicLink( aBulletSrc
);
221 aBrushItem
.SetGraphicPos( GPOS_AREA
);
223 // Only set size if given a width and a height
224 Size
aTwipSz( nWidth
, nHeight
), *pTwipSz
=nullptr;
225 if( nWidth
!=USHRT_MAX
&& nHeight
!=USHRT_MAX
)
227 aTwipSz
= o3tl::convert(aTwipSz
, o3tl::Length::px
, o3tl::Length::twip
);
231 // Only set orientation if given one
232 aNumFormat
.SetGraphicBrush( &aBrushItem
, pTwipSz
,
233 text::VertOrientation::NONE
!=eVertOri
? &eVertOri
: nullptr);
235 // Remember the graphic to not put it into the paragraph
236 m_aBulletGrfs
[nLevel
] = aBulletSrc
;
237 bChangeNumFormat
= true;
240 m_aBulletGrfs
[nLevel
].clear();
242 // don't number the current paragraph (for now)
244 sal_uInt8 nLvl
= nLevel
;
248 // create a new context
249 std::unique_ptr
<HTMLAttrContext
> xCntxt(new HTMLAttrContext(nToken
));
252 if( HasStyleOptions( aStyle
, aId
, aClass
, &aLang
, &aDir
) )
254 SfxItemSet
aItemSet( m_xDoc
->GetAttrPool(), m_pCSS1Parser
->GetWhichMap() );
255 SvxCSS1PropertyInfo aPropInfo
;
257 if( ParseStyleOptions( aStyle
, aId
, aClass
, aItemSet
, aPropInfo
, &aLang
, &aDir
) )
261 if( aPropInfo
.m_bLeftMargin
)
263 // Default indent has already been added
264 tools::Long nAbsLSpace
=
265 aNumFormat
.GetAbsLSpace() - HTML_NUMBER_BULLET_MARGINLEFT
;
266 if( aPropInfo
.m_nLeftMargin
< 0 &&
267 nAbsLSpace
< -aPropInfo
.m_nLeftMargin
)
269 else if( aPropInfo
.m_nLeftMargin
> SHRT_MAX
||
270 nAbsLSpace
+ aPropInfo
.m_nLeftMargin
> SHRT_MAX
)
271 nAbsLSpace
= SHRT_MAX
;
273 nAbsLSpace
= nAbsLSpace
+ aPropInfo
.m_nLeftMargin
;
275 aNumFormat
.SetAbsLSpace( nAbsLSpace
);
276 bChangeNumFormat
= true;
278 if( aPropInfo
.m_bTextIndent
)
281 aItemSet
.Get(RES_MARGIN_FIRSTLINE
).GetTextFirstLineOffset();
282 aNumFormat
.SetFirstLineOffset( nTextIndent
);
283 bChangeNumFormat
= true;
285 if( aPropInfo
.m_bNumbering
)
287 aNumFormat
.SetNumberingType(aPropInfo
.m_nNumberingType
);
288 bChangeNumFormat
= true;
290 if( aPropInfo
.m_bBullet
)
292 aNumFormat
.SetBulletChar( aPropInfo
.m_cBulletChar
);
293 bChangeNumFormat
= true;
296 aPropInfo
.m_bLeftMargin
= aPropInfo
.m_bTextIndent
= false;
297 if( !aPropInfo
.m_bRightMargin
)
298 aItemSet
.ClearItem(RES_MARGIN_RIGHT
); // superfluous?
300 // #i89812# - Perform change to list style before calling <DoPositioning(..)>,
301 // because <DoPositioning(..)> may open a new context and thus may
302 // clear the <SwHTMLNumRuleInfo> instance hold by local variable <rInfo>.
303 if( bChangeNumFormat
)
305 rInfo
.GetNumRule()->Set( nLevel
, aNumFormat
);
306 m_xDoc
->ChgNumRuleFormats( *rInfo
.GetNumRule() );
307 bChangeNumFormat
= false;
310 DoPositioning(aItemSet
, aPropInfo
, xCntxt
.get());
312 InsertAttrs(aItemSet
, aPropInfo
, xCntxt
.get());
316 if( bChangeNumFormat
)
318 rInfo
.GetNumRule()->Set( nLevel
, aNumFormat
);
319 m_xDoc
->ChgNumRuleFormats( *rInfo
.GetNumRule() );
324 // set attributes to the current template
325 SetTextCollAttrs(m_aContexts
.back().get());
328 void SwHTMLParser::EndNumberBulletList( HtmlTokenId nToken
)
330 SwHTMLNumRuleInfo
& rInfo
= GetNumInfo();
332 // A new paragraph needs to be created, when
333 // - the current one isn't empty (it contains text or paragraph-bound objects)
334 // - the current one is numbered
335 bool bAppend
= m_pPam
->GetPoint()->GetContentIndex() > 0;
338 SwTextNode
* pTextNode
= m_pPam
->GetPointNode().GetTextNode();
340 bAppend
= (pTextNode
&& ! pTextNode
->IsOutline() && pTextNode
->IsCountedInList()) ||
342 HasCurrentParaFlys();
345 bool bSpace
= (rInfo
.GetDepth() + m_nDefListDeep
) == 1;
347 AppendTextNode( bSpace
? AM_SPACE
: AM_NOSPACE
, false );
351 // get current context from stack
352 std::unique_ptr
<HTMLAttrContext
> xCntxt(nToken
!= HtmlTokenId::NONE
? PopContext(getOnToken(nToken
)) : nullptr);
354 // Don't end a list because of a token, if the context wasn't created or mustn't be ended
355 if( rInfo
.GetDepth()>0 && (nToken
== HtmlTokenId::NONE
|| xCntxt
) )
358 if( !rInfo
.GetDepth() ) // was that the last level?
360 // The formats not yet modified are now modified, to ease editing
361 const SwNumFormat
*pRefNumFormat
= nullptr;
362 bool bChanged
= false;
363 for( sal_uInt16 i
=0; i
<MAXLEVEL
; i
++ )
365 const SwNumFormat
*pNumFormat
= rInfo
.GetNumRule()->GetNumFormat(i
);
368 pRefNumFormat
= pNumFormat
;
370 else if( pRefNumFormat
)
372 SwNumFormat
aNumFormat( rInfo
.GetNumRule()->Get(i
) );
373 aNumFormat
.SetNumberingType(pRefNumFormat
->GetNumberingType() != SVX_NUM_BITMAP
374 ? pRefNumFormat
->GetNumberingType() : SVX_NUM_CHAR_SPECIAL
);
375 if( SVX_NUM_CHAR_SPECIAL
== aNumFormat
.GetNumberingType() )
377 // #i63395# - Only apply user defined default bullet font
378 if ( numfunc::IsDefBulletFontUserDefined() )
380 aNumFormat
.SetBulletFont( &numfunc::GetDefBulletFont() );
382 aNumFormat
.SetBulletChar( cBulletChar
);
384 aNumFormat
.SetAbsLSpace( (i
+1) * HTML_NUMBER_BULLET_MARGINLEFT
);
385 aNumFormat
.SetFirstLineOffset( HTML_NUMBER_BULLET_INDENT
);
386 aNumFormat
.SetCharFormat( pRefNumFormat
->GetCharFormat() );
387 rInfo
.GetNumRule()->Set( i
, aNumFormat
);
392 m_xDoc
->ChgNumRuleFormats( *rInfo
.GetNumRule() );
394 // On the last append, the NumRule item and NodeNum object were copied.
395 // Now we need to delete them. ResetAttr deletes the NodeNum object as well
396 if (SwTextNode
*pTextNode
= m_pPam
->GetPointNode().GetTextNode())
397 pTextNode
->ResetAttr(RES_PARATR_NUMRULE
);
403 // the next paragraph not numbered first
404 SetNodeNum( rInfo
.GetLevel() );
409 bool bSetAttrs
= false;
412 EndContext(xCntxt
.get());
417 if( nToken
!= HtmlTokenId::NONE
)
421 SetAttr(); // Set paragraph attributes asap because of Javascript
425 void SwHTMLParser::NewNumberBulletListItem( HtmlTokenId nToken
)
427 sal_uInt8 nLevel
= GetNumInfo().GetLevel();
428 OUString aId
, aStyle
, aClass
, aLang
, aDir
;
429 sal_uInt16 nStart
= HtmlTokenId::LISTHEADER_ON
!= nToken
430 ? GetNumInfo().GetNodeStartValue( nLevel
)
432 if( USHRT_MAX
!= nStart
)
433 GetNumInfo().SetNodeStartValue( nLevel
);
435 const HTMLOptions
& rHTMLOptions
= GetOptions();
436 for (size_t i
= rHTMLOptions
.size(); i
; )
438 const HTMLOption
& rOption
= rHTMLOptions
[--i
];
439 switch( rOption
.GetToken() )
441 case HtmlOptionId::VALUE
:
442 nStart
= o3tl::narrowing
<sal_uInt16
>(rOption
.GetNumber());
444 case HtmlOptionId::ID
:
445 aId
= rOption
.GetString();
447 case HtmlOptionId::STYLE
:
448 aStyle
= rOption
.GetString();
450 case HtmlOptionId::CLASS
:
451 aClass
= rOption
.GetString();
453 case HtmlOptionId::LANG
:
454 aLang
= rOption
.GetString();
456 case HtmlOptionId::DIR:
457 aDir
= rOption
.GetString();
463 // create a new paragraph
464 if( m_pPam
->GetPoint()->GetContentIndex() )
465 AppendTextNode( AM_NOSPACE
, false );
466 m_bNoParSpace
= false; // no space in <LI>!
468 SwTextNode
* pTextNode
= m_pPam
->GetPointNode().GetTextNode();
471 SAL_WARN("sw.html", "No Text-Node at PaM-Position");
475 const bool bCountedInList
= nToken
!= HtmlTokenId::LISTHEADER_ON
;
477 std::unique_ptr
<HTMLAttrContext
> xCntxt(new HTMLAttrContext(nToken
));
479 OUString aNumRuleName
;
480 if( GetNumInfo().GetNumRule() )
482 aNumRuleName
= GetNumInfo().GetNumRule()->GetName();
486 aNumRuleName
= m_xDoc
->GetUniqueNumRuleName();
487 SwNumRule
aNumRule( aNumRuleName
,
488 SvxNumberFormat::LABEL_WIDTH_AND_POSITION
);
489 SwNumFormat
aNumFormat( aNumRule
.Get( 0 ) );
490 // #i63395# - Only apply user defined default bullet font
491 if ( numfunc::IsDefBulletFontUserDefined() )
493 aNumFormat
.SetBulletFont( &numfunc::GetDefBulletFont() );
495 aNumFormat
.SetNumberingType(SVX_NUM_CHAR_SPECIAL
);
496 aNumFormat
.SetBulletChar( cBulletChar
); // the bullet character !!
497 aNumFormat
.SetCharFormat( m_pCSS1Parser
->GetCharFormatFromPool(RES_POOLCHR_BULLET_LEVEL
) );
498 aNumFormat
.SetFirstLineOffset( HTML_NUMBER_BULLET_INDENT
);
499 aNumRule
.Set( 0, aNumFormat
);
501 m_xDoc
->MakeNumRule( aNumRuleName
, &aNumRule
);
503 OSL_ENSURE( m_nOpenParaToken
== HtmlTokenId::NONE
,
504 "Now an open paragraph element is lost" );
505 // We'll act like we're in a paragraph. On the next paragraph, at least numbering is gone,
506 // that's gonna be taken over by the next AppendTextNode
507 m_nOpenParaToken
= nToken
;
510 static_cast<SwContentNode
*>(pTextNode
)->SetAttr( SwNumRuleItem(aNumRuleName
) );
511 pTextNode
->SetAttrListLevel(nLevel
);
512 // #i57656# - <IsCounted()> state of text node has to be adjusted accordingly.
513 if ( nLevel
< MAXLEVEL
)
515 pTextNode
->SetCountedInList( bCountedInList
);
518 // correction of refactoring done by cws swnumtree
519 // - <nStart> contains the start value, if the numbering has to be restarted
520 // at this text node. Value <USHRT_MAX> indicates, that numbering isn't
521 // restarted at this text node
522 if ( nStart
!= USHRT_MAX
)
524 pTextNode
->SetListRestart( true );
525 pTextNode
->SetAttrListRestartValue( nStart
);
528 if( GetNumInfo().GetNumRule() )
529 GetNumInfo().GetNumRule()->SetInvalidRule( true );
532 if( HasStyleOptions( aStyle
, aId
, aClass
, &aLang
, &aDir
) )
534 SfxItemSet
aItemSet( m_xDoc
->GetAttrPool(), m_pCSS1Parser
->GetWhichMap() );
535 SvxCSS1PropertyInfo aPropInfo
;
537 if( ParseStyleOptions( aStyle
, aId
, aClass
, aItemSet
, aPropInfo
, &aLang
, &aDir
) )
539 DoPositioning(aItemSet
, aPropInfo
, xCntxt
.get());
540 InsertAttrs(aItemSet
, aPropInfo
, xCntxt
.get());
546 // set the new template
547 SetTextCollAttrs(m_aContexts
.back().get());
549 // Refresh scroll bar
553 void SwHTMLParser::EndNumberBulletListItem( HtmlTokenId nToken
, bool bSetColl
)
555 // Create a new paragraph
556 if( nToken
== HtmlTokenId::NONE
&& m_pPam
->GetPoint()->GetContentIndex() )
557 AppendTextNode( AM_NOSPACE
);
559 // Get context to that token and pop it from stack
560 std::unique_ptr
<HTMLAttrContext
> xCntxt
;
561 auto nPos
= m_aContexts
.size();
562 nToken
= getOnToken(nToken
);
563 while (!xCntxt
&& nPos
>m_nContextStMin
)
565 HtmlTokenId nCntxtToken
= m_aContexts
[--nPos
]->GetToken();
566 switch( nCntxtToken
)
568 case HtmlTokenId::LI_ON
:
569 case HtmlTokenId::LISTHEADER_ON
:
570 if( nToken
== HtmlTokenId::NONE
|| nToken
== nCntxtToken
)
572 xCntxt
= std::move(m_aContexts
[nPos
]);
573 m_aContexts
.erase( m_aContexts
.begin() + nPos
);
576 case HtmlTokenId::ORDERLIST_ON
:
577 case HtmlTokenId::UNORDERLIST_ON
:
578 case HtmlTokenId::MENULIST_ON
:
579 case HtmlTokenId::DIRLIST_ON
:
580 // Don't care about LI/LH outside the current list
581 nPos
= m_nContextStMin
;
590 EndContext(xCntxt
.get());
591 SetAttr(); // set paragraph attributes asap because of Javascript
595 // set current template
600 void SwHTMLParser::SetNodeNum( sal_uInt8 nLevel
)
602 SwTextNode
* pTextNode
= m_pPam
->GetPointNode().GetTextNode();
605 SAL_WARN("sw.html", "No Text-Node at PaM-Position");
609 OSL_ENSURE( GetNumInfo().GetNumRule(), "No numbering rule" );
610 const OUString
& rName
= GetNumInfo().GetNumRule()->GetName();
611 static_cast<SwContentNode
*>(pTextNode
)->SetAttr( SwNumRuleItem(rName
) );
613 pTextNode
->SetAttrListLevel( nLevel
);
614 pTextNode
->SetCountedInList( false );
616 // Invalidate NumRule, it may have been set valid because of an EndAction
617 GetNumInfo().GetNumRule()->SetInvalidRule( false );
620 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */