Skip to content
This repository was archived by the owner on Jun 9, 2021. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
102 changes: 94 additions & 8 deletions students/eowyn/session07/html-rendering/html_render.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,12 @@ class Element():
tag = 'html'
extra_indent = 4 * ' '

def __init__(self, content=None):
def __init__(self, content=None, **kwargs):
if content is None:
self.content = []
else:
self.content = [content]
self.attributes = kwargs # a dictionary w/all the keyword args

def append(self, content):
# Collect additional objects alongside existing objects
Expand All @@ -18,15 +19,40 @@ def render(self, out_file, current_ind=""):
# Pretty-print content and tags to file.
# For objects, pretty-print all the content and tags
# they contain to the file. Nest tags with indentation.

out_file.write('\n' + current_ind + '<' + self.tag + '>')
self.render_open_tag(out_file, current_ind)
for each in self.content:
try:
each.render(out_file, current_ind + self.extra_indent)
except AttributeError:
out_file.write('\n' + current_ind +
self.extra_indent + str(each))
out_file.write('\n' + current_ind + '</' + self.tag + '>')
out_file.write('\n')
close_tag = '{}</{}>'.format(current_ind, self.tag)
out_file.write(close_tag)

def render_open_tag(self, out_file, current_ind):
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

great idea to pull this out to a separate method.

but I think it's a bit cleaner to have this method return the entire tag as a string, and then actually write it to the out_file in the render method. That keeps all the actual file writing in one place.

# This code was repeated so I refactored it!
# Get and write an open tag to out_file
open_tag = self.get_open_tag()
out_file.write('\n')
out_file.write(current_ind)
out_file.write(open_tag)

def get_open_tag(self):
# This code was repeated so I refactored it!
# Construct the open tag with its attributes, if present
open_tag = '<{}'.format(self.tag)
open_tag += self.get_tag_attributes()
open_tag += ">"
return open_tag

def get_tag_attributes(self):
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

very nice!

# This code was repeated so I refactored it!
# Construct string of any/all attributes
att_str = ""
for att, val in self.attributes.items():
att_str += ' {}="{}"'.format(att, val)
return att_str


class Body(Element):
Expand All @@ -45,7 +71,6 @@ def render(self, out_file, ind=""):
# For objects, pretty-print all the content and tags
# they contain to the file. Nest tags with indentation.
# For html files, include a top level DOCTYPE string.

out_file.write('<!DOCTYPE html>')
Element.render(self, out_file, ind)

Expand All @@ -59,12 +84,73 @@ class OneLineTag(Element):
def render(self, out_file, current_ind=""):
# Pretty-print content and tags to file on one line.
# Print the string representation of all objects within content.

out_file.write('\n' + current_ind + '<' + self.tag + '>')
self.render_open_tag(out_file, current_ind)
for each in self.content:
out_file.write(' ' + str(each) + ' ')
out_file.write('</' + self.tag + '>')
close_tag = '</{}>'.format(self.tag)
out_file.write(close_tag)


class Title(OneLineTag):
tag = 'title'


class SelfClosingTag(Element):
# render tags like <hr /> and <br />
# render just one tag and any attributes

def render(self, out_file, current_ind=""):
# Render just a tag and any attributes, ignore contents
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nice! but maybe you should raise an error if there are any contents -- probably in the init -- that would be better than simply ignoring it.

self.render_open_tag(out_file, current_ind)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

since all this doing is calling render_open_tag(), and that is overidden this this subclass, you may as well put the code in there right in here.

unless you think there are other subclasses that might use this get_open_tag method.


def get_open_tag(self):
# Override method to have /> at end of open_tag instead of >
open_tag = '<{}'.format(self.tag)
open_tag += self.get_tag_attributes()
open_tag += ' />'
return open_tag


class Hr(SelfClosingTag):
tag = 'hr'


class Br(SelfClosingTag):
tag = 'br'


class A(OneLineTag):
# Make anchor tags for hyperlinks, like this:
# <a href="http://google.com"> link to google </a>
# Instructions said subclass from Element but we want it on one line
tag = 'a'

