Completed
Push — dev-4.1-unstable ( a64629...cfcdea )
by Felipe A.
01:05
created

HTMLCompressFeed._process()   F

Complexity

Conditions 9

Size

Total Lines 25

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 9
c 0
b 0
f 0
dl 0
loc 25
rs 3
1
import re
2
3
import jinja2
4
import jinja2.ext
5
import jinja2.lexer
6
7
8
class HTMLCompressFeed(object):
9
    re_whitespace = re.compile('(\"[^\"\\n]*\"|\'[^\'\\n]*\')|([ \\t\\r\\n]+)')
10
    token_class = jinja2.lexer.Token
11
    block_tokens = {
12
        'variable_begin': 'variable_end',
13
        'block_begin': 'block_end'
14
        }
15
    ignore_elements = ['textarea', 'pre', 'script', 'style']
16
17
    def __init__(self):
18
        self.intag = False
19
        self.pending = ''
20
        self.lineno = 0
21
        self.skip_until = None
22
        self.ignore_until = None
23
24
    def finalize(self, strip=False):
25
        if self.ignore_until:
26
            data = self.pending
27
        elif self.intag:
28
            data = self._collapse(self.pending)
29
        else:
30
            data = self.pending.rstrip() if strip else self.pending
31
        if data.strip():
32
            yield self.token_class(self.lineno, 'data', data)
33
        self.pending = ''
34
35
    def _collapse(self, data):
36
        last = self.re_whitespace.groups
37
        return self.re_whitespace.sub(
38
            lambda m: ' ' if m.group(last) else m.group(0),
39
            data
40
            )
41
42
    def _process(self, value, lineno):
43
        if self.ignore_until:
44
            s, p, value = value.partition(self.ignore_until)
45
            if p:
46
                self.intag = False
47
                self.ignore_until = None
48
                s = s + p
49
            yield self.token_class(lineno, 'data', s), value
50
        elif self.intag:
51
            s, p, value = value.partition('>')
52
            s = self._collapse(s)
53
            if p:
54
                self.intag = False
55
                s = s.rstrip() + p
56
            yield self.token_class(lineno, 'data', s), value
57
        else:
58
            s, p, value = value.partition('<')
59
            if p:
60
                self.intag = True
61
                s = s + p if s.strip() else p
62
                yield self.token_class(lineno, 'data', s), value
63
                for elm in self.ignore_elements:
64
                    if value.startswith(elm):
65
                        self.ignore_until = '</{}>'.format(elm)
66
                        break
67
68
    def feed(self, token):
69
        if self.skip_until:
70
            yield token
71
            if token.type == self.skip_until:
72
                self.skip_until = None
73
            return
74
75
        if token.type in self.block_tokens:
76
            for data in self.finalize(token.type == 'block_begin'):
77
                yield data
78
            yield token
79
            self.skip_until = self.block_tokens[token.type]
80
            return
81
82
        lineno = self.lineno
83
        size = len(token.value)
84
        value = self.pending + token.value
85
        loop = True
86
        while loop:
87
            loop = False
88
            lineno = self.lineno if len(value) > size else token.lineno
89
            for data, value in self._process(value, lineno):
90
                loop = value
91
                yield data
92
        self.lineno = lineno
93
        self.pending = value
94
95
96
class HTMLCompress(jinja2.ext.Extension):
97
    def filter_stream(self, stream):
98
        feed = HTMLCompressFeed()
99
        for token in stream:
100
            for data in feed.feed(token):
101
                yield data
102
        for data in feed.finalize():
103
            yield data
104