2019-03-21 18:40:54 +00:00
package hatmill
2019-03-21 17:26:40 +00:00
2019-04-03 03:42:16 +00:00
//go:generate go run internal/codegen/codegen.go -input defs.json -elemfile element/generated.go -elempkg element -attribfile attribute/generated.go -attribpkg attribute
2019-04-29 04:03:28 +00:00
import (
"bytes"
"html"
"io"
)
2019-03-21 17:26:40 +00:00
2019-04-28 21:28:57 +00:00
// Term represents a fragment of HTML markup, and is one of VoidElement, ParentElement, Terms, or Text.
2019-03-22 03:31:24 +00:00
type Term interface {
2019-03-21 18:40:54 +00:00
io . WriterTo
2019-03-21 17:26:40 +00:00
2019-03-22 03:31:24 +00:00
// isHtml is a no-op method to prevent external Term implementations, because
2019-03-21 18:40:54 +00:00
// Go doesn't have sum types.
isHtml ( )
2019-03-21 17:26:40 +00:00
}
2019-03-21 18:40:54 +00:00
// writeStringsTo attempts to write the strings in ss to w, incrementing n by the
// number of bytes written.
func writeStringsTo ( w io . Writer , n * int64 , ss ... string ) error {
for _ , s := range ss {
nCurr , err := io . WriteString ( w , s )
* n += int64 ( nCurr )
if err != nil {
return err
}
}
return nil
2019-03-21 17:26:40 +00:00
}
2019-03-25 02:54:09 +00:00
// Attrib represents an HTML attribute.
2019-03-21 17:26:40 +00:00
type Attrib struct {
2019-03-21 18:40:54 +00:00
Key string
Value string
2019-03-21 17:26:40 +00:00
}
2019-03-25 02:54:09 +00:00
// WriteTo writes a to w as an HTML attribute in the form key="value", or
2019-04-29 04:03:28 +00:00
// simply key if value is empty. Special characters in value are replaced with
// HTML entities. It returns the number of bytes written and any
2019-03-25 02:54:09 +00:00
// error encountered.
2019-03-21 17:26:40 +00:00
func ( a Attrib ) WriteTo ( w io . Writer ) ( n int64 , err error ) {
2019-03-21 18:40:54 +00:00
err = writeStringsTo ( w , & n , a . Key )
if err != nil {
return
}
if a . Value != "" {
2019-04-29 04:03:28 +00:00
escaped := html . EscapeString ( a . Value )
err = writeStringsTo ( w , & n , "='" , escaped , "'" )
2019-03-21 18:40:54 +00:00
}
return
2019-03-21 17:26:40 +00:00
}
2019-03-29 02:18:25 +00:00
// VoidElement represents a void HTML element, that is one that cannot have
2019-03-25 02:54:09 +00:00
// children.
2019-03-29 02:18:25 +00:00
type VoidElement struct {
2019-03-21 18:40:54 +00:00
TagName string
Attribs [ ] Attrib
}
2019-03-21 17:26:40 +00:00
2019-03-29 02:18:25 +00:00
func ( VoidElement ) isHtml ( ) { }
2019-03-21 17:26:40 +00:00
2019-03-25 02:54:09 +00:00
// WriteTo writes the HTML markup represented by e to w, returning the number
// of bytes written and any error encountered.
2019-03-29 02:18:25 +00:00
func ( e VoidElement ) WriteTo ( w io . Writer ) ( n int64 , err error ) {
2019-03-21 18:40:54 +00:00
err = writeStringsTo ( w , & n , "<" , e . TagName )
if err != nil {
return
}
2019-03-21 17:26:40 +00:00
2019-03-21 18:40:54 +00:00
for _ , attrib := range e . Attribs {
err = writeStringsTo ( w , & n , " " )
if err != nil {
return
}
2019-03-21 17:26:40 +00:00
2019-03-21 18:40:54 +00:00
var nCurr int64
nCurr , err = attrib . WriteTo ( w )
n += int64 ( nCurr )
if err != nil {
return
}
}
2019-03-21 17:26:40 +00:00
2019-03-21 18:40:54 +00:00
err = writeStringsTo ( w , & n , ">" )
return
2019-03-21 17:26:40 +00:00
}
2019-03-25 02:54:09 +00:00
// ParentElement represents an HTML element that can have children.
2019-03-21 18:40:54 +00:00
type ParentElement struct {
2019-03-29 02:18:25 +00:00
VoidElement
2019-04-28 21:27:46 +00:00
Children Terms
2019-03-21 17:26:40 +00:00
}
2019-03-25 02:46:38 +00:00
func ( e ParentElement ) isHtml ( ) { }
2019-03-21 18:40:54 +00:00
2019-03-25 02:54:09 +00:00
// WriteTo writes the HTML markup represented by e to w, returning the number
// of bytes written and any error encountered.
2019-03-25 02:46:38 +00:00
func ( e ParentElement ) WriteTo ( w io . Writer ) ( n int64 , err error ) {
2019-03-29 02:18:25 +00:00
n , err = e . VoidElement . WriteTo ( w )
2019-03-21 18:40:54 +00:00
if err != nil {
return
}
2019-03-21 17:26:40 +00:00
2019-04-28 21:27:46 +00:00
var nChildren int64
nChildren , err = e . Children . WriteTo ( w )
n += nChildren
if err != nil {
return
2019-03-21 18:40:54 +00:00
}
2019-03-21 17:26:40 +00:00
2019-03-21 18:40:54 +00:00
err = writeStringsTo ( w , & n , "</" , e . TagName , ">" )
return
2019-03-21 17:26:40 +00:00
}
2019-03-25 02:54:09 +00:00
// Text represents an HTML text node.
2019-03-21 18:40:54 +00:00
type Text string
2019-03-21 17:26:40 +00:00
2019-03-21 18:40:54 +00:00
func ( Text ) isHtml ( ) { }
2019-03-21 17:26:40 +00:00
2019-04-29 04:03:28 +00:00
var entities = map [ byte ] [ ] byte {
'&' : [ ] byte ( "&" ) ,
'<' : [ ] byte ( "<" ) ,
'>' : [ ] byte ( ">" ) ,
'\'' : [ ] byte ( "'" ) ,
'"' : [ ] byte ( """ ) ,
}
type escapedWriter struct {
io . Writer
}
func ( w escapedWriter ) Write ( p [ ] byte ) ( n int , err error ) {
const specialChars = ` &<>'" `
for len ( p ) > 0 {
var nCurr int
idx := bytes . IndexAny ( p , specialChars )
if idx == - 1 {
nCurr , err = w . Writer . Write ( p )
n += nCurr
return
}
nCurr , err = w . Writer . Write ( p [ : idx ] )
n += nCurr
if err != nil {
return n , err
}
entity := entities [ p [ idx ] ]
nCurr , err = w . Writer . Write ( entity )
n += nCurr
if err != nil {
return n , err
}
p = p [ idx + 1 : ]
}
return n , nil
}
2019-03-25 02:54:09 +00:00
// WriteTo writes the contents of t to w, returning the number of bytes written
2019-04-29 04:03:28 +00:00
// and any error encountered. It replaces special characters with HTML entities.
2019-03-21 18:40:54 +00:00
func ( t Text ) WriteTo ( w io . Writer ) ( n int64 , err error ) {
2019-04-29 04:03:28 +00:00
err = writeStringsTo ( escapedWriter { Writer : w } , & n , string ( t ) )
2019-03-21 18:40:54 +00:00
return
2019-03-21 17:26:40 +00:00
}
2019-03-21 18:40:54 +00:00
// WriteDocument writes an HTML5 doctype declaration, followed by root.
// root should probably be an <html> element.
2019-03-25 02:46:38 +00:00
func WriteDocument ( w io . Writer , root ParentElement ) ( n int64 , err error ) {
2019-03-21 18:40:54 +00:00
err = writeStringsTo ( w , & n , "<!DOCTYPE html>" )
if err != nil {
return
}
var nroot int64
nroot , err = root . WriteTo ( w )
n += nroot
return
}
2019-04-28 21:27:46 +00:00
// Terms is a list of HTML nodes.
type Terms [ ] Term
func ( Terms ) isHtml ( ) { }
func ( ts Terms ) WriteTo ( w io . Writer ) ( n int64 , err error ) {
for _ , t := range ts {
var nCurr int64
nCurr , err = t . WriteTo ( w )
n += int64 ( nCurr )
if err != nil {
return
}
}
return
}