Skip to content

Commit b55643d

Browse files
authored
[html] Allow ampersands in attribute values. (#2036)
1 parent 9c53358 commit b55643d

File tree

5 files changed

+26
-3
lines changed

5 files changed

+26
-3
lines changed

pkgs/html/CHANGELOG.md

+4
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
## 0.15.5+1
2+
3+
- Support "ambiguous ampersand" in attribute values.
4+
15
## 0.15.5
26

37
- Require Dart `3.2`.

pkgs/html/lib/src/tokenizer.dart

+5-2
Original file line numberDiff line numberDiff line change
@@ -313,7 +313,6 @@ class HtmlTokenizer implements Iterator<Token> {
313313

314314
// Try to find the longest entity the string will match to take care
315315
// of &noti for instance.
316-
317316
int entityLen;
318317
for (entityLen = charStack.length - 1; entityLen > 1; entityLen--) {
319318
final possibleEntityName = charStack.sublist(0, entityLen).join();
@@ -340,7 +339,11 @@ class HtmlTokenizer implements Iterator<Token> {
340339
output = '$output${slice(charStack, entityLen).join()}';
341340
}
342341
} else {
343-
_addToken(ParseErrorToken('expected-named-entity'));
342+
if (!fromAttribute) {
343+
// Only emit this error token when we're consuming this NOT as part of an attribute.
344+
// See: https://html.spec.whatwg.org/multipage/parsing.html#ambiguous-ampersand-state
345+
_addToken(ParseErrorToken('expected-named-entity'));
346+
}
344347
stream.unget(charStack.removeLast());
345348
output = '&${charStack.join()}';
346349
}

pkgs/html/pubspec.yaml

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
name: html
2-
version: 0.15.5
2+
version: 0.15.5+1
33
description: APIs for parsing and manipulating HTML content outside the browser.
44
repository: https://github.com/dart-lang/tools/tree/main/pkgs/html
55
issue_tracker: https://github.com/dart-lang/tools/issues?q=is%3Aissue+is%3Aopen+label%3Apackage%3Ahtml

pkgs/html/test/data/tokenizer/test4.test

+4
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,10 @@
2828
"input":"<z ====>",
2929
"output":["ParseError", "ParseError", "ParseError", ["StartTag", "z", {"=": "=="}]]},
3030

31+
{"description":"Ambiguous ampersand in attribute value",
32+
"input":"<tag attr=\"foo?a=b&c=d\">",
33+
"output":[["StartTag", "tag", {"attr": "foo?a=b&c=d"}]]},
34+
3135
{"description":"Allowed \" after ampersand in attribute value",
3236
"input":"<z z=\"&\">",
3337
"output":[["StartTag", "z", {"z": "&"}]]},

pkgs/html/test/parser_feature_test.dart

+12
Original file line numberDiff line numberDiff line change
@@ -148,6 +148,18 @@ On line 4, column 3 of ParseError: Unexpected DOCTYPE. Ignored.
148148
expect(elem.attributeSpans!['extends'], null);
149149
});
150150

151+
test('attribute spans if value contains & (ambiguous ampersand)', () {
152+
final expectedUrl = 'foo?key=value&key2=value2';
153+
final text = '<script src="$expectedUrl"></script>';
154+
155+
final doc = parse(text, generateSpans: true);
156+
final elem = doc.querySelector('script')!;
157+
final span = elem.attributeValueSpans!['src']!;
158+
159+
expect(span.start.offset, text.indexOf('foo'));
160+
expect(span.text, expectedUrl);
161+
});
162+
151163
test('void element innerHTML', () {
152164
var doc = parse('<div></div>');
153165
expect(doc.body!.innerHtml, '<div></div>');

0 commit comments

Comments
 (0)