Move classes into separate files
diff --git a/lib/mustache.dart b/lib/mustache.dart
index d8ef0ae..17a8848 100644
--- a/lib/mustache.dart
+++ b/lib/mustache.dart
@@ -5,8 +5,12 @@
part 'src/char_reader.dart';
part 'src/lambda_context.dart';
+part 'src/node.dart';
+part 'src/parse.dart';
+part 'src/renderer.dart';
part 'src/scanner.dart';
part 'src/template.dart';
+part 'src/token.dart';
/// [Mustache template documentation](http://mustache.github.com/mustache.5.html)
diff --git a/lib/src/node.dart b/lib/src/node.dart
new file mode 100644
index 0000000..47f468d
--- /dev/null
+++ b/lib/src/node.dart
@@ -0,0 +1,26 @@
+part of mustache;
+
+class _Node {
+
+ _Node(this.type, this.value, this.line, this.column, {this.indent});
+
+ _Node.fromToken(_Token token)
+ : type = token.type,
+ value = token.value,
+ line = token.line,
+ column = token.column,
+ indent = token.indent;
+
+ final int type;
+ final String value;
+ final int line;
+ final int column;
+ final String indent;
+ final List<_Node> children = new List<_Node>();
+
+ //TODO ideally these could be made final.
+ int start;
+ int end;
+
+ String toString() => '_Node: ${_tokenTypeString(type)}';
+}
diff --git a/lib/src/parse.dart b/lib/src/parse.dart
new file mode 100644
index 0000000..d4dcccf
--- /dev/null
+++ b/lib/src/parse.dart
@@ -0,0 +1,66 @@
+part of mustache;
+
+final RegExp _validTag = new RegExp(r'^[0-9a-zA-Z\_\-\.]+$');
+final RegExp _integerTag = new RegExp(r'^[0-9]+$');
+
+_Node _parse(String source, bool lenient, String templateName,
+ Delimiters delimiters) {
+ if (source == null) throw new ArgumentError.notNull('Template source');
+ var tokens = _scan(source, lenient, delimiters);
+ var ast = _parseTokens(tokens, lenient, templateName);
+ return ast;
+}
+
+_Node _parseTokens(List<_Token> tokens, bool lenient, String templateName) {
+
+ var stack = new List<_Node>()..add(new _Node(_OPEN_SECTION, 'root', 0, 0));
+
+ for (var t in tokens) {
+ if (const [_TEXT, _VARIABLE, _UNESC_VARIABLE, _PARTIAL].contains(t.type)) {
+ if (t.type == _VARIABLE || t.type == _UNESC_VARIABLE)
+ _checkTagChars(t, lenient, templateName);
+ stack.last.children.add(new _Node.fromToken(t));
+
+ } else if (t.type == _OPEN_SECTION || t.type == _OPEN_INV_SECTION) {
+ _checkTagChars(t, lenient, templateName);
+ var child = new _Node.fromToken(t);
+ child.start = t.offset;
+ stack.last.children.add(child);
+ stack.add(child);
+
+ } else if (t.type == _CLOSE_SECTION) {
+ _checkTagChars(t, lenient, templateName);
+
+ if (stack.last.value != t.value) {
+ throw new TemplateException(
+ "Mismatched tag, expected: '${stack.last.value}', was: '${t.value}'",
+ templateName, t.line, t.column);
+ }
+
+ stack.last.end = t.offset;
+
+ stack.removeLast();
+
+ } else if (t.type == _CHANGE_DELIMITER) {
+ stack.last.children.add(new _Node.fromToken(t));
+
+ } else if (t.type == _COMMENT) {
+ // Do nothing
+
+ } else {
+ //FIXME Use switch with enums so this becomes a compile time error.
+ throw new UnimplementedError();
+ }
+ }
+
+ return stack.last;
+}
+
+_checkTagChars(_Token t, bool lenient, String templateName) {
+ if (!lenient && !_validTag.hasMatch(t.value)) {
+ throw new TemplateException(
+ 'Tag contained invalid characters in name, '
+ 'allowed: 0-9, a-z, A-Z, underscore, and minus',
+ templateName, t.line, t.column);
+ }
+}
diff --git a/lib/src/renderer.dart b/lib/src/renderer.dart
new file mode 100644
index 0000000..725db8b
--- /dev/null
+++ b/lib/src/renderer.dart
@@ -0,0 +1,370 @@
+part of mustache;
+
+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 _Node _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(_renderNode);
+ } else {
+ _renderWithIndent();
+ }
+ }
+
+ void _renderWithIndent() {
+ // Special case to make sure there is not an extra indent after the last
+ // line in the partial file.
+ if (_root.children.isEmpty) return;
+
+ _write(_indent);
+
+ for (int i = 0; i < _root.children.length - 1; i++) {
+ _renderNode(_root.children[i]);
+ }
+
+ var node = _root.children.last;
+ if (node.type != _TEXT) {
+ _renderNode(node);
+ } else {
+ _renderText(node, lastNode: true);
+ }
+ }
+
+ _write(Object output) => _sink.write(output.toString());
+
+ _renderNode(_Node node) {
+ switch (node.type) {
+ case _TEXT:
+ _renderText(node);
+ break;
+ case _VARIABLE:
+ _renderVariable(node);
+ break;
+ case _UNESC_VARIABLE:
+ _renderVariable(node, escape: false);
+ break;
+ case _OPEN_SECTION:
+ _renderSection(node);
+ break;
+ case _OPEN_INV_SECTION:
+ _renderInvSection(node);
+ break;
+ case _PARTIAL:
+ _renderPartial(node);
+ break;
+ case _COMMENT:
+ break; // Do nothing.
+ case _CHANGE_DELIMITER:
+ _delimiters = node.value;
+ break;
+ default:
+ throw new UnimplementedError();
+ }
+ }
+
+ _renderText(_Node node, {bool lastNode: false}) {
+ var s = node.value;
+ if (s == '') return;
+ if (_indent == null || _indent == '') {
+ _write(s);
+ } else if (lastNode && s.runes.last == _NEWLINE) {
+ s = s.substring(0, s.length - 1);
+ _write(s.replaceAll('\n', '\n$_indent'));
+ _write('\n');
+ } else {
+ _write(s.replaceAll('\n', '\n$_indent'));
+ }
+ }
+
+ // 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) {
+
+ var property = null;
+ 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;
+ }
+
+ _renderVariable(_Node node, {bool escape : true}) {
+ var value = _resolveValue(node.value);
+
+ if (value is Function) {
+ var context = new _LambdaContext(node, this, isSection: false);
+ value = value(context);
+ context.close();
+ }
+
+ if (value == _noSuchProperty) {
+ if (!_lenient)
+ throw new TemplateException(
+ 'Value was missing, variable: ${node.value}',
+ _templateName, node.line, node.column);
+ } else {
+ var valueString = (value == null) ? '' : value.toString();
+ var output = !escape || !_htmlEscapeValues
+ ? valueString
+ : _htmlEscape(valueString);
+ _write(output);
+ }
+ }
+
+ _renderSectionWithValue(node, value) {
+ _stack.add(value);
+ node.children.forEach(_renderNode);
+ _stack.removeLast();
+ }
+
+ String _renderSubtree(node) {
+ var sink = new StringBuffer();
+ var renderer = new _Renderer.subtree(this, node, sink);
+ renderer.render();
+ return sink.toString();
+ }
+
+ _renderSection(_Node node) {
+ var value = _resolveValue(node.value);
+
+ if (value == null) {
+ // Do nothing.
+
+ } else if (value is Iterable) {
+ value.forEach((v) => _renderSectionWithValue(node, v));
+
+ } else if (value is Map) {
+ _renderSectionWithValue(node, value);
+
+ } else if (value == true) {
+ _renderSectionWithValue(node, value);
+
+ } else if (value == false) {
+ // Do nothing.
+
+ } else if (value == _noSuchProperty) {
+ if (!_lenient)
+ throw new TemplateException(
+ 'Value was missing, section: ${node.value}',
+ _templateName, node.line, node.column);
+
+ } else if (value is Function) {
+ var context = new _LambdaContext(node, this, isSection: true);
+ var output = value(context);
+ context.close();
+ _write(output);
+
+ } else {
+ throw new TemplateException(
+ 'Invalid value type for section, '
+ 'section: ${node.value}, '
+ 'type: ${value.runtimeType}',
+ _templateName, node.line, node.column);
+ }
+ }
+
+ _renderInvSection(node) {
+ var value = _resolveValue(node.value);
+
+ 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 Iterable) {
+ // Do nothing.
+
+ } else if (value == _noSuchProperty) {
+ if (_lenient) {
+ _renderSectionWithValue(node, null);
+ } else {
+ throw new TemplateException(
+ 'Value was missing, inverse-section: ${node.value}',
+ _templateName, node.line, node.column);
+ }
+
+ } else if (value is Function) {
+ var context = new _LambdaContext(node, this, isSection: true);
+ var output = value(context);
+ context.close();
+
+ //FIXME Poos. I have no idea what this really is for ?????
+ if (output == false) {
+ // FIXME not sure what to output here, result of function or template
+ // output?
+ _write(output);
+ }
+
+ } else {
+ throw new TemplateException(
+ 'Invalid value type for inverse section, '
+ 'section: ${node.value}, '
+ 'type: ${value.runtimeType}, ',
+ _templateName, node.line, node.column);
+ }
+ }
+
+ _renderPartial(_Node node) {
+ var partialName = node.value;
+ _Template template = _partialResolver == null
+ ? null
+ : _partialResolver(partialName);
+ if (template != null) {
+ var renderer = new _Renderer.partial(this, template, node.indent);
+ renderer.render();
+ } else if (_lenient) {
+ // do nothing
+ } else {
+ throw new TemplateException(
+ 'Partial not found: $partialName',
+ _templateName, node.line, node.column);
+ }
+ }
+
+ 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();
+ }
+}
diff --git a/lib/src/scanner.dart b/lib/src/scanner.dart
index 7edf81f..ba7670a 100644
--- a/lib/src/scanner.dart
+++ b/lib/src/scanner.dart
@@ -3,33 +3,6 @@
List<_Token> _scan(String source, bool lenient, Delimiters delimiters)
=> _trim(new _Scanner(source, null, delimiters).scan());
-//FIXME use enums
-const int _TEXT = 1;
-const int _VARIABLE = 2;
-const int _PARTIAL = 3;
-const int _OPEN_SECTION = 4;
-const int _OPEN_INV_SECTION = 5;
-const int _CLOSE_SECTION = 6;
-const int _COMMENT = 7;
-const int _UNESC_VARIABLE = 8;
-const int _WHITESPACE = 9; // Should be filtered out, before returned by scan.
-const int _LINE_END = 10; // Should be filtered out, before returned by scan.
-const int _CHANGE_DELIMITER = 11;
-
-_tokenTypeString(int type) => [
- '?',
- 'Text',
- 'Var',
- 'Par',
- 'Open',
- 'OpenInv',
- 'Close',
- 'Comment',
- 'UnescVar',
- 'Whitespace',
- 'LineEnd',
- 'ChangeDelimiter'][type];
-
const int _EOF = -1;
const int _TAB = 9;
const int _NEWLINE = 10;
@@ -128,23 +101,6 @@
return result;
}
-class _Token {
- _Token(this.type, this.value, this.line, this.column, {this.indent});
-
- final int type;
- final String value;
- final int line;
- final int column;
- final String indent;
-
- // Store offsets to extract text from source for lambdas.
- // Only used for section, inverse section and close section tags.
- int offset;
-
- toString() => "${_tokenTypeString(type)}: "
- "\"${value.replaceAll('\n', '\\n')}\" $line:$column";
-}
-
class _Scanner {
_Scanner(String source, [this._templateName, Delimiters initial])
diff --git a/lib/src/template.dart b/lib/src/template.dart
index 6172147..82d8fe8 100644
--- a/lib/src/template.dart
+++ b/lib/src/template.dart
@@ -1,72 +1,5 @@
part of mustache;
-const Object _noSuchProperty = const Object();
-
-final RegExp _validTag = new RegExp(r'^[0-9a-zA-Z\_\-\.]+$');
-final RegExp _integerTag = new RegExp(r'^[0-9]+$');
-
-_Node _parseTokens(List<_Token> tokens, bool lenient, String templateName) {
-
- var stack = new List<_Node>()..add(new _Node(_OPEN_SECTION, 'root', 0, 0));
-
- for (var t in tokens) {
- if (const [_TEXT, _VARIABLE, _UNESC_VARIABLE, _PARTIAL].contains(t.type)) {
- if (t.type == _VARIABLE || t.type == _UNESC_VARIABLE)
- _checkTagChars(t, lenient, templateName);
- stack.last.children.add(new _Node.fromToken(t));
-
- } else if (t.type == _OPEN_SECTION || t.type == _OPEN_INV_SECTION) {
- _checkTagChars(t, lenient, templateName);
- var child = new _Node.fromToken(t);
- child.start = t.offset;
- stack.last.children.add(child);
- stack.add(child);
-
- } else if (t.type == _CLOSE_SECTION) {
- _checkTagChars(t, lenient, templateName);
-
- if (stack.last.value != t.value) {
- throw new TemplateException(
- "Mismatched tag, expected: '${stack.last.value}', was: '${t.value}'",
- templateName, t.line, t.column);
- }
-
- stack.last.end = t.offset;
-
- stack.removeLast();
-
- } else if (t.type == _CHANGE_DELIMITER) {
- stack.last.children.add(new _Node.fromToken(t));
-
- } else if (t.type == _COMMENT) {
- // Do nothing
-
- } else {
- //FIXME Use switch with enums so this becomes a compile time error.
- throw new UnimplementedError();
- }
- }
-
- return stack.last;
-}
-
-_checkTagChars(_Token t, bool lenient, String templateName) {
- if (!lenient && !_validTag.hasMatch(t.value)) {
- throw new TemplateException(
- 'Tag contained invalid characters in name, '
- 'allowed: 0-9, a-z, A-Z, underscore, and minus',
- templateName, t.line, t.column);
- }
-}
-
-_Node _parse(String source, bool lenient, String templateName,
- Delimiters delimiters) {
- if (source == null) throw new ArgumentError.notNull('Template source');
- var tokens = _scan(source, lenient, delimiters);
- var ast = _parseTokens(tokens, lenient, templateName);
- return ast;
-}
-
class _Template implements Template {
_Template.fromSource(String source,
@@ -104,405 +37,3 @@
renderer.render();
}
}
-
-
-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 _Node _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(_renderNode);
- } else {
- _renderWithIndent();
- }
- }
-
- void _renderWithIndent() {
- // Special case to make sure there is not an extra indent after the last
- // line in the partial file.
- if (_root.children.isEmpty) return;
-
- _write(_indent);
-
- for (int i = 0; i < _root.children.length - 1; i++) {
- _renderNode(_root.children[i]);
- }
-
- var node = _root.children.last;
- if (node.type != _TEXT) {
- _renderNode(node);
- } else {
- _renderText(node, lastNode: true);
- }
- }
-
- _write(Object output) => _sink.write(output.toString());
-
- _renderNode(_Node node) {
- switch (node.type) {
- case _TEXT:
- _renderText(node);
- break;
- case _VARIABLE:
- _renderVariable(node);
- break;
- case _UNESC_VARIABLE:
- _renderVariable(node, escape: false);
- break;
- case _OPEN_SECTION:
- _renderSection(node);
- break;
- case _OPEN_INV_SECTION:
- _renderInvSection(node);
- break;
- case _PARTIAL:
- _renderPartial(node);
- break;
- case _COMMENT:
- break; // Do nothing.
- case _CHANGE_DELIMITER:
- _delimiters = node.value;
- break;
- default:
- throw new UnimplementedError();
- }
- }
-
- _renderText(_Node node, {bool lastNode: false}) {
- var s = node.value;
- if (s == '') return;
- if (_indent == null || _indent == '') {
- _write(s);
- } else if (lastNode && s.runes.last == _NEWLINE) {
- s = s.substring(0, s.length - 1);
- _write(s.replaceAll('\n', '\n$_indent'));
- _write('\n');
- } else {
- _write(s.replaceAll('\n', '\n$_indent'));
- }
- }
-
- // 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) {
-
- var property = null;
- 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;
- }
-
- _renderVariable(_Node node, {bool escape : true}) {
- var value = _resolveValue(node.value);
-
- if (value is Function) {
- var context = new _LambdaContext(node, this, isSection: false);
- value = value(context);
- context.close();
- }
-
- if (value == _noSuchProperty) {
- if (!_lenient)
- throw new TemplateException(
- 'Value was missing, variable: ${node.value}',
- _templateName, node.line, node.column);
- } else {
- var valueString = (value == null) ? '' : value.toString();
- var output = !escape || !_htmlEscapeValues
- ? valueString
- : _htmlEscape(valueString);
- _write(output);
- }
- }
-
- _renderSectionWithValue(node, value) {
- _stack.add(value);
- node.children.forEach(_renderNode);
- _stack.removeLast();
- }
-
- String _renderSubtree(node) {
- var sink = new StringBuffer();
- var renderer = new _Renderer.subtree(this, node, sink);
- renderer.render();
- return sink.toString();
- }
-
- _renderSection(_Node node) {
- var value = _resolveValue(node.value);
-
- if (value == null) {
- // Do nothing.
-
- } else if (value is Iterable) {
- value.forEach((v) => _renderSectionWithValue(node, v));
-
- } else if (value is Map) {
- _renderSectionWithValue(node, value);
-
- } else if (value == true) {
- _renderSectionWithValue(node, value);
-
- } else if (value == false) {
- // Do nothing.
-
- } else if (value == _noSuchProperty) {
- if (!_lenient)
- throw new TemplateException(
- 'Value was missing, section: ${node.value}',
- _templateName, node.line, node.column);
-
- } else if (value is Function) {
- var context = new _LambdaContext(node, this, isSection: true);
- var output = value(context);
- context.close();
- _write(output);
-
- } else {
- throw new TemplateException(
- 'Invalid value type for section, '
- 'section: ${node.value}, '
- 'type: ${value.runtimeType}',
- _templateName, node.line, node.column);
- }
- }
-
- _renderInvSection(node) {
- var value = _resolveValue(node.value);
-
- 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 Iterable) {
- // Do nothing.
-
- } else if (value == _noSuchProperty) {
- if (_lenient) {
- _renderSectionWithValue(node, null);
- } else {
- throw new TemplateException(
- 'Value was missing, inverse-section: ${node.value}',
- _templateName, node.line, node.column);
- }
-
- } else if (value is Function) {
- var context = new _LambdaContext(node, this, isSection: true);
- var output = value(context);
- context.close();
-
- //FIXME Poos. I have no idea what this really is for ?????
- if (output == false) {
- // FIXME not sure what to output here, result of function or template
- // output?
- _write(output);
- }
-
- } else {
- throw new TemplateException(
- 'Invalid value type for inverse section, '
- 'section: ${node.value}, '
- 'type: ${value.runtimeType}, ',
- _templateName, node.line, node.column);
- }
- }
-
- _renderPartial(_Node node) {
- var partialName = node.value;
- _Template template = _partialResolver == null
- ? null
- : _partialResolver(partialName);
- if (template != null) {
- var renderer = new _Renderer.partial(this, template, node.indent);
- renderer.render();
- } else if (_lenient) {
- // do nothing
- } else {
- throw new TemplateException(
- 'Partial not found: $partialName',
- _templateName, node.line, node.column);
- }
- }
-
- 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();
- }
-}
-
-_visit(_Node root, visitor(_Node n)) {
- var _stack = new List<_Node>()..add(root);
- while (!_stack.isEmpty) {
- var node = _stack.removeLast();
- _stack.addAll(node.children);
- visitor(node);
- }
-}
-
-class _Node {
-
- _Node(this.type, this.value, this.line, this.column, {this.indent});
-
- _Node.fromToken(_Token token)
- : type = token.type,
- value = token.value,
- line = token.line,
- column = token.column,
- indent = token.indent;
-
- final int type;
- final String value;
- final int line;
- final int column;
- final String indent;
- final List<_Node> children = new List<_Node>();
-
- //TODO ideally these could be made final.
- int start;
- int end;
-
- String toString() => '_Node: ${_tokenTypeString(type)}';
-}
diff --git a/lib/src/token.dart b/lib/src/token.dart
new file mode 100644
index 0000000..a435455
--- /dev/null
+++ b/lib/src/token.dart
@@ -0,0 +1,45 @@
+part of mustache;
+
+class _Token {
+ _Token(this.type, this.value, this.line, this.column, {this.indent});
+
+ final int type;
+ final String value;
+ final int line;
+ final int column;
+ final String indent;
+
+ // Store offsets to extract text from source for lambdas.
+ // Only used for section, inverse section and close section tags.
+ int offset;
+
+ toString() => "${_tokenTypeString(type)}: "
+ "\"${value.replaceAll('\n', '\\n')}\" $line:$column";
+}
+
+//FIXME use enums
+const int _TEXT = 1;
+const int _VARIABLE = 2;
+const int _PARTIAL = 3;
+const int _OPEN_SECTION = 4;
+const int _OPEN_INV_SECTION = 5;
+const int _CLOSE_SECTION = 6;
+const int _COMMENT = 7;
+const int _UNESC_VARIABLE = 8;
+const int _WHITESPACE = 9; // Should be filtered out, before returned by scan.
+const int _LINE_END = 10; // Should be filtered out, before returned by scan.
+const int _CHANGE_DELIMITER = 11;
+
+_tokenTypeString(int type) => [
+ '?',
+ 'Text',
+ 'Var',
+ 'Par',
+ 'Open',
+ 'OpenInv',
+ 'Close',
+ 'Comment',
+ 'UnescVar',
+ 'Whitespace',
+ 'LineEnd',
+ 'ChangeDelimiter'][type];