Skip to content

Commit f216a5c

Browse files
authored
Merge pull request UWPCE-PythonCert#137 from Eowyn42/master
Completed html renderer
2 parents 4029c8d + 560c2a2 commit f216a5c

File tree

4 files changed

+578
-11
lines changed

4 files changed

+578
-11
lines changed

students/eowyn/session07/html-rendering/html_render.py

Lines changed: 94 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,12 @@ class Element():
44
tag = 'html'
55
extra_indent = 4 * ' '
66

7-
def __init__(self, content=None):
7+
def __init__(self, content=None, **kwargs):
88
if content is None:
99
self.content = []
1010
else:
1111
self.content = [content]
12+
self.attributes = kwargs # a dictionary w/all the keyword args
1213

1314
def append(self, content):
1415
# Collect additional objects alongside existing objects
@@ -18,15 +19,40 @@ def render(self, out_file, current_ind=""):
1819
# Pretty-print content and tags to file.
1920
# For objects, pretty-print all the content and tags
2021
# they contain to the file. Nest tags with indentation.
21-
22-
out_file.write('\n' + current_ind + '<' + self.tag + '>')
22+
self.render_open_tag(out_file, current_ind)
2323
for each in self.content:
2424
try:
2525
each.render(out_file, current_ind + self.extra_indent)
2626
except AttributeError:
2727
out_file.write('\n' + current_ind +
2828
self.extra_indent + str(each))
29-
out_file.write('\n' + current_ind + '</' + self.tag + '>')
29+
out_file.write('\n')
30+
close_tag = '{}</{}>'.format(current_ind, self.tag)
31+
out_file.write(close_tag)
32+
33+
def render_open_tag(self, out_file, current_ind):
34+
# This code was repeated so I refactored it!
35+
# Get and write an open tag to out_file
36+
open_tag = self.get_open_tag()
37+
out_file.write('\n')
38+
out_file.write(current_ind)
39+
out_file.write(open_tag)
40+
41+
def get_open_tag(self):
42+
# This code was repeated so I refactored it!
43+
# Construct the open tag with its attributes, if present
44+
open_tag = '<{}'.format(self.tag)
45+
open_tag += self.get_tag_attributes()
46+
open_tag += ">"
47+
return open_tag
48+
49+
def get_tag_attributes(self):
50+
# This code was repeated so I refactored it!
51+
# Construct string of any/all attributes
52+
att_str = ""
53+
for att, val in self.attributes.items():
54+
att_str += ' {}="{}"'.format(att, val)
55+
return att_str
3056

3157

3258
class Body(Element):
@@ -45,7 +71,6 @@ def render(self, out_file, ind=""):
4571
# For objects, pretty-print all the content and tags
4672
# they contain to the file. Nest tags with indentation.
4773
# For html files, include a top level DOCTYPE string.
48-
4974
out_file.write('<!DOCTYPE html>')
5075
Element.render(self, out_file, ind)
5176

@@ -59,12 +84,73 @@ class OneLineTag(Element):
5984
def render(self, out_file, current_ind=""):
6085
# Pretty-print content and tags to file on one line.
6186
# Print the string representation of all objects within content.
62-
63-
out_file.write('\n' + current_ind + '<' + self.tag + '>')
87+
self.render_open_tag(out_file, current_ind)
6488
for each in self.content:
6589
out_file.write(' ' + str(each) + ' ')
66-
out_file.write('</' + self.tag + '>')
90+
close_tag = '</{}>'.format(self.tag)
91+
out_file.write(close_tag)
6792

6893

6994
class Title(OneLineTag):
7095
tag = 'title'
96+
97+
98+
class SelfClosingTag(Element):
99+
# render tags like <hr /> and <br />
100+
# render just one tag and any attributes
101+
102+
def render(self, out_file, current_ind=""):
103+
# Render just a tag and any attributes, ignore contents
104+
self.render_open_tag(out_file, current_ind)
105+
106+
def get_open_tag(self):
107+
# Override method to have /> at end of open_tag instead of >
108+
open_tag = '<{}'.format(self.tag)
109+
open_tag += self.get_tag_attributes()
110+
open_tag += ' />'
111+
return open_tag
112+
113+
114+
class Hr(SelfClosingTag):
115+
tag = 'hr'
116+
117+
118+
class Br(SelfClosingTag):
119+
tag = 'br'
120+
121+
122+
class A(OneLineTag):
123+
# Make anchor tags for hyperlinks, like this:
124+
# <a href="http://google.com"> link to google </a>
125+
# Instructions said subclass from Element but we want it on one line
126+
tag = 'a'
127+
128+
def __init__(self, link=None, content=None, **kwargs):
129+
# Include href=link in the kwargs dictionary
130+
Element.__init__(self, content, href=link, **kwargs)
131+
132+
133+
class Ul(Element):
134+
tag = 'ul'
135+
136+
137+
class Li(Element):
138+
tag = 'li'
139+
140+
141+
class H(OneLineTag):
142+
143+
def __init__(self, hlevel, content=None, **kwargs):
144+
# Include hlevel for different header levels eg <h2>
145+
self.tag = 'h' + str(hlevel)
146+
Element.__init__(self, content, **kwargs)
147+
148+
149+
class Meta(SelfClosingTag):
150+
# Render method of self closing tag already uses key:value pairs
151+
# So we only need to update the tag!
152+
tag = 'meta'
153+
154+
155+
156+

