/* -*- 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 "eehtml.hxx"
#include <editdoc.hxx>
#include <editeng/adjustitem.hxx>
#include <editeng/eeitem.hxx>
#include <editeng/flditem.hxx>
#include <tools/urlobj.hxx>
#include <editeng/fhgtitem.hxx>
#include <editeng/fontitem.hxx>
#include <editeng/ulspitem.hxx>
#include <editeng/wghtitem.hxx>
#include <svtools/htmltokn.h>
#include <svtools/htmlkywd.hxx>
#include <tools/tenccvt.hxx>
#include <vcl/mapmod.hxx>
#include <vcl/outdev.hxx>

#include <editeng/editeng.hxx>
#include <utility>

#define STYLE_PRE               101

EditHTMLParser::EditHTMLParser( SvStream& rIn, OUString _aBaseURL, SvKeyValueIterator* pHTTPHeaderAttrs )
    : HTMLParser( rIn, true ),
    aBaseURL(std::move( _aBaseURL )),
    mpEditEngine(nullptr),
    bInPara(false),
    bWasInPara(false),
    mbBreakForDivs(false),
    mbNewBlockNeeded(false),
    bFieldsInserted(false),
    bInTitle(false),
    nInTable(0),
    nInCell(0),
    nDefListLevel(0)
{
    DBG_ASSERT( !IsSwitchToUCS2(), "EditHTMLParser::::EditHTMLParser: Switch to UCS2?" );

    // Although the real default encoding is ISO8859-1, we use MS-1252
    // as default encoding.
    SetSrcEncoding( GetExtendedCompatibilityTextEncoding(  RTL_TEXTENCODING_ISO_8859_1 ) );

    // If the file starts with a BOM, switch to UCS2.
    SetSwitchToUCS2( true );

    if ( pHTTPHeaderAttrs )
    {
        SetEncodingByHTTPHeader( pHTTPHeaderAttrs );
        SetBreakForDivs(*pHTTPHeaderAttrs);
    }
}

void EditHTMLParser::SetBreakForDivs(SvKeyValueIterator& rHTTPOptions)
{
    SvKeyValue aKV;
    bool bCont = rHTTPOptions.GetFirst(aKV);
    while (bCont)
    {
        if (aKV.GetKey() == "newline-on-div")
        {
            mbBreakForDivs = aKV.GetValue() == "true";
            break;
        }
        bCont = rHTTPOptions.GetNext(aKV);
    }
}

EditHTMLParser::~EditHTMLParser()
{
}

SvParserState EditHTMLParser::CallParser(EditEngine* pEE, const EditPaM& rPaM)
{
    DBG_ASSERT(pEE, "CallParser: ImpEditEngine ?!");
    mpEditEngine = pEE;
    SvParserState _eState = SvParserState::NotStarted;
    if ( mpEditEngine )
    {
        // Build in wrap mimic in RTF import?
        aCurSel = EditSelection( rPaM, rPaM );

        if (mpEditEngine->IsHtmlImportHandlerSet())
        {
            HtmlImportInfo aImportInfo(HtmlImportState::Start, this, mpEditEngine->CreateESelection(aCurSel));
            mpEditEngine->CallHtmlImportHandler(aImportInfo);
        }

        ImpSetStyleSheet( 0 );
        _eState = HTMLParser::CallParser();

        if (mpEditEngine->IsHtmlImportHandlerSet())
        {
            HtmlImportInfo aImportInfo(HtmlImportState::End, this, mpEditEngine->CreateESelection(aCurSel));
            mpEditEngine->CallHtmlImportHandler(aImportInfo);
        }

        if ( bFieldsInserted )
            mpEditEngine->UpdateFieldsOnly();
    }
    return _eState;
}

void EditHTMLParser::Newline()
{
    bool bHasText = HasTextInCurrentPara();
    if ( bHasText )
        ImpInsertParaBreak();
    StartPara( false );
}

void EditHTMLParser::NextToken( HtmlTokenId nToken )
{
    switch( nToken )
    {
    case HtmlTokenId::META:
    {
        const HTMLOptions& aOptions = GetOptions();
        size_t nArrLen = aOptions.size();
        bool bEquiv = false;
        for ( size_t i = 0; i < nArrLen; i++ )
        {
            const HTMLOption& aOption = aOptions[i];
            switch( aOption.GetToken() )
            {
                case HtmlOptionId::HTTPEQUIV:
                {
                    bEquiv = true;
                }
                break;
                case HtmlOptionId::CONTENT:
                {
                    if ( bEquiv )
                    {
                        rtl_TextEncoding eEnc = GetEncodingByMIME( aOption.GetString() );
                        if ( eEnc != RTL_TEXTENCODING_DONTKNOW )
                            SetSrcEncoding( eEnc );
                    }
                }
                break;
                default: break;
            }
        }

    }
    break;
    case HtmlTokenId::PLAINTEXT_ON:
    case HtmlTokenId::PLAINTEXT2_ON:
        bInPara = true;
    break;
    case HtmlTokenId::PLAINTEXT_OFF:
    case HtmlTokenId::PLAINTEXT2_OFF:
        bInPara = false;
    break;

    case HtmlTokenId::LINEBREAK:
    case HtmlTokenId::NEWPARA:
    {
        if ( ( bInPara || nInTable ) &&
            ( ( nToken == HtmlTokenId::LINEBREAK ) || HasTextInCurrentPara() ) )
        {
            ImpInsertParaBreak();
        }
    }
    break;
    case HtmlTokenId::HORZRULE:
    {
        if ( HasTextInCurrentPara() )
            ImpInsertParaBreak();
        ImpInsertParaBreak();
    }
    break;
    case HtmlTokenId::NONBREAKSPACE:
    {
        if ( bInPara )
        {
            ImpInsertText( u" "_ustr );
        }
    }
    break;
    case HtmlTokenId::RAWDATA:
        if (IsReadStyle() && !aToken.isEmpty())
        {
            // Each token represents a single line.
            maStyleSource.append(aToken);
            maStyleSource.append('\n');
        }
    break;
    case HtmlTokenId::TEXTTOKEN:
    {
        // #i110937# for <title> content, call aImportHdl (no SkipGroup), but don't insert the text into the EditEngine
        if (!bInTitle)
        {
            if ( !bInPara )
                StartPara( false );

            OUString aText = aToken.toString();
            if ( aText.startsWith(" ") && ThrowAwayBlank() && !IsReadPRE() )
                aText = aText.copy( 1 );

            if ( moCurAnchor )
            {
                moCurAnchor->aText += aText;
            }
            else
            {
                // Only written until HTML with 319?
                if ( IsReadPRE() )
                    aText = aText.replaceAll(u"\t", u"        ");
                ImpInsertText( aText );
            }
        }
    }
    break;

    case HtmlTokenId::CENTER_ON:
    case HtmlTokenId::CENTER_OFF:
                            {
                                sal_Int32 nNode = mpEditEngine->GetEditDoc().GetPos( aCurSel.Max().GetNode() );
                                SfxItemSet aItems( aCurSel.Max().GetNode()->GetContentAttribs().GetItems() );
                                aItems.ClearItem( EE_PARA_JUST );
                                if ( nToken == HtmlTokenId::CENTER_ON )
                                    aItems.Put( SvxAdjustItem( SvxAdjust::Center, EE_PARA_JUST ) );
                                mpEditEngine->SetParaAttribsOnly(nNode, aItems);
                            }
                            break;

    case HtmlTokenId::ANCHOR_ON:    AnchorStart();
                            break;
    case HtmlTokenId::ANCHOR_OFF:   AnchorEnd();
                            break;

    case HtmlTokenId::PARABREAK_ON:
        if( bInPara && HasTextInCurrentPara() )
            EndPara();
        StartPara( true );
        break;

    case HtmlTokenId::PARABREAK_OFF:
        if( bInPara )
            EndPara();
        break;

    case HtmlTokenId::HEAD1_ON:
    case HtmlTokenId::HEAD2_ON:
    case HtmlTokenId::HEAD3_ON:
    case HtmlTokenId::HEAD4_ON:
    case HtmlTokenId::HEAD5_ON:
    case HtmlTokenId::HEAD6_ON:
    {
        HeadingStart( nToken );
    }
    break;

    case HtmlTokenId::HEAD1_OFF:
    case HtmlTokenId::HEAD2_OFF:
    case HtmlTokenId::HEAD3_OFF:
    case HtmlTokenId::HEAD4_OFF:
    case HtmlTokenId::HEAD5_OFF:
    case HtmlTokenId::HEAD6_OFF:
    {
        HeadingEnd();
    }
    break;

    case HtmlTokenId::PREFORMTXT_ON:
    case HtmlTokenId::XMP_ON:
    case HtmlTokenId::LISTING_ON:
    {
        StartPara( true );
        ImpSetStyleSheet( STYLE_PRE );
    }
    break;

    case HtmlTokenId::DEFLIST_ON:
    {
        nDefListLevel++;
    }
    break;

    case HtmlTokenId::DEFLIST_OFF:
    {
        if( nDefListLevel )
            nDefListLevel--;
    }
    break;

    case HtmlTokenId::TABLE_ON:     nInTable++;
                            break;
    case HtmlTokenId::TABLE_OFF:    DBG_ASSERT( nInTable, "Not in Table, but TABLE_OFF?" );
                            nInTable--;
                            break;

    case HtmlTokenId::TABLEHEADER_ON:
    case HtmlTokenId::TABLEDATA_ON:
        nInCell++;
        Newline();
    break;

    case HtmlTokenId::DIVISION_ON:
    case HtmlTokenId::DIVISION_OFF:
    {
        mbNewBlockNeeded = true;
        break;
    }

    case HtmlTokenId::BLOCKQUOTE_ON:
    case HtmlTokenId::BLOCKQUOTE_OFF:
    case HtmlTokenId::BLOCKQUOTE30_ON:
    case HtmlTokenId::BLOCKQUOTE30_OFF:
    case HtmlTokenId::LISTHEADER_ON:
    case HtmlTokenId::LI_ON:
    case HtmlTokenId::DD_ON:
    case HtmlTokenId::DT_ON:
    case HtmlTokenId::ORDERLIST_ON:
    case HtmlTokenId::UNORDERLIST_ON:
        Newline();
    break;

    case HtmlTokenId::TABLEHEADER_OFF:
    case HtmlTokenId::TABLEDATA_OFF:
        if ( nInCell )
            nInCell--;
        EndPara();
    break;
    case HtmlTokenId::LISTHEADER_OFF:
    case HtmlTokenId::LI_OFF:
    case HtmlTokenId::DD_OFF:
    case HtmlTokenId::DT_OFF:
    case HtmlTokenId::ORDERLIST_OFF:
    case HtmlTokenId::UNORDERLIST_OFF:
        EndPara();
    break;

    case HtmlTokenId::TABLEROW_ON:
    case HtmlTokenId::TABLEROW_OFF: // A RETURN only after a CELL, for Calc

    case HtmlTokenId::COL_ON:
    case HtmlTokenId::COLGROUP_ON:
    case HtmlTokenId::COLGROUP_OFF: break;

    case HtmlTokenId::FONT_ON:
                            break;
    case HtmlTokenId::FONT_OFF:
                            break;

    case HtmlTokenId::TITLE_ON:
        bInTitle = true;
        break;
    case HtmlTokenId::TITLE_OFF:
        bInTitle = false;
        break;

    // globals
    case HtmlTokenId::HTML_ON:
    case HtmlTokenId::HTML_OFF:
    case HtmlTokenId::STYLE_ON:
    case HtmlTokenId::STYLE_OFF:
    case HtmlTokenId::BODY_ON:
    case HtmlTokenId::BODY_OFF:
    case HtmlTokenId::HEAD_ON:
    case HtmlTokenId::HEAD_OFF:
    case HtmlTokenId::FORM_ON:
    case HtmlTokenId::FORM_OFF:
    case HtmlTokenId::THEAD_ON:
    case HtmlTokenId::THEAD_OFF:
    case HtmlTokenId::TBODY_ON:
    case HtmlTokenId::TBODY_OFF:
    // inline elements, structural markup
    // HTML 3.0
    case HtmlTokenId::BANNER_ON:
    case HtmlTokenId::BANNER_OFF:
//  case HtmlTokenId::LISTHEADER_ON:        //! special handling
//  case HtmlTokenId::LISTHEADER_OFF:
    case HtmlTokenId::NOTE_ON:
    case HtmlTokenId::NOTE_OFF:
    // inline elements, logical markup
    // HTML 2.0
    case HtmlTokenId::ADDRESS_ON:
    case HtmlTokenId::ADDRESS_OFF:
//  case HtmlTokenId::BLOCKQUOTE_ON:        //! special handling
//  case HtmlTokenId::BLOCKQUOTE_OFF:
    case HtmlTokenId::CITATION_ON:
    case HtmlTokenId::CITATION_OFF:
    case HtmlTokenId::CODE_ON:
    case HtmlTokenId::CODE_OFF:
    case HtmlTokenId::DEFINSTANCE_ON:
    case HtmlTokenId::DEFINSTANCE_OFF:
    case HtmlTokenId::EMPHASIS_ON:
    case HtmlTokenId::EMPHASIS_OFF:
    case HtmlTokenId::KEYBOARD_ON:
    case HtmlTokenId::KEYBOARD_OFF:
    case HtmlTokenId::SAMPLE_ON:
    case HtmlTokenId::SAMPLE_OFF:
    case HtmlTokenId::STRIKE_ON:
    case HtmlTokenId::STRIKE_OFF:
    case HtmlTokenId::STRONG_ON:
    case HtmlTokenId::STRONG_OFF:
    case HtmlTokenId::VARIABLE_ON:
    case HtmlTokenId::VARIABLE_OFF:
    // HTML 3.0
    case HtmlTokenId::ABBREVIATION_ON:
    case HtmlTokenId::ABBREVIATION_OFF:
    case HtmlTokenId::ACRONYM_ON:
    case HtmlTokenId::ACRONYM_OFF:
    case HtmlTokenId::AUTHOR_ON:
    case HtmlTokenId::AUTHOR_OFF:
//  case HtmlTokenId::BLOCKQUOTE30_ON:      //! special handling
//  case HtmlTokenId::BLOCKQUOTE30_OFF:
    case HtmlTokenId::DELETEDTEXT_ON:
    case HtmlTokenId::DELETEDTEXT_OFF:
    case HtmlTokenId::INSERTEDTEXT_ON:
    case HtmlTokenId::INSERTEDTEXT_OFF:
    case HtmlTokenId::LANGUAGE_ON:
    case HtmlTokenId::LANGUAGE_OFF:
    case HtmlTokenId::PERSON_ON:
    case HtmlTokenId::PERSON_OFF:
    case HtmlTokenId::SHORTQUOTE_ON:
    case HtmlTokenId::SHORTQUOTE_OFF:
    case HtmlTokenId::SUBSCRIPT_ON:
    case HtmlTokenId::SUBSCRIPT_OFF:
    case HtmlTokenId::SUPERSCRIPT_ON:
    case HtmlTokenId::SUPERSCRIPT_OFF:
    // inline elements, visual markup
    // HTML 2.0
    case HtmlTokenId::BOLD_ON:
    case HtmlTokenId::BOLD_OFF:
    case HtmlTokenId::ITALIC_ON:
    case HtmlTokenId::ITALIC_OFF:
    case HtmlTokenId::TELETYPE_ON:
    case HtmlTokenId::TELETYPE_OFF:
    case HtmlTokenId::UNDERLINE_ON:
    case HtmlTokenId::UNDERLINE_OFF:
    // HTML 3.0
    case HtmlTokenId::BIGPRINT_ON:
    case HtmlTokenId::BIGPRINT_OFF:
    case HtmlTokenId::STRIKETHROUGH_ON:
    case HtmlTokenId::STRIKETHROUGH_OFF:
    case HtmlTokenId::SMALLPRINT_ON:
    case HtmlTokenId::SMALLPRINT_OFF:
    // figures
    case HtmlTokenId::FIGURE_ON:
    case HtmlTokenId::FIGURE_OFF:
    case HtmlTokenId::CAPTION_ON:
    case HtmlTokenId::CAPTION_OFF:
    case HtmlTokenId::CREDIT_ON:
    case HtmlTokenId::CREDIT_OFF:
    // misc
    case HtmlTokenId::DIRLIST_ON:
    case HtmlTokenId::DIRLIST_OFF:
    case HtmlTokenId::FOOTNOTE_ON:          //! they land so in the text
    case HtmlTokenId::FOOTNOTE_OFF:
    case HtmlTokenId::MENULIST_ON:
    case HtmlTokenId::MENULIST_OFF:
//  case HtmlTokenId::PLAINTEXT_ON:         //! special handling
//  case HtmlTokenId::PLAINTEXT_OFF:
//  case HtmlTokenId::PREFORMTXT_ON:        //! special handling
//  case HtmlTokenId::PREFORMTXT_OFF:
    case HtmlTokenId::SPAN_ON:
    case HtmlTokenId::SPAN_OFF:
    // obsolete
//  case HtmlTokenId::XMP_ON:               //! special handling
//  case HtmlTokenId::XMP_OFF:
//  case HtmlTokenId::LISTING_ON:           //! special handling
//  case HtmlTokenId::LISTING_OFF:
    // Netscape
    case HtmlTokenId::BLINK_ON:
    case HtmlTokenId::BLINK_OFF:
    case HtmlTokenId::NOBR_ON:
    case HtmlTokenId::NOBR_OFF:
    case HtmlTokenId::NOEMBED_ON:
    case HtmlTokenId::NOEMBED_OFF:
    case HtmlTokenId::NOFRAMES_ON:
    case HtmlTokenId::NOFRAMES_OFF:
    // Internet Explorer
    case HtmlTokenId::MARQUEE_ON:
    case HtmlTokenId::MARQUEE_OFF:
//  case HtmlTokenId::PLAINTEXT2_ON:        //! special handling
//  case HtmlTokenId::PLAINTEXT2_OFF:
    break;

    default:
    {
        if ( nToken >= HtmlTokenId::ONOFF_START )
        {
            if ( ( nToken == HtmlTokenId::UNKNOWNCONTROL_ON ) || ( nToken == HtmlTokenId::UNKNOWNCONTROL_OFF ) )
            {
                ;
            }
            else if ( !isOffToken(nToken) )
            {
                DBG_ASSERT( !isOffToken( nToken ), "No Start-Token ?!" );
                SkipGroup( static_cast<HtmlTokenId>(static_cast<int>(nToken) + 1) );
            }
        }
    }
    }   // SWITCH

    if (mpEditEngine->IsHtmlImportHandlerSet())
    {
        HtmlImportInfo aImportInfo(HtmlImportState::NextToken, this, mpEditEngine->CreateESelection(aCurSel));
        aImportInfo.nToken = nToken;
        if ( nToken == HtmlTokenId::TEXTTOKEN )
            aImportInfo.aText = aToken;
        else if (nToken == HtmlTokenId::STYLE_OFF)
            aImportInfo.aText = maStyleSource.makeStringAndClear();
        mpEditEngine->CallHtmlImportHandler(aImportInfo);
    }

}

void EditHTMLParser::ImpInsertParaBreak()
{
    if (mpEditEngine->IsHtmlImportHandlerSet())
    {
        HtmlImportInfo aImportInfo(HtmlImportState::InsertPara, this, mpEditEngine->CreateESelection(aCurSel));
        mpEditEngine->CallHtmlImportHandler(aImportInfo);
    }
    aCurSel = mpEditEngine->InsertParaBreak(aCurSel);
    mbNewBlockNeeded = false;
}

void EditHTMLParser::ImpSetAttribs( const SfxItemSet& rItems )
{
    // pSel, when character attributes, otherwise paragraph attributes for
    // the current paragraph.
    DBG_ASSERT( aCurSel.Min().GetNode() == aCurSel.Max().GetNode(), "ImpInsertAttribs: Selection?" );

    EditPaM aStartPaM( aCurSel.Min() );
    EditPaM aEndPaM( aCurSel.Max() );

    aStartPaM.SetIndex( 0 );
    aEndPaM.SetIndex( aEndPaM.GetNode()->Len() );

    if (mpEditEngine->IsHtmlImportHandlerSet())
    {
        EditSelection aSel( aStartPaM, aEndPaM );
        HtmlImportInfo aImportInfo(HtmlImportState::SetAttr, this, mpEditEngine->CreateESelection(aSel));
        mpEditEngine->CallHtmlImportHandler(aImportInfo);
    }

    ContentNode* pSN = aStartPaM.GetNode();
    sal_Int32 nStartNode = mpEditEngine->GetEditDoc().GetPos( pSN );

    // If an attribute goes from 0 to current Paragraph length,
    // then it should be a paragraph attribute!

    // Note: Selection can reach over several Paragraphs.
    // All complete paragraphs are paragraph attributes ...

    // not really HTML:
#ifdef DBG_UTIL
    ContentNode* pEN = aEndPaM.GetNode();
    sal_Int32 nEndNode = mpEditEngine->GetEditDoc().GetPos( pEN );
    DBG_ASSERT( nStartNode == nEndNode, "ImpSetAttribs: Several paragraphs?" );
#endif

    if ( ( aStartPaM.GetIndex() == 0 ) && ( aEndPaM.GetIndex() == aEndPaM.GetNode()->Len() ) )
    {
        // Has to be merged:
        SfxItemSet aItems = mpEditEngine->GetBaseParaAttribs(nStartNode);
        aItems.Put( rItems );
        mpEditEngine->SetParaAttribsOnly(nStartNode, aItems);
    }
    else
        mpEditEngine->SetAttribs( EditSelection( aStartPaM, aEndPaM ), rItems );
}

void EditHTMLParser::ImpSetStyleSheet( sal_uInt16 nHLevel )
{
    /*
        nHLevel:    0:          Turn off
                    1-6:        Heading
                    STYLE_PRE:  Preformatted
    */
    // Create hard attributes ...
    // Enough for Calc, would have to be clarified with StyleSheets
    // that they should also be in the app so that when they are feed
    // in a different engine still are here ...
    sal_Int32 nNode = mpEditEngine->GetEditDoc().GetPos( aCurSel.Max().GetNode() );

    SfxItemSet aItems( aCurSel.Max().GetNode()->GetContentAttribs().GetItems() );

    aItems.ClearItem( EE_PARA_ULSPACE );

    aItems.ClearItem( EE_CHAR_FONTHEIGHT );
    aItems.ClearItem( EE_CHAR_FONTINFO );
    aItems.ClearItem( EE_CHAR_WEIGHT );

    aItems.ClearItem( EE_CHAR_FONTHEIGHT_CJK );
    aItems.ClearItem( EE_CHAR_FONTINFO_CJK );
    aItems.ClearItem( EE_CHAR_WEIGHT_CJK );

    aItems.ClearItem( EE_CHAR_FONTHEIGHT_CTL );
    aItems.ClearItem( EE_CHAR_FONTINFO_CTL );
    aItems.ClearItem( EE_CHAR_WEIGHT_CTL );

    // Bold in the first 3 Headings
    if ( ( nHLevel >= 1 ) && ( nHLevel <= 3 ) )
    {
        SvxWeightItem aWeightItem( WEIGHT_BOLD, EE_CHAR_WEIGHT );
        aItems.Put( aWeightItem );

        SvxWeightItem aWeightItemCJK( WEIGHT_BOLD, EE_CHAR_WEIGHT_CJK );
        aItems.Put( aWeightItemCJK );

        SvxWeightItem aWeightItemCTL( WEIGHT_BOLD, EE_CHAR_WEIGHT_CTL );
        aItems.Put( aWeightItemCTL );
    }

    // Font height and margins, when LogicToLogic is possible:
    MapUnit eUnit = mpEditEngine->GetRefMapMode().GetMapUnit();
    if ( ( eUnit != MapUnit::MapPixel ) && ( eUnit != MapUnit::MapSysFont ) &&
         ( eUnit != MapUnit::MapAppFont ) && ( eUnit != MapUnit::MapRelative ) )
    {
        tools::Long nPoints = 10;
        if ( nHLevel == 1 )
            nPoints = 22;
        else if ( nHLevel == 2 )
            nPoints = 16;
        else if ( nHLevel == 3 )
            nPoints = 12;
        else if ( nHLevel == 4 )
            nPoints = 11;

        nPoints = OutputDevice::LogicToLogic( nPoints, MapUnit::MapPoint, eUnit );

        SvxFontHeightItem aHeightItem( nPoints, 100, EE_CHAR_FONTHEIGHT );
        aItems.Put( aHeightItem );

        SvxFontHeightItem aHeightItemCJK( nPoints, 100, EE_CHAR_FONTHEIGHT_CJK );
        aItems.Put( aHeightItemCJK );

        SvxFontHeightItem aHeightItemCTL( nPoints, 100, EE_CHAR_FONTHEIGHT_CTL );
        aItems.Put( aHeightItemCTL );

        // Paragraph margins, when Heading:
        if (nHLevel <= 6)
        {
            SvxULSpaceItem aULSpaceItem( EE_PARA_ULSPACE );
            aULSpaceItem.SetUpper( static_cast<sal_uInt16>(OutputDevice::LogicToLogic( 42, MapUnit::Map10thMM, eUnit )) );
            aULSpaceItem.SetLower( static_cast<sal_uInt16>(OutputDevice::LogicToLogic( 35, MapUnit::Map10thMM, eUnit )) );
            aItems.Put( aULSpaceItem );
        }
    }

    // Choose a proportional Font for Pre
    if ( nHLevel == STYLE_PRE )
    {
        vcl::Font aFont = OutputDevice::GetDefaultFont( DefaultFontType::FIXED, LANGUAGE_SYSTEM, GetDefaultFontFlags::NONE );
        SvxFontItem aFontItem( aFont.GetFamilyTypeMaybeAskConfig(), aFont.GetFamilyName(), OUString(), aFont.GetPitchMaybeAskConfig(), aFont.GetCharSet(), EE_CHAR_FONTINFO );
        aItems.Put( aFontItem );

        SvxFontItem aFontItemCJK( aFont.GetFamilyTypeMaybeAskConfig(), aFont.GetFamilyName(), OUString(), aFont.GetPitchMaybeAskConfig(), aFont.GetCharSet(), EE_CHAR_FONTINFO_CJK );
        aItems.Put( aFontItemCJK );

        SvxFontItem aFontItemCTL( aFont.GetFamilyTypeMaybeAskConfig(), aFont.GetFamilyName(), OUString(), aFont.GetPitchMaybeAskConfig(), aFont.GetCharSet(), EE_CHAR_FONTINFO_CTL );
        aItems.Put( aFontItemCTL );
    }

    mpEditEngine->SetParaAttribsOnly(nNode, aItems);
}

void EditHTMLParser::ImpInsertText( const OUString& rText )
{
    if (mbNewBlockNeeded)
    {
        if (mbBreakForDivs)
            Newline();
        mbNewBlockNeeded = false;
    }

    if (mpEditEngine->IsHtmlImportHandlerSet())
    {
        HtmlImportInfo aImportInfo(HtmlImportState::InsertText, this, mpEditEngine->CreateESelection(aCurSel));
        aImportInfo.aText = rText;
        mpEditEngine->CallHtmlImportHandler(aImportInfo);
    }

    aCurSel = mpEditEngine->InsertText(aCurSel, rText);
}

void EditHTMLParser::SkipGroup( HtmlTokenId nEndToken )
{
    // groups in cells are closed upon leaving the cell, because those
    // ******* web authors don't know their job
    // for example: <td><form></td>   lacks a closing </form>
    sal_uInt8 nCellLevel = nInCell;
    HtmlTokenId nToken;
    while( nCellLevel <= nInCell )
    {
        nToken = GetNextToken();
        if (nToken == nEndToken || nToken == HtmlTokenId::NONE)
            break;
        switch ( nToken )
        {
            case HtmlTokenId::TABLEHEADER_ON:
            case HtmlTokenId::TABLEDATA_ON:
                nInCell++;
            break;
            case HtmlTokenId::TABLEHEADER_OFF:
            case HtmlTokenId::TABLEDATA_OFF:
                if ( nInCell )
                    nInCell--;
            break;
            default: break;
        }
    }
}

