2025-09-11 20:50:00 +02:00
|
|
|
package html
|
|
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
"bytes"
|
2025-10-05 14:27:48 +02:00
|
|
|
"errors"
|
|
|
|
|
"io"
|
2025-09-11 20:50:00 +02:00
|
|
|
"unicode"
|
|
|
|
|
)
|
|
|
|
|
|
2025-10-05 14:27:48 +02:00
|
|
|
type wrapper struct {
|
2025-10-05 21:25:53 +02:00
|
|
|
out *indentWriter
|
2025-10-05 14:27:48 +02:00
|
|
|
width int
|
|
|
|
|
line, word *bytes.Buffer
|
|
|
|
|
inWord, inTag, inSingleQuote, inQuote, lastSpace, started bool
|
|
|
|
|
err error
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func newWrapper(out io.Writer, width int, indent string) *wrapper {
|
|
|
|
|
return &wrapper{
|
2025-10-05 21:25:53 +02:00
|
|
|
out: newIndentWriter(out, indent),
|
|
|
|
|
width: width,
|
|
|
|
|
line: bytes.NewBuffer(nil),
|
|
|
|
|
word: bytes.NewBuffer(nil),
|
2025-10-05 14:27:48 +02:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (w *wrapper) feed() error {
|
|
|
|
|
withSpace := w.lastSpace && w.line.Len() > 0
|
|
|
|
|
l := w.line.Len() + w.word.Len()
|
|
|
|
|
if withSpace && w.word.Len() > 0 {
|
|
|
|
|
l++
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
feedLine := l > w.width && w.line.Len() > 0
|
|
|
|
|
if feedLine {
|
|
|
|
|
if w.started {
|
|
|
|
|
if _, err := w.out.Write([]byte{'\n'}); err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if _, err := io.Copy(w.out, w.line); err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
w.line.Reset()
|
|
|
|
|
w.started = true
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if !feedLine && withSpace {
|
|
|
|
|
w.line.WriteRune(' ')
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
io.Copy(w.line, w.word)
|
|
|
|
|
w.word.Reset()
|
|
|
|
|
return nil
|
|
|
|
|
}
|
2025-09-11 20:50:00 +02:00
|
|
|
|
2025-10-05 14:27:48 +02:00
|
|
|
func (w *wrapper) Write(p []byte) (int, error) {
|
|
|
|
|
if w.err != nil {
|
|
|
|
|
return 0, w.err
|
|
|
|
|
}
|
|
|
|
|
|
2025-10-05 20:06:39 +02:00
|
|
|
runes := bytes.NewBuffer(nil)
|
|
|
|
|
if n, err := runes.Write(p); err != nil {
|
|
|
|
|
w.err = err
|
|
|
|
|
return n, w.err
|
|
|
|
|
}
|
|
|
|
|
|
2025-09-11 20:50:00 +02:00
|
|
|
for {
|
2025-10-05 14:27:48 +02:00
|
|
|
r, _, err := runes.ReadRune()
|
|
|
|
|
if errors.Is(err, io.EOF) {
|
|
|
|
|
return len(p), nil
|
2025-09-11 20:50:00 +02:00
|
|
|
}
|
|
|
|
|
|
2025-10-05 21:25:53 +02:00
|
|
|
if err != nil {
|
|
|
|
|
w.err = err
|
|
|
|
|
return len(p), w.err
|
|
|
|
|
}
|
|
|
|
|
|
2025-09-11 20:50:00 +02:00
|
|
|
if r == unicode.ReplacementChar {
|
2025-10-05 20:06:39 +02:00
|
|
|
continue
|
2025-10-05 14:27:48 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if w.inSingleQuote {
|
|
|
|
|
w.inSingleQuote = r != '\''
|
|
|
|
|
w.word.WriteRune(r)
|
|
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if w.inQuote {
|
|
|
|
|
w.inQuote = r != '"'
|
|
|
|
|
w.word.WriteRune(r)
|
2025-09-11 20:50:00 +02:00
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
|
2025-10-05 14:27:48 +02:00
|
|
|
if w.inTag {
|
|
|
|
|
w.inSingleQuote = r == '\''
|
|
|
|
|
w.inQuote = r == '"'
|
|
|
|
|
w.inTag = r != '>'
|
2025-10-05 20:06:39 +02:00
|
|
|
if w.inTag || !unicode.IsSpace(r) {
|
|
|
|
|
w.word.WriteRune(r)
|
|
|
|
|
}
|
|
|
|
|
|
2025-10-05 14:27:48 +02:00
|
|
|
if !w.inTag {
|
|
|
|
|
if err := w.feed(); err != nil {
|
|
|
|
|
w.err = err
|
2025-10-05 20:06:39 +02:00
|
|
|
return len(p), w.err
|
2025-10-05 14:27:48 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
w.lastSpace = unicode.IsSpace(r)
|
2025-09-11 20:50:00 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
|
2025-10-05 14:27:48 +02:00
|
|
|
if w.inWord {
|
|
|
|
|
w.inTag = r == '<'
|
|
|
|
|
w.inWord = !w.inTag && !unicode.IsSpace(r)
|
|
|
|
|
if !w.inWord {
|
|
|
|
|
if err := w.feed(); err != nil {
|
|
|
|
|
w.err = err
|
2025-10-05 20:06:39 +02:00
|
|
|
return len(p), w.err
|
2025-10-05 14:27:48 +02:00
|
|
|
}
|
2025-09-11 20:50:00 +02:00
|
|
|
|
2025-10-05 14:27:48 +02:00
|
|
|
w.lastSpace = unicode.IsSpace(r)
|
|
|
|
|
}
|
2025-09-11 20:50:00 +02:00
|
|
|
|
2025-10-05 14:27:48 +02:00
|
|
|
if w.inWord || w.inTag {
|
|
|
|
|
w.word.WriteRune(r)
|
|
|
|
|
}
|
2025-09-11 20:50:00 +02:00
|
|
|
|
2025-10-05 14:27:48 +02:00
|
|
|
continue
|
2025-09-11 20:50:00 +02:00
|
|
|
}
|
|
|
|
|
|
2025-10-05 14:27:48 +02:00
|
|
|
if unicode.IsSpace(r) {
|
|
|
|
|
w.lastSpace = true
|
2025-09-11 20:50:00 +02:00
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
|
2025-10-05 14:27:48 +02:00
|
|
|
w.word.WriteRune(r)
|
|
|
|
|
w.inTag = r == '<'
|
|
|
|
|
w.inWord = !w.inTag
|
2025-09-11 20:50:00 +02:00
|
|
|
}
|
|
|
|
|
|
2025-10-05 20:06:39 +02:00
|
|
|
return len(p), w.err
|
2025-10-05 14:27:48 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (w *wrapper) Flush() error {
|
|
|
|
|
if w.err != nil {
|
|
|
|
|
return w.err
|
2025-09-11 20:50:00 +02:00
|
|
|
}
|
|
|
|
|
|
2025-10-05 14:27:48 +02:00
|
|
|
if w.inTag || w.inWord {
|
|
|
|
|
if err := w.feed(); err != nil {
|
|
|
|
|
w.err = err
|
|
|
|
|
return err
|
2025-09-11 20:50:00 +02:00
|
|
|
}
|
2025-10-05 14:27:48 +02:00
|
|
|
}
|
2025-09-11 20:50:00 +02:00
|
|
|
|
2025-10-05 14:27:48 +02:00
|
|
|
w.width = 0
|
|
|
|
|
if err := w.feed(); err != nil {
|
|
|
|
|
w.err = err
|
|
|
|
|
return err
|
2025-09-11 20:50:00 +02:00
|
|
|
}
|
|
|
|
|
|
2025-10-05 21:25:53 +02:00
|
|
|
w.err = w.out.Flush()
|
|
|
|
|
return w.err
|
2025-09-11 20:50:00 +02:00
|
|
|
}
|