#!/usr/bin/python # # Copyright (c) 2014, the Dart project authors. Please see the AUTHORS file # for details. All rights reserved. Use of this source code is governed by a # BSD-style license that can be found in the LICENSE file. """Generates CSSStyleDeclaration template file from css property definitions defined in WebKit.""" import tempfile, os, re COMMENT_LINE_PREFIX = ' * ' # TODO(efortuna): Pull from DEPS so that we have latest css *in sync* with our # Dartium. Then remove the checked in CSSPropertyNames.in. SOURCE_PATH = 'CSSPropertyNames.in' #SOURCE_PATH = 'Source/WebCore/css/CSSPropertyNames.in' TEMPLATE_FILE = '../templates/html/impl/impl_CSSStyleDeclaration.darttemplate' # These are the properties that are supported on all Dart project supported # browsers as camelCased names on the CssStyleDeclaration. # Note that we do not use the MDN for compatibility info here. BROWSER_PATHS = [ 'cssProperties.CSS21.txt', # Remove when we have samples from all browsers. 'cssProperties.ie9.txt', 'cssProperties.ie10.txt', 'cssProperties.ie11.txt', 'cssProperties.ff36.txt', 'cssProperties.chrome40.txt', 'cssProperties.safari-7.1.3.txt', 'cssProperties.mobileSafari-8.2.txt', 'cssProperties.iPad4Air.onGoogleSites.txt', ] # Supported annotations for any specific CSS properties. annotated = { 'transition': '''@SupportedBrowser(SupportedBrowser.CHROME) @SupportedBrowser(SupportedBrowser.FIREFOX) @SupportedBrowser(SupportedBrowser.IE, '10') @SupportedBrowser(SupportedBrowser.SAFARI)''' } class Error: def __init__(self, message): self.message = message def __repr__(self): return self.message def camelCaseName(name): """Convert a CSS property name to a lowerCamelCase name.""" name = name.replace('-webkit-', '') words = [] for word in name.split('-'): if words: words.append(word.title()) else: words.append(word) return ''.join(words) def dashifyName(camelName): def fix(match): return '-' + match.group(0).lower() return re.sub(r'[A-Z]', fix, camelName) def isCommentLine(line): return line.strip() == '' or line.startswith('#') or line.startswith('//') def readCssProperties(filename): data = open(filename).readlines() data = sorted([d.strip() for d in set(data) if not isCommentLine(d)]) return data def GenerateCssTemplateFile(): data = open(SOURCE_PATH).readlines() # filter CSSPropertyNames.in to only the properties # TODO(efortuna): do we also want CSSPropertyNames.in? data = [d.strip() for d in data if not isCommentLine(d) and not '=' in d] browser_props = [readCssProperties(file) for file in BROWSER_PATHS] universal_properties = reduce(lambda a, b: set(a).intersection(b), browser_props) universal_properties = universal_properties.difference(['cssText']) universal_properties = universal_properties.intersection( map(camelCaseName, data)) class_file = open(TEMPLATE_FILE, 'w') class_file.write(""" // Copyright (c) 2014, the Dart project authors. Please see the AUTHORS file // for details. All rights reserved. Use of this source code is governed by a // BSD-style license that can be found in the LICENSE file. // WARNING: DO NOT EDIT THIS TEMPLATE FILE. // The template file was generated by scripts/css_code_generator.py // Source of CSS properties: // %s part of $LIBRARYNAME; """ % SOURCE_PATH) class_file.write(""" $(ANNOTATIONS)$(NATIVESPEC)$(CLASS_MODIFIERS)class $CLASSNAME $EXTENDS with $(CLASSNAME)Base $IMPLEMENTS { factory $CLASSNAME() => new CssStyleDeclaration.css(''); factory $CLASSNAME.css(String css) { final style = new DivElement().style; style.cssText = css; return style; } /// Returns the value of the property if the provided *CSS* property /// name is supported on this element and if the value is set. Otherwise /// returns an empty string. /// /// Please note the property name uses camelCase, not-hyphens. String getPropertyValue(String propertyName) { return _getPropertyValueHelper(propertyName); } String _getPropertyValueHelper(String propertyName) { return _getPropertyValue(_browserPropertyName(propertyName)); } /** * Returns true if the provided *CSS* property name is supported on this * element. * * Please note the property name camelCase, not-hyphens. This * method returns true if the property is accessible via an unprefixed _or_ * prefixed property. */ bool supportsProperty(String propertyName) { return _supportsProperty(propertyName) || _supportsProperty(_camelCase("${Device.cssPrefix}$propertyName")); } bool _supportsProperty(String propertyName) { return JS('bool', '# in #', propertyName, this); } void setProperty(String propertyName, String$NULLABLE value, [String$NULLABLE priority]) { return _setPropertyHelper(_browserPropertyName(propertyName), value, priority); } String _browserPropertyName(String propertyName) { String$NULLABLE name = _readCache(propertyName); if (name is String) return name; name = _supportedBrowserPropertyName(propertyName); _writeCache(propertyName, name); return name; } String _supportedBrowserPropertyName(String propertyName) { if (_supportsProperty(_camelCase(propertyName))) { return propertyName; } var prefixed = "${Device.cssPrefix}$propertyName"; if (_supportsProperty(prefixed)) { return prefixed; } // May be a CSS variable, just use it as provided. return propertyName; } static final _propertyCache = JS('', '{}'); static String$NULLABLE _readCache(String key) => JS('String|Null', '#[#]', _propertyCache, key); static void _writeCache(String key, String value) { JS('void', '#[#] = #', _propertyCache, key, value); } static String _camelCase(String hyphenated) { var replacedMs = JS('String', r'#.replace(/^-ms-/, "ms-")', hyphenated); return JS( 'String', r'#.replace(/-([\da-z])/ig,' r'function(_, letter) { return letter.toUpperCase();})', replacedMs); } void _setPropertyHelper(String propertyName, String$NULLABLE value, [String$NULLABLE priority]) { if (value == null) value = ''; if (priority == null) priority = ''; JS('void', '#.setProperty(#, #, #)', this, propertyName, value, priority); } /** * Checks to see if CSS Transitions are supported. */ static bool get supportsTransitions { return document.body$NULLASSERT.style.supportsProperty('transition'); } $!MEMBERS """) for camelName in sorted(universal_properties): property = dashifyName(camelName) class_file.write(""" /** Gets the value of "%s" */ String get %s => this._%s; /** Sets the value of "%s" */ set %s(String$NULLABLE value) { _%s = value == null ? '' : value; } @Returns('String') @JSName('%s') String get _%s native; @JSName('%s') set _%s(String value) native; """ % (property, camelName, camelName, property, camelName, camelName, camelName, camelName, camelName, camelName)) class_file.write(""" } class _CssStyleDeclarationSet extends Object with CssStyleDeclarationBase { final Iterable _elementIterable; Iterable$NULLABLE _elementCssStyleDeclarationSetIterable; _CssStyleDeclarationSet(this._elementIterable) { _elementCssStyleDeclarationSetIterable = new List.from( _elementIterable).map((e) => e.style); } String getPropertyValue(String propertyName) => _elementCssStyleDeclarationSetIterable$NULLASSERT.first.getPropertyValue( propertyName); void setProperty(String propertyName, String$NULLABLE value, [String$NULLABLE priority]) { _elementCssStyleDeclarationSetIterable$NULLASSERT.forEach((e) => e.setProperty(propertyName, value, priority)); } """) class_file.write(""" void _setAll(String propertyName, String$NULLABLE value) { value = value == null ? '' : value; for (Element element in _elementIterable) { JS('void', '#.style[#] = #', element, propertyName, value); } } """) for camelName in sorted(universal_properties): property = dashifyName(camelName) class_file.write(""" /** Sets the value of "%s" */ set %s(String value) { _setAll('%s', value); } """ % (property, camelName, camelName)) class_file.write(""" // Important note: CssStyleDeclarationSet does NOT implement every method // available in CssStyleDeclaration. Some of the methods don't make so much // sense in terms of having a resonable value to return when you're // considering a list of Elements. You will need to manually add any of the // items in the MEMBERS set if you want that functionality. } abstract class CssStyleDeclarationBase { String getPropertyValue(String propertyName); void setProperty(String propertyName, String$NULLABLE value, [String$NULLABLE priority]); """) class_lines = [] seen = set() for prop in sorted(data, key=camelCaseName): camel_case_name = camelCaseName(prop) upper_camel_case_name = camel_case_name[0].upper() + camel_case_name[1:] css_name = prop.replace('-webkit-', '') base_css_name = prop.replace('-webkit-', '') if base_css_name in seen or base_css_name.startswith('-internal'): continue seen.add(base_css_name) comment = ' /** %s the value of "' + base_css_name + '" */' class_lines.append('\n') class_lines.append(comment % 'Gets') if base_css_name in annotated: class_lines.append(annotated[base_css_name]) class_lines.append(""" String get %s => getPropertyValue('%s'); """ % (camel_case_name, css_name)) class_lines.append(comment % 'Sets') if base_css_name in annotated: class_lines.append(annotated[base_css_name]) class_lines.append(""" set %s(String value) { setProperty('%s', value, ''); } """ % (camel_case_name, css_name)) class_file.write(''.join(class_lines)) class_file.write('}\n') class_file.close()