Skip to content

Commit 9bb89ea

Browse files
Support passing a dict of CSS to style tag.
You can now use the full power of Python to compose CSS. For details see the docstrings and the newly added renderCSS function for details.
1 parent c914650 commit 9bb89ea

File tree

1 file changed

+37
-5
lines changed

1 file changed

+37
-5
lines changed

htmltree.py

Lines changed: 37 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@ class Element:
2020
values must be (string | list of strings | dict of styles)
2121
content : (None | string | list of (strings and/or elements)
2222
elements must have a 'render' method that returns valid html.
23+
<style> tag gets special handling. You may pass the css as a dict of
24+
the form {'selector': {'property':'value', ...}, ...}
2325
2426
Instance methods:
2527
render()
@@ -49,13 +51,21 @@ class Element:
4951
>>> doc.render()
5052
'<html><head></head><body style="background-color:black;"><h1>Title</h1><br/></body></html>'
5153
54+
>>> style = E('style', None, {'p.myclass': {'margin': '4px', 'font-color': 'blue'}})
55+
>>> style.render()
56+
'<style>p.myclass { margin:4px; font-color:blue; }</style>'
5257
"""
5358
def __init__(self, tagname, attrs, content):
5459
## Validate arguments
5560
assert isinstance(tagname, str)
61+
if tagname == '!--':
62+
raise ValueError("Sorry, can't handle html comments yet.")
5663
assert isinstance(attrs, (dict, type(None)))
57-
assert isinstance(content, (list, str, type(None))) ## None means an 'empty' element with no closing tag
58-
self.T = tagname
64+
if tagname.lower() == 'style':
65+
assert isinstance(content, (str, dict))
66+
else:
67+
assert isinstance(content, (list, str, type(None))) ## None means an 'empty' element with no closing tag
68+
self.T = tagname.lower()
5969
self.A = attrs
6070
self.C = content
6171

@@ -69,11 +79,13 @@ def render(self):
6979
a = a.replace('_', '-') ## replace underscores with hyphens
7080
if isinstance(v, str):
7181
rlist.append(' {}="{}"'.format(a,v))
82+
elif v is None:
83+
rlist.append(' {}'.format(a)) # bare attribute, e.g. 'disabled'
7284
elif isinstance(v,list):
7385
_ = ' '.join(v) ## must be list of strings
7486
rlist.append(' {}="{}"'.format(a, _))
7587
elif isinstance(v,dict) and a=='style':
76-
rlist.append(' {}="{}"'.format(a,renderstyle(v)))
88+
rlist.append(' {}="{}"'.format(a,renderInlineStyle(v)))
7789
else:
7890
msg="Don't know what to with {}={}".format(a,v)
7991
raise ValueError(msg)
@@ -90,6 +102,8 @@ def render(self):
90102
## render the content
91103
if isinstance(self.C, str):
92104
rlist.append(self.C)
105+
elif self.T == "style":
106+
rlist.append(renderCss(self.C))
93107
else:
94108
for c in self.C:
95109
if isinstance(c, str):
@@ -107,22 +121,40 @@ def render(self):
107121

108122
return ''.join(rlist)
109123

110-
def renderstyle(d):
124+
def renderInlineStyle(d):
111125
"""If d is a dict of styles, return a proper style string """
112126
if isinstance(d, dict):
113127
style=[]
114128
for k,v in d.items():
115129
## for convenience, convert underscores in keys to hyphens
116130
kh = k.replace('_', '-')
117131
style.append("{}:{};".format(kh, v))
118-
result = ' '.join(style)
132+
separator = ' '
133+
result = separator.join(style)
119134
elif isinstance(d, str):
120135
result = d
121136
else:
122137
msg = "Cannot convert {} to style string".format(d)
123138
raise TypeError(msg)
124139
return result
125140

141+
def renderCss(d, newlines=True):
142+
""" If d is a dict of rulesets, render a string of CSS rulesets """
143+
if not isinstance(d, dict):
144+
msg = "Expected dictionary of CSS rulesets, got {}.".format(d)
145+
raise TypeError(msg)
146+
else:
147+
rulesetlist = []
148+
for selector, declaration in d.items():
149+
#print("In renderCss with selector = {}".format(selector))
150+
if not isinstance(selector, str):
151+
msg = "Expected selector string, got {}".format(selector)
152+
raise TypeError(msg)
153+
154+
ruleset = [selector, '{', renderInlineStyle(declaration), '}']
155+
rulesetlist.append(" ".join(ruleset))
156+
separator = '\n' if newlines else ' '
157+
return separator.join(rulesetlist)
126158

127159
## The 'skip' pragma tells the Transcrypt Python to JS transpiler to
128160
## ignore a section of code. It's needed here because the 'run as script'

0 commit comments

Comments
 (0)