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
|
from ..lists import flat_map
from .nodes import TextNode, Element, SelfClosingElement, ForceWrite, NodeVisitor
def text(value):
return TextNode(value)
def element(tag_name, attributes=None, children=None):
return _element(tag_name, attributes, children, collapsible=False)
def collapsible_element(tag_name, attributes=None, children=None):
return _element(tag_name, attributes, children, collapsible=True)
def _element(tag_names, attributes, children, collapsible):
if not isinstance(tag_names, list):
tag_names = [tag_names]
if attributes is None:
attributes = {}
if children is None:
children = []
return Element(tag_names, attributes, children, collapsible=collapsible)
def self_closing_element(tag_name, attributes=None):
if attributes is None:
attributes = {}
return SelfClosingElement(tag_name, attributes)
force_write = ForceWrite()
def strip_empty(nodes):
return flat_map(_strip_empty_node, nodes)
def _strip_empty_node(node):
return StripEmpty().visit(node)
class StripEmpty(NodeVisitor):
def visit_text_node(self, node):
if node.value:
return [node]
else:
return []
def visit_element(self, element):
children = strip_empty(element.children)
if len(children) == 0:
return []
else:
return [Element(
element.tag_names,
element.attributes,
children,
collapsible=element.collapsible)]
def visit_self_closing_element(self, element):
return [element]
def visit_force_write(self, node):
return [node]
def collapse(nodes):
collapsed = []
for node in nodes:
_collapsing_add(collapsed, node)
return collapsed
class _CollapseNode(NodeVisitor):
def visit_text_node(self, node):
return node
def visit_element(self, element):
return Element(
element.tag_names,
element.attributes,
collapse(element.children),
collapsible=element.collapsible)
def visit_self_closing_element(self, element):
return element
def visit_force_write(self, node):
return node
_collapse_node = _CollapseNode().visit
def _collapsing_add(collapsed, node):
collapsed_node = _collapse_node(node)
if not _try_collapse(collapsed, collapsed_node):
collapsed.append(collapsed_node)
def _try_collapse(collapsed, node):
if not collapsed:
return False
last = collapsed[-1]
if not isinstance(last, Element) or not isinstance(node, Element):
return False
if not node.collapsible:
return False
if not _is_match(last, node):
return False
for child in node.children:
_collapsing_add(last.children, child)
return True
def _is_match(first, second):
return first.tag_name in second.tag_names and first.attributes == second.attributes
|