Support arbitrary getters in templates via reflection
Based on the mustache4dart implementation.
https://github.com/valotas/mustache4dart/blob/ce0bc54960f5c54b734ac5b4731837c550f69ac6/lib/src/mustache_context.dart#L84
diff --git a/lib/template.dart b/lib/template.dart
index 8d0d69a..2f46e6b 100644
--- a/lib/template.dart
+++ b/lib/template.dart
@@ -126,27 +126,60 @@
}
var parts = name.split('.');
- var map = _stack
- .reversed
- .firstWhere(
- (v) => v is Map && v.containsKey(parts[0]),
- orElse: () => null);
- if (map == null)
- return null;
-
- var v;
- for (int i = 0; i < parts.length; i++) {
- v = map[parts[i]];
- if (v == null) {
- return null;
- } else if (v is Map) {
- map = v;
- } else if (i < parts.length - 1) {
- return null;
- }
- }
- return v;
+ // 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]];
+ }
+
+ for (int i = 1; i < parts.length; i++) {
+ if (object == null) {
+ return null;
+ }
+ 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().
+ _getNamedProperty(object, name) {
+ if (object is Map) {
+ return object[name];
+ }
+ var instance = reflect(object);
+ var field = instance.type.members[new Symbol(name)];
+ if (field == null) {
+ return null;
+ }
+ 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 null;
+ }
+ return invocation.reflectee;
}
_renderVariable(node, {bool escape : true}) {
@@ -173,7 +206,7 @@
_renderSection(node) {
final value = _resolveValue(node.value);
- if (value is Iterable) {
+ if (value is List) {
value.forEach((v) => _renderSectionWithValue(node, v));
} else if (value is Map) {
_renderSectionWithValue(node, value);
@@ -198,9 +231,9 @@
_renderInvSection(node) {
final value = _resolveValue(node.value);
- if ((value is Iterable && value.isEmpty) || value == false) {
+ if ((value is List && value.isEmpty) || value == false) {
_renderSectionWithValue(node, value);
- } else if (value == true || value is Map || value is Iterable) {
+ } else if (value == true || value is Map || value is List) {
// Do nothing.
} else if (value == null) {
if (_lenient) {