this is a green box
93 |this is a blue box
97 |this is paragraph
100 |├── .gitignore ├── README ├── demo ├── css │ ├── stylesheet1.css │ └── stylesheet2.css ├── js │ └── test.js ├── single-file │ └── view-with-inline-styles.html └── views │ ├── view1.html │ └── view2.html ├── munch ├── muncher ├── __init__.py ├── config.py ├── muncher.py ├── sizetracker.py ├── util.py └── varfactory.py └── setup.py /.gitignore: -------------------------------------------------------------------------------- 1 | *.pyc 2 | *.opt.html 3 | *.opt.js 4 | *.opt.css 5 | demo/css_opt 6 | demo/js_opt 7 | demo/views_opt 8 | -------------------------------------------------------------------------------- /README: -------------------------------------------------------------------------------- 1 | -------------- 2 | ABOUT 3 | -------------- 4 | 5 | HTML Muncher is a Python utility that rewrites CSS, HTML, and JavaScript files in order to save precious bytes and obfuscate your code 6 | 7 | if your stylesheet starts out looking like this: 8 | 9 | .file2 #special { 10 | font-size: 1.5em; 11 | color: #F737FF; 12 | } 13 | 14 | .file2 #special2 { 15 | letter-spacing: 0; 16 | } 17 | 18 | .box { 19 | border: 2px solid #aaa; 20 | -webkit-border-radius: 5px; 21 | background: #eee; 22 | padding: 5px; 23 | } 24 | 25 | it will be rewritten as 26 | 27 | .a #a { 28 | font-size: 1.5em; 29 | color: #F737FF; 30 | } 31 | 32 | .a #b { 33 | letter-spacing: 0; 34 | } 35 | 36 | .b { 37 | border: 2px solid #aaa; 38 | -webkit-border-radius: 5px; 39 | background: #eee; 40 | padding: 5px; 41 | } 42 | 43 | 44 | -------------- 45 | INSTALLATION 46 | -------------- 47 | 48 | easy_install http://htmlmuncher.com/htmlmuncher.egg 49 | 50 | OR: 51 | 52 | download the source from http://github.com/ccampbell/html-muncher 53 | cd html-muncher 54 | python setup.py install 55 | 56 | 57 | -------------- 58 | USAGE 59 | -------------- 60 | http://htmlmuncher.com/#usage 61 | 62 | OR: 63 | 64 | munch --help 65 | 66 | 67 | -------------- 68 | EXAMPLES 69 | -------------- 70 | 71 | to update a bunch of stylesheets and views: 72 | munch --css demo/css --html demo/views 73 | 74 | to update a single file with inline styles/javascript: 75 | munch --html demo/single-file/view-with-inline-styles.html 76 | 77 | you can also select specific files: 78 | munch --css file1.css,file2.css --html view1.html,view2.html 79 | 80 | or you can mix and match files and directories 81 | munch --css /my/css/directory,global.css --html /view/directory1,/view/directory2,/view/directory3,template.html 82 | -------------------------------------------------------------------------------- /demo/css/stylesheet1.css: -------------------------------------------------------------------------------- 1 | body { 2 | font-family: helvetica; 3 | } 4 | 5 | h1 { 6 | text-align: center; 7 | font-size: 2em; 8 | } 9 | 10 | .red { 11 | color: red; 12 | } 13 | 14 | .blue { 15 | color: blue; 16 | } 17 | 18 | .green { 19 | color: green; 20 | } 21 | 22 | .box.purple { 23 | color: purple; 24 | } 25 | 26 | .underline { 27 | text-decoration: underline; 28 | } 29 | 30 | #special { 31 | color: orange; 32 | font-style: italic; 33 | } 34 | 35 | .italic { 36 | font-style: italic; 37 | } 38 | 39 | #special2 { 40 | letter-spacing: .5em; 41 | color: grey; 42 | } 43 | 44 | #new_id, #special, #special2 { 45 | font-size: 1em; 46 | } -------------------------------------------------------------------------------- /demo/css/stylesheet2.css: -------------------------------------------------------------------------------- 1 | .file2 #special { 2 | font-size: 1.5em; 3 | color: #F737FF; 4 | } 5 | 6 | .file2 #special2 { 7 | letter-spacing: 0; 8 | } 9 | 10 | .box { 11 | border: 2px solid #aaa; 12 | -webkit-border-radius: 5px; 13 | background: #eee; 14 | padding: 5px; 15 | } -------------------------------------------------------------------------------- /demo/js/test.js: -------------------------------------------------------------------------------- 1 | $ = { 2 | qs: function(query) { 3 | return document.querySelector(query); 4 | } 5 | }; 6 | 7 | window.onload = function() 8 | { 9 | $.qs("#special").innerHTML = "new text for this paragraph"; 10 | document.getElementById("special").innerHTML = "change it again"; 11 | var italic = document.getElementsByClassName('italic'); 12 | 13 | // mootools 14 | var test = $('test'); 15 | if (test.hasClass("dont_know")) { 16 | test.removeClass("dont_know"); 17 | test.addClass('now_i_know'); 18 | } 19 | 20 | var class_thing = $('class_thing'); 21 | class_thing.addClass(test, "whatever"); 22 | class_thing.removeClass(test, "whatever"); 23 | $.qs(".dont_know", class_thing).value; 24 | var cool = $.qs("#one_id.class_thing", test); 25 | var another_weird_thing = $.qs(".class1.class2 #another_id"); 26 | $.qs(".selector1 > .selector2 .selector3"); 27 | var test = document.querySelector(".selector1"); 28 | } 29 | -------------------------------------------------------------------------------- /demo/single-file/view-with-inline-styles.html: -------------------------------------------------------------------------------- 1 | 2 |
3 |this is a green box
93 |this is a blue box
97 |this is paragraph
100 |Assertively leverage existing scalable growth strategies with revolutionary value. Distinctively recaptiualize top-line models rather than leveraged e-commerce. Quickly engineer orthogonal e-markets via holistic human capital.
9 |this is a test with two classes
10 |Appropriately incubate collaborative imperatives after team building networks. Uniquely enhance sticky e-services for value-added solutions. Seamlessly parallel task value-added quality vectors before state of the art methods of empowerment.
11 |Interactively strategize plug-and-play platforms whereas efficient infrastructures. Synergistically negotiate user-centric metrics rather than worldwide quality vectors. Holisticly deliver bleeding-edge leadership vis-a-vis world-class architectures.
12 |Professionally iterate multifunctional systems before optimal materials. Efficiently reintermediate wireless total linkage with distributed portals. Globally exploit resource-leveling human capital without economically sound infomediaries.
13 |Intrinsicly streamline user-centric users before visionary scenarios. Dynamically repurpose seamless channels via multifunctional process improvements. Professionally synthesize exceptional infrastructures without premier vortals.
14 | 15 | 16 | -------------------------------------------------------------------------------- /demo/views/view2.html: -------------------------------------------------------------------------------- 1 | 2 | 3 |Assertively leverage existing scalable growth strategies with revolutionary value. Distinctively recaptiualize top-line models rather than leveraged e-commerce. Quickly engineer orthogonal e-markets via holistic human capital.
10 |Appropriately incubate collaborative imperatives after team building networks. Uniquely enhance sticky e-services for value-added solutions. Seamlessly parallel task value-added quality vectors before state of the art methods of empowerment.
11 |Professionally iterate multifunctional systems before optimal materials. Efficiently reintermediate wireless total linkage with distributed portals. Globally exploit resource-leveling human capital without economically sound infomediaries.
12 |Intrinsicly streamline user-centric users before visionary scenarios. Dynamically repurpose seamless channels via multifunctional process improvements. Professionally synthesize exceptional infrastructures without premier vortals.
13 | 14 | 15 | -------------------------------------------------------------------------------- /munch: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | # Copyright 2011 Craig Campbell 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | 17 | import sys 18 | from muncher.config import Config 19 | from muncher.muncher import Muncher 20 | 21 | config = Config() 22 | config.processArgs() 23 | muncher = Muncher(config) 24 | muncher.run() 25 | -------------------------------------------------------------------------------- /muncher/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ccampbell/html-muncher/86c425b0a3db2409f3de11ed282da78aeb94ec6c/muncher/__init__.py -------------------------------------------------------------------------------- /muncher/config.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # Copyright 2011 Craig Campbell 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | 16 | import sys, getopt 17 | from muncher import Muncher 18 | 19 | class Config(object): 20 | """configuration object for handling all config options for html-muncher""" 21 | def __init__(self): 22 | """config object constructor 23 | 24 | Returns: 25 | void 26 | 27 | """ 28 | self.css = [] 29 | self.views = [] 30 | self.js = [] 31 | self.ignore = [] 32 | self.class_selectors = ["getElementsByClassName", "hasClass", "addClass", "removeClass"] 33 | self.id_selectors = ["getElementById"] 34 | self.custom_selectors = ["document.querySelector"] 35 | self.framework = None 36 | self.view_extension = "html" 37 | self.js_manifest = None 38 | self.show_savings = False 39 | self.compress_html = False 40 | self.rewrite_constants = False 41 | self.verbose = False 42 | 43 | def getArgCount(self): 44 | """gets the count of how many arguments are present 45 | 46 | Returns: 47 | int 48 | 49 | """ 50 | return len(sys.argv) 51 | 52 | def setIgnore(self, value): 53 | """sets what classes and ids we should ignore and not shorten 54 | 55 | Arguments: 56 | value -- comma separated list of classes or ids 57 | 58 | Returns: 59 | void 60 | 61 | """ 62 | for name in value.split(","): 63 | self.ignore.append(name) 64 | 65 | def setCustomSelectors(self, value): 66 | for value in value.split(","): 67 | self.custom_selectors.append(value.lstrip(".")) 68 | 69 | def addClassSelectors(self, value): 70 | for value in value.split(","): 71 | self.class_selectors.append(value) 72 | 73 | def addIdSelectors(self, value): 74 | for value in value.split(","): 75 | self.id_selectors.append(value) 76 | 77 | def setCssFiles(self, value): 78 | for value in value.split(","): 79 | self.css.append(value.rstrip("/")) 80 | 81 | def setViewFiles(self, value): 82 | for value in value.split(","): 83 | self.views.append(value.rstrip("/")) 84 | 85 | def setJsFiles(self, value): 86 | for value in value.split(","): 87 | self.js.append(value.rstrip("/")) 88 | 89 | def setFramework(self, name): 90 | self.framework = name.lower() 91 | if self.framework == "jquery": 92 | self.custom_selectors.append("$") 93 | self.custom_selectors.append("jQuery") 94 | elif self.framework == "mootools": 95 | self.id_selectors.append("$") 96 | self.custom_selectors.append("getElement") 97 | 98 | def processArgs(self): 99 | """processes arguments passed in via command line and sets config settings accordingly 100 | 101 | Returns: 102 | void 103 | 104 | """ 105 | try: 106 | opts, args = getopt.getopt(sys.argv[1:], "", ["css=", "views=", "html=", "js=", "help", "view-ext=", "ignore=", "framework=", "selectors=", "class-selectors=", "id-selectors=", "compress-html", "show-savings", "verbose", "js-manifest=", "rewrite-constants"]) 107 | except: 108 | Muncher.showUsage() 109 | 110 | views_set = False 111 | 112 | for key, value in opts: 113 | if key == "--help": 114 | Muncher.showUsage() 115 | elif key == "--css": 116 | self.setCssFiles(value) 117 | elif key == "--views" or key == "--html": 118 | views_set = True 119 | self.setViewFiles(value) 120 | elif key == "--js": 121 | self.setJsFiles(value) 122 | elif key == "--ignore": 123 | self.setIgnore(value) 124 | elif key == "--view-ext": 125 | self.view_extension = value 126 | elif key == "--framework": 127 | self.setFramework(value) 128 | elif key == "--selectors": 129 | self.setCustomSelectors(value) 130 | elif key == "--class-selectors": 131 | self.addClassSelectors(value) 132 | elif key == "--id-selectors": 133 | self.addIdSelectors(value) 134 | elif key == "--compress-html": 135 | self.compress_html = True 136 | elif key == "--show-savings": 137 | self.show_savings = True 138 | elif key == "--verbose": 139 | self.verbose = True 140 | elif key == "--js-manifest": 141 | self.js_manifest = value 142 | elif key == "--rewrite-constants": 143 | self.rewrite_constants = True 144 | 145 | # you have to at least have a view 146 | if views_set is False: 147 | Muncher.showUsage() 148 | -------------------------------------------------------------------------------- /muncher/muncher.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # Copyright 2011 Craig Campbell 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | 16 | import sys, re, glob, os 17 | from operator import itemgetter 18 | from util import Util 19 | from varfactory import VarFactory 20 | from sizetracker import SizeTracker 21 | 22 | class Muncher(object): 23 | def __init__(self, config): 24 | """constructor 25 | 26 | Returns: 27 | void 28 | 29 | """ 30 | self.id_counter = {} 31 | self.class_counter = {} 32 | self.id_map = {} 33 | self.class_map = {} 34 | self.config = config 35 | 36 | @staticmethod 37 | def showUsage(): 38 | """shows usage information for this script""" 39 | print "\n---------------------------------" 40 | print " html-muncher" 41 | print "---------------------------------" 42 | 43 | print "\n" + '\033[91m' + "USAGE:" + '\033[0m' 44 | print "munch --css file1.css,/path/to/css1,file2.css,file3.css --html /path/to/views1,file1.html,/path/to/views2/,file3.html --js main.js,/path/to/js" 45 | print "\n" + '\033[91m' + "REQUIRED ARGUMENTS:" + '\033[0m' 46 | print "--html {path/to/views} html files to rewrite (comma separated list of directories and files)" 47 | print "\n" + '\033[91m' + "OPTIONAL ARGUMENTS:" + '\033[0m' 48 | print "--css {path/to/css} css files to rewrite (comma separated list of directories and files)" 49 | print "" 50 | print "--js {path/to/js} js files to rewrite (comma separated list of directories and files)" 51 | print "" 52 | print "--view-ext {extension} sets the extension to look for in the view directory (defaults to html)" 53 | print "" 54 | print "--ignore {classes,ids} comma separated list of classes or ids to ignore when rewriting css (ie .sick_class,#sweet_id)" 55 | print "" 56 | print "--compress-html strips new line characters to compress html files specified with --html" 57 | print " be careful when using this becuase it has not been thoroughly tested" 58 | print "" 59 | print "--framework name of js framework to use for selectors (currently only jquery or mootools)" 60 | print "" 61 | print "--selectors comma separated custom selectors using css selectors" 62 | print " for example if you have $.qs(\"#test .div\") this param would be qs" 63 | print "" 64 | print "--id-selectors comma separated id selectors with strings" 65 | print " for example if you are using .addId(\"test\") this param would be addId" 66 | print "" 67 | print "--class-selectors comma separated class selectors with strings" 68 | print " for example if you have selectClass(\"my_class\") this param would be selectClass" 69 | print "" 70 | print "--js-manifest path to a js file containing class name/id constants" 71 | print "" 72 | print "--rewrite-constants when using a manifest file this will take any constants with values as strings" 73 | print " and rewrite the values to be numbers" 74 | print "" 75 | print "--show-savings will output how many bytes were saved by munching" 76 | print "" 77 | print "--verbose output more information while the script runs" 78 | print "" 79 | print "--help shows this menu\n" 80 | sys.exit(2) 81 | 82 | def run(self): 83 | """runs the optimizer and does all the magic 84 | 85 | Returns: 86 | void 87 | 88 | """ 89 | self.output("searching for classes and ids...", False) 90 | 91 | if self.config.js_manifest is not None: 92 | self.outputJsWarnings() 93 | 94 | self.processCss() 95 | self.processViews() 96 | 97 | if self.config.js_manifest is None: 98 | self.processJs() 99 | else: 100 | self.processJsManifest() 101 | 102 | self.output("mapping classes and ids to new names...", False) 103 | # maps all classes and ids found to shorter names 104 | self.processMaps() 105 | 106 | # optimize everything 107 | self.output("munching css files...", False) 108 | self.optimizeFiles(self.config.css, self.optimizeCss) 109 | 110 | self.output("munching html files...", False) 111 | self.optimizeFiles(self.config.views, self.optimizeHtml, self.config.view_extension, self.config.compress_html) 112 | 113 | self.output("munching js files...", False) 114 | 115 | if self.config.js_manifest is None: 116 | self.optimizeFiles(self.config.js, self.optimizeJavascript) 117 | else: 118 | self.optimizeJsManifest() 119 | 120 | self.output("done", False) 121 | 122 | if self.config.show_savings: 123 | self.output(SizeTracker.savings(), False) 124 | 125 | def outputJsWarnings(self): 126 | pass 127 | 128 | def output(self, text, verbose_only = True): 129 | """outputs text during the script run 130 | 131 | Arguments: 132 | text -- string of text to output 133 | verbose_only -- should we only show this in verbose mode? 134 | 135 | Returns: 136 | void 137 | 138 | """ 139 | if verbose_only and not self.config.verbose: 140 | return 141 | 142 | print text 143 | 144 | def processCssDirectory(self, file): 145 | """processes a directory of css files 146 | 147 | Arguments: 148 | file -- path to directory 149 | 150 | Returns: 151 | void 152 | 153 | """ 154 | if ".svn" in file: 155 | return 156 | 157 | for dir_file in Util.getFilesFromDir(file): 158 | if Util.isDir(dir_file): 159 | self.processCssDirectory(dir_file) 160 | continue 161 | 162 | self.processCssFile(dir_file) 163 | 164 | def processCss(self): 165 | """gets all css files from config and processes them to see what to replace 166 | 167 | Returns: 168 | void 169 | 170 | """ 171 | files = self.config.css 172 | for file in files: 173 | if not Util.isDir(file): 174 | self.processCssFile(file) 175 | continue 176 | self.processCssDirectory(file) 177 | 178 | def processViewDirectory(self, file): 179 | """processes a directory of view files 180 | 181 | Arguments: 182 | file -- path to directory 183 | 184 | Returns: 185 | void 186 | 187 | """ 188 | if ".svn" in file: 189 | return 190 | 191 | for dir_file in Util.getFilesFromDir(file): 192 | if Util.isDir(dir_file): 193 | self.processViewDirectory(dir_file) 194 | continue 195 | 196 | self.processView(dir_file) 197 | 198 | def processViews(self): 199 | """processes all view files 200 | 201 | Returns: 202 | void 203 | 204 | """ 205 | files = self.config.views 206 | for file in files: 207 | if not Util.isDir(file): 208 | self.processView(file) 209 | continue 210 | self.processViewDirectory(file) 211 | 212 | def processJsDirectory(self, file): 213 | """processes a directory of js files 214 | 215 | Arguments: 216 | file -- path to directory 217 | 218 | Returns: 219 | void 220 | 221 | """ 222 | if ".svn" in file: 223 | return 224 | 225 | for dir_file in Util.getFilesFromDir(file): 226 | if Util.isDir(dir_file): 227 | self.processJsDirectory(dir_file) 228 | continue 229 | self.processJsFile(dir_file) 230 | 231 | def processJs(self): 232 | """gets all js files from config and processes them to see what to replace 233 | 234 | Returns: 235 | void 236 | 237 | """ 238 | files = self.config.js 239 | for file in files: 240 | if not Util.isDir(file): 241 | self.processJsFile(file) 242 | continue 243 | self.processJsDirectory(file) 244 | 245 | def processView(self, file): 246 | """processes a single view file 247 | 248 | Arguments: 249 | file -- path to directory 250 | 251 | """ 252 | self.processCssFile(file, True) 253 | self.processJsFile(file, True) 254 | 255 | def processCssFile(self, path, inline = False): 256 | """processes a single css file to find all classes and ids to replace 257 | 258 | Arguments: 259 | path -- path to css file to process 260 | 261 | Returns: 262 | void 263 | 264 | """ 265 | contents = Util.fileGetContents(path) 266 | if inline is True: 267 | blocks = self.getCssBlocks(contents) 268 | contents = "" 269 | for block in blocks: 270 | contents = contents + block 271 | 272 | ids_found = re.findall(r'((?)', '', content, re.MULTILINE) 642 | return content 643 | 644 | def optimizeCss(self, path): 645 | """replaces classes and ids with new values in a css file 646 | 647 | Arguments: 648 | path -- string path to css file to optimize 649 | 650 | Returns: 651 | string 652 | 653 | """ 654 | css = Util.fileGetContents(path) 655 | return self.replaceCss(css) 656 | 657 | def optimizeHtml(self, path): 658 | """replaces classes and ids with new values in an html file 659 | 660 | Uses: 661 | Muncher.replaceHtml 662 | 663 | Arguments: 664 | path -- string path to file to optimize 665 | 666 | Returns: 667 | string 668 | 669 | """ 670 | html = Util.fileGetContents(path) 671 | html = self.replaceHtml(html) 672 | html = self.optimizeCssBlocks(html) 673 | html = self.optimizeJavascriptBlocks(html) 674 | 675 | return html 676 | 677 | def replaceHtml(self, html): 678 | """replaces classes and ids with new values in an html file 679 | 680 | Arguments: 681 | html -- contents to replace 682 | 683 | Returns: 684 | string 685 | 686 | """ 687 | html = self.replaceHtmlIds(html) 688 | html = self.replaceHtmlClasses(html) 689 | return html 690 | 691 | def replaceHtmlIds(self, html): 692 | """replaces any instances of ids in html markup 693 | 694 | Arguments: 695 | html -- contents of file to replaces ids in 696 | 697 | Returns: 698 | string 699 | 700 | """ 701 | for key, value in self.id_map.items(): 702 | key = key[1:] 703 | value = value[1:] 704 | html = html.replace("id=\"" + key + "\"", "id=\"" + value + "\"") 705 | 706 | return html 707 | 708 | def replaceClassBlock(self, class_block, key, value): 709 | """replaces a class string with the new class name 710 | 711 | Arguments: 712 | class_block -- string from what would be found within class="{class_block}" 713 | key -- current class 714 | value -- new class 715 | 716 | Returns: 717 | string 718 | 719 | """ 720 | key_length = len(key) 721 | classes = class_block.split(" ") 722 | i = 0 723 | for class_name in classes: 724 | if class_name == key: 725 | classes[i] = value 726 | 727 | # allows support for things like a.class_name as one of the js selectors 728 | elif key[0] in (".", "#") and class_name[-key_length:] == key: 729 | classes[i] = class_name.replace(key, value) 730 | i = i + 1 731 | 732 | return " ".join(classes) 733 | 734 | def replaceHtmlClasses(self, html): 735 | """replaces any instances of classes in html markup 736 | 737 | Arguments: 738 | html -- contents of file to replace classes in 739 | 740 | Returns: 741 | string 742 | 743 | """ 744 | for key, value in self.class_map.items(): 745 | key = key[1:] 746 | value = value[1:] 747 | class_blocks = re.findall(r'class\=((\'|\")(.*?)(\'|\"))', html) 748 | for class_block in class_blocks: 749 | new_block = self.replaceClassBlock(class_block[2], key, value) 750 | html = html.replace("class=" + class_block[0], "class=" + class_block[1] + new_block + class_block[3]) 751 | 752 | return html 753 | 754 | def optimizeCssBlocks(self, html): 755 | """rewrites css blocks that are part of an html file 756 | 757 | Arguments: 758 | html -- contents of file we are replacing 759 | 760 | Returns: 761 | string 762 | 763 | """ 764 | result_css = "" 765 | matches = self.getCssBlocks(html) 766 | for match in matches: 767 | match = self.replaceCss(match) 768 | result_css = result_css + match 769 | 770 | if len(matches): 771 | return html.replace(matches[0], result_css) 772 | 773 | return html 774 | 775 | @staticmethod 776 | def getCssBlocks(html): 777 | """searches a file and returns all css blocks 778 | 779 | Arguments: 780 | html -- contents of file we are replacing 781 | 782 | Returns: 783 | list 784 | 785 | """ 786 | return re.compile(r'\