From 566fdd77500f914d9d60cd029416595543e89800 Mon Sep 17 00:00:00 2001 From: Ryan Nuzzaci Date: Tue, 23 Jan 2018 22:59:26 -0700 Subject: [PATCH 1/8] fix to allow element with no children (issue #16) --- tree.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tree.go b/tree.go index 3beb721..8fc81c4 100644 --- a/tree.go +++ b/tree.go @@ -204,9 +204,6 @@ func (self node) outputChildren(scope map[string]interface{}, buf *bytes.Buffer, buf.WriteString("\n") buf.WriteString(curIndent) } - buf.WriteString("") } else { if autoclose || self._autoclose { buf.WriteString(" />") @@ -214,6 +211,9 @@ func (self node) outputChildren(scope map[string]interface{}, buf *bytes.Buffer, buf.WriteString(">") } } + buf.WriteString("") } func contains(value string, slice []string) bool { From ffc7fd6307b0ed649bdd9aa23b6892652215c05a Mon Sep 17 00:00:00 2001 From: Ryan Nuzzaci Date: Wed, 24 Jan 2018 00:04:31 -0700 Subject: [PATCH 2/8] only autoclose void elements --- haml_io_test.go | 42 ++++++++++++++++++++---------------------- haml_nesting_test.go | 19 +++++++++---------- tree.go | 34 +++++++++++++++++++++++++++++----- 3 files changed, 58 insertions(+), 37 deletions(-) diff --git a/haml_io_test.go b/haml_io_test.go index 66c8814..4f798dc 100644 --- a/haml_io_test.go +++ b/haml_io_test.go @@ -22,19 +22,19 @@ type testcase struct { var autoCloseTests = []testcase{ testcase{"plain ∏ text", "plain ∏ text"}, - testcase{"%tag", ""}, + testcase{"%tag", ""}, testcase{"%tag tag content", "tag content"}, - testcase{"%tag.tagClass", ""}, - testcase{"%tag.tagClass1.tagClass2", ""}, - testcase{".tagClass", "
"}, + testcase{"%tag.tagClass", ""}, + testcase{"%tag.tagClass1.tagClass2", ""}, + testcase{".tagClass", "
"}, testcase{".tagClass tag content", "
tag content
"}, testcase{".tagClass1.tagClass2 tag content", "
tag content
"}, testcase{"=key1", "value1"}, testcase{"%tag.tagClass= key1", "value1"}, testcase{"\\%tag.tagClass= key1", "%tag.tagClass= key1"}, testcase{"\\=key1", "=key1"}, - testcase{"%tag#tagId", ""}, - testcase{"#tagId", "
"}, + testcase{"%tag#tagId", ""}, + testcase{"#tagId", "
"}, testcase{"%tag#tagId.tagClass= key1", "value1"}, testcase{"#tagId tag content", "
tag content
"}, testcase{"%tag#tagId= key1", "value1"}, @@ -44,8 +44,8 @@ var autoCloseTests = []testcase{ testcase{"%a{:href => \"/another/page\"}<\n %span.button Press me!", "Press me!"}, testcase{"%a{:href => \"/another/page\"}<\n %span.button Press me!\n %span Me, too!", "Press me!\nMe, too!"}, testcase{"%p\n %a<\n %span Press me!\n %span\n %span Me, too\n %span And, me!", "

\n\tPress me!\n\t\n\t\tMe, too\n\t\n\tAnd, me!\n

"}, - testcase{".tagClass{:attribute => key2}", "
"}, - testcase{".tagClass{key1 => key2}", "
"}, + testcase{".tagClass{:attribute => key2}", "
"}, + testcase{".tagClass{key1 => key2}", "
"}, testcase{"#tagId= complexKey.SubKey1", "
Fortune presents gifts not according to the book.
"}, testcase{"#tagId= complexKey.SubKey2.SubKey1", "
That's what I said.
"}, testcase{"#tagId= complexKey.SubKey2.SubKey2", "
5
"}, @@ -53,11 +53,11 @@ var autoCloseTests = []testcase{ testcase{"#tagId= complexKey.SubKey2.SubKey4.SubKey1", "
Down deep.
"}, testcase{"#tagId= complexKey.SubKey2.SubKey4.SubKey2", "
3
"}, testcase{"#tagId= complexKey.SubKey2.SubKey4.SubKey3", "
0.2
"}, - testcase{"#tagId= complexKey.SubKey2.SubKey4.SubKey4", "
"}, + testcase{"#tagId= complexKey.SubKey2.SubKey4.SubKey4", "
"}, testcase{"=complexKey.SubKey2.SubKey3", "0.1"}, testcase{"=complexKey.SubKey3.key", "I got map!"}, testcase{"%p= key1", "

value1

"}, - testcase{"%tag{:attribute1 => \"value1\", :attribute2 => \"value2\"}", ""}, + testcase{"%tag{:attribute1 => \"value1\", :attribute2 => \"value2\"}", ""}, testcase{"%tag{:attribute1 => \"value1\", :attribute2 => \"value2\"} tag content", "tag content"}, testcase{"%tag#tagId.tagClass{:id => \"tagId\", :class => \"tagClass\"} tag content", "tag content"}, testcase{"%tag#tagId{:attribute => \"value\"} tag content", "tag content"}, @@ -65,8 +65,8 @@ var autoCloseTests = []testcase{ testcase{"%input{:type => \"checkbox\", :checked => false}", ""}, testcase{"%input{:type => \"checkbox\", :checked => outputTrue}", ""}, testcase{"%input{:type => \"checkbox\", cd => outputTrue}", ""}, - testcase{"%one\n %two\n %three\n", "\n\t\n\t\t\n\t\n"}, - testcase{"%one\n %two\n %three\n ", "\n\t\n\t\t\n\t\n"}, + testcase{"%one\n %two\n %three\n", "\n\t\n\t\t\n\t\n"}, + testcase{"%one\n %two\n %three\n ", "\n\t\n\t\t\n\t\n"}, testcase{"!!!", ""}, testcase{"!!! Strict", ""}, testcase{"!!! Frameset", ""}, @@ -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", ""}, - testcase{"%tag/", ""}, - testcase{"%tag.tagClass", ""}, - testcase{"%tag.tagClass1.tagClass2", ""}, - testcase{".tagClass", "
"}, - testcase{"%tag#tagId", ""}, - testcase{"#tagId", "
"}, - testcase{"%tag{:attribute1 => \"value1\", :attribute2 => \"value2\"}", ""}, + testcase{"%tag", ""}, + testcase{"%tag/", ""}, + testcase{"%tag.tagClass", ""}, + testcase{"%tag.tagClass1.tagClass2", ""}, + testcase{".tagClass", "
"}, + testcase{"%tag#tagId", ""}, + testcase{"#tagId", "
"}, + testcase{"%tag{:attribute1 => \"value1\", :attribute2 => \"value2\"}", ""}, } func TestNoAutoCloseIO(t *testing.T) { @@ -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 } } } diff --git a/haml_nesting_test.go b/haml_nesting_test.go index b976008..694d5d8 100644 --- a/haml_nesting_test.go +++ b/haml_nesting_test.go @@ -3,16 +3,16 @@ package gohaml import "testing" var nestingTests = []testcase{ - testcase{"%tag1\n %tag2", "\n \n"}, - testcase{"%tag1\n%tag2", "\n"}, - testcase{"%tag1\n%tag2\n%tag3", "\n\n"}, - testcase{"%tag1\n %tag2\n %tag3", "\n\t\n\t\n"}, - testcase{"%tag1\n %tag2\n %tag3", "\n\t\n\t\t\n\t\n"}, + testcase{"%tag1\n %tag2", "\n\t\n"}, + testcase{"%tag1\n%tag2", "\n"}, + testcase{"%tag1\n%tag2\n%tag3", "\n\n"}, + testcase{"%tag1\n %tag2\n %tag3", "\n\t\n\t\n"}, + testcase{"%tag1\n %tag2\n %tag3", "\n\t\n\t\t\n\t\n"}, testcase{"%tag1\n %tag2\n %tag3 tag content", "\n\t\n\t\ttag content\n\t\n"}, - testcase{"%tag1\n %tag2\n %tag3 tag content\n %tag4", "\n\t\n\t\ttag content\n\t\t\n\t\n"}, - testcase{"%tag1\n %tag2\n %tag3\n %tag4 tag content", "\n\t\n\t\t\n\t\ttag content\n\t\n"}, - testcase{"%tag1\n %tag2\n %tag3\n %tag4", "\n\t\n\t\t\n\t\n\t\n"}, - testcase{"%tag1\n %tag4 tag content\n %tag2#tag2Id.class2.class3\n %tag3", "\n\ttag content\n\t\n\t\t\n\t\n"}, + testcase{"%tag1\n %tag2\n %tag3 tag content\n %tag4", "\n\t\n\t\ttag content\n\t\t\n\t\n"}, + testcase{"%tag1\n %tag2\n %tag3\n %tag4 tag content", "\n\t\n\t\t\n\t\ttag content\n\t\n"}, + testcase{"%tag1\n %tag2\n %tag3\n %tag4", "\n\t\n\t\t\n\t\n\t\n"}, + testcase{"%tag1\n %tag4 tag content\n %tag2#tag2Id.class2.class3\n %tag3", "\n\ttag content\n\t\n\t\t\n\t\n"}, } func TestNesting(t *testing.T) { @@ -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 } } } diff --git a/tree.go b/tree.go index 8fc81c4..afd5f60 100644 --- a/tree.go +++ b/tree.go @@ -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 @@ -204,16 +220,24 @@ func (self node) outputChildren(scope map[string]interface{}, buf *bytes.Buffer, buf.WriteString("\n") buf.WriteString(curIndent) } + 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("") + } func contains(value string, slice []string) bool { From 60405b5ccb557422230861336043167929e746ab Mon Sep 17 00:00:00 2001 From: Ryan Nuzzaci Date: Wed, 24 Jan 2018 01:05:54 -0700 Subject: [PATCH 3/8] allow quoted attribute keys and allow colon (:) attribute key/value separator --- parser.go | 2 +- tree.go | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/parser.go b/parser.go index 1c5a226..c56ae5e 100644 --- a/parser.go +++ b/parser.go @@ -185,7 +185,7 @@ 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 diff --git a/tree.go b/tree.go index afd5f60..f4aaffc 100644 --- a/tree.go +++ b/tree.go @@ -300,6 +300,9 @@ func (self *node) addAttr(key string, value string) { if key[0] == ':' { keyLookup = false key = key[1:] + } else if key[0] == '"' { + keyLookup = false + key = key[1 : len(key)-1] } if value == "true" || value == "false" { valueLookup = false From f26d663647a30d4e6544b47aa53e925bb0d70c6b Mon Sep 17 00:00:00 2001 From: Ryan Nuzzaci Date: Wed, 24 Jan 2018 11:23:09 -0700 Subject: [PATCH 4/8] add single quote use for attribute key/values --- tree.go | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/tree.go b/tree.go index f4aaffc..4cb3c95 100644 --- a/tree.go +++ b/tree.go @@ -296,18 +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] == '"' { - keyLookup = false + } 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] } From a26bb8f090c18096dc1dfdb23f0c278f28808f90 Mon Sep 17 00:00:00 2001 From: Ryan Nuzzaci Date: Wed, 24 Jan 2018 11:56:10 -0700 Subject: [PATCH 5/8] allow spaces after element --- parser.go | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/parser.go b/parser.go index c56ae5e..a58dd03 100644 --- a/parser.go +++ b/parser.go @@ -298,13 +298,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 From 60959eba9b0ac6fcaf2c719e75b7705016d99644 Mon Sep 17 00:00:00 2001 From: Ryan Nuzzaci Date: Tue, 30 Jan 2018 01:41:31 -0700 Subject: [PATCH 6/8] fixed angular conflicts --- parser.go | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/parser.go b/parser.go index a58dd03..4b01cc8 100644 --- a/parser.go +++ b/parser.go @@ -192,11 +192,16 @@ func parseAttributes(input string, node *node, line int) (output inode, err erro } else if inRocket && r != '>' && r != '=' && r != '}' && !unicode.IsSpace(r) { inRocket = false attrStart = i - } else if r == ',' { + } else if r == ',' && (input[i-1] == '\'' || 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) From b22b95aeabb4794807bb9ef025565ef58b1b689c Mon Sep 17 00:00:00 2001 From: Ryan Nuzzaci Date: Tue, 30 Jan 2018 02:13:40 -0700 Subject: [PATCH 7/8] fix to allow true/false before attr sep identifier --- parser.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/parser.go b/parser.go index 4b01cc8..027a2d4 100644 --- a/parser.go +++ b/parser.go @@ -192,7 +192,7 @@ func parseAttributes(input string, node *node, line int) (output inode, err erro } else if inRocket && r != '>' && r != '=' && r != '}' && !unicode.IsSpace(r) { inRocket = false attrStart = i - } else if r == ',' && (input[i-1] == '\'' || input[i-1] == '"') { + } else if r == ',' && (input[i-1] == '\'' || input[i-1] == '"' || input[i-4:i] == "true" || input[i-5:i] == "false") { node.addAttr(t(input[0:keyEnd]), t(input[attrStart:i])) output, err = parseAttributes(tl(input[i+1:]), node, line) break From 89e8c4e2cad1ce257c1d856ffb5c7e73ac20c08e Mon Sep 17 00:00:00 2001 From: Ryan Nuzzaci Date: Tue, 30 Jan 2018 11:13:17 -0700 Subject: [PATCH 8/8] must have double quotes surrounding attr --- parser.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/parser.go b/parser.go index 027a2d4..dfc28e7 100644 --- a/parser.go +++ b/parser.go @@ -192,7 +192,7 @@ func parseAttributes(input string, node *node, line int) (output inode, err erro } else if inRocket && r != '>' && r != '=' && r != '}' && !unicode.IsSpace(r) { inRocket = false attrStart = i - } else if r == ',' && (input[i-1] == '\'' || input[i-1] == '"' || input[i-4:i] == "true" || input[i-5:i] == "false") { + } 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