Index: pkg/polymer/lib/src/css_emitters.dart |
diff --git a/pkg/polymer/lib/src/css_emitters.dart b/pkg/polymer/lib/src/css_emitters.dart |
new file mode 100644 |
index 0000000000000000000000000000000000000000..d1863f59ff24e1a9cb28fa76719b3fa0e0eaee17 |
--- /dev/null |
+++ b/pkg/polymer/lib/src/css_emitters.dart |
@@ -0,0 +1,155 @@ |
+// Copyright (c) 2013, 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. |
+ |
+library polymer.src.css_emitters; |
+ |
+import 'package:csslib/visitor.dart' show Visitor, CssPrinter, ElementSelector, |
+ UriTerm, Selector, HostDirective, SimpleSelectorSequence, StyleSheet; |
+ |
+import 'info.dart'; |
+import 'paths.dart' show PathMapper; |
+import 'utils.dart'; |
+ |
+void rewriteCssUris(PathMapper pathMapper, String cssPath, bool rewriteUrls, |
+ StyleSheet styleSheet) { |
+ new _UriVisitor(pathMapper, cssPath, rewriteUrls).visitTree(styleSheet); |
+} |
+ |
+/** Compute each CSS URI resource relative from the generated CSS file. */ |
+class _UriVisitor extends Visitor { |
+ /** |
+ * Relative path from the output css file to the location of the original |
+ * css file that contained the URI to each resource. |
+ */ |
+ final String _pathToOriginalCss; |
+ |
+ factory _UriVisitor(PathMapper pathMapper, String cssPath, bool rewriteUrl) { |
+ var cssDir = path.dirname(cssPath); |
+ var outCssDir = rewriteUrl ? pathMapper.outputDirPath(cssPath) |
+ : path.dirname(cssPath); |
+ return new _UriVisitor._internal(path.relative(cssDir, from: outCssDir)); |
+ } |
+ |
+ _UriVisitor._internal(this._pathToOriginalCss); |
+ |
+ void visitUriTerm(UriTerm node) { |
+ // Don't touch URIs that have any scheme (http, etc.). |
+ var uri = Uri.parse(node.text); |
+ if (uri.host != '') return; |
+ if (uri.scheme != '' && uri.scheme != 'package') return; |
+ |
+ node.text = pathToUrl( |
+ path.normalize(path.join(_pathToOriginalCss, node.text))); |
+ } |
+} |
+ |
+ |
+/** Emit the contents of the style tag outside of a component. */ |
+String emitStyleSheet(StyleSheet ss, FileInfo file) => |
+ (new _CssEmitter(file.components.keys.toSet()) |
+ ..visitTree(ss, pretty: true)).toString(); |
+ |
+/** Emit a component's style tag content emulating scoped css. */ |
+String emitComponentStyleSheet(StyleSheet ss, String tagName) => |
+ (new _ComponentCssEmitter(tagName)..visitTree(ss, pretty: true)).toString(); |
+ |
+String emitOriginalCss(StyleSheet css) => |
+ (new CssPrinter()..visitTree(css)).toString(); |
+ |
+/** Only x-tag name element selectors are emitted as [is="x-"]. */ |
+class _CssEmitter extends CssPrinter { |
+ final Set _componentsTag; |
+ _CssEmitter(this._componentsTag); |
+ |
+ void visitElementSelector(ElementSelector node) { |
+ // If element selector is a component's tag name, then change selector to |
+ // find element who's is attribute's the component's name. |
+ if (_componentsTag.contains(node.name)) { |
+ emit('[is="${node.name}"]'); |
+ return; |
+ } |
+ super.visitElementSelector(node); |
+ } |
+} |
+ |
+/** |
+ * Emits a css stylesheet applying rules to emulate scoped css. The rules adjust |
+ * element selectors to include the component's tag name. |
+ */ |
+class _ComponentCssEmitter extends CssPrinter { |
+ final String _componentTagName; |
+ bool _inHostDirective = false; |
+ bool _selectorStartInHostDirective = false; |
+ |
+ _ComponentCssEmitter(this._componentTagName); |
+ |
+ /** Is the element selector an x-tag name. */ |
+ bool _isSelectorElementXTag(Selector node) { |
+ if (node.simpleSelectorSequences.length > 0) { |
+ var selector = node.simpleSelectorSequences[0].simpleSelector; |
+ return selector is ElementSelector && selector.name == _componentTagName; |
+ } |
+ return false; |
+ } |
+ |
+ void visitSelector(Selector node) { |
+ // If the selector starts with an x-tag name don't emit it twice. |
+ if (!_isSelectorElementXTag(node)) { |
+ if (_inHostDirective) { |
+ // Style the element that's hosting the component, therefore don't emit |
+ // the descendent combinator (first space after the [is="x-..."]). |
+ emit('[is="$_componentTagName"]'); |
+ // Signal that first simpleSelector must be checked. |
+ _selectorStartInHostDirective = true; |
+ } else { |
+ // Emit its scoped as a descendent (space at end). |
+ emit('[is="$_componentTagName"] '); |
+ } |
+ } |
+ super.visitSelector(node); |
+ } |
+ |
+ /** |
+ * If first simple selector of a ruleset in a @host directive is a wildcard |
+ * then don't emit the wildcard. |
+ */ |
+ void visitSimpleSelectorSequence(SimpleSelectorSequence node) { |
+ if (_selectorStartInHostDirective) { |
+ _selectorStartInHostDirective = false; |
+ if (node.simpleSelector.isWildcard) { |
+ // Skip the wildcard if first item in the sequence. |
+ return; |
+ } |
+ assert(node.isCombinatorNone); |
+ } |
+ |
+ super.visitSimpleSelectorSequence(node); |
+ } |
+ |
+ void visitElementSelector(ElementSelector node) { |
+ // If element selector is the component's tag name, then change selector to |
+ // find element who's is attribute is the component's name. |
+ if (_componentTagName == node.name) { |
+ emit('[is="$_componentTagName"]'); |
+ return; |
+ } |
+ super.visitElementSelector(node); |
+ } |
+ |
+ /** |
+ * If we're polyfilling scoped styles the @host directive is stripped. Any |
+ * ruleset(s) processed in an @host will fixup the first selector. See |
+ * visitSelector and visitSimpleSelectorSequence in this class, they adjust |
+ * the selectors so it styles the element hosting the compopnent. |
+ */ |
+ void visitHostDirective(HostDirective node) { |
+ _inHostDirective = true; |
+ emit('/* @host */'); |
+ for (var ruleset in node.rulesets) { |
+ ruleset.visit(this); |
+ } |
+ _inHostDirective = false; |
+ emit('/* end of @host */\n'); |
+ } |
+} |