HTMLCompress.filter_stream()   F
last analyzed

Complexity

Conditions 9

Size

Total Lines 27

Duplication

Lines 0
Ratio 0 %

Importance

Changes 3
Bugs 1 Features 0
Metric Value
c 3
b 1
f 0
dl 0
loc 27
rs 3
cc 9
1
import re
2
3
import jinja2
4
import jinja2.ext
5
import jinja2.lexer
6
7
from . import StateMachine
8
9
10
class SGMLCompressContext(StateMachine):
11
    re_whitespace = re.compile('[ \\t\\r\\n]+')
12
    block_tags = {}  # block content will be treated as literal text
13
    jumps = {  # state machine jumps
14
        'text': {
15
            '<': 'tag',
16
            '<!--': 'comment',
17
            '<![CDATA[': 'cdata',
18
            },
19
        'lit1': {'"': 'tag'},
20
        'lit2': ("'", 'tag'),
21
        'tag': {
22
            '>': 'text',
23
            '"': 'lit1',
24
            "'": 'lit2'
25
            },
26
        'comment': {'-->': 'text'},
27
        'cdata': {']]>': 'text'}
28
        }
29
    skip_until_text = None  # inside text until this is met
30
    current = 'text'
31
32
    @property
33
    def nearest(self):
34
        if self.skip_until_text and self.current == 'text':
35
            mark = self.skip_until_text
36
            index = self.pending.find(mark, len(self.start))
37
            if index == -1:
38
                return len(self.pending), '', None
39
            return index, mark, self.current
40
        return super(SGMLCompressContext, self).nearest
41
42
    def transform_tag(self, data, mark, next):
43
        tagstart = self.start == '<'
44
        data = self.re_whitespace.sub(' ', data[1:] if tagstart else data)
45
        if tagstart:
46
            data = data.lstrip() if next is None else data.strip()
47
            tagname = data.split(' ', 1)[0]
48
            self.skip_until_text = self.block_tags.get(tagname)
49
            return '<' + data
50
        elif next is None:
51
            return data.rstrip()
52
        return self.start if data.strip() == self.start else data
53
54
    def transform_text(self, data, mark, next):
55
        if not self.skip_until_text:
56
            return self.start if data.strip() == self.start else data
57
        elif next is not None:
58
            self.skip_until_text = None
59
        return data
60
61
62
class HTMLCompressContext(SGMLCompressContext):
63
    block_tags = {
64
        'textarea': '</textarea>',
65
        'pre': '</pre>',
66
        'script': '</script>',
67
        'style': '</style>',
68
        }
69
70
71
class HTMLCompress(jinja2.ext.Extension):
72
    context_class = HTMLCompressContext
73
    token_class = jinja2.lexer.Token
74
    block_tokens = {
75
        'variable_begin': 'variable_end',
76
        'block_begin': 'block_end'
77
        }
78
79
    def filter_stream(self, stream):
80
        transform = self.context_class()
81
        lineno = 0
82
        skip_until_token = None
83
        for token in stream:
84
            if skip_until_token:
85
                yield token
86
                if token.type == skip_until_token:
87
                    skip_until_token = None
88
                continue
89
90
            if token.type != 'data':
91
                for data in transform.finish():
92
                    yield self.token_class(lineno, 'data', data)
93
                yield token
94
                skip_until_token = self.block_tokens.get(token.type)
95
                continue
96
97
            if not transform.pending:
98
                lineno = token.lineno
99
100
            for data in transform.feed(token.value):
101
                yield self.token_class(lineno, 'data', data)
102
                lineno = token.lineno
103
104
        for data in transform.finish():
105
            yield self.token_class(lineno, 'data', data)
106