/* -*- Mode: C; indent-tabs-mode:nil; c-basic-offset: 8-*- */

/*
 * This file is part of The Croco Library
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of version 2.1 of the GNU Lesser General Public
 * License as published by the Free Software Foundation.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
 * USA
 *
 * Author: Dodji Seketeli
 * See the COPYRIGHTS file for copyrights information.
 */

/**
 *@file
 *The definition of the #CRTknzr (tokenizer)
 *class.
 */

#include "string.h"
#include "math.h"
#include "cr-tknzr.h"
#include "cr-doc-handler.h"

struct _CRTknzrPriv {
        /**The parser input stream of bytes*/
        CRInput *input;

        /**
         *A cache where tknzr_unget_token()
         *puts back the token. tknzr_get_next_token()
         *first look in this cache, and if and 
         *only if it's empty, fetches the next token
         *from the input stream.
         */
        CRToken *token_cache;

        /**
         *The position of the end of the previous token
         *or char fetched.
         */
        CRInputPos prev_pos;

        CRDocHandler *sac_handler;

        /**
         *The reference count of the current instance
         *of #CRTknzr. Is manipulated by cr_tknzr_ref()
         *and cr_tknzr_unref().
         */
        glong ref_count;
};

#define PRIVATE(obj) ((obj)->priv)

/**
 *return TRUE if the character is a number ([0-9]), FALSE otherwise
 *@param a_char the char to test.
 */
#define IS_NUM(a_char) (((a_char) >= '0' && (a_char) <= '9')?TRUE:FALSE)

/**
 *Checks if 'status' equals CR_OK. If not, goto the 'error' label.
 *
 *@param status the status (of type enum CRStatus) to test.
 *@param is_exception if set to FALSE, the final status returned the
 *current function will be CR_PARSING_ERROR. If set to TRUE, the
 *current status will be the current value of the 'status' variable.
 *
 */
#define CHECK_PARSING_STATUS(status, is_exception) \
if ((status) != CR_OK) \
{ \
        if (is_exception == FALSE) \
        { \
                status = CR_PARSING_ERROR ; \
        } \
        goto error ; \
}

/**
 *Peeks the next char from the input stream of the current tokenizer.
 *invokes CHECK_PARSING_STATUS on the status returned by
 *cr_tknzr_input_peek_char().
 *
 *@param the current instance of #CRTkzr.
 *@param to_char a pointer to the char where to store the
 *char peeked.
 */
#define PEEK_NEXT_CHAR(a_tknzr, a_to_char) \
{\
status = cr_tknzr_peek_char  (a_tknzr, a_to_char) ; \
CHECK_PARSING_STATUS (status, TRUE) \
}

/**
 *Reads the next char from the input stream of the current parser.
 *In case of error, jumps to the "error:" label located in the
 *function where this macro is called.
 *@param parser the current instance of #CRTknzr
 *@param to_char a pointer to the guint32 char where to store
 *the character read.
 */
#define READ_NEXT_CHAR(a_tknzr, to_char) \
status = cr_tknzr_read_char (a_tknzr, to_char) ;\
CHECK_PARSING_STATUS (status, TRUE)

/**
 *Gets information about the current position in
 *the input of the parser.
 *In case of failure, this macro returns from the 
 *calling function and
 *returns a status code of type enum #CRStatus.
 *@param parser the current instance of #CRTknzr.
 *@param pos out parameter. A pointer to the position 
 *inside the current parser input. Must
 */
#define RECORD_INITIAL_POS(a_tknzr, a_pos) \
status = cr_input_get_cur_pos (PRIVATE  \
(a_tknzr)->input, a_pos) ; \
g_return_val_if_fail (status == CR_OK, status)

/**
 *Gets the address of the current byte inside the
 *parser input.
 *@param parser the current instance of #CRTknzr.
 *@param addr out parameter a pointer (guchar*)
 *to where the address  must be put.
 */
#define RECORD_CUR_BYTE_ADDR(a_tknzr, a_addr) \
status = cr_input_get_cur_byte_addr \
            (PRIVATE (a_tknzr)->input, a_addr) ; \
CHECK_PARSING_STATUS (status, TRUE)

/**
 *Peeks a byte from the topmost parser input at
 *a given offset from the current position.
 *If it fails, goto the "error:" label.
 *
 *@param a_parser the current instance of #CRTknzr.
 *@param a_offset the offset of the byte to peek, the
 *current byte having the offset '0'.
 *@param a_byte_ptr out parameter a pointer (guchar*) to
 *where the peeked char is to be stored.
 */
#define PEEK_BYTE(a_tknzr, a_offset, a_byte_ptr) \
status = cr_tknzr_peek_byte (a_tknzr, \
                             a_offset, \
                             a_byte_ptr) ; \
CHECK_PARSING_STATUS (status, TRUE) ;

#define BYTE(a_input, a_n, a_eof) \
cr_input_peek_byte2 (a_input, a_n, a_eof)

/**
 *Reads a byte from the topmost parser input
 *steam.
 *If it fails, goto the "error" label.
 *@param a_parser the current instance of #CRTknzr.
 *@param a_byte_ptr the guchar * where to put the read char.
 */
#define READ_NEXT_BYTE(a_tknzr, a_byte_ptr) \
status = \
cr_input_read_byte (PRIVATE (a_tknzr)->input, a_byte_ptr) ;\
CHECK_PARSING_STATUS (status, TRUE) ;

/**
 *Skips a given number of byte in the topmost
 *parser input. Don't update line and column number.
 *In case of error, jumps to the "error:" label
 *of the surrounding function.
 *@param a_parser the current instance of #CRTknzr.
 *@param a_nb_bytes the number of bytes to skip.
 */
#define SKIP_BYTES(a_tknzr, a_nb_bytes) \
status = cr_input_seek_index (PRIVATE (a_tknzr)->input, \
                                     CR_SEEK_CUR, a_nb_bytes) ; \
CHECK_PARSING_STATUS (status, TRUE) ;

/**
 *Skip utf8 encoded characters.
 *Updates line and column numbers.
 *@param a_parser the current instance of #CRTknzr.
 *@param a_nb_chars the number of chars to skip. Must be of
 *type glong.
 */
#define SKIP_CHARS(a_tknzr, a_nb_chars) \
{ \
gulong nb_chars = a_nb_chars ; \
status = cr_input_consume_chars \
     (PRIVATE (a_tknzr)->input,0, &nb_chars) ; \
CHECK_PARSING_STATUS (status, TRUE) ; \
}

/**
 *Tests the condition and if it is false, sets
 *status to "CR_PARSING_ERROR" and goto the 'error'
 *label.
 *@param condition the condition to test.
 */
#define ENSURE_PARSING_COND(condition) \
if (! (condition)) {status = CR_PARSING_ERROR; goto error ;}

static enum CRStatus  cr_tknzr_parse_nl (CRTknzr * a_this, 
                                         guchar ** a_start, 
                                         guchar ** a_end,
                                         CRParsingLocation *a_location);

static enum CRStatus cr_tknzr_parse_w (CRTknzr * a_this, 
                                       guchar ** a_start, 
                                       guchar ** a_end,
                                       CRParsingLocation *a_location) ;

static enum CRStatus cr_tknzr_parse_unicode_escape (CRTknzr * a_this, 
                                                    guint32 * a_unicode,
                                                    CRParsingLocation *a_location) ;

static enum CRStatus cr_tknzr_parse_escape (CRTknzr * a_this, 
                                            guint32 * a_esc_code,
                                            CRParsingLocation *a_location);

static enum CRStatus cr_tknzr_parse_string (CRTknzr * a_this, 
                                            CRString ** a_str);

static enum CRStatus cr_tknzr_parse_comment (CRTknzr * a_this, 
                                             CRString ** a_comment);

static enum CRStatus cr_tknzr_parse_nmstart (CRTknzr * a_this, 
                                             guint32 * a_char, 
                                             CRParsingLocation *a_location);

static enum CRStatus cr_tknzr_parse_num (CRTknzr * a_this,
                                         CRNum ** a_num);

/**********************************
 *PRIVATE methods
 **********************************/

/**
 *Parses a "w" as defined by the css spec at [4.1.1]:
 * w ::= [ \t\r\n\f]*
 *
 *@param a_this the current instance of #CRTknzr.
 *@param a_start out param. Upon successful completion, points
 *to the beginning of the parsed white space, points to NULL otherwise.
 *Can also point to NULL is there is no white space actually.
 *@param a_end out param. Upon successful completion, points
 *to the end of the parsed white space, points to NULL otherwise.
 *Can also point to NULL is there is no white space actually.
 */
static enum CRStatus
cr_tknzr_parse_w (CRTknzr * a_this, 
                  guchar ** a_start, 
                  guchar ** a_end, 
                  CRParsingLocation *a_location)
{
        guint32 cur_char = 0;
        CRInputPos init_pos;
        enum CRStatus status = CR_OK;

        g_return_val_if_fail (a_this && PRIVATE (a_this)
                              && PRIVATE (a_this)->input
                              && a_start && a_end, 
                              CR_BAD_PARAM_ERROR);

        RECORD_INITIAL_POS (a_this, &init_pos);

        *a_start = NULL;
        *a_end = NULL;

        READ_NEXT_CHAR (a_this, &cur_char);

        if (cr_utils_is_white_space (cur_char) == FALSE) {
                status = CR_PARSING_ERROR;
                goto error;
        }
        if (a_location) {
                cr_tknzr_get_parsing_location (a_this, 
                                               a_location) ;
        }
        RECORD_CUR_BYTE_ADDR (a_this, a_start);
        *a_end = *a_start;

        for (;;) {
                gboolean is_eof = FALSE;

                cr_input_get_end_of_file (PRIVATE (a_this)->input, &is_eof);
                if (is_eof)
                        break;

                status = cr_tknzr_peek_char (a_this, &cur_char);
                if (status == CR_END_OF_INPUT_ERROR) {
                        status = CR_OK;
                        break;
                } else if (status != CR_OK) {
                        goto error;
                }

                if (cr_utils_is_white_space (cur_char) == TRUE) {
                        READ_NEXT_CHAR (a_this, &cur_char);
                        RECORD_CUR_BYTE_ADDR (a_this, a_end);
                } else {
                        break;
                }
        }

        return CR_OK;

      error:
        cr_tknzr_set_cur_pos (a_this, &init_pos);

        return status;
}

/**
 *Parses a newline as defined in the css2 spec:
 * nl   ::=    \n|\r\n|\r|\f
 *
 *@param a_this the "this pointer" of the current instance of #CRTknzr.
 *@param a_start a pointer to the first character of the successfully 
 *parsed string.
 *@param a_end a pointer to the last character of the successfully parsed
 *string.
 *@result CR_OK upon successful completion, an error code otherwise.
 */
static enum CRStatus
cr_tknzr_parse_nl (CRTknzr * a_this, 
                   guchar ** a_start, 
                   guchar ** a_end, 
                   CRParsingLocation *a_location)
{
        CRInputPos init_pos;
        guchar next_chars[2] = { 0 };
        enum CRStatus status = CR_PARSING_ERROR;

        g_return_val_if_fail (a_this && PRIVATE (a_this)
                              && a_start && a_end, CR_BAD_PARAM_ERROR);

        RECORD_INITIAL_POS (a_this, &init_pos);

        PEEK_BYTE (a_this, 1, &next_chars[0]);
        PEEK_BYTE (a_this, 2, &next_chars[1]);

        if ((next_chars[0] == '\r' && next_chars[1] == '\n')) {
                SKIP_BYTES (a_this, 1);
                if (a_location) {
                        cr_tknzr_get_parsing_location 
                                (a_this, a_location) ;
                }
                SKIP_CHARS (a_this, 1);

                RECORD_CUR_BYTE_ADDR (a_this, a_end);

                status = CR_OK;
        } else if (next_chars[0] == '\n'
                   || next_chars[0] == '\r' || next_chars[0] == '\f') {
                SKIP_CHARS (a_this, 1);
                if (a_location) {
                        cr_tknzr_get_parsing_location 
                                (a_this, a_location) ;
                }
                RECORD_CUR_BYTE_ADDR (a_this, a_start);
                *a_end = *a_start;
                status = CR_OK;
        } else {
                status = CR_PARSING_ERROR;
                goto error;
        }
        return CR_OK ;

 error:
        cr_tknzr_set_cur_pos (a_this, &init_pos) ;
        return status;
}

