// Copyright 2022 The Gitea Authors. All rights reserved. // SPDX-License-Identifier: MIT package charset import ( "fmt" "io" "golang.org/x/net/html" ) // HTMLStreamer represents a SAX-like interface for HTML type HTMLStreamer interface { Error(err error) error Doctype(data string) error Comment(data string) error StartTag(data string, attrs ...html.Attribute) error SelfClosingTag(data string, attrs ...html.Attribute) error EndTag(data string) error Text(data string) error } // PassthroughHTMLStreamer is a passthrough streamer type PassthroughHTMLStreamer struct { next HTMLStreamer } func NewPassthroughStreamer(next HTMLStreamer) *PassthroughHTMLStreamer { return &PassthroughHTMLStreamer{next: next} } var _ (HTMLStreamer) = &PassthroughHTMLStreamer{} // Error tells the next streamer in line that there is an error func (p *PassthroughHTMLStreamer) Error(err error) error { return p.next.Error(err) } // Doctype tells the next streamer what the doctype is func (p *PassthroughHTMLStreamer) Doctype(data string) error { return p.next.Doctype(data) } // Comment tells the next streamer there is a comment func (p *PassthroughHTMLStreamer) Comment(data string) error { return p.next.Comment(data) } // StartTag tells the next streamer there is a starting tag func (p *PassthroughHTMLStreamer) StartTag(data string, attrs ...html.Attribute) error { return p.next.StartTag(data, attrs...) } // SelfClosingTag tells the next streamer there is a self-closing tag func (p *PassthroughHTMLStreamer) SelfClosingTag(data string, attrs ...html.Attribute) error { return p.next.SelfClosingTag(data, attrs...) } // EndTag tells the next streamer there is a end tag func (p *PassthroughHTMLStreamer) EndTag(data string) error { return p.next.EndTag(data) } // Text tells the next streamer there is a text func (p *PassthroughHTMLStreamer) Text(data string) error { return p.next.Text(data) } // HTMLStreamWriter acts as a writing sink type HTMLStreamerWriter struct { io.Writer err error } // Write implements io.Writer func (h *HTMLStreamerWriter) Write(data []byte) (int, error) { if h.err != nil { return 0, h.err } return h.Writer.Write(data) } // Write implements io.StringWriter func (h *HTMLStreamerWriter) WriteString(data string) (int, error) { if h.err != nil { return 0, h.err } return h.Writer.Write([]byte(data)) } // Error tells the next streamer in line that there is an error func (h *HTMLStreamerWriter) Error(err error) error { if h.err == nil { h.err = err } return h.err } // Doctype tells the next streamer what the doctype is func (h *HTMLStreamerWriter) Doctype(data string) error { _, h.err = h.WriteString("<!DOCTYPE " + data + ">") return h.err } // Comment tells the next streamer there is a comment func (h *HTMLStreamerWriter) Comment(data string) error { _, h.err = h.WriteString("<!--" + data + "-->") return h.err } // StartTag tells the next streamer there is a starting tag func (h *HTMLStreamerWriter) StartTag(data string, attrs ...html.Attribute) error { return h.startTag(data, attrs, false) } // SelfClosingTag tells the next streamer there is a self-closing tag func (h *HTMLStreamerWriter) SelfClosingTag(data string, attrs ...html.Attribute) error { return h.startTag(data, attrs, true) } func (h *HTMLStreamerWriter) startTag(data string, attrs []html.Attribute, selfclosing bool) error { if _, h.err = h.WriteString("<" + data); h.err != nil { return h.err } for _, attr := range attrs { if _, h.err = h.WriteString(" " + attr.Key + "=\"" + html.EscapeString(attr.Val) + "\""); h.err != nil { return h.err } } if selfclosing { if _, h.err = h.WriteString("/>"); h.err != nil { return h.err } } else { if _, h.err = h.WriteString(">"); h.err != nil { return h.err } } return h.err } // EndTag tells the next streamer there is a end tag func (h *HTMLStreamerWriter) EndTag(data string) error { _, h.err = h.WriteString("</" + data + ">") return h.err } // Text tells the next streamer there is a text func (h *HTMLStreamerWriter) Text(data string) error { _, h.err = h.WriteString(html.EscapeString(data)) return h.err } // StreamHTML streams an html to a provided streamer func StreamHTML(source io.Reader, streamer HTMLStreamer) error { tokenizer := html.NewTokenizer(source) for { tt := tokenizer.Next() switch tt { case html.ErrorToken: if tokenizer.Err() != io.EOF { return tokenizer.Err() } return nil case html.DoctypeToken: token := tokenizer.Token() if err := streamer.Doctype(token.Data); err != nil { return err } case html.CommentToken: token := tokenizer.Token() if err := streamer.Comment(token.Data); err != nil { return err } case html.StartTagToken: token := tokenizer.Token() if err := streamer.StartTag(token.Data, token.Attr...); err != nil { return err } case html.SelfClosingTagToken: token := tokenizer.Token() if err := streamer.StartTag(token.Data, token.Attr...); err != nil { return err } case html.EndTagToken: token := tokenizer.Token() if err := streamer.EndTag(token.Data); err != nil { return err } case html.TextToken: token := tokenizer.Token() if err := streamer.Text(token.Data); err != nil { return err } default: return fmt.Errorf("unknown type of token: %d", tt) } } }