Completed
Push — dev-4.1-unstable ( 82b839...891c45 )
by Felipe A.
01:06
created

HTMLCompressFeed   A

Complexity

Total Complexity 26

Size/Duplication

Total Lines 109
Duplicated Lines 0 %

Importance

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

6 Methods

Rating   Name   Duplication   Size   Complexity  
F _minify() 0 25 10
A _next() 0 11 3
A finalize() 0 5 2
A __init__() 0 6 1
D feed() 0 25 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('(\"[^\"\\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
    block_tags = {
16
        'textarea': '</textarea>',
17
        'pre': '</textarea>',
18
        'script': '</script>',
19
        'style': '</script>',
20
        }
21
    block_text = {
22
        '<![CDATA[': ']]>'
23
        }
24
    jumps = {
25
        '': {'<': '<'},   # text
26
        '"': {'"': '<'},  # attr-value
27
        "'": ("'", '<'),  # attr-value
28
        '<': {            # tag
29
            '>': '',
30
            '"': '"',
31
            "'": "'"
32
            },
33
        }
34
35
    def __init__(self):
36
        self.current = ''
37
        self.pending = ''
38
        self.lineno = 0
39
        self.skip_until_token = None
40
        self.skip_until_substring = None
41
42
    def finalize(self, strip=False):
43
        if self.pending.strip():
44
            data = self._minify(self.pending, self.current, True)
45
            yield self.token_class(self.lineno, 'data', data)
46
        self.pending = ''
47
48
    def _minify(self, data, current, incomplete=False):
49
        if current == '<':
50
            for name, until in self.block_tags.items():
51
                if data.startswith(name):
52
                    self.skip_until_substring = until
53
                    break
54
            last = self.re_whitespace.groups
55
            return self.re_whitespace.sub(
56
                lambda m: ' ' if m.group(last) else m.group(0),
57
                data.rstrip()
58
                )
59
        elif current == '':
60
            prefix = ''
61
            if self.skip_until_substring:
62
                substring = self.skip_until_substring
63
                if substring in data:
64
                    self.skip_until_substring = None
65
                    index = data.index(substring) + len(substring)
66
                    prefix = data[:index]
67
                    data = data[index:]
68
                else:
69
                    prefix = data
70
                    data = ''
71
            return prefix + (data if data.strip() else '')
72
        return data
73
74
    def _next(self, data, current):
75
        endmark = None
76
        endnext = None
77
        endindex = len(data) + 1
78
        for mark, next in self.jumps[current].items():
79
            index = data.find(mark)
80
            if -1 < index < endindex:
81
                endmark = mark
82
                endnext = next
83
                endindex = index
84
        return endmark, endindex, endnext
85
86
    def _process(self, lineno, value, current):
87
        endmark, endindex, endnext = self._next(value, current)
88
        if endmark:
89
            s = self._minify(value[:endindex], current) + endmark
90
            yield s, value[endindex + len(endmark):], endnext
91
92
    def feed(self, token):
93
        if self.skip_until_token:
94
            yield token
95
            if token.type == self.skip_until_token:
96
                self.skip_until_token = None
97
        elif token.type in self.block_tokens:
98
            for data in self.finalize(token.type == 'block_begin'):
99
                yield data
100
            yield token
101
            self.skip_until_token = self.block_tokens[token.type]
102
        else:
103
            size = len(token.value)
104
            lno = self.lineno
105
            value = self.pending + token.value
106
            current = self.current
107
            loop = True
108
            while loop:
109
                loop = False
110
                lno = self.lineno if len(value) > size else token.lineno
111
                for data, value, current in self._process(lno, value, current):
112
                    loop = value
113
                    self.token_class(lno, 'data', data)
114
            self.lineno = lno
115
            self.pending = value
116
            self.current = current
117
118
119
class HTMLCompress(jinja2.ext.Extension):
120
    def filter_stream(self, stream):
121
        feed = HTMLCompressFeed()
122
        for token in stream:
123
            for data in feed.feed(token):
124
                yield data
125
        for data in feed.finalize():
126
            yield data
127