Completed
Push — dev-4.1-unstable ( 891c45...2ae7a5 )
by Felipe A.
01:18
created

HTMLCompressFeed   A

Complexity

Total Complexity 26

Size/Duplication

Total Lines 107
Duplicated Lines 0 %

Importance

Changes 2
Bugs 0 Features 1
Metric Value
c 2
b 0
f 1
dl 0
loc 107
rs 10
wmc 26

6 Methods

Rating   Name   Duplication   Size   Complexity  
C _minify() 0 15 10
A _next() 0 12 3
A finalize() 0 6 2
A __init__() 0 7 1
D feed() 0 30 8
A _process() 0 5 2
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('[ \\t\\r\\n]+')
10
    token_class = jinja2.lexer.Token
11
    block_tokens = {
12
        'variable_begin': 'variable_end',
13
        'block_begin': 'block_end'
14
        }
15
    block_tags = {
16
        'textarea': '</textarea>',
17
        'pre': '</pre>',
18
        'script': '</script>',
19
        'style': '</style>',
20
        }
21
    block_text = {
22
        '<![CDATA[': ']]>'
23
        }
24
    jumps = {
25
        'text': {'<': 'tag'},
26
        'lit1': {'"': 'tag'},
27
        "lit2": ("'", 'tag'),
28
        'tag': {
29
            '>': 'text',
30
            '"': 'lit1',
31
            "'": 'lit2'
32
            },
33
        }
34
35
    def __init__(self):
36
        self.start = ''  # character which started current stae
37
        self.current = 'text'  # current state
38
        self.pending = ''  # buffer of current state data
39
        self.lineno = 0  # current token lineno
40
        self.skip_until_token = None  # inside token until this is met
41
        self.skip_until_tag = None  # inside literal tag until this is met
42
43
    def finalize(self):
44
        if self.pending:
45
            data = self._minify(self.pending, self.current, self.start, True)
46
            yield self.token_class(self.lineno, 'data', data)
47
        self.start = ''
48
        self.pending = ''
49
50
    def _minify(self, data, current, start, partial=False):
51
        if current == 'tag':
52
            tagstart = start == '<'
53
            data = self.re_whitespace.sub(' ', data[1:] if tagstart else data)
54
            if tagstart:
55
                data = data.lstrip() if partial else data.strip()
56
                tagname = data.split(' ', 1)[0]
57
                self.skip_until_tag = self.block_tags.get(tagname)
58
                return '<' + data
59
            elif partial:
60
                return data.rstrip()
61
            return start if data.strip() == start else data
62
        elif current == 'text' and not self.skip_until_tag:
63
            return start if data.strip() == start else data
64
        return data
65
66
    def _next(self, data, current, start):
67
        endmark = None
68
        endnext = None
69
        endindex = len(data) + 1
70
        endindexstart = len(start)
71
        for mark, next in self.jumps[current].items():
72
            index = data.find(mark, endindexstart)
73
            if -1 < index < endindex:
74
                endmark = mark
75
                endnext = next
76
                endindex = index
77
        return endmark, endindex, endnext
78
79
    def _process(self, lineno, value, current, start):
80
        endmark, endindex, endnext = self._next(value, current, start)
81
        if endmark:
82
            s = self._minify(value[:endindex], current, start)
83
            yield s, value[endindex:], endnext, endmark
84
85
    def feed(self, token):
86
        if self.skip_until_token:
87
            yield token
88
            if token.type == self.skip_until_token:
89
                self.skip_until_token = None
90
            return
91
92
        if token.type in self.block_tokens:
93
            for data in self.finalize():
94
                yield data
95
            yield token
96
            self.skip_until_token = self.block_tokens[token.type]
97
            return
98
99
        size = len(token.value)
100
        start = self.start
101
        lno = self.lineno
102
        val = self.pending + token.value
103
        curr = self.current
104
        loop = val
105
        while loop:
106
            loop = None
107
            lno = self.lineno if len(val) > size else token.lineno
108
            for data, val, curr, start in self._process(lno, val, curr, start):
109
                yield self.token_class(lno, 'data', data)
110
                loop = val
111
        self.start = start
112
        self.lineno = lno
113
        self.pending = val
114
        self.current = curr
115
116
117
class HTMLCompress(jinja2.ext.Extension):
118
    def filter_stream(self, stream):
119
        feed = HTMLCompressFeed()
120
        for token in stream:
121
            for data in feed.feed(token):
122
                yield data
123
        for data in feed.finalize():
124
            yield data
125