Chromium Code Reviews| OLD | NEW |
|---|---|
| 1 // Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file | 1 // Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file |
| 2 // for details. All rights reserved. Use of this source code is governed by a | 2 // for details. All rights reserved. Use of this source code is governed by a |
| 3 // BSD-style license that can be found in the LICENSE file. | 3 // BSD-style license that can be found in the LICENSE file. |
| 4 | 4 |
| 5 part of dart.convert; | 5 part of dart.convert; |
| 6 | 6 |
| 7 // TODO(floitsch) - Document - Issue 13097 | 7 // TODO(floitsch) - Document - Issue 13097 |
| 8 const HtmlEscape HTML_ESCAPE = const HtmlEscape(); | 8 const HtmlEscape HTML_ESCAPE = const HtmlEscape(); |
| 9 | 9 |
| 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. | |
|
Søren Gjesse
2015/03/19 14:09:33
Maybe mention what the different contexts are.
Lasse Reichstein Nielsen
2015/03/20 08:47:04
Done.
| |
| 15 * | |
| 16 * All modes require escaping of `&` (ampersand) characters, and may | |
| 17 * enable escaping of more characters. | |
| 18 */ | |
| 10 class HtmlEscapeMode { | 19 class HtmlEscapeMode { |
| 11 final String _name; | 20 final String _name; |
| 21 /** Whether to escape '<' and '>'. */ | |
| 12 final bool escapeLtGt; | 22 final bool escapeLtGt; |
| 23 /** Whether to escape '"' (quote). */ | |
| 13 final bool escapeQuot; | 24 final bool escapeQuot; |
| 25 /** Whether to escape "'" (apostrophe). */ | |
| 14 final bool escapeApos; | 26 final bool escapeApos; |
| 15 final bool escapeSlash; | |
| 16 | 27 |
| 17 // TODO(floitsch) - Document - Issue 13097 | 28 /** |
| 29 * Default escaping mode which escape all characters. | |
| 30 * | |
| 31 * The result of such an escaping is usable both in element content and | |
| 32 * in any attribute value. | |
| 33 * | |
| 34 * The escaping only works for elements with normal HTML content, | |
| 35 * and not for, for example, script or style element content, | |
| 36 * which require escapes matching their particular content syntax. | |
| 37 */ | |
| 18 static const HtmlEscapeMode UNKNOWN = | 38 static const HtmlEscapeMode UNKNOWN = |
| 19 const HtmlEscapeMode._('unknown', true, true, true, true); | 39 const HtmlEscapeMode._('unknown', true, true, true); |
| 20 | 40 |
| 21 // TODO(floitsch) - Document - Issue 13097 | 41 /** |
| 42 * Escaping mode for text going into double-quoted HTML attribute values. | |
| 43 * | |
| 44 * The result should not be used as the content of an unquoted | |
| 45 * or single-quoted attribute value. | |
| 46 * | |
| 47 * Escapes only double quotes (`"`) but not single quotes (`'`). | |
| 48 */ | |
| 22 static const HtmlEscapeMode ATTRIBUTE = | 49 static const HtmlEscapeMode ATTRIBUTE = |
| 23 const HtmlEscapeMode._('attribute', false, true, false, false); | 50 const HtmlEscapeMode._('attribute', false, true, false); |
| 24 | 51 |
| 25 // TODO(floitsch) - Document - Issue 13097 | 52 /** |
| 53 * Escaping mode for text going into single-quoted HTML attribute values. | |
| 54 * | |
| 55 * The result should not be used as the content of an unquoted | |
| 56 * or double-quoted attribute value. | |
| 57 * | |
| 58 * Escapes only single quotes (`'`) but not double quotes (`"`). | |
| 59 */ | |
| 60 static const HtmlEscapeMode SQ_ATTRIBUTE = | |
| 61 const HtmlEscapeMode._('attribute', false, false, true); | |
| 62 | |
| 63 /** | |
| 64 * Escaping mode for text going into HTML element content. | |
| 65 * | |
| 66 * The escaping only works for elements with normal HTML content, | |
| 67 * and not for, for example, script or style element content, | |
| 68 * which require escapes matching their particular content syntax. | |
| 69 * | |
| 70 * Escapes `<` and `>` characters. | |
| 71 */ | |
| 26 static const HtmlEscapeMode ELEMENT = | 72 static const HtmlEscapeMode ELEMENT = |
| 27 const HtmlEscapeMode._('element', true, false, false, true); | 73 const HtmlEscapeMode._('element', true, false, false); |
| 28 | 74 |
| 29 // TODO(floitsch) - Document - Issue 13097 | 75 const HtmlEscapeMode._( |
| 30 const HtmlEscapeMode._(this._name, this.escapeLtGt, this.escapeQuot, | 76 this._name, this.escapeLtGt, this.escapeQuot, this.escapeApos); |
| 31 this.escapeApos, this.escapeSlash); | 77 |
| 78 /** | |
| 79 * Create a custom escaping mode. | |
| 80 * | |
| 81 * All modes escape `&`. | |
| 82 * The mode can further be set to escape `<` and `>` ([escapeLtGt]), | |
| 83 * `"` ([escapeQuot]) and/or `'` ([escapeApos]). | |
| 84 */ | |
| 85 const HtmlEscapeMode({String name: "custom", | |
| 86 this.escapeLtGt: false, | |
| 87 this.escapeQuot: false, | |
| 88 this.escapeApos: false}) : _name = name; | |
| 32 | 89 |
| 33 String toString() => _name; | 90 String toString() => _name; |
| 34 } | 91 } |
| 35 | 92 |
| 36 // TODO(floitsch) - Document - Issue 13097 | 93 /** |
| 94 * Converter which escapes characters that means something when parsed as HTML. | |
|
floitsch
2015/03/19 14:10:39
that have a meaning in HTML?
otherwise: "mean" (n
Lasse Reichstein Nielsen
2015/03/20 08:47:04
Rewritten.
| |
| 95 * | |
| 96 * The converter finds characters that have meaning in HTML and replaces | |
| 97 * them with HTML corresponding entities. | |
| 98 * | |
| 99 * The characters that need escaping in HTML are: | |
| 100 * | |
| 101 * * `&` (ampersand) always need to be escaped. | |
| 102 * * `<` (less than) and '>' (greater than) when inside an element. | |
| 103 * * `"` (quote) when inside a double-quoted attribute value. | |
| 104 * * `'` (apostrophe) when inside a single-quoted attribute value. | |
| 105 * Apostrophe is escaped as `'` instead of `'` since | |
| 106 * not all browsers understand `'`. | |
| 107 * | |
| 108 * Escaping `>` (greater than) isn't necessary, but the result is often | |
| 109 * found to be easier to read if greater-than is also escaped whenever | |
| 110 * less-than is. | |
| 111 */ | |
| 37 class HtmlEscape extends Converter<String, String> { | 112 class HtmlEscape extends Converter<String, String> { |
| 38 | 113 |
| 39 // TODO(floitsch) - Document - Issue 13097 | 114 /** The [HtmlEscapeMode] used by the converter. */ |
| 40 final HtmlEscapeMode mode; | 115 final HtmlEscapeMode mode; |
| 41 | 116 |
| 42 // TODO(floitsch) - Document - Issue 13097 | 117 /** |
| 118 * Create converter that escapes HTML characters. | |
| 119 * | |
| 120 * If [mode] is provided as either [HtmlEscapeMode.ATTRIBUTE] or | |
| 121 * [HtmlEscapeMode.ELEMENT], only the corresponding subset of HTML | |
| 122 * characters are escaped. | |
| 123 * The default is to escape all HTML characters. | |
| 124 */ | |
| 43 const HtmlEscape([this.mode = HtmlEscapeMode.UNKNOWN]); | 125 const HtmlEscape([this.mode = HtmlEscapeMode.UNKNOWN]); |
| 44 | 126 |
| 45 String convert(String text) { | 127 String convert(String text) { |
| 46 var val = _convert(text, 0, text.length); | 128 var val = _convert(text, 0, text.length); |
| 47 return val == null ? text : val; | 129 return val == null ? text : val; |
| 48 } | 130 } |
| 49 | 131 |
| 132 /** | |
| 133 * Converts the substring of text from start to end. | |
| 134 * | |
| 135 * Returns `null` if no changes were necessary, otherwise returns | |
| 136 * the converted string. | |
| 137 */ | |
| 50 String _convert(String text, int start, int end) { | 138 String _convert(String text, int start, int end) { |
| 51 StringBuffer result = null; | 139 StringBuffer result = null; |
| 52 for (int i = start; i < end; i++) { | 140 for (int i = start; i < end; i++) { |
| 53 var ch = text[i]; | 141 var ch = text[i]; |
| 54 String replace = null; | 142 String replacement = null; |
| 55 switch (ch) { | 143 switch (ch) { |
| 56 case '&': replace = '&'; break; | 144 case '&': replacement = '&'; break; |
| 57 case '\u00A0'/*NO-BREAK SPACE*/: replace = ' '; break; | 145 case '"': if (mode.escapeQuot) replacement = '"'; break; |
| 58 case '"': if (mode.escapeQuot) replace = '"'; break; | 146 case "'": if (mode.escapeApos) replacement = '''; break; |
|
Søren Gjesse
2015/03/19 14:09:33
Maybe add a comment why ' is not used.
Lasse Reichstein Nielsen
2015/03/20 08:47:05
It's in the documentation for the class.
| |
| 59 case "'": if (mode.escapeApos) replace = '''; break; | 147 case '<': if (mode.escapeLtGt) replacement = '<'; break; |
| 60 case '<': if (mode.escapeLtGt) replace = '<'; break; | 148 case '>': if (mode.escapeLtGt) replacement = '>'; break; |
| 61 case '>': if (mode.escapeLtGt) replace = '>'; break; | |
| 62 case '/': if (mode.escapeSlash) replace = '/'; break; | |
| 63 } | 149 } |
| 64 if (replace != null) { | 150 if (replacement != null) { |
| 65 if (result == null) result = new StringBuffer(text.substring(start, i)); | 151 if (result == null) result = new StringBuffer(); |
| 66 result.write(replace); | 152 if (i > start) result.write(text.substring(start, i)); |
| 67 } else if (result != null) { | 153 result.write(replacement); |
| 68 result.write(ch); | 154 start = i + 1; |
| 69 } | 155 } |
| 70 } | 156 } |
| 71 | 157 if (result == null) return null; |
| 72 return result != null ? result.toString() : null; | 158 if (end > start) result.write(text.substring(start, end)); |
| 159 return result.toString(); | |
| 73 } | 160 } |
| 74 | 161 |
| 75 StringConversionSink startChunkedConversion(Sink<String> sink) { | 162 StringConversionSink startChunkedConversion(Sink<String> sink) { |
| 76 if (sink is! StringConversionSink) { | 163 if (sink is! StringConversionSink) { |
| 77 sink = new StringConversionSink.from(sink); | 164 sink = new StringConversionSink.from(sink); |
| 78 } | 165 } |
| 79 return new _HtmlEscapeSink(this, sink); | 166 return new _HtmlEscapeSink(this, sink); |
| 80 } | 167 } |
| 81 } | 168 } |
| 82 | 169 |
| 83 class _HtmlEscapeSink extends StringConversionSinkBase { | 170 class _HtmlEscapeSink extends StringConversionSinkBase { |
| 84 final HtmlEscape _escape; | 171 final HtmlEscape _escape; |
| 85 final StringConversionSink _sink; | 172 final StringConversionSink _sink; |
| 86 | 173 |
| 87 _HtmlEscapeSink(this._escape, this._sink); | 174 _HtmlEscapeSink(this._escape, this._sink); |
| 88 | 175 |
| 89 void addSlice(String chunk, int start, int end, bool isLast) { | 176 void addSlice(String chunk, int start, int end, bool isLast) { |
| 90 var val = _escape._convert(chunk, start, end); | 177 var val = _escape._convert(chunk, start, end); |
| 91 if(val == null) { | 178 if(val == null) { |
| 92 _sink.addSlice(chunk, start, end, isLast); | 179 _sink.addSlice(chunk, start, end, isLast); |
| 93 } else { | 180 } else { |
| 94 _sink.add(val); | 181 _sink.add(val); |
| 95 if (isLast) _sink.close(); | 182 if (isLast) _sink.close(); |
| 96 } | 183 } |
| 97 } | 184 } |
| 98 | 185 |
| 99 void close() => _sink.close(); | 186 void close() => _sink.close(); |
| 100 } | 187 } |
| OLD | NEW |