| /* -*- Mode: C; indent-tabs-mode:nil; c-basic-offset: 8-*- */ |
| |
| /* libcroco - Library for parsing and applying CSS |
| * Copyright (C) 2006-2019 Free Software Foundation, Inc. |
| * |
| * This file is not part of the GNU gettext program, but is used with |
| * GNU gettext. |
| * |
| * The original copyright notice is as follows: |
| */ |
| |
| /* |
| * This file is part of The Croco Library |
| * |
| * Copyright (C) 2003-2004 Dodji Seketeli. All Rights Reserved. |
| * |
| * 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 |
| */ |
| |
| #include <config.h> |
| #include <string.h> |
| #include "cr-sel-eng.h" |
| |
| /** |
| *@CRSelEng: |
| * |
| *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); |
| |
| /* Quick strcmp. Test only for == 0 or != 0, not < 0 or > 0. */ |
| #define strqcmp(str,lit,lit_len) \ |
| (strlen (str) != (lit_len) || memcmp (str, lit, lit_len)) |
| |
| 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 (strqcmp (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, (const xmlChar *) "lang"); |
| if (val |
| && !strqcmp ((const char *) 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, (guchar *) 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, (const xmlChar *) "class")) { |
| klass = xmlGetProp (a_node, (const xmlChar *) "class"); |
| for (cur = klass; cur && *cur; cur++) { |
| while (cur && *cur |
| && cr_utils_is_white_space (*cur) |
| == TRUE) |
| cur++; |
| |
| if (!strncmp ((const char *) 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; |
| } else { /* if it doesn't match, */ |
| /* then skip to next whitespace character to try again */ |
| while (cur && *cur && !(cr_utils_is_white_space(*cur) == TRUE)) |
| cur++; |
| } |
| 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, (const xmlChar *) "id")) { |
| id = xmlGetProp (a_node, (const xmlChar *) "id"); |
| if (!strqcmp ((const char *) 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, |
| (const xmlChar *) 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, |
| (const xmlChar *) cur_sel->name->stryng->str)) |
| return FALSE; |
| |
| value = xmlGetProp |
| (a_node, |
| (const xmlChar *) cur_sel->name->stryng->str); |
| |
| if (value |
| && strcmp |
| ((const char *) 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, |
| (const xmlChar *) cur_sel->name->stryng->str)) |
| return FALSE; |
| value = xmlGetProp |
| (a_node, |
| (const xmlChar *) 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 |
| ((const char *) 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, |
| (const xmlChar *) cur_sel->name->stryng->str)) |
| return FALSE; |
| value = xmlGetProp |
| (a_node, |
| (const xmlChar *) 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 |
| ((const gchar *) 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, |
| (const char *) 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 occurred. |
| */ |
| 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 |
| ****************************************/ |
| |
| /** |
| * cr_sel_eng_new: |
| *Creates a new instance of #CRSelEng. |
| * |
| *Returns 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; |
| } |
| |
| /** |
| * cr_sel_eng_register_pseudo_class_sel_handler: |
| *@a_this: the current instance of #CRSelEng |
| *@a_pseudo_class_sel_name: the name of the pseudo class selector. |
| *@a_pseudo_class_type: the type of the pseudo class selector. |
| *@a_handler: the actual handler or callback to be called during |
| *the selector evaluation process. |
| * |
| *Adds a new handler entry in the handlers entry table. |
| * |
| *Returns 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 = (guchar *) g_strdup ((const gchar *) 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 ((const char *) entry->name, (const char *) 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; |
| } |
| |
| /** |
| * cr_sel_eng_unregister_all_pseudo_class_sel_handlers: |
| *@a_this: the current instance of #CRSelEng . |
| * |
| *Unregisters all the pseudo class sel handlers |
| *and frees all the associated allocated datastructures. |
| * |
| *Returns 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 ((const char *) a_name, (const char *) 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; |
| } |
| |
| /** |
| * cr_sel_eng_matches_node: |
| *@a_this: the selection engine. |
| *@a_sel: the simple selector against which the xml node |
| *is going to be matched. |
| *@a_node: the node against which the selector is going to be matched. |
| *@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. |
| * |
| *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. |
| * |
| *Returns 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); |
| } |
| |
| /** |
| * cr_sel_eng_get_matched_rulesets: |
| *@a_this: the current instance of the selection engine. |
| *@a_sheet: the stylesheet that holds the selectors. |
| *@a_node: the xml node to consider during the walk thru |
| *the stylesheet. |
| *@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. |
| *@a_len: the length of *a_ruleset. |
| * |
| *Returns an array of pointers to selectors that matches |
| *the xml node given in parameter. |
| * |
| *Returns 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; |
| } |
| |
| /** |
| * cr_sel_eng_destroy: |
| *@a_this: the current instance of the selection engine. |
| * |
| *The destructor of #CRSelEng |
| */ |
| 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); |
| } |
| } |