OLD | NEW |
(Empty) | |
| 1 part of mustache; |
| 2 |
| 3 final Object _NO_SUCH_PROPERTY = new Object(); |
| 4 |
| 5 final RegExp _validTag = new RegExp(r'^[0-9a-zA-Z\_\-\.]+$'); |
| 6 final RegExp _integerTag = new RegExp(r'^[0-9]+$'); |
| 7 |
| 8 Template _parse(String source, {bool lenient : false}) { |
| 9 var tokens = _scan(source, lenient); |
| 10 var ast = _parseTokens(tokens, lenient); |
| 11 return new _Template(ast, lenient); |
| 12 } |
| 13 |
| 14 _Node _parseTokens(List<_Token> tokens, bool lenient) { |
| 15 var stack = new List<_Node>()..add(new _Node(_OPEN_SECTION, 'root', 0, 0
)); |
| 16 for (var t in tokens) { |
| 17 if (t.type == _TEXT || t.type == _VARIABLE || t.type == _UNESC_V
ARIABLE) { |
| 18 if (t.type == _VARIABLE || t.type == _UNESC_VARIABLE) |
| 19 _checkTagChars(t, lenient); |
| 20 stack.last.children.add(new _Node.fromToken(t)); |
| 21 |
| 22 } else if (t.type == _OPEN_SECTION || t.type == _OPEN_INV_SECTIO
N) { |
| 23 _checkTagChars(t, lenient); |
| 24 var child = new _Node.fromToken(t); |
| 25 stack.last.children.add(child); |
| 26 stack.add(child); |
| 27 |
| 28 } else if (t.type == _CLOSE_SECTION) { |
| 29 _checkTagChars(t, lenient); |
| 30 |
| 31 if (stack.last.value != t.value) { |
| 32 throw new MustacheFormatException('Mismatched ta
g, ' |
| 33 "expected: '${stack.last.value}', " |
| 34 "was: '${t.value}', " |
| 35 'at: ${t.line}:${t.column}.', t.line, t.
column); |
| 36 } |
| 37 |
| 38 stack.removeLast(); |
| 39 |
| 40 } else if (t.type == _COMMENT) { |
| 41 // Do nothing |
| 42 |
| 43 } else { |
| 44 throw new UnimplementedError(); |
| 45 } |
| 46 } |
| 47 |
| 48 return stack.last; |
| 49 } |
| 50 |
| 51 _checkTagChars(_Token t, bool lenient) { |
| 52 if (!lenient && !_validTag.hasMatch(t.value)) { |
| 53 throw new MustacheFormatException( |
| 54 'Tag contained invalid characters in name, ' |
| 55 'allowed: 0-9, a-z, A-Z, underscore, and minus,
' |
| 56 'at: ${t.line}:${t.column}.', t.line, t.column); |
| 57 } |
| 58 } |
| 59 |
| 60 class _Template implements Template { |
| 61 _Template(this._root, this._lenient) { |
| 62 _htmlEscapeMap[_AMP] = '&'; |
| 63 _htmlEscapeMap[_LT] = '<'; |
| 64 _htmlEscapeMap[_GT] = '>'; |
| 65 _htmlEscapeMap[_QUOTE] = '"'; |
| 66 _htmlEscapeMap[_APOS] = '''; |
| 67 _htmlEscapeMap[_FORWARD_SLASH] = '/'; |
| 68 } |
| 69 |
| 70 final _Node _root; |
| 71 final List _stack = new List(); |
| 72 final Map _htmlEscapeMap = new Map<int, String>(); |
| 73 final bool _lenient; |
| 74 |
| 75 bool _htmlEscapeValues; |
| 76 StringSink _sink; |
| 77 |
| 78 String renderString(values, {bool lenient : false, bool htmlEscapeValues
: true}) { |
| 79 var buf = new StringBuffer(); |
| 80 render(values, buf, lenient: lenient, htmlEscapeValues: htmlEsca
peValues); |
| 81 return buf.toString(); |
| 82 } |
| 83 |
| 84 void render(values, StringSink sink, {bool lenient : false, bool htmlEsc
apeValues : true}) { |
| 85 _sink = sink; |
| 86 _htmlEscapeValues = htmlEscapeValues; |
| 87 _stack.clear(); |
| 88 _stack.add(values); |
| 89 _root.children.forEach(_renderNode); |
| 90 _sink = null; |
| 91 } |
| 92 |
| 93 _write(String output) => _sink.write(output); |
| 94 |
| 95 _renderNode(node) { |
| 96 switch (node.type) { |
| 97 case _TEXT: |
| 98 _renderText(node); |
| 99 break; |
| 100 case _VARIABLE: |
| 101 _renderVariable(node); |
| 102 break; |
| 103 case _UNESC_VARIABLE: |
| 104 _renderVariable(node, escape: false); |
| 105 break; |
| 106 case _OPEN_SECTION: |
| 107 _renderSection(node); |
| 108 break; |
| 109 case _OPEN_INV_SECTION: |
| 110 _renderInvSection(node); |
| 111 break; |
| 112 case _COMMENT: |
| 113 break; // Do nothing. |
| 114 default: |
| 115 throw new UnimplementedError(); |
| 116 } |
| 117 } |
| 118 |
| 119 _renderText(node) { |
| 120 _write(node.value); |
| 121 } |
| 122 |
| 123 // Walks up the stack looking for the variable. |
| 124 // Handles dotted names of the form "a.b.c". |
| 125 _resolveValue(String name) { |
| 126 if (name == '.') { |
| 127 return _stack.last; |
| 128 } |
| 129 var parts = name.split('.'); |
| 130 var object = _NO_SUCH_PROPERTY; |
| 131 for (var o in _stack.reversed) { |
| 132 object = _getNamedProperty(o, parts[0]); |
| 133 if (object != _NO_SUCH_PROPERTY) { |
| 134 break; |
| 135 } |
| 136 } |
| 137 for (int i = 1; i < parts.length; i++) { |
| 138 if (object == null || object == _NO_SUCH_PROPERTY) { |
| 139 return _NO_SUCH_PROPERTY; |
| 140 } |
| 141 object = _getNamedProperty(object, parts[i]); |
| 142 } |
| 143 return object; |
| 144 } |
| 145 |
| 146 // Returns the property of the given object by name. For a map, |
| 147 // which contains the key name, this is object[name]. For other |
| 148 // objects, this is object.name or object.name(). If no property |
| 149 // by the given name exists, this method returns _NO_SUCH_PROPERTY. |
| 150 _getNamedProperty(object, name) { |
| 151 var property = null; |
| 152 if (object is Map && object.containsKey(name)) { |
| 153 return object[name]; |
| 154 } |
| 155 if (object is List && _integerTag.hasMatch(name)) { |
| 156 return object[int.parse(name)]; |
| 157 } |
| 158 if (_lenient && !_validTag.hasMatch(name)) { |
| 159 return _NO_SUCH_PROPERTY; |
| 160 } |
| 161 var instance = reflect(object); |
| 162 var field = instance.type.instanceMembers[new Symbol(name)]; |
| 163 if (field == null) { |
| 164 return _NO_SUCH_PROPERTY; |
| 165 } |
| 166 var invocation = null; |
| 167 if ((field is VariableMirror) || ((field is MethodMirror) && (fi
eld.isGetter))) { |
| 168 invocation = instance.getField(field.simpleName); |
| 169 } else if ((field is MethodMirror) && (field.parameters.length =
= 0)) { |
| 170 invocation = instance.invoke(field.simpleName, []); |
| 171 } |
| 172 if (invocation == null) { |
| 173 return _NO_SUCH_PROPERTY; |
| 174 } |
| 175 return invocation.reflectee; |
| 176 } |
| 177 |
| 178 _renderVariable(node, {bool escape : true}) { |
| 179 final value = _resolveValue(node.value); |
| 180 if (value == _NO_SUCH_PROPERTY) { |
| 181 if (!_lenient) |
| 182 throw new MustacheFormatException( |
| 183 'Value was missing, ' |
| 184 'variable: ${node.value}, ' |
| 185 'at: ${node.line}:${node.column}.', node
.line, node.column); |
| 186 } else { |
| 187 var valueString = (value == null) ? '' : value.toString(
); |
| 188 var output = !escape || !_htmlEscapeValues |
| 189 ? valueString |
| 190 : _htmlEscape(valueString); |
| 191 _write(output); |
| 192 } |
| 193 } |
| 194 |
| 195 _renderSectionWithValue(node, value) { |
| 196 _stack.add(value); |
| 197 node.children.forEach(_renderNode); |
| 198 _stack.removeLast(); |
| 199 } |
| 200 |
| 201 _renderSection(node) { |
| 202 final value = _resolveValue(node.value); |
| 203 if (value == null) { |
| 204 // Do nothing. |
| 205 } else if (value is Iterable) { |
| 206 value.forEach((v) => _renderSectionWithValue(node, v)); |
| 207 } else if (value is Map) { |
| 208 _renderSectionWithValue(node, value); |
| 209 } else if (value == true) { |
| 210 _renderSectionWithValue(node, value); |
| 211 } else if (value == false) { |
| 212 // Do nothing. |
| 213 } else if (value == _NO_SUCH_PROPERTY) { |
| 214 if (!_lenient) |
| 215 throw new MustacheFormatException( |
| 216 'Value was missing, ' |
| 217 'section: ${node.value}, ' |
| 218 'at: ${node.line}:${node.column}.', node
.line, node.column); |
| 219 } else { |
| 220 throw new MustacheFormatException( |
| 221 'Invalid value type for section, ' |
| 222 'section: ${node.value}, ' |
| 223 'type: ${value.runtimeType}, ' |
| 224 'at: ${node.line}:${node.column}.', node.line, n
ode.column); |
| 225 } |
| 226 } |
| 227 |
| 228 _renderInvSection(node) { |
| 229 final value = _resolveValue(node.value); |
| 230 if (value == null) { |
| 231 _renderSectionWithValue(node, null); |
| 232 } else if ((value is Iterable && value.isEmpty) || value == fals
e) { |
| 233 _renderSectionWithValue(node, value); |
| 234 } else if (value == true || value is Map || value is Iterable) { |
| 235 // Do nothing. |
| 236 } else if (value == _NO_SUCH_PROPERTY) { |
| 237 if (_lenient) { |
| 238 _renderSectionWithValue(node, null); |
| 239 } else { |
| 240 throw new MustacheFormatException( |
| 241 'Value was missing, ' |
| 242 'inverse-section: ${node.value}, ' |
| 243 'at: ${node.line}:${node.column}.', node
.line, node.column); |
| 244 } |
| 245 } else { |
| 246 throw new MustacheFormatException( |
| 247 'Invalid value type for inverse section, ' |
| 248 'section: ${node.value}, ' |
| 249 'type: ${value.runtimeType}, ' |
| 250 'at: ${node.line}:${node.column}.', node.line, n
ode.column); |
| 251 } |
| 252 } |
| 253 |
| 254 String _htmlEscape(String s) { |
| 255 var buffer = new StringBuffer(); |
| 256 int startIndex = 0; |
| 257 int i = 0; |
| 258 for (int c in s.runes) { |
| 259 if (c == _AMP |
| 260 || c == _LT |
| 261 || c == _GT |
| 262 || c == _QUOTE |
| 263 || c == _APOS |
| 264 || c == _FORWARD_SLASH) { |
| 265 buffer.write(s.substring(startIndex, i)); |
| 266 buffer.write(_htmlEscapeMap[c]); |
| 267 startIndex = i + 1; |
| 268 } |
| 269 i++; |
| 270 } |
| 271 buffer.write(s.substring(startIndex)); |
| 272 return buffer.toString(); |
| 273 } |
| 274 } |
| 275 |
| 276 _visit(_Node root, visitor(_Node n)) { |
| 277 var _stack = new List<_Node>()..add(root); |
| 278 while (!_stack.isEmpty) { |
| 279 var node = _stack.removeLast(); |
| 280 _stack.addAll(node.children); |
| 281 visitor(node); |
| 282 } |
| 283 } |
| 284 |
| 285 class _Node { |
| 286 _Node(this.type, this.value, this.line, this.column); |
| 287 _Node.fromToken(_Token token) |
| 288 : type = token.type, |
| 289 value = token.value, |
| 290 line = token.line, |
| 291 column = token.column; |
| 292 final int type; |
| 293 final String value; |
| 294 final int line; |
| 295 final int column; |
| 296 final List<_Node> children = new List<_Node>(); |
| 297 String toString() => '_Node: ${tokenTypeString(type)}'; |
| 298 } |
OLD | NEW |