| #!/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. |
| 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 Element.tag('div').style; |
| style.cssText = css; |
| return style; |
| } |
| |
| String getPropertyValue(String propertyName) { |
| var propValue = _getPropertyValueHelper(propertyName); |
| return propValue != null ? propValue : ''; |
| } |
| |
| String _getPropertyValueHelper(String propertyName) { |
| if (_supportsProperty(_camelCase(propertyName))) { |
| return _getPropertyValue(propertyName); |
| } else { |
| return _getPropertyValue(Device.cssPrefix + 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) { |
| $if DART2JS |
| return JS('bool', '# in #', propertyName, this); |
| $else |
| // You can't just check the value of a property, because there is no way |
| // to distinguish between property not being present in the browser and |
| // not having a value at all. (Ultimately we'll want the native method to |
| // return null if the property doesn't exist and empty string if it's |
| // defined but just doesn't have a value. |
| return _hasProperty(propertyName); |
| $endif |
| } |
| |
| $if DARTIUM |
| bool _hasProperty(String propertyName) => |
| $if JSINTEROP |
| _blink.BlinkCSSStyleDeclaration.instance.$__propertyQuery___Callback_1_(this, propertyName); |
| $else |
| _blink.BlinkCSSStyleDeclaration.$__propertyQuery___Callback_1(this, propertyName); |
| $endif |
| $endif |
| |
| @DomName('CSSStyleDeclaration.setProperty') |
| void setProperty(String propertyName, String value, [String priority]) { |
| return _setPropertyHelper(_browserPropertyName(propertyName), |
| value, priority); |
| } |
| |
| String _browserPropertyName(String propertyName) { |
| String name = _readCache(propertyName); |
| if (name is String) return name; |
| if (_supportsProperty(_camelCase(propertyName))) { |
| name = propertyName; |
| } else { |
| name = Device.cssPrefix + propertyName; |
| } |
| _writeCache(propertyName, name); |
| return name; |
| } |
| |
| $if DART2JS |
| static final _propertyCache = JS('', '{}'); |
| static String _readCache(String key) => |
| JS('String|Null', '#[#]', _propertyCache, key); |
| static void _writeCache(String key, String value) { |
| JS('void', '#[#] = #', _propertyCache, key, value); |
| } |
| $else |
| static String _readCache(String key) => null; |
| static void _writeCache(String key, value) {} |
| $endif |
| |
| static String _camelCase(String hyphenated) { |
| $if DART2JS |
| var replacedMs = JS('String', r'#.replace(/^-ms-/, "ms-")', hyphenated); |
| |
| var fToUpper = const JS_CONST( |
| r'function(_, letter) { return letter.toUpperCase(); }'); |
| return JS('String', r'#.replace(/-([\da-z])/ig, #)', replacedMs, fToUpper); |
| $else |
| // The "ms" prefix is always lowercased. |
| return hyphenated.replaceFirst(new RegExp('^-ms-'), 'ms-').replaceAllMapped( |
| new RegExp('-([a-z]+)', caseSensitive: false), |
| (match) => match[0][1].toUpperCase() + match[0].substring(2)); |
| $endif |
| } |
| |
| $if DART2JS |
| void _setPropertyHelper(String propertyName, String value, [String 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.style.supportsProperty('transition'); |
| } |
| $else |
| void _setPropertyHelper(String propertyName, String value, [String priority]) { |
| if (priority == null) { |
| priority = ''; |
| } |
| _setProperty(propertyName, value, priority); |
| } |
| |
| /** |
| * Checks to see if CSS Transitions are supported. |
| */ |
| static bool get supportsTransitions => true; |
| $endif |
| $!MEMBERS |
| $if DART2JS |
| """) |
| |
| 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 value) { |
| _%s = value == null ? '' : value; |
| } |
| @Returns('String') |
| @JSName('%s') |
| String _%s; |
| """ % (property, camelName, camelName, |
| property, camelName, camelName, |
| camelName, camelName)) |
| |
| class_file.write(""" |
| $endif |
| } |
| |
| class _CssStyleDeclarationSet extends Object with CssStyleDeclarationBase { |
| final Iterable<Element> _elementIterable; |
| Iterable<CssStyleDeclaration> _elementCssStyleDeclarationSetIterable; |
| |
| _CssStyleDeclarationSet(this._elementIterable) { |
| _elementCssStyleDeclarationSetIterable = new List.from( |
| _elementIterable).map((e) => e.style); |
| } |
| |
| String getPropertyValue(String propertyName) => |
| _elementCssStyleDeclarationSetIterable.first.getPropertyValue( |
| propertyName); |
| |
| void setProperty(String propertyName, String value, [String priority]) { |
| _elementCssStyleDeclarationSetIterable.forEach((e) => |
| e.setProperty(propertyName, value, priority)); |
| } |
| |
| """) |
| |
| class_file.write(""" |
| $if DART2JS |
| void _setAll(String propertyName, String 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(""" |
| $endif |
| |
| // 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. |
| } |
| |
| $if DART2JS |
| abstract class CssStyleDeclarationBase { |
| String getPropertyValue(String propertyName); |
| void setProperty(String propertyName, String value, [String priority]); |
| $else |
| $if JSINTEROP |
| class CssStyleDeclarationBase { |
| String getPropertyValue(String propertyName) => |
| throw new StateError('getProperty not overridden in dart:html'); |
| void setProperty(String propertyName, String value, [String priority]) => |
| throw new StateError('setProperty not overridden in dart:html'); |
| $else |
| abstract class CssStyleDeclarationBase { |
| String getPropertyValue(String propertyName); |
| void setProperty(String propertyName, String value, [String priority]); |
| $endif |
| $endif |
| """) |
| |
| 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() |