/**
 *Go ahead in the parser input, skipping all the spaces.
 *If the next char if not a white space, this function does nothing.
 *In any cases, it stops when it encounters a non white space character.
 *
 *@param a_this the current instance of #CRTknzr.
 *@return CR_OK upon successful completion, an error code otherwise.
 */
static enum CRStatus
cr_tknzr_try_to_skip_spaces (CRTknzr * a_this)
{
        enum CRStatus status = CR_ERROR;
        guint32 cur_char = 0;

        g_return_val_if_fail (a_this && PRIVATE (a_this)
                              && PRIVATE (a_this)->input, CR_BAD_PARAM_ERROR);

        status = cr_input_peek_char (PRIVATE (a_this)->input, &cur_char);

        if (status != CR_OK) {
                if (status == CR_END_OF_INPUT_ERROR)
                        return CR_OK;
                return status;
        }

        if (cr_utils_is_white_space (cur_char) == TRUE) {
                gulong nb_chars = -1; /*consume all spaces */

                status = cr_input_consume_white_spaces
                        (PRIVATE (a_this)->input, &nb_chars);
        }

        return status;
}

/**
 *Parses a "comment" as defined in the css spec at [4.1.1]:
 *COMMENT ::= \/\*[^*]*\*+([^/][^*]*\*+)*\/ .
 *This complex regexp is just to say that comments start
 *with the two chars '/''*' and ends with the two chars '*''/'.
 *It also means that comments cannot be nested.
 *So based on that, I've just tried to implement the parsing function
 *simply and in a straight forward manner.
 */
static enum CRStatus
cr_tknzr_parse_comment (CRTknzr * a_this, 
                        CRString ** a_comment)
{
        enum CRStatus status = CR_OK;
        CRInputPos init_pos;
        guint32 cur_char = 0, next_char= 0;
        CRString *comment = NULL;
        CRParsingLocation loc = {0,0,0} ;

        g_return_val_if_fail (a_this && PRIVATE (a_this)
                              && PRIVATE (a_this)->input, 
                              CR_BAD_PARAM_ERROR);

        RECORD_INITIAL_POS (a_this, &init_pos);        
        READ_NEXT_CHAR (a_this, &cur_char) ;        
        ENSURE_PARSING_COND (cur_char == '/');
        cr_tknzr_get_parsing_location (a_this, &loc) ;

        READ_NEXT_CHAR (a_this, &cur_char);
        ENSURE_PARSING_COND (cur_char == '*');
        comment = cr_string_new ();
        for (;;) { /* [^*]* */
                PEEK_NEXT_CHAR (a_this, &next_char);
                if (next_char == '*')
                        break;
                READ_NEXT_CHAR (a_this, &cur_char);
                g_string_append_unichar (comment->stryng, cur_char);
        }
        /* Stop condition: next_char == '*' */
        for (;;) { /* \*+ */
                READ_NEXT_CHAR(a_this, &cur_char);
                ENSURE_PARSING_COND (cur_char == '*');
                g_string_append_unichar (comment->stryng, cur_char);
                PEEK_NEXT_CHAR (a_this, &next_char);
                if (next_char != '*')
                        break;
        }
        /* Stop condition: next_char != '*' */
        for (;;) { /* ([^/][^*]*\*+)* */
                if (next_char == '/')
                        break;
                READ_NEXT_CHAR(a_this, &cur_char);
                g_string_append_unichar (comment->stryng, cur_char);
                for (;;) { /* [^*]* */
                        PEEK_NEXT_CHAR (a_this, &next_char);
                        if (next_char == '*')
                                break;
                        READ_NEXT_CHAR (a_this, &cur_char);
                        g_string_append_unichar (comment->stryng, cur_char);
                }
                /* Stop condition: next_char = '*', no need to verify, because peek and read exit to error anyway */
                for (;;) { /* \*+ */
                        READ_NEXT_CHAR(a_this, &cur_char);
                        ENSURE_PARSING_COND (cur_char == '*');
                        g_string_append_unichar (comment->stryng, cur_char);
                        PEEK_NEXT_CHAR (a_this, &next_char);
                        if (next_char != '*')
                                break;
                }
                /* Continue condition: next_char != '*' */
        }
        /* Stop condition: next_char == '\/' */
        READ_NEXT_CHAR(a_this, &cur_char);
        g_string_append_unichar (comment->stryng, cur_char);

        if (status == CR_OK) {
                cr_parsing_location_copy (&comment->location, 
                                          &loc) ;
                *a_comment = comment;                
                return CR_OK;
        }
 error:

        if (comment) {
                cr_string_destroy (comment);
                comment = NULL;
        }

        cr_tknzr_set_cur_pos (a_this, &init_pos);

        return status;
}

/**
 *Parses an 'unicode' escape sequence defined
 *in css spec at chap 4.1.1:
 *unicode ::= \\[0-9a-f]{1,6}[ \n\r\t\f]?
 *@param a_this the current instance of #CRTknzr.
 *@param a_start out parameter. A pointer to the start
 *of the unicode escape sequence. Must *NOT* be deleted by
 *the caller.
 *@param a_end out parameter. A pointer to the last character
 *of the unicode escape sequence. Must *NOT* be deleted by the caller.
 *@return CR_OK if parsing succeeded, an error code otherwise.
 *Error code can be either CR_PARSING_ERROR if the string 
 *parsed just doesn't
 *respect the production or another error if a 
 *lower level error occurred.
 */
static enum CRStatus
cr_tknzr_parse_unicode_escape (CRTknzr * a_this, 
                               guint32 * a_unicode,
                               CRParsingLocation *a_location)
{
        guint32 cur_char;
        CRInputPos init_pos;
        glong occur = 0;
        guint32 unicode = 0;
        guchar *tmp_char_ptr1 = NULL,
                *tmp_char_ptr2 = NULL;
        enum CRStatus status = CR_OK;

        g_return_val_if_fail (a_this && PRIVATE (a_this)
                              && a_unicode, CR_BAD_PARAM_ERROR);

        /*first, let's backup the current position pointer */
        RECORD_INITIAL_POS (a_this, &init_pos);

        READ_NEXT_CHAR (a_this, &cur_char);

        if (cur_char != '\\') {
                status = CR_PARSING_ERROR;
                goto error;
        }
        if (a_location) {
                cr_tknzr_get_parsing_location 
                        (a_this, a_location) ;
        }
        PEEK_NEXT_CHAR (a_this, &cur_char);

        for (occur = 0, unicode = 0; ((cur_char >= '0' && cur_char <= '9')
                                      || (cur_char >= 'a' && cur_char <= 'f')
                                      || (cur_char >= 'A' && cur_char <= 'F'))
             && occur < 6; occur++) {
                gint cur_char_val = 0;

                READ_NEXT_CHAR (a_this, &cur_char);

                if ((cur_char >= '0' && cur_char <= '9')) {
                        cur_char_val = (cur_char - '0');
                } else if ((cur_char >= 'a' && cur_char <= 'f')) {
                        cur_char_val = 10 + (cur_char - 'a');
                } else if ((cur_char >= 'A' && cur_char <= 'F')) {
                        cur_char_val = 10 + (cur_char - 'A');
                }

                unicode = unicode * 16 + cur_char_val;

                PEEK_NEXT_CHAR (a_this, &cur_char);
        }

        /* Eat a whitespace if possible. */
        cr_tknzr_parse_w (a_this, &tmp_char_ptr1, 
                          &tmp_char_ptr2, NULL);
        *a_unicode = unicode;
        return CR_OK;

      error:
        /*
         *restore the initial position pointer backuped at
         *the beginning of this function.
         */
        cr_tknzr_set_cur_pos (a_this, &init_pos);

        return status;
}

/**
 *parses an escape sequence as defined by the css spec:
 *escape ::= {unicode}|\\[ -~\200-\4177777]
 *@param a_this the current instance of #CRTknzr .
 */
static enum CRStatus
cr_tknzr_parse_escape (CRTknzr * a_this, guint32 * a_esc_code,
                       CRParsingLocation *a_location)
{
        enum CRStatus status = CR_OK;
        guint32 cur_char = 0;
        CRInputPos init_pos;
        guchar next_chars[2];

        g_return_val_if_fail (a_this && PRIVATE (a_this)
                              && a_esc_code, CR_BAD_PARAM_ERROR);

        RECORD_INITIAL_POS (a_this, &init_pos);

        PEEK_BYTE (a_this, 1, &next_chars[0]);
        PEEK_BYTE (a_this, 2, &next_chars[1]);

        if (next_chars[0] != '\\') {
                status = CR_PARSING_ERROR;
                goto error;
        }

        if ((next_chars[1] >= '0' && next_chars[1] <= '9')
            || (next_chars[1] >= 'a' && next_chars[1] <= 'f')
            || (next_chars[1] >= 'A' && next_chars[1] <= 'F')) {
                status = cr_tknzr_parse_unicode_escape (a_this, a_esc_code, 
                                                        a_location);
        } else {
                /*consume the '\' char */
                READ_NEXT_CHAR (a_this, &cur_char);
                if (a_location) {
                        cr_tknzr_get_parsing_location (a_this, 
                                                       a_location) ;
                }
                /*then read the char after the '\' */
                READ_NEXT_CHAR (a_this, &cur_char);

                if (cur_char != ' ' && (cur_char < 200 || cur_char > 4177777)) {
                        status = CR_PARSING_ERROR;
                        goto error;
                }
                *a_esc_code = cur_char;

        }
        if (status == CR_OK) {
                return CR_OK;
        }
 error:
        cr_tknzr_set_cur_pos (a_this, &init_pos);
        return status;
}

/**
 *Parses a string type as defined in css spec [4.1.1]:
 *
 *string ::= {string1}|{string2}
 *string1 ::= \"([\t !#$%&(-~]|\\{nl}|\'|{nonascii}|{escape})*\"
 *string2 ::= \'([\t !#$%&(-~]|\\{nl}|\"|{nonascii}|{escape})*\'
 *
 *@param a_this the current instance of #CRTknzr.
 *@param a_start out parameter. Upon successful completion, 
 *points to the beginning of the string, points to an undefined value
 *otherwise.
 *@param a_end out parameter. Upon successful completion, points to
 *the beginning of the string, points to an undefined value otherwise.
 *@return CR_OK upon successful completion, an error code otherwise.
 */
