1
0
html/escape.go

137 lines
2.2 KiB
Go
Raw Normal View History

2025-10-05 20:06:39 +02:00
package html
import (
"bufio"
"bytes"
"errors"
2025-10-05 21:25:53 +02:00
"io"
2025-10-05 20:06:39 +02:00
"unicode"
)
2025-10-05 21:25:53 +02:00
const unicodeNBSP = 0xa0
2025-10-05 20:06:39 +02:00
type escapeWriter struct {
2025-10-05 21:25:53 +02:00
out *bufio.Writer
2025-10-29 21:04:07 +01:00
nonbreakSpaces bool
2025-10-05 20:06:39 +02:00
spaceStarted, lastSpace bool
2025-10-05 21:25:53 +02:00
err error
2025-10-05 20:06:39 +02:00
}
2025-10-29 21:04:07 +01:00
func newEscapeWriter(out io.Writer, nonbreakSpaces bool) *escapeWriter {
return &escapeWriter{
out: bufio.NewWriter(out),
nonbreakSpaces: nonbreakSpaces,
}
2025-10-05 20:06:39 +02:00
}
func (w *escapeWriter) write(r ...rune) {
if w.err != nil {
return
}
for _, ri := range r {
if _, err := w.out.WriteRune(ri); err != nil {
w.err = err
return
}
}
}
func (w *escapeWriter) Write(p []byte) (int, error) {
if w.err != nil {
return 0, w.err
}
runes := bytes.NewBuffer(nil)
if n, err := runes.Write(p); err != nil {
w.err = err
return n, w.err
}
for {
r, _, err := runes.ReadRune()
if errors.Is(err, io.EOF) {
return len(p), nil
}
2025-10-05 21:25:53 +02:00
if err != nil {
w.err = err
return len(p), w.err
}
2025-10-05 20:06:39 +02:00
if r == unicode.ReplacementChar {
continue
}
2025-10-29 21:04:07 +01:00
space := w.nonbreakSpaces && r == ' '
2025-10-05 20:06:39 +02:00
switch {
case space && w.spaceStarted:
w.write([]rune("  ")...)
case space && w.lastSpace:
w.write([]rune(" ")...)
case w.spaceStarted:
w.write(' ')
}
w.spaceStarted = space && !w.lastSpace
w.lastSpace = space
if space {
continue
}
switch r {
case '<':
w.write([]rune("&lt;")...)
case '>':
w.write([]rune("&gt;")...)
case '&':
w.write([]rune("&amp;")...)
case unicodeNBSP:
w.write([]rune("&nbsp;")...)
default:
w.write(r)
}
}
return len(p), w.err
}
func (w *escapeWriter) Flush() error {
if w.err != nil {
return w.err
}
if w.spaceStarted {
w.write(' ')
w.spaceStarted = false
}
w.err = w.out.Flush()
return w.err
}
2025-10-06 23:49:11 +02:00
2025-10-29 21:04:07 +01:00
func escape(s string, nonbreakSpaces bool) string {
2025-10-06 23:49:11 +02:00
var b bytes.Buffer
2025-10-29 21:04:07 +01:00
w := newEscapeWriter(&b, nonbreakSpaces)
2025-10-06 23:49:11 +02:00
w.Write([]byte(s))
w.Flush()
return b.String()
}
func escapeAttribute(value string) string {
var rr []rune
r := []rune(value)
for i := range r {
switch r[i] {
case '"':
rr = append(rr, []rune("&quot;")...)
case '&':
rr = append(rr, []rune("&amp;")...)
default:
rr = append(rr, r[i])
}
}
return string(rr)
}