1
2 u"""
3 :Copyright:
4
5 Copyright 2006 - 2013
6 Andr\xe9 Malo or his licensors, as applicable
7
8 :License:
9
10 Licensed under the Apache License, Version 2.0 (the "License");
11 you may not use this file except in compliance with the License.
12 You may obtain a copy of the License at
13
14 http://www.apache.org/licenses/LICENSE-2.0
15
16 Unless required by applicable law or agreed to in writing, software
17 distributed under the License is distributed on an "AS IS" BASIS,
18 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
19 See the License for the specific language governing permissions and
20 limitations under the License.
21
22 ===========
23 CSS Tools
24 ===========
25
26 CSS Tools.
27 """
28 __author__ = u"Andr\xe9 Malo"
29 __docformat__ = "restructuredtext en"
30
31 from tdi import filters as _filters
32 from tdi.tools._util import norm_enc as _norm_enc
33
34
36 """
37 Cleanup a single CSS buffer
38
39 This methods removes CDATA and starting/finishing comment containers.
40
41 :Parameters:
42 `style` : ``basestring``
43 Buffer to cleanup
44
45 `encoding` : ``str``
46 Encoding in case that toescape is a ``str``. If omitted or
47 ``None``, no encoding is applied and `style` is expected to be
48 ASCII compatible.
49
50 :Return: The cleaned up buffer, typed as input
51 :Rtype: ``basestring``
52 """
53 isuni = isinstance(style, unicode)
54 if not isuni:
55
56
57 if encoding is None or _norm_enc(encoding) == 'ascii':
58 encoding = 'latin-1'
59 style = str(style).decode(encoding)
60 style = style.strip()
61 if style.startswith(u'<!--'):
62 style = style[4:]
63 if style.endswith(u'-->'):
64 style = style[:-3]
65 style = style.strip()
66 if style.startswith(u'/*'):
67 pos = style.find(u'*/')
68 if pos >= 0:
69 style = style[pos + 2:]
70 if style.endswith(u'*/'):
71 style = style[::-1]
72 pos = style.find(u'*/')
73 if pos >= 0:
74 style = style[pos + 2:]
75 style = style[::-1]
76 style = style.strip()
77 if style.startswith(u'<![CDATA['):
78 style = style[len(u'<![CDATA['):]
79 if style.endswith(u']]>'):
80 style = style[:-3]
81 style = style.strip()
82 if isuni:
83 return style
84 return style.encode(encoding)
85
86
87 -def cdata(style, encoding=None):
88 """
89 Add a failsafe CDATA container around a style
90
91 See <http://lists.w3.org/Archives/Public/www-html/2002Apr/0053.html>
92 for details.
93
94 :Parameters:
95 `style` : ``basestring``
96 JS to contain
97
98 `encoding` : ``str``
99 Encoding in case that toescape is a ``str``. If omitted or
100 ``None``, no encoding is applied and `style` is expected to be
101 ASCII compatible.
102
103 :Return: The contained style, typed as input
104 :Rtype: ``basestring``
105 """
106 isuni = isinstance(style, unicode)
107 if not isuni:
108
109
110 if encoding is None or _norm_enc(encoding) == 'ascii':
111 encoding = 'latin-1'
112 style = str(style).decode(encoding)
113 style = cleanup(style)
114 if style:
115 style = u'<!--/*--><![CDATA[/*><!--*/\n%s\n/*]]>*/-->' % style
116 if isuni:
117 return style
118 return style.encode(encoding)
119
120
121 -def minify(style, encoding=None):
122 """
123 Minify CSS (using `rcssmin`_)
124
125 .. _rcssmin: http://opensource.perlig.de/rcssmin/
126
127 :Parameters:
128 `style` : ``basestring``
129 CSS to minify
130
131 `encoding` : ``str``
132 Encoding in case that toescape is a ``str``. If omitted or
133 ``None``, no encoding is applied and `style` is expected to be
134 ASCII compatible.
135
136 :Return: Minified CSS, typed as input
137 :Rtype: ``basestring``
138 """
139 from tdi.tools import rcssmin as _rcssmin
140
141 isuni = isinstance(style, unicode)
142 if not isuni and encoding is not None:
143
144
145 if _norm_enc(encoding) == 'ascii':
146 encoding = 'latin-1'
147 return _rcssmin.cssmin(style.decode(encoding)).encode(encoding)
148 return _rcssmin.cssmin(style)
149
150
152 """
153 TDI filter for modifying inline css
154
155 :IVariables:
156 `_collecting` : ``bool``
157 Currently collecting CSS text?
158
159 `_buffer` : ``list``
160 Collection buffer
161
162 `_starttag` : ``tuple`` or ``None``
163 Original style starttag parameters
164
165 `_modify` : callable
166 Modifier function
167
168 `_attribute` : ``str``
169 ``tdi`` attribute name or ``None`` (if standalone)
170
171 `_strip` : ``bool``
172 Strip empty style elements?
173 """
174
175 - def __init__(self, builder, modifier, strip_empty=True, standalone=False):
176 """
177 Initialization
178
179 :Parameters:
180 `builder` : `tdi.interfaces.BuildingListenerInterface`
181 Builder
182
183 `modifier` : callable
184 Modifier function. Takes a style and returns the (possibly)
185 modified result.
186
187 `strip_empty` : ``bool``
188 Strip empty style elements?
189
190 `standalone` : ``bool``
191 Standalone context? In this case, we won't watch out for TDI
192 attributes.
193 """
194 super(CSSInlineFilter, self).__init__(builder)
195 self._collecting = False
196 self._buffer = []
197 self._starttag = None
198 self._modify = modifier
199 self._normalize = self.builder.decoder.normalize
200 if standalone:
201 self._attribute = None
202 else:
203 self._attribute = self._normalize(
204 self.builder.analyze.attribute
205 )
206 self._strip = strip_empty
207
209 """
210 Handle starttag
211
212 Style starttags are delayed until the endtag is found. The whole
213 element is then evaluated (and possibly thrown away).
214
215 :See: `tdi.interfaces.ListenerInterface`
216 """
217 if not closed and self._normalize(name) == 'style':
218 self._collecting = True
219 self._buffer = []
220 self._starttag = name, attr, closed, data
221 else:
222 self.builder.handle_starttag(name, attr, closed, data)
223
225 """
226 Handle endtag
227
228 When currently collecting, it must be a style endtag. The style
229 element content is then cleaned up (using `cleanup`) and then
230 modified (using the modifiy function passed during initialization).
231 The result replaces the original. If it's empty and the starttag
232 does not provide a ``tdi`` attribute and the filter was
233 configured to do so: the whole element is thrown away.
234
235 :See: `tdi.interfaces.ListenerInterface`
236 """
237 if self._collecting:
238 normalize = self._normalize
239 if normalize(name) != 'style':
240 raise AssertionError("Invalid event stream")
241
242 self._collecting = False
243 style, self._buffer = ''.join(self._buffer), []
244 style = self._modify(cleanup(style))
245
246 if not style and self._strip:
247 attrdict = dict((normalize(name), val)
248 for name, val in self._starttag[1]
249 )
250 if self._attribute is None or self._attribute not in attrdict:
251 return
252
253 self.builder.handle_starttag(*self._starttag)
254 self._starttag = None
255 self.builder.handle_text(style)
256
257 self.builder.handle_endtag(name, data)
258
259 - def handle_text(self, data):
260 """
261 Handle text
262
263 While collecting style text, the received data is buffered.
264 Otherwise the event is just passed through.
265
266 :See: `tdi.interfaces.ListenerInterface`
267 """
268 if not self._collecting:
269 return self.builder.handle_text(data)
270 self._buffer.append(data)
271
272
274 """
275 TDI Filter for minifying inline CSS
276
277 :Parameters:
278 `minifier` : callable``
279 Minifier function. If omitted or ``None``, the `builtin minifier`_ is
280 applied.
281
282 .. _builtin minifier: http://opensource.perlig.de/rcssmin/
283
284 `standalone` : ``bool``
285 Standalone context? In this case, we won't watch out for TDI
286 attributes.
287 """
288
289 if minifier is None:
290 minifier = minify
291 return CSSInlineFilter(builder, minify, standalone=standalone)
292
293
295 """
296 TDI filter for adding failsafe CDATA containers around CSS styles
297
298 See <http://lists.w3.org/Archives/Public/www-html/2002Apr/0053.html>
299 for details.
300
301 :Parameters:
302 `standalone` : ``bool``
303 Standalone context? In this case, we won't watch out for TDI
304 attributes.
305 """
306 return CSSInlineFilter(builder, cdata, standalone=standalone)
307