static enum CRStatus
cr_tknzr_parse_string (CRTknzr * a_this, CRString ** a_str)
{
        guint32 cur_char = 0,
                delim = 0;
        CRInputPos init_pos;
        enum CRStatus status = CR_OK;
        CRString *str = NULL;

        g_return_val_if_fail (a_this && PRIVATE (a_this)
                              && PRIVATE (a_this)->input
                              && a_str, CR_BAD_PARAM_ERROR);

        RECORD_INITIAL_POS (a_this, &init_pos);
        READ_NEXT_CHAR (a_this, &cur_char);

        if (cur_char == '"')
                delim = '"';
        else if (cur_char == '\'')
                delim = '\'';
        else {
                status = CR_PARSING_ERROR;
                goto error;
        }
        str = cr_string_new ();
        if (str) {
                cr_tknzr_get_parsing_location 
                        (a_this, &str->location) ;
        }
        for (;;) {
                guchar next_chars[2] = { 0 };

                PEEK_BYTE (a_this, 1, &next_chars[0]);

                if (next_chars[0] == '\\') {
                        guchar *tmp_char_ptr1 = NULL,
                                *tmp_char_ptr2 = NULL;
                        guint32 esc_code = 0;

                        PEEK_BYTE (a_this, 2, &next_chars[1]);

                        if (next_chars[1] == '\'' || next_chars[1] == '"' || next_chars[1] == '\\') {
                                g_string_append_unichar (str->stryng, 
                                                         next_chars[1]);
                                SKIP_BYTES (a_this, 2);
                                status = CR_OK;
                        } else {
                                status = cr_tknzr_parse_escape
                                        (a_this, &esc_code, NULL);

                                if (status == CR_OK) {
                                        g_string_append_unichar
                                                (str->stryng, 
                                                 esc_code);
                                }
                        }

                        if (status != CR_OK) {
                                /*
                                 *consume the '\' char, and try to parse
                                 *a newline.
                                 */
                                READ_NEXT_CHAR (a_this, &cur_char);

                                status = cr_tknzr_parse_nl
                                        (a_this, &tmp_char_ptr1,
                                         &tmp_char_ptr2, NULL);
                        }

                        CHECK_PARSING_STATUS (status, FALSE);
                } else if (next_chars[0] == delim) {
                        READ_NEXT_CHAR (a_this, &cur_char);
                        break;
                } else if (next_chars[0] == '\t'
                           || (next_chars[0] >= ' ' && next_chars[0] <= '~')) {
                        READ_NEXT_CHAR (a_this, &cur_char);
                        g_string_append_unichar (str->stryng, 
                                                 cur_char);
                        status = CR_OK;
                }

                else if (cr_utils_is_nonascii (next_chars[0])) {
                        READ_NEXT_CHAR (a_this, &cur_char);
                        g_string_append_unichar (str->stryng, cur_char);
                } else {
                        status = CR_PARSING_ERROR;
                        goto error;
                }
        }

        if (status == CR_OK) {
                if (*a_str == NULL) {
                        *a_str = str;
                        str = NULL;
                } else {
                        (*a_str)->stryng = g_string_append_len
                                ((*a_str)->stryng,
                                 str->stryng->str, 
                                 str->stryng->len);
                        cr_string_destroy (str);
                }
                return CR_OK;
        }

 error:

        if (str) {
                cr_string_destroy (str) ;
                str = NULL;
        }
        cr_tknzr_set_cur_pos (a_this, &init_pos);
        return status;
}

/**
 *Parses the an nmstart as defined by the css2 spec [4.1.1]:
 * nmstart [a-zA-Z]|{nonascii}|{escape}
 *
 *@param a_this the current instance of #CRTknzr.
 *@param a_start out param. A pointer to the starting point of
 *the token.
 *@param a_end out param. A pointer to the ending point of the
 *token.
 *@param a_char out param. The actual parsed nmchar.
 *@return CR_OK upon successful completion, 
 *an error code otherwise.
 */
static enum CRStatus
cr_tknzr_parse_nmstart (CRTknzr * a_this, 
                        guint32 * a_char,
                        CRParsingLocation *a_location)
{
        CRInputPos init_pos;
        enum CRStatus status = CR_OK;
        guint32 cur_char = 0,
                next_char = 0;

        g_return_val_if_fail (a_this && PRIVATE (a_this)
                              && PRIVATE (a_this)->input
                              && a_char, CR_BAD_PARAM_ERROR);

        RECORD_INITIAL_POS (a_this, &init_pos);

        PEEK_NEXT_CHAR (a_this, &next_char);

        if (next_char == '\\') {
                status = cr_tknzr_parse_escape (a_this, a_char,
                                                a_location);

                if (status != CR_OK)
                        goto error;

        } else if (cr_utils_is_nonascii (next_char) == TRUE
                   || ((next_char >= 'a') && (next_char <= 'z'))
                   || ((next_char >= 'A') && (next_char <= 'Z'))
                ) {
                READ_NEXT_CHAR (a_this, &cur_char);
                if (a_location) {
                        cr_tknzr_get_parsing_location (a_this, 
                                                       a_location) ;
                }
                *a_char = cur_char;
                status = CR_OK;
        } else {
                status = CR_PARSING_ERROR;
                goto error;
        }

        return CR_OK;

 error:        
        cr_tknzr_set_cur_pos (a_this, &init_pos);

        return status;

}

/**
 *Parses an nmchar as described in the css spec at
 *chap 4.1.1:
 *nmchar ::= [a-z0-9-]|{nonascii}|{escape}
 *
 *Humm, I have added the possibility for nmchar to
 *contain upper case letters.
 *
 *@param a_this the current instance of #CRTknzr.
 *@param a_start out param. A pointer to the starting point of
 *the token.
 *@param a_end out param. A pointer to the ending point of the
 *token.
 *@param a_char out param. The actual parsed nmchar.
 *@return CR_OK upon successful completion, 
 *an error code otherwise.
 */
static enum CRStatus
cr_tknzr_parse_nmchar (CRTknzr * a_this, guint32 * a_char,
                       CRParsingLocation *a_location)
{
        guint32 cur_char = 0,
                next_char = 0;
        enum CRStatus status = CR_OK;
        CRInputPos init_pos;

        g_return_val_if_fail (a_this && PRIVATE (a_this) && a_char,
                              CR_BAD_PARAM_ERROR);

        RECORD_INITIAL_POS (a_this, &init_pos);

        status = cr_input_peek_char (PRIVATE (a_this)->input, 
                                     &next_char) ;
        if (status != CR_OK)
                goto error;

        if (next_char == '\\') {
                status = cr_tknzr_parse_escape (a_this, a_char, 
                                                a_location);

                if (status != CR_OK)
                        goto error;

        } else if (cr_utils_is_nonascii (next_char) == TRUE
                   || ((next_char >= 'a') && (next_char <= 'z'))
                   || ((next_char >= 'A') && (next_char <= 'Z'))
                   || ((next_char >= '0') && (next_char <= '9'))
                   || (next_char == '-')
                   || (next_char == '_') /*'_' not allowed by the spec. */
                ) {
                READ_NEXT_CHAR (a_this, &cur_char);
                *a_char = cur_char;
                status = CR_OK;
                if (a_location) {
                        cr_tknzr_get_parsing_location
                                (a_this, a_location) ;
                }
        } else {
                status = CR_PARSING_ERROR;
                goto error;
        }
        return CR_OK;

 error:
        cr_tknzr_set_cur_pos (a_this, &init_pos);
        return status;
}

/**
 *Parses an "ident" as defined in css spec [4.1.1]:
 *ident ::= {nmstart}{nmchar}*
 *
 *Actually parses it using the css3 grammar:
 *ident ::= -?{nmstart}{nmchar}*
 *@param a_this the currens instance of #CRTknzr.
 *
 *@param a_str a pointer to parsed ident. If *a_str is NULL,
 *this function allocates a new instance of CRString. If not, 
 *the function just appends the parsed string to the one passed.
 *In both cases it is up to the caller to free *a_str.
 *
 *@return CR_OK upon successful completion, an error code 
 *otherwise.
 */
static enum CRStatus
cr_tknzr_parse_ident (CRTknzr * a_this, CRString ** a_str)
{
        guint32 tmp_char = 0;
        CRString *stringue = NULL ;
        CRInputPos init_pos;
        enum CRStatus status = CR_OK;
        gboolean location_is_set = FALSE ;
        gboolean skip_nmstart = FALSE;

        g_return_val_if_fail (a_this && PRIVATE (a_this)
                              && PRIVATE (a_this)->input
                              && a_str, CR_BAD_PARAM_ERROR);

        RECORD_INITIAL_POS (a_this, &init_pos);
        PEEK_NEXT_CHAR (a_this, &tmp_char) ;
        stringue = cr_string_new () ;
        g_return_val_if_fail (stringue, 
                              CR_OUT_OF_MEMORY_ERROR) ;

        if (tmp_char == '-') {
                READ_NEXT_CHAR (a_this, &tmp_char) ;
                cr_tknzr_get_parsing_location
                        (a_this, &stringue->location) ;
                location_is_set = TRUE ;
                g_string_append_unichar (stringue->stryng, 
                                         tmp_char) ;
                // CSS3 <ident-token> may start with "--"
                PEEK_NEXT_CHAR (a_this, &tmp_char);
                if (tmp_char == '-') {
                        skip_nmstart = TRUE;
                }
        }
        if (!skip_nmstart) {
                status = cr_tknzr_parse_nmstart (a_this, &tmp_char, NULL);
                if (status != CR_OK) {
                        status = CR_PARSING_ERROR;
                        goto end ;
                }
                if (location_is_set == FALSE) {
                        cr_tknzr_get_parsing_location 
                                (a_this, &stringue->location) ;
                        location_is_set = TRUE ;
                }
                g_string_append_unichar (stringue->stryng, tmp_char);
        }
        for (;;) {
                status = cr_tknzr_parse_nmchar (a_this, 
                                                &tmp_char, 
                                                NULL);
                if (status != CR_OK) {
                        status = CR_OK ;
                        break;
                }
                g_string_append_unichar (stringue->stryng, tmp_char);
        }
        if (status == CR_OK) {
                if (!*a_str) {
                        *a_str = stringue ;
                
                } else {
                        g_string_append_len ((*a_str)->stryng, 
                                             stringue->stryng->str, 
                                             stringue->stryng->len) ;
                        cr_string_destroy (stringue) ;
                }
                stringue = NULL ;
        }

 error:
 end:
        if (stringue) {
                cr_string_destroy (stringue) ;
                stringue = NULL ;
        }
        if (status != CR_OK ) {
                cr_tknzr_set_cur_pos (a_this, &init_pos) ;
        }
        return status ;
}


/**
 *Parses a "name" as defined by css spec [4.1.1]:
 *name ::= {nmchar}+
 *
 *@param a_this the current instance of #CRTknzr.
 *
 *@param a_str out parameter. A pointer to the successfully parsed
 *name. If *a_str is set to NULL, this function allocates a new instance
 *of CRString. If not, it just appends the parsed name to the passed *a_str.
 *In both cases, it is up to the caller to free *a_str.
 *
 *@return CR_OK upon successful completion, an error code otherwise.
 */
static enum CRStatus
cr_tknzr_parse_name (CRTknzr * a_this, 
                     CRString ** a_str)
{
        guint32 tmp_char = 0;
        CRInputPos init_pos;
        enum CRStatus status = CR_OK;
        gboolean str_needs_free = FALSE,
                is_first_nmchar=TRUE ;
        glong i = 0;
        CRParsingLocation loc = {0,0,0} ;

        g_return_val_if_fail (a_this && PRIVATE (a_this)
                              && PRIVATE (a_this)->input
                              && a_str,
                              CR_BAD_PARAM_ERROR) ;

        RECORD_INITIAL_POS (a_this, &init_pos);

        if (*a_str == NULL) {
                *a_str = cr_string_new ();
                str_needs_free = TRUE;
        }
        for (i = 0;; i++) {
                if (is_first_nmchar == TRUE) {
                        status = cr_tknzr_parse_nmchar 
                                (a_this, &tmp_char,
                                 &loc) ;
                        is_first_nmchar = FALSE ;
                } else {
                        status = cr_tknzr_parse_nmchar 
                                (a_this, &tmp_char, NULL) ;
                }
                if (status != CR_OK)
                        break;                
                g_string_append_unichar ((*a_str)->stryng, 
                                         tmp_char);
        }
        if (i > 0) {
                cr_parsing_location_copy 
                        (&(*a_str)->location, &loc) ;
                return CR_OK;
        }
        if (str_needs_free == TRUE && *a_str) {
                cr_string_destroy (*a_str);
                *a_str = NULL;
        }
        cr_tknzr_set_cur_pos (a_this, &init_pos);
        return CR_PARSING_ERROR;
}

