aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorThomas Voss <mail@thomasvoss.com> 2023-09-09 23:03:57 +0200
committerThomas Voss <mail@thomasvoss.com> 2023-09-09 23:03:57 +0200
commit8fd0f17b7bcca4725046ca82d3193b9986fe1faa (patch)
treee0ab1680bba6bee3eb2cc653d88111b153fd396f
parent96aef4c6a473e380b027a7b9db87cbbbcbae1cba (diff)
Add primitive support for doctypes and XML
-rw-r--r--formatter/formatter.go55
-rw-r--r--parser/parser.go65
2 files changed, 104 insertions, 16 deletions
diff --git a/formatter/formatter.go b/formatter/formatter.go
index 7d44891..738581d 100644
--- a/formatter/formatter.go
+++ b/formatter/formatter.go
@@ -7,6 +7,8 @@ import (
"git.thomasvoss.com/gsp/parser"
)
+var xml = false
+
var stringEscapes = map[rune]string{
'"': "&quot;",
'&': "&amp;",
@@ -19,6 +21,24 @@ func PrintHtml(ast parser.AstNode) {
return
}
+ if ast.Type == parser.DocType || ast.Type == parser.XmlDocType {
+ if ast.Type == parser.DocType {
+ fmt.Print("<!DOCTYPE")
+ } else {
+ xml = true
+ fmt.Print("<?xml")
+ }
+
+ for _, a := range ast.Attrs {
+ printAttr(a)
+ }
+
+ if ast.Type == parser.XmlDocType {
+ fmt.Print("?")
+ }
+ fmt.Print(">")
+ }
+
if ast.Type == parser.Normal {
fmt.Printf("<%s", ast.Text)
@@ -44,22 +64,14 @@ func PrintHtml(ast parser.AstNode) {
}
for _, a := range notClasses {
- fmt.Printf(" %s", a.Key)
- if a.Value == "" {
- continue
- }
- fmt.Print("=\"")
- for _, r := range a.Value {
- if v, ok := stringEscapes[r]; ok {
- fmt.Print(v)
- } else {
- fmt.Printf("%c", r)
- }
- }
- fmt.Print("\"")
+ printAttr(a)
}
- fmt.Print(">")
+ if xml && len(ast.Children) == 0 {
+ fmt.Print("/>")
+ } else {
+ fmt.Print(">")
+ }
}
if len(ast.Children) == 0 {
@@ -85,6 +97,21 @@ func PrintHtml(ast parser.AstNode) {
}
}
+func printAttr(a parser.Attr) {
+ fmt.Printf(" %s", a.Key)
+ if a.Value != "" {
+ fmt.Print("=\"")
+ for _, r := range a.Value {
+ if v, ok := stringEscapes[r]; ok {
+ fmt.Print(v)
+ } else {
+ fmt.Printf("%c", r)
+ }
+ }
+ fmt.Print("\"")
+ }
+}
+
func trimLeftSpaces(s string) string {
i := 0
rs := []rune(s)
diff --git a/parser/parser.go b/parser/parser.go
index 9e0a329..1101e65 100644
--- a/parser/parser.go
+++ b/parser/parser.go
@@ -12,9 +12,11 @@ import (
type nodeType uint
const (
- Normal nodeType = iota
+ DocType nodeType = iota
+ Normal
Tagless
Text
+ XmlDocType
)
type Attr struct {
@@ -31,7 +33,66 @@ type AstNode struct {
func ParseFile(file *os.File) (AstNode, error) {
r := reader{r: bufio.NewReader(file)}
- return r.parseNode()
+ return r.parseDocument()
+}
+
+func (reader *reader) parseDocument() (AstNode, error) {
+ document := AstNode{Type: Tagless}
+ if doctype, err, exists := reader.parseDocType(); err != nil {
+ return AstNode{}, err
+ } else if exists {
+ document.Children = append(document.Children, doctype)
+ }
+
+ if node, err := reader.parseNode(); err != nil {
+ return AstNode{}, err
+ } else {
+ document.Children = append(document.Children, node)
+ }
+
+ return document, nil
+}
+
+func (reader *reader) parseDocType() (AstNode, error, bool) {
+ doctype := AstNode{}
+
+ r, err := reader.readNonSpaceRune()
+ if err != nil {
+ return AstNode{}, err, false
+ }
+
+ switch r {
+ case '!':
+ doctype.Type = DocType
+ case '?':
+ doctype.Type = XmlDocType
+ default:
+ return AstNode{}, reader.unreadRune(), false
+ }
+
+ if attrs, err := reader.parseAttrs(); err != nil {
+ return AstNode{}, err, false
+ } else {
+ doctype.Attrs = attrs
+ }
+
+ // The above call to reader.parseAttrs() guarantees that we have the ā€˜{ā€™
+ // token.
+ if _, err := reader.readRune(); err != nil {
+ return AstNode{}, err, false
+ }
+
+ if r, err := reader.readNonSpaceRune(); err != nil {
+ return AstNode{}, err, false
+ } else if r != '}' {
+ return AstNode{}, invalidSyntax{
+ pos: reader.pos,
+ expected: "empty body (doctypes must have empty bodies)",
+ found: fmt.Sprintf("ā€˜%cā€™\n", r),
+ }, false
+ }
+
+ return doctype, nil, true
}
func (reader *reader) parseNode() (AstNode, error) {