Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(533)

Side by Side Diff: samples/swarm/htmlconverter.py

Issue 10910013: Remove instances of dart:dom_deprecated from outside of lib/html and lib/dom. Part 1 of many. (Closed) Base URL: http://dart.googlecode.com/svn/branches/bleeding_edge/dart/
Patch Set: Created 8 years, 3 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
OLDNEW
(Empty)
1 #!/usr/bin/env python
2 # Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file
3 # for details. All rights reserved. Use of this source code is governed by a
4 # BSD-style license that can be found in the LICENSE file.
5
6 #
7
8 """Rewrites HTML files, converting Dart script sections into JavaScript.
9
10 Process HTML files, and internally changes script sections that use Dart code
11 into JavaScript sections. It also can optimize the HTML to inline code.
12 """
13
14 from HTMLParser import HTMLParser
15 import os.path
16 from os.path import abspath, basename, dirname, exists, isabs, join
17 import base64, re, optparse, os, shutil, subprocess, sys, tempfile, codecs
18 import urllib2
19
20 CLIENT_PATH = dirname(dirname(abspath(__file__)))
21 DART_PATH = dirname(CLIENT_PATH)
22 TOOLS_PATH = join(DART_PATH, 'tools')
23
24 sys.path.append(TOOLS_PATH)
25 import utils
26
27 DART_MIME_TYPE = "application/dart"
28 LIBRARY_PATTERN = "^#library\(.*\);"
29 IMPORT_SOURCE_MATCHER = re.compile(
30 r"^ *(#import|#source)(\(['\"])([^'\"]*)(.*\);)", re.MULTILINE)
31 DOM_IMPORT_MATCHER = re.compile(
32 r"^#import\(['\"]dart\:dom_deprecated['\"].*\);", re.MULTILINE)
33 HTML_IMPORT_MATCHER = re.compile(
34 r"^#import\(['\"]dart\:html['\"].*\);", re.MULTILINE)
35
36 FROG_NOT_FOUND_ERROR = (
37 """Couldn't find compiler: please run the following commands:
38 $ cd %s
39 $ ./tools/build.py -m release""")
40
41 ENTRY_POINT = """
42 #library('entry');
43 #import('%s', prefix: 'original');
44 main() => original.main();
45 """
46
47 CSS_TEMPLATE = '<style type="text/css">%s</style>'
48 CHROMIUM_SCRIPT_TEMPLATE = '<script type="application/javascript">%s</script>'
49
50 DARTIUM_TO_JS_SCRIPT = """
51 <script type="text/javascript">
52 (function() {
53 // Let the user know that Dart is required.
54 if (!window.navigator.webkitStartDart) {
55 if (confirm(
56 "You are trying to run Dart code on a browser " +
57 "that doesn't support Dart. Do you want to redirect to " +
58 "a version compiled to JavaScript instead?")) {
59 var addr = window.location;
60 window.location = addr.toString().replace('-dart.html', '-js.html');
61 }
62 } else {
63 window.navigator.webkitStartDart();
64 }
65 })();
66 </script>
67 """
68
69 def adjustImports(contents):
70 def repl(matchobj):
71 path = matchobj.group(3)
72 if not path.startswith('dart:'):
73 path = abspath(path)
74 return (matchobj.group(1) + matchobj.group(2) + path + matchobj.group(4))
75 return IMPORT_SOURCE_MATCHER.sub(repl, contents)
76
77 class DartCompiler(object):
78 """ Common code for compiling Dart script tags in an HTML file. """
79
80 def __init__(self, verbose=False,
81 extra_flags=""):
82 self.verbose = verbose
83 self.extra_flags = extra_flags
84
85 def compileCode(self, src=None, body=None):
86 """ Compile the given source code.
87
88 Either the script tag has a src attribute or a non-empty body (one of the
89 arguments will be none, the other is not).
90
91 Args:
92 src: a string pointing to a Dart script file.
93 body: a string containing Dart code.
94 """
95
96 outdir = tempfile.mkdtemp()
97 indir = None
98 useDartHtml = False
99 if src is not None:
100 if body is not None and body.strip() != '':
101 raise ConverterException(
102 "The script body should be empty if src is specified")
103 elif src.endswith('.dart'):
104 indir = tempfile.mkdtemp()
105 inputfile = abspath(src)
106 with open(inputfile, 'r') as f:
107 contents = f.read();
108
109 if HTML_IMPORT_MATCHER.search(contents):
110 useDartHtml = True
111
112 # We will import the source file to emulate in JS that code is run after
113 # DOMContentLoaded. We need a #library to ensure #import won't fail:
114 if not re.search(LIBRARY_PATTERN, contents, re.MULTILINE):
115 inputfile = join(indir, 'code.dart')
116 with open(inputfile, 'w') as f:
117 f.write("#library('code');")
118 f.write(adjustImports(contents))
119
120 else:
121 raise ConverterException("invalid file type:" + src)
122 else:
123 if body is None or body.strip() == '':
124 # nothing to do
125 print 'Warning: empty script tag with no src attribute'
126 return ''
127
128 indir = tempfile.mkdtemp()
129 # eliminate leading spaces in front of directives
130 body = adjustImports(body)
131
132 if HTML_IMPORT_MATCHER.search(body):
133 useDartHtml = True
134
135 inputfile = join(indir, 'code.dart')
136 with open(inputfile, 'w') as f:
137 f.write("#library('inlinedcode');\n")
138 f.write(body)
139
140 wrappedfile = join(indir, 'entry.dart')
141 with open(wrappedfile, 'w') as f:
142 f.write(ENTRY_POINT % inputfile)
143
144 status, out, err = execute(self.compileCommand(wrappedfile, outdir),
145 self.verbose)
146 if status:
147 raise ConverterException('compilation errors')
148
149 # Inline the compiled code in the page
150 with open(self.outputFileName(wrappedfile, outdir), 'r') as f:
151 res = f.read()
152
153 # Cleanup
154 if indir is not None:
155 shutil.rmtree(indir)
156 shutil.rmtree(outdir)
157 return CHROMIUM_SCRIPT_TEMPLATE % res
158
159 def compileCommand(self, inputfile, outdir):
160 binary = abspath(join(DART_PATH,
161 utils.GetBuildRoot(utils.GuessOS(),
162 'release', 'ia32'),
163 'dart-sdk', 'bin', 'dart2js'))
164 if not exists(binary):
165 raise ConverterException(FROG_NOT_FOUND_ERROR % DART_PATH)
166
167 cmd = [binary,
168 '--out=' + self.outputFileName(inputfile, outdir)]
169 if self.extra_flags != "":
170 cmd.append(self.extra_flags);
171 cmd.append(inputfile)
172 return cmd
173
174 def outputFileName(self, inputfile, outdir):
175 return join(outdir, basename(inputfile) + '.js')
176
177 def execute(cmd, verbose=False):
178 """Execute a command in a subprocess. """
179 if verbose: print 'Executing: ' + ' '.join(cmd)
180 try:
181 pipe = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
182 output, err = pipe.communicate()
183 if pipe.returncode != 0:
184 print 'Execution failed: ' + output + '\n' + err
185 if verbose or pipe.returncode != 0:
186 print output
187 print err
188 return pipe.returncode, output, err
189 except Exception as e:
190 print 'Exception when executing: ' + ' '.join(cmd)
191 print e
192 return 1, None, None
193
194
195 def convertPath(project_path, prefix_path):
196 """ Convert a project path (whose root corresponds to the current working
197 directory) to a system path.
198 Args:
199 - project_path: path in the project context.
200 - prefix_path: prefix for relative paths.
201 """
202 if isabs(project_path):
203 # TODO(sigmund): add a flag to pass in the root-level for absolute paths.
204 return project_path[1:]
205 elif not (project_path.startswith('http://') or
206 project_path.startswith('https://')):
207 return join(prefix_path, project_path)
208 else:
209 return project_path
210
211 def encodeImage(rootDir, filename):
212 """ Returns a base64 url encoding for an image """
213 filetype = filename[-3:]
214 if filetype == 'svg': filetype = 'svg+xml'
215 with open(join(rootDir, filename), 'r') as f:
216 return 'url(data:image/%s;charset=utf-8;base64,%s)' % (
217 filetype,
218 base64.b64encode(f.read()))
219
220 def processCss(filename):
221 """ Reads and converts a css file by replacing all image refernces into
222 base64 encoded images.
223 """
224 css = open(filename, 'r').read()
225 cssDir = os.path.split(filename)[0]
226 def transformUrl(match):
227 imagefile = match.group(1)
228 # if the image is not local or can't be found, leave the url alone:
229 if (imagefile.startswith('http://')
230 or imagefile.startswith('https://')
231 or not exists(join(cssDir, imagefile))):
232 return match.group(0)
233 return encodeImage(cssDir, imagefile)
234
235 pattern = 'url\((.*\.(svg|png|jpg|gif))\)'
236 return re.sub(pattern, transformUrl, css)
237
238 class DartHTMLConverter(HTMLParser):
239 """ An HTML processor that inlines css and compiled dart code.
240
241 Args:
242 - compiler: an implementation of DartAnyCompiler
243 - prefix_path: prefix for relative paths encountered in the HTML.
244 """
245 def __init__(self, compiler, prefix_path):
246 HTMLParser.__init__(self)
247 self.in_dart_tag = False
248 self.output = []
249 self.dart_inline_code = []
250 self.contains_dart = False
251 self.compiler = compiler
252 self.prefix_path = prefix_path
253
254 def inlineCss(self, attrDic):
255 path = convertPath(attrDic['href'], self.prefix_path)
256 self.output.append(CSS_TEMPLATE % processCss(path))
257
258 def compileScript(self, attrDic):
259 if 'src' in attrDic:
260 self.output.append(self.compiler.compileCode(
261 src=convertPath(attrDic.pop('src'), self.prefix_path),
262 body=None))
263 else:
264 self.in_dart_tag = True
265 # no tag is generated until we parse the body of the tag
266 self.dart_inline_code = []
267 return True
268
269 def convertImage(self, attrDic):
270 pass
271
272 def starttagHelper(self, tag, attrs, isEnd):
273 attrDic = dict(attrs)
274
275 # collect all script files, and generate a single script before </body>
276 if (tag == 'script' and 'type' in attrDic
277 and (attrDic['type'] == DART_MIME_TYPE)):
278 if self.compileScript(attrDic):
279 return
280
281 # convert css imports into inlined css
282 elif (tag == 'link' and
283 'rel' in attrDic and attrDic['rel'] == 'stylesheet' and
284 'type' in attrDic and attrDic['type'] == 'text/css' and
285 'href' in attrDic):
286 self.inlineCss(attrDic)
287 return
288
289 elif tag == 'img' and 'src' in attrDic:
290 self.convertImage(attrDic)
291
292 # emit everything else as in the input
293 self.output.append('<%s%s%s>' % (
294 tag + (' ' if len(attrDic) else ''),
295 ' '.join(['%s="%s"' % (k, attrDic[k]) for k in attrDic]),
296 '/' if isEnd else ''))
297
298 def handle_starttag(self, tag, attrs):
299 self.starttagHelper(tag, attrs, False)
300
301 def handle_startendtag(self, tag, attrs):
302 self.starttagHelper(tag, attrs, True)
303
304 def handle_data(self, data):
305 if self.in_dart_tag:
306 # collect the dart source code and compile it all at once when no more
307 # script tags can be included. Note: the code will anyways start on
308 # DOMContentLoaded, so moving the script is OK.
309 self.dart_inline_code.append(data)
310 else:
311 self.output.append(data),
312
313 def handle_endtag(self, tag):
314 if tag == 'script' and self.in_dart_tag:
315 self.in_dart_tag = False
316 self.output.append(self.compiler.compileCode(
317 src=None, body='\n'.join(self.dart_inline_code)))
318 else:
319 self.output.append('</%s>' % tag)
320
321 def handle_charref(self, ref):
322 self.output.append('&#%s;' % ref)
323
324 def handle_entityref(self, name):
325 self.output.append('&%s;' % name)
326
327 def handle_comment(self, data):
328 self.output.append('<!--%s-->' % data)
329
330 def handle_decl(self, decl):
331 self.output.append('<!%s>' % decl)
332
333 def unknown_decl(self, data):
334 self.output.append('<!%s>' % data)
335
336 def handle_pi(self, data):
337 self.output.append('<?%s>' % data)
338
339 def getResult(self):
340 return ''.join(self.output)
341
342
343 class DartToDartHTMLConverter(DartHTMLConverter):
344 def __init__(self, prefix_path, outdir, verbose):
345 # Note: can't use super calls because HTMLParser is not a subclass of object
346 DartHTMLConverter.__init__(self, None, prefix_path)
347 self.outdir = outdir
348 self.verbose = verbose
349
350 def compileScript(self, attrDic):
351 self.contains_dart = True
352 if 'src' in attrDic:
353 status, out, err = execute([
354 sys.executable,
355 join(DART_PATH, 'tools', 'copy_dart.py'),
356 self.outdir,
357 convertPath(attrDic['src'], self.prefix_path)],
358 self.verbose)
359
360 if status:
361 raise ConverterException('exception calling copy_dart.py')
362
363 # do not rewrite the script tag
364 return False
365
366 def handle_endtag(self, tag):
367 if tag == 'body' and self.contains_dart:
368 self.output.append(DARTIUM_TO_JS_SCRIPT)
369 DartHTMLConverter.handle_endtag(self, tag)
370
371 # A data URL for a blank 1x1 PNG. The PNG's data is from
372 # convert -size 1x1 +set date:create +set date:modify \
373 # xc:'rgba(0,0,0,0)' 1x1.png
374 # base64.b64encode(open('1x1.png').read())
375 # (The +set stuff is because just doing "-strip" apparently doesn't work;
376 # it leaves several info chunks resulting in a 224-byte PNG.)
377 BLANK_IMAGE_BASE64_URL = 'data:image/png;charset=utf-8;base64,%s' % (
378 ('iVBORw0KGgoAAAANSUhEUgAAAAEAAAABEAQAAADljNBBAAAAAmJLR0T//xSrMc0AAAAJc'
379 'EhZcwAAAEgAAABIAEbJaz4AAAAJdnBBZwAAAAEAAAABAMeVX+0AAAANSURBVAjXY2BgYG'
380 'AAAAAFAAFe8yo6AAAAAElFTkSuQmCC'))
381
382 class OfflineHTMLConverter(DartHTMLConverter):
383 def __init__(self, prefix_path, outdir, verbose, inline_images):
384 # Note: can't use super calls because HTMLParser is not a subclass of object
385 DartHTMLConverter.__init__(self, None, prefix_path)
386 self.outdir = outdir
387 self.verbose = verbose
388 self.inline_images = inline_images # Inline as data://, vs. use local file.
389
390 def compileScript(self, attrDic):
391 # do not rewrite the script tag
392 return False
393
394 def downloadImageUrlToEncode(self, url):
395 """ Downloads an image and returns a base64 url encoding for it.
396 May throw if the download fails.
397 """
398 # Don't try to re-encode an image that's already data://.
399 filetype = url[-3:]
400 if filetype == 'svg': filetype = 'svg+xml'
401 if self.verbose:
402 print 'Downloading ' + url
403 f = urllib2.urlopen(url)
404
405 return 'data:image/%s;charset=utf-8;base64,%s' % (
406 filetype,
407 base64.b64encode(f.read()))
408
409 def downloadImageUrlToFile(self, url):
410 """Downloads an image and returns the filename. May throw if the
411 download fails.
412 """
413 extension = os.path.splitext(url)[1]
414 # mkstemp() happens to work to create a non-temporary, so we use it.
415 filename = tempfile.mkstemp(extension, 'img_', self.prefix_path)[1]
416 if self.verbose:
417 print 'Downloading %s to %s' % (url, filename)
418 writeOut(urllib2.urlopen(url).read(), filename)
419 return os.path.join(self.prefix_path, os.path.basename(filename))
420
421 def downloadImage(self, url):
422 """Downloads an image either to file or to data://, and return the URL."""
423 if url.startswith('data:image/'):
424 return url
425 try:
426 if self.inline_images:
427 return self.downloadImageUrlToEncode(url)
428 else:
429 return self.downloadImageUrlToFile(url)
430 except:
431 print '*** Image download failed: %s' % url
432 return BLANK_IMAGE_BASE64_URL
433
434 def convertImage(self, attrDic):
435 attrDic['src'] = self.downloadImage(attrDic['src'])
436
437 def safeMakeDirs(dirname):
438 """ Creates a directory and, if necessary its parent directories.
439
440 This function will safely return if other concurrent jobs try to create the
441 same directory.
442 """
443 if not exists(dirname):
444 try:
445 os.makedirs(dirname)
446 except Exception:
447 # this check allows invoking this script concurrently in many jobs
448 if not exists(dirname):
449 raise
450
451 class ConverterException(Exception):
452 """ An exception encountered during the convertion process """
453 pass
454
455 def Flags():
456 """ Constructs a parser for extracting flags from the command line. """
457 result = optparse.OptionParser()
458 result.add_option("--verbose",
459 help="Print verbose output",
460 default=False,
461 action="store_true")
462 result.add_option("-o", "--out",
463 help="Output directory",
464 type="string",
465 default=None,
466 action="store")
467 result.add_option("-t", "--target",
468 help="The target html to generate",
469 metavar="[js,chromium,dartium]",
470 default='chromium')
471 result.add_option("--extra-flags",
472 help="Extra flags for dart2js",
473 type="string",
474 default="")
475 result.set_usage("htmlconverter.py input.html -o OUTDIR")
476 return result
477
478 def writeOut(contents, filepath):
479 """ Writes contents to a file, ensuring that the output directory exists. """
480 safeMakeDirs(dirname(filepath))
481 with open(filepath, 'w') as f:
482 f.write(contents)
483 print "Generated output in: " + abspath(filepath)
484
485 def convertForDartium(filename, outdirBase, outfile, verbose):
486 """ Converts a file for a dartium target. """
487 with open(filename, 'r') as f:
488 contents = f.read()
489 prefix_path = dirname(filename)
490
491 # outdirBase is the directory to place all subdirectories for other dart files
492 # and resources.
493 converter = DartToDartHTMLConverter(prefix_path, outdirBase, verbose)
494 converter.feed(contents)
495 converter.close()
496 writeOut(converter.getResult(), outfile)
497
498 def convertForChromium(
499 filename, extra_flags, outfile, verbose):
500 """ Converts a file for a chromium target. """
501 with open(filename, 'r') as f:
502 contents = f.read()
503 prefix_path = dirname(filename)
504 converter = DartHTMLConverter(
505 DartCompiler(verbose, extra_flags), prefix_path)
506 converter.feed(contents)
507 converter.close()
508 writeOut(converter.getResult(), outfile)
509
510 def convertForOffline(filename, outfile, verbose, encode_images):
511 """ Converts a file for offline use. """
512 with codecs.open(filename, 'r', 'utf-8') as f:
513 contents = f.read()
514 converter = OfflineHTMLConverter(dirname(filename),
515 dirname(outfile),
516 verbose,
517 encode_images)
518 converter.feed(contents)
519 converter.close()
520
521 contents = converter.getResult()
522 safeMakeDirs(dirname(outfile))
523 with codecs.open(outfile, 'w', 'utf-8') as f:
524 f.write(contents)
525 print "Generated output in: " + abspath(outfile)
526
527 RED_COLOR = "\033[31m"
528 NO_COLOR = "\033[0m"
529
530 def main():
531 parser = Flags()
532 options, args = parser.parse_args()
533 if len(args) < 1 or not options.out or not options.target:
534 parser.print_help()
535 return 1
536
537 try:
538 filename = args[0]
539 extension = filename[filename.rfind('.'):]
540 if extension != '.html' and extension != '.htm':
541 print "Invalid input file extension: %s" % extension
542 return 1
543 outfile = join(options.out, filename)
544 if 'chromium' in options.target or 'js' in options.target:
545 convertForChromium(filename,
546 options.extra_flags,
547 outfile.replace(extension, '-js' + extension), options.verbose)
548 if 'dartium' in options.target:
549 convertForDartium(filename, options.out,
550 outfile.replace(extension, '-dart' + extension), options.verbose)
551 except Exception as e:
552 print "%sERROR%s: %s" % (RED_COLOR, NO_COLOR, str(e))
553 return 1
554 return 0
555
556 if __name__ == '__main__':
557 sys.exit(main())
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698