Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow elements with no children (issue #16) #23

Open
wants to merge 8 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 20 additions & 22 deletions haml_io_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,19 +22,19 @@ type testcase struct {

var autoCloseTests = []testcase{
testcase{"plain ∏ text", "plain ∏ text"},
testcase{"%tag", "<tag />"},
testcase{"%tag", "<tag></tag>"},
testcase{"%tag tag content", "<tag>tag content</tag>"},
testcase{"%tag.tagClass", "<tag class=\"tagClass\" />"},
testcase{"%tag.tagClass1.tagClass2", "<tag class=\"tagClass1 tagClass2\" />"},
testcase{".tagClass", "<div class=\"tagClass\" />"},
testcase{"%tag.tagClass", "<tag class=\"tagClass\"></tag>"},
testcase{"%tag.tagClass1.tagClass2", "<tag class=\"tagClass1 tagClass2\"></tag>"},
testcase{".tagClass", "<div class=\"tagClass\"></div>"},
testcase{".tagClass tag content", "<div class=\"tagClass\">tag content</div>"},
testcase{".tagClass1.tagClass2 tag content", "<div class=\"tagClass1 tagClass2\">tag content</div>"},
testcase{"=key1", "value1"},
testcase{"%tag.tagClass= key1", "<tag class=\"tagClass\">value1</tag>"},
testcase{"\\%tag.tagClass= key1", "%tag.tagClass= key1"},
testcase{"\\=key1", "=key1"},
testcase{"%tag#tagId", "<tag id=\"tagId\" />"},
testcase{"#tagId", "<div id=\"tagId\" />"},
testcase{"%tag#tagId", "<tag id=\"tagId\"></tag>"},
testcase{"#tagId", "<div id=\"tagId\"></div>"},
testcase{"%tag#tagId.tagClass= key1", "<tag id=\"tagId\" class=\"tagClass\">value1</tag>"},
testcase{"#tagId tag content", "<div id=\"tagId\">tag content</div>"},
testcase{"%tag#tagId= key1", "<tag id=\"tagId\">value1</tag>"},
Expand All @@ -44,29 +44,29 @@ var autoCloseTests = []testcase{
testcase{"%a{:href => \"/another/page\"}<\n %span.button Press me!", "<a href=\"/another/page\"><span class=\"button\">Press me!</span></a>"},
testcase{"%a{:href => \"/another/page\"}<\n %span.button Press me!\n %span Me, too!", "<a href=\"/another/page\"><span class=\"button\">Press me!</span>\n<span>Me, too!</span></a>"},
testcase{"%p\n %a<\n %span Press me!\n %span\n %span Me, too\n %span And, me!", "<p>\n\t<a><span>Press me!</span>\n\t<span>\n\t\t<span>Me, too</span>\n\t</span>\n\t<span>And, me!</span></a>\n</p>"},
testcase{".tagClass{:attribute => key2}", "<div class=\"tagClass\" attribute=\"value2\" />"},
testcase{".tagClass{key1 => key2}", "<div class=\"tagClass\" value1=\"value2\" />"},
testcase{".tagClass{:attribute => key2}", "<div class=\"tagClass\" attribute=\"value2\"></div>"},
testcase{".tagClass{key1 => key2}", "<div class=\"tagClass\" value1=\"value2\"></div>"},
testcase{"#tagId= complexKey.SubKey1", "<div id=\"tagId\">Fortune presents gifts not according to the book.</div>"},
testcase{"#tagId= complexKey.SubKey2.SubKey1", "<div id=\"tagId\">That's what I said.</div>"},
testcase{"#tagId= complexKey.SubKey2.SubKey2", "<div id=\"tagId\">5</div>"},
testcase{"#tagId= complexKey.SubKey2.SubKey3", "<div id=\"tagId\">0.1</div>"},
testcase{"#tagId= complexKey.SubKey2.SubKey4.SubKey1", "<div id=\"tagId\">Down deep.</div>"},
testcase{"#tagId= complexKey.SubKey2.SubKey4.SubKey2", "<div id=\"tagId\">3</div>"},
testcase{"#tagId= complexKey.SubKey2.SubKey4.SubKey3", "<div id=\"tagId\">0.2</div>"},
testcase{"#tagId= complexKey.SubKey2.SubKey4.SubKey4", "<div id=\"tagId\" />"},
testcase{"#tagId= complexKey.SubKey2.SubKey4.SubKey4", "<div id=\"tagId\"></div>"},
testcase{"=complexKey.SubKey2.SubKey3", "0.1"},
testcase{"=complexKey.SubKey3.key", "I got map!"},
testcase{"%p= key1", "<p>value1</p>"},
testcase{"%tag{:attribute1 => \"value1\", :attribute2 => \"value2\"}", "<tag attribute1=\"value1\" attribute2=\"value2\" />"},
testcase{"%tag{:attribute1 => \"value1\", :attribute2 => \"value2\"}", "<tag attribute1=\"value1\" attribute2=\"value2\"></tag>"},
testcase{"%tag{:attribute1 => \"value1\", :attribute2 => \"value2\"} tag content", "<tag attribute1=\"value1\" attribute2=\"value2\">tag content</tag>"},
testcase{"%tag#tagId.tagClass{:id => \"tagId\", :class => \"tagClass\"} tag content", "<tag id=\"tagId tagId\" class=\"tagClass tagClass\">tag content</tag>"},
testcase{"%tag#tagId{:attribute => \"value\"} tag content", "<tag id=\"tagId\" attribute=\"value\">tag content</tag>"},
testcase{"%input{:type => \"checkbox\", :checked => true}", "<input type=\"checkbox\" checked=\"checked\" />"},
testcase{"%input{:type => \"checkbox\", :checked => false}", "<input type=\"checkbox\" />"},
testcase{"%input{:type => \"checkbox\", :checked => outputTrue}", "<input type=\"checkbox\" checked=\"checked\" />"},
testcase{"%input{:type => \"checkbox\", cd => outputTrue}", "<input type=\"checkbox\" checked=\"checked\" />"},
testcase{"%one\n %two\n %three\n", "<one>\n\t<two>\n\t\t<three />\n\t</two>\n</one>"},
testcase{"%one\n %two\n %three\n ", "<one>\n\t<two>\n\t\t<three />\n\t</two>\n</one>"},
testcase{"%one\n %two\n %three\n", "<one>\n\t<two>\n\t\t<three></three>\n\t</two>\n</one>"},
testcase{"%one\n %two\n %three\n ", "<one>\n\t<two>\n\t\t<three></three>\n\t</two>\n</one>"},
testcase{"!!!", "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\">"},
testcase{"!!! Strict", "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Strict//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd\">"},
testcase{"!!! Frameset", "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Frameset//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-frameset.dtd\">"},
Expand Down Expand Up @@ -98,20 +98,19 @@ func TestAutoCloseIO(t *testing.T) {
output := engine.Render(scope)
if output != io.expected {
t.Errorf("(%d) Input %q\nexpected %q\ngot %q", i, io.input, io.expected, output)
return
}
}
}

var noAutoCloseTests = []testcase{
testcase{"%tag", "<tag>"},
testcase{"%tag/", "<tag />"},
testcase{"%tag.tagClass", "<tag class=\"tagClass\">"},
testcase{"%tag.tagClass1.tagClass2", "<tag class=\"tagClass1 tagClass2\">"},
testcase{".tagClass", "<div class=\"tagClass\">"},
testcase{"%tag#tagId", "<tag id=\"tagId\">"},
testcase{"#tagId", "<div id=\"tagId\">"},
testcase{"%tag{:attribute1 => \"value1\", :attribute2 => \"value2\"}", "<tag attribute1=\"value1\" attribute2=\"value2\">"},
testcase{"%tag", "<tag></tag>"},
testcase{"%tag/", "<tag></tag>"},
testcase{"%tag.tagClass", "<tag class=\"tagClass\"></tag>"},
testcase{"%tag.tagClass1.tagClass2", "<tag class=\"tagClass1 tagClass2\"></tag>"},
testcase{".tagClass", "<div class=\"tagClass\"></div>"},
testcase{"%tag#tagId", "<tag id=\"tagId\"></tag>"},
testcase{"#tagId", "<div id=\"tagId\"></div>"},
testcase{"%tag{:attribute1 => \"value1\", :attribute2 => \"value2\"}", "<tag attribute1=\"value1\" attribute2=\"value2\"></tag>"},
}

func TestNoAutoCloseIO(t *testing.T) {
Expand All @@ -125,7 +124,6 @@ func TestNoAutoCloseIO(t *testing.T) {
output := engine.Render(scope)
if output != io.expected {
t.Errorf("(%d)Input %q\nexpected %q\ngot %q", i, io.input, io.expected, output)
return
}
}
}
19 changes: 9 additions & 10 deletions haml_nesting_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,16 @@ package gohaml
import "testing"

var nestingTests = []testcase{
testcase{"%tag1\n %tag2", "<tag1>\n <tag2 />\n</tag1>"},
testcase{"%tag1\n%tag2", "<tag1 />\n<tag2 />"},
testcase{"%tag1\n%tag2\n%tag3", "<tag1 />\n<tag2 />\n<tag3 />"},
testcase{"%tag1\n %tag2\n %tag3", "<tag1>\n\t<tag2 />\n\t<tag3 />\n</tag1>"},
testcase{"%tag1\n %tag2\n %tag3", "<tag1>\n\t<tag2>\n\t\t<tag3 />\n\t</tag2>\n</tag1>"},
testcase{"%tag1\n %tag2", "<tag1>\n\t<tag2></tag2>\n</tag1>"},
testcase{"%tag1\n%tag2", "<tag1></tag1>\n<tag2></tag2>"},
testcase{"%tag1\n%tag2\n%tag3", "<tag1></tag1>\n<tag2></tag2>\n<tag3></tag3>"},
testcase{"%tag1\n %tag2\n %tag3", "<tag1>\n\t<tag2></tag2>\n\t<tag3></tag3>\n</tag1>"},
testcase{"%tag1\n %tag2\n %tag3", "<tag1>\n\t<tag2>\n\t\t<tag3></tag3>\n\t</tag2>\n</tag1>"},
testcase{"%tag1\n %tag2\n %tag3 tag content", "<tag1>\n\t<tag2>\n\t\t<tag3>tag content</tag3>\n\t</tag2>\n</tag1>"},
testcase{"%tag1\n %tag2\n %tag3 tag content\n %tag4", "<tag1>\n\t<tag2>\n\t\t<tag3>tag content</tag3>\n\t\t<tag4 />\n\t</tag2>\n</tag1>"},
testcase{"%tag1\n %tag2\n %tag3\n %tag4 tag content", "<tag1>\n\t<tag2>\n\t\t<tag3 />\n\t\t<tag4>tag content</tag4>\n\t</tag2>\n</tag1>"},
testcase{"%tag1\n %tag2\n %tag3\n %tag4", "<tag1>\n\t<tag2>\n\t\t<tag3 />\n\t</tag2>\n\t<tag4 />\n</tag1>"},
testcase{"%tag1\n %tag4 tag content\n %tag2#tag2Id.class2.class3\n %tag3", "<tag1>\n\t<tag4>tag content</tag4>\n\t<tag2 id=\"tag2Id\" class=\"class2 class3\">\n\t\t<tag3 />\n\t</tag2>\n</tag1>"},
testcase{"%tag1\n %tag2\n %tag3 tag content\n %tag4", "<tag1>\n\t<tag2>\n\t\t<tag3>tag content</tag3>\n\t\t<tag4></tag4>\n\t</tag2>\n</tag1>"},
testcase{"%tag1\n %tag2\n %tag3\n %tag4 tag content", "<tag1>\n\t<tag2>\n\t\t<tag3></tag3>\n\t\t<tag4>tag content</tag4>\n\t</tag2>\n</tag1>"},
testcase{"%tag1\n %tag2\n %tag3\n %tag4", "<tag1>\n\t<tag2>\n\t\t<tag3></tag3>\n\t</tag2>\n\t<tag4></tag4>\n</tag1>"},
testcase{"%tag1\n %tag4 tag content\n %tag2#tag2Id.class2.class3\n %tag3", "<tag1>\n\t<tag4>tag content</tag4>\n\t<tag2 id=\"tag2Id\" class=\"class2 class3\">\n\t\t<tag3></tag3>\n\t</tag2>\n</tag1>"},
}

func TestNesting(t *testing.T) {
Expand All @@ -25,7 +25,6 @@ func TestNesting(t *testing.T) {
output := engine.Render(scope)
if output != io.expected {
t.Errorf("(%d)Input %q\nexpected %q\ngot %q", i, io.input, io.expected, output)
return
}
}
}
25 changes: 16 additions & 9 deletions parser.go
Original file line number Diff line number Diff line change
Expand Up @@ -185,18 +185,23 @@ func parseAttributes(input string, node *node, line int) (output inode, err erro
inRocket := false
keyEnd, attrStart := 0, 0
for i, r := range input {
if inKey && (r == '=' || unicode.IsSpace(r)) {
if inKey && (r == '=' || (r == ':' && input[0] != ':') || unicode.IsSpace(r)) {
inKey = false
inRocket = true
keyEnd = i
} else if inRocket && r != '>' && r != '=' && r != '}' && !unicode.IsSpace(r) {
inRocket = false
attrStart = i
} else if r == ',' {
} else if r == ',' && (input[i-1] == '"' || input[i-4:i] == "true" || input[i-5:i] == "false") { //input[i-1] == '\'' ||
node.addAttr(t(input[0:keyEnd]), t(input[attrStart:i]))
output, err = parseAttributes(tl(input[i+1:]), node, line)
break
} else if r == '}' {
if i < len(input)-1 {
if input[i+1] == '}' || input[i-1] == '}' || input[i+1] == '"' {
continue
}
}
if attrStart == 0 {
msg := fmt.Sprintf("Syntax error on line %d: Attribute requires a value.\n", line)
//err = os.NewError(msg)
Expand Down Expand Up @@ -298,13 +303,15 @@ func parseClass(input string, node *node, line int) (output inode, err error) {
}

func parseRemainder(input string, node *node, line int) (output inode) {
if input[len(input)-1] == '<' {
node = parseNoNewline("", node, line)
node._remainder.value = input[0 : len(input)-1]
node._remainder.needsResolution = false
} else {
node._remainder.value = input
node._remainder.needsResolution = false
if len(input) > 0 {
if input[len(input)-1] == '<' {
node = parseNoNewline("", node, line)
node._remainder.value = input[0 : len(input)-1]
node._remainder.needsResolution = false
} else {
node._remainder.value = input
node._remainder.needsResolution = false
}
}
output = node
return
Expand Down
37 changes: 32 additions & 5 deletions tree.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,22 @@ import (
"strings"
)

var voidElements map[string]bool = map[string]bool{
"area": true,
"base": true,
"br": true,
"col": true,
"hr": true,
"img": true,
"input": true,
"link": true,
"meta": true,
"param": true,
"command": true,
"keygen": true,
"source": true,
}

type res struct {
value string
needsResolution bool
Expand Down Expand Up @@ -208,12 +224,20 @@ func (self node) outputChildren(scope map[string]interface{}, buf *bytes.Buffer,
buf.WriteString(self._name)
buf.WriteString(">")
} else {
if autoclose || self._autoclose {
buf.WriteString(" />")
if isVoidElement, ok := voidElements[self._name]; ok && isVoidElement {
if autoclose || self._autoclose {
buf.WriteString(" />")
} else {
buf.WriteString(">")
}
} else {
buf.WriteString(">")
buf.WriteString("</")
buf.WriteString(self._name)
buf.WriteString(">")
}
}

}

func contains(value string, slice []string) bool {
Expand Down Expand Up @@ -272,15 +296,18 @@ func (self *node) addChild(n inode) {
}

func (self *node) addAttr(key string, value string) {
keyLookup, valueLookup := true, true
keyLookup, valueLookup := false, true
if key[0] == ':' {
keyLookup = false
// keyLookup = false
key = key[1:]
} else if key[0] == '"' || key[0] == '\'' {
// keyLookup = false
key = key[1 : len(key)-1]
}
if value == "true" || value == "false" {
valueLookup = false
}
if value[0] == '"' {
if value[0] == '"' || value[0] == '\'' {
valueLookup = false
value = value[1 : len(value)-1]
}
Expand Down