cr-sel-eng.c revision f534eb5832f0adc1775a0b23be1de3eb18ca00b6
/* -*- Mode: C; indent-tabs-mode:nil; c-basic-offset: 8-*- */
/*
* This file is part of The Croco Library
*
* modify it under the terms of version 2.1 of the GNU Lesser General Public
* License as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU Lesser
* General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
* USA
*
* See COPYRIGHTS file for copyright informations.
*/
#include <string.h>
#include "cr-sel-eng.h"
#include "cr-node-iface.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.
*/
struct CRPseudoClassSelHandlerEntry {
enum CRPseudoType type;
};
struct _CRSelEngPriv {
/*not used yet */
CRNodeIface const *node_iface;
/**
*where to store the next statement
*to be visited so that we can remember
*it from one method call to another.
*/
} ;
CRSimpleSel * a_sel,
CRStatement **
static CRXMLNodePtr get_next_child_element_node (CRNodeIface const * a_node_iface, CRXMLNodePtr a_node);
static CRXMLNodePtr get_next_parent_element_node (CRNodeIface const * a_node_iface, CRXMLNodePtr a_node);
void
{
/* Allow NULL: the caller may be just ensuring that the previous node_iface
value doesn't get used until next cr_sel_eng_set_node_iface call. */
}
/* Quick strcmp. Test only for == 0 or != 0, not < 0 or > 0. */
static gboolean
{
CRNodeIface const *node_iface;
/* "xml:lang" needed for SVG */
cr_utils_trace_info ("This handler is for :lang only");
return FALSE;
}
/*lang code should exist and be at least of length 2 */
return FALSE;
if (val) {
break;
}
}
}
return result;
}
static gboolean
{
"first-child")
cr_utils_trace_info ("This handler is for :first-child only");
return FALSE;
}
if (!parent)
return FALSE;
}
static gboolean
{
&& a_add_sel
return FALSE;
}
/**
*@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
{
if (klass) {
char const *cur;
&& cr_utils_is_white_space (*cur)
== TRUE)
cur++;
} else { /* if it doesn't match, */
/* then skip to next whitespace character to try again */
cur++;
}
break ;
}
}
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
{
if (id) {
}
}
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
{
return FALSE;
if (!value)
goto free_and_return_false;
case SET:
break;
case EQUALS:
goto free_and_return_false;
}
if (strcmp
(value,
goto free_and_return_false;
}
break;
case INCLUDES:
{
/*
*here, make sure value is a space
*separated list of "words", where one
*value is exactly cur_sel->value->str
*/
/*
*set ptr1 to the first non white space
*char addr.
*/
while (cr_utils_is_white_space (*cur)
&& *cur)
cur++;
if (!*cur)
break;
/*
*set ptr2 to the end the word.
*/
while (!cr_utils_is_white_space (*cur)
&& *cur)
cur++;
cur--;
if (!strncmp
((const char *) ptr1,
break;
}
}
if (!found) {
goto free_and_return_false;
}
}
break;
case DASHMATCH:
{
/*
*here, make sure value is an hyphen
*separated list of "words", each of which
*starting with "cur_sel->value->str"
*/
if (*cur == '-')
cur++;
cur++;
cur--;
if (g_strstr_len
== ptr1) {
break;
}
}
if (!found) {
goto free_and_return_false;
}
}
break;
default:
goto free_and_return_false;
}
continue;
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
{
for (cur_add_sel = tail ;
return FALSE;
}
a_node)) {
return FALSE;
}
continue ;
a_node)) {
return FALSE;
}
continue ;
/*
*here, call a function that does the match
*against an attribute additionnal selector
*and an xml node.
*/
a_node)) {
return FALSE;
}
continue ;
return FALSE;
}
continue ;
}
}
return TRUE;
return FALSE ;
}
static CRXMLNodePtr
{
do {
return cur_node;
}
/* TODO: Consider renaming this to get_first_child_element_node.
(cf get_first_parent_element_node, which does getParent until element node
rather than getNextSibling). */
static CRXMLNodePtr
{
if (!cur_node)
return cur_node;
return cur_node;
}
static CRXMLNodePtr
{
do {
return cur_node;
}
static CRXMLNodePtr
{
do {
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
{
&& a_result, CR_BAD_PARAM_ERROR);
return CR_OK;
if (a_eval_sel_list_from_end == TRUE) {
/*go and get the last simple selector of the list */
} else {
}
/*
*this simple selector
*matches the current xml node
*Let's see if the preceding
*simple selectors also match
*their xml node counterpart.
*/
goto walk_a_step_in_expr;
} else {
goto done;
}
} else {
goto walk_a_step_in_expr;
}
}
goto done;
}
== TRUE) {
goto walk_a_step_in_expr;
} else {
goto done;
}
} else {
goto done ;
}
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.
*/
break;
switch (cur_sel->combinator) {
case NO_COMBINATOR:
break;
case COMB_WS: /*descendant selector */
{
CRXMLNodePtr n = NULL;
/*
*walk the xml tree upward looking for a parent
*node that matches the preceding selector.
*/
n;
n = node_iface->getParentNode (n)) {
goto done;
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:
if (!cur_node)
goto done;
break;
case COMB_GT:
if (!cur_node)
goto done;
break;
default:
goto done;
}
continue;
}
/*
*if we reached this point, it means the selector matches
*the xml node.
*/
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.
*returned array of rulesets statements that match the xml node
*given in parameter. The caller allocates the array before calling this
*function.
*of the returned array.
*(the length of a_rulesets, more precisely).
*The caller must set it to the length of a_ruleset prior to calling this
*function. In return, the function sets it to the length
*(in sizeof (#CRStatement)) of the actually returned CRStatement array.
*@return CR_OUTPUT_TOO_SHORT_ERROR if found more rulesets than the size
*of the a_rulesets array. In this case, the first *a_len rulesets found
*are put in a_rulesets, and a further call will return the following
*ruleset(s) following the same principle.
*@return CR_OK if all the rulesets found have been returned. In this
*case, *a_len is set to the actual number of ruleset found.
*@return CR_BAD_PARAM_ERROR in case any of the given parameter are
*bad (e.g null pointer).
*@return CR_ERROR if any other error occured.
*/
static enum CRStatus
{
gulong i = 0;
&& a_stylesheet
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.
*/
}
/*
*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.
*/
/*
*initialyze the selector list in which we will
*really perform the search.
*/
/*
*get the damn selector list in
*which we have to look
*/
case RULESET_STMT:
}
break;
case AT_MEDIA_RULE_STMT:
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.
*/
if (!cur_sel->simple_sel)
continue;
/*
*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).
*/
CR_ERROR);
} 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.
*/
*a_len = i;
return CR_OK;
}
static enum CRStatus
{
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
*/
&pair);
if (!pair) {
if (tmp_props) {
}
continue;
}
/*
*A property with the same name already exists.
*We must apply here
*some cascading rules
*to compute the precedence.
*/
/*
*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
/*
*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)
*/
!= ORIGIN_UA) {
continue;
}
if (props) {
}
continue;
} else if (decl->parent_statement
&& (decl->parent_statement->
("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
continue;
if (pair) {
}
cur_decl);
}
}
/*TODO: this may leak. Check this out */
return CR_OK;
}
static void
{
}
}
/****************************************
*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)
{
if (!result) {
cr_utils_trace_info ("Out of memory");
return NULL;
}
cr_utils_trace_info ("Out of memory");
return NULL;
}
IDENT_PSEUDO, /*(CRPseudoClassSelectorHandler)*/
FUNCTION_PSEUDO, /*(CRPseudoClassSelectorHandler)*/
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
enum CRPseudoType a_type,
{
(sizeof (struct CRPseudoClassSelHandlerEntry));
if (!handler_entry) {
return CR_OUT_OF_MEMORY_ERROR;
}
memset (handler_entry, 0,
sizeof (struct CRPseudoClassSelHandlerEntry));
if (!list) {
return CR_OUT_OF_MEMORY_ERROR;
}
return CR_OK;
}
enum CRStatus
enum CRPseudoType a_type)
{
*deleted_elem = NULL;
break;
}
}
}
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
{
return CR_OK;
if (!entry)
continue;
}
}
return CR_OK;
}
enum CRStatus
enum CRPseudoType a_type,
{
&& a_name, CR_BAD_PARAM_ERROR);
break;
}
}
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
{
&& a_result, CR_BAD_PARAM_ERROR);
return CR_OK;
}
}
/**
* 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
{
tab_len = 0,
index = 0;
&& a_sheet
&& a_node
&& a_len, CR_BAD_PARAM_ERROR);
if (!stmts_tab) {
cr_utils_trace_info ("Out of memory");
goto error;
}
while ((status = cr_sel_eng_get_matched_rulesets_real
* sizeof (CRStatement *));
if (!stmts_tab) {
cr_utils_trace_info ("Out of memory");
goto error;
}
}
*a_rulesets = stmts_tab;
return CR_OK;
if (stmts_tab) {
}
*a_len = 0;
return status;
}
enum CRStatus
CRPropList ** a_props)
{
tab_len = 0,
i = 0,
index = 0;
enum CRStyleOrigin origin;
&& a_cascade
if (!sheet)
continue;
* sizeof (CRStatement *));
if (!stmts_tab) {
cr_utils_trace_info ("Out of memory");
goto cleanup;
}
/*
*compute the max size left for
*cr_sel_eng_get_matched_rulesets_real()'s output tab
*/
}
while ((status = cr_sel_eng_get_matched_rulesets_real
* sizeof (CRStatement *));
if (!stmts_tab) {
cr_utils_trace_info ("Out of memory");
goto cleanup;
}
/*
*compute the max size left for
*cr_sel_eng_get_matched_rulesets_real()'s output tab
*/
}
cr_utils_trace_info ("Error while running "
"selector engine");
goto cleanup;
}
}
/*
*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++) {
if (!stmt)
continue;
case RULESET_STMT:
if (!stmt->parent_sheet)
continue;
break;
default:
break;
}
}
if (stmts_tab) {
}
return status;
}
enum CRStatus
{
if (props) {
if (!*a_style) {
} else {
if (a_set_props_to_initial_values == TRUE) {
} else {
}
}
if (props) {
}
}
return CR_OK;
}
/**
* cr_sel_eng_destroy:
*@a_this: the current instance of the selection engine.
*
*The destructor of #CRSelEng
*/
void
{
goto end ;
(a_this) ;
}
end:
if (a_this) {
}
}