Commit 81a5c9c3 authored by Agniva De Sarker's avatar Agniva De Sarker Committed by Robert Griesemer

go/doc: convert to unicode quotes for ToText and Synopsis

We refactor the conversion of quotes to their unicode equivalent
to a separate function so that it can be called from ToText and Synopsis.

And we introduce a temp buffer to write the escaped HTML and convert
the unicode quotes back to html escaped entities. This simplifies the logic
and gets rid of the need to track the index of the escaped text.

Fixes #27759

Change-Id: I71cf47ddcd4c6794ccdf2898ac25539388b393c1
Reviewed-on: https://go-review.googlesource.com/c/150377
Run-TryBot: Robert Griesemer <gri@golang.org>
Reviewed-by: default avatarRobert Griesemer <gri@golang.org>
parent 689fae2d
...@@ -7,6 +7,7 @@ ...@@ -7,6 +7,7 @@
package doc package doc
import ( import (
"bytes"
"io" "io"
"strings" "strings"
"text/template" // for HTMLEscape "text/template" // for HTMLEscape
...@@ -14,32 +15,38 @@ import ( ...@@ -14,32 +15,38 @@ import (
"unicode/utf8" "unicode/utf8"
) )
const (
ldquo = "&ldquo;"
rdquo = "&rdquo;"
ulquo = "“"
urquo = "”"
)
var ( var (
ldquo = []byte("&ldquo;") htmlQuoteReplacer = strings.NewReplacer(ulquo, ldquo, urquo, rdquo)
rdquo = []byte("&rdquo;") unicodeQuoteReplacer = strings.NewReplacer("``", ulquo, "''", urquo)
) )
// Escape comment text for HTML. If nice is set, // Escape comment text for HTML. If nice is set,
// also turn `` into &ldquo; and '' into &rdquo;. // also turn `` into &ldquo; and '' into &rdquo;.
func commentEscape(w io.Writer, text string, nice bool) { func commentEscape(w io.Writer, text string, nice bool) {
last := 0
if nice { if nice {
for i := 0; i < len(text)-1; i++ { // In the first pass, we convert `` and '' into their unicode equivalents.
ch := text[i] // This prevents them from being escaped in HTMLEscape.
if ch == text[i+1] && (ch == '`' || ch == '\'') { text = convertQuotes(text)
template.HTMLEscape(w, []byte(text[last:i])) var buf bytes.Buffer
last = i + 2 template.HTMLEscape(&buf, []byte(text))
switch ch { // Now we convert the unicode quotes to their HTML escaped entities to maintain old behavior.
case '`': // We need to use a temp buffer to read the string back and do the conversion,
w.Write(ldquo) // otherwise HTMLEscape will escape & to &amp;
case '\'': htmlQuoteReplacer.WriteString(w, buf.String())
w.Write(rdquo) return
}
i++ // loop will add one more
}
}
} }
template.HTMLEscape(w, []byte(text[last:])) template.HTMLEscape(w, []byte(text))
}
func convertQuotes(text string) string {
return unicodeQuoteReplacer.Replace(text)
} }
const ( const (
...@@ -248,7 +255,7 @@ func heading(line string) string { ...@@ -248,7 +255,7 @@ func heading(line string) string {
} }
// allow "." when followed by non-space // allow "." when followed by non-space
for b := line;; { for b := line; ; {
i := strings.IndexRune(b, '.') i := strings.IndexRune(b, '.')
if i < 0 { if i < 0 {
break break
...@@ -429,12 +436,14 @@ func ToText(w io.Writer, text string, indent, preIndent string, width int) { ...@@ -429,12 +436,14 @@ func ToText(w io.Writer, text string, indent, preIndent string, width int) {
case opPara: case opPara:
// l.write will add leading newline if required // l.write will add leading newline if required
for _, line := range b.lines { for _, line := range b.lines {
line = convertQuotes(line)
l.write(line) l.write(line)
} }
l.flush() l.flush()
case opHead: case opHead:
w.Write(nl) w.Write(nl)
for _, line := range b.lines { for _, line := range b.lines {
line = convertQuotes(line)
l.write(line + "\n") l.write(line + "\n")
} }
l.flush() l.flush()
...@@ -445,6 +454,7 @@ func ToText(w io.Writer, text string, indent, preIndent string, width int) { ...@@ -445,6 +454,7 @@ func ToText(w io.Writer, text string, indent, preIndent string, width int) {
w.Write([]byte("\n")) w.Write([]byte("\n"))
} else { } else {
w.Write([]byte(preIndent)) w.Write([]byte(preIndent))
line = convertQuotes(line)
w.Write([]byte(line)) w.Write([]byte(line))
} }
} }
......
...@@ -7,6 +7,7 @@ package doc ...@@ -7,6 +7,7 @@ package doc
import ( import (
"bytes" "bytes"
"reflect" "reflect"
"strings"
"testing" "testing"
) )
...@@ -212,3 +213,20 @@ func TestPairedParensPrefixLen(t *testing.T) { ...@@ -212,3 +213,20 @@ func TestPairedParensPrefixLen(t *testing.T) {
} }
} }
} }
func TestCommentEscape(t *testing.T) {
commentTests := []struct {
in, out string
}{
{"typically invoked as ``go tool asm'',", "typically invoked as " + ldquo + "go tool asm" + rdquo + ","},
{"For more detail, run ``go help test'' and ``go help testflag''", "For more detail, run " + ldquo + "go help test" + rdquo + " and " + ldquo + "go help testflag" + rdquo},
}
for i, tt := range commentTests {
var buf strings.Builder
commentEscape(&buf, tt.in, true)
out := buf.String()
if out != tt.out {
t.Errorf("#%d: mismatch\nhave: %q\nwant: %q", i, out, tt.out)
}
}
}
...@@ -72,6 +72,7 @@ func Synopsis(s string) string { ...@@ -72,6 +72,7 @@ func Synopsis(s string) string {
return "" return ""
} }
} }
s = convertQuotes(s)
return s return s
} }
......
...@@ -35,6 +35,7 @@ var tests = []struct { ...@@ -35,6 +35,7 @@ var tests = []struct {
{"All Rights reserved. Package foo does bar.", 20, ""}, {"All Rights reserved. Package foo does bar.", 20, ""},
{"All rights reserved. Package foo does bar.", 20, ""}, {"All rights reserved. Package foo does bar.", 20, ""},
{"Authors: foo@bar.com. Package foo does bar.", 21, ""}, {"Authors: foo@bar.com. Package foo does bar.", 21, ""},
{"typically invoked as ``go tool asm'',", 37, "typically invoked as " + ulquo + "go tool asm" + urquo + ","},
} }
func TestSynopsis(t *testing.T) { func TestSynopsis(t *testing.T) {
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment