/* -*- 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 <o3tl/string_view.hxx>
#include <o3tl/unit_conversion.hxx>
#include <osl/diagnose.h>
#include <rtl/character.hxx>
#include <rtl/ustrbuf.hxx>
#include <tools/color.hxx>
#include <tools/solar.h>
#include <svtools/htmltokn.h>
#include <comphelper/string.hxx>
#include "parcss1.hxx"
 
// Loop-Check: Used to avoid infinite loops, is checked after every
// loop, if there is progress of the input position
#define LOOP_CHECK
 
#ifdef LOOP_CHECK
 
#define LOOP_CHECK_DECL \
    sal_Int32 nOldInPos = SAL_MAX_INT32;
#define LOOP_CHECK_RESTART \
    nOldInPos = SAL_MAX_INT32;
#define LOOP_CHECK_CHECK( where ) \
    OSL_ENSURE( nOldInPos!=m_nInPos || m_cNextCh==sal_Unicode(EOF), where );    \
    if( nOldInPos==m_nInPos && m_cNextCh!=sal_Unicode(EOF) )                    \
        break;                                                              \
    else                                                                    \
        nOldInPos = m_nInPos;
 
#else
 
#define LOOP_CHECK_DECL
#define LOOP_CHECK_RESTART
#define LOOP_CHECK_CHECK( where )
 
#endif
 
const sal_Int32 MAX_LEN = 1024;
 
void CSS1Parser::InitRead( const OUString& rIn )
{
    m_nlLineNr = 0;
    m_nlLinePos = 0;
 
    m_bWhiteSpace = true; // if nothing was read it's like there was WS
    m_bEOF = false;
    m_eState = CSS1_PAR_WORKING;
    m_nValue = 0.;
 
    m_aIn = rIn;
    m_nInPos = 0;
    m_cNextCh = GetNextChar();
    m_nToken = GetNextToken();
}
 
sal_Unicode CSS1Parser::GetNextChar()
{
    if( m_nInPos >= m_aIn.getLength() )
    {
        m_bEOF = true;
        return sal_Unicode(EOF);
    }
 
    sal_Unicode c = m_aIn[m_nInPos];
    m_nInPos++;
 
    if( c == '\n' )
    {
        ++m_nlLineNr;
        m_nlLinePos = 1;
    }
    else
        ++m_nlLinePos;
 
    return c;
}
 
// This function implements the scanner described in
 
//       http://www.w3.org/pub/WWW/TR/WD-css1.html
// resp. http://www.w3.org/pub/WWW/TR/WD-css1-960220.html
 
// for CSS1. It's a direct implementation of the
// described Lex grammar.
 
CSS1Token CSS1Parser::GetNextToken()
{
    CSS1Token nRet = CSS1_NULL;
    m_aToken.clear();
 
    do {
        // remember if white space was read
        bool bPrevWhiteSpace = m_bWhiteSpace;
        m_bWhiteSpace = false;
 
        bool bNextCh = true;
        switch( m_cNextCh )
        {
        case '/': // COMMENT | '/'
            {
                m_cNextCh = GetNextChar();
                if( '*' == m_cNextCh )
                {
                    // COMMENT
                    m_cNextCh = GetNextChar();
 
                    bool bAsterisk = false;
                    while( !(bAsterisk && '/'==m_cNextCh) && !IsEOF() )
                    {
                        bAsterisk = ('*'==m_cNextCh);
                        m_cNextCh = GetNextChar();
                    }
                }
                else
                {
                    // '/'
                    bNextCh = false;
                    nRet = CSS1_SLASH;
                }
            }
            break;
 
        case '@': // '@import' | '@XXX'
            {
                m_cNextCh = GetNextChar();
                if (rtl::isAsciiAlpha(m_cNextCh))
                {
                    // scan the next identifier
                    OUStringBuffer sTmpBuffer(32);
                    do {
                        sTmpBuffer.append( m_cNextCh );
                        m_cNextCh = GetNextChar();
                    } while( (rtl::isAsciiAlphanumeric(m_cNextCh) ||
                             '-' == m_cNextCh) && !IsEOF() );
 
                    m_aToken += sTmpBuffer;
 
                    // check if we know it
                    switch( m_aToken[0] )
                    {
                    case 'i':
                    case 'I':
                        if( m_aToken.equalsIgnoreAsciiCase( "import" ) )
                            nRet = CSS1_IMPORT_SYM;
                        break;
                    case 'p':
                    case 'P':
                        if( m_aToken.equalsIgnoreAsciiCase( "page" ) )
                            nRet = CSS1_PAGE_SYM;
                        break;
                    }
 
                    // error handling: ignore '@indent' and the rest until
                    // semicolon at end of the next block
                    if( CSS1_NULL==nRet )
                    {
                        m_aToken.clear();
                        int nBlockLvl = 0;
                        sal_Unicode cQuoteCh = 0;
                        bool bDone = false, bEscape = false;
                        while( !bDone && !IsEOF() )
                        {
                            bool bOldEscape = bEscape;
                            bEscape = false;
                            switch( m_cNextCh )
                            {
                            case '{':
                                if( !cQuoteCh && !bOldEscape )
                                    nBlockLvl++;
                                break;
                            case ';':
                                if( !cQuoteCh && !bOldEscape )
                                    bDone = nBlockLvl==0;
                                break;
                            case '}':
                                if( !cQuoteCh && !bOldEscape )
                                    bDone = --nBlockLvl==0;
                                break;
                            case '\"':
                            case '\'':
                                if( !bOldEscape )
                                {
                                    if( cQuoteCh )
                                    {
                                        if( cQuoteCh == m_cNextCh )
                                            cQuoteCh = 0;
                                    }
                                    else
                                    {
                                        cQuoteCh = m_cNextCh;
                                    }
                                }
                                break;
                            case '\\':
                                if( !bOldEscape )
                                    bEscape = true;
                                break;
                            }
                            m_cNextCh = GetNextChar();
                        }
                    }
 
                    bNextCh = false;
                }
            }
            break;
 
        case '!': // '!' 'legal' | '!' 'important' | syntax error
            {
                // ignore white space
                m_cNextCh = GetNextChar();
                while( ( ' ' == m_cNextCh ||
                       (m_cNextCh >= 0x09 && m_cNextCh <= 0x0d) ) && !IsEOF() )
                {
                    m_bWhiteSpace = true;
                    m_cNextCh = GetNextChar();
                }
 
                if( 'i'==m_cNextCh || 'I'==m_cNextCh)
                {
                    // scan next identifier
                    OUStringBuffer sTmpBuffer(32);
                    do {
                        sTmpBuffer.append( m_cNextCh );
                        m_cNextCh = GetNextChar();
                    } while( (rtl::isAsciiAlphanumeric(m_cNextCh) ||
                             '-' == m_cNextCh) && !IsEOF() );
 
                    m_aToken += sTmpBuffer;
 
                    if( ( 'i'==m_aToken[0] || 'I'==m_aToken[0] ) &&
                        m_aToken.equalsIgnoreAsciiCase( "important" ) )
                    {
                        // '!' 'important'
                        nRet = CSS1_IMPORTANT_SYM;
                    }
                    else
                    {
                        // error handling: ignore '!', not IDENT
                        nRet = CSS1_IDENT;
                    }
 
                    m_bWhiteSpace = false;
                    bNextCh = false;
                }
                else
                {
                    // error handling: ignore '!'
                    bNextCh = false;
                }
            }
            break;
 
        case '\"':
        case '\'': // STRING
            {
                // \... isn't possible yet!!!
                sal_Unicode cQuoteChar = m_cNextCh;
                m_cNextCh = GetNextChar();
 
                OUStringBuffer sTmpBuffer( MAX_LEN );
                do {
                    sTmpBuffer.append( m_cNextCh );
                    m_cNextCh = GetNextChar();
                } while( cQuoteChar != m_cNextCh && !IsEOF() );
 
                m_aToken += sTmpBuffer;
 
                nRet = CSS1_STRING;
            }
            break;
 
        case '0':
        case '1':
        case '2':
        case '3':
        case '4':
        case '5':
        case '6':
        case '7':
        case '8':
        case '9': // NUMBER | PERCENTAGE | LENGTH
            {
                // save current position
                std::size_t nInPosSave = m_nInPos;
                sal_Unicode cNextChSave = m_cNextCh;
                sal_uInt32 nlLineNrSave = m_nlLineNr;
                sal_uInt32 nlLinePosSave = m_nlLinePos;
                bool bEOFSave = m_bEOF;
 
                // first try to parse a hex digit
                OUStringBuffer sTmpBuffer( 16 );
                do {
                    sTmpBuffer.append( m_cNextCh );
                    m_cNextCh = GetNextChar();
                } while( sTmpBuffer.getLength() < 7 &&
                         ( ('0'<=m_cNextCh && '9'>=m_cNextCh) ||
                           ('A'<=m_cNextCh && 'F'>=m_cNextCh) ||
                           ('a'<=m_cNextCh && 'f'>=m_cNextCh) ) &&
                         !IsEOF() );
 
                if( sTmpBuffer.getLength()==6 )
                {
                    // we found a color in hex
                    m_aToken += sTmpBuffer;
                    nRet = CSS1_HEXCOLOR;
                    bNextCh = false;
 
                    break;
                }
 
                // otherwise we try a number
                m_nInPos = nInPosSave;
                m_cNextCh = cNextChSave;
                m_nlLineNr = nlLineNrSave;
                m_nlLinePos = nlLinePosSave;
                m_bEOF = bEOFSave;
 
                // first parse the number
                sTmpBuffer.setLength( 0 );
                do {
                    sTmpBuffer.append( m_cNextCh );
                    m_cNextCh = GetNextChar();
                } while( (('0'<=m_cNextCh && '9'>=m_cNextCh) || '.'==m_cNextCh) &&
                         !IsEOF() );
 
                m_aToken += sTmpBuffer;
                m_nValue = m_aToken.toDouble();
 
                // ignore white space
                while( ( ' ' == m_cNextCh ||
                       (m_cNextCh >= 0x09 && m_cNextCh <= 0x0d) ) && !IsEOF() )
                {
                    m_bWhiteSpace = true;
                    m_cNextCh = GetNextChar();
                }
 
                // check now, of there is a unit
                switch( m_cNextCh )
                {
                case '%': // PERCENTAGE
                    m_bWhiteSpace = false;
                    nRet = CSS1_PERCENTAGE;
                    break;
 
                case 'c':
                case 'C': // LENGTH cm | LENGTH IDENT
                case 'e':
                case 'E': // LENGTH (em | ex) | LENGTH IDENT
                case 'i':
                case 'I': // LENGTH inch | LENGTH IDENT
                case 'p':
                case 'P': // LENGTH (pt | px | pc) | LENGTH IDENT
                case 'm':
                case 'M': // LENGTH mm | LENGTH IDENT
                    {
                        // save current position
                        sal_Int32 nInPosOld = m_nInPos;
                        sal_Unicode cNextChOld = m_cNextCh;
                        sal_uInt32 nlLineNrOld  = m_nlLineNr;
                        sal_uInt32 nlLinePosOld = m_nlLinePos;
                        bool bEOFOld = m_bEOF;
 
                        // parse the next identifier
                        OUStringBuffer sTmpBuffer2(64);
                        do {
                            sTmpBuffer2.append(static_cast<sal_Unicode>(rtl::toAsciiLowerCase(m_cNextCh)));
                            m_cNextCh = GetNextChar();
                        } while( (rtl::isAsciiAlphanumeric(m_cNextCh) ||
                                 '-' == m_cNextCh) && !IsEOF() );
 
                        OUString aIdent = sTmpBuffer2.makeStringAndClear();
                        nRet = CSS1_NUMBER;
 
                        // Is it a unit?
                        switch( aIdent[0] )
                        {
                        case 'c':
                            if (aIdent == "cm")
                            {
                                m_nValue = o3tl::convert(m_nValue, o3tl::Length::cm, o3tl::Length::twip);
                                nRet = CSS1_LENGTH;
                            }
                            break;
                        case 'e':
                            if (aIdent == "em")
                                nRet = CSS1_EMS;
                            else if (aIdent == "ex")
                                nRet = CSS1_EMX;
                            break;
                        case 'i':
                            if (aIdent == "in")
                            {
                                nRet = CSS1_LENGTH;
                                m_nValue = o3tl::convert(m_nValue, o3tl::Length::in, o3tl::Length::twip);
                            }
                            break;
                        case 'm':
                            if (aIdent == "mm")
                            {
                                nRet = CSS1_LENGTH;
                                m_nValue = o3tl::convert(m_nValue, o3tl::Length::mm, o3tl::Length::twip);
                            }
                            break;
                        case 'p':
                            if (aIdent == "pt")
                            {
                                nRet = CSS1_LENGTH;
                                m_nValue = o3tl::convert(m_nValue, o3tl::Length::pt, o3tl::Length::twip);
                            }
                            else if (aIdent == "pc")
                            {
                                nRet = CSS1_LENGTH;
                                m_nValue = o3tl::convert(m_nValue, o3tl::Length::pc, o3tl::Length::twip);
                            }
                            else if (aIdent == "px")
                                nRet = CSS1_PIXLENGTH;
                            break;
                        }
 
                        if( nRet == CSS1_NUMBER )
                        {
                            m_nInPos = nInPosOld;
                            m_cNextCh = cNextChOld;
                            m_nlLineNr = nlLineNrOld;
                            m_nlLinePos = nlLinePosOld;
                            m_bEOF = bEOFOld;
                        }
                        else
                        {
                            m_bWhiteSpace = false;
                        }
                        bNextCh = false;
                    }
                    break;
                default: // NUMBER IDENT
                    bNextCh = false;
                    nRet = CSS1_NUMBER;
                    break;
                }
            }
            break;
 
        case ':': // ':'
            // catch link/visited/active !!!
            nRet = CSS1_COLON;
            break;
 
        case '.': // DOT_W_WS | DOT_WO_WS
            nRet = bPrevWhiteSpace ? CSS1_DOT_W_WS : CSS1_DOT_WO_WS;
            break;
 
        case '+': // '+'
            nRet = CSS1_PLUS;
            break;
 
        case '-': // '-'
            nRet = CSS1_MINUS;
            break;
 
        case '{': // '{'
            nRet = CSS1_OBRACE;
            break;
 
        case '}': // '}'
            nRet = CSS1_CBRACE;
            break;
 
        case ';': // ';'
            nRet = CSS1_SEMICOLON;
            break;
 
        case ',': // ','
            nRet = CSS1_COMMA;
            break;
 
        case '#': // '#'
            m_cNextCh = GetNextChar();
            if( ('0'<=m_cNextCh && '9'>=m_cNextCh) ||
                ('a'<=m_cNextCh && 'f'>=m_cNextCh) ||
                ('A'<=m_cNextCh && 'F'>=m_cNextCh) )
            {
                // save current position
                sal_Int32 nInPosSave = m_nInPos;
                sal_Unicode cNextChSave = m_cNextCh;
                sal_uInt32 nlLineNrSave = m_nlLineNr;
                sal_uInt32 nlLinePosSave = m_nlLinePos;
                bool bEOFSave = m_bEOF;
 
                // first try to parse a hex digit
                OUStringBuffer sTmpBuffer(8);
                do {
                    sTmpBuffer.append( m_cNextCh );
                    m_cNextCh = GetNextChar();
                } while( sTmpBuffer.getLength() < 9 &&
                         ( ('0'<=m_cNextCh && '9'>=m_cNextCh) ||
                           ('A'<=m_cNextCh && 'F'>=m_cNextCh) ||
                           ('a'<=m_cNextCh && 'f'>=m_cNextCh) ) &&
                         !IsEOF() );
 
                if( sTmpBuffer.getLength()==6 || sTmpBuffer.getLength()==3 )
                {
                    // we found a color in hex (RGB)
                    m_aToken += sTmpBuffer;
                    nRet = CSS1_HEXCOLOR;
                    bNextCh = false;
 
                    break;
                }
 
                if( sTmpBuffer.getLength()==8 )
                {
                    // we found a color in hex (RGBA)
                    // we convert it to RGB assuming white background
                    sal_uInt32 nColor = sTmpBuffer.makeStringAndClear().toUInt32(16);
                    sal_uInt32 nRed = (nColor & 0xff000000) >> 24;
                    sal_uInt32 nGreen = (nColor & 0xff0000) >> 16;
                    sal_uInt32 nBlue = (nColor & 0xff00) >> 8;
                    double nAlpha = (nColor & 0xff) / 255.0;
                    nRed = (1 - nAlpha) * 255 + nAlpha * nRed;
                    nGreen = (1 - nAlpha) * 255 + nAlpha * nGreen;
                    nBlue = (1 - nAlpha) * 255 + nAlpha * nBlue;
                    nColor = (nRed << 16) + (nGreen << 8) + nBlue;
                    m_aToken += OUString::number(nColor, 16);
                    nRet = CSS1_HEXCOLOR;
                    bNextCh = false;
 
                    break;
                }
 
                // otherwise we try a number
                m_nInPos = nInPosSave;
                m_cNextCh = cNextChSave;
                m_nlLineNr = nlLineNrSave;
                m_nlLinePos = nlLinePosSave;
                m_bEOF = bEOFSave;
            }
 
            nRet = CSS1_HASH;
            bNextCh = false;
            break;
 
        case ' ':
        case '\t':
        case '\r':
        case '\n': // White-Space
            m_bWhiteSpace = true;
            break;
 
        case sal_Unicode(EOF):
            if( IsEOF() )
            {
                m_eState = CSS1_PAR_ACCEPTED;
                bNextCh = false;
                break;
            }
            [[fallthrough]];
 
        default: // IDENT | syntax error
            if (rtl::isAsciiAlpha(m_cNextCh))
            {
                // IDENT
 
                bool bHexColor = true;
 
                // parse the next identifier
                OUStringBuffer sTmpBuffer(64);
                do {
                    sTmpBuffer.append( m_cNextCh );
                    if( bHexColor )
                    {
                        bHexColor =  sTmpBuffer.getLength()<7 &&
                                     ( ('0'<=m_cNextCh && '9'>=m_cNextCh) ||
                                       ('A'<=m_cNextCh && 'F'>=m_cNextCh) ||
                                       ('a'<=m_cNextCh && 'f'>=m_cNextCh) );
                    }
                    m_cNextCh = GetNextChar();
                } while( (rtl::isAsciiAlphanumeric(m_cNextCh) ||
                           '-' == m_cNextCh) && !IsEOF() );
 
                m_aToken += sTmpBuffer;
 
                if( bHexColor && sTmpBuffer.getLength()==6 )
                {
                    bNextCh = false;
                    nRet = CSS1_HEXCOLOR;
 
                    break;
                }
                if( '('==m_cNextCh &&
                    ( (('u'==m_aToken[0] || 'U'==m_aToken[0]) &&
                       m_aToken.equalsIgnoreAsciiCase( "url" )) ||
                      (('r'==m_aToken[0] || 'R'==m_aToken[0]) &&
                       (m_aToken.equalsIgnoreAsciiCase( "rgb" ) || m_aToken.equalsIgnoreAsciiCase( "rgba" ) )
                  ) ) )
                {
                    int nNestCnt = 0;
                    OUStringBuffer sTmpBuffer2(64);
                    do {
                        sTmpBuffer2.append( m_cNextCh );
                        switch( m_cNextCh )
                        {
                        case '(':   nNestCnt++; break;
                        case ')':   nNestCnt--; break;
                        }
                        m_cNextCh = GetNextChar();
                    } while( (nNestCnt>1 || ')'!=m_cNextCh) && !IsEOF() );
                    sTmpBuffer2.append( m_cNextCh );
                    m_aToken += sTmpBuffer2;
                    bNextCh = true;
                    nRet = 'u'==m_aToken[0] || 'U'==m_aToken[0]
                                ? CSS1_URL
                                : CSS1_RGB;
                }
                else
                {
                    bNextCh = false;
                    nRet = CSS1_IDENT;
                }
            }
            // error handling: ignore digit
            break;
        }
        if( bNextCh )
            m_cNextCh = GetNextChar();
 
    } while( CSS1_NULL==nRet && IsParserWorking() );
 
    return nRet;
}
 
// These functions implement the parser described in
 
//       http://www.w3.org/pub/WWW/TR/WD-css1.html
// resp. http://www.w3.org/pub/WWW/TR/WD-css1-960220.html
 
// for CSS1. It's a direct implementation of the
// described Lex grammar.
 
// stylesheet
//  : import* rule*
 
// import
//  : IMPORT_SYM url
 
// url
//  : STRING
 
void CSS1Parser::ParseStyleSheet()
{
    LOOP_CHECK_DECL
 
    // import*
    bool bDone = false;
    while( !bDone && IsParserWorking() )
    {
        LOOP_CHECK_CHECK( "Infinite loop in ParseStyleSheet()/import *" )
 
        switch( m_nToken )
        {
        case CSS1_IMPORT_SYM:
            // IMPORT_SYM url
            // URL are skipped without checks
            m_nToken = GetNextToken();
            break;
        case CSS1_IDENT:            // Look-Aheads
        case CSS1_DOT_W_WS:
        case CSS1_HASH:
        case CSS1_PAGE_SYM:
            // rule
            bDone = true;
            break;
        default:
            // error handling: ignore
            break;
        }
 
        if( !bDone )
            m_nToken = GetNextToken();
    }
 
    LOOP_CHECK_RESTART
 
    // rule *
    while( IsParserWorking() )
    {
        LOOP_CHECK_CHECK( "Infinite loop in ParseStyleSheet()/rule *" )
 
        switch( m_nToken )
        {
        case CSS1_IDENT:        // Look-Aheads
        case CSS1_DOT_W_WS:
        case CSS1_HASH:
        case CSS1_PAGE_SYM:
            // rule
            ParseRule();
            break;
        default:
            // error handling: ignore
            m_nToken = GetNextToken();
            break;
        }
    }
}
 
// rule
//  : selector [ ',' selector ]*
//    '{' declaration [ ';' declaration ]* '}'
 
void CSS1Parser::ParseRule()
{
    // selector
    std::unique_ptr<CSS1Selector> pSelector = ParseSelector();
    if( !pSelector )
        return;
 
    // process selector
    SelectorParsed( std::move(pSelector), true );
 
    LOOP_CHECK_DECL
 
    // [ ',' selector ]*
    while( CSS1_COMMA==m_nToken && IsParserWorking() )
    {
        LOOP_CHECK_CHECK( "Infinite loop in ParseRule()/selector *" )
 
        // ignore ','
        m_nToken = GetNextToken();
 
        // selector
        pSelector = ParseSelector();
        if( !pSelector )
            return;
 
        // process selector
        SelectorParsed( std::move(pSelector), false );
    }
 
    // '{'
    if( CSS1_OBRACE != m_nToken )
        return;
    m_nToken = GetNextToken();
 
    // declaration
    OUString aProperty;
    std::unique_ptr<CSS1Expression> pExpr = ParseDeclaration( aProperty );
    if( !pExpr )
        return;
 
    // process expression
    DeclarationParsed( aProperty, std::move(pExpr) );
 
    LOOP_CHECK_RESTART
 
    // [ ';' declaration ]*
    while( CSS1_SEMICOLON==m_nToken && IsParserWorking() )
    {
        LOOP_CHECK_CHECK( "Infinite loop in ParseRule()/declaration *" )
 
        // ';'
        m_nToken = GetNextToken();
 
        // declaration
        if( CSS1_IDENT == m_nToken )
        {
            std::unique_ptr<CSS1Expression> pExp = ParseDeclaration( aProperty );
            if( pExp )
            {
                // process expression
                DeclarationParsed( aProperty, std::move(pExp));
            }
        }
    }
 
    // '}'
    if( CSS1_CBRACE == m_nToken )
        m_nToken = GetNextToken();
}
 
// selector
//  : simple_selector+ [ ':' pseudo_element ]?
 
// simple_selector
//  : element_name [ DOT_WO_WS class ]?
//  | DOT_W_WS class
//  | id_selector
 
// element_name
//  : IDENT
 
// class
//  : IDENT
 
// id_selector
//  : '#' IDENT
 
// pseudo_element
//  : IDENT
 
std::unique_ptr<CSS1Selector> CSS1Parser::ParseSelector()
{
    std::unique_ptr<CSS1Selector> pRoot;
    CSS1Selector *pLast = nullptr;
 
    bool bDone = false;
    CSS1Selector *pNew = nullptr;
 
    LOOP_CHECK_DECL
 
    // simple_selector+
    while( !bDone && IsParserWorking() )
    {
        LOOP_CHECK_CHECK( "Infinite loop in ParseSelector()" )
 
        bool bNextToken = true;
 
        switch( m_nToken )
        {
        case CSS1_IDENT:
            {
                // element_name [ DOT_WO_WS class ]?
 
                // element_name
                OUString aElement = m_aToken;
                CSS1SelectorType eType = CSS1_SELTYPE_ELEMENT;
                m_nToken = GetNextToken();
 
                if( CSS1_DOT_WO_WS == m_nToken )
                {
                    // DOT_WO_WS
                    m_nToken = GetNextToken();
 
                    // class
                    if( CSS1_IDENT == m_nToken )
                    {
                        aElement += "." + m_aToken;
                        eType = CSS1_SELTYPE_ELEM_CLASS;
                    }
                    else
                    {
                        // missing class
                        return pRoot;
                    }
                }
                else
                {
                    // that was a look-ahead
                    bNextToken = false;
                }
                pNew = new CSS1Selector( eType, aElement );
            }
            break;
        case CSS1_DOT_W_WS:
            // DOT_W_WS class
 
            // DOT_W_WS
            m_nToken = GetNextToken();
 
            if( CSS1_IDENT==m_nToken )
            {
                // class
                pNew = new CSS1Selector( CSS1_SELTYPE_CLASS, m_aToken );
            }
            else
            {
                // missing class
                return pRoot;
            }
            break;
        case CSS1_HASH:
            // '#' id_selector
 
            // '#'
            m_nToken = GetNextToken();
 
            if( CSS1_IDENT==m_nToken )
            {
                // id_selector
                pNew = new CSS1Selector( CSS1_SELTYPE_ID, m_aToken );
            }
            else
            {
                // missing id_selector
                return pRoot;
            }
            break;
 
        case CSS1_PAGE_SYM:
            {
                //  @page
                pNew = new CSS1Selector( CSS1_SELTYPE_PAGE, m_aToken );
            }
            break;
 
        default:
            // stop because we don't know what's next
            bDone = true;
            break;
        }
 
        // if created a new selector then save it
        if( pNew )
        {
            OSL_ENSURE( (pRoot!=nullptr) == (pLast!=nullptr),
                    "Root-Selector, but no Last" );
            if( pLast )
                pLast->SetNext( pNew );
            else
                pRoot.reset(pNew);
 
            pLast = pNew;
            pNew = nullptr;
        }
 
        if( bNextToken && !bDone )
            m_nToken = GetNextToken();
    }
 
    if( !pRoot )
    {
        // missing simple_selector
        return pRoot;
    }
 
    // [ ':' pseudo_element ]?
    if( CSS1_COLON==m_nToken && IsParserWorking() )
    {
        // ':' pseudo element
        m_nToken = GetNextToken();
        if( CSS1_IDENT==m_nToken )
        {
            if (pLast)
                pLast->SetNext( new CSS1Selector(CSS1_SELTYPE_PSEUDO,m_aToken) );
            m_nToken = GetNextToken();
        }
        else
        {
            // missing pseudo_element
            return pRoot;
        }
    }
 
    return pRoot;
}
 
// declaration
//  : property ':' expr prio?
//  | /* empty */
 
// expression
//  : term [ operator term ]*
 
// term
//  : unary_operator?
//     [ NUMBER | STRING | PERCENTAGE | LENGTH | EMS | EXS | IDENT |
//       HEXCOLOR | URL | RGB ]
 
// operator
//  : '/' | ',' | /* empty */
 
// unary_operator
//  : '-' | '+'
 
// property
//  : ident
 
// the sign is only used for numeric values (except PERCENTAGE)
// and it's applied on nValue!
std::unique_ptr<CSS1Expression> CSS1Parser::ParseDeclaration( OUString& rProperty )
{
    std::unique_ptr<CSS1Expression> pRoot;
    CSS1Expression *pLast = nullptr;
 
    // property
    if( CSS1_IDENT != m_nToken )
    {
        // missing property
        return pRoot;
    }
    rProperty = m_aToken;
 
    m_nToken = GetNextToken();
 
    // ':'
    if( CSS1_COLON != m_nToken )
    {
        // missing ':'
        return pRoot;
    }
    m_nToken = GetNextToken();
 
    // term [operator term]*
    // here we're pretty lax regarding the syntax, but this shouldn't
    // be a problem
    bool bDone = false;
    sal_Unicode cSign = 0, cOp = 0;
    CSS1Expression *pNew = nullptr;
 
    LOOP_CHECK_DECL
 
    while( !bDone && IsParserWorking() )
    {
        LOOP_CHECK_CHECK( "Infinite loop in ParseDeclaration()" )
 
        switch( m_nToken )
        {
        case CSS1_MINUS:
            cSign = '-';
            break;
 
        case CSS1_PLUS:
            cSign = '+';
            break;
 
        case CSS1_NUMBER:
        case CSS1_LENGTH:
        case CSS1_PIXLENGTH:
        case CSS1_EMS:
        case CSS1_EMX:
            if( '-'==cSign )
                m_nValue = -m_nValue;
            [[fallthrough]];
        case CSS1_STRING:
        case CSS1_PERCENTAGE:
        case CSS1_IDENT:
        case CSS1_URL:
        case CSS1_RGB:
        case CSS1_HEXCOLOR:
            pNew = new CSS1Expression( m_nToken, m_aToken, m_nValue, cOp );
            m_nValue = 0; // otherwise this also is applied to next ident
            cSign = 0;
            cOp = 0;
            break;
 
        case CSS1_SLASH:
            cOp = '/';
            cSign = 0;
            break;
 
        case CSS1_COMMA:
            cOp = ',';
            cSign = 0;
            break;
 
        default:
            bDone = true;
            break;
        }
 
        // if created a new expression save it
        if( pNew )
        {
            OSL_ENSURE( (pRoot!=nullptr) == (pLast!=nullptr),
                    "Root-Selector, but no Last" );
            if( pLast )
                pLast->SetNext( pNew );
            else
                pRoot.reset(pNew);
 
            pLast = pNew;
            pNew = nullptr;
        }
 
        if( !bDone )
            m_nToken = GetNextToken();
    }
 
    if( !pRoot )
    {
        // missing term
        return pRoot;
    }
 
    // prio?
    if( CSS1_IMPORTANT_SYM==m_nToken )
    {
        // IMPORTANT_SYM
        m_nToken = GetNextToken();
    }
 
    return pRoot;
}
 
