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 HtmlRenderer @html_renderer
|
||||||
|
|
||||||
#define HTML_WORK_BUFFER_SIZE 2048
|
#define HTML_WORK_BUFFER_SIZE 4096
|
||||||
|
|
||||||
// Initialize CSS default rules
|
// Initialize CSS default rules
|
||||||
JsonArray* CSS_DEFAULT_RULES = Json.CreateArray(erythros_mem_task);
|
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;
|
Bool should_display = TRUE;
|
||||||
U8 node_classes_buffer[HTML_WORK_BUFFER_SIZE];
|
I64 i, j;
|
||||||
U8 prepend_buffer[64];
|
JsonArray* values = NULL;
|
||||||
U8 append_buffer[64];
|
JsonKey* font_key = NULL;
|
||||||
MemSet(prepend_buffer, 0, 64);
|
JsonKey* key = properties->keys;
|
||||||
MemSet(append_buffer, 0, 64);
|
|
||||||
U8 node_tmpnum_buf[16];
|
U8 node_tmpnum_buf[16];
|
||||||
U8** node_classes;
|
U8* match_font_family = NULL;
|
||||||
I64 node_classes_count = 0;
|
|
||||||
I64 color = TRANSPARENT;
|
|
||||||
U8 node_ptr_string[32];
|
|
||||||
U8* tmpmd5;
|
|
||||||
U8* match_font_family;
|
|
||||||
JsonKey* font_key;
|
|
||||||
|
|
||||||
if (!node)
|
for (i = 0; i < properties->length; i++) {
|
||||||
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++) {
|
|
||||||
values = properties->@(key->name);
|
values = properties->@(key->name);
|
||||||
|
|
||||||
if (!StrICmp(key->name, "display")) {
|
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")) {
|
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);
|
||||||
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:
|
css_continue_to_next_property:
|
||||||
key = key->next;
|
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