This commit is contained in:
Tobe O 2018-04-03 14:07:34 -04:00
parent ececca6ec8
commit 1cc960044e
15 changed files with 61 additions and 39 deletions

View file

@ -1,3 +1,8 @@
# 1.0.3
* Fix a scanner bug that prevented proper parsing of HTML nodes
followed by free text.
* Don't trim `<textarea>` content.
# 1.0.2
* Use `package:dart2_constant`.
* Upgrade `package:symbol_table`.

View file

@ -1,4 +1,4 @@
export 'src/ast/ast.dart';
export 'src/text/parser.dart';
export 'src/text/scanner.dart';
export 'src/renderer.dart';
export 'src/renderer.dart';

View file

@ -15,4 +15,4 @@ export 'member.dart';
export 'new.dart';
export 'number.dart';
export 'string.dart';
export 'token.dart';
export 'token.dart';

View file

@ -2,4 +2,4 @@ import 'package:source_span/source_span.dart';
abstract class AstNode {
FileSpan get span;
}
}

View file

@ -22,8 +22,6 @@ class Attribute extends AstNode {
@override
FileSpan get span {
if (equals == null) return nameNode.span;
return nameNode.span
.expand(equals?.span ?? nequ.span)
.expand(value.span);
return nameNode.span.expand(equals?.span ?? nequ.span).expand(value.span);
}
}

View file

