Main Page | Alphabetical List | Data Structures | File List | Data Fields | Globals | Related Pages

cr-sel-eng.c

Go to the documentation of this file.
00001 /* -*- Mode: C; indent-tabs-mode:nil; c-basic-offset: 8-*- */
00002 
00003 /*
00004  * This file is part of The Croco Library
00005  *
00006  * Copyright (C) 2002-2003 Dodji Seketeli <dodji@seketeli.org>
00007  *
00008  * This program is free software; you can redistribute it and/or
00009  * modify it under the terms of version 2.1 of the GNU Lesser General Public
00010  * License as published by the Free Software Foundation.
00011  *
00012  * This program is distributed in the hope that it will be useful,
00013  * but WITHOUT ANY WARRANTY; without even the implied warranty of
00014  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
00015  * GNU General Public License for more details.
00016  *
00017  * You should have received a copy of the GNU Lesser 
00018  * General Public License
00019  * along with this program; if not, write to the Free Software
00020  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
00021  * USA
00022  */
00023 
00024 
00025 #include <string.h>
00026 #include "cr-sel-eng.h"
00027 
00028 /**
00029  *@file:
00030  *The definition of the  #CRSelEng class.
00031  *The #CRSelEng is actually the "Selection Engine"
00032  *class. This is highly experimental for at the moment and
00033  *its api is very likely to change in a near future.
00034  */
00035 
00036 #define PRIVATE(a_this) (a_this)->priv
00037 
00038 static gboolean
00039 class_add_sel_matches_node (CRAdditionalSel *a_add_sel,
00040                             xmlNode *a_node) ;
00041 
00042 static gboolean
00043 id_add_sel_matches_node (CRAdditionalSel *a_add_sel,
00044                          xmlNode *a_node) ;
00045 
00046 static gboolean
00047 attr_add_sel_matches_node (CRAdditionalSel *a_add_sel,
00048                            xmlNode *a_node) ;
00049 
00050 static enum CRStatus
00051 sel_matches_node_real (CRSelEng *a_this, CRSimpleSel *a_sel,
00052                        xmlNode *a_node, gboolean *a_result,
00053                        gboolean a_recurse) ;
00054 
00055 static enum CRStatus
00056 cr_sel_eng_get_matched_rulesets_real (CRSelEng *a_this, 
00057                                       CRStyleSheet *a_stylesheet,
00058                                       xmlNode *a_node,
00059                                       CRStatement **a_rulesets, 
00060                                       gulong *a_len) ;
00061 
00062 static enum CRStatus
00063 put_css_properties_in_hashtable (GHashTable **a_props_hashtable,
00064                                  CRStatement *a_ruleset) ;
00065 
00066 static void
00067 set_style_from_props_hash_hr_func (gpointer a_prop, gpointer a_decl,
00068                                    gpointer a_style) ;
00069 
00070 struct _CRSelEngPriv
00071 {
00072         /*not used yet*/
00073         gboolean case_sensitive ;
00074 
00075         CRStyleSheet *sheet ;
00076 
00077         /**
00078          *where to store the next statement
00079          *to be visited so that we can remember
00080          *it from one method call to another.
00081          */
00082         CRStatement *cur_stmt ;
00083 };
00084 
00085 /**
00086  *@param a_add_sel the class additional selector to consider.
00087  *@param a_node the xml node to consider.
00088  *@return TRUE if the class additional selector matches
00089  *the xml node given in argument, FALSE otherwise.
00090  */
00091 static gboolean
00092 class_add_sel_matches_node (CRAdditionalSel *a_add_sel,
00093                             xmlNode *a_node)
00094 {
00095         gboolean result = FALSE ;
00096         xmlChar *class = NULL ;
00097 
00098         g_return_val_if_fail (a_add_sel
00099                               && a_add_sel->type == CLASS_ADD_SELECTOR
00100                               && a_add_sel->content.class_name
00101                               && a_add_sel->content.class_name->str
00102                               && a_node, FALSE) ;
00103 
00104         if (xmlHasProp (a_node, "class"))
00105         {
00106                 class = xmlGetProp (a_node, "class") ;
00107                 if (!strncmp (class, a_add_sel->content.class_name->str,
00108                              a_add_sel->content.class_name->len))
00109                 {
00110                         result = TRUE ;
00111                 }
00112         }
00113 
00114         if (class)
00115         {
00116                 xmlFree (class) ;
00117                 class = NULL ;
00118         }
00119         return result ;
00120         
00121 }
00122 
00123 /**
00124  *@return TRUE if the additional attribute selector matches
00125  *the current xml node given in argument, FALSE otherwise.
00126  *@param a_add_sel the additional attribute selector to consider.
00127  *@param a_node the xml node to consider.
00128  */
00129 static gboolean
00130 id_add_sel_matches_node (CRAdditionalSel *a_add_sel,
00131                          xmlNode *a_node)
00132 {
00133         g_return_val_if_fail (a_add_sel 
00134                               && a_add_sel->type == ID_ADD_SELECTOR
00135                               && a_add_sel->content.id_name
00136                               && a_add_sel->content.id_name->str
00137                               && a_node, FALSE) ;
00138 
00139         gboolean result = FALSE ;
00140         xmlChar *id = NULL ;
00141 
00142         g_return_val_if_fail (a_add_sel
00143                               && a_add_sel->type == ID_ADD_SELECTOR
00144                               && a_node, FALSE) ;
00145 
00146         if (xmlHasProp (a_node, "id"))
00147         {
00148                 id = xmlGetProp (a_node, "id") ;
00149                 if (!strncmp (id, a_add_sel->content.id_name->str,
00150                               a_add_sel->content.id_name->len))
00151                 {
00152                         result = TRUE ;
00153                 }
00154         }
00155 
00156         if (id)
00157         {
00158                 xmlFree (id) ;
00159                 id = NULL ;
00160         }
00161         return result ;
00162 }
00163 
00164 /**
00165  *Returns TRUE if the instance of #CRAdditional selector matches
00166  *the node given in parameter, FALSE otherwise.
00167  *@param a_add_sel the additional selector to evaluate.
00168  *@param a_node the xml node against whitch the selector is to
00169  *be evaluated
00170  *return TRUE if the additional selector matches the current xml node
00171  *FALSE otherwise.
00172  */
00173 static gboolean
00174 attr_add_sel_matches_node (CRAdditionalSel *a_add_sel,
00175                            xmlNode *a_node) 
00176 {        
00177         CRAttrSel *cur_sel = NULL ;
00178 
00179         g_return_val_if_fail (a_add_sel 
00180                               && a_add_sel->type == ATTRIBUTE_ADD_SELECTOR
00181                               && a_node, FALSE) ;
00182         
00183         for (cur_sel = a_add_sel->content.attr_sel ; 
00184              cur_sel ; cur_sel = cur_sel->next)
00185         {
00186                 switch (cur_sel->match_way)
00187                 {
00188                 case SET:
00189                         if (!cur_sel->name || !cur_sel->name->str)
00190                                 return FALSE ;
00191 
00192                         if (!xmlHasProp (a_node, cur_sel->name->str))
00193                                 return FALSE ;
00194                         break ;
00195 
00196                 case EQUALS:
00197                 {
00198                         xmlChar *value = NULL ;
00199 
00200                         if (!cur_sel->name || !cur_sel->name->str
00201                             || !cur_sel->value || !cur_sel->value->str)
00202                                 return FALSE ;
00203 
00204                         if (!xmlHasProp (a_node, cur_sel->name->str))
00205                                 return FALSE ;
00206                         
00207                         value = xmlGetProp (a_node, cur_sel->name->str) ;
00208 
00209                         if (value && strncmp (value, cur_sel->value->str,
00210                                               cur_sel->value->len))
00211                         {
00212                                 xmlFree (value) ;
00213                                 return FALSE ;
00214                         }
00215                         xmlFree (value);
00216                 }
00217                 break ;
00218 
00219                 case INCLUDES:
00220                  {
00221                          xmlChar *value = NULL, *ptr1 = NULL, *ptr2 = NULL,
00222                                  *cur = NULL;
00223                          gboolean found = FALSE ;
00224 
00225                         if (!xmlHasProp (a_node, cur_sel->name->str))
00226                                 return FALSE ;
00227                         value = xmlGetProp (a_node, cur_sel->name->str) ;
00228                         
00229                         if (!value)                        
00230                                 return FALSE;
00231                         
00232                         /*
00233                          *here, make sure value is a space
00234                          *separated list of "words", where one
00235                          *value is exactly cur_sel->value->str
00236                          */
00237                         for (cur = value ; *cur ; cur++)
00238                         {
00239                                 /*
00240                                  *set ptr1 to the first non white space
00241                                  *char addr.
00242                                  */
00243                                 while (cr_utils_is_white_space 
00244                                        (*cur) == TRUE && *cur)
00245                                         cur ++ ;
00246                                 if (!*cur)
00247                                         break ;
00248                                 ptr1 = cur ;
00249 
00250                                 /*
00251                                  *set ptr2 to the end the word.
00252                                  */
00253                                 while (cr_utils_is_white_space 
00254                                        (*cur) == FALSE && *cur)
00255                                         cur++ ;
00256                                 if (!*cur)
00257                                         break ;
00258                                 cur-- ;
00259                                 ptr2 = cur ;
00260 
00261                                 if (!strncmp (ptr1, cur_sel->value->str,
00262                                               ptr2 - ptr1 + 1))
00263                                 {
00264                                         found = TRUE ;
00265                                         break ;
00266                                 }
00267                                 ptr1 = ptr2 = NULL ;
00268                         }
00269 
00270                         if (found == FALSE)
00271                         {
00272                                 xmlFree (value) ;
00273                                 return FALSE ;
00274                         }
00275                         xmlFree (value) ;
00276                 }
00277                  break ;
00278 
00279                 case DASHMATCH:
00280                 {
00281                         xmlChar *value = NULL, *ptr1 = NULL, *ptr2 = NULL,
00282                                  *cur = NULL;
00283                          gboolean found = FALSE ;
00284                         
00285                         if (!xmlHasProp (a_node, cur_sel->name->str))
00286                                 return FALSE ;
00287                         value = xmlGetProp (a_node, cur_sel->name->str) ;
00288 
00289                         /*
00290                          *here, make sure value is an hyphen
00291                          *separated list of "words", each of which
00292                          *starting with "cur_sel->value->str"
00293                          */
00294                         for (cur = value ; *cur ; cur++)
00295                         {
00296                                 if (*cur == '-')
00297                                         cur ++ ;
00298                                 ptr1 = cur ;
00299                                 
00300                                 while (*cur != '-' && *cur)
00301                                         cur ++ ;
00302                                 if (!*cur)
00303                                         break ;
00304                                 cur-- ;
00305                                 ptr2 = cur ;
00306                                 
00307                                 if (g_strstr_len (ptr1, ptr2 - ptr1 + 1,
00308                                                   cur_sel->value->str)
00309                                         == (gchar*)ptr1)
00310                                 {
00311                                         found = TRUE ;
00312                                         break ;
00313                                 }
00314                         }
00315 
00316                         if (found == FALSE)
00317                         {
00318                                 xmlFree (value) ;
00319                                 return FALSE ;
00320                         }
00321                         xmlFree (value) ;
00322                 }
00323                 break ;
00324                 default:
00325                         return FALSE ;
00326                 }
00327         }
00328 
00329         return TRUE ;
00330 }
00331 
00332 /**
00333  *Evaluate a selector (a simple selectors list) and says
00334  *if it matches the xml node given in parameter.
00335  *The algorithm used here is the following:
00336  *Walk the combinator separated list of simple selectors backward, starting
00337  *from the end of the list. For each simple selector, looks if
00338  *if matches the current node.
00339  *
00340  *@param a_this the selection engine.
00341  *@param a_sel the simple selection list.
00342  *@param a_node the xml node.
00343  *@param a_result out parameter. Set to true if the
00344  *selector matches the xml node, FALSE otherwise.
00345  *@param a_recurse if set to TRUE, the function will walk to
00346  *the next simple selector (after the evaluation of the current one) 
00347  *and recursively evaluate it. Must be usually set to TRUE unless you
00348  *know what you are doing.
00349  */
00350 static enum CRStatus
00351 sel_matches_node_real (CRSelEng *a_this, CRSimpleSel *a_sel,
00352                        xmlNode *a_node, gboolean *a_result,
00353                        gboolean a_recurse)
00354 {
00355         CRSimpleSel *cur_sel = NULL ;
00356         xmlNode *cur_node = NULL ;
00357         
00358         g_return_val_if_fail (a_this && PRIVATE (a_this)
00359                               && a_this && a_node 
00360                               && a_result,
00361                               CR_BAD_PARAM_ERROR) ;
00362 
00363         *a_result = FALSE ;
00364 
00365         if (a_node->type != XML_ELEMENT_NODE)
00366                 return CR_OK ;
00367 
00368         
00369         if (a_recurse == TRUE)
00370         {
00371                 /*go and get the last simple selector of the list*/
00372                 for (cur_sel = a_sel ; 
00373                      cur_sel && cur_sel->next ; 
00374                      cur_sel = cur_sel->next) ;
00375         }
00376         else
00377         {
00378                 cur_sel = a_sel ;
00379         }        
00380 
00381         for (cur_node = a_node ; cur_sel ; cur_sel = cur_sel->prev)
00382         {
00383                 if (cur_sel->type_mask & UNIVERSAL_SELECTOR)
00384                 {
00385                         goto walk_a_step_in_expr ;
00386                 }
00387                 else if (cur_sel->type_mask & TYPE_SELECTOR)
00388                 {
00389                         if (cur_sel && cur_sel->name && cur_sel->name->str)
00390                         {
00391                                 if (!strcmp (cur_sel->name->str,
00392                                              cur_node->name))
00393                                 {
00394                                         /*
00395                                          *this simple selector
00396                                          *matches the current xml node
00397                                          *Let's see if the preceding
00398                                          *simple selectors also match
00399                                          *their xml node counterpart.
00400                                          */
00401                                         goto walk_a_step_in_expr ;
00402                                 }
00403                                 goto done ;
00404                         }
00405                         else
00406                         {
00407                                 goto done ;
00408                         }
00409                 }
00410 
00411                 if (!cur_sel->add_sel)
00412                 {
00413                         goto done ;
00414                 }
00415 
00416                 if (cur_sel->add_sel->type == NO_ADD_SELECTOR)
00417                 {
00418                         goto done ;
00419                 }
00420                 
00421                 if (cur_sel->add_sel->type == CLASS_ADD_SELECTOR
00422                     && cur_sel->add_sel->content.class_name
00423                     && cur_sel->add_sel->content.class_name->str)
00424                 {
00425                         if (class_add_sel_matches_node 
00426                             (cur_sel->add_sel, cur_node) == FALSE)
00427                         {
00428                                 goto done ;
00429                         }
00430                         goto walk_a_step_in_expr ;
00431                 }
00432                 else if (cur_sel->add_sel->type == ID_ADD_SELECTOR
00433                          && cur_sel->add_sel->content.id_name
00434                          && cur_sel->add_sel->content.id_name->str)
00435                 {
00436                         if (id_add_sel_matches_node 
00437                             (cur_sel->add_sel, cur_node) == FALSE)
00438                         {
00439                                goto done;
00440                         }
00441                         goto walk_a_step_in_expr ;
00442                 }
00443                 else if (cur_sel->add_sel->type == ATTRIBUTE_ADD_SELECTOR
00444                          && cur_sel->add_sel->content.attr_sel)
00445                 {
00446                         /*
00447                          *here, call a function that does the match
00448                          *against an attribute additionnal selector
00449                          *and an xml node.
00450                          */
00451                         if (attr_add_sel_matches_node 
00452                             (cur_sel->add_sel, cur_node)
00453                             == FALSE)
00454                         {
00455                                 goto done ;
00456                         }
00457                         goto walk_a_step_in_expr ;
00458                 }
00459 
00460         walk_a_step_in_expr:
00461                 if (a_recurse == FALSE)
00462                 {
00463                         *a_result = TRUE ;
00464                         goto done ;
00465                 }
00466 
00467                 /*
00468                  *here, depending on the combinator of cur_sel
00469                  *choose the axis of the xml tree traversal
00470                  *and walk one step in the xml tree.
00471                  */
00472                 if (!cur_sel->prev)
00473                         break ;
00474 
00475                 switch (cur_sel->combinator)
00476                 {
00477                 case NO_COMBINATOR:
00478                         break ;
00479 
00480                 case COMB_WS:/*descendant selector*/
00481                 {
00482                         xmlNode *n = NULL ;
00483                         enum CRStatus status = CR_OK ;
00484                         gboolean matches= FALSE ;
00485 
00486                         /*
00487                          *walk the xml tree upward looking for a parent
00488                          *node that matches the preceding selector.
00489                          */
00490                         for (n = cur_node->parent ; n ; n = n->parent)
00491                         {
00492                                 status = 
00493                                         sel_matches_node_real (a_this,
00494                                                                cur_sel->prev,
00495                                                                n,
00496                                                                &matches,
00497                                                                FALSE) ;
00498                                 if (status != CR_OK)
00499                                         goto done ;
00500 
00501                                 if (matches == TRUE)
00502                                 {
00503                                         cur_node = n ;
00504                                         break ;
00505                                 }
00506                         }
00507 
00508                         if (!n)
00509                         {
00510                                 /*
00511                                  *didn't find any ancestor that matches
00512                                  *the previous simple selector.
00513                                  */
00514                                 goto done ;
00515                         }
00516                         /*
00517                          *in this case, the preceding simple sel
00518                          *will have been interpreted twice, which
00519                          *is a cpu and mem waste ... I need to find
00520                          *another way to do this. Anyway, this is
00521                          *my first attempt to write this function and
00522                          *I am a bit clueless.
00523                          */
00524                         break ;
00525                 }
00526      
00527                 case COMB_PLUS:
00528                 {
00529                         if (!cur_node->prev)
00530                                 goto done ;
00531                         cur_node = cur_node->prev ;
00532                 }
00533                 break ;
00534 
00535                 case COMB_GT:
00536                         if (!cur_node->parent)
00537                                 goto done ;
00538                         cur_node = cur_node->parent ;
00539                         break ;
00540 
00541                 default:
00542                         goto done ;
00543                 }
00544                 continue ;
00545                 
00546         }
00547         
00548         /*
00549          *if we reached this point, it means the selector matches
00550          *the xml node.
00551          */
00552         *a_result = TRUE ;
00553         
00554  done:
00555         return CR_OK ;
00556 }
00557 
00558 
00559 /**
00560  *Returns  array of the ruleset statements that matches the
00561  *given xml node.
00562  *The engine keeps in memory the last statement he
00563  *visited during the match. So, the next call
00564  *to this function will eventually return a rulesets list starting
00565  *from the last ruleset statement visited during the previous call.
00566  *The enable users to get matching rulesets in an incremental way.
00567  *Note that for each statement returned, 
00568  *the engine calculates the specificity of the selector
00569  *that matched the xml node and stores it in the "specifity" field
00570  *of the statement structure.
00571  *
00572  *@param a_sel_eng the current selection engine
00573  *@param a_node the xml node for which the request
00574  *is being made.
00575  *@param a_sel_list the list of selectors to perform the search in.
00576  *@param a_rulesets in/out parameter. A pointer to the
00577  *returned array of rulesets statements that match the xml node
00578  *given in parameter. The caller allocates the array before calling this
00579  *function.
00580  *@param a_len in/out parameter the length (in sizeof (#CRStatement*)) 
00581  *of the returned array.
00582  *(the length of a_rulesets, more precisely).
00583  *The caller must set it to the length of a_ruleset prior to calling this
00584  *function. In return, the function sets it to the length 
00585  *(in sizeof (#CRStatement)) of the actually returned CRStatement array.
00586  *@return CR_OUTPUT_TOO_SHORT_ERROR if found more rulesets than the size
00587  *of the a_rulesets array. In this case, the first *a_len rulesets found
00588  *are put in a_rulesets, and a further call will return the following
00589  *ruleset(s) following the same principle.
00590  *@return CR_OK if all the rulesets found have been returned. In this
00591  *case, *a_len is set to the actual number of ruleset found.
00592  *@return CR_BAD_PARAM_ERROR in case any of the given parameter are
00593  *bad (e.g null pointer).
00594  *@return CR_ERROR if any other error occured.
00595  */
00596 static enum CRStatus
00597 cr_sel_eng_get_matched_rulesets_real (CRSelEng *a_this, 
00598                                       CRStyleSheet *a_stylesheet,
00599                                       xmlNode *a_node,
00600                                       CRStatement **a_rulesets, 
00601                                       gulong *a_len)
00602 {
00603         CRStatement *cur_stmt = NULL ;
00604         CRSelector *sel_list = NULL, *cur_sel = NULL ;
00605         gboolean matches = FALSE ;
00606         enum CRStatus status = CR_OK ;
00607         gulong i = 0;
00608 
00609         g_return_val_if_fail (a_this
00610                               && a_stylesheet
00611                               && a_stylesheet->statements
00612                               && a_node
00613                               && a_rulesets,
00614                               CR_BAD_PARAM_ERROR) ;
00615 
00616         /*
00617          *if this stylesheet is "new one"
00618          *let's remember it for subsequent calls.
00619          */
00620         if (PRIVATE (a_this)->sheet != a_stylesheet)
00621         {
00622                 PRIVATE (a_this)->sheet = a_stylesheet ;
00623                 PRIVATE (a_this)->cur_stmt =  a_stylesheet->statements ;
00624         }
00625 
00626         /*
00627          *walk through the list of statements and,
00628          *get the selectors list inside the statements that
00629          *contain some, and try to match our xml node in these
00630          *selectors lists.
00631          */
00632         for (cur_stmt = PRIVATE (a_this)->cur_stmt, i = 0 ;
00633              (PRIVATE (a_this)->cur_stmt = cur_stmt); 
00634              cur_stmt = cur_stmt->next)
00635         {
00636                 /*
00637                  *initialyze the selector list in which we will
00638                  *really perform the search.
00639                  */
00640                 sel_list = NULL ;
00641 
00642                 /*
00643                  *get the the damn selector list in 
00644                  *which we have to look
00645                  */
00646                 switch (cur_stmt->type)
00647                 {
00648                 case RULESET_STMT:
00649                         if (cur_stmt->kind.ruleset 
00650                             && cur_stmt->kind.ruleset->sel_list)
00651                         {
00652                                 sel_list = cur_stmt->kind.ruleset->sel_list ;
00653                         }
00654                         break ;
00655                 
00656                 case AT_MEDIA_RULE_STMT:
00657                         if (cur_stmt->kind.media_rule
00658                             && cur_stmt->kind.media_rule->rulesets
00659                             && cur_stmt->kind.media_rule->rulesets->
00660                             kind.ruleset
00661                             &&cur_stmt->kind.media_rule->rulesets->
00662                                 kind.ruleset->sel_list)
00663                         {
00664                                 sel_list = 
00665                                         cur_stmt->kind.media_rule->
00666                                         rulesets->kind.ruleset->sel_list ;
00667                         }
00668                         break ;
00669 
00670                 case AT_IMPORT_RULE_STMT:
00671                         /*
00672                          *some recursivity may be needed here.
00673                          *I don't like this :(
00674                          */
00675                         break ;
00676                 default:
00677                         break ;
00678                 }
00679 
00680                 if (!sel_list)
00681                         continue ;
00682 
00683                 /*
00684                  *now, we have a comma separated selector list to look in.
00685                  *let's walk it and try to match the xml_node
00686                  *on each item of the list.
00687                  */
00688                 for (cur_sel = sel_list ; cur_sel ; cur_sel = cur_sel->next)
00689                 {
00690                         if (!cur_sel->simple_sel)
00691                                 continue ;
00692 
00693                         status = cr_sel_eng_matches_node 
00694                                 (a_this, cur_sel->simple_sel,
00695                                  a_node, &matches) ;
00696 
00697                         if (status == CR_OK && matches == TRUE)
00698                         {
00699                                 /*
00700                                  *bingo!!! we found one ruleset that
00701                                  *matches that fucking node.
00702                                  *lets put it in the out array.
00703                                  */
00704 
00705                                 if (i < *a_len)
00706                                 {
00707                                         a_rulesets[i] = cur_stmt ;
00708                                         i++ ;
00709 
00710                                         /*
00711                                          *For the cascade computing algorithm
00712                                          *(which is gonna take place later)
00713                                          *we must compute the specificity
00714                                          *(css2 spec chap 6.4.1) of the selector
00715                                          *that matched the current xml node
00716                                          *and store it in the css2 statement
00717                                          *(statement == ruleset here).
00718                                          */
00719                                         status = 
00720                                                 cr_simple_sel_compute_specificity
00721                                                 (cur_sel->simple_sel) ;
00722 
00723                                         g_return_val_if_fail (status == CR_OK,
00724                                                               CR_ERROR) ;
00725                                         cur_stmt->specificity = 
00726                                                 cur_sel->simple_sel->specificity;
00727                                 }
00728                                 else
00729                                         
00730                                 {
00731                                         *a_len = i ;
00732                                         return CR_OUTPUT_TOO_SHORT_ERROR ;
00733                                 }
00734                         }
00735                 }
00736         }
00737 
00738         /*
00739          *if we reached this point, it means
00740          *we reached the end of stylesheet.
00741          *no need to store any info about the stylesheet
00742          *anymore.
00743          */
00744         g_return_val_if_fail (!PRIVATE (a_this)->cur_stmt, CR_ERROR) ;
00745         PRIVATE (a_this)->sheet = NULL ;
00746         *a_len = i ;
00747         return CR_OK ;
00748 }
00749 
00750 /**
00751  *Walks through the property/value pairs of a ruleset
00752  *statement and put the properties found into a hashtable.
00753  *Each key of the hashtable is a css property. The
00754  *associated value is a pointer to the current #CRDeclaration.
00755  *This function is where the cascading property sorting is done.
00756  *
00757  *@param a_props_hashtable in/out parameter. The hashtable into
00758  *which the the property/Declaration pairs will be added.
00759  *Note that each hashtable key (a statement property) is a null terminated 
00760  *instance of guchar *.
00761  *Each value associated to a key is an instance of #CRDeclaration. 
00762  *The declaration is actually the css declaration (rule) 
00763  *that contains the property (the key).
00764  *@param a_ruleset the ruleset from wich the properties are gathered.
00765  *@return CR_OK upon successfull completion, an error code otherwise.
00766  */
00767 static enum CRStatus
00768 put_css_properties_in_hashtable (GHashTable **a_props_hashtable,
00769                                  CRStatement *a_stmt)
00770 {
00771         GHashTable *props_hash = NULL ;
00772         CRDeclaration *cur_decl = NULL ;
00773 
00774         g_return_val_if_fail (a_props_hashtable && a_stmt
00775                               && a_stmt->type == RULESET_STMT
00776                               && a_stmt->kind.ruleset,
00777                               CR_BAD_PARAM_ERROR) ;
00778 
00779         if (!*a_props_hashtable)
00780         {
00781                 *a_props_hashtable = g_hash_table_new (g_str_hash,
00782                                                        g_str_equal) ;
00783         }
00784         props_hash = *a_props_hashtable ;
00785 
00786         for (cur_decl = a_stmt->kind.ruleset->decl_list ; 
00787              cur_decl ; cur_decl = cur_decl->next)
00788         {
00789                 if (!cur_decl->property || !cur_decl->property->str)
00790                         continue ;
00791 
00792                 CRDeclaration *decl = NULL ;
00793 
00794                 /*
00795                  *First, test if the property is not
00796                  *already present in our properties hashtable.
00797                  *If yes, apply the cascading rules to
00798                  *compute the precedence. If not, insert
00799                  *the property into the hashtable.
00800                  */
00801                 decl = g_hash_table_lookup 
00802                         (props_hash, cur_decl->property->str) ;
00803 
00804                 if (!decl)
00805                 {
00806                         g_hash_table_replace 
00807                                 (props_hash,
00808                                  cur_decl->property->str,
00809                                  cur_decl) ;
00810                         continue ;
00811                 }
00812 
00813                 /*
00814                  *A property with the same name already exists.
00815                  *We must apply here 
00816                  *some cascading rules
00817                  *to compute the precedence.
00818                  */
00819 
00820                 /*
00821                  *first, look at the origin.
00822                  *6.4.1 says: 
00823                  *"for normal declarations, 
00824                  *author style sheets override user 
00825                  *style sheets which override 
00826                  *the default style sheet."
00827                  */
00828                 if (decl->parent_statement 
00829                     && decl->parent_statement->parent_sheet
00830                     && (decl->parent_statement->parent_sheet->origin 
00831                         <
00832                         a_stmt->parent_sheet->origin))
00833                 {
00834                         g_hash_table_insert 
00835                                 (props_hash,
00836                                  cur_decl->property->str,
00837                                  cur_decl) ;
00838                         continue ;
00839                 }
00840                 else if (decl->parent_statement 
00841                          && decl->parent_statement->parent_sheet
00842                          && (decl->parent_statement->
00843                              parent_sheet->origin 
00844                              >
00845                              a_stmt->parent_sheet->origin))
00846                 {
00847                         /*TODO: support !important rule.*/
00848                         continue ;
00849                 }
00850 
00851                 /*
00852                  *A property with the same
00853                  *name and the same origin already exist.
00854                  *shit. This is lasting longer than expected ...
00855                  *Luckily, the spec says in 6.4.1:
00856                  *"more specific selectors will override 
00857                  *more general ones"
00858                  *and
00859                  *"if two rules have the same weight, 
00860                  *origin and specificity, 
00861                  *the latter specified wins"
00862                  */
00863                 if (a_stmt->specificity
00864                     >= decl->parent_statement->specificity)
00865                 {
00866                         g_hash_table_insert
00867                                 (props_hash,
00868                                  cur_decl->property->str,
00869                                  cur_decl) ;
00870                 }
00871         }
00872 
00873 
00874         return CR_OK ;
00875 }
00876 
00877 
00878 
00879 static void
00880 set_style_from_props_hash_hr_func (gpointer a_prop, gpointer a_decl,
00881                                    gpointer a_style)
00882 {
00883         CRDeclaration *decl = a_decl ;
00884         CRStyle *style = a_style ;
00885 
00886         g_return_if_fail (a_decl && a_prop && a_style) ;
00887 
00888         cr_style_set_style_from_decl (style, decl) ;
00889 }
00890 
00891 
00892 /****************************************
00893  *PUBLIC METHODS
00894  ****************************************/
00895 
00896 /**
00897  *Creates a new instance of #CRSelEng.
00898  *@return the newly built instance of #CRSelEng of
00899  *NULL if an error occurs.
00900  */
00901 CRSelEng *
00902 cr_sel_eng_new (void)
00903 {
00904         CRSelEng *result = NULL;
00905 
00906         result = g_try_malloc (sizeof (CRSelEng)) ;
00907         if (!result)
00908         {
00909                 cr_utils_trace_info ("Out of memory") ;
00910                 return NULL ;
00911         }
00912         memset (result, 0, sizeof (CRSelEng)) ;
00913 
00914         PRIVATE (result) = g_try_malloc (sizeof (CRSelEngPriv)) ;
00915         if (!PRIVATE (result))
00916         {
00917                 cr_utils_trace_info ("Out of memory") ;
00918                 g_free (result) ;
00919                 return NULL ;
00920         }
00921         memset (PRIVATE (result), 0, sizeof (CRSelEngPriv)) ;
00922 
00923         return result ;
00924 }
00925 
00926 
00927 /**
00928  *Evaluates a chained list of simple selectors (known as a css2 selector).
00929  *Says wheter if this selector matches the xml node given in parameter or
00930  *not.
00931  *@param a_this the selection engine.
00932  *@param a_sel the simple selector against which the xml node 
00933  *is going to be matched.
00934  *@param a_node the node against which the selector is going to be matched.
00935  *@param a_result out parameter. The result of the match. Is set to
00936  *TRUE if the selector matches the node, FALSE otherwise. This value
00937  *is considered if and only if this functions returns CR_OK.
00938  *@return the CR_OK if the selection ran correctly, an error code otherwise.
00939  */
00940 enum CRStatus
00941 cr_sel_eng_matches_node (CRSelEng *a_this, CRSimpleSel *a_sel,
00942                          xmlNode *a_node, gboolean *a_result)
00943 {
00944         g_return_val_if_fail (a_this && PRIVATE (a_this)
00945                               && a_this && a_node 
00946                               && a_result,
00947                               CR_BAD_PARAM_ERROR) ;
00948 
00949         if (a_node->type != XML_ELEMENT_NODE)
00950         {
00951                 *a_result = FALSE ;
00952                 return CR_OK ;
00953         }
00954 
00955         return sel_matches_node_real (a_this, a_sel, a_node,
00956                                       a_result, TRUE) ;
00957 }
00958 
00959 /**
00960  *Returns an array of pointers to selectors that matches
00961  *the xml node given in parameter.
00962  *
00963  *@param a_this the current instance of the selection engine.
00964  *@param a_sheet the stylesheet that holds the selectors.
00965  *@param a_node the xml node to consider during the walk thru
00966  *the stylesheet.
00967  *@param a_rulesets out parameter. A pointer to an array of
00968  *rulesets statement pointers. *a_rulesets is allocated by
00969  *this function and must be freed by the caller. However, the caller
00970  *must not alter the rulesets statements pointer because they
00971  *point to statements that are still in the css stylesheet.
00972  *@param a_len the length of *a_ruleset.
00973  *@return CR_OK upon sucessfull completion, an error code otherwise.
00974  */
00975 enum CRStatus
00976 cr_sel_eng_get_matched_rulesets (CRSelEng *a_this,
00977                                  CRStyleSheet *a_sheet,
00978                                  xmlNode *a_node,
00979                                  CRStatement ***a_rulesets,
00980                                  gulong *a_len)
00981 {
00982         CRStatement ** stmts_tab = NULL ;
00983         enum CRStatus status = CR_OK ;
00984         gulong tab_size = 0, tab_len = 0, index = 0 ;
00985         gushort stmts_chunck_size = 8 ;
00986 
00987         g_return_val_if_fail (a_this
00988                               && a_sheet
00989                               && a_node
00990                               && a_rulesets && *a_rulesets == NULL
00991                               && a_len,
00992                               CR_BAD_PARAM_ERROR) ;
00993 
00994         stmts_tab = g_try_malloc (stmts_chunck_size *
00995                                   sizeof (CRStatement *)) ;
00996 
00997         if (!stmts_tab)
00998         {
00999                 cr_utils_trace_info ("Out of memory") ;
01000                 status = CR_ERROR ;
01001                 goto error ;
01002         }
01003         memset (stmts_tab, 0, stmts_chunck_size * sizeof (CRStatement*)) ;
01004 
01005         tab_size = stmts_chunck_size ;
01006         tab_len = tab_size ;
01007 
01008         while ((status = cr_sel_eng_get_matched_rulesets_real 
01009                 (a_this, a_sheet, a_node, stmts_tab + index, &tab_len))
01010                == CR_OUTPUT_TOO_SHORT_ERROR)
01011         {
01012                 stmts_tab = g_try_realloc (stmts_tab,
01013                                            (tab_size + stmts_chunck_size)
01014                                            * sizeof (CRStatement*)) ;
01015                 if (!stmts_tab)
01016                 {
01017                         cr_utils_trace_info ("Out of memory") ;
01018                         status = CR_ERROR ;
01019                         goto error ;
01020                 }
01021                 tab_size +=  stmts_chunck_size ;
01022                 index += tab_len ;
01023                 tab_len = tab_size - index ;
01024         }
01025 
01026 
01027         tab_len = tab_size - stmts_chunck_size +tab_len ;
01028         *a_rulesets = stmts_tab ;
01029         *a_len = tab_len ;
01030 
01031         return CR_OK ;
01032 
01033  error:
01034 
01035         if (stmts_tab)
01036         {
01037                 g_free (stmts_tab) ;
01038                 stmts_tab = NULL ;
01039                 
01040         }
01041 
01042         *a_len = 0 ;
01043         return status ;
01044 }
01045 
01046 enum CRStatus
01047 cr_sel_eng_get_matched_properties_from_cascade  (CRSelEng *a_this,
01048                                                  CRCascade *a_cascade,
01049                                                  xmlNode *a_node,
01050                                                  GHashTable **a_props_hashtable)
01051 {
01052         CRStatement ** stmts_tab = NULL ;
01053         enum CRStatus status = CR_OK ;
01054         gulong tab_size = 0, tab_len = 0, index = 0, i = 0  ;
01055         enum CRStyleOrigin origin = 0 ;
01056         gushort stmts_chunck_size = 8 ;
01057         CRStyleSheet *sheet = NULL ;
01058 
01059         g_return_val_if_fail (a_this
01060                               && a_cascade
01061                               && a_node
01062                               && a_props_hashtable,
01063                               CR_BAD_PARAM_ERROR) ;
01064 
01065         stmts_tab = g_try_malloc (stmts_chunck_size *
01066                                   sizeof (CRStatement *)) ;
01067 
01068         if (!stmts_tab)
01069         {
01070                 cr_utils_trace_info ("Out of memory") ;
01071                 status = CR_ERROR ;
01072                 goto error ;
01073         }
01074         memset (stmts_tab, 0, stmts_chunck_size * sizeof (CRStatement*)) ;
01075         tab_size = stmts_chunck_size ;
01076                 tab_len = tab_size ;
01077 
01078         for (origin = ORIGIN_UA ; origin < NB_ORIGINS ; origin++)
01079         {
01080                 sheet = cr_cascade_get_sheet (a_cascade, origin) ;
01081                 if (!sheet)
01082                         continue ;                
01083 
01084                 while ((status = cr_sel_eng_get_matched_rulesets_real 
01085                         (a_this, sheet, a_node, stmts_tab + index, &tab_len))
01086                        == CR_OUTPUT_TOO_SHORT_ERROR)
01087                 {
01088                         stmts_tab = g_try_realloc 
01089                                 (stmts_tab,
01090                                  (tab_size + stmts_chunck_size)
01091                                  * sizeof (CRStatement*)) ;
01092                         if (!stmts_tab)
01093                         {
01094                                 cr_utils_trace_info ("Out of memory") ;
01095                                 status = CR_ERROR ;
01096                                 goto error ;
01097                         }
01098                         tab_size +=  stmts_chunck_size ;
01099                         index += tab_len ;
01100                         tab_len = tab_size - index ;
01101                 }
01102                 if (status != CR_OK)
01103                 {
01104                         cr_utils_trace_info ("Error while running "
01105                                              "selector engine") ;
01106                         goto error ;
01107                 }                
01108                 
01109         }
01110 
01111         /*
01112          *TODO, walk down the stmts_tab and build the
01113          *property_name/declaration hashtable.
01114          *Make sure one can walk from the declaration to
01115          *the stylesheet.
01116          */
01117         for (i = 0 ; i < tab_len ; i ++)
01118         {
01119                 CRStatement *stmt = stmts_tab[i] ;
01120 
01121                 if (!stmt)
01122                         continue ;
01123                 
01124                 switch (stmt->type)
01125                 {
01126                 case RULESET_STMT:
01127                         if (!stmt->parent_sheet)
01128                                 continue ;
01129                         status = put_css_properties_in_hashtable
01130                                 (a_props_hashtable,
01131                                  stmt) ;
01132                         break ;
01133 
01134                 default:
01135                         break ;
01136                 }
01137                 
01138         }
01139 
01140         return CR_OK ;
01141  error:
01142 
01143         if (stmts_tab)
01144         {
01145                 g_free (stmts_tab) ;
01146                 stmts_tab = NULL ;
01147                 
01148         }
01149 
01150         return status ;
01151 }
01152 
01153 
01154 enum CRStatus
01155 cr_sel_eng_get_matched_style (CRSelEng *a_this,
01156                               CRCascade *a_cascade,
01157                               xmlNode *a_node,
01158                               CRStyle *a_parent_style,
01159                               CRStyle **a_style)
01160 {
01161         enum CRStatus status = CR_OK ;
01162         GHashTable *props_hash = NULL ;
01163 
01164         g_return_val_if_fail (a_this && a_cascade
01165                               && a_node && a_style
01166                               && (*a_style == NULL),
01167                               CR_BAD_PARAM_ERROR) ;
01168         
01169         status = cr_sel_eng_get_matched_properties_from_cascade 
01170                 (a_this, a_cascade, a_node, &props_hash) ;
01171         g_return_val_if_fail (status == CR_OK, status) ;
01172 
01173         if (props_hash && g_hash_table_size (props_hash))
01174         {
01175                 
01176                 if (!*a_style)
01177                 {
01178                         *a_style = cr_style_new () ;
01179                         g_return_val_if_fail (*a_style, CR_ERROR) ;
01180                 }
01181                 (*a_style)->parent_style = a_parent_style ;
01182                 
01183                 g_hash_table_foreach (props_hash,
01184                                       ((GHFunc)
01185                                        set_style_from_props_hash_hr_func),
01186                                       *a_style) ;                
01187         }
01188 
01189         if (props_hash)
01190         {
01191                 g_hash_table_destroy (props_hash) ;
01192                 props_hash = NULL ;
01193         }
01194 
01195         return CR_OK ;
01196 }
01197 
01198 /**
01199  *The destructor of #CRSelEng
01200  *@param a_this the current instance of the selection engine.
01201  */
01202 void
01203 cr_sel_eng_destroy (CRSelEng *a_this)
01204 {
01205         g_return_if_fail (a_this) ;
01206 
01207         if (PRIVATE (a_this))
01208         {
01209                 g_free (PRIVATE (a_this)) ;
01210                 PRIVATE (a_this) = NULL ;
01211         }
01212 
01213         if (a_this)
01214         {
01215                 g_free (a_this) ;
01216         }
01217 }
01218 

Generated on Wed Oct 1 01:36:47 2003 for Libcroco by doxygen 1.3.3