diff --git a/README.md b/README.md
index 572ab22..555a5a5 100644
--- a/README.md
+++ b/README.md
@@ -1,6 +1,6 @@
# htnoml
-Experimental DSL that compiles to HTML.
+Experimental alternative to HTML
### Example
@@ -14,7 +14,7 @@ Experimental DSL that compiles to HTML.
{:link rel[stylesheet] href[css/style.css]}
}
{:body
- {class[container main] > This HTnoML syntax}
+ {class[container main] > This is HTnoML syntax}
}
}
```
diff --git a/fixtures/basic.html b/fixtures/basic.html
new file mode 100644
index 0000000..2a3e792
--- /dev/null
+++ b/fixtures/basic.html
@@ -0,0 +1 @@
+
This is a HTML5 template This is HTnoML syntax
\ No newline at end of file
diff --git a/example.htnoml b/fixtures/basic.htnoml
similarity index 83%
rename from example.htnoml
rename to fixtures/basic.htnoml
index 792e44c..93f7a78 100644
--- a/example.htnoml
+++ b/fixtures/basic.htnoml
@@ -7,6 +7,6 @@
{:link rel[stylesheet] href[css/style.css]}
}
{:body
- {class[container main] > This HTnoML syntax}
+ {class[container main] > This is HTnoML syntax}
}
-}
+}
\ No newline at end of file
diff --git a/go.mod b/go.mod
index 8abcea1..d50277f 100644
--- a/go.mod
+++ b/go.mod
@@ -1,3 +1,5 @@
module github.com/radulucut/htnoml
go 1.20
+
+require github.com/google/go-cmp v0.6.0 // indirect
diff --git a/go.sum b/go.sum
new file mode 100644
index 0000000..5a8d551
--- /dev/null
+++ b/go.sum
@@ -0,0 +1,2 @@
+github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
+github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
diff --git a/parser.go b/parser.go
index bb98f40..22b4472 100644
--- a/parser.go
+++ b/parser.go
@@ -1,13 +1,8 @@
package htnoml
-import "io"
-
-type Node struct {
- ElementType *Chunk
- Text *Chunk // Only for text nodes
- Attributes []Attribute // Only for element nodes
- Children []Node // Only for element nodes
-}
+import (
+ "io"
+)
type Chunk struct {
Start int
@@ -19,6 +14,13 @@ type Attribute struct {
Value *Chunk
}
+type Node struct {
+ ElementType *Chunk
+ Text *Chunk // Only for text nodes
+ Attributes []Attribute // Only for element nodes
+ Children []Node // Only for element nodes
+}
+
type TokenType byte
const (
@@ -54,7 +56,42 @@ func NewParser(reader io.ReadSeeker) (*Parser, error) {
}, nil
}
-func (p *Parser) Parse() {
+func (p *Parser) ToHTML() string {
+ if p.node == nil {
+ p.parse()
+ }
+ return p.nodeToHTML(p.node, true)
+}
+
+func (p *Parser) nodeToHTML(node *Node, isRoot bool) string {
+ if node.Text != nil {
+ return string(p.buf[node.Text.Start:node.Text.End])
+ }
+ children := ""
+ for _, child := range node.Children {
+ children += p.nodeToHTML(&child, false)
+ }
+ if isRoot {
+ return children
+ }
+ attributes := ""
+ for _, attr := range node.Attributes {
+ attributes += " " + string(p.buf[attr.Name.Start:attr.Name.End])
+ if attr.Value != nil {
+ attributes += "=" + "\"" + string(p.buf[attr.Value.Start:attr.Value.End]) + "\""
+ }
+ }
+ if node.ElementType == nil {
+ return "" + children + "
"
+ }
+ tag := string(p.buf[node.ElementType.Start:node.ElementType.End])
+ if children == "" && isVoidElement(tag) {
+ return "<" + tag + attributes + " />"
+ }
+ return "<" + tag + attributes + ">" + children + "" + tag + ">"
+}
+
+func (p *Parser) parse() {
p.node = &Node{}
childParentMap := map[*Node]*Node{}
curr := p.node
@@ -64,10 +101,12 @@ func (p *Parser) Parse() {
continue
}
if t.Type == TokenTypeReserved && p.buf[t.Start] == ':' {
+ t = p.nextToken()
curr.ElementType = &Chunk{
Start: t.Start,
End: t.End,
}
+ continue
}
if t.Type == TokenTypeIdentifier {
name := &Chunk{
@@ -93,6 +132,7 @@ func (p *Parser) Parse() {
Name: name,
Value: value,
})
+ continue
}
if t.Type == TokenTypeReserved && p.buf[t.Start] == '{' {
curr.Children = append(curr.Children, Node{})
@@ -119,6 +159,7 @@ func (p *Parser) Parse() {
End: t.End,
},
})
+ continue
}
}
}
@@ -154,7 +195,7 @@ func (p *Parser) readNextToken() *Token {
return &Token{
Type: TokenTypeWhitespace,
Start: start,
- End: p.pos - 1,
+ End: p.pos,
}
}
if p.isReserved(p.buf[p.pos]) {
@@ -163,6 +204,7 @@ func (p *Parser) readNextToken() *Token {
return &Token{
Type: TokenTypeReserved,
Start: p.pos - 1,
+ End: p.pos,
}
}
start := p.pos
@@ -173,7 +215,7 @@ func (p *Parser) readNextToken() *Token {
return &Token{
Type: TokenTypeIdentifier,
Start: start,
- End: p.pos - 1,
+ End: p.pos,
}
}
diff --git a/parser_test.go b/parser_test.go
index ee4ec02..f7dc119 100644
--- a/parser_test.go
+++ b/parser_test.go
@@ -1,15 +1,16 @@
package htnoml
import (
- "log"
"os"
"testing"
+
+ "github.com/google/go-cmp/cmp"
)
func TestParser(t *testing.T) {
- t.Run("Parse", func(t *testing.T) {
- t.Run("should return correct html", func(t *testing.T) {
- f, err := os.Open("example.htnoml")
+ t.Run("ToHTML", func(t *testing.T) {
+ t.Run("basic.htnoml - should return correct html", func(t *testing.T) {
+ f, err := os.Open("fixtures/basic.htnoml")
if err != nil {
t.Fatal(err)
}
@@ -18,9 +19,14 @@ func TestParser(t *testing.T) {
if err != nil {
t.Fatal(err)
}
- p.Parse()
- log.Printf("%+v", p.node)
- // TODO: assert
+ html := p.ToHTML()
+ expected, err := os.ReadFile("fixtures/basic.html")
+ if err != nil {
+ t.Fatal(err)
+ }
+ if diff := cmp.Diff(string(expected), html); diff != "" {
+ t.Errorf("ToHTML() mismatch (-want +got):\n%s", diff)
+ }
})
})
}
diff --git a/utils.go b/utils.go
new file mode 100644
index 0000000..11d6ecd
--- /dev/null
+++ b/utils.go
@@ -0,0 +1,10 @@
+package htnoml
+
+func isVoidElement(tag string) bool {
+ switch tag {
+ case "area", "base", "br", "col", "embed", "hr", "img", "input", "link",
+ "meta", "param", "source", "track", "wbr":
+ return true
+ }
+ return false
+}