1
2 r"""
3 :Copyright:
4
5 Copyright 2006 - 2015
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 Javascript Tools
24 ==================
25
26 Javascript Tools.
27 """
28 if __doc__:
29
30 __doc__ = __doc__.encode('ascii').decode('unicode_escape')
31 __author__ = r"Andr\xe9 Malo".encode('ascii').decode('unicode_escape')
32 __docformat__ = "restructuredtext en"
33
34 import re as _re
35
36 from .. import filters as _filters
37 from .. import _htmldecode
38 from ._util import norm_enc as _norm_enc
39
40
42 """ Make bigsub """
43 sub = _re.compile(r'(?<!\\)((?:\\\\)*)\\U([0-9a-fA-F]{8})').sub
44 int_ = int
45 return lambda v: sub(lambda m: "%s\\u%04x\\u%04x" % (
46 m.group(1),
47 ((((int_(m.group(2), 16) - 0x10000) >> 10) & 0x3FF) + 0xD800),
48 ((int_(m.group(2), 16) & 0x3FF) + 0xDC00),
49 ), v)
50
51 _big_sub_b = _make_big_sub_b()
52
53
55 """ Make bigsub """
56 sub = _re.compile(ur'(?<!\\)((?:\\\\)*)\\U([0-9a-fA-F]{8})').sub
57 int_ = int
58 return lambda v: sub(lambda m: u"%s\\u%04x\\u%04x" % (
59 m.group(1),
60 ((((int_(m.group(2), 16) - 0x10000) >> 10) & 0x3FF) + 0xD800),
61 ((int_(m.group(2), 16) & 0x3FF) + 0xDC00),
62 ), v)
63
64 _big_sub = _make_big_sub()
65
66
68 """ Make small sub """
69 sub = _re.compile(ur'(?<!\\)((?:\\\\)*)\\x([0-9a-fA-F]{2})').sub
70 return lambda v: sub(lambda m: u"%s\\u00%s" % (
71 m.group(1), m.group(2)
72 ), v)
73
74 _small_sub = _make_small_sub()
75
76
78 """ Make escape_inlined """
79 dash_sub = _re.compile(ur'-(-+)').sub
80 dash_sub_b = _re.compile(r'-(-+)').sub
81 len_, str_, unicode_, isinstance_ = len, str, unicode, isinstance
82 norm_enc = _norm_enc
83
84 subber = lambda m: u'-' + u'\\-' * len_(m.group(1))
85 subber_b = lambda m: '-' + '\\-' * len_(m.group(1))
86
87 def escape_inlined(toescape, encoding=None):
88 """
89 Escape value for inlining
90
91 :Parameters:
92 `toescape` : ``basestring``
93 The value to escape
94
95 `encoding` : ``str``
96 Encoding in case that toescape is a ``str``. If omitted or
97 ``None``, no encoding is applied and `toescape` is expected to be
98 ASCII compatible.
99
100 :Return: The escaped value, typed as input
101 :Rtype: ``basestring``
102 """
103
104
105 if isinstance_(toescape, unicode_):
106 return (
107 dash_sub(subber, toescape)
108 .replace(u'</', u'<\\/')
109 .replace(u']]>', u']\\]>')
110 )
111 elif encoding is None:
112 return (
113 dash_sub_b(subber_b, str_(toescape))
114 .replace('</', '<\\/')
115 .replace(']]>', ']\\]>')
116 )
117
118
119 if norm_enc(encoding) == 'ascii':
120 encoding = 'latin-1'
121 return (
122 dash_sub(subber, str_(toescape).decode(encoding))
123 .replace(u'</', u'<\\/')
124 .replace(u']]>', u']\\]>')
125 ).encode(encoding)
126
127 return escape_inlined
128
129 escape_inlined = _make_escape_inlined()
130
131
133 """ Make escape_string function """
134 big_sub_b = _big_sub_b
135 unicode_, str_, isinstance_ = unicode, str, isinstance
136 escape_inlined_, norm_enc = escape_inlined, _norm_enc
137
138 need_solid = '\\'.encode('string_escape') == '\\'
139 need_solid_u = u'\\'.encode('unicode_escape') == '\\'
140 need_apos = "'".encode('string_escape') == "'"
141 need_apos_u = u"'".encode('unicode_escape') == "'"
142 need_quote = '"'.encode('string_escape') == '"'
143 need_quote_u = u'"'.encode('unicode_escape') == '"'
144
145 def escape_string(toescape, inlined=True, encoding=None):
146 """
147 Escape a string for JS output (to be inserted into a JS string)
148
149 This function is one of the building blocks of the
150 `tdi.tools.javascript.replace` function. You probably shouldn't
151 use it directly in your rendering code.
152
153 :See:
154 - `tdi.tools.javascript.fill`
155 - `tdi.tools.javascript.fill_attr`
156 - `tdi.tools.javascript.replace`
157
158 :Parameters:
159 `toescape` : ``basestring``
160 The string to escape
161
162 `inlined` : ``bool``
163 Do additional escapings (possibly needed for inlining the script
164 within a HTML page)?
165
166 `encoding` : ``str``
167 Encoding in case that toescape is a ``str``. If omitted or
168 ``None``, no encoding is applied and `toescape` is expected to be
169 ASCII compatible.
170
171 :Return: The escaped string (ascii)
172 :Rtype: ``str``
173 """
174
175
176 isuni = isinstance_(toescape, unicode_)
177 if isuni or encoding is not None:
178 if not isuni:
179
180
181 if norm_enc(encoding) == 'ascii':
182 encoding = 'latin-1'
183 toescape = str_(toescape).decode(encoding)
184 if need_solid_u:
185 toescape = toescape.replace(u'\\', u'\\\\')
186 result = big_sub_b(toescape.encode('unicode_escape'))
187 if need_apos_u:
188 result = result.replace("'", "\\'")
189 if need_quote_u:
190 result = result.replace('"', '\\"')
191 else:
192 result = str_(toescape)
193 if need_solid:
194 result = result.replace('\\', '\\\\')
195 result = result.encode('string_escape')
196 if need_apos:
197 result = result.replace("'", "\\'")
198 if need_quote:
199 result = result.replace('"', '\\"')
200
201 if inlined:
202 return escape_inlined_(result)
203 return result
204
205 return escape_string
206
207 escape_string = _make_escape_string()
208
209
211 """ Make replace function """
212 default_sub = _re.compile(ur'__(?P<name>[^_]*(?:_[^_]+)*)__').sub
213 escape_string_, getattr_, unicode_ = escape_string, getattr, unicode
214 isinstance_, escape_inlined_, str_ = isinstance, escape_inlined, str
215 big_sub, small_sub, norm_enc = _big_sub, _small_sub, _norm_enc
216
217 def replace(script, holders, pattern=None, as_json=True, inlined=True,
218 encoding=None):
219 """
220 Replace javascript values
221
222 See `fill` and `fill_attr` for more specific usage.
223
224 This functions provides safe (single pass) javascript value
225 replacement::
226
227 filled = javascript.replace(script_template, dict(
228 a=10,
229 b=u'Andr\\xe9',
230 c=javascript.SimpleJSON(dict(foo='bar')),
231 ))
232
233 Where script_template is something like::
234
235 // more script...
236 var count = __a__;
237 var name = '__b__';
238 var param = __c__;
239 // more script...
240
241 :See:
242 - `fill`
243 - `fill_attr`
244
245 :Parameters:
246 `script` : ``basestring``
247 Script content to modify
248
249 `holders` : ``dict``
250 Placeholders mappings (name -> value). If a placeholder is found
251 within the script which has no mapping, it's simply left as-is.
252 If `as_json` is true, the values are checked if they have an
253 ``as_json`` method. *If* they do have one, the method is called
254 and the result (of type ``unicode``) is used as replacement.
255 Otherwise the mapped value is piped through the `escape_string`
256 function and that result is used as replacement. ``as_json`` is
257 passed a boolean ``inlined`` parameter which indicates whether the
258 method should escape for inline usage or not.
259
260 Use the `LiteralJSON` class for passing any JSON content literally
261 to the script. There is also a `SimpleJSON` class for converting
262 complex structures to JSON using the simplejson converter. You may
263 pass your own classes as well, of course, as long as they provide
264 a proper ``as_json()`` method.
265
266 `pattern` : ``unicode`` or compiled ``re`` object
267 Placeholder name pattern. If omitted or ``None``, the pattern is
268 (simplified [#]_): ``__(?P<name>.+)__``, i.e. the placeholder name
269 enclosed in double-underscores. The name group is expected.
270
271 .. [#] The actual pattern is: ``__(?P<name>[^_]*(?:_[^_]+)*)__``
272
273 `as_json` : ``bool``
274 Check the placeholder values for an ``as_json`` method? See the
275 description of the `holders` parameter for details.
276
277 `inlined` : ``bool``
278 Escape simple content for being inlined (e.g.
279 no CDATA endmarkers, ``</script>``).
280
281 `encoding` : ``str``
282 Script encoding if `script` is a ``str``. If omitted or ``None``,
283 the script is expected to be ASCII compatible.
284
285 If ``script`` is of type ``unicode``, the encoding is applied to
286 ``as_json`` method return values. This is to make sure, the JSON
287 stuff is encoded safely. If omitted or ``None``, ASCII is assumed.
288 JSON result characters not fitting into the this encoding are
289 escaped (\\uxxxx).
290
291 :Return: The modified script, typed as input
292 :Rtype: ``basestring``
293 """
294
295
296 if not holders:
297 return script
298 isuni = isinstance_(script, unicode_)
299 if isuni:
300 if encoding is None:
301 json_encoding = 'ascii'
302 else:
303 json_encoding = encoding
304 else:
305 if encoding is None:
306 encoding = 'latin-1'
307 json_encoding = 'ascii'
308 else:
309 json_encoding = encoding
310
311
312 if norm_enc(encoding) == 'ascii':
313 encoding = 'latin-1'
314 script = str_(script).decode(encoding)
315 if pattern is None:
316 pattern = default_sub
317 else:
318 pattern = _re.compile(pattern).sub
319
320 def simple_subber(match):
321 """ Substitution function without checking .as_json() """
322 name = match.group(u'name')
323 if name and name in holders:
324 return escape_string_(
325 holders[name], encoding=encoding, inlined=inlined
326 ).decode('ascii')
327 return match.group(0)
328
329 def json_subber(match):
330 """ Substitution function with .as_json() checking """
331 name = match.group(u'name')
332 if name and name in holders:
333 value = holders[name]
334 method = getattr_(value, 'as_json', None)
335 if method is None:
336 return escape_string_(
337 value, encoding=encoding, inlined=inlined
338 ).decode('ascii')
339 value = small_sub(big_sub(
340 unicode_(method(inlined=False))
341 .encode(json_encoding, 'backslashreplace')
342 .decode(json_encoding)
343 ))
344 if inlined:
345 return escape_inlined_(value)
346 return value
347 return match.group(0)
348
349 script = pattern(as_json and json_subber or simple_subber, script)
350 if not isuni:
351 return script.encode(encoding)
352 return script
353
354 return replace
355
356 replace = _make_replace()
357
358
359 -def fill(node, holders, pattern=None, as_json=True):
360 """
361 Replace javascript values in a script node
362
363 This functions provides safe (single pass) javascript value
364 replacement (utilizing the `replace` function)::
365
366 javascript.fill(node, dict(
367 a=10,
368 b=u'Andr\\xe9',
369 c=javascript.SimpleJSON(dict(foo='bar')),
370 ))
371
372 Where `node` is something like::
373
374 <script tdi="name">
375 var count = __a__;
376 var name = '__b__';
377 var param = __c__;
378 </script>
379
380 :See:
381 - `fill_attr`
382 - `replace`
383
384 :Parameters:
385 `node` : TDI node
386 The script node
387
388 `holders` : ``dict``
389 Placeholders mappings (name -> value). If a placeholder is found
390 within the script which has no mapping, it's simply left as-is.
391 If `as_json` is true, the values are checked if they have an
392 ``as_json`` method. *If* they do have one, the method is called
393 and the result (of type ``unicode``) is used as replacement.
394 Otherwise the mapped value is piped through the `escape_string`
395 function and the result is used as replacement. ``as_json`` is
396 passed a boolean ``inlined`` parameter which indicates whether the
397 method should escape for inline usage or not.
398
399 Use the `LiteralJSON` class for passing any JSON content literally
400 to the script. There is also a `SimpleJSON` class for converting
401 complex structures to JSON using the simplejson converter. You may
402 pass your own classes as well, of course, as long as they provide
403 a proper ``as_json()`` method.
404
405 `pattern` : ``unicode`` or compiled ``re`` object
406 Placeholder name pattern. If omitted or ``None``, the pattern is
407 (simplified [#]_): ``__(?P<name>.+)__``, i.e. the placeholder name
408 enclosed in double-underscores. The name group is expected.
409
410 .. [#] The actual pattern is: ``__(?P<name>[^_]*(?:_[^_]+)*)__``
411
412 `as_json` : ``bool``
413 Check the placeholder values for an ``as_json`` method? See the
414 description of the `holders` parameter for details.
415 """
416 node.raw.content = replace(
417 node.raw.content,
418 holders,
419 pattern=pattern,
420 as_json=as_json,
421 inlined=True,
422 encoding=node.raw.encoder.encoding,
423 )
424
425
426 -def fill_attr(node, attr, holders, pattern=None, as_json=True):
427 """
428 Replace javascript values in a script attribute
429
430 This functions provides safe (single pass) javascript value
431 replacement (utilizing the `replace` function)::
432
433 javascript.fill_attr(node, u'onclick', dict(
434 a=10,
435 b=u'Andr\\xe9',
436 c=javascript.SimpleJSON(dict(foo='bar')),
437 ))
438
439 Where `node` is something like::
440
441 <div onclick="return foo(__a__)">...</div>
442
443 :See:
444 - `fill`
445 - `replace`
446
447 :Parameters:
448 `node` : TDI node
449 The script node
450
451 `attr` : ``basestring``
452 The name of the attribute
453
454 `holders` : ``dict``
455 Placeholders mappings (name -> value). If a placeholder is found
456 within the script which has no mapping, it's simply left as-is.
457 If `as_json` is true, the values are checked if they have an
458 ``as_json`` method. *If* they do have one, the method is called
459 and the result (of type ``unicode``) is used as replacement.
460 Otherwise the mapped value is piped through the `escape_string`
461 function and that result is used as replacement. ``as_json`` is
462 passed a boolean ``inlined`` parameter which indicates whether the
463 method should escape for inline usage or not.
464
465 Use the `LiteralJSON` class for passing any JSON content literally
466 to the script. There is also a `SimpleJSON` class for converting
467 complex structures to JSON using the simplejson converter. You may
468 pass your own classes as well, of course, as long as they provide
469 a proper ``as_json()`` method.
470
471 `pattern` : ``unicode`` or compiled ``re`` object
472 Placeholder name pattern. If omitted or ``None``, the pattern is
473 (simplified [#]_): ``__(?P<name>.+)__``, i.e. the placeholder name
474 enclosed in double-underscores. The name group is expected.
475
476 .. [#] The actual pattern is: ``__(?P<name>[^_]*(?:_[^_]+)*)__``
477
478 `as_json` : ``bool``
479 Check the placeholder values for an ``as_json`` method? See the
480 description of the `holders` parameter for details.
481 """
482 encoding = node.raw.encoder.encoding
483 node[attr] = replace(
484 _htmldecode.decode(node[attr], encoding=encoding), holders,
485 pattern=pattern,
486 as_json=as_json,
487 inlined=False,
488 encoding=encoding,
489 )
490
491
493 """
494 Literal JSON container for use with `replace` or `fill`
495
496 The container just passes its input back through as_json().
497
498 :IVariables:
499 `_json` : JSON input
500 JSON input
501
502 `_inlined` : ``bool``
503 Escape for inlining?
504
505 `_encoding` : ``str``
506 Encoding of `_json`
507 """
508
509 - def __init__(self, json, inlined=False, encoding=None):
510 """
511 Initialization
512
513 :Parameters:
514 `json` : ``basestring``
515 JSON to output
516
517 `inlined` : ``bool``
518 Escape for inlining? See `escape_inlined` for details.
519
520 `encoding` : ``str``
521 Encoding of `json`, in case it's a ``str``. If omitted or ``None``
522 and `json` is ``str``, `json` is expected to be UTF-8 encoded (or
523 ASCII only, which is compatible here)
524 """
525 self._json = json
526 self._inlined = bool(inlined)
527 self._encoding = encoding
528
530 """ Debug representation """
531 return "%s(%r, inlined=%r, encoding=%r)" % (
532 self.__class__.__name__,
533 self._json, self._inlined, self._encoding
534 )
535
537 """
538 Content as JSON
539
540 :Parameters:
541 `inlined` : ``bool`` or ``None``
542 escape for inlining? If omitted or ``None``, the default value
543 from construction is used.
544
545 :Return: JSON string
546 :Rtype: ``unicode``
547 """
548 json = self._json
549 if inlined is None:
550 inlined = self._inlined
551 if inlined:
552 json = escape_inlined(json, encoding=self._encoding)
553 if not isinstance(json, unicode):
554 encoding = self._encoding
555 if encoding is None:
556 encoding = 'utf-8'
557 json = json.decode(encoding)
558 return (
559 json
560 .replace(u'\u2028', u'\\u2028')
561 .replace(u'\u2029', u'\\u2029')
562 )
563
564 __unicode__ = as_json
565
567 """ JSON as ``str`` (UTF-8 encoded) """
568 return self.as_json().encode('utf-8')
569
570
572 """
573 JSON generator for use with `replace` or `fill`
574
575 This class uses simplejson for generating JSON output.
576
577 The encoder looks for either the ``json`` module or, if that fails, for
578 the ``simplejson`` module. If both fail, an ImportError is raised from the
579 `as_json` method.
580
581 :IVariables:
582 `_content` : any
583 Wrapped content
584
585 `_inlined` : ``bool``
586 Escape for inlining?
587
588 `_str_encoding` : ``str``
589 Str encoding
590 """
591
592 - def __init__(self, content, inlined=False, str_encoding='latin-1'):
593 """
594 Initialization
595
596 :Parameters:
597 `content` : any
598 Content to wrap for json conversion
599
600 `inlined` : ``bool``
601 Is it going to be inlined? Certain sequences are escaped then.
602
603 `str_encoding` : ``str``
604 Encoding to be applied on ``str`` content parts. Latin-1 is
605 a failsafe default here, because it always translates. It may be
606 wrong though.
607 """
608 self._content = content
609 self._inlined = bool(inlined)
610 self._str_encoding = str_encoding
611
613 """ Debug representation """
614 return "%s(%r, %r)" % (
615 self.__class__.__name__, self._content, bool(self._inlined)
616 )
617
619 """
620 Content as JSON
621
622 :Parameters:
623 `inlined` : ``bool`` or ``None``
624 escape for inlining? If omitted or ``None``, the default value
625 from construction is used.
626
627 :Return: The JSON encoded content
628 :Rtype: ``unicode``
629 """
630 try:
631 import json as _json
632 except ImportError:
633 import simplejson as _json
634 json = _json.dumps(
635 self._content,
636 separators=(',', ':'),
637 ensure_ascii=False,
638 encoding=self._str_encoding,
639 )
640 if isinstance(json, str):
641 json = json.decode(self._str_encoding)
642 if inlined is None:
643 inlined = self._inlined
644 if inlined:
645 json = escape_inlined(json)
646 return (
647 json
648 .replace(u'\u2028', u'\\u2028')
649 .replace(u'\u2029', u'\\u2029')
650 )
651
652 __unicode__ = as_json
653
655 """
656 JSON as ``str`` (UTF-8 encoded)
657
658 :Return: JSON string
659 :Rtype: ``str``
660 """
661 return self.as_json().encode('utf-8')
662
663
664 -def cleanup(script, encoding=None):
665 """
666 Cleanup single JS buffer
667
668 This method attempts to remove CDATA and starting/finishing comment
669 containers.
670
671 :Parameters:
672 `script` : ``basestring``
673 Buffer to cleanup
674
675 `encoding` : ``str``
676 Encoding in case that toescape is a ``str``. If omitted or
677 ``None``, no encoding is applied and `script` is expected to be
678 ASCII compatible.
679
680 :Return: The cleaned up buffer, typed as input
681 :Rtype: ``basestring``
682 """
683
684
685 isuni = isinstance(script, unicode)
686 if not isuni:
687
688
689 if encoding is None or _norm_enc(encoding) == 'ascii':
690 encoding = 'latin-1'
691 script = str(script).decode(encoding)
692 script = script.strip()
693 if script.startswith(u'<!--'):
694 script = script[4:]
695 if script.endswith(u'-->'):
696 script = script[:-3]
697 script = script.strip()
698 if script.startswith(u'//'):
699 pos = script.find(u'\n')
700 if pos >= 0:
701 script = script[pos + 1:]
702 script = script[::-1]
703 pos = script.find(u'\n')
704 if pos >= 0:
705 line = script[:pos].strip()
706 else:
707 line = script.strip()
708 pos = len(line)
709 if line.endswith(u'//'):
710 script = script[pos + 1:]
711 script = script[::-1].strip()
712 if script.startswith(u'<![CDATA['):
713 script = script[len(u'<![CDATA['):]
714 if script.endswith(u']]>'):
715 script = script[:-3]
716 script = script.strip()
717 if script.endswith(u'-->'):
718 script = script[:-3]
719 script = script.strip()
720 if isuni:
721 return script
722 return script.encode(encoding)
723
724
725 -def cdata(script, encoding=None):
726 """
727 Add a failsafe CDATA container around a script
728
729 See <http://lists.w3.org/Archives/Public/www-html/2002Apr/0053.html>
730 for details.
731
732 :Parameters:
733 `script` : ``basestring``
734 JS to contain
735
736 `encoding` : ``str``
737 Encoding in case that toescape is a ``str``. If omitted or
738 ``None``, no encoding is applied and `script` is expected to be
739 ASCII compatible.
740
741 :Return: The contained JS, typed as input
742 :Rtype: ``basestring``
743 """
744 isuni = isinstance(script, unicode)
745 if not isuni:
746
747
748 if encoding is None or _norm_enc(encoding) == 'ascii':
749 encoding = 'latin-1'
750 script = str(script).decode(encoding)
751 script = cleanup(script)
752 if script:
753 script = u'<!--//--><![CDATA[//><!--\n%s\n//--><!]]>' % script
754 if isuni:
755 return script
756 return script.encode(encoding)
757
758
759 -def minify(script, encoding=None):
760 """
761 Minify a script (using `rjsmin`_)
762
763 .. _rjsmin: http://opensource.perlig.de/rjsmin/
764
765 :Parameters:
766 `script` : ``basestring``
767 JS to minify
768
769 `encoding` : ``str``
770 Encoding in case that toescape is a ``str``. If omitted or
771 ``None``, no encoding is applied and `script` is expected to be
772 ASCII compatible.
773
774 :Return: The minified JS, typed as input
775 :Rtype: ``basestring``
776 """
777 from . import rjsmin as _rjsmin
778
779 isuni = isinstance(script, unicode)
780 if not isuni and encoding is not None:
781
782
783 if _norm_enc(encoding) == 'ascii':
784 encoding = 'latin-1'
785 return _rjsmin.jsmin(script.decode(encoding)).encode(encoding)
786 return _rjsmin.jsmin(script)
787
788
790 """
791 TDI filter for modifying inline javascript
792
793 :IVariables:
794 `_collecting` : ``bool``
795 Currently collecting javascript text?
796
797 `_buffer` : ``list``
798 Collection buffer
799
800 `_starttag` : ``tuple`` or ``None``
801 Original script starttag parameters
802
803 `_modify` : callable
804 Modifier function
805
806 `_attribute` : ``str``
807 ``tdi`` attribute name or ``None`` (if standalone)
808
809 `_strip` : ``bool``
810 Strip empty script elements?
811 """
812
813 - def __init__(self, builder, modifier, strip_empty=True, standalone=False):
814 """
815 Initialization
816
817 :Parameters:
818 `builder` : `tdi.interfaces.BuildingListenerInterface`
819 Builder
820
821 `modifier` : callable
822 Modifier function. Takes a script and returns the (possibly)
823 modified result.
824
825 `strip_empty` : ``bool``
826 Strip empty script elements?
827
828 `standalone` : ``bool``
829 Standalone context? In this case, we won't watch out for TDI
830 attributes.
831 """
832 super(JSInlineFilter, self).__init__(builder)
833 self._collecting = False
834 self._buffer = []
835 self._starttag = None
836 self._modify = modifier
837 self._normalize = self.builder.decoder.normalize
838 if standalone:
839 self._attribute = None
840 else:
841 self._attribute = self._normalize(
842 self.builder.analyze.attribute
843 )
844 self._strip = strip_empty
845
847 """
848 Handle starttag
849
850 Script starttags are delayed until the endtag is found. The whole
851 element is then evaluated (and possibly thrown away).
852
853 :See: `tdi.interfaces.ListenerInterface`
854 """
855 if not closed and self._normalize(name) == 'script':
856 self._collecting = True
857 self._buffer = []
858 self._starttag = name, attr, closed, data
859 else:
860 self.builder.handle_starttag(name, attr, closed, data)
861
863 """
864 Handle endtag
865
866 When currently collecting, it must be a script endtag. The script
867 element content is then modified (using the modifiy function passed
868 during initialization). The result replaces the original. If it's
869 empty and the starttag neither provides ``src`` nor ``tdi`` attributes
870 and the filter was configured to do so: the whole element is thrown
871 away.
872
873 :See: `tdi.interfaces.ListenerInterface`
874 """
875 normalize = self._normalize
876 if self._collecting:
877 if normalize(name) != 'script':
878 raise AssertionError("Invalid event stream")
879
880 self._collecting = False
881 script, self._buffer = ''.join(self._buffer), []
882 script = self._modify(script)
883
884 if not script and self._strip:
885 attrdict = dict((
886 normalize(name), val
887 ) for name, val in self._starttag[1])
888 if normalize('src') not in attrdict:
889 if self._attribute is None or \
890 self._attribute not in attrdict:
891 return
892
893 self.builder.handle_starttag(*self._starttag)
894 self._starttag = None
895 self.builder.handle_text(script)
896
897 self.builder.handle_endtag(name, data)
898
899 - def handle_text(self, data):
900 """
901 Handle text
902
903 While collecting javascript text, the received data is buffered.
904 Otherwise the event is just passed through.
905
906 :See: `tdi.interfaces.ListenerInterface`
907 """
908 if not self._collecting:
909 return self.builder.handle_text(data)
910 self._buffer.append(data)
911
912
914 """
915 TDI Filter for minifying inline javascript
916
917 :Parameters:
918 `minifier` : callable
919 Minifier function. If omitted or ``None``, the builtin minifier (see
920 `minify`) is applied.
921
922 `standalone` : ``bool``
923 Standalone context? In this case, we won't watch out for TDI
924 attributes.
925 """
926
927
928 if minifier is None:
929 minifier = minify
930 work = lambda x, m=minifier, c=cleanup: m(c(x))
931 return JSInlineFilter(builder, work, standalone=standalone)
932
933
935 """
936 TDI filter for adding failsafe CDATA containers around scripts
937
938 :Parameters:
939 `standalone` : ``bool``
940 Standalone context? In this case, we won't watch out for TDI
941 attributes.
942
943 See <http://lists.w3.org/Archives/Public/www-html/2002Apr/0053.html>
944 for details.
945 """
946
947
948 return JSInlineFilter(builder, cdata, standalone=standalone)
949