import cgi import sys class HtmlGenerator(object): def __init__(self): self._stack = [] self._fragments = [] def text(self, text): if text: self._write_all() self._fragments.append(_escape_html(text)) def start(self, name, attributes=None, always_write=False): self._stack.append(_Element(name, attributes)) if always_write: self._write_all() def end(self): element = self._stack.pop() if element.written: self._fragments.append("".format(element.name)) def end_all(self): while self._stack: self.end() def self_closing(self, name, attributes=None): attribute_string = _generate_attribute_string(attributes) self._fragments.append("<{0}{1} />".format(name, attribute_string)) def _write_all(self): for element in self._stack: if not element.written: element.written = True self._write_element(element) def _write_element(self, element): attribute_string = _generate_attribute_string(element.attributes) self._fragments.append("<{0}{1}>".format(element.name, attribute_string)) def append(self, other): if other._fragments: self._write_all() for fragment in other._fragments: self._fragments.append(fragment) def html_string(self): return u("").join(self._fragments) class _Element(object): def __init__(self, name, attributes): if attributes is None: attributes = {} self.name = name self.attributes = attributes self.written = False def _escape_html(text): return cgi.escape(text, quote=True) def _generate_attribute_string(attributes): if attributes is None: return "" else: return "".join( ' {0}="{1}"'.format(key, _escape_html(value)) for key, value in _iteritems(attributes) ) def satisfy_html_path(generator, path): first_unsatisfied_index = _find_first_unsatisfied_index(generator, path) while len(generator._stack) > first_unsatisfied_index: generator.end() for element in path.elements[first_unsatisfied_index:]: attributes = {} if element.class_names: attributes["class"] = _generate_class_attribute(element) generator.start(element.names[0], attributes=attributes) def _find_first_unsatisfied_index(generator, path): for index, (generated_element, path_element) in enumerate(zip(generator._stack, path.elements)): if not _is_element_match(generated_element, path_element): return index return len(generator._stack) def _is_element_match(generated_element, path_element): return ( not path_element.fresh and generated_element.name in path_element.names and generated_element.attributes.get("class", "") == _generate_class_attribute(path_element) ) def _generate_class_attribute(path_element): return " ".join(path_element.class_names) if sys.version_info[0] == 2: _iteritems = lambda x: x.iteritems() u = lambda value: unicode(value, "unicode_escape") else: _iteritems = lambda x: x.items() u = lambda value: value