Attempt #2 at reflection
diff --git a/lib/template.dart b/lib/template.dart
index 2f46e6b..a38df32 100644
--- a/lib/template.dart
+++ b/lib/template.dart
@@ -1,5 +1,7 @@
part of mustache;
+final Object _NO_SUCH_PROPERTY = new Object();
+
final RegExp _validTag = new RegExp(r'^[0-9a-zA-Z\_\-\.]+$');
Template _parse(String source, {bool lenient : false}) {
@@ -15,7 +17,7 @@
if (t.type == _VARIABLE || t.type == _UNESC_VARIABLE)
_checkTagChars(t, lenient);
stack.last.children.add(new _Node.fromToken(t));
-
+
} else if (t.type == _OPEN_SECTION || t.type == _OPEN_INV_SECTION) {
_checkTagChars(t, lenient);
var child = new _Node.fromToken(t);
@@ -33,7 +35,7 @@
}
stack.removeLast();
-
+
} else if (t.type == _COMMENT) {
// Do nothing
@@ -51,7 +53,7 @@
'Tag contained invalid characters in name, '
'allowed: 0-9, a-z, A-Z, underscore, and minus, '
'at: ${t.line}:${t.column}.', t.line, t.column);
- }
+ }
}
class _Template implements Template {
@@ -68,7 +70,7 @@
final List _stack = new List();
final Map _htmlEscapeMap = new Map<int, String>();
final bool _lenient;
-
+
bool _htmlEscapeValues;
StringSink _sink;
@@ -82,7 +84,7 @@
_sink = sink;
_htmlEscapeValues = htmlEscapeValues;
_stack.clear();
- _stack.add(values);
+ _stack.add(values);
_root.children.forEach(_renderNode);
_sink = null;
}
@@ -120,80 +122,68 @@
// Walks up the stack looking for the variable.
// Handles dotted names of the form "a.b.c".
_resolveValue(String name) {
- // Handle implicit iterators
if (name == '.') {
return _stack.last;
}
-
var parts = name.split('.');
-
- // Optimistically assume all names are map keys at first,
- // since reflection is more expensive than map lookups.
- // This strategy should avoid penalizing users who do only
- // use maps and avoid relying on reflection.
-
- var object = _stack
- .reversed
- .firstWhere(
- (v) => v is Map && v.containsKey(parts[0]),
- orElse: () => null);
-
- if (object == null) {
- object = _stack
- .reversed
- .firstWhere(
- (v) => (object = _getNamedProperty(v, parts[0])) != null,
- orElse: () => null);
- } else {
- object = object[parts[0]];
+ var object = _NO_SUCH_PROPERTY;
+ for (var o in _stack.reversed) {
+ object = _getNamedProperty(o, parts[0]);
+ if (object != _NO_SUCH_PROPERTY) {
+ break;
+ }
}
-
for (int i = 1; i < parts.length; i++) {
- if (object == null) {
- return null;
+ if (object == null || object == _NO_SUCH_PROPERTY) {
+ return _NO_SUCH_PROPERTY;
}
object = _getNamedProperty(object, parts[i]);
}
-
return object;
}
- // Returns the property of the given object by name. For a map,
- // this is object[name]. For other objects, this is object.name
- // or object.name().
+ // 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 _NO_SUCH_PROPERTY.
_getNamedProperty(object, name) {
- if (object is Map) {
+ var property = null;
+ if (object is Map && object.containsKey(name)) {
return object[name];
}
+ if (_lenient && !_validTag.hasMatch(name)) {
+ return _NO_SUCH_PROPERTY;
+ }
var instance = reflect(object);
var field = instance.type.members[new Symbol(name)];
if (field == null) {
- return null;
+ return _NO_SUCH_PROPERTY;
}
var invocation = null;
- if (field is VariableMirror || field is MethodMirror && field.isGetter) {
+ if ((field is VariableMirror) || ((field is MethodMirror) && (field.isGetter))) {
invocation = instance.getField(field.simpleName);
- } else if (field is MethodMirror && field.parameters.length == 0) {
+ } else if ((field is MethodMirror) && (field.parameters.length == 0)) {
invocation = instance.invoke(field.simpleName, []);
}
if (invocation == null) {
- return null;
+ return _NO_SUCH_PROPERTY;
}
return invocation.reflectee;
}
_renderVariable(node, {bool escape : true}) {
final value = _resolveValue(node.value);
- if (value == null) {
+ if (value == _NO_SUCH_PROPERTY) {
if (!_lenient)
throw new MustacheFormatException(
- 'Value was null or missing, '
+ 'Value was missing, '
'variable: ${node.value}, '
'at: ${node.line}:${node.column}.', node.line, node.column);
} else {
- var output = !escape || !_htmlEscapeValues
- ? value.toString()
- : _htmlEscape(value.toString());
+ var valueString = (value == null) ? '' : value.toString();
+ var output = !escape || !_htmlEscapeValues
+ ? valueString
+ : _htmlEscape(valueString);
_write(output);
}
}
@@ -206,7 +196,9 @@
_renderSection(node) {
final value = _resolveValue(node.value);
- if (value is List) {
+ if (value == null) {
+ // Do nothing.
+ } else if (value is Iterable) {
value.forEach((v) => _renderSectionWithValue(node, v));
} else if (value is Map) {
_renderSectionWithValue(node, value);
@@ -214,10 +206,10 @@
_renderSectionWithValue(node, value);
} else if (value == false) {
// Do nothing.
- } else if (value == null) {
+ } else if (value == _NO_SUCH_PROPERTY) {
if (!_lenient)
throw new MustacheFormatException(
- 'Value was null or missing, '
+ 'Value was missing, '
'section: ${node.value}, '
'at: ${node.line}:${node.column}.', node.line, node.column);
} else {
@@ -231,16 +223,18 @@
_renderInvSection(node) {
final value = _resolveValue(node.value);
- if ((value is List && value.isEmpty) || value == false) {
+ if (value == null) {
+ _renderSectionWithValue(node, null);
+ } else if ((value is Iterable && value.isEmpty) || value == false) {
_renderSectionWithValue(node, value);
- } else if (value == true || value is Map || value is List) {
+ } else if (value == true || value is Map || value is Iterable) {
// Do nothing.
- } else if (value == null) {
+ } else if (value == _NO_SUCH_PROPERTY) {
if (_lenient) {
- _renderSectionWithValue(node, value);
+ _renderSectionWithValue(node, null);
} else {
throw new MustacheFormatException(
- 'Value was null or missing, '
+ 'Value was missing, '
'inverse-section: ${node.value}, '
'at: ${node.line}:${node.column}.', node.line, node.column);
}
@@ -257,7 +251,7 @@
var buffer = new StringBuffer();
int startIndex = 0;
int i = 0;
- for (int c in s.runes) {
+ for (int c in s.runes) {
if (c == _AMP
|| c == _LT
|| c == _GT
@@ -270,7 +264,7 @@
}
i++;
}
- buffer.write(s.substring(startIndex));
+ buffer.write(s.substring(startIndex));
return buffer.toString();
}
}
@@ -288,9 +282,9 @@
_Node(this.type, this.value, this.line, this.column);
_Node.fromToken(_Token token)
: type = token.type,
- value = token.value,
- line = token.line,
- column = token.column;
+ value = token.value,
+ line = token.line,
+ column = token.column;
final int type;
final String value;
final int line;