def __init__(self, link=None, content=None, **kwargs):
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

perfect! remarkably easy, isn't it?

# Include href=link in the kwargs dictionary
Element.__init__(self, content, href=link, **kwargs)


class Ul(Element):
tag = 'ul'


class Li(Element):
tag = 'li'


class H(OneLineTag):

def __init__(self, hlevel, content=None, **kwargs):
# Include hlevel for different header levels eg <h2>
self.tag = 'h' + str(hlevel)
Element.__init__(self, content, **kwargs)


class Meta(SelfClosingTag):
# Render method of self closing tag already uses key:value pairs
# So we only need to update the tag!
tag = 'meta'




215 changes: 212 additions & 3 deletions students/eowyn/session07/html-rendering/test_html_render.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
import os
from html_render import Element, Body, P, Html, Head, OneLineTag, Title
from html_render import Hr, Br, A, Ul, Li, H, Meta
from io import StringIO


def render_element(el_object, filename='test1.html', remove=True):
def render_element_file(el_object, filename='test1.html', remove=True):
with open(filename, 'w') as out_file:
el_object.render(out_file)
with open(filename, 'r') as in_file:
Expand All @@ -12,6 +14,21 @@ def render_element(el_object, filename='test1.html', remove=True):
return contents


def test_new_element():
"""
not much here, but it shows Elements can be initialized
"""
el_object = Element()
el_object2 = Element('content')


def render_element(element, cur_ind=""):
sio = StringIO()
element.render(sio, cur_ind)
return sio.getvalue()



def test_add_content():
el_object = Element('content')
el_object = Element()
Expand Down Expand Up @@ -45,8 +62,8 @@ def test_render():
more_stuff = '\neggs, eggs, eggs'
el_object = Element(my_stuff)
el_object.append(more_stuff)
contents = render_element(el_object)
contents = contents.strip()
contents = render_element(el_object).strip()
print(contents)
assert contents.startswith('<html>')
assert contents.endswith('</html>')
assert my_stuff in contents
Expand Down Expand Up @@ -227,3 +244,195 @@ def test_title_indendation_twothings():
assert len(lines) == 1
assert lines[0].startswith('<title> ')
assert my_stuff in lines[0]


def test_single_attribute():
# <p style="text-align: center; font-style: oblique;">
# Here is a paragraph of text
# </p>
p = P("Here is a paragraph of text",
style="text-align: center; font-style: oblique;")
results = render_element(p).strip() # need this to be string IO I think
print(results)
assert results.startswith('<p style="text-align: center; font-style: oblique;">')


def test_multiple_attributes():
p = P("here is a paragraph of text",
id="fred", color="red", size="12px")
contents = render_element(p).strip()
print(contents)
lines = contents.split('\n')
assert lines[0].startswith('<p ')
assert lines[0].endswith('>')
assert 'id="fred"' in lines[0]
assert 'color="red"' in lines[0]
assert 'size="12px"' in lines[0]


def test_multiple_attributes_title():
p = Title("here is a paragraph of text",
id="fred", color="red", size="12px")
contents = render_element(p).strip()
print(contents)
lines = contents.split('\n')
assert lines[0].startswith('<title ')
assert lines[0].endswith('title>')
assert 'id="fred"' in lines[0]
assert 'color="red"' in lines[0]
assert 'size="12px"' in lines[0]


def test_class_attribute():
# Use a dictionary to get around class being a reserved word
atts = {"id": "fred", "class": "special", "size": "12px"}
p = P("here is a paragraph of text", **atts)
contents = render_element(p).strip()
print(contents)
lines = contents.split('\n')
assert lines[0].startswith('<p ')
assert lines[0].endswith('">')
assert 'id="fred"' in lines[0]
assert 'class="special"' in lines[0]
assert 'size="12px"' in lines[0]