/**
 *Parses a "hash" as defined by the css spec in [4.1.1]:
 *HASH ::= #{name}
 */
static enum CRStatus
cr_tknzr_parse_hash (CRTknzr * a_this, CRString ** a_str)
{
        guint32 cur_char = 0;
        CRInputPos init_pos;
        enum CRStatus status = CR_OK;
        gboolean str_needs_free = FALSE;
        CRParsingLocation loc = {0,0,0} ;

        g_return_val_if_fail (a_this && PRIVATE (a_this)
                              && PRIVATE (a_this)->input,
                              CR_BAD_PARAM_ERROR);

        RECORD_INITIAL_POS (a_this, &init_pos);
        READ_NEXT_CHAR (a_this, &cur_char);
        if (cur_char != '#') {
                status = CR_PARSING_ERROR;
                goto error;
        }
        if (*a_str == NULL) {
                *a_str = cr_string_new ();
                str_needs_free = TRUE;
        }
        cr_tknzr_get_parsing_location (a_this,
                                       &loc) ;
        status = cr_tknzr_parse_name (a_this, a_str);
        cr_parsing_location_copy (&(*a_str)->location, &loc) ;
        if (status != CR_OK) {
                goto error;
        }
        return CR_OK;

 error:
        if (str_needs_free == TRUE && *a_str) {
                cr_string_destroy (*a_str);
                *a_str = NULL;
        }

        cr_tknzr_set_cur_pos (a_this, &init_pos);
        return status;
}

/**
 *Parses an uri as defined by the css spec [4.1.1]:
 * URI ::= url\({w}{string}{w}\)
 *         |url\({w}([!#$%&*-~]|{nonascii}|{escape})*{w}\)
 *
 *@param a_this the current instance of #CRTknzr.
 *@param a_str the successfully parsed url.
 *@return CR_OK upon successful completion, an error code otherwise.
 */
static enum CRStatus
cr_tknzr_parse_uri (CRTknzr * a_this, 
                    CRString ** a_str)
{
        guint32 cur_char = 0;
        CRInputPos init_pos;
        enum CRStatus status = CR_PARSING_ERROR;
        guchar tab[4] = { 0 }, *tmp_ptr1 = NULL, *tmp_ptr2 = NULL;
        CRString *str = NULL;
        CRParsingLocation location = {0,0,0} ;

        g_return_val_if_fail (a_this 
                              && PRIVATE (a_this)
                              && PRIVATE (a_this)->input
                              && a_str, 
                              CR_BAD_PARAM_ERROR);

        RECORD_INITIAL_POS (a_this, &init_pos);

        PEEK_BYTE (a_this, 1, &tab[0]);
        PEEK_BYTE (a_this, 2, &tab[1]);
        PEEK_BYTE (a_this, 3, &tab[2]);
        PEEK_BYTE (a_this, 4, &tab[3]);

        if (tab[0] != 'u' || tab[1] != 'r' || tab[2] != 'l' || tab[3] != '(') {
                status = CR_PARSING_ERROR;
                goto error;
        }
        /*
         *Here, we want to skip 4 bytes ('u''r''l''(').
         *But we also need to keep track of the parsing location
         *of the 'u'. So, we skip 1 byte, we record the parsing
         *location, then we skip the 3 remaining bytes.
         */
        SKIP_CHARS (a_this, 1);
        cr_tknzr_get_parsing_location (a_this, &location) ;
        SKIP_CHARS (a_this, 3);
        cr_tknzr_try_to_skip_spaces (a_this);
        status = cr_tknzr_parse_string (a_this, a_str);

        if (status == CR_OK) {
                guint32 next_char = 0;
                status = cr_tknzr_parse_w (a_this, &tmp_ptr1, 
                                           &tmp_ptr2, NULL);
                cr_tknzr_try_to_skip_spaces (a_this);
                PEEK_NEXT_CHAR (a_this, &next_char);
                if (next_char == ')') {
                        READ_NEXT_CHAR (a_this, &cur_char);
                        status = CR_OK;
                } else {
                        status = CR_PARSING_ERROR;
                }
        }
        if (status != CR_OK) {
                str = cr_string_new ();
                for (;;) {
                        guint32 next_char = 0;
                        PEEK_NEXT_CHAR (a_this, &next_char);
                        if (strchr ("!#$%&", next_char)
                            || (next_char >= '*' && next_char <= '~')
                            || (cr_utils_is_nonascii (next_char) == TRUE)) {
                                READ_NEXT_CHAR (a_this, &cur_char);
                                g_string_append_unichar 
                                        (str->stryng, cur_char);
                                status = CR_OK;
                        } else {
                                guint32 esc_code = 0;
                                status = cr_tknzr_parse_escape
                                        (a_this, &esc_code, NULL);
                                if (status == CR_OK) {
                                        g_string_append_unichar
                                                (str->stryng, 
                                                 esc_code);
                                } else {
                                        status = CR_OK;
                                        break;
                                }
                        }
                }
                cr_tknzr_try_to_skip_spaces (a_this);
                READ_NEXT_CHAR (a_this, &cur_char);
                if (cur_char == ')') {
                        status = CR_OK;
                } else {
                        status = CR_PARSING_ERROR;
                        goto error;
                }
                if (str) {                        
                        if (*a_str == NULL) {
                                *a_str = str;
                                str = NULL;
                        } else {
                                g_string_append_len
                                        ((*a_str)->stryng,
                                         str->stryng->str,
                                         str->stryng->len);
                                cr_string_destroy (str);
                        }                        
                }
        }

        cr_parsing_location_copy
                (&(*a_str)->location,
                 &location) ;
        return CR_OK ;
 error:
        if (str) {
                cr_string_destroy (str);
                str = NULL;
        }
        cr_tknzr_set_cur_pos (a_this, &init_pos);
        return status;
}

/**
 *parses an RGB as defined in the css2 spec.
 *rgb: rgb '('S*{num}%?S* ',' {num}#?S*,S*{num}#?S*')'
 *
 *@param a_this the "this pointer" of the current instance of
 *@param a_rgb out parameter the parsed rgb.
 *@return CR_OK upon successful completion, an error code otherwise.
 */
static enum CRStatus
cr_tknzr_parse_rgb (CRTknzr * a_this, CRRgb ** a_rgb)
{
        enum CRStatus status = CR_OK;
        CRInputPos init_pos;
        CRNum *num = NULL;
        guchar next_bytes[3] = { 0 }, cur_byte = 0;
        glong red = 0,
                green = 0,
                blue = 0,
                i = 0;
        gboolean is_percentage = FALSE;
        CRParsingLocation location = {0,0,0} ;

        g_return_val_if_fail (a_this && PRIVATE (a_this), CR_BAD_PARAM_ERROR);

        RECORD_INITIAL_POS (a_this, &init_pos);

        PEEK_BYTE (a_this, 1, &next_bytes[0]);
        PEEK_BYTE (a_this, 2, &next_bytes[1]);
        PEEK_BYTE (a_this, 3, &next_bytes[2]);

        if (((next_bytes[0] == 'r') || (next_bytes[0] == 'R'))
            && ((next_bytes[1] == 'g') || (next_bytes[1] == 'G'))
            && ((next_bytes[2] == 'b') || (next_bytes[2] == 'B'))) {
                SKIP_CHARS (a_this, 1);
                cr_tknzr_get_parsing_location (a_this, &location) ;
                SKIP_CHARS (a_this, 2);
        } else {
                status = CR_PARSING_ERROR;
                goto error;
        }
        READ_NEXT_BYTE (a_this, &cur_byte);
        ENSURE_PARSING_COND (cur_byte == '(');

        cr_tknzr_try_to_skip_spaces (a_this);
        status = cr_tknzr_parse_num (a_this, &num);
        ENSURE_PARSING_COND ((status == CR_OK) && (num != NULL));

        red = (glong)num->val;
        cr_num_destroy (num);
        num = NULL;

        PEEK_BYTE (a_this, 1, &next_bytes[0]);
        if (next_bytes[0] == '%') {
                SKIP_CHARS (a_this, 1);
                is_percentage = TRUE;
        }
        cr_tknzr_try_to_skip_spaces (a_this);

        for (i = 0; i < 2; i++) {
                READ_NEXT_BYTE (a_this, &cur_byte);
                ENSURE_PARSING_COND (cur_byte == ',');

                cr_tknzr_try_to_skip_spaces (a_this);
                status = cr_tknzr_parse_num (a_this, &num);
                ENSURE_PARSING_COND ((status == CR_OK) && (num != NULL));

                PEEK_BYTE (a_this, 1, &next_bytes[0]);
                if (next_bytes[0] == '%') {
                        SKIP_CHARS (a_this, 1);
                        is_percentage = 1;
                }

                if (i == 0) {
                        green = (glong)num->val;
                } else if (i == 1) {
                        blue = (glong)num->val;
                }

                if (num) {
                        cr_num_destroy (num);
                        num = NULL;
                }
                cr_tknzr_try_to_skip_spaces (a_this);
        }

        READ_NEXT_BYTE (a_this, &cur_byte);
        if (*a_rgb == NULL) {
                *a_rgb = cr_rgb_new_with_vals (red, green, blue,
                                               is_percentage);

                if (*a_rgb == NULL) {
                        status = CR_ERROR;
                        goto error;
                }
                status = CR_OK;
        } else {
                (*a_rgb)->red = red;
                (*a_rgb)->green = green;
                (*a_rgb)->blue = blue;
                (*a_rgb)->is_percentage = is_percentage;

                status = CR_OK;
        }

        if (status == CR_OK) {
                if (a_rgb && *a_rgb) {
                        cr_parsing_location_copy 
                                (&(*a_rgb)->location, 
                                 &location) ;
                }
                return CR_OK;
        }

 error:
        if (num) {
                cr_num_destroy (num);
                num = NULL;
        }

        cr_tknzr_set_cur_pos (a_this, &init_pos);
        return CR_OK;
}

/**
 *Parses a atkeyword as defined by the css spec in [4.1.1]:
 *ATKEYWORD ::= @{ident}
 *
 *@param a_this the "this pointer" of the current instance of
 *#CRTknzr.
 *
 *@param a_str out parameter. The parsed atkeyword. If *a_str is
 *set to NULL this function allocates a new instance of CRString and
 *sets it to the parsed atkeyword. If not, this function just appends
 *the parsed atkeyword to the end of *a_str. In both cases it is up to
 *the caller to free *a_str.
 *
 *@return CR_OK upon successful completion, an error code otherwise.
 */
