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

Source Code for Module tdi.tools.javascript

  1  # -*- coding: ascii -*- 
  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      # pylint: disable = redefined-builtin 
 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   
41 -def _make_big_sub_b():
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
54 -def _make_big_sub():
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
67 -def _make_small_sub():
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
77 -def _make_escape_inlined():
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 # pylint: disable = redefined-outer-name 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 # don't decode ascii, but latin-1. just in case, if it's a 118 # dumb default. Doesn't hurt here, but avoids failures. 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
132 -def _make_escape_string():
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 # pylint: disable = redefined-outer-name 175 176 isuni = isinstance_(toescape, unicode_) 177 if isuni or encoding is not None: 178 if not isuni: 179 # don't decode ascii, but latin-1. just in case, if it's a 180 # dumb default. The result is similar to encoding = None. 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
210 -def _make_replace():
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 # pylint: disable = redefined-outer-name 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 # don't decode ascii, but latin-1. just in case, if it's a 311 # dumb default. Doesn't hurt here, but avoids failures. 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
492 -class LiteralJSON(object):
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
529 - def __repr__(self):
530 """ Debug representation """ 531 return "%s(%r, inlined=%r, encoding=%r)" % ( 532 self.__class__.__name__, 533 self._json, self._inlined, self._encoding 534 )
535
536 - def as_json(self, inlined=None):
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
566 - def __str__(self):
567 """ JSON as ``str`` (UTF-8 encoded) """ 568 return self.as_json().encode('utf-8')
569 570
571 -class SimpleJSON(object):
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
612 - def __repr__(self):
613 """ Debug representation """ 614 return "%s(%r, %r)" % ( 615 self.__class__.__name__, self._content, bool(self._inlined) 616 )
617
618 - def as_json(self, inlined=None):
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
654 - def __str__(self):
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 # pylint: disable = too-many-branches 684 685 isuni = isinstance(script, unicode) 686 if not isuni: 687 # don't decode ascii, but latin-1. just in case, if it's a 688 # dumb default. Doesn't hurt here, but avoids failures. 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 # don't decode ascii, but latin-1. just in case, if it's a 747 # dumb default. Doesn't hurt here, but avoids failures. 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 # don't decode ascii, but latin-1. just in case, if it's a 782 # dumb default. Doesn't hurt here, but avoids failures. 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
789 -class JSInlineFilter(_filters.BaseEventFilter):
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
846 - def handle_starttag(self, name, attr, closed, data):
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
862 - def handle_endtag(self, name, data):
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
913 -def MinifyFilter(builder, minifier=None, standalone=False):
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 # pylint: disable = invalid-name 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
934 -def CDATAFilter(builder, standalone=False):
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 # pylint: disable = invalid-name 947 948 return JSInlineFilter(builder, cdata, standalone=standalone)
949