Skip to content

Commit 44f397f

Browse files
committed
c3fa47b feat(i18n): implement an i18n-aware html parser
1 parent ccab535 commit 44f397f

21 files changed

Lines changed: 1400 additions & 689 deletions

‎BUILD_INFO‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,2 @@
1-
Thu Mar 24 20:19:13 UTC 2016
2-
025eebc634e6f79f000f95181f5257a38e44b4a2
1+
Thu Mar 24 20:29:22 UTC 2016
2+
c3fa47bbb3156e43b8ee03464cf8e58c4fc73fd6

‎_analyzer.dart‎

Lines changed: 323 additions & 320 deletions
Large diffs are not rendered by default.

‎lib/src/compiler/html_ast.dart‎

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,10 @@ class HtmlElementAst implements HtmlAst {
3232
List<HtmlAttrAst> attrs;
3333
List<HtmlAst> children;
3434
ParseSourceSpan sourceSpan;
35-
HtmlElementAst(this.name, this.attrs, this.children, this.sourceSpan) {}
35+
ParseSourceSpan startSourceSpan;
36+
ParseSourceSpan endSourceSpan;
37+
HtmlElementAst(this.name, this.attrs, this.children, this.sourceSpan,
38+
this.startSourceSpan, this.endSourceSpan) {}
3639
dynamic visit(HtmlAstVisitor visitor, dynamic context) {
3740
return visitor.visitElement(this, context);
3841
}

‎lib/src/compiler/html_parser.dart‎

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -162,11 +162,12 @@ class TreeBuilder {
162162
selfClosing = false;
163163
}
164164
var end = this.peek.sourceSpan.start;
165-
var el = new HtmlElementAst(fullName, attrs, [],
166-
new ParseSourceSpan(startTagToken.sourceSpan.start, end));
165+
var span = new ParseSourceSpan(startTagToken.sourceSpan.start, end);
166+
var el = new HtmlElementAst(fullName, attrs, [], span, span, null);
167167
this._pushElement(el);
168168
if (selfClosing) {
169169
this._popElement(fullName);
170+
el.endSourceSpan = span;
170171
}
171172
}
172173