static enum CRStatus
cr_tknzr_parse_atkeyword (CRTknzr * a_this, 
                          CRString ** a_str)
{
        guint32 cur_char = 0;
        CRInputPos init_pos;
        gboolean str_needs_free = FALSE;
        enum CRStatus status = CR_OK;

        g_return_val_if_fail (a_this && PRIVATE (a_this)
                              && PRIVATE (a_this)->input
                              && a_str, CR_BAD_PARAM_ERROR);

        RECORD_INITIAL_POS (a_this, &init_pos);

        READ_NEXT_CHAR (a_this, &cur_char);

        if (cur_char != '@') {
                status = CR_PARSING_ERROR;
                goto error;
        }

        if (*a_str == NULL) {
                *a_str = cr_string_new ();
                str_needs_free = TRUE;
        }
        status = cr_tknzr_parse_ident (a_this, a_str);
        if (status != CR_OK) {
                goto error;
        }
        return CR_OK;
 error:

        if (str_needs_free == TRUE && *a_str) {
                cr_string_destroy (*a_str);
                *a_str = NULL;
        }
        cr_tknzr_set_cur_pos (a_this, &init_pos);
        return status;
}

static enum CRStatus
cr_tknzr_parse_important (CRTknzr * a_this,
                          CRParsingLocation *a_location)
{
        guint32 cur_char = 0;
        CRInputPos init_pos;
        enum CRStatus status = CR_OK;

        g_return_val_if_fail (a_this && PRIVATE (a_this)
                              && PRIVATE (a_this)->input,
                              CR_BAD_PARAM_ERROR);

        RECORD_INITIAL_POS (a_this, &init_pos);
        READ_NEXT_CHAR (a_this, &cur_char);
        ENSURE_PARSING_COND (cur_char == '!');
        if (a_location) {
                cr_tknzr_get_parsing_location (a_this, 
                                               a_location) ;
        }
        cr_tknzr_try_to_skip_spaces (a_this);

        if (BYTE (PRIVATE (a_this)->input, 1, NULL) == 'i'
            && BYTE (PRIVATE (a_this)->input, 2, NULL) == 'm'
            && BYTE (PRIVATE (a_this)->input, 3, NULL) == 'p'
            && BYTE (PRIVATE (a_this)->input, 4, NULL) == 'o'
            && BYTE (PRIVATE (a_this)->input, 5, NULL) == 'r'
            && BYTE (PRIVATE (a_this)->input, 6, NULL) == 't'
            && BYTE (PRIVATE (a_this)->input, 7, NULL) == 'a'
            && BYTE (PRIVATE (a_this)->input, 8, NULL) == 'n'
            && BYTE (PRIVATE (a_this)->input, 9, NULL) == 't') {
                SKIP_BYTES (a_this, 9);
                if (a_location) {
                        cr_tknzr_get_parsing_location (a_this,
                                                       a_location) ;
                }
                return CR_OK;
        } else {
                status = CR_PARSING_ERROR;
        }

 error:
        cr_tknzr_set_cur_pos (a_this, &init_pos);

        return status;
}

/**
 *Parses a num as defined in the css spec [4.1.1]:
 *[0-9]+|[0-9]*\.[0-9]+
 *@param a_this the current instance of #CRTknzr.
 *@param a_num out parameter. The parsed number.
 *@return CR_OK upon successful completion, 
 *an error code otherwise.
 *
 *The CSS specification says that numbers may be
 *preceded by '+' or '-' to indicate the sign.
 *Technically, the "num" construction as defined
 *by the tokenizer doesn't allow this, but we parse
 *it here for simplicity.
 */
static enum CRStatus
cr_tknzr_parse_num (CRTknzr * a_this, 
                    CRNum ** a_num)
{
        enum CRStatus status = CR_PARSING_ERROR;
        enum CRNumType val_type = NUM_GENERIC;
        gboolean parsing_dec,  /* true iff seen decimal point. */
                parsed, /* true iff the substring seen so far is a valid CSS
                           number, i.e. `[0-9]+|[0-9]*\.[0-9]+'. */
                parsing_exp = FALSE;   /* true if seen an exponential */
        gdouble exponent = 0;       /* Start off the exponent at 0 */
        int exp_sign = 1;
        guint32 cur_char = 0,
                next_char = 0;
        gdouble numerator, denominator = 1;
        CRInputPos init_pos;
        CRParsingLocation location = {0,0,0};
        int sign = 1;

        g_return_val_if_fail (a_this && PRIVATE (a_this)
                              && PRIVATE (a_this)->input, 
                              CR_BAD_PARAM_ERROR);

        RECORD_INITIAL_POS (a_this, &init_pos);
        READ_NEXT_CHAR (a_this, &cur_char);

        if (cur_char == '+' || cur_char == '-') {
                if (cur_char == '-') {
                        sign = -1;
                }
                READ_NEXT_CHAR (a_this, &cur_char);
        }

        if (IS_NUM (cur_char)) {
                numerator = (cur_char - '0');
                parsing_dec = FALSE;
                parsed = TRUE;
        } else if (cur_char == '.') {
                numerator = 0;
                parsing_dec = TRUE;
                parsed = FALSE;
        } else {
                status = CR_PARSING_ERROR;
                goto error;
        }
        cr_tknzr_get_parsing_location (a_this, &location) ;

        for (;;) {
                status = cr_tknzr_peek_char (a_this, &next_char);
                if (status != CR_OK) {
                        if (status == CR_END_OF_INPUT_ERROR)
                                status = CR_OK;
                        break;
                }
                if (next_char == '.') {
                        if (parsing_dec) {
                                status = CR_PARSING_ERROR;
                                goto error;
                        }

                        READ_NEXT_CHAR (a_this, &cur_char);
                        parsing_dec = TRUE;
                        parsed = FALSE;  /* In CSS, there must be at least
                                            one digit after `.'. */
                } else if (!parsing_exp && (next_char == 'E' || next_char == 'e') && parsed) {
                        guchar next_byte;
                        PEEK_BYTE (a_this, 2, &next_byte)
                        if (next_byte == '+') {
                                READ_NEXT_CHAR (a_this, &cur_char);
                        } else if (next_byte == '-') {
                                exp_sign = -1;
                                READ_NEXT_CHAR (a_this, &cur_char);
                        } else if (!IS_NUM (next_byte)) {
                                break;
                        }

                        READ_NEXT_CHAR (a_this, &cur_char);
                        parsing_exp = TRUE;
                        parsed = FALSE;
                } else if (IS_NUM (next_char)) {
                        READ_NEXT_CHAR (a_this, &cur_char);
                        parsed = TRUE;
                        
                        if (parsing_exp) {
                                exponent = exponent * 10 + (cur_char - '0');
                        } else {
                                numerator = numerator * 10 + (cur_char - '0');
                                if (parsing_dec) {
                                        denominator *= 10;
                                }
                        }
                } else {
                        break;
                }
        }

        if (!parsed) {
                status = CR_PARSING_ERROR;
        }

        /*
         *Now, set the output param values.
         */
        if (status == CR_OK) {
                gdouble val = ((numerator / denominator) * sign) * pow(10, exponent * exp_sign);
                if (*a_num == NULL) {
                        *a_num = cr_num_new_with_val (val, val_type);

                        if (*a_num == NULL) {
                                status = CR_ERROR;
                                goto error;
                        }
                } else {
                        (*a_num)->val = val;
                        (*a_num)->type = val_type;
                }
                cr_parsing_location_copy (&(*a_num)->location,
                                          &location) ;
                return CR_OK;
        }

 error:

        cr_tknzr_set_cur_pos (a_this, &init_pos);

        return status;
}

/*********************************************
 *PUBLIC methods
 ********************************************/

CRTknzr *
cr_tknzr_new (CRInput * a_input)
{
        CRTknzr *result = NULL;

        result = (CRTknzr *) g_try_malloc (sizeof (CRTknzr));

        if (result == NULL) {
                cr_utils_trace_info ("Out of memory");
                return NULL;
        }

        memset (result, 0, sizeof (CRTknzr));

        result->priv = (CRTknzrPriv *)g_try_malloc (sizeof (CRTknzrPriv));

        if (result->priv == NULL) {
                cr_utils_trace_info ("Out of memory");

                if (result) {
                        g_free (result);
                        result = NULL;
                }

                return NULL;
        }
        memset (result->priv, 0, sizeof (CRTknzrPriv));
        if (a_input)
                cr_tknzr_set_input (result, a_input);
        return result;
}

CRTknzr *
cr_tknzr_new_from_buf (guchar * a_buf, gulong a_len,
                       enum CREncoding a_enc, 
                       gboolean a_free_at_destroy)
{
        CRTknzr *result = NULL;
        CRInput *input = NULL;

        input = cr_input_new_from_buf (a_buf, a_len, a_enc,
                                       a_free_at_destroy);

        g_return_val_if_fail (input != NULL, NULL);

        result = cr_tknzr_new (input);

        return result;
}

CRTknzr *
cr_tknzr_new_from_uri (const guchar * a_file_uri, 
                       enum CREncoding a_enc)
{
        CRTknzr *result = NULL;
        CRInput *input = NULL;

        input = cr_input_new_from_uri ((const gchar *) a_file_uri, a_enc);
        g_return_val_if_fail (input != NULL, NULL);

        result = cr_tknzr_new (input);

        return result;
}

void
cr_tknzr_ref (CRTknzr * a_this)
{
        g_return_if_fail (a_this && PRIVATE (a_this));

        PRIVATE (a_this)->ref_count++;
}

gboolean
cr_tknzr_unref (CRTknzr * a_this)
{
        g_return_val_if_fail (a_this && PRIVATE (a_this), FALSE);

        if (PRIVATE (a_this)->ref_count > 0) {
                PRIVATE (a_this)->ref_count--;
        }

        if (PRIVATE (a_this)->ref_count == 0) {
                cr_tknzr_destroy (a_this);
                return TRUE;
        }

        return FALSE;
}

enum CRStatus
cr_tknzr_set_input (CRTknzr * a_this, CRInput * a_input)
{
        g_return_val_if_fail (a_this && PRIVATE (a_this), CR_BAD_PARAM_ERROR);

        if (PRIVATE (a_this)->input) {
                cr_input_unref (PRIVATE (a_this)->input);
        }

        PRIVATE (a_this)->input = a_input;

        cr_input_ref (PRIVATE (a_this)->input);

        return CR_OK;
}

enum CRStatus
cr_tknzr_get_input (CRTknzr * a_this, CRInput ** a_input)
{
        g_return_val_if_fail (a_this && PRIVATE (a_this), CR_BAD_PARAM_ERROR);

        *a_input = PRIVATE (a_this)->input;

        return CR_OK;
}

/*********************************
 *Tokenizer input handling routines
 *********************************/

/**
 *Reads the next byte from the parser input stream.
 *@param a_this the "this pointer" of the current instance of
 *#CRParser.
 *@param a_byte out parameter the place where to store the byte
 *read.
 *@return CR_OK upon successful completion, an error 
 *code otherwise.
 */
enum CRStatus
cr_tknzr_read_byte (CRTknzr * a_this, guchar * a_byte)
{
        g_return_val_if_fail (a_this && PRIVATE (a_this), CR_BAD_PARAM_ERROR);

        return cr_input_read_byte (PRIVATE (a_this)->input, a_byte);

}

/**
 *Reads the next char from the parser input stream.
 *@param a_this the current instance of #CRTknzr.
 *@param a_char out parameter. The read char.
 *@return CR_OK upon successful completion, an error code
 *otherwise.
 */
enum CRStatus
cr_tknzr_read_char (CRTknzr * a_this, guint32 * a_char)
{
        g_return_val_if_fail (a_this && PRIVATE (a_this)
                              && PRIVATE (a_this)->input
                              && a_char, CR_BAD_PARAM_ERROR);

        if (PRIVATE (a_this)->token_cache) {
                cr_input_set_cur_pos (PRIVATE (a_this)->input,
                                      &PRIVATE (a_this)->prev_pos);
                cr_token_destroy (PRIVATE (a_this)->token_cache);
                PRIVATE (a_this)->token_cache = NULL;
        }

        return cr_input_read_char (PRIVATE (a_this)->input, a_char);
}