def test_self_closing_tag():
atts = {"id": "fred", "class": "special", "size": "12px"}
p = Hr(**atts)
contents = render_element(p).strip()
lines = contents.split('\n')
print(contents)
assert lines[0].startswith('<hr')
assert lines[0].endswith("/>")
assert 'id="fred"' in lines[0]
assert 'class="special"' in lines[0]
assert 'size="12px"' in lines[0]
assert len(lines) == 1

def test_self_closing_tag_string():
atts = {"id": "fred", "class": "special", "size": "12px"}
p = Br("Now Leasing", **atts)
contents = render_element(p).strip()
lines = contents.split('\n')
print(contents)
assert lines[0].startswith('<br')
assert lines[0].endswith("/>")
assert 'id="fred"' in lines[0]
assert 'class="special"' in lines[0]
assert 'size="12px"' in lines[0]
assert len(lines) == 1
assert "Now Leasing" not in lines[0]


def test_anchor_element():
p = A("http://google.com", "link to google")
contents = render_element(p).strip()
print(contents)
assert contents.startswith('<a href="http:')
assert contents.endswith('</a>')
assert 'google.com' in contents
assert 'link to' in contents
assert contents.index('google.com') < contents.index('link to')
assert contents.index('link to') < contents.index('</a>')
lines = contents.split('\n')
assert len(lines)==1


def test_anchor_element_additional_atts():
atts = {"size": "12px"}
p = A("http://google.com", "link to google", **atts)
contents = render_element(p).strip()
print(contents)
assert contents.startswith('<a href="http:')
assert contents.endswith('</a>')
assert 'google.com' in contents
assert 'link to' in contents
assert contents.index('google.com') < contents.index('link to')
assert contents.index('link to') < contents.index('</a>')
lines = contents.split('\n')
assert len(lines) == 1
assert 'size="12px"' in lines[0]


def test_ul_element():
atts = {"size": "12px"}
list_name = "These are a few of my favorite things"
p = Ul(list_name, **atts)
contents = render_element(p).strip()
print(contents)
lines = contents.split('\n')
assert len(lines) == 3
assert lines[0].startswith('<ul size=')
assert lines[2].endswith('</ul>')
assert list_name in lines[1]
assert 'size="12px"' in lines[0]


def test_li_element():
list_name = "These are a few of my favorite things"
thing1 = "Roses on raindrops"
atts1 = {"size": "12px"}
thing2 = "Whiskers on kittens"
atts2 = {"size": "14px"}
p = Ul(list_name)
p.append(Li(thing1, **atts1))
p.append(Li(thing2, **atts2))
contents = render_element(p).strip()
lines = contents.split('\n')
print(contents)
assert len(lines) == 9
assert lines[0].startswith('<ul')
assert 'size="14px"' in lines[5]
assert lines[2].startswith(Element.extra_indent + '<li size=')
assert lines[3].startswith(2*Element.extra_indent + thing1)
assert lines[6].startswith(2*Element.extra_indent + thing2)


def test_header_element():
# <h2> The text of the header </h2>
# THIS TEST IS PASSING BUT THE RENDERED EXAMPLE LOOKS BAD
text = 'The text of the header'
p = H(2, text)
contents = render_element(p).strip()
lines = contents.split('\n')
print(contents)
assert len(lines) == 1
assert lines[0].startswith('<h2>')
assert lines[0].endswith('</h2>')
assert contents.index(text) < contents.index('</h2>')
assert contents.index(text) > contents.index('<h2>')


def test_meta_element_dict():
# <meta charset="UTF-8" />
atts = {"charset": "UTF-8"}
p = Meta(**atts)
contents = render_element(p).strip()
lines = contents.split('\n')
print(contents)
assert len(lines)==1
assert lines[0].startswith('<meta charset=')
assert lines[0].endswith('"UTF-8" />')


def test_meta_element():
# <meta charset="UTF-8" />
p = Meta(charset="UTF-8")
contents = render_element(p).strip()
lines = contents.split('\n')
print(contents)
assert len(lines)==1
assert lines[0].startswith('<meta charset=')
assert lines[0].endswith('"UTF-8" />')











Loading