Index: pkg/template_binding/lib/src/mustache_tokens.dart |
diff --git a/pkg/template_binding/lib/src/mustache_tokens.dart b/pkg/template_binding/lib/src/mustache_tokens.dart |
new file mode 100644 |
index 0000000000000000000000000000000000000000..be84d26205cba07597410dc02446e1604e4f646d |
--- /dev/null |
+++ b/pkg/template_binding/lib/src/mustache_tokens.dart |
@@ -0,0 +1,146 @@ |
+// 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 template_binding.src.mustache_tokens; |
+ |
+import 'package:observe/observe.dart'; |
+import 'package:template_binding/template_binding.dart'; |
+ |
+/** |
+ * Represents a set of parsed tokens from a {{ mustache binding expression }}. |
+ * This can be created by calling [parse]. |
+ * |
+ * For performance reasons the data is stored in one linear array in [_tokens]. |
+ * This class wraps that array and provides accessors in an attempt to make the |
+ * pattern easier to understand. See [length] and [getText] for example. |
+ */ |
+class MustacheTokens { |
+ // Constants for indexing into the exploded structs in [_tokens] . |
+ static const _TOKEN_TEXT = 0; |
+ static const _TOKEN_ONETIME = 1; |
+ static const _TOKEN_PATH = 2; |
+ static const _TOKEN_PREPAREFN = 3; |
+ static const _TOKEN_SIZE = 4; |
+ |
+ // There is 1 extra entry for the end text. |
+ static const _TOKEN_ENDTEXT = 1; |
+ |
+ bool get hasOnePath => _tokens.length == _TOKEN_SIZE + _TOKEN_ENDTEXT; |
+ bool get isSimplePath => hasOnePath && |
+ _tokens[_TOKEN_TEXT] == '' && _tokens[_TOKEN_SIZE + _TOKEN_TEXT] == ''; |
+ |
+ /** |
+ * [TEXT, (ONE_TIME?, PATH, DELEGATE_FN, TEXT)+] if there is at least one |
+ * mustache. |
+ */ |
+ final List _tokens; |
+ |
+ final bool onlyOneTime; |
+ |
+ // Dart note: I think this is cached in JavaScript to avoid an extra |
+ // allocation per template instance. Seems reasonable, so we do the same. |
+ Function _combinator; |
+ Function get combinator => _combinator; |
+ |
+ MustacheTokens._(this._tokens, this.onlyOneTime) { |
+ // Should be: [TEXT, (ONE_TIME?, PATH, DELEGATE_FN, TEXT)+]. |
+ assert((_tokens.length - _TOKEN_ENDTEXT) % _TOKEN_SIZE == 0); |
+ |
+ _combinator = hasOnePath ? _singleCombinator : _listCombinator; |
+ } |
+ |
+ int get length => _tokens.length ~/ _TOKEN_SIZE; |
+ |
+ /** |
+ * Gets the [i]th text entry. Note that [length] can be passed to get the |
+ * final text entry. |
+ */ |
+ String getText(int i) => _tokens[i * _TOKEN_SIZE + _TOKEN_TEXT]; |
+ |
+ /** Gets the oneTime flag for the [i]th token. */ |
+ bool getOneTime(int i) => _tokens[i * _TOKEN_SIZE + _TOKEN_ONETIME]; |
+ |
+ /** Gets the path for the [i]th token. */ |
+ PropertyPath getPath(int i) => _tokens[i * _TOKEN_SIZE + _TOKEN_PATH]; |
+ |
+ /** Gets the prepareBinding function for the [i]th token. */ |
+ Function getPrepareBinding(int i) => |
+ _tokens[i * _TOKEN_SIZE + _TOKEN_PREPAREFN]; |
+ |
+ |
+ /** |
+ * Parses {{ mustache }} bindings. |
+ * |
+ * Returns null if there are no matches. Otherwise returns the parsed tokens. |
+ */ |
+ static MustacheTokens parse(String s, String name, node, |
+ BindingDelegate delegate) { |
+ if (s == null || s.isEmpty) return null; |
+ |
+ var tokens = null; |
+ var length = s.length; |
+ var lastIndex = 0; |
+ var onlyOneTime = true; |
+ while (lastIndex < length) { |
+ var startIndex = s.indexOf('{{', lastIndex); |
+ var oneTimeStart = s.indexOf('[[', lastIndex); |
+ var oneTime = false; |
+ var terminator = '}}'; |
+ |
+ if (oneTimeStart >= 0 && |
+ (startIndex < 0 || oneTimeStart < startIndex)) { |
+ startIndex = oneTimeStart; |
+ oneTime = true; |
+ terminator = ']]'; |
+ } |
+ |
+ var endIndex = -1; |
+ if (startIndex >= 0) { |
+ endIndex = s.indexOf(terminator, startIndex + 2); |
+ } |
+ |
+ if (endIndex < 0) { |
+ if (tokens == null) return null; |
+ |
+ tokens.add(s.substring(lastIndex)); // TEXT |
+ break; |
+ } |
+ |
+ if (tokens == null) tokens = []; |
+ tokens.add(s.substring(lastIndex, startIndex)); // TEXT |
+ var pathString = s.substring(startIndex + 2, endIndex).trim(); |
+ tokens.add(oneTime); // ONETIME? |
+ onlyOneTime = onlyOneTime && oneTime; |
+ tokens.add(new PropertyPath(pathString)); // PATH |
+ var delegateFn = delegate == null ? null : |
+ delegate.prepareBinding(pathString, name, node); |
+ tokens.add(delegateFn); |
+ |
+ lastIndex = endIndex + 2; |
+ } |
+ |
+ if (lastIndex == length) tokens.add(''); |
+ |
+ return new MustacheTokens._(tokens, onlyOneTime); |
+ } |
+ |
+ |
+ // Dart note: split "combinator" into the single/list variants, so the |
+ // argument can be typed. |
+ String _singleCombinator(Object value) { |
+ if (value == null) value = ''; |
+ return '${getText(0)}$value${getText(length)}'; |
+ } |
+ |
+ String _listCombinator(List<Object> values) { |
+ var newValue = new StringBuffer(getText(0)); |
+ int len = this.length; |
+ for (var i = 0; i < len; i++) { |
+ var value = values[i]; |
+ if (value != null) newValue.write(value); |
+ newValue.write(getText(i + 1)); |
+ } |
+ return newValue.toString(); |
+ } |
+} |