/**
 *Peeks a char from the parser input stream.
 *To "peek a char" means reads the next char without consuming it.
 *Subsequent calls to this function return the same char.
 *@param a_this the current instance of #CRTknzr.
 *@param a_char out parameter. The peeked char upon successful completion.
 *@return CR_OK upon successful completion, an error code otherwise.
 */
enum CRStatus
cr_tknzr_peek_char (CRTknzr * a_this, guint32 * a_char)
{
        g_return_val_if_fail (a_this && PRIVATE (a_this)
                              && PRIVATE (a_this)->input
                              && a_char, CR_BAD_PARAM_ERROR);

        if (PRIVATE (a_this)->token_cache) {
                cr_input_set_cur_pos (PRIVATE (a_this)->input,
                                      &PRIVATE (a_this)->prev_pos);
                cr_token_destroy (PRIVATE (a_this)->token_cache);
                PRIVATE (a_this)->token_cache = NULL;
        }

        return cr_input_peek_char (PRIVATE (a_this)->input, a_char);
}

/**
 *Peeks a byte ahead at a given position in the parser input stream.
 *@param a_this the current instance of #CRTknzr.
 *@param a_offset the offset of the peeked byte starting from the current
 *byte in the parser input stream.
 *@param a_byte out parameter. The peeked byte upon 
 *successful completion.
 *@return CR_OK upon successful completion, an error code otherwise.
 */
enum CRStatus
cr_tknzr_peek_byte (CRTknzr * a_this, gulong a_offset, guchar * a_byte)
{
        g_return_val_if_fail (a_this && PRIVATE (a_this)
                              && PRIVATE (a_this)->input && a_byte,
                              CR_BAD_PARAM_ERROR);

        if (PRIVATE (a_this)->token_cache) {
                cr_input_set_cur_pos (PRIVATE (a_this)->input,
                                      &PRIVATE (a_this)->prev_pos);
                cr_token_destroy (PRIVATE (a_this)->token_cache);
                PRIVATE (a_this)->token_cache = NULL;
        }

        return cr_input_peek_byte (PRIVATE (a_this)->input,
                                   CR_SEEK_CUR, a_offset, a_byte);
}

/**
 *Same as cr_tknzr_peek_byte() but this api returns the byte peeked.
 *@param a_this the current instance of #CRTknzr.
 *@param a_offset the offset of the peeked byte starting from the current
 *byte in the parser input stream.
 *@param a_eof out parameter. If not NULL, is set to TRUE if we reached end of
 *file, FALE otherwise. If the caller sets it to NULL, this parameter 
 *is just ignored.
 *@return the peeked byte.
 */
guchar
cr_tknzr_peek_byte2 (CRTknzr * a_this, gulong a_offset, gboolean * a_eof)
{
        g_return_val_if_fail (a_this && PRIVATE (a_this)
                              && PRIVATE (a_this)->input, 0);

        return cr_input_peek_byte2 (PRIVATE (a_this)->input, a_offset, a_eof);
}

/**
 *Gets the number of bytes left in the topmost input stream
 *associated to this parser.
 *@param a_this the current instance of #CRTknzr
 *@return the number of bytes left or -1 in case of error.
 */
glong
cr_tknzr_get_nb_bytes_left (CRTknzr * a_this)
{
        g_return_val_if_fail (a_this && PRIVATE (a_this)
                              && PRIVATE (a_this)->input, CR_BAD_PARAM_ERROR);

        if (PRIVATE (a_this)->token_cache) {
                cr_input_set_cur_pos (PRIVATE (a_this)->input,
                                      &PRIVATE (a_this)->prev_pos);
                cr_token_destroy (PRIVATE (a_this)->token_cache);
                PRIVATE (a_this)->token_cache = NULL;
        }

        return cr_input_get_nb_bytes_left (PRIVATE (a_this)->input);
}

enum CRStatus
cr_tknzr_get_cur_pos (CRTknzr * a_this, CRInputPos * a_pos)
{
        g_return_val_if_fail (a_this && PRIVATE (a_this)
                              && PRIVATE (a_this)->input
                              && a_pos, CR_BAD_PARAM_ERROR);

        if (PRIVATE (a_this)->token_cache) {
                cr_input_set_cur_pos (PRIVATE (a_this)->input,
                                      &PRIVATE (a_this)->prev_pos);
                cr_token_destroy (PRIVATE (a_this)->token_cache);
                PRIVATE (a_this)->token_cache = NULL;
        }

        return cr_input_get_cur_pos (PRIVATE (a_this)->input, a_pos);
}

enum CRStatus 
cr_tknzr_get_parsing_location (CRTknzr *a_this,
                               CRParsingLocation *a_loc)
{
        g_return_val_if_fail (a_this 
                              && PRIVATE (a_this)
                              && a_loc,
                              CR_BAD_PARAM_ERROR) ;

        return cr_input_get_parsing_location 
                (PRIVATE (a_this)->input, a_loc) ;
}

enum CRStatus
cr_tknzr_get_cur_byte_addr (CRTknzr * a_this, guchar ** a_addr)
{
        g_return_val_if_fail (a_this && PRIVATE (a_this)
                              && PRIVATE (a_this)->input, CR_BAD_PARAM_ERROR);
        if (PRIVATE (a_this)->token_cache) {
                cr_input_set_cur_pos (PRIVATE (a_this)->input,
                                      &PRIVATE (a_this)->prev_pos);
                cr_token_destroy (PRIVATE (a_this)->token_cache);
                PRIVATE (a_this)->token_cache = NULL;
        }

        return cr_input_get_cur_byte_addr (PRIVATE (a_this)->input, a_addr);
}

enum CRStatus
cr_tknzr_seek_index (CRTknzr * a_this, enum CRSeekPos a_origin, gint a_pos)
{
        g_return_val_if_fail (a_this && PRIVATE (a_this)
                              && PRIVATE (a_this)->input, CR_BAD_PARAM_ERROR);

        if (PRIVATE (a_this)->token_cache) {
                cr_input_set_cur_pos (PRIVATE (a_this)->input,
                                      &PRIVATE (a_this)->prev_pos);
                cr_token_destroy (PRIVATE (a_this)->token_cache);
                PRIVATE (a_this)->token_cache = NULL;
        }

        return cr_input_seek_index (PRIVATE (a_this)->input, a_origin, a_pos);
}

enum CRStatus
cr_tknzr_consume_chars (CRTknzr * a_this, guint32 a_char, glong * a_nb_char)
{
	gulong consumed = *(gulong *) a_nb_char;
	enum CRStatus status;
        g_return_val_if_fail (a_this && PRIVATE (a_this)
                              && PRIVATE (a_this)->input, CR_BAD_PARAM_ERROR);

        if (PRIVATE (a_this)->token_cache) {
                cr_input_set_cur_pos (PRIVATE (a_this)->input,
                                      &PRIVATE (a_this)->prev_pos);
                cr_token_destroy (PRIVATE (a_this)->token_cache);
                PRIVATE (a_this)->token_cache = NULL;
        }

        status = cr_input_consume_chars (PRIVATE (a_this)->input,
                                         a_char, &consumed);
	*a_nb_char = (glong) consumed;
	return status;
}

enum CRStatus
cr_tknzr_set_cur_pos (CRTknzr * a_this, CRInputPos * a_pos)
{
        g_return_val_if_fail (a_this && PRIVATE (a_this)
                              && PRIVATE (a_this)->input, CR_BAD_PARAM_ERROR);

        if (PRIVATE (a_this)->token_cache) {
                cr_token_destroy (PRIVATE (a_this)->token_cache);
                PRIVATE (a_this)->token_cache = NULL;
        }

        return cr_input_set_cur_pos (PRIVATE (a_this)->input, a_pos);
}

enum CRStatus
cr_tknzr_unget_token (CRTknzr * a_this, CRToken * a_token)
{
        g_return_val_if_fail (a_this && PRIVATE (a_this)
                              && PRIVATE (a_this)->token_cache == NULL,
                              CR_BAD_PARAM_ERROR);

        PRIVATE (a_this)->token_cache = a_token;

        return CR_OK;
}

/**
 *Returns the next token of the input stream.
 *This method is really central. Each parsing
 *method calls it.
 *@param a_this the current tokenizer.
 *@param a_tk out parameter. The returned token.
 *for the sake of mem leak avoidance, *a_tk must
 *be NULL.
 *@param CR_OK upon successful completion, an error code
 *otherwise.
 */