CSS1Parser::CSS1Parser()
    : m_bWhiteSpace(false)
    , m_bEOF(false)
    , m_cNextCh(0)
    , m_nInPos(0)
    , m_nlLineNr(0)
    , m_nlLinePos(0)
    , m_nValue(0)
    , m_eState(CSS1_PAR_ACCEPTED)
    , m_nToken(CSS1_NULL)
{
}
 
CSS1Parser::~CSS1Parser()
{
}
 
void CSS1Parser::ParseStyleSheet( const OUString& rIn )
{
    OUString aTmp( rIn );
 
    sal_Unicode c;
    while( !aTmp.isEmpty() &&
           ( ' '==(c=aTmp[0]) || '\t'==c || '\r'==c || '\n'==c ) )
        aTmp = aTmp.copy( 1 );
 
    while( !aTmp.isEmpty() && ( ' '==(c=aTmp[aTmp.getLength()-1])
           || '\t'==c || '\r'==c || '\n'==c ) )
        aTmp = aTmp.copy( 0, aTmp.getLength()-1 );
 
    // remove SGML comments
    if( aTmp.getLength() >= 4 &&
        aTmp.startsWith( "<!--" ) )
        aTmp = aTmp.copy( 4 );
 
    if( aTmp.getLength() >=3 &&
        aTmp.endsWith("-->") )
        aTmp = aTmp.copy( 0, aTmp.getLength() - 3 );
 
    if( aTmp.isEmpty() )
        return;
 
    InitRead( aTmp );
 
    ParseStyleSheet();
}
 
