🌐 AI搜索 & 代理 主页
Skip to content

Commit 8b8223d

Browse files
committed
Document and fix HTML-escape.
Update documentation and behavior of HtmlEscape. BUG= http://dartbug.com/21913, http://dartbug.com/13097 R=floitsch@google.com, sgjesse@google.com Review URL: https://codereview.chromium.org//1019853002 git-svn-id: https://dart.googlecode.com/svn/branches/bleeding_edge/dart@45003 260f80e4-7a28-3924-810f-c04153c831b5
1 parent 0a1f0d3 commit 8b8223d

File tree

2 files changed

+133
-38
lines changed

2 files changed

+133
-38
lines changed

sdk/lib/convert/html_escape.dart

Lines changed: 122 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -7,69 +7,160 @@ part of dart.convert;
77
// TODO(floitsch) - Document - Issue 13097
88
const HtmlEscape HTML_ESCAPE = const HtmlEscape();
99

10+
/**
11+
* HTML escape modes.
12+
*
13+
* Allows specifying a mode for HTML escaping that depend on the context
14+
* where the escaped result is going to be used.
15+
* The relevant contexts are:
16+
*
17+
* * as text content of an HTML element.
18+
* * as value of a (single- or double-) quoted attribute value.
19+
*
20+
* All modes require escaping of `&` (ampersand) characters, and may
21+
* enable escaping of more characters.
22+
*/
1023
class HtmlEscapeMode {
1124
final String _name;
25+
/** Whether to escape '<' and '>'. */
1226
final bool escapeLtGt;
27+
/** Whether to escape '"' (quote). */
1328
final bool escapeQuot;
29+
/** Whether to escape "'" (apostrophe). */
1430
final bool escapeApos;
15-
final bool escapeSlash;
1631

17-
// TODO(floitsch) - Document - Issue 13097
32+
/**
33+
* Default escaping mode which escape all characters.
34+
*
35+
* The result of such an escaping is usable both in element content and
36+
* in any attribute value.
37+
*
38+
* The escaping only works for elements with normal HTML content,
39+
* and not for, for example, script or style element content,
40+
* which require escapes matching their particular content syntax.
41+
*/
1842
static const HtmlEscapeMode UNKNOWN =
19-
const HtmlEscapeMode._('unknown', true, true, true, true);
20-
21-
// TODO(floitsch) - Document - Issue 13097
43+
const HtmlEscapeMode._('unknown', true, true, true);
44+
45+
/**
46+
* Escaping mode for text going into double-quoted HTML attribute values.
47+
*
48+
* The result should not be used as the content of an unquoted
49+
* or single-quoted attribute value.
50+
*
51+
* Escapes only double quotes (`"`) but not single quotes (`'`).
52+
*/
2253
static const HtmlEscapeMode ATTRIBUTE =
23-
const HtmlEscapeMode._('attribute', false, true, false, false);
24-
25-
// TODO(floitsch) - Document - Issue 13097
54+
const HtmlEscapeMode._('attribute', false, true, false);
55+
56+
/**
57+
* Escaping mode for text going into single-quoted HTML attribute values.
58+
*
59+
* The result should not be used as the content of an unquoted
60+
* or double-quoted attribute value.
61+
*
62+
* Escapes only single quotes (`'`) but not double quotes (`"`).
63+
*/
64+
static const HtmlEscapeMode SQ_ATTRIBUTE =
65+
const HtmlEscapeMode._('attribute', false, false, true);
66+
67+
/**
68+
* Escaping mode for text going into HTML element content.
69+
*
70+
* The escaping only works for elements with normal HTML content,
71+
* and not for, for example, script or style element content,
72+
* which require escapes matching their particular content syntax.
73+
*
74+
* Escapes `<` and `>` characters.
75+
*/
2676
static const HtmlEscapeMode ELEMENT =
27-
const HtmlEscapeMode._('element', true, false, false, true);
28-
29-
// TODO(floitsch) - Document - Issue 13097
30-
const HtmlEscapeMode._(this._name, this.escapeLtGt, this.escapeQuot,
31-
this.escapeApos, this.escapeSlash);
77+
const HtmlEscapeMode._('element', true, false, false);
78+
79+
const HtmlEscapeMode._(
80+
this._name, this.escapeLtGt, this.escapeQuot, this.escapeApos);
81+
82+
/**
83+
* Create a custom escaping mode.
84+
*
85+
* All modes escape `&`.
86+
* The mode can further be set to escape `<` and `>` ([escapeLtGt]),
87+
* `"` ([escapeQuot]) and/or `'` ([escapeApos]).
88+
*/
89+
const HtmlEscapeMode({String name: "custom",
90+
this.escapeLtGt: false,
91+
this.escapeQuot: false,
92+
this.escapeApos: false}) : _name = name;
3293

3394
String toString() => _name;
3495
}
3596

