I am writing an HTML generator. The class Element("elemente name") generates elements. It has a self.html() method that returns the complete element as a string:
import json
def dict2json(dictionary):
return json.dumps(dictionary, sort_keys=False, indent=4, separators=(',', ': '))
class Element():
def __init__(self, name, attrs={}):
self.__name = name
self.ending_tag = True
self.__attrs = attrs
self.required_attrs = []
self.children = []
@property
def name(self):
return self.__name
@property
def attrs(self):
return dict2json(self.__attrs)
def attr(self, a):
key, val = a.split("=")
self.__attrs[key] = val
def append(self, element):
"Take another Element object and add it as a child to self.children."
self.children.append(element)
def html(self):
"Returns a string of the HTML element."
if self.children: # generate the HTML string for the children
nodes = "".join([e.html() for e in self.children])
else:
nodes = ""
if self.__attrs:
attributes = " " + " ".join(['{}="{}"'.format(k, v) for k, v in self.__attrs.items()])
else:
attributes = ""
if self.ending_tag:
return "<{}{}>{}</{}>".format(self.__name, attributes, nodes, self.__name)
else:
return "<{}{}>".format(self.__name, attributes)
The problem is, when I modify an element's attributes with self.attr(), all child nodes inherit the same value I set for the parent element:
if __name__ == '__main__':
div = Element("div")
div.attr("class=printable") # should affect div only, and not its children
div.append(Element("p")) # div > p
div.children[0].append(Element("b")) # div > p > b
print(div.children[0].children[0].html()) # this returns <b class="printable"></b>
print(div.html()) # this returns <div class="printable"><p class="printable"><b class="printable"></b></p></div>
When I use print(div.html())
, I am actually expecting it to return <div class="printable"><p><b></b></p></div>
, and not <div class="printable"><p class="printable"><b class="printable"></b></p></div>
. Why is that happening?
Also, I am not sure this code will properly handle deeper levels of element ancestors when generating the HTML string for, say, html > body > div > ul > li... etc. I would appreciate tips on this!
EDIT:
Thanks Asocia for pointing out the other post. So basically, default arguments are part of the function object, and the common solution is to use None as the default and then initialize in the function body, creating a new property for each instance:
class Element():
def __init__(self, name, attrs=None): # don't initialize a dict here
self.__name = name
self.ending_tag = True
self.__attrs = attrs
self.required_attrs = []
self.children = []
def attr(self, a):
if not self.__attrs:
self.__attrs = {} # do it here
key, val = a.split("=")
self.__attrs[key] = val