@@ -180,8 +181,8 @@ class TreeBuilder {
180181
var tagDef = getHtmlTagDefinition(el.name);
181182
var parentEl = this._getParentElement();
182183
if (tagDef.requireExtraParent(isPresent(parentEl) ? parentEl.name : null)) {
183-
var newParent =
184-
new HtmlElementAst(tagDef.parentToAdd, [], [el], el.sourceSpan);
184+
var newParent = new HtmlElementAst(tagDef.parentToAdd, [], [el],
185+
el.sourceSpan, el.startSourceSpan, el.endSourceSpan);
185186
this._addToParent(newParent);
186187
this.elementStack.add(newParent);
187188
this.elementStack.add(el);
@@ -194,6 +195,7 @@ class TreeBuilder {
194195
_consumeEndTag(HtmlToken endTagToken) {
195196
var fullName = getElementFullName(
196197
endTagToken.parts[0], endTagToken.parts[1], this._getParentElement());
198+
this._getParentElement().endSourceSpan = endTagToken.sourceSpan;
197199
if (getHtmlTagDefinition(fullName).isVoid) {
198200
this.errors.add(HtmlTreeError.create(fullName, endTagToken.sourceSpan,
199201
'''Void elements do not have end tags "${ endTagToken . parts [ 1 ]}"'''));

‎lib/src/compiler/legacy_template.dart‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,8 @@ class LegacyHtmlAstTransformer implements HtmlAstVisitor {
5151
var attrs = ast.attrs.map((attr) => attr.visit(this, null)).toList();
5252
var children =
5353
ast.children.map((child) => child.visit(this, null)).toList();
54-
return new HtmlElementAst(ast.name, attrs, children, ast.sourceSpan);
54+
return new HtmlElementAst(ast.name, attrs, children, ast.sourceSpan,
55+
ast.startSourceSpan, ast.endSourceSpan);
5556
}
5657

5758
HtmlAttrAst visitAttr(HtmlAttrAst originalAst, dynamic context) {

‎lib/src/compiler/template_parser.dart‎

Lines changed: 9 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -297,39 +297,28 @@ class TemplateParseVisitor implements HtmlAstVisitor {
297297
preparsedElement.nonBindable ? NON_BINDABLE_VISITOR : this,
298298
element.children,
299299
Component.create(directives));
300-
// Override the actual selector when the `ngProjectAs` attribute is provided
301-
var projectionSelector = isPresent(preparsedElement.projectAs)
302-
? CssSelector.parse(preparsedElement.projectAs)[0]
303-
: elementCssSelector;
304-
var ngContentIndex = component.findNgContentIndex(projectionSelector);
300+
var elementNgContentIndex = hasInlineTemplates
301+
? null
302+
: component.findNgContentIndex(elementCssSelector);
305303
var parsedElement;
306304
if (identical(preparsedElement.type, PreparsedElementType.NG_CONTENT)) {
307305
if (isPresent(element.children) && element.children.length > 0) {
308306
this._reportError(
309307
'''<ng-content> element cannot have content. <ng-content> must be immediately followed by </ng-content>''',
310308
element.sourceSpan);
311309
}
312-
parsedElement = new NgContentAst(this.ngContentCount++,
313-
hasInlineTemplates ? null : ngContentIndex, element.sourceSpan);
310+
parsedElement = new NgContentAst(
311+
this.ngContentCount++, elementNgContentIndex, element.sourceSpan);
314312
} else if (isTemplateElement) {
315313
this._assertAllEventsPublishedByDirectives(directives, events);
316314
this._assertNoComponentsNorElementBindingsOnTemplate(
317315
directives, elementProps, element.sourceSpan);
318-
parsedElement = new EmbeddedTemplateAst(
319-
attrs,
320-
events,
321-
vars,
322-
directives,
323-
children,
324-
hasInlineTemplates ? null : ngContentIndex,
325-
element.sourceSpan);
316+
parsedElement = new EmbeddedTemplateAst(attrs, events, vars, directives,
317+
children, elementNgContentIndex, element.sourceSpan);
326318
} else {
327319
this._assertOnlyOneComponent(directives, element.sourceSpan);
328320
var elementExportAsVars =
329321
vars.where((varAst) => identical(varAst.value.length, 0)).toList();
330-
var ngContentIndex = hasInlineTemplates
331-
? null
332-
: component.findNgContentIndex(projectionSelector);
333322
parsedElement = new ElementAst(
334323
nodeName,
335324
attrs,
@@ -338,7 +327,7 @@ class TemplateParseVisitor implements HtmlAstVisitor {
338327
elementExportAsVars,
339328
directives,
340329
children,
341-
hasInlineTemplates ? null : ngContentIndex,
330+
elementNgContentIndex,
342331
element.sourceSpan);
343332
}
344333
if (hasInlineTemplates) {
@@ -361,7 +350,7 @@ class TemplateParseVisitor implements HtmlAstVisitor {
361350
templateVars,
362351
templateDirectives,
363352
[parsedElement],
364-
ngContentIndex,
353+
component.findNgContentIndex(templateCssSelector),
365354
element.sourceSpan);
366355
}
367356
return parsedElement;

‎lib/src/compiler/template_preparser.dart‎

Lines changed: 3 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -13,13 +13,11 @@ const LINK_STYLE_REL_VALUE = "stylesheet";
1313
const STYLE_ELEMENT = "style";
1414
const SCRIPT_ELEMENT = "script";
1515
const NG_NON_BINDABLE_ATTR = "ngNonBindable";
16-
const NG_PROJECT_AS = "ngProjectAs";
1716
PreparsedElement preparseElement(HtmlElementAst ast) {
1817
var selectAttr = null;
1918
var hrefAttr = null;
2019
var relAttr = null;
2120
var nonBindable = false;
22-
String projectAs = null;
2321
ast.attrs.forEach((attr) {
2422
var lcAttrName = attr.name.toLowerCase();
2523
if (lcAttrName == NG_CONTENT_SELECT_ATTR) {
@@ -30,10 +28,6 @@ PreparsedElement preparseElement(HtmlElementAst ast) {
3028
relAttr = attr.value;
3129
} else if (attr.name == NG_NON_BINDABLE_ATTR) {
3230
nonBindable = true;
33-
} else if (attr.name == NG_PROJECT_AS) {
34-
if (attr.value.length > 0) {
35-
projectAs = attr.value;
36-
}
3731
}
3832
});
3933
selectAttr = normalizeNgContentSelect(selectAttr);
@@ -48,8 +42,7 @@ PreparsedElement preparseElement(HtmlElementAst ast) {
4842
} else if (nodeName == LINK_ELEMENT && relAttr == LINK_STYLE_REL_VALUE) {
4943
type = PreparsedElementType.STYLESHEET;
5044
}
51-
return new PreparsedElement(
52-
type, selectAttr, hrefAttr, nonBindable, projectAs);
45+
return new PreparsedElement(type, selectAttr, hrefAttr, nonBindable);
5346
}
5447

5548
enum PreparsedElementType { NG_CONTENT, STYLE, STYLESHEET, SCRIPT, OTHER }
@@ -59,9 +52,8 @@ class PreparsedElement {
5952
String selectAttr;
6053
String hrefAttr;
6154
bool nonBindable;
62-
String projectAs;
63-
PreparsedElement(this.type, this.selectAttr, this.hrefAttr, this.nonBindable,
64-
this.projectAs) {}
55+
PreparsedElement(
56+
this.type, this.selectAttr, this.hrefAttr, this.nonBindable) {}
6557
}
6658

6759
String normalizeNgContentSelect(String selectAttr) {

‎lib/src/core/change_detection/parser/parser.dart‎

Lines changed: 22 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,12 @@ class ParseException extends BaseException {
6464
}
6565
}
6666

67+
class SplitInterpolation {
68+
List<String> strings;
69+
List<String> expressions;
70+
SplitInterpolation(this.strings, this.expressions) {}
71+
}
72+
6773
@Injectable()
6874
class Parser {
6975
Lexer _lexer;
@@ -128,6 +134,20 @@ class Parser {
128134
}
129135

130136
ASTWithSource parseInterpolation(String input, dynamic location) {
137+
var split = this.splitInterpolation(input, location);
138+
if (split == null) return null;
139+
var expressions = [];
140+
for (var i = 0; i < split.expressions.length; ++i) {
141+
var tokens = this._lexer.tokenize(split.expressions[i]);
142+
var ast = new _ParseAST(input, location, tokens, this._reflector, false)
143+
.parseChain();
144+
expressions.add(ast);
145+
}
146+
return new ASTWithSource(
147+
new Interpolation(split.strings, expressions), input, location);
148+
}
149+
150+
SplitInterpolation splitInterpolation(String input, String location) {
131151
var parts = StringWrapper.split(input, INTERPOLATION_REGEXP);
132152
if (parts.length <= 1) {
133153
return null;
@@ -140,10 +160,7 @@ class Parser {
140160
// fixed string
141161
strings.add(part);
142162
} else if (part.trim().length > 0) {
143-
var tokens = this._lexer.tokenize(part);
144-
var ast = new _ParseAST(input, location, tokens, this._reflector, false)
145-
.parseChain();
146-
expressions.add(ast);
163+
expressions.add(part);
147164
} else {
148165
throw new ParseException(
149166
"Blank expressions are not allowed in interpolated strings",
@@ -152,8 +169,7 @@ class Parser {
152169
location);
153170
}
154171
}
155-
return new ASTWithSource(
156-
new Interpolation(strings, expressions), input, location);
172+
return new SplitInterpolation(strings, expressions);
157173
}
158174

159175
ASTWithSource wrapLiteralPrimitive(String input, dynamic location) {

‎lib/src/facade/collection.dart‎

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -242,6 +242,14 @@ class ListWrapper {
242242
static bool isImmutable(List l) {
243243
return l is UnmodifiableListView;
244244
}
245+
246+
static List flatten(List l) {
247+
final res = [];
248+
l.forEach((item) {
249+
res.addAll(item);
250+
});
251+
return res;
252+
}
245253
}
246254

247255
bool isListLikeIterable(obj) => obj is Iterable;

‎lib/src/facade/lang.dart‎

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -215,6 +215,20 @@ class RegExpWrapper {
215215
static Iterator<Match> matcher(RegExp regExp, String input) {
216216
return regExp.allMatches(input).iterator;
217217
}
218+
219+
static String replaceAll(RegExp regExp, String input, Function replace) {
220+
final m = RegExpWrapper.matcher(regExp, input);
221+
var res = "";
222+
var prev = 0;
223+
while (m.moveNext()) {
224+
var c = m.current;
225+
res += input.substring(prev, c.start);
226+
res += replace(c);
227+
prev = c.start + c[0].length;
228+
}
229+
res += input.substring(prev);
230+
return res;
231+
}
218232
}
219233

220234
class RegExpMatcherWrapper {

0 commit comments

Comments
 (0)