/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/*
* This file is part of the LibreOffice project.
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*
* This file incorporates work covered by the following license notice:
*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed
* with this work for additional information regarding copyright
* ownership. The ASF licenses this file to you under the Apache
* License, Version 2.0 (the "License"); you may not use this file
* except in compliance with the License. You may obtain a copy of
* the License at http://www.apache.org/licenses/LICENSE-2.0 .
*/
#include <com/sun/star/text/VertOrientation.hpp>
#include <hintids.hxx>
#include <svtools/htmltokn.h>
#include <svtools/htmlkywd.hxx>
#include <svl/urihelper.hxx>
#include <editeng/brushitem.hxx>
#include <editeng/lrspitem.hxx>
#include <vcl/svapp.hxx>
#include <sal/log.hxx>
#include <osl/diagnose.h>
#include <numrule.hxx>
#include <doc.hxx>
#include <docary.hxx>
#include <poolfmt.hxx>
#include <ndtxt.hxx>
#include <paratr.hxx>
#include "htmlnum.hxx"
#include "swcss1.hxx"
#include "swhtml.hxx"
using namespace css;
// <UL TYPE=...>
HTMLOptionEnum<sal_UCS4> const aHTMLULTypeTable[] =
{
{ OOO_STRING_SVTOOLS_HTML_ULTYPE_disc, HTML_BULLETCHAR_DISC },
{ OOO_STRING_SVTOOLS_HTML_ULTYPE_circle, HTML_BULLETCHAR_CIRCLE },
{ OOO_STRING_SVTOOLS_HTML_ULTYPE_square, HTML_BULLETCHAR_SQUARE },
{ nullptr, 0 }
};
void SwHTMLParser::NewNumberBulletList( HtmlTokenId nToken )
{
SwHTMLNumRuleInfo& rInfo = GetNumInfo();
// Create a new paragraph
bool bSpace = (rInfo.GetDepth() + m_nDefListDeep) == 0;
if( m_pPam->GetPoint()->GetContentIndex() )
AppendTextNode( bSpace ? AM_SPACE : AM_NOSPACE, false );
else if( bSpace )
AddParSpace();
// Increment the numbering depth
rInfo.IncDepth();
sal_uInt8 nLevel = static_cast<sal_uInt8>( (rInfo.GetDepth() <= MAXLEVEL ? rInfo.GetDepth()
: MAXLEVEL) - 1 );
// Create rules if needed
if( !rInfo.GetNumRule() )
{
sal_uInt16 nPos = m_xDoc->MakeNumRule( m_xDoc->GetUniqueNumRuleName() );
rInfo.SetNumRule( m_xDoc->GetNumRuleTable()[nPos] );
}
// Change the format for this level if that hasn't happened yet for this level
bool bNewNumFormat = rInfo.GetNumRule()->GetNumFormat( nLevel ) == nullptr;
bool bChangeNumFormat = false;
// Create the default numbering format
SwNumFormat aNumFormat( rInfo.GetNumRule()->Get(nLevel) );
rInfo.SetNodeStartValue( nLevel );
if( bNewNumFormat )
{
sal_uInt16 nChrFormatPoolId = 0;
if( HtmlTokenId::ORDERLIST_ON == nToken )
{
aNumFormat.SetNumberingType(SVX_NUM_ARABIC);
nChrFormatPoolId = RES_POOLCHR_NUM_LEVEL;
}
else
{
// We'll set a default style because the UI does the same. This meant a 9pt font, which
// was not the case in Netscape. That didn't bother anyone so far
// #i63395# - Only apply user defined default bullet font
if ( numfunc::IsDefBulletFontUserDefined() )
{
aNumFormat.SetBulletFont( &numfunc::GetDefBulletFont() );
}
aNumFormat.SetNumberingType(SVX_NUM_CHAR_SPECIAL);
aNumFormat.SetBulletChar( cBulletChar );
nChrFormatPoolId = RES_POOLCHR_BULLET_LEVEL;
}
sal_Int32 nAbsLSpace = HTML_NUMBER_BULLET_MARGINLEFT;
sal_Int32 nFirstLineIndent = HTML_NUMBER_BULLET_INDENT;
if( nLevel > 0 )
{
const SwNumFormat& rPrevNumFormat = rInfo.GetNumRule()->Get( nLevel-1 );
nAbsLSpace = nAbsLSpace + rPrevNumFormat.GetAbsLSpace();
nFirstLineIndent = rPrevNumFormat.GetFirstLineOffset();
}
aNumFormat.SetAbsLSpace( nAbsLSpace );
aNumFormat.SetFirstLineOffset( nFirstLineIndent );
aNumFormat.SetCharFormat( m_pCSS1Parser->GetCharFormatFromPool(nChrFormatPoolId) );
bChangeNumFormat = true;
}
else if( 1 != aNumFormat.GetStart() )
{
// If the layer has already been used, the start value may need to be set hard to the paragraph.
rInfo.SetNodeStartValue( nLevel, 1 );
}
// and set that in the options
OUString aId, aStyle, aClass, aLang, aDir;
OUString aBulletSrc;
sal_Int16 eVertOri = text::VertOrientation::NONE;
sal_uInt16 nWidth=USHRT_MAX, nHeight=USHRT_MAX;
const HTMLOptions& rHTMLOptions = GetOptions();
for (size_t i = rHTMLOptions.size(); i; )
{
const HTMLOption& rOption = rHTMLOptions[--i];
switch( rOption.GetToken() )
{
case HtmlOptionId::ID:
aId = rOption.GetString();
break;
case HtmlOptionId::TYPE:
if( bNewNumFormat && !rOption.GetString().isEmpty() )
{
switch( nToken )
{
case HtmlTokenId::ORDERLIST_ON:
bChangeNumFormat = true;
switch( rOption.GetString()[0] )
{
case 'A': aNumFormat.SetNumberingType(SVX_NUM_CHARS_UPPER_LETTER); break;
case 'a': aNumFormat.SetNumberingType(SVX_NUM_CHARS_LOWER_LETTER); break;
case 'I': aNumFormat.SetNumberingType(SVX_NUM_ROMAN_UPPER); break;
case 'i': aNumFormat.SetNumberingType(SVX_NUM_ROMAN_LOWER); break;
default: bChangeNumFormat = false;
}
break;
case HtmlTokenId::UNORDERLIST_ON:
aNumFormat.SetBulletChar( rOption.GetEnum(
aHTMLULTypeTable,aNumFormat.GetBulletChar() ) );
bChangeNumFormat = true;
break;
default: break;
}
}
break;
case HtmlOptionId::START:
{
sal_uInt16 nStart = o3tl::narrowing<sal_uInt16>(rOption.GetNumber());
if( bNewNumFormat )
{
aNumFormat.SetStart( nStart );
bChangeNumFormat = true;
}
else
{
rInfo.SetNodeStartValue( nLevel, nStart );
}
}
break;
case HtmlOptionId::STYLE:
aStyle = rOption.GetString();
break;
case HtmlOptionId::CLASS:
aClass = rOption.GetString();
break;
case HtmlOptionId::LANG:
aLang = rOption.GetString();
break;
case HtmlOptionId::DIR:
aDir = rOption.GetString();
break;
case HtmlOptionId::SRC:
if( bNewNumFormat )
{
aBulletSrc = rOption.GetString();
if( !InternalImgToPrivateURL(aBulletSrc) )
aBulletSrc = URIHelper::SmartRel2Abs( INetURLObject( m_sBaseURL ), aBulletSrc, Link<OUString *, bool>(), false );
}
break;
case HtmlOptionId::WIDTH:
nWidth = o3tl::narrowing<sal_uInt16>(rOption.GetNumber());
break;
case HtmlOptionId::HEIGHT:
nHeight = o3tl::narrowing<sal_uInt16>(rOption.GetNumber());
break;
case HtmlOptionId::ALIGN:
eVertOri = rOption.GetEnum( aHTMLImgVAlignTable, eVertOri );
break;
default: break;
}
}
if( !aBulletSrc.isEmpty() )
{
// A bullet list with graphics
aNumFormat.SetNumberingType(SVX_NUM_BITMAP);
// Create the graphic as a brush
SvxBrushItem aBrushItem( RES_BACKGROUND );
aBrushItem.SetGraphicLink( aBulletSrc );
aBrushItem.SetGraphicPos( GPOS_AREA );
// Only set size if given a width and a height
Size aTwipSz( nWidth, nHeight), *pTwipSz=nullptr;
if( nWidth!=USHRT_MAX && nHeight!=USHRT_MAX )
{
aTwipSz = o3tl::convert(aTwipSz, o3tl::Length::px, o3tl::Length::twip);
pTwipSz = &aTwipSz;
}
// Only set orientation if given one
aNumFormat.SetGraphicBrush( &aBrushItem, pTwipSz,
text::VertOrientation::NONE!=eVertOri ? &eVertOri : nullptr);
// Remember the graphic to not put it into the paragraph
m_aBulletGrfs[nLevel] = aBulletSrc;
bChangeNumFormat = true;
}
else
m_aBulletGrfs[nLevel].clear();
// don't number the current paragraph (for now)
{
sal_uInt8 nLvl = nLevel;
SetNodeNum( nLvl );
}
// create a new context
std::unique_ptr<HTMLAttrContext> xCntxt(new HTMLAttrContext(nToken));
// Parse styles
if( HasStyleOptions( aStyle, aId, aClass, &aLang, &aDir ) )
{
SfxItemSet aItemSet( m_xDoc->GetAttrPool(), m_pCSS1Parser->GetWhichMap() );
SvxCSS1PropertyInfo aPropInfo;
if( ParseStyleOptions( aStyle, aId, aClass, aItemSet, aPropInfo, &aLang, &aDir ) )
{
if( bNewNumFormat )
{
if( aPropInfo.m_bLeftMargin )
{
// Default indent has already been added
tools::Long nAbsLSpace =
aNumFormat.GetAbsLSpace() - HTML_NUMBER_BULLET_MARGINLEFT;
if( aPropInfo.m_nLeftMargin < 0 &&
nAbsLSpace < -aPropInfo.m_nLeftMargin )
nAbsLSpace = 0U;
else if( aPropInfo.m_nLeftMargin > SHRT_MAX ||
nAbsLSpace + aPropInfo.m_nLeftMargin > SHRT_MAX )
nAbsLSpace = SHRT_MAX;
else
nAbsLSpace = nAbsLSpace + aPropInfo.m_nLeftMargin;
aNumFormat.SetAbsLSpace( nAbsLSpace );
bChangeNumFormat = true;
}
if( aPropInfo.m_bTextIndent )
{
short nTextIndent =
aItemSet.Get(RES_MARGIN_FIRSTLINE).GetTextFirstLineOffset();
aNumFormat.SetFirstLineOffset( nTextIndent );
bChangeNumFormat = true;
}
if( aPropInfo.m_bNumbering )
{
aNumFormat.SetNumberingType(aPropInfo.m_nNumberingType);
bChangeNumFormat = true;
}
if( aPropInfo.m_bBullet )
{
aNumFormat.SetBulletChar( aPropInfo.m_cBulletChar );
bChangeNumFormat = true;
}
}
aPropInfo.m_bLeftMargin = aPropInfo.m_bTextIndent = false;
if( !aPropInfo.m_bRightMargin )
aItemSet.ClearItem(RES_MARGIN_RIGHT); // superfluous?
// #i89812# - Perform change to list style before calling <DoPositioning(..)>,
// because <DoPositioning(..)> may open a new context and thus may
// clear the <SwHTMLNumRuleInfo> instance hold by local variable <rInfo>.
if( bChangeNumFormat )
{
rInfo.GetNumRule()->Set( nLevel, aNumFormat );
m_xDoc->ChgNumRuleFormats( *rInfo.GetNumRule() );
bChangeNumFormat = false;
}
DoPositioning(aItemSet, aPropInfo, xCntxt.get());
InsertAttrs(aItemSet, aPropInfo, xCntxt.get());
}
}
if( bChangeNumFormat )
{
rInfo.GetNumRule()->Set( nLevel, aNumFormat );
m_xDoc->ChgNumRuleFormats( *rInfo.GetNumRule() );
}
PushContext(xCntxt);
// set attributes to the current template
SetTextCollAttrs(m_aContexts.back().get());
}
void SwHTMLParser::EndNumberBulletList( HtmlTokenId nToken )
{
SwHTMLNumRuleInfo& rInfo = GetNumInfo();
// A new paragraph needs to be created, when
// - the current one isn't empty (it contains text or paragraph-bound objects)
// - the current one is numbered
bool bAppend = m_pPam->GetPoint()->GetContentIndex() > 0;
if( !bAppend )
{
SwTextNode* pTextNode = m_pPam->GetPointNode().GetTextNode();
bAppend = (pTextNode && ! pTextNode->IsOutline() && pTextNode->IsCountedInList()) ||
HasCurrentParaFlys();
}
bool bSpace = (rInfo.GetDepth() + m_nDefListDeep) == 1;
if( bAppend )
AppendTextNode( bSpace ? AM_SPACE : AM_NOSPACE, false );
else if( bSpace )
AddParSpace();
// get current context from stack
std::unique_ptr<HTMLAttrContext> xCntxt(nToken != HtmlTokenId::NONE ? PopContext(getOnToken(nToken)) : nullptr);
// Don't end a list because of a token, if the context wasn't created or mustn't be ended
if( rInfo.GetDepth()>0 && (nToken == HtmlTokenId::NONE || xCntxt) )
{
rInfo.DecDepth();
if( !rInfo.GetDepth() ) // was that the last level?
{
// The formats not yet modified are now modified, to ease editing
const SwNumFormat *pRefNumFormat = nullptr;
bool bChanged = false;
for( sal_uInt16 i=0; i<MAXLEVEL; i++ )
{
const SwNumFormat *pNumFormat = rInfo.GetNumRule()->GetNumFormat(i);
if( pNumFormat )
{
pRefNumFormat = pNumFormat;
}
else if( pRefNumFormat )
{
SwNumFormat aNumFormat( rInfo.GetNumRule()->Get(i) );
aNumFormat.SetNumberingType(pRefNumFormat->GetNumberingType() != SVX_NUM_BITMAP
? pRefNumFormat->GetNumberingType() : SVX_NUM_CHAR_SPECIAL);
if( SVX_NUM_CHAR_SPECIAL == aNumFormat.GetNumberingType() )
{
// #i63395# - Only apply user defined default bullet font
if ( numfunc::IsDefBulletFontUserDefined() )
{
aNumFormat.SetBulletFont( &numfunc::GetDefBulletFont() );
}
aNumFormat.SetBulletChar( cBulletChar );
}
aNumFormat.SetAbsLSpace( (i+1) * HTML_NUMBER_BULLET_MARGINLEFT );
aNumFormat.SetFirstLineOffset( HTML_NUMBER_BULLET_INDENT );
aNumFormat.SetCharFormat( pRefNumFormat->GetCharFormat() );
rInfo.GetNumRule()->Set( i, aNumFormat );
bChanged = true;
}
}
if( bChanged )
m_xDoc->ChgNumRuleFormats( *rInfo.GetNumRule() );
// On the last append, the NumRule item and NodeNum object were copied.
// Now we need to delete them. ResetAttr deletes the NodeNum object as well
if (SwTextNode *pTextNode = m_pPam->GetPointNode().GetTextNode())
pTextNode->ResetAttr(RES_PARATR_NUMRULE);
rInfo.Clear();
}
else
{
// the next paragraph not numbered first
SetNodeNum( rInfo.GetLevel() );
}
}
// end attributes
bool bSetAttrs = false;
if (xCntxt)
{
EndContext(xCntxt.get());
xCntxt.reset();
bSetAttrs = true;
}
if( nToken != HtmlTokenId::NONE )
SetTextCollAttrs();
if( bSetAttrs )
SetAttr(); // Set paragraph attributes asap because of Javascript
}
void SwHTMLParser::NewNumberBulletListItem( HtmlTokenId nToken )
{
sal_uInt8 nLevel = GetNumInfo().GetLevel();
OUString aId, aStyle, aClass, aLang, aDir;
sal_uInt16 nStart = HtmlTokenId::LISTHEADER_ON != nToken
? GetNumInfo().GetNodeStartValue( nLevel )
: USHRT_MAX;
if( USHRT_MAX != nStart )
GetNumInfo().SetNodeStartValue( nLevel );
const HTMLOptions& rHTMLOptions = GetOptions();
for (size_t i = rHTMLOptions.size(); i; )
{
const HTMLOption& rOption = rHTMLOptions[--i];
switch( rOption.GetToken() )
{
case HtmlOptionId::VALUE:
nStart = o3tl::narrowing<sal_uInt16>(rOption.GetNumber());
break;
case HtmlOptionId::ID:
aId = rOption.GetString();
break;
case HtmlOptionId::STYLE:
aStyle = rOption.GetString();
break;
case HtmlOptionId::CLASS:
aClass = rOption.GetString();
break;
case HtmlOptionId::LANG:
aLang = rOption.GetString();
break;
case HtmlOptionId::DIR:
aDir = rOption.GetString();
break;
default: break;
}
}
// create a new paragraph
if( m_pPam->GetPoint()->GetContentIndex() )
AppendTextNode( AM_NOSPACE, false );
m_bNoParSpace = false; // no space in <LI>!
SwTextNode* pTextNode = m_pPam->GetPointNode().GetTextNode();
if (!pTextNode)
{
SAL_WARN("sw.html", "No Text-Node at PaM-Position");
return;
}
const bool bCountedInList = nToken != HtmlTokenId::LISTHEADER_ON;
std::unique_ptr<HTMLAttrContext> xCntxt(new HTMLAttrContext(nToken));
OUString aNumRuleName;
if( GetNumInfo().GetNumRule() )
{
aNumRuleName = GetNumInfo().GetNumRule()->GetName();
}
else
{
aNumRuleName = m_xDoc->GetUniqueNumRuleName();
SwNumRule aNumRule( aNumRuleName,
SvxNumberFormat::LABEL_WIDTH_AND_POSITION );
SwNumFormat aNumFormat( aNumRule.Get( 0 ) );
// #i63395# - Only apply user defined default bullet font
if ( numfunc::IsDefBulletFontUserDefined() )
{
aNumFormat.SetBulletFont( &numfunc::GetDefBulletFont() );
}
aNumFormat.SetNumberingType(SVX_NUM_CHAR_SPECIAL);
aNumFormat.SetBulletChar( cBulletChar ); // the bullet character !!
aNumFormat.SetCharFormat( m_pCSS1Parser->GetCharFormatFromPool(RES_POOLCHR_BULLET_LEVEL) );
aNumFormat.SetFirstLineOffset( HTML_NUMBER_BULLET_INDENT );
aNumRule.Set( 0, aNumFormat );
m_xDoc->MakeNumRule( aNumRuleName, &aNumRule );
OSL_ENSURE( m_nOpenParaToken == HtmlTokenId::NONE,
"Now an open paragraph element is lost" );
// We'll act like we're in a paragraph. On the next paragraph, at least numbering is gone,
// that's gonna be taken over by the next AppendTextNode
m_nOpenParaToken = nToken;
}
static_cast<SwContentNode *>(pTextNode)->SetAttr( SwNumRuleItem(aNumRuleName) );
pTextNode->SetAttrListLevel(nLevel);
// #i57656# - <IsCounted()> state of text node has to be adjusted accordingly.
if ( nLevel < MAXLEVEL )
{
pTextNode->SetCountedInList( bCountedInList );
}
// #i57919#
// correction of refactoring done by cws swnumtree
// - <nStart> contains the start value, if the numbering has to be restarted
// at this text node. Value <USHRT_MAX> indicates, that numbering isn't
// restarted at this text node
if ( nStart != USHRT_MAX )
{
pTextNode->SetListRestart( true );
pTextNode->SetAttrListRestartValue( nStart );
}
if( GetNumInfo().GetNumRule() )
GetNumInfo().GetNumRule()->Invalidate();
// parse styles
if( HasStyleOptions( aStyle, aId, aClass, &aLang, &aDir ) )
{
SfxItemSet aItemSet( m_xDoc->GetAttrPool(), m_pCSS1Parser->GetWhichMap() );
SvxCSS1PropertyInfo aPropInfo;
if( ParseStyleOptions( aStyle, aId, aClass, aItemSet, aPropInfo, &aLang, &aDir ) )
{
DoPositioning(aItemSet, aPropInfo, xCntxt.get());
InsertAttrs(aItemSet, aPropInfo, xCntxt.get());
}
}
PushContext(xCntxt);
// set the new template
SetTextCollAttrs(m_aContexts.back().get());
// Refresh scroll bar
ShowStatline();
}
void SwHTMLParser::EndNumberBulletListItem( HtmlTokenId nToken, bool bSetColl )
{
// Create a new paragraph
if( nToken == HtmlTokenId::NONE && m_pPam->GetPoint()->GetContentIndex() )
AppendTextNode( AM_NOSPACE );
// Get context to that token and pop it from stack
std::unique_ptr<HTMLAttrContext> xCntxt;
auto nPos = m_aContexts.size();
nToken = getOnToken(nToken);
while (!xCntxt && nPos>m_nContextStMin)
{
HtmlTokenId nCntxtToken = m_aContexts[--nPos]->GetToken();
switch( nCntxtToken )
{
case HtmlTokenId::LI_ON:
case HtmlTokenId::LISTHEADER_ON:
if( nToken == HtmlTokenId::NONE || nToken == nCntxtToken )
{
xCntxt = std::move(m_aContexts[nPos]);
m_aContexts.erase( m_aContexts.begin() + nPos );
}
break;
case HtmlTokenId::ORDERLIST_ON:
case HtmlTokenId::UNORDERLIST_ON:
case HtmlTokenId::MENULIST_ON:
case HtmlTokenId::DIRLIST_ON:
// Don't care about LI/LH outside the current list
nPos = m_nContextStMin;
break;
default: break;
}
}
// end attributes
if (xCntxt)
{
EndContext(xCntxt.get());
SetAttr(); // set paragraph attributes asap because of Javascript
xCntxt.reset();
}
// set current template
if( bSetColl )
SetTextCollAttrs();
}
void SwHTMLParser::SetNodeNum( sal_uInt8 nLevel )
{
SwTextNode* pTextNode = m_pPam->GetPointNode().GetTextNode();
if (!pTextNode)
{
SAL_WARN("sw.html", "No Text-Node at PaM-Position");
return;
}
OSL_ENSURE( GetNumInfo().GetNumRule(), "No numbering rule" );
const OUString& rName = GetNumInfo().GetNumRule()->GetName();
static_cast<SwContentNode *>(pTextNode)->SetAttr( SwNumRuleItem(rName) );
pTextNode->SetAttrListLevel( nLevel );
pTextNode->SetCountedInList( false );
// Invalidate NumRule, it may have been set valid because of an EndAction
GetNumInfo().GetNumRule()->Invalidate();
}
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
↑ V547 Expression 'nLevel < MAXLEVEL' is always true.