1 // Copyright 2010 The Go Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style
3 // license that can be found in the LICENSE file.
13 // These replacements permit compatibility with old numeric entities that
14 // assumed Windows-1252 encoding.
15 // http://www.whatwg.org/specs/web-apps/current-work/multipage/tokenization.html#consume-a-character-reference
16 var replacementTable = [...]int{
17 '\u20AC', // First entry is what 0x80 should be replaced with.
48 '\u0178', // Last entry is 0x9F.
49 // 0x00->'\uFFFD' is handled programmatically.
50 // 0x0D->'\u000D' is a no-op.
53 // unescapeEntity reads an entity like "<" from b[src:] and writes the
54 // corresponding "<" to b[dst:], returning the incremented dst and src cursors.
55 // Precondition: b[src] == '&' && dst <= src.
56 func unescapeEntity(b []byte, dst, src int) (dst1, src1 int) {
57 // http://www.whatwg.org/specs/web-apps/current-work/multipage/tokenization.html#consume-a-character-reference
59 // i starts at 1 because we already know that s[0] == '&'.
64 return dst + 1, src + 1
68 if len(s) <= 3 { // We need to have at least "&#.".
70 return dst + 1, src + 1
75 if c == 'x' || c == 'X' {
85 if '0' <= c && c <= '9' {
86 x = 16*x + int(c) - '0'
88 } else if 'a' <= c && c <= 'f' {
89 x = 16*x + int(c) - 'a' + 10
91 } else if 'A' <= c && c <= 'F' {
92 x = 16*x + int(c) - 'A' + 10
95 } else if '0' <= c && c <= '9' {
96 x = 10*x + int(c) - '0'
105 if i <= 3 { // No characters matched.
107 return dst + 1, src + 1
110 if 0x80 <= x && x <= 0x9F {
111 // Replace characters from Windows-1252 with UTF-8 equivalents.
112 x = replacementTable[x-0x80]
113 } else if x == 0 || (0xD800 <= x && x <= 0xDFFF) || x > 0x10FFFF {
114 // Replace invalid characters with the replacement character.
118 return dst + utf8.EncodeRune(b[dst:], x), src + i
121 // Consume the maximum number of characters possible, with the
122 // consumed characters matching one of the named references.
124 // TODO(nigeltao): unescape("¬it;") should be "¬it;"
128 // Lower-cased characters are more common in entities, so we check for them first.
129 if 'a' <= c && c <= 'z' || 'A' <= c && c <= 'Z' {
138 entityName := string(s[1:i])
139 if x := entity[entityName]; x != 0 {
140 return dst + utf8.EncodeRune(b[dst:], x), src + i
141 } else if x := entity2[entityName]; x[0] != 0 { // Check if it's a two-character entity.
142 dst1 := dst + utf8.EncodeRune(b[dst:], x[0])
143 return dst1 + utf8.EncodeRune(b[dst1:], x[1]), src + i
146 dst1, src1 = dst+i, src+i
147 copy(b[dst:dst1], b[src:src1])
151 // unescape unescapes b's entities in-place, so that "a<b" becomes "a<b".
152 func unescape(b []byte) []byte {
153 for i, c := range b {
155 dst, src := unescapeEntity(b, i, i)
159 dst, src = unescapeEntity(b, dst, src)
162 dst, src = dst+1, src+1
171 const escapedChars = `&'<>"`
173 func escape(buf *bytes.Buffer, s string) {
174 i := strings.IndexAny(s, escapedChars)
176 buf.WriteString(s[0:i])
190 panic("unrecognized escape character")
194 i = strings.IndexAny(s, escapedChars)
199 // EscapeString escapes special characters like "<" to become "<". It
200 // escapes only five such characters: amp, apos, lt, gt and quot.
201 // UnescapeString(EscapeString(s)) == s always holds, but the converse isn't
203 func EscapeString(s string) string {
204 if strings.IndexAny(s, escapedChars) == -1 {
207 buf := bytes.NewBuffer(nil)
212 // UnescapeString unescapes entities like "<" to become "<". It unescapes a
213 // larger range of entities than EscapeString escapes. For example, "á"
214 // unescapes to "á", as does "á" and "&xE1;".
215 // UnescapeString(EscapeString(s)) == s always holds, but the converse isn't
217 func UnescapeString(s string) string {
218 for _, c := range s {
220 return string(unescape([]byte(s)))