blob: a6ca9f128f6cf04cf344288be19e6a7750ccb915 [file] [log] [blame]
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: '&amp;',
_LT: '&lt;',
_GT: '&gt;',
_QUOTE: '&quot;',
_APOS: '&#x27;',
_FORWARD_SLASH: '&#x2F;'
};
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);
}