/* -*- 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 COPYRIGHTS file for copyrights information. */ /** *@file *The definition of the #CRParser class. */ #include "string.h" #include "cr-parser.h" #include "cr-num.h" #include "cr-term.h" #include "cr-simple-sel.h" #include "cr-attr-sel.h" /* *Random notes: *CSS core syntax vs CSS level 2 syntax *===================================== * *One must keep in mind *that css UA must comply with two syntax. * *1/the specific syntax that defines the css language *for a given level of specificatin (e.g css2 syntax *defined in appendix D.1 of the css2 spec) * *2/the core (general) syntax that is there to allow *UAs to parse style sheets written in levels of CSS that *didn't exist at the time the UAs were created. * *the name of parsing functions (or methods) contained in this file *follows the following scheme: cr_parser_parse_ (...) ; *where is the name *of a production of the css2 language. *When a given production is *defined by the css2 level grammar *and* by the *css core syntax, there will be two functions to parse that production: *one will parse the production defined by the css2 level grammar and the *other will parse the production defined by the css core grammar. *The css2 level grammar related parsing function will be called: *cr_parser_parse_ (...) ; *Then css core grammar related parsing function will be called: *cr_parser_parse__core (...) ; * *If a production is defined only by the css core grammar, then *it will be named: *cr_parser_parse__core (...) ; */ typedef struct _CRParserError CRParserError; /** *An abstraction of an error reported by by the *parsing routines. */ struct _CRParserError { guchar *msg; enum CRStatus status; glong line; glong column; glong byte_num; }; enum CRParserState { READY_STATE = 0, TRY_PARSE_CHARSET_STATE, CHARSET_PARSED_STATE, TRY_PARSE_IMPORT_STATE, IMPORT_PARSED_STATE, TRY_PARSE_RULESET_STATE, RULESET_PARSED_STATE, TRY_PARSE_MEDIA_STATE, MEDIA_PARSED_STATE, TRY_PARSE_PAGE_STATE, PAGE_PARSED_STATE, TRY_PARSE_FONT_FACE_STATE, FONT_FACE_PARSED_STATE } ; /** *The private attributes of *#CRParser. */ struct _CRParserPriv { /** *The tokenizer */ CRTknzr *tknzr; /** *The sac handlers to call *to notify the parsing of *the css2 constructions. */ CRDocHandler *sac_handler; /** *A stack of errors reported *by the parsing routines. *Contains instance of #CRParserError. *This pointer is the top of the stack. */ GList *err_stack; enum CRParserState state; gboolean resolve_import; gboolean is_case_sensitive; gboolean use_core_grammar; }; #define PRIVATE(obj) ((obj)->priv) #define CHARS_TAB_SIZE 12 /** *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 *by 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 ; \ } /** *same as CHECK_PARSING_STATUS() but this one pushes an error *on the parser error stack when an error arises. *@param a_this the current instance of #CRParser . *@param a_status the status to check. Is of type enum #CRStatus. *@param a_is_exception in case of error, if is TRUE, the status *is set to CR_PARSING_ERROR before goto error. If is false, the *real low level status is kept and will be returned by the *upper level function that called this macro. Usally,this must *be set to FALSE. * */ #define CHECK_PARSING_STATUS_ERR(a_this, a_status, a_is_exception,\ a_err_msg, a_err_status) \ if ((a_status) != CR_OK) \ { \ if (a_is_exception == FALSE) a_status = CR_PARSING_ERROR ; \ cr_parser_push_error (a_this, a_err_msg, a_err_status) ; \ goto error ; \ } /** *Peeks the next char from the input stream of the current parser *by invoking cr_tknzr_input_peek_char(). *invokes CHECK_PARSING_STATUS on the status returned by *cr_tknzr_peek_char(). * *@param a_this the current instance of #CRParser. *@param a_to_char a pointer to the char where to store the *char peeked. */ #define PEEK_NEXT_CHAR(a_this, a_to_char) \ {\ enum CRStatus status ; \ status = cr_tknzr_peek_char (PRIVATE (a_this)->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 a_this the curent instance of #CRParser *@param to_char a pointer to the guint32 char where to store *the character read. */ #define READ_NEXT_CHAR(a_this, a_to_char) \ status = cr_tknzr_read_char (PRIVATE (a_this)->tknzr, a_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 a_this the current instance of #CRParser. *@param a_pos out parameter. A pointer to the position *inside the current parser input. Must */ #define RECORD_INITIAL_POS(a_this, a_pos) \ status = cr_tknzr_get_cur_pos (PRIVATE \ (a_this)->tknzr, 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 #CRParser. *@param addr out parameter a pointer (guchar*) *to where the address must be put. */ #define RECORD_CUR_BYTE_ADDR(a_this, a_addr) \ status = cr_tknzr_get_cur_byte_addr \ (PRIVATE (a_this)->tknzr, 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 #CRParser. *@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_parser, a_offset, a_byte_ptr) \ status = cr_tknzr_peek_byte (PRIVATE (a_this)->tknzr, \ a_offset, \ a_byte_ptr) ; \ CHECK_PARSING_STATUS (status, TRUE) ; #define BYTE(a_parser, a_offset, a_eof) \ cr_tknzr_peek_byte2 (PRIVATE (a_this)->tknzr, a_offset, a_eof) /** *Reads a byte from the topmost parser input *steam. *If it fails, goto the "error" label. *@param a_this the current instance of #CRParser. *@param a_byte_ptr the guchar * where to put the read char. */ #define READ_NEXT_BYTE(a_this, a_byte_ptr) \ status = cr_tknzr_read_byte (PRIVATE (a_this)->tknzr, 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 #CRParser. *@param a_nb_bytes the number of bytes to skip. */ #define SKIP_BYTES(a_this, a_nb_bytes) \ status = cr_tknzr_seek_index (PRIVATE (a_this)->tknzr, \ 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 #CRParser. *@param a_nb_chars the number of chars to skip. Must be of *type glong. */ #define SKIP_CHARS(a_parser, a_nb_chars) \ { \ glong nb_chars = a_nb_chars ; \ status = cr_tknzr_consume_chars \ (PRIVATE (a_parser)->tknzr,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 ;} #define ENSURE_PARSING_COND_ERR(a_this, a_condition, \ a_err_msg, a_err_status) \ if (! (a_condition)) \ { \ status = CR_PARSING_ERROR; \ cr_parser_push_error (a_this, a_err_msg, a_err_status) ; \ goto error ; \ } #define GET_NEXT_TOKEN(a_this, a_token_ptr) \ status = cr_tknzr_get_next_token (PRIVATE (a_this)->tknzr, \ a_token_ptr) ; \ ENSURE_PARSING_COND (status == CR_OK) ; #ifdef WITH_UNICODE_ESCAPE_AND_RANGE static enum CRStatus cr_parser_parse_unicode_escape (CRParser * a_this, guint32 * a_unicode); static enum CRStatus cr_parser_parse_escape (CRParser * a_this, guint32 * a_esc_code); static enum CRStatus cr_parser_parse_unicode_range (CRParser * a_this, CRString ** a_inf, CRString ** a_sup); #endif static enum CRStatus cr_parser_parse_stylesheet_core (CRParser * a_this); static enum CRStatus cr_parser_parse_atrule_core (CRParser * a_this); static enum CRStatus cr_parser_parse_ruleset_core (CRParser * a_this); static enum CRStatus cr_parser_parse_selector_core (CRParser * a_this); static enum CRStatus cr_parser_parse_declaration_core (CRParser * a_this); static enum CRStatus cr_parser_parse_any_core (CRParser * a_this); static enum CRStatus cr_parser_parse_block_core (CRParser * a_this); static enum CRStatus cr_parser_parse_value_core (CRParser * a_this); static enum CRStatus cr_parser_parse_string (CRParser * a_this, CRString ** a_str); static enum CRStatus cr_parser_parse_ident (CRParser * a_this, CRString ** a_str); static enum CRStatus cr_parser_parse_uri (CRParser * a_this, CRString ** a_str); static enum CRStatus cr_parser_parse_function (CRParser * a_this, CRString ** a_func_name, CRTerm ** a_expr); static enum CRStatus cr_parser_parse_property (CRParser * a_this, CRString ** a_property); static enum CRStatus cr_parser_parse_attribute_selector (CRParser * a_this, CRAttrSel ** a_sel); static enum CRStatus cr_parser_parse_simple_selector (CRParser * a_this, CRSimpleSel ** a_sel); static enum CRStatus cr_parser_parse_simple_sels (CRParser * a_this, CRSimpleSel ** a_sel); static CRParserError *cr_parser_error_new (const guchar * a_msg, enum CRStatus); static void cr_parser_error_set_msg (CRParserError * a_this, const guchar * a_msg); static void cr_parser_error_dump (CRParserError * a_this); static void cr_parser_error_set_status (CRParserError * a_this, enum CRStatus a_status); static void cr_parser_error_set_pos (CRParserError * a_this, glong a_line, glong a_column, glong a_byte_num); static void cr_parser_error_destroy (CRParserError * a_this); static enum CRStatus cr_parser_push_error (CRParser * a_this, const guchar * a_msg, enum CRStatus a_status); static enum CRStatus cr_parser_dump_err_stack (CRParser * a_this, gboolean a_clear_errs); static enum CRStatus cr_parser_clear_errors (CRParser * a_this); /***************************** *error managemet methods *****************************/ /** *Constructor of #CRParserError class. *@param a_msg the brute error message. *@param a_status the error status. *@return the newly built instance of #CRParserError. */ static CRParserError * cr_parser_error_new (const guchar * a_msg, enum CRStatus a_status) { CRParserError *result = (CRParserError *)g_try_malloc (sizeof (CRParserError)); if (result == NULL) { cr_utils_trace_info ("Out of memory"); return NULL; } memset (result, 0, sizeof (CRParserError)); cr_parser_error_set_msg (result, a_msg); cr_parser_error_set_status (result, a_status); return result; } /** *Sets the message associated to this instance of #CRError. *@param a_this the current instance of #CRParserError. *@param a_msg the new message. */ static void cr_parser_error_set_msg (CRParserError * a_this, const guchar * a_msg) { g_return_if_fail (a_this); if (a_this->msg) { g_free (a_this->msg); } a_this->msg = (guchar *)g_strdup ((gchar *)a_msg); } /** *Sets the error status. *@param a_this the current instance of #CRParserError. *@param a_status the new error status. * */ static void cr_parser_error_set_status (CRParserError * a_this, enum CRStatus a_status) { g_return_if_fail (a_this); a_this->status = a_status; } /** *Sets the position of the parser error. *@param a_this the current instance of #CRParserError. *@param a_line the line number. *@param a_column the column number. *@param a_byte_num the byte number. */ static void cr_parser_error_set_pos (CRParserError * a_this, glong a_line, glong a_column, glong a_byte_num) { g_return_if_fail (a_this); a_this->line = a_line; a_this->column = a_column; a_this->byte_num = a_byte_num; } static void cr_parser_error_dump (CRParserError * a_this) { g_return_if_fail (a_this); g_printerr ("parsing error: %ld:%ld:", a_this->line, a_this->column); g_printerr ("%s\n", a_this->msg); } /** *The destructor of #CRParserError. *@param a_this the current instance of #CRParserError. */ static void cr_parser_error_destroy (CRParserError * a_this) { g_return_if_fail (a_this); if (a_this->msg) { g_free (a_this->msg); a_this->msg = NULL; } g_free (a_this); } /** *Pushes an error on the parser error stack. *@param a_this the current instance of #CRParser. *@param a_msg the error message. *@param a_status the error status. *@return CR_OK upon successful completion, an error code otherwise. */ static enum CRStatus cr_parser_push_error (CRParser * a_this, const guchar * a_msg, enum CRStatus a_status) { enum CRStatus status = CR_OK; CRParserError *error = NULL; CRInputPos pos; g_return_val_if_fail (a_this && PRIVATE (a_this) && a_msg, CR_BAD_PARAM_ERROR); error = cr_parser_error_new (a_msg, a_status); g_return_val_if_fail (error, CR_ERROR); RECORD_INITIAL_POS (a_this, &pos); cr_parser_error_set_pos (error, pos.line, pos.col, pos.next_byte_index - 1); PRIVATE (a_this)->err_stack = g_list_prepend (PRIVATE (a_this)->err_stack, error); if (PRIVATE (a_this)->err_stack == NULL) goto error; return CR_OK; error: if (error) { cr_parser_error_destroy (error); error = NULL; } return status; } /** *Dumps the error stack using g_printerr. *@param a_this the current instance of #CRParser. *@param a_clear_errs whether to clear the error stack *after the dump or not. *@return CR_OK upon successfull completion, an error code *otherwise. */ static enum CRStatus cr_parser_dump_err_stack (CRParser * a_this, gboolean a_clear_errs) { GList *cur = NULL; g_return_val_if_fail (a_this && PRIVATE (a_this), CR_BAD_PARAM_ERROR); if (PRIVATE (a_this)->err_stack == NULL) return CR_OK; for (cur = PRIVATE (a_this)->err_stack; cur; cur = cur->next) { cr_parser_error_dump ((CRParserError *) cur->data); } if (a_clear_errs == TRUE) { cr_parser_clear_errors (a_this); } return CR_OK; } /** *Clears all the errors contained in the parser error stack. *Frees all the errors, and the stack that contains'em. *@param a_this the current instance of #CRParser. */ static enum CRStatus cr_parser_clear_errors (CRParser * a_this) { GList *cur = NULL; g_return_val_if_fail (a_this && PRIVATE (a_this), CR_BAD_PARAM_ERROR); for (cur = PRIVATE (a_this)->err_stack; cur; cur = cur->next) { if (cur->data) { cr_parser_error_destroy ((CRParserError *) cur->data); } } if (PRIVATE (a_this)->err_stack) { g_list_free (PRIVATE (a_this)->err_stack); PRIVATE (a_this)->err_stack = NULL; } return CR_OK; } /** *Same as cr_parser_try_to_skip_spaces() but this one skips *spaces and comments. * *@param a_this the current instance of #CRParser. *@return CR_OK upon successfull completion, an error code otherwise. */ enum CRStatus cr_parser_try_to_skip_spaces_and_comments (CRParser * a_this) { enum CRStatus status = CR_ERROR; CRToken *token = NULL; g_return_val_if_fail (a_this && PRIVATE (a_this) && PRIVATE (a_this)->tknzr, CR_BAD_PARAM_ERROR); do { if (token) { cr_token_destroy (token); token = NULL; } status = cr_tknzr_get_next_token (PRIVATE (a_this)->tknzr, &token); if (status != CR_OK) goto error; } while ((token != NULL) && (token->type == COMMENT_TK || token->type == S_TK)); cr_tknzr_unget_token (PRIVATE (a_this)->tknzr, token); return status; error: if (token) { cr_token_destroy (token); token = NULL; } return status; } /*************************************** *End of Parser input handling routines ***************************************/ /************************************* *Non trivial terminal productions *parsing routines *************************************/ /** *Parses a css stylesheet following the core css grammar. *This is mainly done for test purposes. *During the parsing, no callback is called. This is just *to validate that the stylesheet is well formed according to the *css core syntax. *stylesheet : [ CDO | CDC | S | statement ]*; *@param a_this the current instance of #CRParser. *@return CR_OK upon successful completion, an error code otherwise. */ static enum CRStatus cr_parser_parse_stylesheet_core (CRParser * a_this) { CRToken *token = NULL; CRInputPos init_pos; enum CRStatus status = CR_ERROR; g_return_val_if_fail (a_this && PRIVATE (a_this), CR_BAD_PARAM_ERROR); RECORD_INITIAL_POS (a_this, &init_pos); continue_parsing: if (token) { cr_token_destroy (token); token = NULL; } cr_parser_try_to_skip_spaces_and_comments (a_this); status = cr_tknzr_get_next_token (PRIVATE (a_this)->tknzr, &token); if (status == CR_END_OF_INPUT_ERROR) { status = CR_OK; goto done; } else if (status != CR_OK) { goto error; } switch (token->type) { case CDO_TK: case CDC_TK: goto continue_parsing; break; default: status = cr_tknzr_unget_token (PRIVATE (a_this)->tknzr, token); CHECK_PARSING_STATUS (status, TRUE); token = NULL; status = cr_parser_parse_statement_core (a_this); cr_parser_clear_errors (a_this); if (status == CR_OK) { goto continue_parsing; } else if (status == CR_END_OF_INPUT_ERROR) { goto done; } else { goto error; } } done: if (token) { cr_token_destroy (token); token = NULL; } cr_parser_clear_errors (a_this); return CR_OK; error: cr_parser_push_error (a_this, (guchar *)"could not recognize next production", CR_ERROR); cr_parser_dump_err_stack (a_this, TRUE); if (token) { cr_token_destroy (token); token = NULL; } cr_tknzr_set_cur_pos (PRIVATE (a_this)->tknzr, &init_pos); return status; } /** *Parses an at-rule as defined by the css core grammar *in chapter 4.1 in the css2 spec. *at-rule : ATKEYWORD S* any* [ block | ';' S* ]; *@param a_this the current instance of #CRParser. *@return CR_OK upon successfull completion, an error code *otherwise. */ static enum CRStatus cr_parser_parse_atrule_core (CRParser * a_this) { CRToken *token = NULL; CRInputPos init_pos; enum CRStatus status = CR_ERROR; g_return_val_if_fail (a_this && PRIVATE (a_this), CR_BAD_PARAM_ERROR); RECORD_INITIAL_POS (a_this, &init_pos); status = cr_tknzr_get_next_token (PRIVATE (a_this)->tknzr, &token); ENSURE_PARSING_COND (status == CR_OK && token && (token->type == ATKEYWORD_TK || token->type == IMPORT_SYM_TK || token->type == PAGE_SYM_TK || token->type == MEDIA_SYM_TK || token->type == FONT_FACE_SYM_TK || token->type == CHARSET_SYM_TK)); cr_token_destroy (token); token = NULL; cr_parser_try_to_skip_spaces_and_comments (a_this); do { status = cr_parser_parse_any_core (a_this); } while (status == CR_OK); status = cr_tknzr_get_next_token (PRIVATE (a_this)->tknzr, &token); ENSURE_PARSING_COND (status == CR_OK && token); if (token->type == CBO_TK) { cr_tknzr_unget_token (PRIVATE (a_this)->tknzr, token); token = NULL; status = cr_parser_parse_block_core (a_this); CHECK_PARSING_STATUS (status, FALSE); goto done; } else if (token->type == SEMICOLON_TK) { goto done; } else { status = CR_PARSING_ERROR ; goto error; } done: if (token) { cr_token_destroy (token); token = NULL; } return CR_OK; error: if (token) { cr_token_destroy (token); token = NULL; } cr_tknzr_set_cur_pos (PRIVATE (a_this)->tknzr, &init_pos); return status; } /** *Parses a ruleset as defined by the css core grammar in chapter *4.1 of the css2 spec. *ruleset ::= selector? '{' S* declaration? [ ';' S* declaration? ]* '}' S*; *@param a_this the current instance of #CRParser. *@return CR_OK upon successfull completion, an error code otherwise. */ static enum CRStatus cr_parser_parse_ruleset_core (CRParser * a_this) { CRToken *token = NULL; CRInputPos init_pos; enum CRStatus status = CR_ERROR; g_return_val_if_fail (a_this && PRIVATE (a_this), CR_BAD_PARAM_ERROR); RECORD_INITIAL_POS (a_this, &init_pos); status = cr_parser_parse_selector_core (a_this); ENSURE_PARSING_COND (status == CR_OK || status == CR_PARSING_ERROR || status == CR_END_OF_INPUT_ERROR); status = cr_tknzr_get_next_token (PRIVATE (a_this)->tknzr, &token); ENSURE_PARSING_COND (status == CR_OK && token && token->type == CBO_TK); cr_token_destroy (token); token = NULL; cr_parser_try_to_skip_spaces_and_comments (a_this); status = cr_parser_parse_declaration_core (a_this); parse_declaration_list: if (token) { cr_token_destroy (token); token = NULL; } status = cr_tknzr_get_next_token (PRIVATE (a_this)->tknzr, &token); ENSURE_PARSING_COND (status == CR_OK && token); if (token->type == CBC_TK) { goto done; } ENSURE_PARSING_COND (status == CR_OK && token && token->type == SEMICOLON_TK); cr_token_destroy (token); token = NULL; cr_parser_try_to_skip_spaces_and_comments (a_this); status = cr_parser_parse_declaration_core (a_this); cr_parser_clear_errors (a_this); ENSURE_PARSING_COND (status == CR_OK || status == CR_PARSING_ERROR); status = cr_tknzr_get_next_token (PRIVATE (a_this)->tknzr, &token); ENSURE_PARSING_COND (status == CR_OK && token); if (token->type == CBC_TK) { cr_token_destroy (token); token = NULL; cr_parser_try_to_skip_spaces_and_comments (a_this); goto done; } else { status = cr_tknzr_unget_token (PRIVATE (a_this)->tknzr, token); token = NULL; goto parse_declaration_list; } done: if (token) { cr_token_destroy (token); token = NULL; } if (status == CR_OK) { return CR_OK; } error: if (token) { cr_token_destroy (token); token = NULL; } cr_tknzr_set_cur_pos (PRIVATE (a_this)->tknzr, &init_pos); return status; } /** *Parses a "selector" as specified by the css core *grammar. *selector : any+; *@param a_this the current instance of #CRParser. *@return CR_OK upon successfull completion, an error code *otherwise. */ static enum CRStatus cr_parser_parse_selector_core (CRParser * a_this) { CRToken *token = NULL; CRInputPos init_pos; enum CRStatus status = CR_ERROR; g_return_val_if_fail (a_this && PRIVATE (a_this), CR_BAD_PARAM_ERROR); RECORD_INITIAL_POS (a_this, &init_pos); status = cr_parser_parse_any_core (a_this); CHECK_PARSING_STATUS (status, FALSE); do { status = cr_parser_parse_any_core (a_this); } while (status == CR_OK); return CR_OK; error: if (token) { cr_token_destroy (token); token = NULL; } cr_tknzr_set_cur_pos (PRIVATE (a_this)->tknzr, &init_pos); return status; } /** *Parses a "block" as defined in the css core grammar *in chapter 4.1 of the css2 spec. *block ::= '{' S* [ any | block | ATKEYWORD S* | ';' ]* '}' S*; *@param a_this the current instance of #CRParser. *FIXME: code this function. */ static enum CRStatus cr_parser_parse_block_core (CRParser * a_this) { CRToken *token = NULL; CRInputPos init_pos; enum CRStatus status = CR_ERROR; g_return_val_if_fail (a_this && PRIVATE (a_this), CR_BAD_PARAM_ERROR); RECORD_INITIAL_POS (a_this, &init_pos); status = cr_tknzr_get_next_token (PRIVATE (a_this)->tknzr, &token); ENSURE_PARSING_COND (status == CR_OK && token && token->type == CBO_TK); parse_block_content: if (token) { cr_token_destroy (token); token = NULL; } cr_parser_try_to_skip_spaces_and_comments (a_this); status = cr_tknzr_get_next_token (PRIVATE (a_this)->tknzr, &token); ENSURE_PARSING_COND (status == CR_OK && token); if (token->type == CBC_TK) { cr_parser_try_to_skip_spaces_and_comments (a_this); goto done; } else if (token->type == SEMICOLON_TK) { goto parse_block_content; } else if (token->type == ATKEYWORD_TK) { cr_parser_try_to_skip_spaces_and_comments (a_this); goto parse_block_content; } else if (token->type == CBO_TK) { cr_tknzr_unget_token (PRIVATE (a_this)->tknzr, token); token = NULL; status = cr_parser_parse_block_core (a_this); CHECK_PARSING_STATUS (status, FALSE); goto parse_block_content; } else { cr_tknzr_unget_token (PRIVATE (a_this)->tknzr, token); token = NULL; status = cr_parser_parse_any_core (a_this); CHECK_PARSING_STATUS (status, FALSE); goto parse_block_content; } done: if (token) { cr_token_destroy (token); token = NULL; } if (status == CR_OK) return CR_OK; error: if (token) { cr_token_destroy (token); token = NULL; } cr_tknzr_set_cur_pos (PRIVATE (a_this)->tknzr, &init_pos); return status; } static enum CRStatus cr_parser_parse_declaration_core (CRParser * a_this) { CRToken *token = NULL; CRInputPos init_pos; enum CRStatus status = CR_ERROR; CRString *prop = NULL; g_return_val_if_fail (a_this && PRIVATE (a_this), CR_BAD_PARAM_ERROR); RECORD_INITIAL_POS (a_this, &init_pos); status = cr_parser_parse_property (a_this, &prop); CHECK_PARSING_STATUS (status, FALSE); cr_parser_clear_errors (a_this); ENSURE_PARSING_COND (status == CR_OK && prop); cr_string_destroy (prop); prop = NULL; status = cr_tknzr_get_next_token (PRIVATE (a_this)->tknzr, &token); ENSURE_PARSING_COND (status == CR_OK && token && token->type == DELIM_TK && token->u.unichar == ':'); cr_token_destroy (token); token = NULL; cr_parser_try_to_skip_spaces_and_comments (a_this); status = cr_parser_parse_value_core (a_this); CHECK_PARSING_STATUS (status, FALSE); return CR_OK; error: if (prop) { cr_string_destroy (prop); prop = NULL; } if (token) { cr_token_destroy (token); token = NULL; } cr_tknzr_set_cur_pos (PRIVATE (a_this)->tknzr, &init_pos); return status; } /** *Parses a "value" production as defined by the css core grammar *in chapter 4.1. *value ::= [ any | block | ATKEYWORD S* ]+; *@param a_this the current instance of #CRParser. *@return CR_OK upon successfull completion, an error code otherwise. */ static enum CRStatus cr_parser_parse_value_core (CRParser * a_this) { CRToken *token = NULL; CRInputPos init_pos; enum CRStatus status = CR_ERROR; glong ref = 0; g_return_val_if_fail (a_this && PRIVATE (a_this), CR_BAD_PARAM_ERROR); RECORD_INITIAL_POS (a_this, &init_pos); continue_parsing: if (token) { cr_token_destroy (token); token = NULL; } status = cr_tknzr_get_next_token (PRIVATE (a_this)->tknzr, &token); ENSURE_PARSING_COND (status == CR_OK && token); switch (token->type) { case CBO_TK: status = cr_tknzr_unget_token (PRIVATE (a_this)->tknzr, token); token = NULL; status = cr_parser_parse_block_core (a_this); CHECK_PARSING_STATUS (status, FALSE); ref++; goto continue_parsing; case ATKEYWORD_TK: cr_parser_try_to_skip_spaces_and_comments (a_this); ref++; goto continue_parsing; default: status = cr_tknzr_unget_token (PRIVATE (a_this)->tknzr, token); token = NULL; status = cr_parser_parse_any_core (a_this); if (status == CR_OK) { ref++; goto continue_parsing; } else if (status == CR_PARSING_ERROR) { status = CR_OK; goto done; } else { goto error; } } done: if (token) { cr_token_destroy (token); token = NULL; } if (status == CR_OK && ref) return CR_OK; error: if (token) { cr_token_destroy (token); token = NULL; } cr_tknzr_set_cur_pos (PRIVATE (a_this)->tknzr, &init_pos); return status; } /** *Parses an "any" as defined by the css core grammar in the *css2 spec in chapter 4.1. *any ::= [ IDENT | NUMBER | PERCENTAGE | DIMENSION | STRING * | DELIM | URI | HASH | UNICODE-RANGE | INCLUDES * | FUNCTION | DASHMATCH | '(' any* ')' | '[' any* ']' ] S*; * *@param a_this the current instance of #CRParser. *@return CR_OK upon successfull completion, an error code otherwise. */ static enum CRStatus cr_parser_parse_any_core (CRParser * a_this) { CRToken *token1 = NULL, *token2 = NULL; CRInputPos init_pos; enum CRStatus status = CR_ERROR; g_return_val_if_fail (a_this, CR_BAD_PARAM_ERROR); RECORD_INITIAL_POS (a_this, &init_pos); status = cr_tknzr_get_next_token (PRIVATE (a_this)->tknzr, &token1); ENSURE_PARSING_COND (status == CR_OK && token1); switch (token1->type) { case IDENT_TK: case NUMBER_TK: case RGB_TK: case PERCENTAGE_TK: case DIMEN_TK: case EMS_TK: case EXS_TK: case LENGTH_TK: case ANGLE_TK: case FREQ_TK: case TIME_TK: case STRING_TK: case DELIM_TK: case URI_TK: case HASH_TK: case UNICODERANGE_TK: case INCLUDES_TK: case DASHMATCH_TK: case S_TK: case COMMENT_TK: case IMPORTANT_SYM_TK: status = CR_OK; break; case FUNCTION_TK: /* *this case isn't specified by the spec but it *does happen. So we have to handle it. *We must consider function with parameters. *We consider parameter as being an "any*" production. */ do { status = cr_parser_parse_any_core (a_this); } while (status == CR_OK); ENSURE_PARSING_COND (status == CR_PARSING_ERROR); status = cr_tknzr_get_next_token (PRIVATE (a_this)->tknzr, &token2); ENSURE_PARSING_COND (status == CR_OK && token2 && token2->type == PC_TK); break; case PO_TK: status = cr_tknzr_get_next_token (PRIVATE (a_this)->tknzr, &token2); ENSURE_PARSING_COND (status == CR_OK && token2); if (token2->type == PC_TK) { cr_token_destroy (token2); token2 = NULL; goto done; } else { status = cr_tknzr_unget_token (PRIVATE (a_this)->tknzr, token2); token2 = NULL; } do { status = cr_parser_parse_any_core (a_this); } while (status == CR_OK); ENSURE_PARSING_COND (status == CR_PARSING_ERROR); status = cr_tknzr_get_next_token (PRIVATE (a_this)->tknzr, &token2); ENSURE_PARSING_COND (status == CR_OK && token2 && token2->type == PC_TK); status = CR_OK; break; case BO_TK: status = cr_tknzr_get_next_token (PRIVATE (a_this)->tknzr, &token2); ENSURE_PARSING_COND (status == CR_OK && token2); if (token2->type == BC_TK) { cr_token_destroy (token2); token2 = NULL; goto done; } else { status = cr_tknzr_unget_token (PRIVATE (a_this)->tknzr, token2); token2 = NULL; } do { status = cr_parser_parse_any_core (a_this); } while (status == CR_OK); ENSURE_PARSING_COND (status == CR_PARSING_ERROR); status = cr_tknzr_get_next_token (PRIVATE (a_this)->tknzr, &token2); ENSURE_PARSING_COND (status == CR_OK && token2 && token2->type == BC_TK); status = CR_OK; break; default: status = CR_PARSING_ERROR; goto error; } done: if (token1) { cr_token_destroy (token1); token1 = NULL; } if (token2) { cr_token_destroy (token2); token2 = NULL; } return CR_OK; error: if (token1) { cr_token_destroy (token1); token1 = NULL; } if (token2) { cr_token_destroy (token2); token2 = NULL; } cr_tknzr_set_cur_pos (PRIVATE (a_this)->tknzr, &init_pos); return status; } /** *Parses an attribute selector as defined in the css2 spec in *appendix D.1: *attrib ::= '[' S* IDENT S* [ [ '=' | INCLUDES | DASHMATCH ] S* * [ IDENT | STRING ] S* ]? ']' * *@param a_this the "this pointer" of the current instance of *#CRParser . *@param a_sel out parameter. The successfully parsed attribute selector. *@return CR_OK upon successfull completion, an error code otherwise. */ static enum CRStatus cr_parser_parse_attribute_selector (CRParser * a_this, CRAttrSel ** a_sel) { enum CRStatus status = CR_OK; CRInputPos init_pos; CRToken *token = NULL; CRAttrSel *result = NULL; CRParsingLocation location = {0,0,0} ; g_return_val_if_fail (a_this && a_sel, CR_BAD_PARAM_ERROR); RECORD_INITIAL_POS (a_this, &init_pos); status = cr_tknzr_get_next_token (PRIVATE (a_this)->tknzr, &token); ENSURE_PARSING_COND (status == CR_OK && token && token->type == BO_TK); cr_parsing_location_copy (&location, &token->location) ; cr_token_destroy (token); token = NULL; cr_parser_try_to_skip_spaces_and_comments (a_this); result = cr_attr_sel_new (); if (!result) { cr_utils_trace_info ("result failed") ; status = CR_OUT_OF_MEMORY_ERROR ; goto error ; } cr_parsing_location_copy (&result->location, &location) ; status = cr_tknzr_get_next_token (PRIVATE (a_this)->tknzr, &token); ENSURE_PARSING_COND (status == CR_OK && token && token->type == IDENT_TK); result->name = token->u.str; token->u.str = NULL; cr_token_destroy (token); token = NULL; cr_parser_try_to_skip_spaces_and_comments (a_this); status = cr_tknzr_get_next_token (PRIVATE (a_this)->tknzr, &token); ENSURE_PARSING_COND (status == CR_OK && token); if (token->type == INCLUDES_TK) { result->match_way = INCLUDES; goto parse_right_part; } else if (token->type == DASHMATCH_TK) { result->match_way = DASHMATCH; goto parse_right_part; } else if (token->type == DELIM_TK && token->u.unichar == '=') { result->match_way = EQUALS; goto parse_right_part; } else if (token->type == BC_TK) { result->match_way = SET; goto done; } parse_right_part: if (token) { cr_token_destroy (token); token = NULL; } cr_parser_try_to_skip_spaces_and_comments (a_this); status = cr_tknzr_get_next_token (PRIVATE (a_this)->tknzr, &token); ENSURE_PARSING_COND (status == CR_OK && token); if (token->type == IDENT_TK) { result->value = token->u.str; token->u.str = NULL; } else if (token->type == STRING_TK) { result->value = token->u.str; token->u.str = NULL; } else { status = CR_PARSING_ERROR; goto error; } if (token) { cr_token_destroy (token); token = NULL; } cr_parser_try_to_skip_spaces_and_comments (a_this); status = cr_tknzr_get_next_token (PRIVATE (a_this)->tknzr, &token); ENSURE_PARSING_COND (status == CR_OK && token && token->type == BC_TK); done: if (token) { cr_token_destroy (token); token = NULL; } if (*a_sel) { status = cr_attr_sel_append_attr_sel (*a_sel, result); CHECK_PARSING_STATUS (status, FALSE); } else { *a_sel = result; } cr_parser_clear_errors (a_this); return CR_OK; error: if (result) { cr_attr_sel_destroy (result); result = NULL; } if (token) { cr_token_destroy (token); token = NULL; } cr_tknzr_set_cur_pos (PRIVATE (a_this)->tknzr, &init_pos); return status; } /** *Parses a "property" as specified by the css2 spec at [4.1.1]: *property : IDENT S*; * *@param a_this the "this pointer" of the current instance of #CRParser. *@param GString a_property out parameter. The parsed property without the *trailing spaces. If *a_property is NULL, this function allocates a *new instance of GString and set it content to the parsed property. *If not, the property is just appended to a_property's previous content. *In both cases, it is up to the caller to free a_property. *@return CR_OK upon successfull completion, CR_PARSING_ERROR if the *next construction was not a "property", or an error code. */ static enum CRStatus cr_parser_parse_property (CRParser * a_this, CRString ** a_property) { enum CRStatus status = CR_OK; CRInputPos init_pos; g_return_val_if_fail (a_this && PRIVATE (a_this) && PRIVATE (a_this)->tknzr && a_property, CR_BAD_PARAM_ERROR); RECORD_INITIAL_POS (a_this, &init_pos); status = cr_parser_parse_ident (a_this, a_property); CHECK_PARSING_STATUS (status, TRUE); cr_parser_try_to_skip_spaces_and_comments (a_this); cr_parser_clear_errors (a_this); return CR_OK; error: cr_tknzr_set_cur_pos (PRIVATE (a_this)->tknzr, &init_pos); return status; } /** *Parses a "term" as defined in the css2 spec, appendix D.1: *term ::= unary_operator? [NUMBER S* | PERCENTAGE S* | LENGTH S* | *EMS S* | EXS S* | ANGLE S* | TIME S* | FREQ S* | function ] | *STRING S* | IDENT S* | URI S* | RGB S* | UNICODERANGE S* | hexcolor * *TODO: handle parsing of 'RGB' * *@param a_term out parameter. The successfully parsed term. *@return CR_OK upon successfull completion, an error code otherwise. */ enum CRStatus cr_parser_parse_term (CRParser * a_this, CRTerm ** a_term) { enum CRStatus status = CR_PARSING_ERROR; CRInputPos init_pos; CRTerm *result = NULL; CRTerm *param = NULL; CRToken *token = NULL; CRString *func_name = NULL; CRParsingLocation location = {0,0,0} ; g_return_val_if_fail (a_this && a_term, CR_BAD_PARAM_ERROR); RECORD_INITIAL_POS (a_this, &init_pos); result = cr_term_new (); status = cr_tknzr_get_next_token (PRIVATE (a_this)->tknzr, &token); if (status != CR_OK || !token) goto error; cr_parsing_location_copy (&location, &token->location) ; if (token->type == DELIM_TK && token->u.unichar == '+') { result->unary_op = PLUS_UOP; cr_token_destroy (token) ; token = NULL ; cr_parser_try_to_skip_spaces_and_comments (a_this); status = cr_tknzr_get_next_token (PRIVATE (a_this)->tknzr, &token); if (status != CR_OK || !token) goto error; } else if (token->type == DELIM_TK && token->u.unichar == '-') { result->unary_op = MINUS_UOP; cr_token_destroy (token) ; token = NULL ; cr_parser_try_to_skip_spaces_and_comments (a_this); status = cr_tknzr_get_next_token (PRIVATE (a_this)->tknzr, &token); if (status != CR_OK || !token) goto error; } if (token->type == EMS_TK || token->type == EXS_TK || token->type == LENGTH_TK || token->type == ANGLE_TK || token->type == TIME_TK || token->type == FREQ_TK || token->type == PERCENTAGE_TK || token->type == NUMBER_TK) { status = cr_term_set_number (result, token->u.num); CHECK_PARSING_STATUS (status, TRUE); token->u.num = NULL; status = CR_OK; } else if (token && token->type == FUNCTION_TK) { status = cr_tknzr_unget_token (PRIVATE (a_this)->tknzr, token); token = NULL; status = cr_parser_parse_function (a_this, &func_name, ¶m); if (status == CR_OK) { status = cr_term_set_function (result, func_name, param); CHECK_PARSING_STATUS (status, TRUE); } } else if (token && token->type == STRING_TK) { status = cr_term_set_string (result, token->u.str); CHECK_PARSING_STATUS (status, TRUE); token->u.str = NULL; } else if (token && token->type == IDENT_TK) { status = cr_term_set_ident (result, token->u.str); CHECK_PARSING_STATUS (status, TRUE); token->u.str = NULL; } else if (token && token->type == URI_TK) { status = cr_term_set_uri (result, token->u.str); CHECK_PARSING_STATUS (status, TRUE); token->u.str = NULL; } else if (token && token->type == RGB_TK) { status = cr_term_set_rgb (result, token->u.rgb); CHECK_PARSING_STATUS (status, TRUE); token->u.rgb = NULL; } else if (token && token->type == UNICODERANGE_TK) { result->type = TERM_UNICODERANGE; status = CR_PARSING_ERROR; } else if (token && token->type == HASH_TK) { status = cr_term_set_hash (result, token->u.str); CHECK_PARSING_STATUS (status, TRUE); token->u.str = NULL; } else { status = CR_PARSING_ERROR; } if (status != CR_OK) { goto error; } cr_parsing_location_copy (&result->location, &location) ; *a_term = cr_term_append_term (*a_term, result); result = NULL; cr_parser_try_to_skip_spaces_and_comments (a_this); if (token) { cr_token_destroy (token); token = NULL; } cr_parser_clear_errors (a_this); return CR_OK; error: if (result) { cr_term_destroy (result); result = NULL; } if (token) { cr_token_destroy (token); token = NULL; } if (param) { cr_term_destroy (param); param = NULL; } if (func_name) { cr_string_destroy (func_name); func_name = NULL; } cr_tknzr_set_cur_pos (PRIVATE (a_this)->tknzr, &init_pos); return status; } /** *Parses a "simple_selector" as defined by the css2 spec in appendix D.1 : *element_name? [ HASH | class | attrib | pseudo ]* S* *and where pseudo is: *pseudo ::= ':' [ IDENT | FUNCTION S* IDENT S* ')' ] * *@Param a_this the "this pointer" of the current instance of #CRParser. *@param a_sel out parameter. Is set to the successfully parsed simple *selector. *@return CR_OK upon successfull completion, an error code otherwise. */ static enum CRStatus cr_parser_parse_simple_selector (CRParser * a_this, CRSimpleSel ** a_sel) { enum CRStatus status = CR_ERROR; CRInputPos init_pos; CRToken *token = NULL; CRSimpleSel *sel = NULL; CRAdditionalSel *add_sel_list = NULL; gboolean found_sel = FALSE; guint32 cur_char = 0; g_return_val_if_fail (a_this && a_sel, CR_BAD_PARAM_ERROR); RECORD_INITIAL_POS (a_this, &init_pos); status = cr_tknzr_get_next_token (PRIVATE (a_this)->tknzr, &token); if (status != CR_OK) goto error; sel = cr_simple_sel_new (); ENSURE_PARSING_COND (sel); cr_parsing_location_copy (&sel->location, &token->location) ; if (token && token->type == DELIM_TK && token->u.unichar == '*') { int comb = (int)sel->type_mask | (int) UNIVERSAL_SELECTOR; sel->type_mask = (enum SimpleSelectorType)comb; //sel->type_mask |= UNIVERSAL_SELECTOR; sel->name = cr_string_new_from_string ("*"); found_sel = TRUE; } else if (token && token->type == IDENT_TK) { sel->name = token->u.str; int comb = (int)sel->type_mask | (int) TYPE_SELECTOR; sel->type_mask = (enum SimpleSelectorType)comb; //sel->type_mask |= TYPE_SELECTOR; token->u.str = NULL; found_sel = TRUE; } else { status = cr_tknzr_unget_token (PRIVATE (a_this)->tknzr, token); token = NULL; } if (token) { cr_token_destroy (token); token = NULL; } cr_parser_try_to_skip_spaces_and_comments (a_this); for (;;) { if (token) { cr_token_destroy (token); token = NULL; } status = cr_tknzr_get_next_token (PRIVATE (a_this)->tknzr, &token); if (status != CR_OK) goto error; if (token && token->type == HASH_TK) { /*we parsed an attribute id */ CRAdditionalSel *add_sel = NULL; add_sel = cr_additional_sel_new_with_type (ID_ADD_SELECTOR); add_sel->content.id_name = token->u.str; token->u.str = NULL; cr_parsing_location_copy (&add_sel->location, &token->location) ; add_sel_list = cr_additional_sel_append (add_sel_list, add_sel); found_sel = TRUE; } else if (token && (token->type == DELIM_TK) && (token->u.unichar == '.')) { cr_token_destroy (token); token = NULL; status = cr_tknzr_get_next_token (PRIVATE (a_this)->tknzr, &token); if (status != CR_OK) goto error; if (token && token->type == IDENT_TK) { CRAdditionalSel *add_sel = NULL; add_sel = cr_additional_sel_new_with_type (CLASS_ADD_SELECTOR); add_sel->content.class_name = token->u.str; token->u.str = NULL; add_sel_list = cr_additional_sel_append (add_sel_list, add_sel); found_sel = TRUE; cr_parsing_location_copy (&add_sel->location, & token->location) ; } else { status = CR_PARSING_ERROR; goto error; } } else if (token && token->type == BO_TK) { CRAttrSel *attr_sel = NULL; CRAdditionalSel *add_sel = NULL; status = cr_tknzr_unget_token (PRIVATE (a_this)->tknzr, token); if (status != CR_OK) goto error; token = NULL; status = cr_parser_parse_attribute_selector (a_this, &attr_sel); CHECK_PARSING_STATUS (status, FALSE); add_sel = cr_additional_sel_new_with_type (ATTRIBUTE_ADD_SELECTOR); ENSURE_PARSING_COND (add_sel != NULL); add_sel->content.attr_sel = attr_sel; add_sel_list = cr_additional_sel_append (add_sel_list, add_sel); found_sel = TRUE; cr_parsing_location_copy (&add_sel->location, &attr_sel->location) ; } else if (token && (token->type == DELIM_TK) && (token->u.unichar == ':')) { CRPseudo *pseudo = NULL; /*try to parse a pseudo */ if (token) { cr_token_destroy (token); token = NULL; } pseudo = cr_pseudo_new (); status = cr_tknzr_get_next_token (PRIVATE (a_this)->tknzr, &token); ENSURE_PARSING_COND (status == CR_OK && token); cr_parsing_location_copy (&pseudo->location, &token->location) ; if (token->type == IDENT_TK) { pseudo->type = IDENT_PSEUDO; pseudo->name = token->u.str; token->u.str = NULL; found_sel = TRUE; } else if (token->type == FUNCTION_TK) { pseudo->name = token->u.str; token->u.str = NULL; cr_parser_try_to_skip_spaces_and_comments (a_this); status = cr_parser_parse_ident (a_this, &pseudo->extra); ENSURE_PARSING_COND (status == CR_OK); READ_NEXT_CHAR (a_this, &cur_char); ENSURE_PARSING_COND (cur_char == ')'); pseudo->type = FUNCTION_PSEUDO; found_sel = TRUE; } else { status = CR_PARSING_ERROR; goto error; } if (status == CR_OK) { CRAdditionalSel *add_sel = NULL; add_sel = cr_additional_sel_new_with_type (PSEUDO_CLASS_ADD_SELECTOR); add_sel->content.pseudo = pseudo; cr_parsing_location_copy (&add_sel->location, &pseudo->location) ; add_sel_list = cr_additional_sel_append (add_sel_list, add_sel); status = CR_OK; } } else { status = cr_tknzr_unget_token (PRIVATE (a_this)->tknzr, token); token = NULL; break; } } if (status == CR_OK && found_sel == TRUE) { cr_parser_try_to_skip_spaces_and_comments (a_this); sel->add_sel = add_sel_list; add_sel_list = NULL; if (*a_sel == NULL) { *a_sel = sel; } else { cr_simple_sel_append_simple_sel (*a_sel, sel); } sel = NULL; if (token) { cr_token_destroy (token); token = NULL; } cr_parser_clear_errors (a_this); return CR_OK; } else { status = CR_PARSING_ERROR; } error: if (token) { cr_token_destroy (token); token = NULL; } if (add_sel_list) { cr_additional_sel_destroy (add_sel_list); add_sel_list = NULL; } if (sel) { cr_simple_sel_destroy (sel); sel = NULL; } cr_tknzr_set_cur_pos (PRIVATE (a_this)->tknzr, &init_pos); return status; } /** *Parses a "selector" as defined by the css2 spec in appendix D.1: *selector ::= simple_selector [ combinator simple_selector ]* * *@param a_this the this pointer of the current instance of #CRParser. *@param a_start a pointer to the *first chararcter of the successfully parsed *string. *@param a_end a pointer to the last character of the successfully parsed *string. *@return CR_OK upon successfull completion, an error code otherwise. */ static enum CRStatus cr_parser_parse_simple_sels (CRParser * a_this, CRSimpleSel ** a_sel) { enum CRStatus status = CR_ERROR; CRInputPos init_pos; CRSimpleSel *sel = NULL; guint32 cur_char = 0; g_return_val_if_fail (a_this && PRIVATE (a_this) && a_sel, CR_BAD_PARAM_ERROR); RECORD_INITIAL_POS (a_this, &init_pos); status = cr_parser_parse_simple_selector (a_this, &sel); CHECK_PARSING_STATUS (status, FALSE); *a_sel = cr_simple_sel_append_simple_sel (*a_sel, sel); for (;;) { guint32 next_char = 0; int comb = 0; sel = NULL; PEEK_NEXT_CHAR (a_this, &next_char); if (next_char == '+') { READ_NEXT_CHAR (a_this, &cur_char); comb = COMB_PLUS; cr_parser_try_to_skip_spaces_and_comments (a_this); } else if (next_char == '>') { READ_NEXT_CHAR (a_this, &cur_char); comb = COMB_GT; cr_parser_try_to_skip_spaces_and_comments (a_this); } else { comb = COMB_WS; } status = cr_parser_parse_simple_selector (a_this, &sel); if (status != CR_OK) break; if (comb && sel) { sel->combinator = (enum Combinator)comb; comb = 0; } if (sel) { *a_sel = cr_simple_sel_append_simple_sel (*a_sel, sel) ; } } cr_parser_clear_errors (a_this); return CR_OK; error: cr_tknzr_set_cur_pos (PRIVATE (a_this)->tknzr, &init_pos); return status; } /** *Parses a comma separated list of selectors. *@param a_this the current instance of #CRParser. *@param a_selector the parsed list of comma separated *selectors. *@return CR_OK upon successful completion, an error *code otherwise. */ static enum CRStatus cr_parser_parse_selector (CRParser * a_this, CRSelector ** a_selector) { enum CRStatus status = CR_OK; CRInputPos init_pos; guint32 cur_char = 0, next_char = 0; CRSimpleSel *simple_sels = NULL; CRSelector *selector = NULL; g_return_val_if_fail (a_this && a_selector, CR_BAD_PARAM_ERROR); RECORD_INITIAL_POS (a_this, &init_pos); status = cr_parser_parse_simple_sels (a_this, &simple_sels); CHECK_PARSING_STATUS (status, FALSE); if (simple_sels) { selector = cr_selector_append_simple_sel (selector, simple_sels); if (selector) { cr_parsing_location_copy (&selector->location, &simple_sels->location) ; } simple_sels = NULL; } else { status = CR_PARSING_ERROR ; goto error ; } status = cr_tknzr_peek_char (PRIVATE (a_this)->tknzr, &next_char); if (status != CR_OK) { if (status == CR_END_OF_INPUT_ERROR) { status = CR_OK; goto okay; } else { goto error; } } if (next_char == ',') { for (;;) { simple_sels = NULL; status = cr_tknzr_peek_char (PRIVATE (a_this)->tknzr, &next_char); if (status != CR_OK) { if (status == CR_END_OF_INPUT_ERROR) { status = CR_OK; break; } else { goto error; } } if (next_char != ',') break; /*consume the ',' char */ READ_NEXT_CHAR (a_this, &cur_char); cr_parser_try_to_skip_spaces_and_comments (a_this); status = cr_parser_parse_simple_sels (a_this, &simple_sels); CHECK_PARSING_STATUS (status, FALSE); if (simple_sels) { selector = cr_selector_append_simple_sel (selector, simple_sels); simple_sels = NULL; } } } okay: cr_parser_try_to_skip_spaces_and_comments (a_this); if (!*a_selector) { *a_selector = selector; } else { *a_selector = cr_selector_append (*a_selector, selector); } selector = NULL; return CR_OK; error: if (simple_sels) { cr_simple_sel_destroy (simple_sels); simple_sels = NULL; } if (selector) { cr_selector_unref (selector); selector = NULL; } cr_tknzr_set_cur_pos (PRIVATE (a_this)->tknzr, &init_pos); return status; } /** *Parses a "function" as defined in css spec at appendix D.1: *function ::= FUNCTION S* expr ')' S* *FUNCTION ::= ident'(' * *@param a_this the "this pointer" of the current instance of *#CRParser. * *@param a_func_name out parameter. The parsed function name *@param a_expr out parameter. The successfully parsed term. *@return CR_OK upon successfull completion, an error code otherwise. */ static enum CRStatus cr_parser_parse_function (CRParser * a_this, CRString ** a_func_name, CRTerm ** a_expr) { CRInputPos init_pos; enum CRStatus status = CR_OK; CRToken *token = NULL; CRTerm *expr = NULL; g_return_val_if_fail (a_this && PRIVATE (a_this) && a_func_name, CR_BAD_PARAM_ERROR); RECORD_INITIAL_POS (a_this, &init_pos); status = cr_tknzr_get_next_token (PRIVATE (a_this)->tknzr, &token); if (status != CR_OK) goto error; if (token && token->type == FUNCTION_TK) { *a_func_name = token->u.str; token->u.str = NULL; } else { status = CR_PARSING_ERROR; goto error; } cr_token_destroy (token); token = NULL; cr_parser_try_to_skip_spaces_and_comments (a_this) ; status = cr_parser_parse_expr (a_this, &expr); CHECK_PARSING_STATUS (status, FALSE); status = cr_tknzr_get_next_token (PRIVATE (a_this)->tknzr, &token); if (status != CR_OK) goto error; ENSURE_PARSING_COND (token && token->type == PC_TK); cr_token_destroy (token); token = NULL; if (expr) { *a_expr = cr_term_append_term (*a_expr, expr); expr = NULL; } cr_parser_clear_errors (a_this); return CR_OK; error: if (*a_func_name) { cr_string_destroy (*a_func_name); *a_func_name = NULL; } if (expr) { cr_term_destroy (expr); expr = NULL; } if (token) { cr_token_destroy (token); } cr_tknzr_set_cur_pos (PRIVATE (a_this)->tknzr, &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 #CRParser. *@param a_str the successfully parsed url. *@return CR_OK upon successfull completion, an error code otherwise. */ static enum CRStatus cr_parser_parse_uri (CRParser * a_this, CRString ** a_str) { enum CRStatus status = CR_PARSING_ERROR; g_return_val_if_fail (a_this && PRIVATE (a_this) && PRIVATE (a_this)->tknzr, CR_BAD_PARAM_ERROR); status = cr_tknzr_parse_token (PRIVATE (a_this)->tknzr, URI_TK, NO_ET, a_str, NULL); 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 #CRParser. *@param a_start out parameter. Upon successfull completion, *points to the beginning of the string, points to an undefined value *otherwise. *@param a_end out parameter. Upon successfull completion, points to *the beginning of the string, points to an undefined value otherwise. *@return CR_OK upon successfull completion, an error code otherwise. */ static enum CRStatus cr_parser_parse_string (CRParser * a_this, CRString ** a_str) { enum CRStatus status = CR_OK; g_return_val_if_fail (a_this && PRIVATE (a_this) && PRIVATE (a_this)->tknzr && a_str, CR_BAD_PARAM_ERROR); status = cr_tknzr_parse_token (PRIVATE (a_this)->tknzr, STRING_TK, NO_ET, a_str, NULL); return status; } /** *Parses an "ident" as defined in css spec [4.1.1]: *ident ::= {nmstart}{nmchar}* * *@param a_this the currens instance of #CRParser. * *@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 successfull completion, an error code *otherwise. */ static enum CRStatus cr_parser_parse_ident (CRParser * a_this, CRString ** a_str) { enum CRStatus status = CR_OK; g_return_val_if_fail (a_this && PRIVATE (a_this) && PRIVATE (a_this)->tknzr && a_str, CR_BAD_PARAM_ERROR); status = cr_tknzr_parse_token (PRIVATE (a_this)->tknzr, IDENT_TK, NO_ET, a_str, NULL); return status; } /** *the next rule is ignored as well. This seems to be a bug *Parses a stylesheet as defined in the css2 spec in appendix D.1: *stylesheet ::= [ CHARSET_SYM S* STRING S* ';' ]? * [S|CDO|CDC]* [ import [S|CDO|CDC]* ]* * [ [ ruleset | media | page | font_face ] [S|CDO|CDC]* ]* * *TODO: Finish the code of this function. Think about splitting it into *smaller functions. * *@param a_this the "this pointer" of the current instance of #CRParser. *@param a_start out parameter. A pointer to the first character of *the successfully parsed string. *@param a_end out parameter. A pointer to the first character of *the successfully parsed string. * *@return CR_OK upon successfull completion, an error code otherwise. */ static enum CRStatus cr_parser_parse_stylesheet (CRParser * a_this) { enum CRStatus status = CR_OK; CRInputPos init_pos; CRToken *token = NULL; CRString *charset = NULL; g_return_val_if_fail (a_this && PRIVATE (a_this) && PRIVATE (a_this)->tknzr, CR_BAD_PARAM_ERROR); RECORD_INITIAL_POS (a_this, &init_pos); PRIVATE (a_this)->state = READY_STATE; if (PRIVATE (a_this)->sac_handler && PRIVATE (a_this)->sac_handler->start_document) { PRIVATE (a_this)->sac_handler->start_document (PRIVATE (a_this)->sac_handler); } parse_charset: status = cr_tknzr_get_next_token (PRIVATE (a_this)->tknzr, &token); if (status == CR_END_OF_INPUT_ERROR) goto done; CHECK_PARSING_STATUS (status, TRUE); if (token && token->type == CHARSET_SYM_TK) { CRParsingLocation location = {0,0,0} ; status = cr_tknzr_unget_token (PRIVATE (a_this)->tknzr, token); CHECK_PARSING_STATUS (status, TRUE); token = NULL; status = cr_parser_parse_charset (a_this, &charset, &location); if (status == CR_OK && charset) { if (PRIVATE (a_this)->sac_handler && PRIVATE (a_this)->sac_handler->charset) { PRIVATE (a_this)->sac_handler->charset (PRIVATE (a_this)->sac_handler, charset, &location); } } else if (status != CR_END_OF_INPUT_ERROR) { status = cr_parser_parse_atrule_core (a_this); CHECK_PARSING_STATUS (status, FALSE); } if (charset) { cr_string_destroy (charset); charset = NULL; } } else if (token && (token->type == S_TK || token->type == COMMENT_TK)) { status = cr_tknzr_unget_token (PRIVATE (a_this)->tknzr, token); token = NULL; CHECK_PARSING_STATUS (status, TRUE); cr_parser_try_to_skip_spaces_and_comments (a_this); goto parse_charset ; } else if (token) { status = cr_tknzr_unget_token (PRIVATE (a_this)->tknzr, token); token = NULL; CHECK_PARSING_STATUS (status, TRUE); } /* parse_imports:*/ do { if (token) { cr_token_destroy (token); token = NULL; } cr_parser_try_to_skip_spaces_and_comments (a_this) ; status = cr_tknzr_get_next_token (PRIVATE (a_this)->tknzr, &token); if (status == CR_END_OF_INPUT_ERROR) goto done; CHECK_PARSING_STATUS (status, TRUE); } while (token && (token->type == S_TK || token->type == CDO_TK || token->type == CDC_TK)); if (token) { status = cr_tknzr_unget_token (PRIVATE (a_this)->tknzr, token); token = NULL; } for (;;) { status = cr_tknzr_get_next_token (PRIVATE (a_this)->tknzr, &token); if (status == CR_END_OF_INPUT_ERROR) goto done; CHECK_PARSING_STATUS (status, TRUE); if (token && token->type == IMPORT_SYM_TK) { GList *media_list = NULL; CRString *import_string = NULL; CRParsingLocation location = {0,0,0} ; status = cr_tknzr_unget_token (PRIVATE (a_this)->tknzr, token); token = NULL; CHECK_PARSING_STATUS (status, TRUE); status = cr_parser_parse_import (a_this, &media_list, &import_string, &location); if (status == CR_OK) { if (import_string && PRIVATE (a_this)->sac_handler && PRIVATE (a_this)->sac_handler->import_style) { PRIVATE (a_this)->sac_handler->import_style (PRIVATE(a_this)->sac_handler, media_list, import_string, NULL, &location) ; if ((PRIVATE (a_this)->sac_handler->resolve_import == TRUE)) { /* *TODO: resolve the *import rule. */ } if ((PRIVATE (a_this)->sac_handler->import_style_result)) { PRIVATE (a_this)->sac_handler->import_style_result (PRIVATE (a_this)->sac_handler, media_list, import_string, NULL, NULL); } } } else if (status != CR_END_OF_INPUT_ERROR) { if (PRIVATE (a_this)->sac_handler && PRIVATE (a_this)->sac_handler->error) { PRIVATE (a_this)->sac_handler->error (PRIVATE (a_this)->sac_handler); } status = cr_parser_parse_atrule_core (a_this); CHECK_PARSING_STATUS (status, TRUE) ; } else { goto error ; } /* *then, after calling the appropriate *SAC handler, free *the media_list and import_string. */ if (media_list) { GList *cur = NULL; /*free the medium list */ for (cur = media_list; cur; cur = cur->next) { if (cur->data) { cr_string_destroy ((CRString *)cur->data); } } g_list_free (media_list); media_list = NULL; } if (import_string) { cr_string_destroy (import_string); import_string = NULL; } cr_parser_try_to_skip_spaces_and_comments (a_this); } else if (token && (token->type == S_TK || token->type == CDO_TK || token->type == CDC_TK)) { status = cr_tknzr_unget_token (PRIVATE (a_this)->tknzr, token); token = NULL; do { if (token) { cr_token_destroy (token); token = NULL; } status = cr_tknzr_get_next_token (PRIVATE (a_this)->tknzr, &token); if (status == CR_END_OF_INPUT_ERROR) goto done; CHECK_PARSING_STATUS (status, TRUE); } while (token && (token->type == S_TK || token->type == CDO_TK || token->type == CDC_TK)); } else { if (token) { status = cr_tknzr_unget_token (PRIVATE (a_this)->tknzr, token); token = NULL; } goto parse_ruleset_and_others; } } parse_ruleset_and_others: cr_parser_try_to_skip_spaces_and_comments (a_this); for (;;) { status = cr_tknzr_get_next_token (PRIVATE (a_this)->tknzr, &token); if (status == CR_END_OF_INPUT_ERROR) goto done; CHECK_PARSING_STATUS (status, TRUE); if (token && (token->type == S_TK || token->type == CDO_TK || token->type == CDC_TK)) { status = cr_tknzr_unget_token (PRIVATE (a_this)->tknzr, token); token = NULL; do { if (token) { cr_token_destroy (token); token = NULL; } cr_parser_try_to_skip_spaces_and_comments (a_this); status = cr_tknzr_get_next_token (PRIVATE (a_this)->tknzr, &token); } while (token && (token->type == S_TK || token->type == COMMENT_TK || token->type == CDO_TK || token->type == CDC_TK)); if (token) { cr_tknzr_unget_token (PRIVATE (a_this)->tknzr, token); token = NULL; } } else if (token && (token->type == HASH_TK || (token->type == DELIM_TK && token->u.unichar == '.') || (token->type == DELIM_TK && token->u.unichar == ':') || (token->type == DELIM_TK && token->u.unichar == '*') || (token->type == BO_TK) || token->type == IDENT_TK)) { /* *Try to parse a CSS2 ruleset. *if the parsing fails, try to parse *a css core ruleset. */ status = cr_tknzr_unget_token (PRIVATE (a_this)->tknzr, token); CHECK_PARSING_STATUS (status, TRUE); token = NULL; status = cr_parser_parse_ruleset (a_this); if (status == CR_OK) { continue; } else { if (PRIVATE (a_this)->sac_handler && PRIVATE (a_this)->sac_handler->error) { PRIVATE (a_this)->sac_handler-> error (PRIVATE (a_this)-> sac_handler); } status = cr_parser_parse_ruleset_core (a_this); if (status == CR_OK) { continue; } else { break; } } } else if (token && token->type == MEDIA_SYM_TK) { status = cr_tknzr_unget_token (PRIVATE (a_this)->tknzr, token); CHECK_PARSING_STATUS (status, TRUE); token = NULL; status = cr_parser_parse_media (a_this); if (status == CR_OK) { continue; } else { if (PRIVATE (a_this)->sac_handler && PRIVATE (a_this)->sac_handler->error) { PRIVATE (a_this)->sac_handler-> error (PRIVATE (a_this)-> sac_handler); } status = cr_parser_parse_atrule_core (a_this); if (status == CR_OK) { continue; } else { break; } } } else if (token && token->type == PAGE_SYM_TK) { status = cr_tknzr_unget_token (PRIVATE (a_this)->tknzr, token); CHECK_PARSING_STATUS (status, TRUE); token = NULL; status = cr_parser_parse_page (a_this); if (status == CR_OK) { continue; } else { if (PRIVATE (a_this)->sac_handler && PRIVATE (a_this)->sac_handler->error) { PRIVATE (a_this)->sac_handler-> error (PRIVATE (a_this)-> sac_handler); } status = cr_parser_parse_atrule_core (a_this); if (status == CR_OK) { continue; } else { break; } } } else if (token && token->type == FONT_FACE_SYM_TK) { status = cr_tknzr_unget_token (PRIVATE (a_this)->tknzr, token); CHECK_PARSING_STATUS (status, TRUE); token = NULL; status = cr_parser_parse_font_face (a_this); if (status == CR_OK) { continue; } else { if (PRIVATE (a_this)->sac_handler && PRIVATE (a_this)->sac_handler->error) { PRIVATE (a_this)->sac_handler-> error (PRIVATE (a_this)-> sac_handler); } status = cr_parser_parse_atrule_core (a_this); if (status == CR_OK) { continue; } else { break; } } } else { status = cr_tknzr_unget_token (PRIVATE (a_this)->tknzr, token); CHECK_PARSING_STATUS (status, TRUE); token = NULL; status = cr_parser_parse_statement_core (a_this); if (status == CR_OK) { continue; } else { break; } } } done: if (token) { cr_token_destroy (token); token = NULL; } if (status == CR_END_OF_INPUT_ERROR || status == CR_OK) { if (PRIVATE (a_this)->sac_handler && PRIVATE (a_this)->sac_handler->end_document) { PRIVATE (a_this)->sac_handler->end_document (PRIVATE (a_this)->sac_handler); } return CR_OK; } cr_parser_push_error (a_this, (guchar *)"could not recognize next production", CR_ERROR); if (PRIVATE (a_this)->sac_handler && PRIVATE (a_this)->sac_handler->unrecoverable_error) { PRIVATE (a_this)->sac_handler-> unrecoverable_error (PRIVATE (a_this)->sac_handler); } cr_parser_dump_err_stack (a_this, TRUE); return status; error: if (token) { cr_token_destroy (token); token = NULL; } if (PRIVATE (a_this)->sac_handler && PRIVATE (a_this)->sac_handler->unrecoverable_error) { PRIVATE (a_this)->sac_handler-> unrecoverable_error (PRIVATE (a_this)->sac_handler); } cr_tknzr_set_cur_pos (PRIVATE (a_this)->tknzr, &init_pos); return status; } /**************************************** *Public CRParser Methods ****************************************/ /** *Creates a new parser to parse data *coming the input stream given in parameter. *@param a_input the input stream of the parser. *Note that the newly created parser will ref *a_input and unref it when parsing reaches the *end of the input stream. *@return the newly created instance of #CRParser, *or NULL if an error occured. */ CRParser * cr_parser_new (CRTknzr * a_tknzr) { enum CRStatus status = CR_OK; CRParser *result = (CRParser *)g_malloc0 (sizeof (CRParser)); PRIVATE (result) = (CRParserPriv *)g_malloc0 (sizeof (CRParserPriv)); if (a_tknzr) { status = cr_parser_set_tknzr (result, a_tknzr); } g_return_val_if_fail (status == CR_OK, NULL); return result; } /** *Instanciates a new parser from a memory buffer. *@param a_buf the buffer to parse. *@param a_len the length of the data in the buffer. *@param a_enc the encoding of the input buffer a_buf. *@param a_free_buf if set to TRUE, a_buf will be freed *during the destruction of the newly built instance *of #CRParser. If set to FALSE, it is up to the caller to *eventually free it. *@return the newly built parser, or NULL if an error arises. */ CRParser * cr_parser_new_from_buf (guchar * a_buf, gulong a_len, enum CREncoding a_enc, gboolean a_free_buf) { CRParser *result = NULL; CRInput *input = NULL; g_return_val_if_fail (a_buf, NULL); input = cr_input_new_from_buf (a_buf, a_len, a_enc, a_free_buf); g_return_val_if_fail (input, NULL); result = cr_parser_new_from_input (input); if (!result) { cr_input_destroy (input); input = NULL; return NULL; } return result; } CRParser * cr_parser_new_from_input (CRInput * a_input) { CRParser *result = NULL; CRTknzr *tokenizer = NULL; if (a_input) { tokenizer = cr_tknzr_new (a_input); g_return_val_if_fail (tokenizer, NULL); } result = cr_parser_new (tokenizer); g_return_val_if_fail (result, NULL); return result; } CRParser * cr_parser_new_from_file (const guchar * a_file_uri, enum CREncoding a_enc) { CRParser *result = NULL; CRTknzr *tokenizer = NULL; tokenizer = cr_tknzr_new_from_uri (a_file_uri, a_enc); if (!tokenizer) { cr_utils_trace_info ("Could not open input file"); return NULL; } result = cr_parser_new (tokenizer); g_return_val_if_fail (result, NULL); return result; } /** *Sets a SAC document handler to the parser. *@param a_this the "this pointer" of the current instance of #CRParser. *@param a_handler the handler to set. *@return CR_OK upon successfull completion, an error code otherwise. */ enum CRStatus cr_parser_set_sac_handler (CRParser * a_this, CRDocHandler * a_handler) { g_return_val_if_fail (a_this, CR_BAD_PARAM_ERROR); if (PRIVATE (a_this)->sac_handler) { cr_doc_handler_unref (PRIVATE (a_this)->sac_handler); } PRIVATE (a_this)->sac_handler = a_handler; cr_doc_handler_ref (a_handler); return CR_OK; } /** *Gets the SAC document handler. *@param a_this the "this pointer" of the current instance of *#CRParser. *@param a_handler out parameter. The returned handler. *@return CR_OK upon successfull completion, an error code *otherwise. */ enum CRStatus cr_parser_get_sac_handler (CRParser * a_this, CRDocHandler ** a_handler) { g_return_val_if_fail (a_this, CR_BAD_PARAM_ERROR); *a_handler = PRIVATE (a_this)->sac_handler; return CR_OK; } /** *Sets the SAC handler associated to the current instance *of #CRParser to the default SAC handler. *@param a_this a pointer to the current instance of #CRParser. *@return CR_OK upon successfull completion, an error code otherwise. */ enum CRStatus cr_parser_set_default_sac_handler (CRParser * a_this) { CRDocHandler *default_sac_handler = NULL; enum CRStatus status = CR_ERROR; g_return_val_if_fail (a_this && PRIVATE (a_this), CR_BAD_PARAM_ERROR); default_sac_handler = cr_doc_handler_new (); cr_doc_handler_set_default_sac_handler (default_sac_handler); status = cr_parser_set_sac_handler (a_this, default_sac_handler); if (status != CR_OK) { cr_doc_handler_destroy (default_sac_handler); default_sac_handler = NULL; } return status; } enum CRStatus cr_parser_set_use_core_grammar (CRParser * a_this, gboolean a_use_core_grammar) { g_return_val_if_fail (a_this && PRIVATE (a_this), CR_BAD_PARAM_ERROR); PRIVATE (a_this)->use_core_grammar = a_use_core_grammar; return CR_OK; } enum CRStatus cr_parser_get_use_core_grammar (CRParser * a_this, gboolean * a_use_core_grammar) { g_return_val_if_fail (a_this && PRIVATE (a_this), CR_BAD_PARAM_ERROR); *a_use_core_grammar = PRIVATE (a_this)->use_core_grammar; return CR_OK; } /** *Parses a the given in parameter. *@param a_this a pointer to the current instance of #CRParser. *@param a_file_uri the uri to the file to load. For the time being, *only local files are supported. *@return CR_OK upon successfull completion, an error code otherwise. */ enum CRStatus cr_parser_parse_file (CRParser * a_this, const guchar * a_file_uri, enum CREncoding a_enc) { enum CRStatus status = CR_ERROR; CRTknzr *tknzr = NULL; g_return_val_if_fail (a_this && PRIVATE (a_this) && a_file_uri, CR_BAD_PARAM_ERROR); tknzr = cr_tknzr_new_from_uri (a_file_uri, a_enc); g_return_val_if_fail (tknzr != NULL, CR_ERROR); status = cr_parser_set_tknzr (a_this, tknzr); g_return_val_if_fail (status == CR_OK, CR_ERROR); status = cr_parser_parse (a_this); return status; } /** *Parses an expression as defined by the css2 spec in appendix *D.1: *expr: term [ operator term ]* */ enum CRStatus cr_parser_parse_expr (CRParser * a_this, CRTerm ** a_expr) { enum CRStatus status = CR_ERROR; CRInputPos init_pos; CRTerm *expr = NULL, *expr2 = NULL; guchar next_byte = 0; gulong nb_terms = 0; g_return_val_if_fail (a_this && PRIVATE (a_this) && a_expr, CR_BAD_PARAM_ERROR); RECORD_INITIAL_POS (a_this, &init_pos); status = cr_parser_parse_term (a_this, &expr); CHECK_PARSING_STATUS (status, FALSE); for (;;) { guchar operatr = 0; status = cr_tknzr_peek_byte (PRIVATE (a_this)->tknzr, 1, &next_byte); if (status != CR_OK) { if (status == CR_END_OF_INPUT_ERROR) { /* if (!nb_terms) { goto error ; } */ status = CR_OK; break; } else { goto error; } } if (next_byte == '/' || next_byte == ',') { READ_NEXT_BYTE (a_this, &operatr); } cr_parser_try_to_skip_spaces_and_comments (a_this); status = cr_parser_parse_term (a_this, &expr2); if (status != CR_OK || expr2 == NULL) { status = CR_OK; break; } switch (operatr) { case '/': expr2->the_operator = DIVIDE; break; case ',': expr2->the_operator = COMMA; default: break; } expr = cr_term_append_term (expr, expr2); expr2 = NULL; operatr = 0; nb_terms++; } if (status == CR_OK) { *a_expr = cr_term_append_term (*a_expr, expr); expr = NULL; cr_parser_clear_errors (a_this); return CR_OK; } error: if (expr) { cr_term_destroy (expr); expr = NULL; } if (expr2) { cr_term_destroy (expr2); expr2 = NULL; } cr_tknzr_set_cur_pos (PRIVATE (a_this)->tknzr, &init_pos); return status; } /** *Parses a declaration priority as defined by *the css2 grammar in appendix C: *prio: IMPORTANT_SYM S* *@param a_this the current instance of #CRParser. *@param a_prio a string representing the priority. *Today, only "!important" is returned as only this *priority is defined by css2. */ enum CRStatus cr_parser_parse_prio (CRParser * a_this, CRString ** a_prio) { enum CRStatus status = CR_ERROR; CRInputPos init_pos; CRToken *token = NULL; g_return_val_if_fail (a_this && PRIVATE (a_this) && a_prio && *a_prio == NULL, CR_BAD_PARAM_ERROR); RECORD_INITIAL_POS (a_this, &init_pos); status = cr_tknzr_get_next_token (PRIVATE (a_this)->tknzr, &token); if (status == CR_END_OF_INPUT_ERROR) { goto error; } ENSURE_PARSING_COND (status == CR_OK && token && token->type == IMPORTANT_SYM_TK); cr_parser_try_to_skip_spaces_and_comments (a_this); *a_prio = cr_string_new_from_string ("!important"); cr_token_destroy (token); token = NULL; return CR_OK; error: if (token) { cr_token_destroy (token); token = NULL; } cr_tknzr_set_cur_pos (PRIVATE (a_this)->tknzr, &init_pos); return status; } /** *TODO: return the parsed priority, so that *upper layers can take benefit from it. *Parses a "declaration" as defined by the css2 spec in appendix D.1: *declaration ::= [property ':' S* expr prio?]? * *@param a_this the "this pointer" of the current instance of #CRParser. *@param a_property the successfully parsed property. The caller * *must* free the returned pointer. *@param a_expr the expression that represents the attribute value. *The caller *must* free the returned pointer. *@return CR_OK upon successfull completion, an error code otherwise. */ enum CRStatus cr_parser_parse_declaration (CRParser * a_this, CRString ** a_property, CRTerm ** a_expr, gboolean * a_important) { enum CRStatus status = CR_ERROR; CRInputPos init_pos; guint32 cur_char = 0; CRTerm *expr = NULL; CRString *prio = NULL; g_return_val_if_fail (a_this && PRIVATE (a_this) && a_property && a_expr && a_important, CR_BAD_PARAM_ERROR); RECORD_INITIAL_POS (a_this, &init_pos); status = cr_parser_parse_property (a_this, a_property); if (status == CR_END_OF_INPUT_ERROR) goto error; CHECK_PARSING_STATUS_ERR (a_this, status, FALSE, (guchar *)"while parsing declaration: next property is malformed", CR_SYNTAX_ERROR); READ_NEXT_CHAR (a_this, &cur_char); if (cur_char != ':') { status = CR_PARSING_ERROR; cr_parser_push_error (a_this, (guchar *)"while parsing declaration: this char must be ':'", CR_SYNTAX_ERROR); goto error; } cr_parser_try_to_skip_spaces_and_comments (a_this); status = cr_parser_parse_expr (a_this, &expr); CHECK_PARSING_STATUS_ERR (a_this, status, FALSE, (guchar *)"while parsing declaration: next expression is malformed", CR_SYNTAX_ERROR); cr_parser_try_to_skip_spaces_and_comments (a_this); status = cr_parser_parse_prio (a_this, &prio); if (prio) { cr_string_destroy (prio); prio = NULL; *a_important = TRUE; } else { *a_important = FALSE; } if (*a_expr) { cr_term_append_term (*a_expr, expr); expr = NULL; } else { *a_expr = expr; expr = NULL; } cr_parser_clear_errors (a_this); return CR_OK; error: if (expr) { cr_term_destroy (expr); expr = NULL; } if (*a_property) { cr_string_destroy (*a_property); *a_property = NULL; } cr_tknzr_set_cur_pos (PRIVATE (a_this)->tknzr, &init_pos); return status; } /** *Parses a statement as defined by the css core grammar in *chapter 4.1 of the css2 spec. *statement : ruleset | at-rule; *@param a_this the current instance of #CRParser. *@return CR_OK upon successfull completion, an error code otherwise. */ enum CRStatus cr_parser_parse_statement_core (CRParser * a_this) { CRToken *token = NULL; CRInputPos init_pos; enum CRStatus status = CR_ERROR; g_return_val_if_fail (a_this && PRIVATE (a_this), CR_BAD_PARAM_ERROR); RECORD_INITIAL_POS (a_this, &init_pos); status = cr_tknzr_get_next_token (PRIVATE (a_this)->tknzr, &token); ENSURE_PARSING_COND (status == CR_OK && token); switch (token->type) { case ATKEYWORD_TK: case IMPORT_SYM_TK: case PAGE_SYM_TK: case MEDIA_SYM_TK: case FONT_FACE_SYM_TK: case CHARSET_SYM_TK: cr_tknzr_unget_token (PRIVATE (a_this)->tknzr, token); token = NULL; status = cr_parser_parse_atrule_core (a_this); CHECK_PARSING_STATUS (status, TRUE); break; default: cr_tknzr_unget_token (PRIVATE (a_this)->tknzr, token); token = NULL; status = cr_parser_parse_ruleset_core (a_this); cr_parser_clear_errors (a_this); CHECK_PARSING_STATUS (status, TRUE); } return CR_OK; error: if (token) { cr_token_destroy (token); token = NULL; } cr_tknzr_set_cur_pos (PRIVATE (a_this)->tknzr, &init_pos); return status; } /** *Parses a "ruleset" as defined in the css2 spec at appendix D.1. *ruleset ::= selector [ ',' S* selector ]* *'{' S* declaration? [ ';' S* declaration? ]* '}' S*; * *This methods calls the the SAC handler on the relevant SAC handler *callbacks whenever it encounters some specific constructions. *See the documentation of #CRDocHandler (the SAC handler) to know *when which SAC handler is called. *@param a_this the "this pointer" of the current instance of #CRParser. *@return CR_OK upon successfull completion, an error code otherwise. */ enum CRStatus cr_parser_parse_ruleset (CRParser * a_this) { enum CRStatus status = CR_OK; CRInputPos init_pos; guint32 cur_char = 0, next_char = 0; CRString *property = NULL; CRTerm *expr = NULL; CRSimpleSel *simple_sels = NULL; CRSelector *selector = NULL; gboolean start_selector = FALSE, is_important = FALSE; g_return_val_if_fail (a_this, CR_BAD_PARAM_ERROR); RECORD_INITIAL_POS (a_this, &init_pos); status = cr_parser_parse_selector (a_this, &selector); CHECK_PARSING_STATUS (status, FALSE); READ_NEXT_CHAR (a_this, &cur_char); ENSURE_PARSING_COND_ERR (a_this, cur_char == '{', (guchar *)"while parsing rulset: current char should be '{'", CR_SYNTAX_ERROR); if (PRIVATE (a_this)->sac_handler && PRIVATE (a_this)->sac_handler->start_selector) { /* *the selector is ref counted so that the parser's user *can choose to keep it. */ if (selector) { cr_selector_ref (selector); } PRIVATE (a_this)->sac_handler->start_selector (PRIVATE (a_this)->sac_handler, selector); start_selector = TRUE; } cr_parser_try_to_skip_spaces_and_comments (a_this); PRIVATE (a_this)->state = TRY_PARSE_RULESET_STATE; status = cr_parser_parse_declaration (a_this, &property, &expr, &is_important); if (expr) { cr_term_ref (expr); } if (status == CR_OK && PRIVATE (a_this)->sac_handler && PRIVATE (a_this)->sac_handler->property) { PRIVATE (a_this)->sac_handler->property (PRIVATE (a_this)->sac_handler, property, expr, is_important); } if (status == CR_OK) { /* *free the allocated *'property' and 'term' before parsing *next declarations. */ if (property) { cr_string_destroy (property); property = NULL; } if (expr) { cr_term_unref (expr); expr = NULL; } } else {/*status != CR_OK*/ guint32 c = 0 ; /* *test if we have reached '}', which *would mean that we are parsing an empty ruleset (eg. x{ }) *In that case, goto end_of_ruleset. */ status = cr_tknzr_peek_char (PRIVATE (a_this)->tknzr, &c) ; if (status == CR_OK && c == '}') { status = CR_OK ; goto end_of_ruleset ; } } CHECK_PARSING_STATUS_ERR (a_this, status, FALSE, (guchar *)"while parsing ruleset: next construction should be a declaration", CR_SYNTAX_ERROR); for (;;) { PEEK_NEXT_CHAR (a_this, &next_char); if (next_char != ';') break; /*consume the ';' char */ READ_NEXT_CHAR (a_this, &cur_char); cr_parser_try_to_skip_spaces_and_comments (a_this); status = cr_parser_parse_declaration (a_this, &property, &expr, &is_important); if (expr) { cr_term_ref (expr); } if (status == CR_OK && PRIVATE (a_this)->sac_handler && PRIVATE (a_this)->sac_handler->property) { PRIVATE (a_this)->sac_handler->property (PRIVATE (a_this)->sac_handler, property, expr, is_important); } if (property) { cr_string_destroy (property); property = NULL; } if (expr) { cr_term_unref (expr); expr = NULL; } } end_of_ruleset: cr_parser_try_to_skip_spaces_and_comments (a_this); READ_NEXT_CHAR (a_this, &cur_char); ENSURE_PARSING_COND_ERR (a_this, cur_char == '}', (guchar *)"while parsing rulset: current char must be a '}'", CR_SYNTAX_ERROR); if (PRIVATE (a_this)->sac_handler && PRIVATE (a_this)->sac_handler->end_selector) { PRIVATE (a_this)->sac_handler->end_selector (PRIVATE (a_this)->sac_handler, selector); start_selector = FALSE; } if (expr) { cr_term_unref (expr); expr = NULL; } if (simple_sels) { cr_simple_sel_destroy (simple_sels); simple_sels = NULL; } if (selector) { cr_selector_unref (selector); selector = NULL; } cr_parser_clear_errors (a_this); PRIVATE (a_this)->state = RULESET_PARSED_STATE; return CR_OK; error: if (start_selector == TRUE && PRIVATE (a_this)->sac_handler && PRIVATE (a_this)->sac_handler->error) { PRIVATE (a_this)->sac_handler->error (PRIVATE (a_this)->sac_handler); } if (expr) { cr_term_unref (expr); expr = NULL; } if (simple_sels) { cr_simple_sel_destroy (simple_sels); simple_sels = NULL; } if (property) { cr_string_destroy (property); } if (selector) { cr_selector_unref (selector); selector = NULL; } cr_tknzr_set_cur_pos (PRIVATE (a_this)->tknzr, &init_pos); return status; } /** *Parses an 'import' declaration as defined in the css2 spec *in appendix D.1: * *import ::= *@import [STRING|URI] S* [ medium [ ',' S* medium]* ]? ';' S* * *@param a_this the "this pointer" of the current instance *of #CRParser. * *@param a_medium_list out parameter. A linked list of *#CRString *Each CRString is a string that contains *a 'medium' declaration part of the successfully *parsed 'import' declaration. * *@param a_import_string out parameter. *A string that contains the 'import *string". The import string can be either an uri (if it starts with *the substring "uri(") or a any other css2 string. Note that * *a_import_string must be initially set to NULL or else, this function *will return CR_BAD_PARAM_ERROR. * *@return CR_OK upon sucessfull completion, an error code otherwise. */ enum CRStatus cr_parser_parse_import (CRParser * a_this, GList ** a_media_list, CRString ** a_import_string, CRParsingLocation *a_location) { enum CRStatus status = CR_OK; CRInputPos init_pos; guint32 cur_char = 0, next_char = 0; CRString *medium = NULL; g_return_val_if_fail (a_this && a_import_string && (*a_import_string == NULL), CR_BAD_PARAM_ERROR); RECORD_INITIAL_POS (a_this, &init_pos); if (BYTE (a_this, 1, NULL) == '@' && BYTE (a_this, 2, NULL) == 'i' && BYTE (a_this, 3, NULL) == 'm' && BYTE (a_this, 4, NULL) == 'p' && BYTE (a_this, 5, NULL) == 'o' && BYTE (a_this, 6, NULL) == 'r' && BYTE (a_this, 7, NULL) == 't') { SKIP_CHARS (a_this, 1); if (a_location) { cr_parser_get_parsing_location (a_this, a_location) ; } SKIP_CHARS (a_this, 6); status = CR_OK; } else { status = CR_PARSING_ERROR; goto error; } cr_parser_try_to_skip_spaces_and_comments (a_this); PRIVATE (a_this)->state = TRY_PARSE_IMPORT_STATE; PEEK_NEXT_CHAR (a_this, &next_char); if (next_char == '"' || next_char == '\'') { status = cr_parser_parse_string (a_this, a_import_string); CHECK_PARSING_STATUS (status, FALSE); } else { status = cr_parser_parse_uri (a_this, a_import_string); CHECK_PARSING_STATUS (status, FALSE); } cr_parser_try_to_skip_spaces_and_comments (a_this); status = cr_parser_parse_ident (a_this, &medium); if (status == CR_OK && medium) { *a_media_list = g_list_append (*a_media_list, medium); medium = NULL; } cr_parser_try_to_skip_spaces_and_comments (a_this); for (; status == CR_OK;) { if ((status = cr_tknzr_peek_char (PRIVATE (a_this)->tknzr, &next_char)) != CR_OK) { if (status == CR_END_OF_INPUT_ERROR) { status = CR_OK; goto okay; } goto error; } if (next_char == ',') { READ_NEXT_CHAR (a_this, &cur_char); } else { break; } cr_parser_try_to_skip_spaces_and_comments (a_this); status = cr_parser_parse_ident (a_this, &medium); cr_parser_try_to_skip_spaces_and_comments (a_this); if ((status == CR_OK) && medium) { *a_media_list = g_list_append (*a_media_list, medium); medium = NULL; } CHECK_PARSING_STATUS (status, FALSE); cr_parser_try_to_skip_spaces_and_comments (a_this); } cr_parser_try_to_skip_spaces_and_comments (a_this); READ_NEXT_CHAR (a_this, &cur_char); ENSURE_PARSING_COND (cur_char == ';'); cr_parser_try_to_skip_spaces_and_comments (a_this); okay: cr_parser_clear_errors (a_this); PRIVATE (a_this)->state = IMPORT_PARSED_STATE; return CR_OK; error: if (*a_media_list) { GList *cur = NULL; /* *free each element of *a_media_list. *Note that each element of *a_medium list *must* *be a GString* or else, the code that is coming next *will corrupt the memory and lead to hard to debug *random crashes. *This is where C++ and its compile time *type checking mecanism (through STL containers) would *have prevented us to go through this hassle. */ for (cur = *a_media_list; cur; cur = cur->next) { if (cur->data) { cr_string_destroy ((CRString *)cur->data); } } g_list_free (*a_media_list); *a_media_list = NULL; } if (*a_import_string) { cr_string_destroy (*a_import_string); *a_import_string = NULL; } if (medium) { cr_string_destroy (medium); medium = NULL; } cr_tknzr_set_cur_pos (PRIVATE (a_this)->tknzr, &init_pos); return status; } /** *Parses a 'media' declaration as specified in the css2 spec at *appendix D.1: * *media ::= @media S* medium [ ',' S* medium ]* '{' S* ruleset* '}' S* * *Note that this function calls the required sac handlers during the parsing *to notify media productions. See #CRDocHandler to know the callback called *during @media parsing. *@param a_this the "this pointer" of the current instance of #CRParser. *@return CR_OK upon successfull completion, an error code otherwise. */ enum CRStatus cr_parser_parse_media (CRParser * a_this) { enum CRStatus status = CR_OK; CRInputPos init_pos; CRToken *token = NULL; guint32 next_char = 0, cur_char = 0; CRString *medium = NULL; GList *media_list = NULL; 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); status = cr_tknzr_get_next_token (PRIVATE (a_this)->tknzr, &token); ENSURE_PARSING_COND (status == CR_OK && token && token->type == MEDIA_SYM_TK); cr_parsing_location_copy (&location, &token->location) ; cr_token_destroy (token); token = NULL; cr_parser_try_to_skip_spaces_and_comments (a_this); status = cr_tknzr_get_next_token (PRIVATE (a_this)->tknzr, &token); ENSURE_PARSING_COND (status == CR_OK && token && token->type == IDENT_TK); medium = token->u.str; token->u.str = NULL; cr_token_destroy (token); token = NULL; if (medium) { media_list = g_list_append (media_list, medium); medium = NULL; } for (; status == CR_OK;) { cr_parser_try_to_skip_spaces_and_comments (a_this); PEEK_NEXT_CHAR (a_this, &next_char); if (next_char == ',') { READ_NEXT_CHAR (a_this, &cur_char); } else { break; } cr_parser_try_to_skip_spaces_and_comments (a_this); status = cr_parser_parse_ident (a_this, &medium); CHECK_PARSING_STATUS (status, FALSE); if (medium) { media_list = g_list_append (media_list, medium); medium = NULL; } } READ_NEXT_CHAR (a_this, &cur_char); ENSURE_PARSING_COND (cur_char == '{'); /* *call the SAC handler api here. */ if (PRIVATE (a_this)->sac_handler && PRIVATE (a_this)->sac_handler->start_media) { PRIVATE (a_this)->sac_handler->start_media (PRIVATE (a_this)->sac_handler, media_list, &location); } cr_parser_try_to_skip_spaces_and_comments (a_this); PRIVATE (a_this)->state = TRY_PARSE_MEDIA_STATE; for (; status == CR_OK;) { status = cr_parser_parse_ruleset (a_this); cr_parser_try_to_skip_spaces_and_comments (a_this); } READ_NEXT_CHAR (a_this, &cur_char); ENSURE_PARSING_COND (cur_char == '}'); /* *call the right SAC handler api here. */ if (PRIVATE (a_this)->sac_handler && PRIVATE (a_this)->sac_handler->end_media) { PRIVATE (a_this)->sac_handler->end_media (PRIVATE (a_this)->sac_handler, media_list); } cr_parser_try_to_skip_spaces_and_comments (a_this); /* *Then, free the data structures passed to *the last call to the SAC handler. */ if (medium) { cr_string_destroy (medium); medium = NULL; } if (media_list) { GList *cur = NULL; for (cur = media_list; cur; cur = cur->next) { cr_string_destroy ((CRString *)cur->data); } g_list_free (media_list); media_list = NULL; } cr_parser_clear_errors (a_this); PRIVATE (a_this)->state = MEDIA_PARSED_STATE; return CR_OK; error: if (token) { cr_token_destroy (token); token = NULL; } if (medium) { cr_string_destroy (medium); medium = NULL; } if (media_list) { GList *cur = NULL; for (cur = media_list; cur; cur = cur->next) { cr_string_destroy ((CRString *)cur->data); } g_list_free (media_list); media_list = NULL; } cr_tknzr_set_cur_pos (PRIVATE (a_this)->tknzr, &init_pos); return status; } /** *Parses '@page' rule as specified in the css2 spec in appendix D.1: *page ::= PAGE_SYM S* IDENT? pseudo_page? S* *'{' S* declaration [ ';' S* declaration ]* '}' S* * *This function also calls the relevant SAC handlers whenever it *encounters a construction that must *be reported to the calling application. *@param a_this the "this pointer" of the current instance of #CRParser. *@return CR_OK upon successfull completion, an error code otherwise. */ enum CRStatus cr_parser_parse_page (CRParser * a_this) { enum CRStatus status = CR_OK; CRInputPos init_pos; CRToken *token = NULL; CRTerm *css_expression = NULL; CRString *page_selector = NULL, *page_pseudo_class = NULL, *property = NULL; gboolean important = TRUE; CRParsingLocation location = {0,0,0} ; g_return_val_if_fail (a_this, CR_BAD_PARAM_ERROR); RECORD_INITIAL_POS (a_this, &init_pos); status = cr_tknzr_get_next_token (PRIVATE (a_this)->tknzr, &token) ; ENSURE_PARSING_COND (status == CR_OK && token && token->type == PAGE_SYM_TK); cr_parsing_location_copy (&location, &token->location) ; cr_token_destroy (token); token = NULL; cr_parser_try_to_skip_spaces_and_comments (a_this); status = cr_tknzr_get_next_token (PRIVATE (a_this)->tknzr, &token); ENSURE_PARSING_COND (status == CR_OK && token); if (token->type == IDENT_TK) { page_selector = token->u.str; token->u.str = NULL; cr_token_destroy (token); token = NULL; } else { cr_tknzr_unget_token (PRIVATE (a_this)->tknzr, token); token = NULL; } /* *try to parse pseudo_page */ cr_parser_try_to_skip_spaces_and_comments (a_this); status = cr_tknzr_get_next_token (PRIVATE (a_this)->tknzr, &token); ENSURE_PARSING_COND (status == CR_OK && token); if (token->type == DELIM_TK && token->u.unichar == ':') { cr_token_destroy (token); token = NULL; status = cr_parser_parse_ident (a_this, &page_pseudo_class); CHECK_PARSING_STATUS (status, FALSE); } else { cr_tknzr_unget_token (PRIVATE (a_this)->tknzr, token); token = NULL; } /* *parse_block * */ cr_parser_try_to_skip_spaces_and_comments (a_this); status = cr_tknzr_get_next_token (PRIVATE (a_this)->tknzr, &token); ENSURE_PARSING_COND (status == CR_OK && token && token->type == CBO_TK); cr_token_destroy (token); token = NULL; /* *Call the appropriate SAC handler here. */ if (PRIVATE (a_this)->sac_handler && PRIVATE (a_this)->sac_handler->start_page) { PRIVATE (a_this)->sac_handler->start_page (PRIVATE (a_this)->sac_handler, page_selector, page_pseudo_class, &location); } cr_parser_try_to_skip_spaces_and_comments (a_this); PRIVATE (a_this)->state = TRY_PARSE_PAGE_STATE; status = cr_parser_parse_declaration (a_this, &property, &css_expression, &important); ENSURE_PARSING_COND (status == CR_OK); /* *call the relevant SAC handler here... */ if (PRIVATE (a_this)->sac_handler && PRIVATE (a_this)->sac_handler->property) { if (css_expression) cr_term_ref (css_expression); PRIVATE (a_this)->sac_handler->property (PRIVATE (a_this)->sac_handler, property, css_expression, important); } /* *... and free the data structure passed to that last *SAC handler. */ if (property) { cr_string_destroy (property); property = NULL; } if (css_expression) { cr_term_unref (css_expression); css_expression = NULL; } for (;;) { /*parse the other ';' separated declarations */ if (token) { cr_token_destroy (token); token = NULL; } status = cr_tknzr_get_next_token (PRIVATE (a_this)->tknzr, &token); ENSURE_PARSING_COND (status == CR_OK && token); if (token->type != SEMICOLON_TK) { cr_tknzr_unget_token (PRIVATE (a_this)->tknzr, token); token = NULL ; break; } cr_token_destroy (token); token = NULL; cr_parser_try_to_skip_spaces_and_comments (a_this); status = cr_parser_parse_declaration (a_this, &property, &css_expression, &important); if (status != CR_OK) break ; /* *call the relevant SAC handler here... */ if (PRIVATE (a_this)->sac_handler && PRIVATE (a_this)->sac_handler->property) { cr_term_ref (css_expression); PRIVATE (a_this)->sac_handler->property (PRIVATE (a_this)->sac_handler, property, css_expression, important); } /* *... and free the data structure passed to that last *SAC handler. */ if (property) { cr_string_destroy (property); property = NULL; } if (css_expression) { cr_term_unref (css_expression); css_expression = NULL; } } cr_parser_try_to_skip_spaces_and_comments (a_this) ; if (token) { cr_token_destroy (token) ; token = NULL ; } status = cr_tknzr_get_next_token (PRIVATE (a_this)->tknzr, &token); ENSURE_PARSING_COND (status == CR_OK && token && token->type == CBC_TK) ; cr_token_destroy (token) ; token = NULL ; /* *call the relevant SAC handler here. */ if (PRIVATE (a_this)->sac_handler && PRIVATE (a_this)->sac_handler->end_page) { PRIVATE (a_this)->sac_handler->end_page (PRIVATE (a_this)->sac_handler, page_selector, page_pseudo_class); } if (page_selector) { cr_string_destroy (page_selector); page_selector = NULL; } if (page_pseudo_class) { cr_string_destroy (page_pseudo_class); page_pseudo_class = NULL; } cr_parser_try_to_skip_spaces_and_comments (a_this); /*here goes the former implem of this function ... */ cr_parser_clear_errors (a_this); PRIVATE (a_this)->state = PAGE_PARSED_STATE; return CR_OK; error: if (token) { cr_token_destroy (token); token = NULL; } if (page_selector) { cr_string_destroy (page_selector); page_selector = NULL; } if (page_pseudo_class) { cr_string_destroy (page_pseudo_class); page_pseudo_class = NULL; } if (property) { cr_string_destroy (property); property = NULL; } if (css_expression) { cr_term_destroy (css_expression); css_expression = NULL; } cr_tknzr_set_cur_pos (PRIVATE (a_this)->tknzr, &init_pos); return status; } /** *Parses a charset declaration as defined implictly by the css2 spec in *appendix D.1: *charset ::= CHARSET_SYM S* STRING S* ';' * *@param a_this the "this pointer" of the current instance of #CRParser. *@param a_value out parameter. The actual parsed value of the charset *declararation. Note that for safety check reasons, *a_value must be *set to NULL. *@param a_charset_sym_location the parsing location of *@return CR_OK upon successfull completion, an error code otherwise. */ enum CRStatus cr_parser_parse_charset (CRParser * a_this, CRString ** a_value, CRParsingLocation *a_charset_sym_location) { enum CRStatus status = CR_OK; CRInputPos init_pos; CRToken *token = NULL; CRString *charset_str = NULL; g_return_val_if_fail (a_this && a_value && (*a_value == NULL), CR_BAD_PARAM_ERROR); RECORD_INITIAL_POS (a_this, &init_pos); status = cr_tknzr_get_next_token (PRIVATE (a_this)->tknzr, &token); ENSURE_PARSING_COND (status == CR_OK && token && token->type == CHARSET_SYM_TK); if (a_charset_sym_location) { cr_parsing_location_copy (a_charset_sym_location, &token->location) ; } cr_token_destroy (token); token = NULL; PRIVATE (a_this)->state = TRY_PARSE_CHARSET_STATE; cr_parser_try_to_skip_spaces_and_comments (a_this); status = cr_tknzr_get_next_token (PRIVATE (a_this)->tknzr, &token); ENSURE_PARSING_COND (status == CR_OK && token && token->type == STRING_TK); charset_str = token->u.str; token->u.str = NULL; cr_token_destroy (token); token = NULL; cr_parser_try_to_skip_spaces_and_comments (a_this); status = cr_tknzr_get_next_token (PRIVATE (a_this)->tknzr, &token); ENSURE_PARSING_COND (status == CR_OK && token && token->type == SEMICOLON_TK); cr_token_destroy (token); token = NULL; if (charset_str) { *a_value = charset_str; charset_str = NULL; } PRIVATE (a_this)->state = CHARSET_PARSED_STATE; return CR_OK; error: if (token) { cr_token_destroy (token); token = NULL; } if (*a_value) { cr_string_destroy (*a_value); *a_value = NULL; } if (charset_str) { cr_string_destroy (charset_str); charset_str = NULL; } cr_tknzr_set_cur_pos (PRIVATE (a_this)->tknzr, &init_pos); return status; } /** *Parses the "@font-face" rule specified in the css1 spec in *appendix D.1: * *font_face ::= FONT_FACE_SYM S* *'{' S* declaration [ ';' S* declaration ]* '}' S* * *This function will call SAC handlers whenever it is necessary. *@return CR_OK upon successfull completion, an error code otherwise. */ enum CRStatus cr_parser_parse_font_face (CRParser * a_this) { enum CRStatus status = CR_ERROR; CRInputPos init_pos; CRString *property = NULL; CRTerm *css_expression = NULL; CRToken *token = NULL; gboolean important = FALSE; guint32 next_char = 0, cur_char = 0; CRParsingLocation location = {0,0,0} ; g_return_val_if_fail (a_this, CR_BAD_PARAM_ERROR); RECORD_INITIAL_POS (a_this, &init_pos); status = cr_tknzr_get_next_token (PRIVATE (a_this)->tknzr, &token); ENSURE_PARSING_COND (status == CR_OK && token && token->type == FONT_FACE_SYM_TK); cr_parser_try_to_skip_spaces_and_comments (a_this); if (token) { cr_parsing_location_copy (&location, &token->location) ; cr_token_destroy (token); token = NULL; } status = cr_tknzr_get_next_token (PRIVATE (a_this)->tknzr, &token); ENSURE_PARSING_COND (status == CR_OK && token && token->type == CBO_TK); if (token) { cr_token_destroy (token); token = NULL; } /* *here, call the relevant SAC handler. */ if (PRIVATE (a_this)->sac_handler && PRIVATE (a_this)->sac_handler->start_font_face) { PRIVATE (a_this)->sac_handler->start_font_face (PRIVATE (a_this)->sac_handler, &location); } PRIVATE (a_this)->state = TRY_PARSE_FONT_FACE_STATE; /* *and resume the parsing. */ cr_parser_try_to_skip_spaces_and_comments (a_this); status = cr_parser_parse_declaration (a_this, &property, &css_expression, &important); if (status == CR_OK) { /* *here, call the relevant SAC handler. */ cr_term_ref (css_expression); if (PRIVATE (a_this)->sac_handler && PRIVATE (a_this)->sac_handler->property) { PRIVATE (a_this)->sac_handler->property (PRIVATE (a_this)->sac_handler, property, css_expression, important); } ENSURE_PARSING_COND (css_expression && property); } /*free the data structures allocated during last parsing. */ if (property) { cr_string_destroy (property); property = NULL; } if (css_expression) { cr_term_unref (css_expression); css_expression = NULL; } for (;;) { PEEK_NEXT_CHAR (a_this, &next_char); if (next_char == ';') { READ_NEXT_CHAR (a_this, &cur_char); } else { break; } cr_parser_try_to_skip_spaces_and_comments (a_this); status = cr_parser_parse_declaration (a_this, &property, &css_expression, &important); if (status != CR_OK) break; /* *here, call the relevant SAC handler. */ cr_term_ref (css_expression); if (PRIVATE (a_this)->sac_handler->property) { PRIVATE (a_this)->sac_handler->property (PRIVATE (a_this)->sac_handler, property, css_expression, important); } /* *Then, free the data structures allocated during *last parsing. */ if (property) { cr_string_destroy (property); property = NULL; } if (css_expression) { cr_term_unref (css_expression); css_expression = NULL; } } cr_parser_try_to_skip_spaces_and_comments (a_this); READ_NEXT_CHAR (a_this, &cur_char); ENSURE_PARSING_COND (cur_char == '}'); /* *here, call the relevant SAC handler. */ if (PRIVATE (a_this)->sac_handler->end_font_face) { PRIVATE (a_this)->sac_handler->end_font_face (PRIVATE (a_this)->sac_handler); } cr_parser_try_to_skip_spaces_and_comments (a_this); if (token) { cr_token_destroy (token); token = NULL; } cr_parser_clear_errors (a_this); PRIVATE (a_this)->state = FONT_FACE_PARSED_STATE; return CR_OK; error: if (token) { cr_token_destroy (token); token = NULL; } if (property) { cr_string_destroy (property); property = NULL; } if (css_expression) { cr_term_destroy (css_expression); css_expression = NULL; } cr_tknzr_set_cur_pos (PRIVATE (a_this)->tknzr, &init_pos); return status; } /** *Parses the data that comes from the *input previously associated to the current instance of *#CRParser. *@param a_this the current instance of #CRParser. *@return CR_OK ; */ enum CRStatus cr_parser_parse (CRParser * a_this) { enum CRStatus status = CR_ERROR; g_return_val_if_fail (a_this && PRIVATE (a_this) && PRIVATE (a_this)->tknzr, CR_BAD_PARAM_ERROR); if (PRIVATE (a_this)->use_core_grammar == FALSE) { status = cr_parser_parse_stylesheet (a_this); } else { status = cr_parser_parse_stylesheet_core (a_this); } return status; } enum CRStatus cr_parser_set_tknzr (CRParser * a_this, CRTknzr * a_tknzr) { g_return_val_if_fail (a_this && PRIVATE (a_this), CR_BAD_PARAM_ERROR); if (PRIVATE (a_this)->tknzr) { cr_tknzr_unref (PRIVATE (a_this)->tknzr); } PRIVATE (a_this)->tknzr = a_tknzr; if (a_tknzr) cr_tknzr_ref (a_tknzr); return CR_OK; } /** *Getter of the parser's underlying tokenizer *@param a_this the current instance of #CRParser *@param a_tknzr out parameter. The returned tokenizer *@return CR_OK upon succesful completion, an error code *otherwise */ enum CRStatus cr_parser_get_tknzr (CRParser * a_this, CRTknzr ** a_tknzr) { g_return_val_if_fail (a_this && PRIVATE (a_this) && a_tknzr, CR_BAD_PARAM_ERROR); *a_tknzr = PRIVATE (a_this)->tknzr; return CR_OK; } /** *Gets the current parsing location. *@param a_this the current instance of #CRParser *@param a_loc the parsing location to get. *@return CR_OK upon succesful completion, an error code *otherwise. */ enum CRStatus cr_parser_get_parsing_location (CRParser *a_this, CRParsingLocation *a_loc) { g_return_val_if_fail (a_this && PRIVATE (a_this) && a_loc, CR_BAD_PARAM_ERROR) ; return cr_tknzr_get_parsing_location (PRIVATE (a_this)->tknzr, a_loc) ; } /** *Parses a stylesheet from a buffer *@param a_this the current instance of #CRparser *@param a_buf the input buffer *@param a_len the length of the input buffer *@param a_enc the encoding of the buffer *@return CR_OK upon successful completion, an error code otherwise. */ enum CRStatus cr_parser_parse_buf (CRParser * a_this, const guchar * a_buf, gulong a_len, enum CREncoding a_enc) { enum CRStatus status = CR_ERROR; CRTknzr *tknzr = NULL; g_return_val_if_fail (a_this && PRIVATE (a_this) && a_buf, CR_BAD_PARAM_ERROR); tknzr = cr_tknzr_new_from_buf ((guchar*)a_buf, a_len, a_enc, FALSE); g_return_val_if_fail (tknzr != NULL, CR_ERROR); status = cr_parser_set_tknzr (a_this, tknzr); g_return_val_if_fail (status == CR_OK, CR_ERROR); status = cr_parser_parse (a_this); return status; } /** *Destroys the current instance *of #CRParser. *@param a_this the current instance of #CRParser to *destroy. */ void cr_parser_destroy (CRParser * a_this) { g_return_if_fail (a_this && PRIVATE (a_this)); if (PRIVATE (a_this)->tknzr) { if (cr_tknzr_unref (PRIVATE (a_this)->tknzr) == TRUE) PRIVATE (a_this)->tknzr = NULL; } if (PRIVATE (a_this)->sac_handler) { cr_doc_handler_unref (PRIVATE (a_this)->sac_handler); PRIVATE (a_this)->sac_handler = NULL; } if (PRIVATE (a_this)->err_stack) { cr_parser_clear_errors (a_this); PRIVATE (a_this)->err_stack = NULL; } if (PRIVATE (a_this)) { g_free (PRIVATE (a_this)); PRIVATE (a_this) = NULL; } if (a_this) { g_free (a_this); a_this = NULL; /*useless. Just for the sake of coherence */ } }