void CSS1Parser::ParseStyleOption( const OUString& rIn )
{
    if( rIn.isEmpty() )
        return;
 
    InitRead( rIn );
 
    // fdo#41796: skip over spurious semicolons
    while (CSS1_SEMICOLON == m_nToken)
    {
        m_nToken = GetNextToken();
    }
 
    OUString aProperty;
    std::unique_ptr<CSS1Expression> pExpr = ParseDeclaration( aProperty );
    if( !pExpr )
        return;
 
    // process expression
    DeclarationParsed( aProperty, std::move(pExpr) );
 
    LOOP_CHECK_DECL
 
    // [ ';' declaration ]*
    while( CSS1_SEMICOLON==m_nToken && IsParserWorking() )
    {
        LOOP_CHECK_CHECK( "Infinite loop in ParseStyleOption()" )
 
        m_nToken = GetNextToken();
        if( CSS1_IDENT==m_nToken )
        {
            std::unique_ptr<CSS1Expression> pExp = ParseDeclaration( aProperty );
            if( pExp )
            {
                // process expression
                DeclarationParsed( aProperty, std::move(pExp) );
            }
        }
    }
}
 
void CSS1Parser::SelectorParsed( std::unique_ptr<CSS1Selector> /* pSelector */, bool /*bFirst*/ )
{
}
 
