From 8fd0f17b7bcca4725046ca82d3193b9986fe1faa Mon Sep 17 00:00:00 2001 From: Thomas Voss Date: Sat, 9 Sep 2023 23:03:57 +0200 Subject: Add primitive support for doctypes and XML --- formatter/formatter.go | 55 +++++++++++++++++++++++++++++++----------- parser/parser.go | 65 ++++++++++++++++++++++++++++++++++++++++++++++++-- 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{ '"': """, '&': "&", @@ -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("") + } + 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) { -- cgit v1.2.3