void EditHTMLParser::StartPara( bool bReal )
{
    if ( bReal )
    {
        const HTMLOptions& aOptions = GetOptions();
        SvxAdjust eAdjust = SvxAdjust::Left;
        for (const auto & aOption : aOptions)
        {
            if( aOption.GetToken() == HtmlOptionId::ALIGN )
            {
                OUString const& rTmp(aOption.GetString());
                if (rTmp.equalsIgnoreAsciiCase(OOO_STRING_SVTOOLS_HTML_AL_right))
                    eAdjust = SvxAdjust::Right;
                else if (rTmp.equalsIgnoreAsciiCase(OOO_STRING_SVTOOLS_HTML_AL_middle))
                    eAdjust = SvxAdjust::Center;
                else if (rTmp.equalsIgnoreAsciiCase(OOO_STRING_SVTOOLS_HTML_AL_center))
                    eAdjust = SvxAdjust::Center;
                else
                    eAdjust = SvxAdjust::Left;
            }
        }
        SfxItemSet aItemSet = mpEditEngine->GetEmptyItemSet();
        aItemSet.Put( SvxAdjustItem( eAdjust, EE_PARA_JUST ) );
        ImpSetAttribs( aItemSet );
    }
    bInPara = true;
}

void EditHTMLParser::EndPara()
{
    if ( bInPara )
    {
        bool bHasText = HasTextInCurrentPara();
        if ( bHasText )
            ImpInsertParaBreak();
    }
    bInPara = false;
}