void CSS1Parser::DeclarationParsed( const OUString& /*rProperty*/,
                                    std::unique_ptr<CSS1Expression> /* pExpr */ )
{
}
 
CSS1Selector::~CSS1Selector()
{
    delete m_pNext;
}
 
CSS1Expression::~CSS1Expression()
{
    delete pNext;
}
 
void CSS1Expression::GetURL( OUString& rURL  ) const
{
    OSL_ENSURE( CSS1_URL==eType, "CSS1-Expression is not URL" );
 
    OSL_ENSURE( aValue.startsWithIgnoreAsciiCase( "url" ) &&
                aValue.getLength() > 5 &&
                '(' == aValue[3] &&
                ')' == aValue[aValue.getLength()-1],
                "no valid URL(...)" );
 
    if( aValue.getLength() <= 5 )
        return;
 
    rURL = aValue.copy( 4, aValue.getLength() - 5 );
 
    // tdf#94088 original stripped only spaces, but there may also be
    // double quotes in CSS style URLs, so be prepared to spaces followed
    // by a single quote followed by spaces
    const sal_Unicode aSpace(' ');
    const sal_Unicode aSingleQuote('\'');
 
    rURL = comphelper::string::strip(rURL, aSpace);
    rURL = comphelper::string::strip(rURL, aSingleQuote);
    rURL = comphelper::string::strip(rURL, aSpace);
}
 