36-
// TODO(floitsch) - Document - Issue 13097
97+
/**
98+
* Converter which escapes characters with special meaning in HTML.
99+
*
100+
* The converter finds characters that are siginificant in HTML source and
101+
* replaces them with corresponding HTML entities.
102+
*
103+
* The characters that need escaping in HTML are:
104+
*
105+
* * `&` (ampersand) always need to be escaped.
106+
* * `<` (less than) and '>' (greater than) when inside an element.
107+
* * `"` (quote) when inside a double-quoted attribute value.
108+
* * `'` (apostrophe) when inside a single-quoted attribute value.
109+
* Apostrophe is escaped as `&#39;` instead of `&apos;` since
110+
* not all browsers understand `&apos;`.
111+
*
112+
* Escaping `>` (greater than) isn't necessary, but the result is often
113+
* found to be easier to read if greater-than is also escaped whenever
114+
* less-than is.
115+
*/
37116
class HtmlEscape extends Converter<String, String> {
38117

39-
// TODO(floitsch) - Document - Issue 13097
118+
/** The [HtmlEscapeMode] used by the converter. */
40119
final HtmlEscapeMode mode;
41120

42-
// TODO(floitsch) - Document - Issue 13097
121+
/**
122+
* Create converter that escapes HTML characters.
123+
*
124+
* If [mode] is provided as either [HtmlEscapeMode.ATTRIBUTE] or
125+
* [HtmlEscapeMode.ELEMENT], only the corresponding subset of HTML
126+
* characters are escaped.
127+
* The default is to escape all HTML characters.
128+
*/
43129
const HtmlEscape([this.mode = HtmlEscapeMode.UNKNOWN]);
44130

45131
String convert(String text) {
46132
var val = _convert(text, 0, text.length);
47133
return val == null ? text : val;
48134
}
49135

136+
/**
137+
* Converts the substring of text from start to end.
138+
*
139+
* Returns `null` if no changes were necessary, otherwise returns
140+
* the converted string.
141+
*/
50142
String _convert(String text, int start, int end) {
51143
StringBuffer result = null;
52144
for (int i = start; i < end; i++) {
53145
var ch = text[i];
54-
String replace = null;
146+
String replacement = null;
55147
switch (ch) {
56-
case '&': replace = '&amp;'; break;
57-
case '\u00A0'/*NO-BREAK SPACE*/: replace = '&nbsp;'; break;
58-
case '"': if (mode.escapeQuot) replace = '&quot;'; break;
59-
case "'": if (mode.escapeApos) replace = '&#x27;'; break;
60-
case '<': if (mode.escapeLtGt) replace = '&lt;'; break;
61-
case '>': if (mode.escapeLtGt) replace = '&gt;'; break;
62-
case '/': if (mode.escapeSlash) replace = '&#x2F;'; break;
148+
case '&': replacement = '&amp;'; break;
149+
case '"': if (mode.escapeQuot) replacement = '&quot;'; break;
150+
case "'": if (mode.escapeApos) replacement = '&#39;'; break;
151+
case '<': if (mode.escapeLtGt) replacement = '&lt;'; break;
152+
case '>': if (mode.escapeLtGt) replacement = '&gt;'; break;
63153
}
64-
if (replace != null) {
65-
if (result == null) result = new StringBuffer(text.substring(start, i));
66-
result.write(replace);
67-
} else if (result != null) {
68-
result.write(ch);
154+
if (replacement != null) {
155+
if (result == null) result = new StringBuffer();
156+
if (i > start) result.write(text.substring(start, i));
157+
result.write(replacement);
158+
start = i + 1;
69159
}
70160
}
71-
72-
return result != null ? result.toString() : null;
161+
if (result == null) return null;
162+
if (end > start) result.write(text.substring(start, end));
163+
return result.toString();
73164
}
74165

75166
StringConversionSink startChunkedConversion(Sink<String> sink) {

tests/lib/convert/html_escape_test.dart

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -8,16 +8,19 @@ import 'dart:convert';
88

99
const _NOOP = 'Nothing_to_escape';
1010

11-
const _TEST_INPUT = '<A </test> of \u00A0 "double" & \'single\' values>';
11+
const _TEST_INPUT = """<A </test> of \xA0 "double" & 'single' values>""";
1212

13-
const _OUTPUT_UNKNOWN = '&lt;A &lt;&#x2F;test&gt; of &nbsp; &quot;double&quot; &amp; '
14-
'&#x27;single&#x27; values&gt;';
13+
const _OUTPUT_UNKNOWN = '&lt;A &lt;/test&gt; of \xA0 &quot;double&quot; &amp; '
14+
'&#39;single&#39; values&gt;';
1515

16-
const _OUTPUT_ATTRIBUTE = "<A </test> of &nbsp; &quot;double&quot; &amp; "
17-
"\'single\' values>";
16+
const _OUTPUT_ATTRIBUTE =
17+
"<A </test> of \xA0 &quot;double&quot; &amp; 'single' values>";
1818

19-
const _OUTPUT_ELEMENT = '&lt;A &lt;&#x2F;test&gt; of &nbsp; "double" &amp; '
20-
'\'single\' values&gt;';
19+
const _OUTPUT_SQ_ATTRIBUTE =
20+
'<A </test> of \xA0 "double" &amp; &#39;single&#39; values>';
21+
22+
const _OUTPUT_ELEMENT =
23+
"""&lt;A &lt;/test&gt; of \xA0 "double" &amp; 'single' values&gt;""";
2124

2225
void _testMode(HtmlEscape escape, String input, String expected) {
2326
_testConvert(escape, input, expected);
@@ -80,6 +83,7 @@ void main() {
8083
_testMode(const HtmlEscape(), _TEST_INPUT, _OUTPUT_UNKNOWN);
8184
_testMode(const HtmlEscape(HtmlEscapeMode.UNKNOWN), _TEST_INPUT, _OUTPUT_UNKNOWN);
8285
_testMode(const HtmlEscape(HtmlEscapeMode.ATTRIBUTE), _TEST_INPUT, _OUTPUT_ATTRIBUTE);
86+
_testMode(const HtmlEscape(HtmlEscapeMode.SQ_ATTRIBUTE), _TEST_INPUT, _OUTPUT_SQ_ATTRIBUTE);
8387
_testMode(const HtmlEscape(HtmlEscapeMode.ELEMENT), _TEST_INPUT, _OUTPUT_ELEMENT);
8488
_testMode(HTML_ESCAPE, _NOOP, _NOOP);
8589
}

0 commit comments

Comments
 (0)