mirror of
https://github.com/csd4ni3l/browser.git
synced 2026-01-01 04:03:43 +01:00
Add backgrounds to finish layout tutorial, add inline and remote CSS support with a default of browser.css which currently supports fonts and backgrounds
This commit is contained in:
@@ -1,13 +1,5 @@
|
||||
SELF_CLOSING_TAGS = [
|
||||
"area", "base", "br", "col", "embed", "hr", "img", "input",
|
||||
"link", "meta", "param", "source", "track", "wbr",
|
||||
]
|
||||
|
||||
HEAD_TAGS = [
|
||||
"base", "basefont", "bgsound", "noscript",
|
||||
"link", "meta", "title", "style", "script",
|
||||
]
|
||||
|
||||
from utils.constants import SELF_CLOSING_TAGS, HEAD_TAGS, INHERITED_PROPERTIES
|
||||
import html.entities
|
||||
class Element:
|
||||
def __init__(self, tag, attributes, parent):
|
||||
self.tag = tag
|
||||
@@ -81,7 +73,6 @@ class HTML():
|
||||
|
||||
return tag, attributes
|
||||
|
||||
|
||||
def add_tag(self, tag):
|
||||
tag, attributes = self.get_attributes(tag)
|
||||
|
||||
@@ -150,4 +141,186 @@ class HTML():
|
||||
elif json_list[0] == "element":
|
||||
element = Element(json_list[1], json_list[2], parent)
|
||||
element.children = [HTML.from_json(child, element) for child in json_list[3]]
|
||||
return element
|
||||
return element
|
||||
|
||||
class TagSelector:
|
||||
def __init__(self, tag):
|
||||
self.tag = tag
|
||||
self.priority = 1
|
||||
|
||||
def matches(self, node):
|
||||
return isinstance(node, Element) and self.tag == node.tag
|
||||
|
||||
class DescendantSelector:
|
||||
def __init__(self, ancestor, descendant):
|
||||
self.ancestor = ancestor
|
||||
self.descendant = descendant
|
||||
self.priority = ancestor.priority + descendant.priority
|
||||
|
||||
def matches(self, node):
|
||||
if not self.descendant.matches(node): return False
|
||||
while node.parent:
|
||||
if self.ancestor.matches(node.parent): return True
|
||||
node = node.parent
|
||||
return False
|
||||
|
||||
def cascade_priority(rule):
|
||||
selector, body = rule
|
||||
return selector.priority
|
||||
|
||||
def get_inline_styles(node):
|
||||
all_rules = []
|
||||
|
||||
for node in node.children:
|
||||
if isinstance(node, Element) and node.tag == "style":
|
||||
all_rules.extend(CSSParser(node.children[0].text).parse()) # node's first children will just be a text element that contains the css
|
||||
|
||||
all_rules.extend(get_inline_styles(node))
|
||||
|
||||
return all_rules
|
||||
|
||||
class CSSParser:
|
||||
def __init__(self, s):
|
||||
self.s = s
|
||||
self.i = 0
|
||||
|
||||
def whitespace(self):
|
||||
while self.i < len(self.s) and self.s[self.i].isspace():
|
||||
self.i += 1
|
||||
|
||||
def literal(self, literal):
|
||||
if not (self.i < len(self.s) and self.s[self.i] == literal):
|
||||
raise Exception("Parsing error")
|
||||
self.i += 1
|
||||
|
||||
def word(self):
|
||||
start = self.i
|
||||
while self.i < len(self.s):
|
||||
if self.s[self.i].isalnum() or self.s[self.i] in "#-.%":
|
||||
self.i += 1
|
||||
else:
|
||||
break
|
||||
if not (self.i > start):
|
||||
raise Exception("Parsing error")
|
||||
return self.s[start:self.i]
|
||||
|
||||
def pair(self):
|
||||
prop = self.word()
|
||||
|
||||
self.whitespace()
|
||||
self.literal(":")
|
||||
self.whitespace()
|
||||
|
||||
val = self.word()
|
||||
|
||||
return prop.casefold(), val
|
||||
|
||||
def ignore_until(self, chars):
|
||||
while self.i < len(self.s):
|
||||
if self.s[self.i] in chars:
|
||||
return self.s[self.i]
|
||||
else:
|
||||
self.i += 1
|
||||
return None
|
||||
|
||||
def body(self):
|
||||
pairs = {}
|
||||
while self.i < len(self.s) and self.s[self.i] != "}":
|
||||
try:
|
||||
prop, val = self.pair()
|
||||
pairs[prop] = val
|
||||
|
||||
self.whitespace()
|
||||
|
||||
self.literal(";")
|
||||
|
||||
self.whitespace()
|
||||
except Exception:
|
||||
why = self.ignore_until([";", "}"])
|
||||
if why == ";":
|
||||
self.literal(";")
|
||||
self.whitespace()
|
||||
else:
|
||||
break
|
||||
|
||||
return pairs
|
||||
|
||||
def selector(self):
|
||||
out = TagSelector(self.word().casefold())
|
||||
self.whitespace()
|
||||
while self.i < len(self.s) and self.s[self.i] != "{":
|
||||
tag = self.word()
|
||||
descendant = TagSelector(tag.casefold())
|
||||
out = DescendantSelector(out, descendant)
|
||||
self.whitespace()
|
||||
return out
|
||||
|
||||
def parse(self):
|
||||
rules = []
|
||||
while self.i < len(self.s):
|
||||
try:
|
||||
self.whitespace()
|
||||
|
||||
selector = self.selector()
|
||||
|
||||
self.literal("{")
|
||||
|
||||
self.whitespace()
|
||||
|
||||
body = self.body()
|
||||
|
||||
self.literal("}")
|
||||
|
||||
rules.append((selector, body))
|
||||
except Exception:
|
||||
why = self.ignore_until(["}"])
|
||||
if why == "}":
|
||||
self.literal("}")
|
||||
self.whitespace()
|
||||
else:
|
||||
break
|
||||
return rules
|
||||
|
||||
def style(node, rules):
|
||||
node.style = {}
|
||||
|
||||
for property, default_value in INHERITED_PROPERTIES.items():
|
||||
if node.parent:
|
||||
node.style[property] = node.parent.style[property]
|
||||
else:
|
||||
node.style[property] = default_value
|
||||
|
||||
for selector, body in rules:
|
||||
if not selector.matches(node): continue
|
||||
for property, value in body.items():
|
||||
node.style[property] = value
|
||||
|
||||
if isinstance(node, Element) and "style" in node.attributes:
|
||||
pairs = CSSParser(node.attributes["style"]).body()
|
||||
for property, value in pairs.items():
|
||||
node.style[property] = value
|
||||
|
||||
if node.style["font-size"].endswith("%"):
|
||||
if node.parent:
|
||||
parent_font_size = node.parent.style["font-size"]
|
||||
else:
|
||||
parent_font_size = INHERITED_PROPERTIES["font-size"]
|
||||
|
||||
node_pct = float(node.style["font-size"][:-1]) / 100
|
||||
parent_px = float(parent_font_size[:-2])
|
||||
node.style["font-size"] = str(node_pct * parent_px) + "px"
|
||||
|
||||
for child in node.children:
|
||||
style(child, rules)
|
||||
|
||||
def tree_to_list(tree, list):
|
||||
list.append(tree)
|
||||
for child in tree.children:
|
||||
tree_to_list(child, list)
|
||||
return list
|
||||
|
||||
def replace_symbols(text):
|
||||
for key, value in html.entities.html5.items():
|
||||
text = text.replace(f"&{key};", value)
|
||||
|
||||
return text
|
||||
Reference in New Issue
Block a user