Package tdi :: Package tools :: Module javascript
[frames] | no frames]

Source Code for Module tdi.tools.javascript

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