Skip to content

Commit

Permalink
feat: support void element parsing, fixes #637
Browse files Browse the repository at this point in the history
  • Loading branch information
a-h committed May 8, 2024
1 parent 7d8287e commit 503bf71
Show file tree
Hide file tree
Showing 3 changed files with 149 additions and 26 deletions.
50 changes: 49 additions & 1 deletion parser/v2/elementparser.go
Original file line number Diff line number Diff line change
Expand Up @@ -438,6 +438,48 @@ func (elementOpenCloseParser) Parse(pi *parse.Input) (r Element, ok bool, err er
return r, true, nil
}

var voidElementNamesMap = map[string]struct{}{
"area": {},
"base": {},
"br": {},
"col": {},
"command": {},
"embed": {},
"hr": {},
"img": {},
"input": {},
"keygen": {},
"link": {},
"meta": {},
"param": {},
"source": {},
"track": {},
"wbr": {},
}

func getVoidCloser(name string) parse.Parser[string] {
return parse.Func(func(pi *parse.Input) (s string, ok bool, err error) {
// />
// If it's a self-closing element, take that.
_, ok, err = parse.String("/>").Parse(pi)
if err != nil || ok {
return
}

// >
// HTML5 states that void elements are closed with just a >.
_, ok, err = parse.Rune('>').Parse(pi)
if err != nil {
return
}

// Optional </name>
// However, some people like to close them with </name>.
_, ok, err = parse.Optional(parse.All(parse.String("</"), parse.String(name), parse.Rune('>'))).Parse(pi)
return s, ok, err
})
}

// Element self-closing tag.
var selfClosingElement = parse.Func(func(pi *parse.Input) (e Element, ok bool, err error) {
start := pi.Index()
Expand Down Expand Up @@ -471,7 +513,13 @@ var selfClosingElement = parse.Func(func(pi *parse.Input) (e Element, ok bool, e
e.IndentAttrs = true
}

if _, ok, err = parse.String("/>").Parse(pi); err != nil || !ok {
// Parse closer.
var terminatingParser = parse.String("/>")
// Allow void elements to not be closed.
if _, isVoid := voidElementNamesMap[e.Name]; isVoid {
terminatingParser = getVoidCloser(e.Name)
}
if _, ok, err = terminatingParser.Parse(pi); err != nil || !ok {
pi.Seek(start)
return
}
Expand Down
93 changes: 68 additions & 25 deletions parser/v2/elementparser_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,31 +41,6 @@ func TestAttributeParser(t *testing.T) {
},
},
},
{
name: "element: colon in name",
input: `<maps:map>`,
parser: StripType(elementOpenTagParser),
expected: elementOpenTag{
Name: "maps:map",
NameRange: Range{
From: Position{Index: 1, Line: 0, Col: 1},
To: Position{Index: 9, Line: 0, Col: 9},
},
},
},
{
name: "element: colon in name, closing",
input: `<maps:map>Content</maps:map>`,
parser: StripType(element),
expected: Element{
Name: "maps:map",
NameRange: Range{
From: Position{Index: 1, Line: 0, Col: 1},
To: Position{Index: 9, Line: 0, Col: 9},
},
Children: []Node{Text{Value: "Content"}},
},
},
{
name: "element: open with hyperscript attribute",
input: `<div _="show = true">`,
Expand Down Expand Up @@ -602,6 +577,74 @@ func TestElementParser(t *testing.T) {
},
},
},
{
name: "element: colon in name, empty",
input: `<maps:map></maps:map>`,
expected: Element{
Name: "maps:map",
NameRange: Range{
From: Position{Index: 1, Line: 0, Col: 1},
To: Position{Index: 9, Line: 0, Col: 9},
},
},
},
{
name: "element: colon in name, with content",
input: `<maps:map>Content</maps:map>`,
expected: Element{
Name: "maps:map",
NameRange: Range{
From: Position{Index: 1, Line: 0, Col: 1},
To: Position{Index: 9, Line: 0, Col: 9},
},
Children: []Node{Text{Value: "Content"}},
},
},
{
name: "element: void (input)",
input: `<input>`,
expected: Element{
Name: "input",
NameRange: Range{
From: Position{Index: 1, Line: 0, Col: 1},
To: Position{Index: 6, Line: 0, Col: 6},
},
Children: nil,
},
},
{
name: "element: void (br)",
input: `<br>`,
expected: Element{
Name: "br",
NameRange: Range{
From: Position{Index: 1, Line: 0, Col: 1},
To: Position{Index: 3, Line: 0, Col: 3},
},
Children: nil,
},
},
{
name: "element: void (hr)",
input: `<hr noshade>`,
expected: Element{
Name: "hr",
NameRange: Range{
From: Position{Index: 1, Line: 0, Col: 1},
To: Position{Index: 3, Line: 0, Col: 3},
},
Attributes: []Attribute{
BoolConstantAttribute{
Name: "noshade",
NameRange: Range{
From: Position{Index: 4, Line: 0, Col: 4},
To: Position{Index: 11, Line: 0, Col: 11},
},
},
},
Children: nil,
},
},
{
name: "element: self-closing with single bool expression attribute",
input: `<hr noshade?={ true }/>`,
Expand Down
32 changes: 32 additions & 0 deletions parser/v2/types_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,21 +19,37 @@ func TestFormatting(t *testing.T) {
package test
templ input(value, validation string) {
<area>
<area></area>
<base>
<base></base>
<br>
<br></br>
<col>
<col></col>
<command>
<command></command>
<embed>
<embed></embed>
<hr>
<hr></hr>
<img>
<img></img>
<input>
<input></input>
<keygen>
<keygen></keygen>
<link>
<link></link>
<meta>
<meta></meta>
<param>
<param></param>
<source>
<source></source>
<track>
<track></track>
<wbr>
<wbr></wbr>
}
Expand All @@ -43,20 +59,36 @@ package test
templ input(value, validation string) {
<area/>
<area/>
<base/>
<base/>
<br/>
<br/>
<col/>
<col/>
<command/>
<command/>
<embed/>
<embed/>
<hr/>
<hr/>
<img/>
<img/>
<input/>
<input/>
<keygen/>
<keygen/>
<link/>
<link/>
<meta/>
<meta/>
<param/>
<param/>
<source/>
<source/>
<track/>
<track/>
<wbr/>
<wbr/>
}
`,
Expand Down

0 comments on commit 503bf71

Please sign in to comment.