Refactor node into seperate classes move render methods into nodes from renderer.
diff --git a/lib/src/lambda_context.dart b/lib/src/lambda_context.dart
index 37056a9..ee5f2f8 100644
--- a/lib/src/lambda_context.dart
+++ b/lib/src/lambda_context.dart
@@ -44,12 +44,14 @@
String get source {
_checkClosed();
- var nodes = _node.children;
+ if (_node is! _SectionNode) return '';
+
+ var nodes = (_node as _SectionNode).children;
if (nodes.isEmpty) return '';
- if (nodes.length == 1 && nodes.first.type == _TEXT)
- return nodes.first.value;
+ if (nodes.length == 1 && nodes.first is _TextNode)
+ return nodes.first.text;
var source = _renderer._source.substring(
_node.contentStart, _node.contentEnd);
diff --git a/lib/src/node.dart b/lib/src/node.dart
index 44ab33e..ebd3824 100644
--- a/lib/src/node.dart
+++ b/lib/src/node.dart
@@ -1,18 +1,10 @@
part of mustache;
-class _Node {
+abstract class _Node {
- _Node(this.type, this.value, this.start, this.end, {this.indent});
+ _Node(this.start, this.end);
- _Node.fromToken(_Token token)
- : type = token.type,
- value = token.value,
- start = token.start,
- end = token.end,
- indent = token.indent;
-
- final int type;
- final String value;
+ void render(_Renderer renderer);
// The offset of the start of the token in the file. Unless this is a section
// or inverse section, then this stores the start of the content of the
@@ -23,11 +15,173 @@
int contentStart;
int contentEnd;
+}
+
+
+class _TextNode extends _Node {
+
+ _TextNode(this.text, int start, int end) : super(start, end);
+
+ final String text;
+
+ render(_Renderer renderer, {lastNode: false}) {
+ if (text == '') return;
+ if (renderer._indent == null || renderer._indent == '') {
+ renderer._write(text);
+ } else if (lastNode && text.runes.last == _NEWLINE) {
+ var s = text.substring(0, text.length - 1);
+ renderer._write(s.replaceAll('\n', '\n$renderer._indent'));
+ renderer._write('\n');
+ } else {
+ renderer._write(text.replaceAll('\n', '\n$renderer._indent'));
+ }
+ }
+}
+
+class _VariableNode extends _Node {
+
+ _VariableNode(this.name, int start, int end, this.delimiters,
+ {this.escape: false})
+ : super(start, end);
+
+ final String name;
+ final String delimiters;
+ final bool escape;
+
+ render(_Renderer renderer) {
+
+ var value = renderer._resolveValue(name);
+
+ if (value is Function) {
+ var context = new _LambdaContext(this, renderer, isSection: false);
+ value = value(context);
+ context.close();
+ }
+
+ if (value == _noSuchProperty) {
+ if (!renderer._lenient)
+ throw renderer._error('Value was missing for variable tag: ${name}.', this);
+ } else {
+ var valueString = (value == null) ? '' : value.toString();
+ var output = !escape || !renderer._htmlEscapeValues
+ ? valueString
+ : renderer._htmlEscape(valueString);
+ renderer._write(output);
+ }
+ }
+}
+
+
+class _SectionNode extends _Node {
+
+ _SectionNode(this.name, int start, int end, this.delimiters,
+ {this.inverse: false})
+ : super(start, end);
+
+ final String name;
+ final String delimiters;
+ final bool inverse;
+ int contentStart;
+ int contentEnd;
+ final List<_Node> children = <_Node>[];
+
+ //TODO can probably combine Inv and Normal to shorten.
+ void render(_Renderer renderer) => inverse
+ ? renderInv(renderer)
+ : renderNormal(renderer);
+
+ void renderNormal(_Renderer renderer) {
+ var value = renderer._resolveValue(name);
+
+ if (value == null) {
+ // Do nothing.
+
+ } else if (value is Iterable) {
+ value.forEach((v) => renderer._renderSectionWithValue(this, v));
+
+ } else if (value is Map) {
+ renderer._renderSectionWithValue(this, value);
+
+ } else if (value == true) {
+ renderer._renderSectionWithValue(this, value);
+
+ } else if (value == false) {
+ // Do nothing.
+
+ } else if (value == _noSuchProperty) {
+ if (!renderer._lenient)
+ throw renderer._error('Value was missing for section tag: ${name}.', this);
+
+ } else if (value is Function) {
+ var context = new _LambdaContext(this, renderer, isSection: true);
+ var output = value(context);
+ context.close();
+ renderer._write(output);
+
+ } else {
+ throw renderer._error('Invalid value type for section, '
+ 'section: ${name}, '
+ 'type: ${value.runtimeType}.', this);
+ }
+ }
+
+ void renderInv(_Renderer renderer) {
+ var value = renderer._resolveValue(name);
+
+ if (value == null) {
+ renderer._renderSectionWithValue(this, null);
+
+ } else if ((value is Iterable && value.isEmpty) || value == false) {
+ renderer._renderSectionWithValue(this, name);
+
+ } else if (value == true || value is Map || value is Iterable) {
+ // Do nothing.
+
+ } else if (value == _noSuchProperty) {
+ if (renderer._lenient) {
+ renderer._renderSectionWithValue(this, null);
+ } else {
+ throw renderer._error('Value was missing for inverse section: ${name}.', this);
+ }
+
+ } else if (value is Function) {
+ // Do nothing.
+ //TODO in strict mode should this be an error?
+
+ } else {
+ throw renderer._error(
+ 'Invalid value type for inverse section, '
+ 'section: $name, '
+ 'type: ${value.runtimeType}.', this);
+ }
+ }
+}
+
+class _PartialNode extends _Node {
+
+ _PartialNode(this.name, int start, int end, this.delimiters, this.indent)
+ : super(start, end);
+
+ final String name;
+ final String delimiters; //FIXME
+
// Used to store the preceding whitespace before a partial tag, so that
// it's content can be correctly indented.
final String indent;
- final List<_Node> children = new List<_Node>();
-
- String toString() => '_Node: ${_tokenTypeString(type)}';
+ void render(_Renderer renderer) {
+ var partialName = name;
+ _Template template = renderer._partialResolver == null
+ ? null
+ : renderer._partialResolver(partialName);
+ if (template != null) {
+ var r = new _Renderer.partial(renderer, template, this.indent);
+ r.render(); //FIXME Hmm change this.
+ } else if (renderer._lenient) {
+ // do nothing
+ } else {
+ throw renderer._error('Partial not found: $partialName.', this);
+ }
+ }
}
+
diff --git a/lib/src/parse.dart b/lib/src/parse.dart
index 9858de0..63695c6 100644
--- a/lib/src/parse.dart
+++ b/lib/src/parse.dart
@@ -13,30 +13,44 @@
tokens = _removeStandaloneWhitespace(tokens);
tokens = _mergeAdjacentText(tokens);
- var stack = new List<_Node>()..add(new _Node(_OPEN_SECTION, 'root', 0, 0));
+ var stack = new List<_Node>()..add(new _SectionNode('root', 0, 0, delimiters));
+ var delim;
+
for (var t in tokens) {
switch (t.type) {
case _TEXT:
+ var n = new _TextNode(t.value, t.start, t.end);
+ stack.last.children.add(n);
+ break;
+
case _VARIABLE:
case _UNESC_VARIABLE:
+ var n = new _VariableNode(
+ t.value, t.start, t.end, delim, escape: t.type != _UNESC_VARIABLE);
+ stack.last.children.add(n);
+ break;
+
case _PARTIAL:
- stack.last.children.add(new _Node.fromToken(t));
+ var n = new _PartialNode(t.value, t.start, t.end, delim, t.indent);
+ stack.last.children.add(n);
break;
case _OPEN_SECTION:
case _OPEN_INV_SECTION:
// Store the start, end of the inner string content not
// including the tag.
- var child = new _Node.fromToken(t)..contentStart = t.end;
+ var child = new _SectionNode(t.value, t.start, t.end, delim,
+ inverse: t.type == _OPEN_INV_SECTION)
+ ..contentStart = t.end;
stack.last.children.add(child);
stack.add(child);
break;
case _CLOSE_SECTION:
- if (stack.last.value != t.value) {
+ if (stack.last.name != t.value) {
throw new _TemplateException(
- "Mismatched tag, expected: '${stack.last.value}', was: '${t.value}'",
+ "Mismatched tag, expected: '${stack.last.name}', was: '${t.value}'",
templateName, source, t.start);
}
@@ -46,7 +60,7 @@
break;
case _CHANGE_DELIMITER:
- stack.last.children.add(new _Node.fromToken(t));
+ delimiters = t.value;
break;
case _COMMENT:
diff --git a/lib/src/renderer.dart b/lib/src/renderer.dart
index 0387aa0..a6ca9f1 100644
--- a/lib/src/renderer.dart
+++ b/lib/src/renderer.dart
@@ -65,7 +65,7 @@
source,
delimiters);
- final _Node _root;
+ final _SectionNode _root;
final StringSink _sink;
final _values;
final List _stack;
@@ -80,7 +80,7 @@
void render() {
if (_indent == null || _indent == '') {
- _root.children.forEach(_renderNode);
+ _root.children.forEach((n) => n.render(this));
} else {
_renderWithIndent();
}
@@ -89,68 +89,23 @@
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;
+ var nodes = _root.children;
+ if (nodes.isEmpty) return;
_write(_indent);
- for (int i = 0; i < _root.children.length - 1; i++) {
- _renderNode(_root.children[i]);
- }
-
+ nodes.take(nodes.length - 1).map((n) => n.render(this));
+
var node = _root.children.last;
- if (node.type != _TEXT) {
- _renderNode(node);
+ if (node is _TextNode) {
+ node.render(this, lastNode: true);
} else {
- _renderText(node, lastNode: true);
+ node.render(this);
}
}
_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) {
@@ -205,30 +160,9 @@
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 _error('Value was missing for variable tag: ${node.value}.', node);
- } else {
- var valueString = (value == null) ? '' : value.toString();
- var output = !escape || !_htmlEscapeValues
- ? valueString
- : _htmlEscape(valueString);
- _write(output);
- }
- }
-
- _renderSectionWithValue(node, value) {
+ void _renderSectionWithValue(node, value) {
_stack.add(value);
- node.children.forEach(_renderNode);
+ node.children.forEach((n) => n.render(this));
_stack.removeLast();
}
@@ -238,87 +172,6 @@
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 _error('Value was missing for section tag: ${node.value}.', node);
-
- } else if (value is Function) {
- var context = new _LambdaContext(node, this, isSection: true);
- var output = value(context);
- context.close();
- _write(output);
-
- } else {
- throw _error('Invalid value type for section, '
- 'section: ${node.value}, '
- 'type: ${value.runtimeType}.', node);
- }
- }
-
- _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 _error('Value was missing for inverse section: ${node.value}.', node);
- }
-
- } else if (value is Function) {
- // Do nothing.
- //TODO in strict mode should this be an error?
-
- } else {
- throw _error(
- 'Invalid value type for inverse section, '
- 'section: ${node.value}, '
- 'type: ${value.runtimeType}.', node);
- }
- }
-
- _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 _error('Partial not found: $partialName.', node);
- }
- }
static const Map<String,String> _htmlEscapeMap = const {
_AMP: '&',