bool EditHTMLParser::ThrowAwayBlank()
{
    // A blank must be thrown away if the new text begins with a Blank and
    // if the current paragraph is empty or ends with a Blank...
    ContentNode* pNode = aCurSel.Max().GetNode();
    return !(pNode->Len() && ( pNode->GetChar( pNode->Len()-1 ) != ' ' ));
}

bool EditHTMLParser::HasTextInCurrentPara()
{
    return aCurSel.Max().GetNode()->Len() != 0;
}

void EditHTMLParser::AnchorStart()
{
    // ignore anchor in anchor
    if ( moCurAnchor )
        return;

    const HTMLOptions& aOptions = GetOptions();
    OUString aRef;

    for (const auto & aOption : aOptions)
    {
        if( aOption.GetToken() == HtmlOptionId::HREF)
            aRef = aOption.GetString();
    }

    if ( aRef.isEmpty() )
        return;

    OUString aURL = aRef;
    if ( !aURL.isEmpty() && ( aURL[ 0 ] != '#' ) )
    {
        INetURLObject aTargetURL;
        INetURLObject aRootURL( aBaseURL );
        aRootURL.GetNewAbsURL( aRef, &aTargetURL );
        aURL = aTargetURL.GetMainURL( INetURLObject::DecodeMechanism::ToIUri );
    }
    moCurAnchor.emplace();
    moCurAnchor->aHRef = aURL;
}