students/eowyn/session07/html-rendering/test_html_render.py

Lines changed: 212 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
import os
22
from html_render import Element, Body, P, Html, Head, OneLineTag, Title
3+
from html_render import Hr, Br, A, Ul, Li, H, Meta
4+
from io import StringIO
35

46

5-
def render_element(el_object, filename='test1.html', remove=True):
7+
def render_element_file(el_object, filename='test1.html', remove=True):
68
with open(filename, 'w') as out_file:
79
el_object.render(out_file)
810
with open(filename, 'r') as in_file:
@@ -12,6 +14,21 @@ def render_element(el_object, filename='test1.html', remove=True):
1214
return contents
1315

1416

17+
def test_new_element():
18+
"""
19+
not much here, but it shows Elements can be initialized
20+
"""
21+
el_object = Element()
22+
el_object2 = Element('content')
23+
24+
25+
def render_element(element, cur_ind=""):
26+
sio = StringIO()
27+
element.render(sio, cur_ind)
28+
return sio.getvalue()
29+
30+
31+
1532
def test_add_content():
1633
el_object = Element('content')
1734
el_object = Element()
@@ -45,8 +62,8 @@ def test_render():
4562
more_stuff = '\neggs, eggs, eggs'
4663
el_object = Element(my_stuff)
4764
el_object.append(more_stuff)
48-
contents = render_element(el_object)
49-
contents = contents.strip()
65+
contents = render_element(el_object).strip()
66+
print(contents)
5067
assert contents.startswith('<html>')
5168
assert contents.endswith('</html>')
5269
assert my_stuff in contents
@@ -227,3 +244,195 @@ def test_title_indendation_twothings():
227244
assert len(lines) == 1
228245
assert lines[0].startswith('<title> ')
229246
assert my_stuff in lines[0]
247+
248+
249+
def test_single_attribute():
250+
# <p style="text-align: center; font-style: oblique;">
251+
# Here is a paragraph of text
252+
# </p>
253+
p = P("Here is a paragraph of text",
254+
style="text-align: center; font-style: oblique;")
255+
results = render_element(p).strip() # need this to be string IO I think
256+
print(results)
257+
assert results.startswith('<p style="text-align: center; font-style: oblique;">')
258+
259+
260+
def test_multiple_attributes():
261+
p = P("here is a paragraph of text",
262+
id="fred", color="red", size="12px")
263+
contents = render_element(p).strip()
264+
print(contents)
265+
lines = contents.split('\n')
266+
assert lines[0].startswith('<p ')
267+
assert lines[0].endswith('>')
268+
assert 'id="fred"' in lines[0]
269+
assert 'color="red"' in lines[0]
270+
assert 'size="12px"' in lines[0]
271+
272+
273+
def test_multiple_attributes_title():
274+
p = Title("here is a paragraph of text",
275+
id="fred", color="red", size="12px")
276+
contents = render_element(p).strip()
277+
print(contents)
278+
lines = contents.split('\n')
279+
assert lines[0].startswith('<title ')
280+
assert lines[0].endswith('title>')
281+
assert 'id="fred"' in lines[0]
282+
assert 'color="red"' in lines[0]
283+
assert 'size="12px"' in lines[0]
284+
285+
286+
def test_class_attribute():
287+
# Use a dictionary to get around class being a reserved word
288+
atts = {"id": "fred", "class": "special", "size": "12px"}
289+
p = P("here is a paragraph of text", **atts)
290+
contents = render_element(p).strip()
291+
print(contents)
292+
lines = contents.split('\n')
293+
assert lines[0].startswith('<p ')
294+
assert lines[0].endswith('">')
295+
assert 'id="fred"' in lines[0]
296+
assert 'class="special"' in lines[0]
297+
assert 'size="12px"' in lines[0]
298+
299+
300+
def test_self_closing_tag():
301+
atts = {"id": "fred", "class": "special", "size": "12px"}
302+
p = Hr(**atts)
303+
contents = render_element(p).strip()
304+
lines = contents.split('\n')
305+
print(contents)
306+
assert lines[0].startswith('<hr')
307+
assert lines[0].endswith("/>")
308+
assert 'id="fred"' in lines[0]
309+
assert 'class="special"' in lines[0]
310+
assert 'size="12px"' in lines[0]
311+
assert len(lines) == 1
312+
313+
def test_self_closing_tag_string():
314+
atts = {"id": "fred", "class": "special", "size": "12px"}
315+
p = Br("Now Leasing", **atts)
316+
contents = render_element(p).strip()
317+
lines = contents.split('\n')
318+
print(contents)
319+
assert lines[0].startswith('<br')
320+
assert lines[0].endswith("/>")
321+
assert 'id="fred"' in lines[0]
322+
assert 'class="special"' in lines[0]
323+
assert 'size="12px"' in lines[0]
324+
assert len(lines) == 1
325+
assert "Now Leasing" not in lines[0]
326+
327+
328+
def test_anchor_element():
329+
p = A("http://google.com", "link to google")
330+
contents = render_element(p).strip()
331+
print(contents)
332+
assert contents.startswith('<a href="http:')
333+
assert contents.endswith('</a>')
334+
assert 'google.com' in contents
335+
assert 'link to' in contents
336+
assert contents.index('google.com') < contents.index('link to')
337+
assert contents.index('link to') < contents.index('</a>')
338+
lines = contents.split('\n')
339+
assert len(lines)==1
340+
341+
342+
def test_anchor_element_additional_atts():
343+
atts = {"size": "12px"}
344+
p = A("http://google.com", "link to google", **atts)
345+
contents = render_element(p).strip()
346+
print(contents)
347+
assert contents.startswith('<a href="http:')
348+
assert contents.endswith('</a>')
349+
assert 'google.com' in contents
350+
assert 'link to' in contents
351+
assert contents.index('google.com') < contents.index('link to')
352+
assert contents.index('link to') < contents.index('</a>')
353+
lines = contents.split('\n')
354+
assert len(lines) == 1
355+
assert 'size="12px"' in lines[0]
356+
357+
358+
def test_ul_element():
359+
atts = {"size": "12px"}
360+
list_name = "These are a few of my favorite things"
361+
p = Ul(list_name, **atts)
362+
contents = render_element(p).strip()
363+
print(contents)
364+
lines = contents.split('\n')
365+
assert len(lines) == 3
366+
assert lines[0].startswith('<ul size=')
367+
assert lines[2].endswith('</ul>')
368+
assert list_name in lines[1]
369+
assert 'size="12px"' in lines[0]
370+
371+
372+
def test_li_element():
373+
list_name = "These are a few of my favorite things"
374+
thing1 = "Roses on raindrops"
375+
atts1 = {"size": "12px"}
376+
thing2 = "Whiskers on kittens"
377+
atts2 = {"size": "14px"}
378+
p = Ul(list_name)
379+
p.append(Li(thing1, **atts1))
380+
p.append(Li(thing2, **atts2))
381+
contents = render_element(p).strip()
382+
lines = contents.split('\n')
383+
print(contents)
384+
assert len(lines) == 9
385+
assert lines[0].startswith('<ul')
386+
assert 'size="14px"' in lines[5]
387+
assert lines[2].startswith(Element.extra_indent + '<li size=')
388+
assert lines[3].startswith(2*Element.extra_indent + thing1)
389+
assert lines[6].startswith(2*Element.extra_indent + thing2)
390+
391+
392+
def test_header_element():
393+
# <h2> The text of the header </h2>
394+
# THIS TEST IS PASSING BUT THE RENDERED EXAMPLE LOOKS BAD
395+
text = 'The text of the header'
396+
p = H(2, text)
397+
contents = render_element(p).strip()
398+
lines = contents.split('\n')
399+
print(contents)
400+
assert len(lines) == 1
401+
assert lines[0].startswith('<h2>')
402+
assert lines[0].endswith('</h2>')
403+
assert contents.index(text) < contents.index('</h2>')
404+
assert contents.index(text) > contents.index('<h2>')
405+
406+
407+
def test_meta_element_dict():
408+
# <meta charset="UTF-8" />
409+
atts = {"charset": "UTF-8"}
410+
p = Meta(**atts)
411+
contents = render_element(p).strip()
412+
lines = contents.split('\n')
413+
print(contents)
414+
assert len(lines)==1
415+
assert lines[0].startswith('<meta charset=')
416+
assert lines[0].endswith('"UTF-8" />')
417+
418+
419+
def test_meta_element():
420+
# <meta charset="UTF-8" />
421+
p = Meta(charset="UTF-8")
422+
contents = render_element(p).strip()
423+
lines = contents.split('\n')
424+
print(contents)
425+
assert len(lines)==1
426+
assert lines[0].startswith('<meta charset=')
427+
assert lines[0].endswith('"UTF-8" />')
428+
429+
430+
431+
432+
433+
434+
435+
436+
437+
438+

0 commit comments

Comments
 (0)