System/Libraries/Html/Renderer: A whole bunch of CSS changes
We now handle simple and compound selectors, but not yet pseudo-classes or (most) combinators.
This commit is contained in:
parent
fc2b4ba4e5
commit
3bf6238aac
1 changed files with 372 additions and 211 deletions
|
@ -93,7 +93,7 @@ class @html_renderer
|
|||
|
||||
#define HtmlRenderer @html_renderer
|
||||
|
||||
#define HTML_WORK_BUFFER_SIZE 2048
|
||||
#define HTML_WORK_BUFFER_SIZE 4096
|
||||
|
||||
// Initialize CSS default rules
|
||||
JsonArray* CSS_DEFAULT_RULES = Json.CreateArray(erythros_mem_task);
|
||||
|
@ -283,111 +283,17 @@ U32 @css_resolve_color_from_rrggbb(U8* str)
|
|||
}
|
||||
}
|
||||
|
||||
Bool @apply_css_rules_to_node(@html_dom_node* node, HtmlRenderer* renderer)
|
||||
Bool @apply_css_properties_to_node(@html_dom_node* node, JsonObject* properties)
|
||||
{
|
||||
I64 i, j, k;
|
||||
JsonObject* rule = NULL;
|
||||
JsonArray* matches = NULL;
|
||||
JsonObject* properties = NULL;
|
||||
JsonKey* key = NULL;
|
||||
JsonArray* values = NULL;
|
||||
U8* selector = NULL;
|
||||
Bool matched = FALSE;
|
||||
Bool should_display = TRUE;
|
||||
U8 node_classes_buffer[HTML_WORK_BUFFER_SIZE];
|
||||
U8 prepend_buffer[64];
|
||||
U8 append_buffer[64];
|
||||
MemSet(prepend_buffer, 0, 64);
|
||||
MemSet(append_buffer, 0, 64);
|
||||
I64 i, j;
|
||||
JsonArray* values = NULL;
|
||||
JsonKey* font_key = NULL;
|
||||
JsonKey* key = properties->keys;
|
||||
U8 node_tmpnum_buf[16];
|
||||
U8** node_classes;
|
||||
I64 node_classes_count = 0;
|
||||
I64 color = TRANSPARENT;
|
||||
U8 node_ptr_string[32];
|
||||
U8* tmpmd5;
|
||||
U8* match_font_family;
|
||||
JsonKey* font_key;
|
||||
U8* match_font_family = NULL;
|
||||
|
||||
if (!node)
|
||||
return FALSE;
|
||||
if (node->parentNode) {
|
||||
node->backgroundColor = node->parentNode->backgroundColor;
|
||||
node->color = node->parentNode->color;
|
||||
node->linethroughColor = node->parentNode->linethroughColor;
|
||||
node->underlineColor = node->parentNode->underlineColor;
|
||||
node->fontFamily = node->parentNode->fontFamily;
|
||||
node->fontSize = node->parentNode->fontSize;
|
||||
node->fontWeight = node->parentNode->fontWeight;
|
||||
node->textAlign = node->parentNode->textAlign;
|
||||
node->italic = node->parentNode->italic;
|
||||
}
|
||||
|
||||
for (i = 0; i < renderer->css_rules->length; i++) {
|
||||
rule = renderer->css_rules->@(i);
|
||||
matched = FALSE;
|
||||
if (rule->@("matches")) {
|
||||
|
||||
matches = rule->@("matches");
|
||||
properties = rule->@("properties");
|
||||
|
||||
// check if node md5 hash matches
|
||||
if (*(matches->@(0)(U8*)) == 0xFE) {
|
||||
StrPrint(node_ptr_string, "0x%08x", node);
|
||||
tmpmd5 = md5_string(node_ptr_string, StrLen(node_ptr_string));
|
||||
if (!StrCmp(matches->@(0) + 1, tmpmd5)) {
|
||||
matched = TRUE;
|
||||
Free(tmpmd5);
|
||||
goto @css_rule_check_if_matched;
|
||||
}
|
||||
Free(tmpmd5);
|
||||
}
|
||||
|
||||
// try to match id
|
||||
if (*(matches->@(0)(U8*)) == '#' && node->attributes->@("id")) {
|
||||
if (!StrCmp(matches->@(0) + 1, node->attributes->@("id"))) {
|
||||
matched = TRUE;
|
||||
goto @css_rule_check_if_matched;
|
||||
}
|
||||
}
|
||||
|
||||
// try to match selectors
|
||||
for (j = 0; j < matches->length; j++) {
|
||||
selector = matches->@(j);
|
||||
|
||||
if (node->attributes->@("class") && StrFirstOcc(selector, ".")) {
|
||||
// node has class attribute and current selector has .class
|
||||
|
||||
if (!StrFirstOcc(node->attributes->@("class"), " ")) {
|
||||
if (!StrCmp(node->attributes->@("class"), StrFirstOcc(selector, ".") + 1)) {
|
||||
matched = TRUE;
|
||||
goto @css_rule_check_if_matched;
|
||||
}
|
||||
} else {
|
||||
MemSet(node_classes_buffer, 0, HTML_WORK_BUFFER_SIZE);
|
||||
StrCpy(node_classes_buffer, node->attributes->@("class"));
|
||||
node_classes = String.Split(node_classes_buffer, ' ', &node_classes_count);
|
||||
|
||||
for (k = 0; k < node_classes_count; k++) {
|
||||
if (!StrCmp(node_classes[k], StrFirstOcc(selector, ".") + 1)) {
|
||||
matched = TRUE;
|
||||
Free(node_classes);
|
||||
goto @css_rule_check_if_matched;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// try to match tagName
|
||||
if (!StrICmp(matches->@(0), node->tagName)) {
|
||||
matched = TRUE;
|
||||
goto @css_rule_check_if_matched;
|
||||
}
|
||||
|
||||
@css_rule_check_if_matched : if (matched)
|
||||
{
|
||||
key = properties->keys;
|
||||
for (j = 0; j < properties->length; j++) {
|
||||
for (i = 0; i < properties->length; i++) {
|
||||
values = properties->@(key->name);
|
||||
|
||||
if (!StrICmp(key->name, "display")) {
|
||||
|
@ -496,9 +402,9 @@ Bool @apply_css_rules_to_node(@html_dom_node* node, HtmlRenderer* renderer)
|
|||
}
|
||||
|
||||
if (!StrICmp(key->name, "font-family")) {
|
||||
for (k = 0; k < values->length; k++) {
|
||||
for (j = 0; j < values->length; j++) {
|
||||
|
||||
match_font_family = values->@(k);
|
||||
match_font_family = values->@(j);
|
||||
String.Trim(match_font_family);
|
||||
String.Trim(match_font_family, '"');
|
||||
|
||||
|
@ -521,6 +427,261 @@ Bool @apply_css_rules_to_node(@html_dom_node* node, HtmlRenderer* renderer)
|
|||
css_continue_to_next_property:
|
||||
key = key->next;
|
||||
}
|
||||
return should_display;
|
||||
}
|
||||
|
||||
Bool @css_selector_contains_combinator(U8* selector)
|
||||
{
|
||||
U8* combinators = ">| +~";
|
||||
while (*selector) {
|
||||
if (StrOcc(combinators, *selector))
|
||||
return TRUE;
|
||||
++selector;
|
||||
}
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
Bool @css_selector_is_compound_selector(U8* selector)
|
||||
{
|
||||
U8* tokens = ".:[#";
|
||||
++selector;
|
||||
while (*selector) {
|
||||
if (StrOcc(tokens, *selector))
|
||||
return TRUE;
|
||||
++selector;
|
||||
}
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
Bool @node_matches_simple_selector(@html_dom_node* node, U8* selector)
|
||||
{
|
||||
//"node: 0x%08x, selector: %s, ", node, selector;
|
||||
//"attributes: %s\n", Json.Stringify(node->attributes, erythros_mem_task);
|
||||
Bool match = FALSE;
|
||||
U8** node_classes = NULL;
|
||||
I64 node_classes_count = 0;
|
||||
U8 buffer[HTML_WORK_BUFFER_SIZE];
|
||||
U8* name = NULL;
|
||||
U8* value = NULL;
|
||||
|
||||
I64 i = 0;
|
||||
U8 ptr_to_c_string[32];
|
||||
U8* ptr_to_md5_string = NULL;
|
||||
switch (*selector) {
|
||||
case '\xfe':
|
||||
StrPrint(ptr_to_c_string, "0x%08x", node);
|
||||
ptr_to_md5_string = md5_string(ptr_to_c_string, StrLen(ptr_to_c_string));
|
||||
if (!StrCmp(selector + 1, ptr_to_md5_string)) {
|
||||
match = TRUE;
|
||||
}
|
||||
Free(ptr_to_md5_string);
|
||||
return match;
|
||||
case '#':
|
||||
if (!node->attributes->@("id"))
|
||||
return FALSE;
|
||||
return !StrCmp(selector + 1, node->attributes->@("id"));
|
||||
case '.':
|
||||
if (!node->attributes->@("class"))
|
||||
return FALSE;
|
||||
if (!StrOcc(node->attributes->@("class"), ' ')) {
|
||||
if (!StrCmp(selector + 1, node->attributes->@("class"))) {
|
||||
match = TRUE;
|
||||
}
|
||||
} else {
|
||||
MemSet(buffer, 0, HTML_WORK_BUFFER_SIZE);
|
||||
StrCpy(buffer, node->attributes->@("class"));
|
||||
node_classes = String.Split(buffer, ' ', &node_classes_count);
|
||||
for (i = 0; i < node_classes_count; i++) {
|
||||
if (!StrCmp(selector + 1, node_classes[i])) {
|
||||
match = TRUE;
|
||||
i = node_classes_count;
|
||||
}
|
||||
}
|
||||
Free(node_classes);
|
||||
}
|
||||
return match;
|
||||
case '[':
|
||||
if (selector[StrLen(selector) - 1] == ']') {
|
||||
StrCpy(buffer, selector + 1);
|
||||
value = buffer + StrLen(buffer) - 1;
|
||||
while (StrOcc("'\"]", *value)) {
|
||||
*value = NULL;
|
||||
--value;
|
||||
}
|
||||
while (!StrOcc("'\"=", *value)) {
|
||||
--value;
|
||||
}
|
||||
name = value;
|
||||
++value;
|
||||
while (StrOcc("'\"=", *value)) {
|
||||
*name = NULL;
|
||||
--name;
|
||||
}
|
||||
name = buffer;
|
||||
if (StrLen(name) && node->attributes->@(name) && !StrCmp(node->attributes->@(name), value)) {
|
||||
match = TRUE;
|
||||
}
|
||||
}
|
||||
return match;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return !StrICmp(node->tagName, selector);
|
||||
}
|
||||
|
||||
Bool @node_matches_compound_selector(@html_dom_node* node, U8* compound_selector)
|
||||
{
|
||||
U8 buffer[HTML_WORK_BUFFER_SIZE];
|
||||
U8* tokens = ".:[#";
|
||||
I64 i = 0;
|
||||
I64 j = 1;
|
||||
I64 length = StrLen(compound_selector);
|
||||
while (j && j < length) {
|
||||
if (StrOcc(tokens, compound_selector[j])) {
|
||||
MemSet(buffer, 0, length);
|
||||
MemCpy(buffer, compound_selector + i, j - i);
|
||||
i = j;
|
||||
//"nmcs: try: 0x%08x, %s\n", node, buffer;
|
||||
if (!@node_matches_simple_selector(node, buffer))
|
||||
return FALSE;
|
||||
}
|
||||
++j;
|
||||
}
|
||||
//"nmcs: end: 0x%08x, %s\n", node, compound_selector + i;
|
||||
return @node_matches_simple_selector(node, compound_selector + i);
|
||||
}
|
||||
|
||||
@html_dom_node* @ancestor_who_matches_compound_selector(@html_dom_node* node, U8* selector)
|
||||
{
|
||||
while (node) {
|
||||
//"amcs: node: 0x%08x, selector: %s\n", node, selector;
|
||||
if (@node_matches_compound_selector(node, selector))
|
||||
return node;
|
||||
node = node->parentNode;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
Bool @node_matches_selector_with_combinator(@html_dom_node* node, U8* selector_with_combinator)
|
||||
{
|
||||
//"nmswc: node: 0x%08x, swc: %s\n", node, selector_with_combinator;
|
||||
// FIXME: We only handle the descendant combinator for now
|
||||
U8* ch = selector_with_combinator;
|
||||
while (*ch) {
|
||||
if (StrOcc(">|+~", *ch))
|
||||
return FALSE;
|
||||
++ch;
|
||||
}
|
||||
|
||||
@html_dom_node* ancestor = NULL;
|
||||
U8 buffer[HTML_WORK_BUFFER_SIZE];
|
||||
StrCpy(buffer, selector_with_combinator);
|
||||
String.Trim(buffer);
|
||||
U8* selector_for_ancestor = NULL;
|
||||
|
||||
U8* selector_for_node = buffer + StrLen(buffer) - 1;
|
||||
while (*selector_for_node != ' ') {
|
||||
--selector_for_node;
|
||||
}
|
||||
|
||||
ancestor = node->parentNode;
|
||||
selector_for_ancestor = selector_for_node;
|
||||
++selector_for_node;
|
||||
|
||||
if (!@node_matches_compound_selector(node, selector_for_node))
|
||||
return FALSE;
|
||||
|
||||
// We matched the selector for node, now let's match the selectors for ancestors
|
||||
|
||||
while (selector_for_ancestor > buffer) {
|
||||
// null terminate the selector for ancestor
|
||||
while (*selector_for_ancestor == ' ') {
|
||||
*selector_for_ancestor = NULL;
|
||||
--selector_for_ancestor;
|
||||
}
|
||||
// move selector for ancestor pointer to the left until we either:
|
||||
// 1) hit the beginning of the buffer, or
|
||||
// 2) hit whitespace
|
||||
while (selector_for_ancestor > buffer && (*selector_for_ancestor != ' ')) {
|
||||
--selector_for_ancestor;
|
||||
}
|
||||
if (*selector_for_ancestor == ' ')
|
||||
++selector_for_ancestor;
|
||||
ancestor = @ancestor_who_matches_compound_selector(ancestor, selector_for_ancestor);
|
||||
if (!ancestor)
|
||||
return FALSE;
|
||||
--selector_for_ancestor;
|
||||
}
|
||||
// if we got here, we must be a match
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
Bool @node_matches_css_selector(@html_dom_node* node, U8* selector)
|
||||
{
|
||||
// We need to handle combinators ("div ol li"), compound selectors "p.class#id", simple selectors
|
||||
|
||||
// FIXME: Handle combinators
|
||||
if (@css_selector_contains_combinator(selector)) {
|
||||
return @node_matches_selector_with_combinator(node, selector);
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
// FIXME: Handle compound selectors
|
||||
if (@css_selector_is_compound_selector(selector)) {
|
||||
return @node_matches_compound_selector(node, selector);
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
return @node_matches_simple_selector(node, selector);
|
||||
}
|
||||
|
||||
U0 @inherit_css_values_from_parent_node(@html_dom_node* node)
|
||||
{
|
||||
if (node && node->parentNode) {
|
||||
node->backgroundColor = node->parentNode->backgroundColor;
|
||||
node->color = node->parentNode->color;
|
||||
node->linethroughColor = node->parentNode->linethroughColor;
|
||||
node->underlineColor = node->parentNode->underlineColor;
|
||||
node->fontFamily = node->parentNode->fontFamily;
|
||||
node->fontSize = node->parentNode->fontSize;
|
||||
node->fontWeight = node->parentNode->fontWeight;
|
||||
node->textAlign = node->parentNode->textAlign;
|
||||
node->italic = node->parentNode->italic;
|
||||
}
|
||||
}
|
||||
|
||||
Bool @apply_css_rules_to_node(@html_dom_node* node, HtmlRenderer* renderer)
|
||||
{
|
||||
|
||||
if (!node)
|
||||
return FALSE;
|
||||
|
||||
I64 i, j;
|
||||
JsonObject* rule = NULL;
|
||||
JsonArray* matches = NULL;
|
||||
JsonObject* properties = NULL;
|
||||
U8* selector = NULL;
|
||||
Bool matched = FALSE;
|
||||
Bool should_display = TRUE;
|
||||
|
||||
@inherit_css_values_from_parent_node(node);
|
||||
|
||||
for (i = 0; i < renderer->css_rules->length; i++) {
|
||||
rule = renderer->css_rules->@(i);
|
||||
matched = FALSE;
|
||||
if (rule->@("matches")) {
|
||||
matches = rule->@("matches");
|
||||
properties = rule->@("properties");
|
||||
for (j = 0; j < matches->length; j++) {
|
||||
selector = matches->@(j);
|
||||
if (@node_matches_css_selector(node, selector)) {
|
||||
matched = TRUE;
|
||||
goto @css_rule_check_if_matched;
|
||||
}
|
||||
}
|
||||
@css_rule_check_if_matched : if (matched)
|
||||
{
|
||||
should_display = @apply_css_properties_to_node(node, properties);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue