| part of mustache; |
| |
| abstract class _Node { |
| |
| _Node(this.start, this.end); |
| |
| 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 |
| // section. |
| final int start; |
| final int end; |
| |
| 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; |
| |
| 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); |
| } |
| } |
| } |
| |