@ -23,7 +23,8 @@ class Call extends Expression {
.expand(rParen.span);
}
List computePositional(SymbolTable scope) => arguments.map((e) => e.compute(scope)).toList();
List computePositional(SymbolTable scope) =>
arguments.map((e) => e.compute(scope)).toList();
Map<Symbol, dynamic> computeNamed(SymbolTable scope) {
return namedArguments.fold<Map<Symbol, dynamic>>({}, (out, a) {

View file

@ -24,4 +24,4 @@ class Negation extends Expression {
compute(SymbolTable scope) {
return !(expression.compute(scope));
}
}
}

View file

@ -10,7 +10,7 @@ class Identifier extends Expression {
@override
compute(SymbolTable scope) {
switch(name) {
switch (name) {
case 'null':
return null;
case 'true':
@ -20,14 +20,11 @@ class Identifier extends Expression {
default:
var symbol = scope.resolve(name);
if (symbol == null) {
if (scope.resolve('!strict!')?.value == false)
return null;
if (scope.resolve('!strict!')?.value == false) return null;
throw new ArgumentError(
'The name "$name" does not exist in this scope.');
}
return scope
.resolve(name)
.value;
return scope.resolve(name).value;
}
}

View file

@ -58,4 +58,4 @@ enum TokenType {
hex,
string,
question,
}
}

View file

@ -50,12 +50,12 @@ class Renderer {
for (var error in errors) {
var type =
error.severity == JaelErrorSeverity.warning ? 'warning' : 'error';
error.severity == JaelErrorSeverity.warning ? 'warning' : 'error';
buf
..writeln('<li>')
..indent()
..writeln(
'<b>$type:</b> ${error.span.start.toolString}: ${error.message}')
..writeln('<b>$type:</b> ${error.span.start.toolString}: ${error
.message}')
..writeln('<br>')
..writeln(
'<span style="color: red;">' +
@ -79,7 +79,8 @@ class Renderer {
///
/// If [strictResolution] is `false` (default: `true`), then undefined identifiers will return `null`
/// instead of throwing.
void render(Document document, CodeBuffer buffer, SymbolTable scope, {bool strictResolution: true}) {
void render(Document document, CodeBuffer buffer, SymbolTable scope,
{bool strictResolution: true}) {
scope.create('!strict!', value: strictResolution != false);
if (document.doctype != null) buffer.writeln(document.doctype.span.text);
@ -148,8 +149,8 @@ class Renderer {
for (int i = 0; i < element.children.length; i++) {
var child = element.children.elementAt(i);
renderElementChild(
child, buffer, childScope, html5, i, element.children.length);
renderElementChild(element, child, buffer, childScope, html5, i,
element.children.length);
}
buffer.writeln();
@ -228,7 +229,7 @@ class Renderer {
for (int i = 0; i < element.children.length; i++) {
var child = element.children.elementAt(i);
renderElementChild(
child, buffer, scope, html5, i, element.children.length);
element, child, buffer, scope, html5, i, element.children.length);
}
}
@ -250,7 +251,8 @@ class Renderer {
if (comparison == value) {
for (int i = 0; i < child.children.length; i++) {
var c = child.children.elementAt(i);
renderElementChild(c, buffer, scope, html5, i, child.children.length);
renderElementChild(
element, c, buffer, scope, html5, i, child.children.length);
}
return;
@ -263,15 +265,15 @@ class Renderer {
if (defaultCase != null) {
for (int i = 0; i < defaultCase.children.length; i++) {
var child = defaultCase.children.elementAt(i);
renderElementChild(
child, buffer, scope, html5, i, defaultCase.children.length);
renderElementChild(element, child, buffer, scope, html5, i,
defaultCase.children.length);
}
}
}
void renderElementChild(ElementChild child, CodeBuffer buffer,
void renderElementChild(Element parent, ElementChild child, CodeBuffer buffer,
SymbolTable scope, bool html5, int index, int total) {
if (child is Text) {
if (child is Text && parent?.tagName != 'textarea') {
if (index == 0)
buffer.write(child.span.text.trimLeft());
else if (index == total - 1)

View file

@ -1,4 +1,5 @@
library jael.src.text.parselet;
import '../../ast/ast.dart';
import '../parser.dart';
part 'infix.dart';
@ -11,4 +12,4 @@ abstract class PrefixParselet {
abstract class InfixParselet {
int get precedence;
Expression parse(Parser parser, Expression left, Token token);
}
}

View file

@ -108,10 +108,15 @@ class _Scanner implements Scanner {
var ch = _scanner.readChar();
if (ch == $lt && !_scanner.isDone) {
if (ch == $lt) {
// && !_scanner.isDone) {
if (_scanner.matches('/')) {
// If we reached "</", backtrack and break into HTML
openTags.removeFirst();
_scanner.position--;
state = _ScannerState.html;
break;
} else if (_scanner.matches(_id)) {
// Also break when we reach <foo.
//
// HOWEVER, that is also JavaScript. So we must
@ -164,7 +169,8 @@ class _Scanner implements Scanner {
do {
// Only continue if we find a left bracket
if (true) {// || _scanner.matches('<') || _scanner.matches('{{')) {
if (true) {
// || _scanner.matches('<') || _scanner.matches('{{')) {
var potential = <Token>[];
while (true) {
@ -207,9 +213,20 @@ class _Scanner implements Scanner {
}
} else if (token.type == TokenType.gt) {
// Only pop the bracket if we're at foo>, </foo> or foo/>
if (brackets.isNotEmpty && brackets.first.type == TokenType.slash)
if (brackets.isNotEmpty && brackets.first.type == TokenType.slash) {
// </foo>
brackets.removeFirst();
// Now, ONLY continue parsing HTML if the next character is '<'.
var replay = _scanner.state;
_scanner.scan(_whitespace);
if (!_scanner.matches('<')) {
_scanner.state = replay;
state = _ScannerState.freeText;
break;
}
}
//else if (_scanner.matches('>')) brackets.removeFirst();
else if (brackets.isNotEmpty &&
brackets.first.type == TokenType.lt) {

View file

@ -1,5 +1,5 @@
name: jael
version: 1.0.2
version: 1.0.3
description: A simple server-side HTML templating engine for Dart.
author: Tobe O <thosakwe@gmail.com>
homepage: https://github.com/angel-dart/jael/tree/master/jael

View file

@ -84,7 +84,7 @@ main() {
Pokémon
</h1>
Darkrai - Dark
<img>
<img/>
</body>
</html>
'''
@ -250,13 +250,15 @@ main() {
print(buf);
expect(
buf.toString(),
buf.toString().replaceAll('\n', '').replaceAll(' ', '').trim(),
'''
<div>
<img src="<SCARY XSS>">
<MORE SCARY XSS>
</div>
'''
.replaceAll('\n', '')
.replaceAll(' ', '')
.trim());
});

View file

@ -83,10 +83,9 @@ main() {
<script aria-label="script">
window.alert('a string');
</script>
''',
'''.trim(),
sourceUrl: 'test.jl',
)
.tokens;
).tokens;
tokens.forEach(print);
expect(tokens, hasLength(11));