bool CSS1Expression::GetColor( Color &rColor ) const
{
    OSL_ENSURE( CSS1_IDENT==eType || CSS1_RGB==eType ||
                CSS1_HEXCOLOR==eType || CSS1_STRING==eType,
                "CSS1-Expression cannot be colour" );
 
    bool bRet = false;
    sal_uInt32 nColor = SAL_MAX_UINT32;
 
    switch( eType )
    {
    case CSS1_RGB:
        {
            // fourth value to 255 means no alpha transparency
            // so the right by default value
            sal_uInt8 aColors[4] = { 0, 0, 0, 255 };
 
            // it can be "rgb" or "rgba"
            if (!aValue.startsWithIgnoreAsciiCase( "rgb" ) || aValue.getLength() < 6 ||
                    (aValue[3] != '(' && aValue[4] != '(' ) || aValue[aValue.getLength()-1] != ')')
            {
                break;
            }
 
            sal_Int32 nPos = aValue.startsWithIgnoreAsciiCase( "rgba" )?5:4; // start after "rgba(" or "rgb("
            char cSep = (aValue.indexOf(',') != -1)?',':' ';
            // alpha value can be after a "/" or ","
            bool bIsSepAlphaDiv = (aValue.indexOf('/') != -1)?true:false;
            for ( int nCol = 0; nCol < 4 && nPos > 0; ++nCol )
            {
                const std::u16string_view aNumber = o3tl::getToken(aValue, 0, cSep, nPos);
 
                sal_Int32 nNumber = o3tl::toInt32(aNumber);
                if( nNumber<0 )
                {
                    nNumber = 0;
                }
                else if( aNumber.find('%') != std::u16string_view::npos )
                {
                    if( nNumber > 100 )
                        nNumber = 100;
                    nNumber *= 255;
                    nNumber /= 100;
                }
                else if( nNumber > 255 )
                    nNumber = 255;
                else if( aNumber.find('.') != std::u16string_view::npos )
                {
                    // in this case aNumber contains something like "0.3" so not an sal_Int32
                    nNumber = static_cast<sal_Int32>(255.0*o3tl::toDouble(aNumber));
                }
                aColors[nCol] = static_cast<sal_uInt8>(nNumber);
                // rgb with alpha and '/' has this form: rgb(255 0 0 / 50%)
                if (bIsSepAlphaDiv && nCol == 2)
                {
                    // but there can be some spaces or not before and after the "/", so skip them
                    while (aValue[nPos] == '/' || aValue[nPos] == ' ')
                      ++nPos;
                }
            }
 
            rColor.SetRed( aColors[0] );
            rColor.SetGreen( aColors[1] );
            rColor.SetBlue( aColors[2] );
            rColor.SetAlpha( aColors[3] );
 
            bRet = true;    // something different than a colour isn't possible
        }
        break;
 
    case CSS1_IDENT:
    case CSS1_STRING:
        {
            OUString aTmp( aValue.toAsciiUpperCase() );
            nColor = GetHTMLColor( aTmp );
            bRet = nColor != SAL_MAX_UINT32;
        }
        if( bRet || CSS1_STRING != eType || aValue.isEmpty() ||
            aValue[0] != '#' )
            break;
        [[fallthrough]];
    case CSS1_HEXCOLOR:
        {
            // MS-IE hack: colour can also be a string
            sal_Int32 nOffset = CSS1_STRING==eType ? 1 : 0;
            bool bDouble = aValue.getLength()-nOffset == 3;
            sal_Int32 i = nOffset, nEnd = (bDouble ? 3 : 6) + nOffset;
 
            nColor = 0;
            for( ; i<nEnd; i++ )
            {
                sal_Unicode c = (i<aValue.getLength() ? aValue[i]
                                                         : '0' );
                if( c >= '0' && c <= '9' )
                    c -= 48;
                else if( c >= 'A' && c <= 'F' )
                    c -= 55;
                else if( c >= 'a' && c <= 'f' )
                    c -= 87;
                else
                    c = 16;
 
                nColor *= 16;
                if( c<16 )
                    nColor += c;
                if( bDouble )
                {
                    nColor *= 16;
                    if( c<16 )
                        nColor += c;
                }
            }
            bRet = true;
        }
        break;
    default:
        ;
    }
 
    if( bRet && nColor!=SAL_MAX_UINT32 )
    {
        rColor.SetRed( static_cast<sal_uInt8>((nColor & 0x00ff0000UL) >> 16) );
        rColor.SetGreen( static_cast<sal_uInt8>((nColor & 0x0000ff00UL) >> 8) );
        rColor.SetBlue( static_cast<sal_uInt8>(nColor & 0x000000ffUL) );
    }
 
    return bRet;
}
 
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */

V530 The return value of function 'append' is required to be utilized.

V530 The return value of function 'append' is required to be utilized.

V530 The return value of function 'append' is required to be utilized.

V530 The return value of function 'append' is required to be utilized.

V530 The return value of function 'append' is required to be utilized.

V530 The return value of function 'append' is required to be utilized.

V530 The return value of function 'append' is required to be utilized.

V530 The return value of function 'append' is required to be utilized.

V530 The return value of function 'append' is required to be utilized.

V530 The return value of function 'append' is required to be utilized.

V1048 The 'bNextCh' variable was assigned the same value.