| part of mustache; |
| |
| final RegExp _validTag = new RegExp(r'^[0-9a-zA-Z\_\-\.]+$'); |
| final RegExp _integerTag = new RegExp(r'^[0-9]+$'); |
| |
| const Object _noSuchProperty = const Object(); |
| |
| class _Renderer { |
| |
| _Renderer(this._root, |
| this._sink, |
| this._values, |
| List stack, |
| this._lenient, |
| this._htmlEscapeValues, |
| this._partialResolver, |
| this._templateName, |
| this._indent, |
| this._source, |
| this._delimiters) |
| : _stack = new List.from(stack); |
| |
| _Renderer.partial(_Renderer renderer, _Template partial, String indent) |
| : this(partial._root, |
| renderer._sink, |
| renderer._values, |
| renderer._stack, |
| renderer._lenient, |
| renderer._htmlEscapeValues, |
| renderer._partialResolver, |
| renderer._templateName, |
| renderer._indent + indent, |
| partial.source, |
| '{{ }}'); |
| |
| _Renderer.subtree(_Renderer renderer, _Node node, StringSink sink) |
| : this(node, |
| sink, |
| renderer._values, |
| renderer._stack, |
| renderer._lenient, |
| renderer._htmlEscapeValues, |
| renderer._partialResolver, |
| renderer._templateName, |
| renderer._indent, |
| renderer._source, |
| '{{ }}'); |
| |
| _Renderer.lambda( |
| _Renderer renderer, |
| _Node node, |
| String source, |
| String indent, |
| StringSink sink, |
| String delimiters) |
| : this(node, |
| sink, |
| renderer._values, |
| renderer._stack, |
| renderer._lenient, |
| renderer._htmlEscapeValues, |
| renderer._partialResolver, |
| renderer._templateName, |
| renderer._indent + indent, |
| source, |
| delimiters); |
| |
| final _SectionNode _root; |
| final StringSink _sink; |
| final _values; |
| final List _stack; |
| final bool _lenient; |
| final bool _htmlEscapeValues; |
| final PartialResolver _partialResolver; |
| final String _templateName; |
| final String _indent; |
| final String _source; |
| |
| String _delimiters; |
| |
| void render() { |
| if (_indent == null || _indent == '') { |
| _root.children.forEach((n) => n.render(this)); |
| } else { |
| _renderWithIndent(); |
| } |
| } |
| |
| void _renderWithIndent() { |
| // Special case to make sure there is not an extra indent after the last |
| // line in the partial file. |
| var nodes = _root.children; |
| if (nodes.isEmpty) return; |
| |
| _write(_indent); |
| |
| nodes.take(nodes.length - 1).map((n) => n.render(this)); |
| |
| var node = _root.children.last; |
| if (node is _TextNode) { |
| node.render(this, lastNode: true); |
| } else { |
| node.render(this); |
| } |
| } |
| |
| _write(Object output) => _sink.write(output.toString()); |
| |
| // Walks up the stack looking for the variable. |
| // Handles dotted names of the form "a.b.c". |
| _resolveValue(String name) { |
| if (name == '.') { |
| return _stack.last; |
| } |
| var parts = name.split('.'); |
| var object = _noSuchProperty; |
| for (var o in _stack.reversed) { |
| object = _getNamedProperty(o, parts[0]); |
| if (object != _noSuchProperty) { |
| break; |
| } |
| } |
| for (int i = 1; i < parts.length; i++) { |
| if (object == null || object == _noSuchProperty) { |
| return _noSuchProperty; |
| } |
| object = _getNamedProperty(object, parts[i]); |
| } |
| return object; |
| } |
| |
| // Returns the property of the given object by name. For a map, |
| // which contains the key name, this is object[name]. For other |
| // objects, this is object.name or object.name(). If no property |
| // by the given name exists, this method returns noSuchProperty. |
| _getNamedProperty(object, name) { |
| |
| if (object is Map && object.containsKey(name)) |
| return object[name]; |
| |
| if (object is List && _integerTag.hasMatch(name)) |
| return object[int.parse(name)]; |
| |
| if (_lenient && !_validTag.hasMatch(name)) |
| return _noSuchProperty; |
| |
| var instance = reflect(object); |
| var field = instance.type.instanceMembers[new Symbol(name)]; |
| if (field == null) return _noSuchProperty; |
| |
| var invocation = null; |
| if ((field is VariableMirror) || ((field is MethodMirror) && (field.isGetter))) { |
| invocation = instance.getField(field.simpleName); |
| } else if ((field is MethodMirror) && (field.parameters.length == 0)) { |
| invocation = instance.invoke(field.simpleName, []); |
| } |
| if (invocation == null) { |
| return _noSuchProperty; |
| } |
| return invocation.reflectee; |
| } |
| |
| void _renderSectionWithValue(node, value) { |
| _stack.add(value); |
| node.children.forEach((n) => n.render(this)); |
| _stack.removeLast(); |
| } |
| |
| String _renderSubtree(node) { |
| var sink = new StringBuffer(); |
| var renderer = new _Renderer.subtree(this, node, sink); |
| renderer.render(); |
| return sink.toString(); |
| } |
| |
| static const Map<String,String> _htmlEscapeMap = const { |
| _AMP: '&', |
| _LT: '<', |
| _GT: '>', |
| _QUOTE: '"', |
| _APOS: ''', |
| _FORWARD_SLASH: '/' |
| }; |
| |
| String _htmlEscape(String s) { |
| |
| var buffer = new StringBuffer(); |
| int startIndex = 0; |
| int i = 0; |
| for (int c in s.runes) { |
| if (c == _AMP |
| || c == _LT |
| || c == _GT |
| || c == _QUOTE |
| || c == _APOS |
| || c == _FORWARD_SLASH) { |
| buffer.write(s.substring(startIndex, i)); |
| buffer.write(_htmlEscapeMap[c]); |
| startIndex = i + 1; |
| } |
| i++; |
| } |
| buffer.write(s.substring(startIndex)); |
| return buffer.toString(); |
| } |
| |
| TemplateException _error(String message, _Node node) |
| => new _TemplateException(message, _templateName, _source, node.start); |
| } |