enum CRStatus
cr_tknzr_get_next_token (CRTknzr * a_this, CRToken ** a_tk)
{
        enum CRStatus status = CR_OK;
        CRToken *token = NULL;
        CRInputPos init_pos;
        guint32 next_char = 0;
        guchar next_bytes[4] = { 0 };
        gboolean reached_eof = FALSE;
        CRInput *input = NULL;
        CRString *str = NULL;
        CRRgb *rgb = NULL;
        CRParsingLocation location = {0,0,0} ;

        g_return_val_if_fail (a_this && PRIVATE (a_this)
                              && a_tk && *a_tk == NULL
                              && PRIVATE (a_this)->input, 
                              CR_BAD_PARAM_ERROR);

        if (PRIVATE (a_this)->token_cache) {
                *a_tk = PRIVATE (a_this)->token_cache;
                PRIVATE (a_this)->token_cache = NULL;
                return CR_OK;
        }

        RECORD_INITIAL_POS (a_this, &init_pos);

        status = cr_input_get_end_of_file
                (PRIVATE (a_this)->input, &reached_eof);
        ENSURE_PARSING_COND (status == CR_OK);

        if (reached_eof == TRUE) {
                status = CR_END_OF_INPUT_ERROR;
                goto error;
        }

        input = PRIVATE (a_this)->input;

        PEEK_NEXT_CHAR (a_this, &next_char);
        token = cr_token_new ();
        ENSURE_PARSING_COND (token);

        switch (next_char) {
        case '@':
                {
                        if (BYTE (input, 2, NULL) == 'f'
                            && BYTE (input, 3, NULL) == 'o'
                            && BYTE (input, 4, NULL) == 'n'
                            && BYTE (input, 5, NULL) == 't'
                            && BYTE (input, 6, NULL) == '-'
                            && BYTE (input, 7, NULL) == 'f'
                            && BYTE (input, 8, NULL) == 'a'
                            && BYTE (input, 9, NULL) == 'c'
                            && BYTE (input, 10, NULL) == 'e') {
                                SKIP_CHARS (a_this, 1);
                                cr_tknzr_get_parsing_location 
                                        (a_this, &location) ;
                                SKIP_CHARS (a_this, 9);
                                status = cr_token_set_font_face_sym (token);
                                CHECK_PARSING_STATUS (status, TRUE);
                                cr_parsing_location_copy (&token->location,
                                                          &location) ;
                                goto done;
                        }

                        if (BYTE (input, 2, NULL) == 'c'
                            && BYTE (input, 3, NULL) == 'h'
                            && BYTE (input, 4, NULL) == 'a'
                            && BYTE (input, 5, NULL) == 'r'
                            && BYTE (input, 6, NULL) == 's'
                            && BYTE (input, 7, NULL) == 'e'
                            && BYTE (input, 8, NULL) == 't') {
                                SKIP_CHARS (a_this, 1);
                                cr_tknzr_get_parsing_location
                                        (a_this, &location) ;
                                SKIP_CHARS (a_this, 7);
                                status = cr_token_set_charset_sym (token);
                                CHECK_PARSING_STATUS (status, TRUE);
                                cr_parsing_location_copy (&token->location,
                                                          &location) ;
                                goto done;
                        }

                        if (BYTE (input, 2, NULL) == 'i'
                            && BYTE (input, 3, NULL) == 'm'
                            && BYTE (input, 4, NULL) == 'p'
                            && BYTE (input, 5, NULL) == 'o'
                            && BYTE (input, 6, NULL) == 'r'
                            && BYTE (input, 7, NULL) == 't') {
                                SKIP_CHARS (a_this, 1);
                                cr_tknzr_get_parsing_location 
                                        (a_this, &location) ;
                                SKIP_CHARS (a_this, 6);
                                status = cr_token_set_import_sym (token);
                                CHECK_PARSING_STATUS (status, TRUE);
                                cr_parsing_location_copy (&token->location,
                                                          &location) ;
                                goto done;
                        }

                        if (BYTE (input, 2, NULL) == 'm'
                            && BYTE (input, 3, NULL) == 'e'
                            && BYTE (input, 4, NULL) == 'd'
                            && BYTE (input, 5, NULL) == 'i'
                            && BYTE (input, 6, NULL) == 'a') {
                                SKIP_CHARS (a_this, 1);
                                cr_tknzr_get_parsing_location (a_this, 
                                                               &location) ;
                                SKIP_CHARS (a_this, 5);
                                status = cr_token_set_media_sym (token);
                                CHECK_PARSING_STATUS (status, TRUE);
                                cr_parsing_location_copy (&token->location, 
                                                          &location) ;
                                goto done;
                        }

                        if (BYTE (input, 2, NULL) == 'p'
                            && BYTE (input, 3, NULL) == 'a'
                            && BYTE (input, 4, NULL) == 'g'
                            && BYTE (input, 5, NULL) == 'e') {
                                SKIP_CHARS (a_this, 1);
                                cr_tknzr_get_parsing_location (a_this, 
                                                               &location) ;
                                SKIP_CHARS (a_this, 4);
                                status = cr_token_set_page_sym (token);
                                CHECK_PARSING_STATUS (status, TRUE);
                                cr_parsing_location_copy (&token->location, 
                                                          &location) ;
                                goto done;
                        }
                        status = cr_tknzr_parse_atkeyword (a_this, &str);
                        if (status == CR_OK) {
                                status = cr_token_set_atkeyword (token, str);
                                CHECK_PARSING_STATUS (status, TRUE);
                                if (str) {
                                        cr_parsing_location_copy (&token->location, 
                                                                  &str->location) ;
                                }
                                goto done;
                        }
                }
                break;

        case 'u':

                if (BYTE (input, 2, NULL) == 'r'
                    && BYTE (input, 3, NULL) == 'l'
                    && BYTE (input, 4, NULL) == '(') {
                        CRString *str2 = NULL;

                        status = cr_tknzr_parse_uri (a_this, &str2);
                        if (status == CR_OK) {
                                status = cr_token_set_uri (token, str2);
                                CHECK_PARSING_STATUS (status, TRUE);
                                if (str2) {
                                        cr_parsing_location_copy (&token->location,
                                                                  &str2->location) ;
                                }
                                goto done;
                        }
                } 
                goto fallback;
                break;

        case 'r':
                if (BYTE (input, 2, NULL) == 'g'
                    && BYTE (input, 3, NULL) == 'b'
                    && BYTE (input, 4, NULL) == '(') {
                        status = cr_tknzr_parse_rgb (a_this, &rgb);
                        if (status == CR_OK && rgb) {
                                status = cr_token_set_rgb (token, rgb);
                                CHECK_PARSING_STATUS (status, TRUE);
                                if (rgb) {
                                        cr_parsing_location_copy (&token->location, 
                                                                  &rgb->location) ;
                                }
                                rgb = NULL;
                                goto done;
                        }

                }
                goto fallback;
                break;

        case '<':
                if (BYTE (input, 2, NULL) == '!'
                    && BYTE (input, 3, NULL) == '-'
                    && BYTE (input, 4, NULL) == '-') {
                        SKIP_CHARS (a_this, 1);
                        cr_tknzr_get_parsing_location (a_this, 
                                                       &location) ;
                        SKIP_CHARS (a_this, 3);
                        status = cr_token_set_cdo (token);
                        CHECK_PARSING_STATUS (status, TRUE);
                        cr_parsing_location_copy (&token->location, 
                                                  &location) ;
                        goto done;
                }
                break;

        case '-':
                if (BYTE (input, 2, NULL) == '-'
                    && BYTE (input, 3, NULL) == '>') {
                        SKIP_CHARS (a_this, 1);
                        cr_tknzr_get_parsing_location (a_this, 
                                                       &location) ;
                        SKIP_CHARS (a_this, 2);
                        status = cr_token_set_cdc (token);
                        CHECK_PARSING_STATUS (status, TRUE);
                        cr_parsing_location_copy (&token->location, 
                                                  &location) ;
                        goto done;
                } else {
                        status = cr_tknzr_parse_ident
                                (a_this, &str);
                        if (status == CR_OK) {
                                cr_token_set_ident
                                        (token, str);
                                if (str) {
                                        cr_parsing_location_copy (&token->location, 
                                                                  &str->location) ;
                                }
                                goto done;
                        } else {
                                goto parse_number;
                        }
                }
                break;

        case '~':
                if (BYTE (input, 2, NULL) == '=') {
                        SKIP_CHARS (a_this, 1);
                        cr_tknzr_get_parsing_location (a_this, 
                                                       &location) ;
                        SKIP_CHARS (a_this, 1);
                        status = cr_token_set_includes (token);
                        CHECK_PARSING_STATUS (status, TRUE);
                        cr_parsing_location_copy (&token->location, 
                                                  &location) ;
                        goto done;
                }
                break;

        case '|':
                if (BYTE (input, 2, NULL) == '=') {
                        SKIP_CHARS (a_this, 1);
                        cr_tknzr_get_parsing_location (a_this, 
                                                       &location) ;
                        SKIP_CHARS (a_this, 1);
                        status = cr_token_set_dashmatch (token);
                        CHECK_PARSING_STATUS (status, TRUE);
                        cr_parsing_location_copy (&token->location,
                                                  &location) ;
                        goto done;
                }
                break;

        case '/':
                if (BYTE (input, 2, NULL) == '*') {
                        status = cr_tknzr_parse_comment (a_this, &str);

                        if (status == CR_OK) {
                                status = cr_token_set_comment (token, str);
                                str = NULL;
                                CHECK_PARSING_STATUS (status, TRUE);
                                if (str) {
                                        cr_parsing_location_copy (&token->location, 
                                                                  &str->location) ;
                                }
                                goto done;
                        }
                }
                break ;

        case ';':
                SKIP_CHARS (a_this, 1);
                cr_tknzr_get_parsing_location (a_this, 
                                               &location) ;
                status = cr_token_set_semicolon (token);
                CHECK_PARSING_STATUS (status, TRUE);
                cr_parsing_location_copy (&token->location, 
                                          &location) ;
                goto done;

        case '{':
                SKIP_CHARS (a_this, 1);
                cr_tknzr_get_parsing_location (a_this, 
                                               &location) ;
                status = cr_token_set_cbo (token);
                CHECK_PARSING_STATUS (status, TRUE);
                cr_tknzr_get_parsing_location (a_this, 
                                               &location) ;
                goto done;

        case '}':
                SKIP_CHARS (a_this, 1);
                cr_tknzr_get_parsing_location (a_this, 
                                               &location) ;
                status = cr_token_set_cbc (token);
                CHECK_PARSING_STATUS (status, TRUE);
                cr_parsing_location_copy (&token->location, 
                                          &location) ;
                goto done;

        case '(':
                SKIP_CHARS (a_this, 1);
                cr_tknzr_get_parsing_location (a_this, 
                                               &location) ;
                status = cr_token_set_po (token);
                CHECK_PARSING_STATUS (status, TRUE);
                cr_parsing_location_copy (&token->location, 
                                          &location) ;
                goto done;

        case ')':
                SKIP_CHARS (a_this, 1);
                cr_tknzr_get_parsing_location (a_this, 
                                               &location) ;
                status = cr_token_set_pc (token);
                CHECK_PARSING_STATUS (status, TRUE);
                cr_parsing_location_copy (&token->location, 
                                          &location) ;
                goto done;

        case '[':
                SKIP_CHARS (a_this, 1);
                cr_tknzr_get_parsing_location (a_this, 
                                               &location) ;
                status = cr_token_set_bo (token);
                CHECK_PARSING_STATUS (status, TRUE);
                cr_parsing_location_copy (&token->location, 
                                          &location) ;
                goto done;

        case ']':
                SKIP_CHARS (a_this, 1);
                cr_tknzr_get_parsing_location (a_this, 
                                               &location) ;
                status = cr_token_set_bc (token);
                CHECK_PARSING_STATUS (status, TRUE);
                cr_parsing_location_copy (&token->location, 
                                          &location) ;
                goto done;

        case ' ':
        case '\t':
        case '\n':
        case '\f':
        case '\r':
                {
                        guchar *start = NULL,
                                *end = NULL;

                        status = cr_tknzr_parse_w (a_this, &start, 
                                                   &end, &location);
                        if (status == CR_OK) {
                                status = cr_token_set_s (token);
                                CHECK_PARSING_STATUS (status, TRUE);
                                cr_tknzr_get_parsing_location (a_this, 
                                                               &location) ;
                                goto done;
                        }
                }
                break;

        case '#':
                {
                        status = cr_tknzr_parse_hash (a_this, &str);
                        if (status == CR_OK && str) {
                                status = cr_token_set_hash (token, str);
                                CHECK_PARSING_STATUS (status, TRUE);
                                if (str) {
                                        cr_parsing_location_copy (&token->location,
                                                                  &str->location) ;
                                }
                                str = NULL;
                                goto done;
                        }
                }
                break;

        case '\'':
        case '"':
                status = cr_tknzr_parse_string (a_this, &str);
                if (status == CR_OK && str) {
                        status = cr_token_set_string (token, str);
                        CHECK_PARSING_STATUS (status, TRUE);
                        if (str) {
                                cr_parsing_location_copy (&token->location, 
                                                          &str->location) ;
                        }
                        str = NULL;
                        goto done;
                }
                break;

        case '!':
                status = cr_tknzr_parse_important (a_this, &location);
                if (status == CR_OK) {
                        status = cr_token_set_important_sym (token);
                        CHECK_PARSING_STATUS (status, TRUE);
                        cr_parsing_location_copy (&token->location, 
                                                  &location) ;
                        goto done;
                }
                break;

        case '0':
        case '1':
        case '2':
        case '3':
        case '4':
        case '5':
        case '6':
        case '7':
        case '8':
        case '9':
        case '.':
        case '+':
        /* '-' case is handled separately above for --> comments */
        parse_number:
                {
                        CRNum *num = NULL;

                        status = cr_tknzr_parse_num (a_this, &num);
                        if (status == CR_OK && num) {
                                next_bytes[0] = BYTE (input, 1, NULL);
                                next_bytes[1] = BYTE (input, 2, NULL);
                                next_bytes[2] = BYTE (input, 3, NULL);
                                next_bytes[3] = BYTE (input, 4, NULL);

                                if (next_bytes[0] == 'e'
                                    && next_bytes[1] == 'm') {
                                        num->type = NUM_LENGTH_EM;
                                        status = cr_token_set_ems (token,
                                                                   num);
                                        num = NULL;
                                        SKIP_CHARS (a_this, 2);
                                } else if (next_bytes[0] == 'e'
                                           && next_bytes[1] == 'x') {
                                        num->type = NUM_LENGTH_EX;
                                        status = cr_token_set_exs (token,
                                                                   num);
                                        num = NULL;
                                        SKIP_CHARS (a_this, 2);
                                } else if (next_bytes[0] == 'p'
                                           && next_bytes[1] == 'x') {
                                        num->type = NUM_LENGTH_PX;
                                        status = cr_token_set_length
                                                (token, num, LENGTH_PX_ET);
                                        num = NULL;
                                        SKIP_CHARS (a_this, 2);
                                } else if (next_bytes[0] == 'c'
                                           && next_bytes[1] == 'm') {
                                        num->type = NUM_LENGTH_CM;
                                        status = cr_token_set_length
                                                (token, num, LENGTH_CM_ET);
                                        num = NULL;
                                        SKIP_CHARS (a_this, 2);
                                } else if (next_bytes[0] == 'm'
                                           && next_bytes[1] == 'm') {
                                        num->type = NUM_LENGTH_MM;
                                        status = cr_token_set_length
                                                (token, num, LENGTH_MM_ET);
                                        num = NULL;
                                        SKIP_CHARS (a_this, 2);
                                } else if (next_bytes[0] == 'i'
                                           && next_bytes[1] == 'n') {
                                        num->type = NUM_LENGTH_IN;
                                        status = cr_token_set_length
                                                (token, num, LENGTH_IN_ET);
                                        num = NULL;
                                        SKIP_CHARS (a_this, 2);
                                } else if (next_bytes[0] == 'p'
                                           && next_bytes[1] == 't') {
                                        num->type = NUM_LENGTH_PT;
                                        status = cr_token_set_length
                                                (token, num, LENGTH_PT_ET);
                                        num = NULL;
                                        SKIP_CHARS (a_this, 2);
                                } else if (next_bytes[0] == 'p'
                                           && next_bytes[1] == 'c') {
                                        num->type = NUM_LENGTH_PC;
                                        status = cr_token_set_length
                                                (token, num, LENGTH_PC_ET);
                                        num = NULL;
                                        SKIP_CHARS (a_this, 2);
                                } else if (next_bytes[0] == 'd'
                                           && next_bytes[1] == 'e'
                                           && next_bytes[2] == 'g') {
                                        num->type = NUM_ANGLE_DEG;
                                        status = cr_token_set_angle
                                                (token, num, ANGLE_DEG_ET);
                                        num = NULL;
                                        SKIP_CHARS (a_this, 3);
                                } else if (next_bytes[0] == 'r'
                                           && next_bytes[1] == 'a'
                                           && next_bytes[2] == 'd') {
                                        num->type = NUM_ANGLE_RAD;
                                        status = cr_token_set_angle
                                                (token, num, ANGLE_RAD_ET);
                                        num = NULL;
                                        SKIP_CHARS (a_this, 3);
                                } else if (next_bytes[0] == 'g'
                                           && next_bytes[1] == 'r'
                                           && next_bytes[2] == 'a'
                                           && next_bytes[3] == 'd') {
                                        num->type = NUM_ANGLE_GRAD;
                                        status = cr_token_set_angle
                                                (token, num, ANGLE_GRAD_ET);
                                        num = NULL;
                                        SKIP_CHARS (a_this, 4);
                                } else if (next_bytes[0] == 'm'
                                           && next_bytes[1] == 's') {
                                        num->type = NUM_TIME_MS;
                                        status = cr_token_set_time
                                                (token, num, TIME_MS_ET);
                                        num = NULL;
                                        SKIP_CHARS (a_this, 2);
                                } else if (next_bytes[0] == 's') {
                                        num->type = NUM_TIME_S;
                                        status = cr_token_set_time
                                                (token, num, TIME_S_ET);
                                        num = NULL;
                                        SKIP_CHARS (a_this, 1);
                                } else if (next_bytes[0] == 'H'
                                           && next_bytes[1] == 'z') {
                                        num->type = NUM_FREQ_HZ;
                                        status = cr_token_set_freq
                                                (token, num, FREQ_HZ_ET);
                                        num = NULL;
                                        SKIP_CHARS (a_this, 2);
                                } else if (next_bytes[0] == 'k'
                                           && next_bytes[1] == 'H'
                                           && next_bytes[2] == 'z') {
                                        num->type = NUM_FREQ_KHZ;
                                        status = cr_token_set_freq
                                                (token, num, FREQ_KHZ_ET);
                                        num = NULL;
                                        SKIP_CHARS (a_this, 3);
                                } else if (next_bytes[0] == '%') {
                                        num->type = NUM_PERCENTAGE;
                                        status = cr_token_set_percentage
                                                (token, num);
                                        num = NULL;
                                        SKIP_CHARS (a_this, 1);
                                } else {
                                        status = cr_tknzr_parse_ident (a_this,
                                                                       &str);
                                        if (status == CR_OK && str) {
                                                num->type = NUM_UNKNOWN_TYPE;
                                                status = cr_token_set_dimen
                                                        (token, num, str);
                                                num = NULL;
                                                CHECK_PARSING_STATUS (status,
                                                                      TRUE);
                                                str = NULL;
                                        } else {
                                                status = cr_token_set_number
                                                        (token, num);
                                                num = NULL;
                                                CHECK_PARSING_STATUS (status, CR_OK);
                                                str = NULL;
                                        }
                                }
                                if (token && token->u.num) {
                                        cr_parsing_location_copy (&token->location,
                                                                  &token->u.num->location) ;
                                } else {
                                        status = CR_ERROR ;
                                }
                                goto done ;
                        }
                }
                break;

        default:
        fallback:
                /*process the fallback cases here */

                if (next_char == '\\'
                    || (cr_utils_is_nonascii (next_char) == TRUE)
                    || ((next_char >= 'a') && (next_char <= 'z'))
                    || ((next_char >= 'A') && (next_char <= 'Z'))) {
                        status = cr_tknzr_parse_ident (a_this, &str);
                        if (status == CR_OK && str) {
                                guint32 next_c = 0;

                                status = cr_input_peek_char
                                        (PRIVATE (a_this)->input, &next_c);

                                if (status == CR_OK && next_c == '(') {

                                        SKIP_CHARS (a_this, 1);
                                        status = cr_token_set_function
                                                (token, str);
                                        CHECK_PARSING_STATUS (status, TRUE);
                                        /*ownership is transferred
                                         *to token by cr_token_set_function.
                                         */
                                        if (str) {
                                                cr_parsing_location_copy (&token->location, 
                                                                          &str->location) ;
                                        }
                                        str = NULL;
                                } else {
                                        status = cr_token_set_ident (token,
                                                                     str);
                                        CHECK_PARSING_STATUS (status, TRUE);
                                        if (str) {
                                                cr_parsing_location_copy (&token->location, 
                                                                          &str->location) ;
                                        }
                                        str = NULL;
                                }
                                goto done;
                        } else {
                                if (str) {
                                        cr_string_destroy (str);
                                        str = NULL;
                                }
                        }
                }
                break;
        }

        READ_NEXT_CHAR (a_this, &next_char);
        cr_tknzr_get_parsing_location (a_this, 
                                       &location) ;
        status = cr_token_set_delim (token, next_char);
        CHECK_PARSING_STATUS (status, TRUE);
        cr_parsing_location_copy (&token->location, 
                                  &location) ;
 done:

        if (status == CR_OK && token) {
                *a_tk = token;
                /*
                 *store the previous position input stream pos.
                 */
                memmove (&PRIVATE (a_this)->prev_pos,
                         &init_pos, sizeof (CRInputPos));
                return CR_OK;
        }

 error:
        if (token) {
                cr_token_destroy (token);
                token = NULL;
        }

        if (str) {
                cr_string_destroy (str);
                str = NULL;
        }
        cr_tknzr_set_cur_pos (a_this, &init_pos);
        return status;

}

