200 lines · 5.2 KB · Go
RawDownload
// 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 HTMLtype 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 streamertype 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 errorfunc (p *PassthroughHTMLStreamer) Error(err error) error {	return p.next.Error(err)} // Doctype tells the next streamer what the doctype isfunc (p *PassthroughHTMLStreamer) Doctype(data string) error {	return p.next.Doctype(data)} // Comment tells the next streamer there is a commentfunc (p *PassthroughHTMLStreamer) Comment(data string) error {	return p.next.Comment(data)} // StartTag tells the next streamer there is a starting tagfunc (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 tagfunc (p *PassthroughHTMLStreamer) SelfClosingTag(data string, attrs ...html.Attribute) error {	return p.next.SelfClosingTag(data, attrs...)} // EndTag tells the next streamer there is a end tagfunc (p *PassthroughHTMLStreamer) EndTag(data string) error {	return p.next.EndTag(data)} // Text tells the next streamer there is a textfunc (p *PassthroughHTMLStreamer) Text(data string) error {	return p.next.Text(data)} // HTMLStreamWriter acts as a writing sinktype HTMLStreamerWriter struct {	io.Writer	err error} // Write implements io.Writerfunc (h *HTMLStreamerWriter) Write(data []byte) (int, error) {	if h.err != nil {		return 0, h.err	}	return h.Writer.Write(data)} // Write implements io.StringWriterfunc (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 errorfunc (h *HTMLStreamerWriter) Error(err error) error {	if h.err == nil {		h.err = err	}	return h.err} // Doctype tells the next streamer what the doctype isfunc (h *HTMLStreamerWriter) Doctype(data string) error {	_, h.err = h.WriteString("<!DOCTYPE " + data + ">")	return h.err} // Comment tells the next streamer there is a commentfunc (h *HTMLStreamerWriter) Comment(data string) error {	_, h.err = h.WriteString("<!--" + data + "-->")	return h.err} // StartTag tells the next streamer there is a starting tagfunc (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 tagfunc (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 tagfunc (h *HTMLStreamerWriter) EndTag(data string) error {	_, h.err = h.WriteString("</" + data + ">")	return h.err} // Text tells the next streamer there is a textfunc (h *HTMLStreamerWriter) Text(data string) error {	_, h.err = h.WriteString(html.EscapeString(data))	return h.err} // StreamHTML streams an html to a provided streamerfunc 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)		}	}}