OLD | NEW |
(Empty) | |
| 1 // Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file |
| 2 // for details. All rights reserved. Use of this source code is governed by a |
| 3 // BSD-style license that can be found in the LICENSE file. |
| 4 |
| 5 library template_binding.src.mustache_tokens; |
| 6 |
| 7 import 'package:observe/observe.dart'; |
| 8 |
| 9 // Dart note: this was added to decouple the parse function below from the rest |
| 10 // of template_binding. This allows using this code in command-line tools as |
| 11 // well. |
| 12 typedef Function DelegateFunctionFactory(String pathString); |
| 13 |
| 14 /** |
| 15 * Represents a set of parsed tokens from a {{ mustache binding expression }}. |
| 16 * This can be created by calling [parse]. |
| 17 * |
| 18 * For performance reasons the data is stored in one linear array in [_tokens]. |
| 19 * This class wraps that array and provides accessors in an attempt to make the |
| 20 * pattern easier to understand. See [length] and [getText] for example. |
| 21 */ |
| 22 class MustacheTokens { |
| 23 // Constants for indexing into the exploded structs in [_tokens] . |
| 24 static const _TOKEN_TEXT = 0; |
| 25 static const _TOKEN_ONETIME = 1; |
| 26 static const _TOKEN_PATH = 2; |
| 27 static const _TOKEN_PREPAREFN = 3; |
| 28 static const _TOKEN_SIZE = 4; |
| 29 |
| 30 // There is 1 extra entry for the end text. |
| 31 static const _TOKEN_ENDTEXT = 1; |
| 32 |
| 33 bool get hasOnePath => _tokens.length == _TOKEN_SIZE + _TOKEN_ENDTEXT; |
| 34 bool get isSimplePath => hasOnePath && |
| 35 _tokens[_TOKEN_TEXT] == '' && _tokens[_TOKEN_SIZE + _TOKEN_TEXT] == ''; |
| 36 |
| 37 /** |
| 38 * [TEXT, (ONE_TIME?, PATH, DELEGATE_FN, TEXT)+] if there is at least one |
| 39 * mustache. |
| 40 */ |
| 41 final List _tokens; |
| 42 |
| 43 final bool onlyOneTime; |
| 44 |
| 45 // Dart note: I think this is cached in JavaScript to avoid an extra |
| 46 // allocation per template instance. Seems reasonable, so we do the same. |
| 47 Function _combinator; |
| 48 Function get combinator => _combinator; |
| 49 |
| 50 MustacheTokens._(this._tokens, this.onlyOneTime) { |
| 51 // Should be: [TEXT, (ONE_TIME?, PATH, DELEGATE_FN, TEXT)+]. |
| 52 assert((_tokens.length - _TOKEN_ENDTEXT) % _TOKEN_SIZE == 0); |
| 53 |
| 54 _combinator = hasOnePath ? _singleCombinator : _listCombinator; |
| 55 } |
| 56 |
| 57 int get length => _tokens.length ~/ _TOKEN_SIZE; |
| 58 |
| 59 /** |
| 60 * Gets the [i]th text entry. Note that [length] can be passed to get the |
| 61 * final text entry. |
| 62 */ |
| 63 String getText(int i) => _tokens[i * _TOKEN_SIZE + _TOKEN_TEXT]; |
| 64 |
| 65 /** Gets the oneTime flag for the [i]th token. */ |
| 66 bool getOneTime(int i) => _tokens[i * _TOKEN_SIZE + _TOKEN_ONETIME]; |
| 67 |
| 68 /** Gets the path for the [i]th token. */ |
| 69 PropertyPath getPath(int i) => _tokens[i * _TOKEN_SIZE + _TOKEN_PATH]; |
| 70 |
| 71 /** Gets the prepareBinding function for the [i]th token. */ |
| 72 Function getPrepareBinding(int i) => |
| 73 _tokens[i * _TOKEN_SIZE + _TOKEN_PREPAREFN]; |
| 74 |
| 75 |
| 76 /** |
| 77 * Parses {{ mustache }} bindings. |
| 78 * |
| 79 * Returns null if there are no matches. Otherwise returns the parsed tokens. |
| 80 */ |
| 81 static MustacheTokens parse(String s, [DelegateFunctionFactory fnFactory]) { |
| 82 if (s == null || s.isEmpty) return null; |
| 83 |
| 84 var tokens = null; |
| 85 var length = s.length; |
| 86 var lastIndex = 0; |
| 87 var onlyOneTime = true; |
| 88 while (lastIndex < length) { |
| 89 var startIndex = s.indexOf('{{', lastIndex); |
| 90 var oneTimeStart = s.indexOf('[[', lastIndex); |
| 91 var oneTime = false; |
| 92 var terminator = '}}'; |
| 93 |
| 94 if (oneTimeStart >= 0 && |
| 95 (startIndex < 0 || oneTimeStart < startIndex)) { |
| 96 startIndex = oneTimeStart; |
| 97 oneTime = true; |
| 98 terminator = ']]'; |
| 99 } |
| 100 |
| 101 var endIndex = -1; |
| 102 if (startIndex >= 0) { |
| 103 endIndex = s.indexOf(terminator, startIndex + 2); |
| 104 } |
| 105 |
| 106 if (endIndex < 0) { |
| 107 if (tokens == null) return null; |
| 108 |
| 109 tokens.add(s.substring(lastIndex)); // TEXT |
| 110 break; |
| 111 } |
| 112 |
| 113 if (tokens == null) tokens = []; |
| 114 tokens.add(s.substring(lastIndex, startIndex)); // TEXT |
| 115 var pathString = s.substring(startIndex + 2, endIndex).trim(); |
| 116 tokens.add(oneTime); // ONETIME? |
| 117 onlyOneTime = onlyOneTime && oneTime; |
| 118 var delegateFn = fnFactory == null ? null : fnFactory(pathString); |
| 119 // Don't try to parse the expression if there's a prepareBinding function |
| 120 if (delegateFn == null) { |
| 121 tokens.add(new PropertyPath(pathString)); // PATH |
| 122 } else { |
| 123 tokens.add(null); |
| 124 } |
| 125 tokens.add(delegateFn); // DELEGATE_FN |
| 126 |
| 127 lastIndex = endIndex + 2; |
| 128 } |
| 129 |
| 130 if (lastIndex == length) tokens.add(''); // TEXT |
| 131 |
| 132 return new MustacheTokens._(tokens, onlyOneTime); |
| 133 } |
| 134 |
| 135 |
| 136 // Dart note: split "combinator" into the single/list variants, so the |
| 137 // argument can be typed. |
| 138 String _singleCombinator(Object value) { |
| 139 if (value == null) value = ''; |
| 140 return '${getText(0)}$value${getText(length)}'; |
| 141 } |
| 142 |
| 143 String _listCombinator(List<Object> values) { |
| 144 var newValue = new StringBuffer(getText(0)); |
| 145 int len = this.length; |
| 146 for (var i = 0; i < len; i++) { |
| 147 var value = values[i]; |
| 148 if (value != null) newValue.write(value); |
| 149 newValue.write(getText(i + 1)); |
| 150 } |
| 151 return newValue.toString(); |
| 152 } |
| 153 } |
OLD | NEW |