void EditHTMLParser::AnchorEnd()
{
    if ( !moCurAnchor )
        return;

    // Insert as URL-Field...
    SvxFieldItem aFld( SvxURLField( moCurAnchor->aHRef, moCurAnchor->aText, SvxURLFormat::Repr ), EE_FEATURE_FIELD  );
    aCurSel = mpEditEngine->InsertField(aCurSel, aFld);
    bFieldsInserted = true;
    moCurAnchor.reset();

    if (mpEditEngine->IsHtmlImportHandlerSet())
    {
        HtmlImportInfo aImportInfo(HtmlImportState::InsertField, this, mpEditEngine->CreateESelection(aCurSel));
        mpEditEngine->CallHtmlImportHandler(aImportInfo);
    }
}

void EditHTMLParser::HeadingStart( HtmlTokenId nToken )
{
    bWasInPara = bInPara;
    StartPara( false );

    if ( bWasInPara && HasTextInCurrentPara() )
        ImpInsertParaBreak();

    sal_uInt16 nId = sal::static_int_cast< sal_uInt16 >(
        1 + ( ( static_cast<int>(nToken) - int(HtmlTokenId::HEAD1_ON) ) / 2 ) );
    DBG_ASSERT( (nId >= 1) && (nId <= 9), "HeadingStart: ID can not be correct!" );
    ImpSetStyleSheet( nId );
}

void EditHTMLParser::HeadingEnd()
{
    EndPara();
    ImpSetStyleSheet( 0 );

    if ( bWasInPara )
    {
        bInPara = true;
        bWasInPara = false;
    }
}

/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
