Skip to content

Commit

Permalink
html: simplify and optimize escape/unescape
Browse files Browse the repository at this point in the history
The html package uses some specific code to escape special characters.
Actually, the strings.Replacer can be used instead, and is much more
efficient. The converse operation is more complex but can still be
slightly optimized.

Credits to Ken Bloom ([email protected]), who first submitted a
similar patch at https://codereview.appspot.com/141930043

Added benchmarks and slightly optimized UnescapeString.

benchmark                   old ns/op     new ns/op     delta
BenchmarkEscape-4           118713        19825         -83.30%
BenchmarkEscapeNone-4       87653         3784          -95.68%
BenchmarkUnescape-4         24888         23417         -5.91%
BenchmarkUnescapeNone-4     14423         157           -98.91%

benchmark                   old allocs     new allocs     delta
BenchmarkEscape-4           9              2              -77.78%
BenchmarkEscapeNone-4       0              0              +0.00%
BenchmarkUnescape-4         2              2              +0.00%
BenchmarkUnescapeNone-4     0              0              +0.00%

benchmark                   old bytes     new bytes     delta
BenchmarkEscape-4           24800         12288         -50.45%
BenchmarkEscapeNone-4       0             0             +0.00%
BenchmarkUnescape-4         10240         10240         +0.00%
BenchmarkUnescapeNone-4     0             0             +0.00%

Fixes golang#8697

Change-Id: I208261ed7cbe9b3dee6317851f8c0cf15528bce4
Reviewed-on: https://go-review.googlesource.com/9808
Run-TryBot: Brad Fitzpatrick <[email protected]>
Reviewed-by: Brad Fitzpatrick <[email protected]>
TryBot-Result: Gobot Gobot <[email protected]>
  • Loading branch information
dspezia authored and bradfitz committed May 8, 2015
1 parent 2b83366 commit 2d9a50b
Show file tree
Hide file tree
Showing 2 changed files with 50 additions and 47 deletions.
57 changes: 11 additions & 46 deletions src/html/escape.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@
package html

import (
"bytes"
"strings"
"unicode/utf8"
)
Expand Down Expand Up @@ -187,52 +186,20 @@ func unescape(b []byte) []byte {
return b
}

const escapedChars = `&'<>"`

func escape(w writer, s string) error {
i := strings.IndexAny(s, escapedChars)
for i != -1 {
if _, err := w.WriteString(s[:i]); err != nil {
return err
}
var esc string
switch s[i] {
case '&':
esc = "&amp;"
case '\'':
// "&#39;" is shorter than "&apos;" and apos was not in HTML until HTML5.
esc = "&#39;"
case '<':
esc = "&lt;"
case '>':
esc = "&gt;"
case '"':
// "&#34;" is shorter than "&quot;".
esc = "&#34;"
default:
panic("unrecognized escape character")
}
s = s[i+1:]
if _, err := w.WriteString(esc); err != nil {
return err
}
i = strings.IndexAny(s, escapedChars)
}
_, err := w.WriteString(s)
return err
}
var htmlEscaper = strings.NewReplacer(
`&`, "&amp;",
`'`, "&#39;", // "&#39;" is shorter than "&apos;" and apos was not in HTML until HTML5.
`<`, "&lt;",
`>`, "&gt;",
`"`, "&#34;", // "&#34;" is shorter than "&quot;".
)

// EscapeString escapes special characters like "<" to become "&lt;". It
// escapes only five such characters: <, >, &, ' and ".
// UnescapeString(EscapeString(s)) == s always holds, but the converse isn't
// always true.
func EscapeString(s string) string {
if strings.IndexAny(s, escapedChars) == -1 {
return s
}
var buf bytes.Buffer
escape(&buf, s)
return buf.String()
return htmlEscaper.Replace(s)
}

// UnescapeString unescapes entities like "&lt;" to become "<". It unescapes a
Expand All @@ -241,10 +208,8 @@ func EscapeString(s string) string {
// UnescapeString(EscapeString(s)) == s always holds, but the converse isn't
// always true.
func UnescapeString(s string) string {
for _, c := range s {
if c == '&' {
return string(unescape([]byte(s)))
}
if !strings.Contains(s, "&") {
return s
}
return s
return string(unescape([]byte(s)))
}
40 changes: 39 additions & 1 deletion src/html/escape_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,10 @@

package html

import "testing"
import (
"strings"
"testing"
)

type unescapeTest struct {
// A short description of the test case.
Expand Down Expand Up @@ -113,3 +116,38 @@ func TestUnescapeEscape(t *testing.T) {
}
}
}

var (
benchEscapeData = strings.Repeat("AAAAA < BBBBB > CCCCC & DDDDD ' EEEEE \" ", 100)
benchEscapeNone = strings.Repeat("AAAAA x BBBBB x CCCCC x DDDDD x EEEEE x ", 100)
)

func BenchmarkEscape(b *testing.B) {
n := 0
for i := 0; i < b.N; i++ {
n += len(EscapeString(benchEscapeData))
}
}

func BenchmarkEscapeNone(b *testing.B) {
n := 0
for i := 0; i < b.N; i++ {
n += len(EscapeString(benchEscapeNone))
}
}

func BenchmarkUnescape(b *testing.B) {
s := EscapeString(benchEscapeData)
n := 0
for i := 0; i < b.N; i++ {
n += len(UnescapeString(s))
}
}

func BenchmarkUnescapeNone(b *testing.B) {
s := EscapeString(benchEscapeNone)
n := 0
for i := 0; i < b.N; i++ {
n += len(UnescapeString(s))
}
}

0 comments on commit 2d9a50b

Please sign in to comment.