/* -*- 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 #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); } }