enum CRStatus
cr_tknzr_parse_token (CRTknzr * a_this, enum CRTokenType a_type,
                      enum CRTokenExtraType a_et, gpointer a_res,
                      gpointer a_extra_res)
{
        enum CRStatus status = CR_OK;
        CRToken *token = NULL;

        g_return_val_if_fail (a_this && PRIVATE (a_this)
                              && PRIVATE (a_this)->input
                              && a_res, CR_BAD_PARAM_ERROR);

        status = cr_tknzr_get_next_token (a_this, &token);
        if (status != CR_OK)
                return status;
        if (token == NULL)
                return CR_PARSING_ERROR;

        if (token->type == a_type) {
                switch (a_type) {
                case NO_TK:
                case S_TK:
                case CDO_TK:
                case CDC_TK:
                case INCLUDES_TK:
                case DASHMATCH_TK:
                case IMPORT_SYM_TK:
                case PAGE_SYM_TK:
                case MEDIA_SYM_TK:
                case FONT_FACE_SYM_TK:
                case CHARSET_SYM_TK:
                case IMPORTANT_SYM_TK:
                        status = CR_OK;
                        break;

                case STRING_TK:
                case IDENT_TK:
                case HASH_TK:
                case ATKEYWORD_TK:
                case FUNCTION_TK:
                case COMMENT_TK:
                case URI_TK:
                        *((CRString **) a_res) = token->u.str;
                        token->u.str = NULL;
                        status = CR_OK;
                        break;

                case EMS_TK:
                case EXS_TK:
                case PERCENTAGE_TK:
                case NUMBER_TK:
                        *((CRNum **) a_res) = token->u.num;
                        token->u.num = NULL;
                        status = CR_OK;
                        break;

                case LENGTH_TK:
                case ANGLE_TK:
                case TIME_TK:
                case FREQ_TK:
                        if (token->extra_type == a_et) {
                                *((CRNum **) a_res) = token->u.num;
                                token->u.num = NULL;
                                status = CR_OK;
                        }
                        break;

                case DIMEN_TK:
                        *((CRNum **) a_res) = token->u.num;
                        if (a_extra_res == NULL) {
                                status = CR_BAD_PARAM_ERROR;
                                goto error;
                        }

                        *((CRString **) a_extra_res) = token->dimen;
                        token->u.num = NULL;
                        token->dimen = NULL;
                        status = CR_OK;
                        break;

                case DELIM_TK:
                        *((guint32 *) a_res) = token->u.unichar;
                        status = CR_OK;
                        break;

                case UNICODERANGE_TK:
                default:
                        status = CR_PARSING_ERROR;
                        break;
                }

                cr_token_destroy (token);
                token = NULL;
        } else {
                cr_tknzr_unget_token (a_this, token);
                token = NULL;
                status = CR_PARSING_ERROR;
        }

        return status;

      error:

        if (token) {
                cr_tknzr_unget_token (a_this, token);
                token = NULL;
        }

        return status;
}

void
cr_tknzr_destroy (CRTknzr * a_this)
{
        g_return_if_fail (a_this);

        if (PRIVATE (a_this) && PRIVATE (a_this)->input) {
                if (cr_input_unref (PRIVATE (a_this)->input)
                    == TRUE) {
                        PRIVATE (a_this)->input = NULL;
                }
        }

        if (PRIVATE (a_this)->token_cache) {
                cr_token_destroy (PRIVATE (a_this)->token_cache);
                PRIVATE (a_this)->token_cache = NULL;
        }

        if (PRIVATE (a_this)) {
                g_free (PRIVATE (a_this));
                PRIVATE (a_this) = NULL;
        }

        g_free (a_this);
}
