/cssed-0.4.0/libcroco/parser/cr-sel-eng.c
C | 1555 lines | 1044 code | 180 blank | 331 comment | 329 complexity | 952cb9f104507a46704af74444047d28 MD5 | raw file
Possible License(s): GPL-2.0
- /* -*- 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
- *
- * See COPYRIGHTS file for copyright informations.
- */
- #include <string.h>
- #include "cr-sel-eng.h"
- /**
- *@file:
- *The definition of the #CRSelEng class.
- *The #CRSelEng is actually the "Selection Engine"
- *class. This is highly experimental for at the moment and
- *its api is very likely to change in a near future.
- */
- #define PRIVATE(a_this) (a_this)->priv
- struct CRPseudoClassSelHandlerEntry {
- guchar *name;
- enum CRPseudoType type;
- CRPseudoClassSelectorHandler handler;
- };
- struct _CRSelEngPriv {
- /*not used yet */
- gboolean case_sensitive;
- CRStyleSheet *sheet;
- /**
- *where to store the next statement
- *to be visited so that we can remember
- *it from one method call to another.
- */
- CRStatement *cur_stmt;
- GList *pcs_handlers;
- gint pcs_handlers_size;
- } ;
- static gboolean class_add_sel_matches_node (CRAdditionalSel * a_add_sel,
- xmlNode * a_node);
- static gboolean id_add_sel_matches_node (CRAdditionalSel * a_add_sel,
- xmlNode * a_node);
- static gboolean attr_add_sel_matches_node (CRAdditionalSel * a_add_sel,
- xmlNode * a_node);
- static enum CRStatus sel_matches_node_real (CRSelEng * a_this,
- CRSimpleSel * a_sel,
- xmlNode * a_node,
- gboolean * a_result,
- gboolean a_eval_sel_list_from_end,
- gboolean a_recurse);
- static enum CRStatus cr_sel_eng_get_matched_rulesets_real (CRSelEng * a_this,
- CRStyleSheet *
- a_stylesheet,
- xmlNode * a_node,
- CRStatement **
- a_rulesets,
- gulong * a_len);
- static enum CRStatus put_css_properties_in_props_list (CRPropList ** a_props,
- CRStatement *
- a_ruleset);
- static gboolean pseudo_class_add_sel_matches_node (CRSelEng * a_this,
- CRAdditionalSel *
- a_add_sel,
- xmlNode * a_node);
- static gboolean lang_pseudo_class_handler (CRSelEng * a_this,
- CRAdditionalSel * a_sel,
- xmlNode * a_node);
- static gboolean first_child_pseudo_class_handler (CRSelEng * a_this,
- CRAdditionalSel * a_sel,
- xmlNode * a_node);
- static xmlNode *get_next_element_node (xmlNode * a_node);
- static xmlNode *get_next_child_element_node (xmlNode * a_node);
- static xmlNode *get_prev_element_node (xmlNode * a_node);
- static xmlNode *get_next_parent_element_node (xmlNode * a_node);
- static gboolean
- lang_pseudo_class_handler (CRSelEng * a_this,
- CRAdditionalSel * a_sel, xmlNode * a_node)
- {
- xmlNode *node = a_node;
- xmlChar *val = NULL;
- gboolean result = FALSE;
- g_return_val_if_fail (a_this && PRIVATE (a_this)
- && a_sel && a_sel->content.pseudo
- && a_sel->content.pseudo
- && a_sel->content.pseudo->name
- && a_sel->content.pseudo->name->stryng
- && a_node, CR_BAD_PARAM_ERROR);
- if (strncmp (a_sel->content.pseudo->name->stryng->str,
- "lang", 4)
- || !a_sel->content.pseudo->type == FUNCTION_PSEUDO) {
- cr_utils_trace_info ("This handler is for :lang only");
- return CR_BAD_PSEUDO_CLASS_SEL_HANDLER_ERROR;
- }
- /*lang code should exist and be at least of length 2 */
- if (!a_sel->content.pseudo->extra
- || !a_sel->content.pseudo->extra->stryng
- || a_sel->content.pseudo->extra->stryng->len < 2)
- return FALSE;
- for (; node; node = get_next_parent_element_node (node)) {
- val = xmlGetProp (node, "lang");
- if (val
- && !strncmp (val,
- a_sel->content.pseudo->extra->stryng->str,
- a_sel->content.pseudo->extra->stryng->len)) {
- result = TRUE;
- }
- if (val) {
- xmlFree (val);
- val = NULL;
- }
- }
- return result;
- }
- static gboolean
- first_child_pseudo_class_handler (CRSelEng * a_this,
- CRAdditionalSel * a_sel, xmlNode * a_node)
- {
- xmlNode *node = NULL;
- g_return_val_if_fail (a_this && PRIVATE (a_this)
- && a_sel && a_sel->content.pseudo
- && a_sel->content.pseudo
- && a_sel->content.pseudo->name
- && a_sel->content.pseudo->name->stryng
- && a_node, CR_BAD_PARAM_ERROR);
- if (strcmp (a_sel->content.pseudo->name->stryng->str,
- "first-child")
- || !a_sel->content.pseudo->type == IDENT_PSEUDO) {
- cr_utils_trace_info ("This handler is for :first-child only");
- return CR_BAD_PSEUDO_CLASS_SEL_HANDLER_ERROR;
- }
- if (!a_node->parent)
- return FALSE;
- node = get_next_child_element_node (a_node->parent);
- if (node == a_node)
- return TRUE;
- return FALSE;
- }
- static gboolean
- pseudo_class_add_sel_matches_node (CRSelEng * a_this,
- CRAdditionalSel * a_add_sel,
- xmlNode * a_node)
- {
- enum CRStatus status = CR_OK;
- CRPseudoClassSelectorHandler handler = NULL;
- g_return_val_if_fail (a_this && PRIVATE (a_this)
- && a_add_sel
- && a_add_sel->content.pseudo
- && a_add_sel->content.pseudo->name
- && a_add_sel->content.pseudo->name->stryng
- && a_add_sel->content.pseudo->name->stryng->str
- && a_node, CR_BAD_PARAM_ERROR);
- status = cr_sel_eng_get_pseudo_class_selector_handler
- (a_this, a_add_sel->content.pseudo->name->stryng->str,
- a_add_sel->content.pseudo->type, &handler);
- if (status != CR_OK || !handler)
- return FALSE;
- return handler (a_this, a_add_sel, a_node);
- }
- /**
- *@param a_add_sel the class additional selector to consider.
- *@param a_node the xml node to consider.
- *@return TRUE if the class additional selector matches
- *the xml node given in argument, FALSE otherwise.
- */
- static gboolean
- class_add_sel_matches_node (CRAdditionalSel * a_add_sel, xmlNode * a_node)
- {
- gboolean result = FALSE;
- xmlChar *klass = NULL,
- *cur = NULL;
- g_return_val_if_fail (a_add_sel
- && a_add_sel->type == CLASS_ADD_SELECTOR
- && a_add_sel->content.class_name
- && a_add_sel->content.class_name->stryng
- && a_add_sel->content.class_name->stryng->str
- && a_node, FALSE);
- if (xmlHasProp (a_node, "class")) {
- klass = xmlGetProp (a_node, "class");
- for (cur = klass; cur && *cur; cur++) {
- while (cur && *cur
- && cr_utils_is_white_space (*cur)
- == TRUE)
- cur++;
- if (!strncmp (cur,
- a_add_sel->content.class_name->stryng->str,
- a_add_sel->content.class_name->stryng->len)) {
- cur += a_add_sel->content.class_name->stryng->len;
- if ((cur && !*cur)
- || cr_utils_is_white_space (*cur) == TRUE)
- result = TRUE;
- }
- if (cur && !*cur)
- break ;
- }
- }
- if (klass) {
- xmlFree (klass);
- klass = NULL;
- }
- return result;
- }
- /**
- *@return TRUE if the additional attribute selector matches
- *the current xml node given in argument, FALSE otherwise.
- *@param a_add_sel the additional attribute selector to consider.
- *@param a_node the xml node to consider.
- */
- static gboolean
- id_add_sel_matches_node (CRAdditionalSel * a_add_sel, xmlNode * a_node)
- {
- gboolean result = FALSE;
- xmlChar *id = NULL;
- g_return_val_if_fail (a_add_sel
- && a_add_sel->type == ID_ADD_SELECTOR
- && a_add_sel->content.id_name
- && a_add_sel->content.id_name->stryng
- && a_add_sel->content.id_name->stryng->str
- && a_node, FALSE);
- g_return_val_if_fail (a_add_sel
- && a_add_sel->type == ID_ADD_SELECTOR
- && a_node, FALSE);
- if (xmlHasProp (a_node, "id")) {
- id = xmlGetProp (a_node, "id");
- if (!strncmp (id, a_add_sel->content.id_name->stryng->str,
- a_add_sel->content.id_name->stryng->len)) {
- result = TRUE;
- }
- }
- if (id) {
- xmlFree (id);
- id = NULL;
- }
- return result;
- }
- /**
- *Returns TRUE if the instance of #CRAdditional selector matches
- *the node given in parameter, FALSE otherwise.
- *@param a_add_sel the additional selector to evaluate.
- *@param a_node the xml node against whitch the selector is to
- *be evaluated
- *return TRUE if the additional selector matches the current xml node
- *FALSE otherwise.
- */
- static gboolean
- attr_add_sel_matches_node (CRAdditionalSel * a_add_sel, xmlNode * a_node)
- {
- CRAttrSel *cur_sel = NULL;
- g_return_val_if_fail (a_add_sel
- && a_add_sel->type == ATTRIBUTE_ADD_SELECTOR
- && a_node, FALSE);
- for (cur_sel = a_add_sel->content.attr_sel;
- cur_sel; cur_sel = cur_sel->next) {
- switch (cur_sel->match_way) {
- case SET:
- if (!cur_sel->name
- || !cur_sel->name->stryng
- || !cur_sel->name->stryng->str)
- return FALSE;
- if (!xmlHasProp (a_node,
- cur_sel->name->stryng->str))
- return FALSE;
- break;
- case EQUALS:
- {
- xmlChar *value = NULL;
- if (!cur_sel->name
- || !cur_sel->name->stryng
- || !cur_sel->name->stryng->str
- || !cur_sel->value
- || !cur_sel->value->stryng
- || !cur_sel->value->stryng->str)
- return FALSE;
- if (!xmlHasProp
- (a_node,
- cur_sel->name->stryng->str))
- return FALSE;
- value = xmlGetProp
- (a_node,
- cur_sel->name->stryng->str);
- if (value
- && strcmp
- (value,
- cur_sel->value->stryng->str)) {
- xmlFree (value);
- return FALSE;
- }
- xmlFree (value);
- }
- break;
- case INCLUDES:
- {
- xmlChar *value = NULL,
- *ptr1 = NULL,
- *ptr2 = NULL,
- *cur = NULL;
- gboolean found = FALSE;
- if (!xmlHasProp
- (a_node,
- cur_sel->name->stryng->str))
- return FALSE;
- value = xmlGetProp
- (a_node,
- cur_sel->name->stryng->str);
- if (!value)
- return FALSE;
- /*
- *here, make sure value is a space
- *separated list of "words", where one
- *value is exactly cur_sel->value->str
- */
- for (cur = value; *cur; cur++) {
- /*
- *set ptr1 to the first non white space
- *char addr.
- */
- while (cr_utils_is_white_space
- (*cur) == TRUE && *cur)
- cur++;
- if (!*cur)
- break;
- ptr1 = cur;
- /*
- *set ptr2 to the end the word.
- */
- while (cr_utils_is_white_space
- (*cur) == FALSE && *cur)
- cur++;
- cur--;
- ptr2 = cur;
- if (!strncmp
- (ptr1,
- cur_sel->value->stryng->str,
- ptr2 - ptr1 + 1)) {
- found = TRUE;
- break;
- }
- ptr1 = ptr2 = NULL;
- }
- if (found == FALSE) {
- xmlFree (value);
- return FALSE;
- }
- xmlFree (value);
- }
- break;
- case DASHMATCH:
- {
- xmlChar *value = NULL,
- *ptr1 = NULL,
- *ptr2 = NULL,
- *cur = NULL;
- gboolean found = FALSE;
- if (!xmlHasProp
- (a_node,
- cur_sel->name->stryng->str))
- return FALSE;
- value = xmlGetProp
- (a_node,
- cur_sel->name->stryng->str);
- /*
- *here, make sure value is an hyphen
- *separated list of "words", each of which
- *starting with "cur_sel->value->str"
- */
- for (cur = value; *cur; cur++) {
- if (*cur == '-')
- cur++;
- ptr1 = cur;
- while (*cur != '-' && *cur)
- cur++;
- cur--;
- ptr2 = cur;
- if (g_strstr_len
- (ptr1, ptr2 - ptr1 + 1,
- cur_sel->value->stryng->str)
- == (gchar *) ptr1) {
- found = TRUE;
- break;
- }
- }
- if (found == FALSE) {
- xmlFree (value);
- return FALSE;
- }
- xmlFree (value);
- }
- break;
- default:
- return FALSE;
- }
- }
- return TRUE;
- }
- /**
- *Evaluates if a given additional selector matches an xml node.
- *@param a_add_sel the additional selector to consider.
- *@param a_node the xml node to consider.
- *@return TRUE is a_add_sel matches a_node, FALSE otherwise.
- */
- static gboolean
- additional_selector_matches_node (CRSelEng * a_this,
- CRAdditionalSel * a_add_sel,
- xmlNode * a_node)
- {
- CRAdditionalSel *cur_add_sel = NULL, *tail = NULL ;
- gboolean evaluated = FALSE ;
- for (tail = a_add_sel ;
- tail && tail->next;
- tail = tail->next) ;
- g_return_val_if_fail (tail, FALSE) ;
- for (cur_add_sel = tail ;
- cur_add_sel ;
- cur_add_sel = cur_add_sel->prev) {
- evaluated = TRUE ;
- if (cur_add_sel->type == NO_ADD_SELECTOR) {
- return FALSE;
- }
- if (cur_add_sel->type == CLASS_ADD_SELECTOR
- && cur_add_sel->content.class_name
- && cur_add_sel->content.class_name->stryng
- && cur_add_sel->content.class_name->stryng->str) {
- if (class_add_sel_matches_node (cur_add_sel,
- a_node) == FALSE) {
- return FALSE;
- }
- continue ;
- } else if (cur_add_sel->type == ID_ADD_SELECTOR
- && cur_add_sel->content.id_name
- && cur_add_sel->content.id_name->stryng
- && cur_add_sel->content.id_name->stryng->str) {
- if (id_add_sel_matches_node (cur_add_sel, a_node) == FALSE) {
- return FALSE;
- }
- continue ;
- } else if (cur_add_sel->type == ATTRIBUTE_ADD_SELECTOR
- && cur_add_sel->content.attr_sel) {
- /*
- *here, call a function that does the match
- *against an attribute additionnal selector
- *and an xml node.
- */
- if (attr_add_sel_matches_node (cur_add_sel, a_node)
- == FALSE) {
- return FALSE;
- }
- continue ;
- } else if (cur_add_sel->type == PSEUDO_CLASS_ADD_SELECTOR
- && cur_add_sel->content.pseudo) {
- if (pseudo_class_add_sel_matches_node
- (a_this, cur_add_sel, a_node) == TRUE) {
- return TRUE;
- }
- return FALSE;
- }
- }
- if (evaluated == TRUE)
- return TRUE;
- return FALSE ;
- }
- static xmlNode *
- get_next_element_node (xmlNode * a_node)
- {
- xmlNode *cur_node = NULL;
- g_return_val_if_fail (a_node, NULL);
- cur_node = a_node->next;
- while (cur_node && cur_node->type != XML_ELEMENT_NODE) {
- cur_node = cur_node->next;
- }
- return cur_node;
- }
- static xmlNode *
- get_next_child_element_node (xmlNode * a_node)
- {
- xmlNode *cur_node = NULL;
- g_return_val_if_fail (a_node, NULL);
- cur_node = a_node->children;
- if (!cur_node)
- return cur_node;
- if (a_node->children->type == XML_ELEMENT_NODE)
- return a_node->children;
- return get_next_element_node (a_node->children);
- }
- static xmlNode *
- get_prev_element_node (xmlNode * a_node)
- {
- xmlNode *cur_node = NULL;
- g_return_val_if_fail (a_node, NULL);
- cur_node = a_node->prev;
- while (cur_node && cur_node->type != XML_ELEMENT_NODE) {
- cur_node = cur_node->prev;
- }
- return cur_node;
- }
- static xmlNode *
- get_next_parent_element_node (xmlNode * a_node)
- {
- xmlNode *cur_node = NULL;
- g_return_val_if_fail (a_node, NULL);
- cur_node = a_node->parent;
- while (cur_node && cur_node->type != XML_ELEMENT_NODE) {
- cur_node = cur_node->parent;
- }
- return cur_node;
- }
- /**
- *Evaluate a selector (a simple selectors list) and says
- *if it matches the xml node given in parameter.
- *The algorithm used here is the following:
- *Walk the combinator separated list of simple selectors backward, starting
- *from the end of the list. For each simple selector, looks if
- *if matches the current node.
- *
- *@param a_this the selection engine.
- *@param a_sel the simple selection list.
- *@param a_node the xml node.
- *@param a_result out parameter. Set to true if the
- *selector matches the xml node, FALSE otherwise.
- *@param a_recurse if set to TRUE, the function will walk to
- *the next simple selector (after the evaluation of the current one)
- *and recursively evaluate it. Must be usually set to TRUE unless you
- *know what you are doing.
- */
- static enum CRStatus
- sel_matches_node_real (CRSelEng * a_this, CRSimpleSel * a_sel,
- xmlNode * a_node, gboolean * a_result,
- gboolean a_eval_sel_list_from_end,
- gboolean a_recurse)
- {
- CRSimpleSel *cur_sel = NULL;
- xmlNode *cur_node = NULL;
- g_return_val_if_fail (a_this && PRIVATE (a_this)
- && a_this && a_node
- && a_result, CR_BAD_PARAM_ERROR);
- *a_result = FALSE;
- if (a_node->type != XML_ELEMENT_NODE)
- return CR_OK;
- if (a_eval_sel_list_from_end == TRUE) {
- /*go and get the last simple selector of the list */
- for (cur_sel = a_sel;
- cur_sel && cur_sel->next; cur_sel = cur_sel->next) ;
- } else {
- cur_sel = a_sel;
- }
- for (cur_node = a_node; cur_sel; cur_sel = cur_sel->prev) {
- if (((cur_sel->type_mask & TYPE_SELECTOR)
- && (cur_sel->name
- && cur_sel->name->stryng
- && cur_sel->name->stryng->str)
- && (!strcmp (cur_sel->name->stryng->str,
- cur_node->name)))
- || (cur_sel->type_mask & UNIVERSAL_SELECTOR)) {
- /*
- *this simple selector
- *matches the current xml node
- *Let's see if the preceding
- *simple selectors also match
- *their xml node counterpart.
- */
- if (cur_sel->add_sel) {
- if (additional_selector_matches_node (a_this, cur_sel->add_sel,
- cur_node) == TRUE) {
- goto walk_a_step_in_expr;
- } else {
- goto done;
- }
- } else {
- goto walk_a_step_in_expr;
- }
- }
- if (!(cur_sel->type_mask & TYPE_SELECTOR)
- && !(cur_sel->type_mask & UNIVERSAL_SELECTOR)) {
- if (!cur_sel->add_sel) {
- goto done;
- }
- if (additional_selector_matches_node
- (a_this, cur_sel->add_sel, cur_node)
- == TRUE) {
- goto walk_a_step_in_expr;
- } else {
- goto done;
- }
- } else {
- goto done ;
- }
- walk_a_step_in_expr:
- if (a_recurse == FALSE) {
- *a_result = TRUE;
- goto done;
- }
- /*
- *here, depending on the combinator of cur_sel
- *choose the axis of the xml tree traversal
- *and walk one step in the xml tree.
- */
- if (!cur_sel->prev)
- break;
- switch (cur_sel->combinator) {
- case NO_COMBINATOR:
- break;
- case COMB_WS: /*descendant selector */
- {
- xmlNode *n = NULL;
- enum CRStatus status = CR_OK;
- gboolean matches = FALSE;
- /*
- *walk the xml tree upward looking for a parent
- *node that matches the preceding selector.
- */
- for (n = cur_node->parent; n; n = n->parent) {
- status = sel_matches_node_real
- (a_this, cur_sel->prev,
- n, &matches, FALSE, TRUE);
- if (status != CR_OK)
- goto done;
- if (matches == TRUE) {
- cur_node = n ;
- break;
- }
- }
- if (!n) {
- /*
- *didn't find any ancestor that matches
- *the previous simple selector.
- */
- goto done;
- }
- /*
- *in this case, the preceding simple sel
- *will have been interpreted twice, which
- *is a cpu and mem waste ... I need to find
- *another way to do this. Anyway, this is
- *my first attempt to write this function and
- *I am a bit clueless.
- */
- break;
- }
- case COMB_PLUS:
- cur_node = get_prev_element_node (cur_node);
- if (!cur_node)
- goto done;
- break;
- case COMB_GT:
- cur_node = get_next_parent_element_node (cur_node);
- if (!cur_node)
- goto done;
- break;
- default:
- goto done;
- }
- continue;
- }
- /*
- *if we reached this point, it means the selector matches
- *the xml node.
- */
- *a_result = TRUE;
- done:
- return CR_OK;
- }
- /**
- *Returns array of the ruleset statements that matches the
- *given xml node.
- *The engine keeps in memory the last statement he
- *visited during the match. So, the next call
- *to this function will eventually return a rulesets list starting
- *from the last ruleset statement visited during the previous call.
- *The enable users to get matching rulesets in an incremental way.
- *Note that for each statement returned,
- *the engine calculates the specificity of the selector
- *that matched the xml node and stores it in the "specifity" field
- *of the statement structure.
- *
- *@param a_sel_eng the current selection engine
- *@param a_node the xml node for which the request
- *is being made.
- *@param a_sel_list the list of selectors to perform the search in.
- *@param a_rulesets in/out parameter. A pointer to the
- *returned array of rulesets statements that match the xml node
- *given in parameter. The caller allocates the array before calling this
- *function.
- *@param a_len in/out parameter the length (in sizeof (#CRStatement*))
- *of the returned array.
- *(the length of a_rulesets, more precisely).
- *The caller must set it to the length of a_ruleset prior to calling this
- *function. In return, the function sets it to the length
- *(in sizeof (#CRStatement)) of the actually returned CRStatement array.
- *@return CR_OUTPUT_TOO_SHORT_ERROR if found more rulesets than the size
- *of the a_rulesets array. In this case, the first *a_len rulesets found
- *are put in a_rulesets, and a further call will return the following
- *ruleset(s) following the same principle.
- *@return CR_OK if all the rulesets found have been returned. In this
- *case, *a_len is set to the actual number of ruleset found.
- *@return CR_BAD_PARAM_ERROR in case any of the given parameter are
- *bad (e.g null pointer).
- *@return CR_ERROR if any other error occured.
- */
- static enum CRStatus
- cr_sel_eng_get_matched_rulesets_real (CRSelEng * a_this,
- CRStyleSheet * a_stylesheet,
- xmlNode * a_node,
- CRStatement ** a_rulesets,
- gulong * a_len)
- {
- CRStatement *cur_stmt = NULL;
- CRSelector *sel_list = NULL,
- *cur_sel = NULL;
- gboolean matches = FALSE;
- enum CRStatus status = CR_OK;
- gulong i = 0;
- g_return_val_if_fail (a_this
- && a_stylesheet
- && a_node && a_rulesets, CR_BAD_PARAM_ERROR);
- if (!a_stylesheet->statements) {
- *a_rulesets = NULL;
- *a_len = 0;
- return CR_OK;
- }
- /*
- *if this stylesheet is "new one"
- *let's remember it for subsequent calls.
- */
- if (PRIVATE (a_this)->sheet != a_stylesheet) {
- PRIVATE (a_this)->sheet = a_stylesheet;
- PRIVATE (a_this)->cur_stmt = a_stylesheet->statements;
- }
- /*
- *walk through the list of statements and,
- *get the selectors list inside the statements that
- *contain some, and try to match our xml node in these
- *selectors lists.
- */
- for (cur_stmt = PRIVATE (a_this)->cur_stmt, i = 0;
- (PRIVATE (a_this)->cur_stmt = cur_stmt);
- cur_stmt = cur_stmt->next) {
- /*
- *initialyze the selector list in which we will
- *really perform the search.
- */
- sel_list = NULL;
- /*
- *get the the damn selector list in
- *which we have to look
- */
- switch (cur_stmt->type) {
- case RULESET_STMT:
- if (cur_stmt->kind.ruleset
- && cur_stmt->kind.ruleset->sel_list) {
- sel_list = cur_stmt->kind.ruleset->sel_list;
- }
- break;
- case AT_MEDIA_RULE_STMT:
- if (cur_stmt->kind.media_rule
- && cur_stmt->kind.media_rule->rulesets
- && cur_stmt->kind.media_rule->rulesets->
- kind.ruleset
- && cur_stmt->kind.media_rule->rulesets->
- kind.ruleset->sel_list) {
- sel_list =
- cur_stmt->kind.media_rule->
- rulesets->kind.ruleset->sel_list;
- }
- break;
- case AT_IMPORT_RULE_STMT:
- /*
- *some recursivity may be needed here.
- *I don't like this :(
- */
- break;
- default:
- break;
- }
- if (!sel_list)
- continue;
- /*
- *now, we have a comma separated selector list to look in.
- *let's walk it and try to match the xml_node
- *on each item of the list.
- */
- for (cur_sel = sel_list; cur_sel; cur_sel = cur_sel->next) {
- if (!cur_sel->simple_sel)
- continue;
- status = cr_sel_eng_matches_node
- (a_this, cur_sel->simple_sel,
- a_node, &matches);
- if (status == CR_OK && matches == TRUE) {
- /*
- *bingo!!! we found one ruleset that
- *matches that fucking node.
- *lets put it in the out array.
- */
- if (i < *a_len) {
- a_rulesets[i] = cur_stmt;
- i++;
- /*
- *For the cascade computing algorithm
- *(which is gonna take place later)
- *we must compute the specificity
- *(css2 spec chap 6.4.1) of the selector
- *that matched the current xml node
- *and store it in the css2 statement
- *(statement == ruleset here).
- */
- status = cr_simple_sel_compute_specificity (cur_sel->simple_sel);
- g_return_val_if_fail (status == CR_OK,
- CR_ERROR);
- cur_stmt->specificity =
- cur_sel->simple_sel->
- specificity;
- } else
- {
- *a_len = i;
- return CR_OUTPUT_TOO_SHORT_ERROR;
- }
- }
- }
- }
- /*
- *if we reached this point, it means
- *we reached the end of stylesheet.
- *no need to store any info about the stylesheet
- *anymore.
- */
- g_return_val_if_fail (!PRIVATE (a_this)->cur_stmt, CR_ERROR);
- PRIVATE (a_this)->sheet = NULL;
- *a_len = i;
- return CR_OK;
- }
- static enum CRStatus
- put_css_properties_in_props_list (CRPropList ** a_props, CRStatement * a_stmt)
- {
- CRPropList *props = NULL,
- *pair = NULL,
- *tmp_props = NULL;
- CRDeclaration *cur_decl = NULL;
- g_return_val_if_fail (a_props && a_stmt
- && a_stmt->type == RULESET_STMT
- && a_stmt->kind.ruleset, CR_BAD_PARAM_ERROR);
- props = *a_props;
- for (cur_decl = a_stmt->kind.ruleset->decl_list;
- cur_decl; cur_decl = cur_decl->next) {
- CRDeclaration *decl;
- decl = NULL;
- pair = NULL;
- if (!cur_decl->property
- || !cur_decl->property->stryng
- || !cur_decl->property->stryng->str)
- continue;
- /*
- *First, test if the property is not
- *already present in our properties list
- *If yes, apply the cascading rules to
- *compute the precedence. If not, insert
- *the property into the list
- */
- cr_prop_list_lookup_prop (props,
- cur_decl->property,
- &pair);
- if (!pair) {
- tmp_props = cr_prop_list_append2
- (props, cur_decl->property, cur_decl);
- if (tmp_props) {
- props = tmp_props;
- tmp_props = NULL;
- }
- continue;
- }
- /*
- *A property with the same name already exists.
- *We must apply here
- *some cascading rules
- *to compute the precedence.
- */
- cr_prop_list_get_decl (pair, &decl);
- g_return_val_if_fail (decl, CR_ERROR);
- /*
- *first, look at the origin.
- *6.4.1 says:
- *"for normal declarations,
- *author style sheets override user
- *style sheets which override
- *the default style sheet."
- */
- if (decl->parent_statement
- && decl->parent_statement->parent_sheet
- && (decl->parent_statement->parent_sheet->origin
- < a_stmt->parent_sheet->origin)) {
- /*
- *if the already selected declaration
- *is marked as being !important the current
- *declaration must not overide it
- *(unless the already selected declaration
- *has an UA origin)
- */
- if (decl->important == TRUE
- && decl->parent_statement->parent_sheet->origin
- != ORIGIN_UA) {
- continue;
- }
- tmp_props = cr_prop_list_unlink (props, pair);
- if (props) {
- cr_prop_list_destroy (pair);
- }
- props = tmp_props;
- tmp_props = NULL;
- props = cr_prop_list_append2
- (props, cur_decl->property, cur_decl);
- continue;
- } else if (decl->parent_statement
- && decl->parent_statement->parent_sheet
- && (decl->parent_statement->
- parent_sheet->origin
- > a_stmt->parent_sheet->origin)) {
- cr_utils_trace_info
- ("We should not reach this line\n");
- continue;
- }
- /*
- *A property with the same
- *name and the same origin already exists.
- *shit. This is lasting longer than expected ...
- *Luckily, the spec says in 6.4.1:
- *"more specific selectors will override
- *more general ones"
- *and
- *"if two rules have the same weight,
- *origin and specificity,
- *the later specified wins"
- */
- if (a_stmt->specificity
- >= decl->parent_statement->specificity) {
- if (decl->important == TRUE)
- continue;
- props = cr_prop_list_unlink (props, pair);
- if (pair) {
- cr_prop_list_destroy (pair);
- pair = NULL;
- }
- props = cr_prop_list_append2 (props,
- cur_decl->property,
- cur_decl);
- }
- }
- /*TODO: this may leak. Check this out */
- *a_props = props;
- return CR_OK;
- }
- static void
- set_style_from_props (CRStyle * a_style, CRPropList * a_props)
- {
- CRPropList *cur = NULL;
- CRDeclaration *decl = NULL;
- for (cur = a_props; cur; cur = cr_prop_list_get_next (cur)) {
- cr_prop_list_get_decl (cur, &decl);
- cr_style_set_style_from_decl (a_style, decl);
- decl = NULL;
- }
- }
- /****************************************
- *PUBLIC METHODS
- ****************************************/
- /**
- *Creates a new instance of #CRSelEng.
- *@return the newly built instance of #CRSelEng of
- *NULL if an error occurs.
- */
- CRSelEng *
- cr_sel_eng_new (void)
- {
- CRSelEng *result = NULL;
- result = g_try_malloc (sizeof (CRSelEng));
- if (!result) {
- cr_utils_trace_info ("Out of memory");
- return NULL;
- }
- memset (result, 0, sizeof (CRSelEng));
- PRIVATE (result) = g_try_malloc (sizeof (CRSelEngPriv));
- if (!PRIVATE (result)) {
- cr_utils_trace_info ("Out of memory");
- g_free (result);
- return NULL;
- }
- memset (PRIVATE (result), 0, sizeof (CRSelEngPriv));
- cr_sel_eng_register_pseudo_class_sel_handler
- (result, (guchar *) "first-child",
- IDENT_PSEUDO, (CRPseudoClassSelectorHandler)
- first_child_pseudo_class_handler);
- cr_sel_eng_register_pseudo_class_sel_handler
- (result, (guchar *) "lang",
- FUNCTION_PSEUDO, (CRPseudoClassSelectorHandler)
- lang_pseudo_class_handler);
- return result;
- }
- /**
- *Adds a new handler entry in the handlers entry table.
- *@param a_this the current instance of #CRSelEng
- *@param a_pseudo_class_sel_name the name of the pseudo class selector.
- *@param a_pseudo_class_type the type of the pseudo class selector.
- *@param a_handler the actual handler or callback to be called during
- *the selector evaluation process.
- *@return CR_OK, upon successful completion, an error code otherwise.
- */
- enum CRStatus
- cr_sel_eng_register_pseudo_class_sel_handler (CRSelEng * a_this,
- guchar * a_name,
- enum CRPseudoType a_type,
- CRPseudoClassSelectorHandler
- a_handler)
- {
- struct CRPseudoClassSelHandlerEntry *handler_entry = NULL;
- GList *list = NULL;
- g_return_val_if_fail (a_this && PRIVATE (a_this)
- && a_handler && a_name, CR_BAD_PARAM_ERROR);
- handler_entry = g_try_malloc
- (sizeof (struct CRPseudoClassSelHandlerEntry));
- if (!handler_entry) {
- return CR_OUT_OF_MEMORY_ERROR;
- }
- memset (handler_entry, 0,
- sizeof (struct CRPseudoClassSelHandlerEntry));
- handler_entry->name = g_strdup (a_name);
- handler_entry->type = a_type;
- handler_entry->handler = a_handler;
- list = g_list_append (PRIVATE (a_this)->pcs_handlers, handler_entry);
- if (!list) {
- return CR_OUT_OF_MEMORY_ERROR;
- }
- PRIVATE (a_this)->pcs_handlers = list;
- return CR_OK;
- }
- enum CRStatus
- cr_sel_eng_unregister_pseudo_class_sel_handler (CRSelEng * a_this,
- guchar * a_name,
- enum CRPseudoType a_type)
- {
- GList *elem = NULL,
- *deleted_elem = NULL;
- gboolean found = FALSE;
- struct CRPseudoClassSelHandlerEntry *entry = NULL;
- g_return_val_if_fail (a_this && PRIVATE (a_this), CR_BAD_PARAM_ERROR);
- for (elem = PRIVATE (a_this)->pcs_handlers;
- elem; elem = g_list_next (elem)) {
- entry = elem->data;
- if (!strcmp (entry->name, a_name)
- && entry->type == a_type) {
- found = TRUE;
- break;
- }
- }
- if (found == FALSE)
- return CR_PSEUDO_CLASS_SEL_HANDLER_NOT_FOUND_ERROR;
- PRIVATE (a_this)->pcs_handlers = g_list_delete_link
- (PRIVATE (a_this)->pcs_handlers, elem);
- entry = elem->data;
- if (entry->name)
- g_free (entry->name);
- g_free (elem);
- g_list_free (deleted_elem);
- return CR_OK;
- }
- /**
- *Unregisters all the pseudo class sel handlers
- *and frees all the associated allocated datastructures.
- *@param a_this the current instance of #CRSelEng .
- *@return CR_OK upon succesful completion, an error code
- *otherwise.
- */
- enum CRStatus
- cr_sel_eng_unregister_all_pseudo_class_sel_handlers (CRSelEng * a_this)
- {
- GList *elem = NULL;
- struct CRPseudoClassSelHandlerEntry *entry = NULL;
- g_return_val_if_fail (a_this && PRIVATE (a_this), CR_BAD_PARAM_ERROR);
- if (!PRIVATE (a_this)->pcs_handlers)
- return CR_OK;
- for (elem = PRIVATE (a_this)->pcs_handlers;
- elem; elem = g_list_next (elem)) {
- entry = elem->data;
- if (!entry)
- continue;
- if (entry->name) {
- g_free (entry->name);
- entry->name = NULL;
- }
- g_free (entry);
- elem->data = NULL;
- }
- g_list_free (PRIVATE (a_this)->pcs_handlers);
- PRIVATE (a_this)->pcs_handlers = NULL;
- return CR_OK;
- }
- enum CRStatus
- cr_sel_eng_get_pseudo_class_selector_handler (CRSelEng * a_this,
- guchar * a_name,
- enum CRPseudoType a_type,
- CRPseudoClassSelectorHandler *
- a_handler)
- {
- GList *elem = NULL;
- struct CRPseudoClassSelHandlerEntry *entry = NULL;
- gboolean found = FALSE;
- g_return_val_if_fail (a_this && PRIVATE (a_this)
- && a_name, CR_BAD_PARAM_ERROR);
- for (elem = PRIVATE (a_this)->pcs_handlers;
- elem; elem = g_list_next (elem)) {
- entry = elem->data;
- if (!strcmp (a_name, entry->name)
- && entry->type == a_type) {
- found = TRUE;
- break;
- }
- }
- if (found == FALSE)
- return CR_PSEUDO_CLASS_SEL_HANDLER_NOT_FOUND_ERROR;
- *a_handler = entry->handler;
- return CR_OK;
- }
- /**
- *Evaluates a chained list of simple selectors (known as a css2 selector).
- *Says wheter if this selector matches the xml node given in parameter or
- *not.
- *@param a_this the selection engine.
- *@param a_sel the simple selector against which the xml node
- *is going to be matched.
- *@param a_node the node against which the selector is going to be matched.
- *@param a_result out parameter. The result of the match. Is set to
- *TRUE if the selector matches the node, FALSE otherwise. This value
- *is considered if and only if this functions returns CR_OK.
- *@return the CR_OK if the selection ran correctly, an error code otherwise.
- */
- enum CRStatus
- cr_sel_eng_matches_node (CRSelEng * a_this, CRSimpleSel * a_sel,
- xmlNode * a_node, gboolean * a_result)
- {
- g_return_val_if_fail (a_this && PRIVATE (a_this)
- && a_this && a_node
- && a_result, CR_BAD_PARAM_ERROR);
- if (a_node->type != XML_ELEMENT_NODE) {
- *a_result = FALSE;
- return CR_OK;
- }
- return sel_matches_node_real (a_this, a_sel,
- a_node, a_result,
- TRUE, TRUE);
- }
- /**
- *Returns an array of pointers to selectors that matches
- *the xml node given in parameter.
- *
- *@param a_this the current instance of the selection engine.
- *@param a_sheet the stylesheet that holds the selectors.
- *@param a_node the xml node to consider during the walk thru
- *the stylesheet.
- *@param a_rulesets out parameter. A pointer to an array of
- *rulesets statement pointers. *a_rulesets is allocated by
- *this function and must be freed by the caller. However, the caller
- *must not alter the rulesets statements pointer because they
- *point to statements that are still in the css stylesheet.
- *@param a_len the length of *a_ruleset.
- *@return CR_OK upon sucessfull completion, an error code otherwise.
- */
- enum CRStatus
- cr_sel_eng_get_matched_rulesets (CRSelEng * a_this,
- CRStyleSheet * a_sheet,
- xmlNode * a_node,
- CRStatement *** a_rulesets, gulong * a_len)
- {
- CRStatement **stmts_tab = NULL;
- enum CRStatus status = CR_OK;
- gulong tab_size = 0,
- tab_len = 0,
- index = 0;
- gushort stmts_chunck_size = 8;
- g_return_val_if_fail (a_this
- && a_sheet
- && a_node
- && a_rulesets && *a_rulesets == NULL
- && a_len, CR_BAD_PARAM_ERROR);
- stmts_tab = g_try_malloc (stmts_chunck_size * sizeof (CRStatement *));
- if (!stmts_tab) {
- cr_utils_trace_info ("Out of memory");
- status = CR_ERROR;
- goto error;
- }
- memset (stmts_tab, 0, stmts_chunck_size * sizeof (CRStatement *));
- tab_size = stmts_chunck_size;
- tab_len = tab_size;
- while ((status = cr_sel_eng_get_matched_rulesets_real
- (a_this, a_sheet, a_node, stmts_tab + index, &tab_len))
- == CR_OUTPUT_TOO_SHORT_ERROR) {
- stmts_tab = g_try_realloc (stmts_tab,
- (tab_size + stmts_chunck_size)
- * sizeof (CRStatement *));
- if (!stmts_tab) {
- cr_utils_trace_info ("Out of memory");
- status = CR_ERROR;
- goto error;
- }
- tab_size += stmts_chunck_size;
- index += tab_len;
- tab_len = tab_size - index;
- }
- tab_len = tab_size - stmts_chunck_size + tab_len;
- *a_rulesets = stmts_tab;
- *a_len = tab_len;
- return CR_OK;
- error:
- if (stmts_tab) {
- g_free (stmts_tab);
- stmts_tab = NULL;
- }
- *a_len = 0;
- return status;
- }
- enum CRStatus
- cr_sel_eng_get_matched_properties_from_cascade (CRSelEng * a_this,
- CRCascade * a_cascade,
- xmlNode * a_node,
- CRPropList ** a_props)
- {
- CRStatement **stmts_tab = NULL;
- enum CRStatus status = CR_OK;
- gulong tab_size = 0,
- tab_len = 0,
- i = 0,
- index = 0;
- enum CRStyleOrigin origin = 0;
- gushort stmts_chunck_size = 8;
- CRStyleSheet *sheet = NULL;
- g_return_val_if_fail (a_this
- && a_cascade
- && a_node && a_props, CR_BAD_PARAM_ERROR);
- for (origin = ORIGIN_UA; origin < NB_ORIGINS; origin++) {
- sheet = cr_cascade_get_sheet (a_cascade, origin);
- if (!sheet)
- continue;
- if (tab_size - index < 1) {
- stmts_tab = g_try_realloc
- (stmts_tab, (tab_size + stmts_chunck_size)
- * sizeof (CRStatement *));
- if (!stmts_tab) {
- cr_utils_trace_info ("Out of memory");
- status = CR_ERROR;
- goto cleanup;
- }
- tab_size += stmts_chunck_size;
- /*
- *compute the max size left for
- *cr_sel_eng_get_matched_rulesets_real()'s output tab
- */
- tab_len = tab_size - index;
- }
- while ((status = cr_sel_eng_get_matched_rulesets_real
- (a_this, sheet, a_node, stmts_tab + index, &tab_len))
- == CR_OUTPUT_TOO_SHORT_ERROR) {
- stmts_tab = g_try_realloc
- (stmts_tab, (tab_size + stmts_chunck_size)
- * sizeof (CRStatement *));
- if (!stmts_tab) {
- cr_utils_trace_info ("Out of memory");
- status = CR_ERROR;
- goto cleanup;
- }
- tab_size += stmts_chunck_size;
- index += tab_len;
- /*
- *compute the max size left for
- *cr_sel_eng_get_matched_rulesets_real()'s output tab
- */
- tab_len = tab_size - index;
- }
- if (status != CR_OK) {
- cr_utils_trace_info ("Error while running "
- "selector engine");
- goto cleanup;
- }
- index += tab_len;
- tab_len = tab_size - index;
- }
- /*
- *TODO, walk down the stmts_tab and build the
- *property_name/declaration hashtable.
- *Make sure one can walk from the declaration to
- *the stylesheet.
- */
- for (i = 0; i < index; i++) {
- CRStatement *stmt = stmts_tab[i];
- if (!stmt)
- continue;
- switch (stmt->type) {
- case RULESET_STMT:
- if (!stmt->parent_sheet)
- continue;
- status = put_css_properties_in_props_list
- (a_props, stmt);
- break;
- default:
- break;
- }
- }
- status = CR_OK ;
- cleanup:
- if (stmts_tab) {
- g_free (stmts_tab);
- stmts_tab = NULL;
- }
- return status;
- }
- enum CRStatus
- cr_sel_eng_get_matched_style (CRSelEng * a_this,
- CRCascade * a_cascade,
- xmlNode * a_node,
- CRStyle * a_parent_style,
- CRStyle ** a_style,
- gboolean a_set_props_to_initial_values)
- {
- enum CRStatus status = CR_OK;
- CRPropList *props = NULL;
- g_return_val_if_fail (a_this && a_cascade
- && a_node && a_style, CR_BAD_PARAM_ERROR);
- status = cr_sel_eng_get_matched_properties_from_cascade
- (a_this, a_cascade, a_node, &props);
- g_return_val_if_fail (status == CR_OK, status);
- if (props) {
- if (!*a_style) {
- *a_style = cr_style_new (a_set_props_to_initial_values) ;
- g_return_val_if_fail (*a_style, CR_ERROR);
- } else {
- if (a_set_props_to_initial_values == TRUE) {
- cr_style_set_props_to_initial_values (*a_style) ;
- } else {
- cr_style_set_props_to_default_values (*a_style);
- }
- }
- (*a_style)->parent_style = a_parent_style;
- set_style_from_props (*a_style, props);
- if (props) {
- cr_prop_list_destroy (props);
- props = NULL;
- }
- }
- return CR_OK;
- }
- /**
- *The destructor of #CRSelEng
- *@param a_this the current instance of the selection engine.
- */
- void
- cr_sel_eng_destroy (CRSelEng * a_this)
- {
- g_return_if_fail (a_this);
- if (!PRIVATE (a_this))
- goto end ;
- if (PRIVATE (a_this)->pcs_handlers) {
- cr_sel_eng_unregister_all_pseudo_class_sel_handlers
- (a_this) ;
- PRIVATE (a_this)->pcs_handlers = NULL ;
- }
- g_free (PRIVATE (a_this));
- PRIVATE (a_this) = NULL;
- end:
- if (a_this) {
- g_free (a_this);
- }
- }