| #!/usr/bin/env python3 | |
| # | |
| # 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 = [set(readCssProperties(file)) for file in BROWSER_PATHS] | |
| universal_properties = set.intersection(*browser_props) | |
| universal_properties = universal_properties.difference(['cssText']) | |
| universal_properties = universal_properties.intersection( | |
| list(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<Element> _elementIterable; | |
| Iterable<CssStyleDeclaration> _elementCssStyleDeclarationSetIterable; | |
| _CssStyleDeclarationSet(this._elementIterable) | |
| : _elementCssStyleDeclarationSetIterable = | |
| new List.of(_elementIterable).map((e) => e.style); | |
| String getPropertyValue(String propertyName) => | |
| _elementCssStyleDeclarationSetIterable.first.getPropertyValue( | |
| propertyName); | |
| void setProperty(String propertyName, String$NULLABLE value, | |
| [String$NULLABLE priority]) { | |
| _elementCssStyleDeclarationSetIterable.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 reasonable 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 mixin 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() |