-
Notifications
You must be signed in to change notification settings - Fork 146
Expand file tree
/
Copy pathhtml_generation.py
More file actions
133 lines (100 loc) · 3.81 KB
/
html_generation.py
File metadata and controls
133 lines (100 loc) · 3.81 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
from __future__ import unicode_literals
import cgi
from .html_paths import HtmlPath, HtmlPathElement
#:: str -> str
def _escape_html(text):
return cgi.escape(text, quote=True)
#:: dict[str, str] | none -> str
def _generate_attribute_string(attributes):
if attributes is None:
#:: dict[str, str]
attributes = {}
return "".join(
' {0}="{1}"'.format(key, _escape_html(value))
for key, value in attributes.items()
)
class _Element(object):
#:: Self, str, dict[str, str] | none -> none
def __init__(self, name, attributes):
if attributes is None:
#:: dict[str, str]
attributes = {}
self.name = name
self.attributes = attributes
self.written = False
class HtmlGenerator(object):
#:: Self -> none
def __init__(self):
#:: list[_Element]
self._stack = []
#:: list[str]
self._fragments = []
#:: Self, str -> none
def text(self, text):
if text:
self._write_all()
self._fragments.append(_escape_html(text))
#:: Self, str, ?attributes: dict[str, str], ?always_write: bool -> none
def start(self, name, attributes=None, always_write=None):
self._stack.append(_Element(name, attributes))
if always_write:
self._write_all()
#:: Self -> none
def end(self):
element = self._stack.pop()
if element.written:
self._fragments.append("</{0}>".format(element.name))
#:: Self -> none
def end_all(self):
while self._stack:
self.end()
#:: Self, str, dict[str, str] -> none
def self_closing(self, name, attributes=None):
attribute_string = _generate_attribute_string(attributes)
self._fragments.append("<{0}{1} />".format(name, attribute_string))
#:: Self -> none
def _write_all(self):
for element in self._stack:
if not element.written:
element.written = True
self._write_element(element)
#:: Self, _Element -> none
def _write_element(self, element):
attribute_string = _generate_attribute_string(element.attributes)
self._fragments.append("<{0}{1}>".format(element.name, attribute_string))
#:: Self, Self -> none
def append(self, other):
if other._fragments:
self._write_all()
for fragment in other._fragments:
self._fragments.append(fragment)
#:: Self -> str
def html_string(self):
return "".join(self._fragments)
#:: HtmlPathElement -> str
def _generate_class_attribute(path_element):
return " ".join(path_element.class_names)
#:: _Element, HtmlPathElement -> bool
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)
)
#:: HtmlGenerator, HtmlPath -> int
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)
#:: HtmlGenerator, HtmlPath -> none
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:]:
#:: dict[str, str]
attributes = {}
if element.class_names:
attributes["class"] = _generate_class_attribute(element)
generator.start(element.names[0], attributes=attributes)