document = _ ignore* _ "" body:elements _ "" _ { return body; } elements = element* element = _ "<" startTag:id _ attr:attr* _ ">" _ children:elements _ "" _ { if (startTag != endTag) { throw new Error("Expected but found."); } return {name: startTag, attr: attr, children: children } } / "<" tag:id _ attr:attr* _ "/>" _ { return {name: tag, attr: attr } } / _ text:text _ { return text } ignore = "" { return } attr = name:id _ "=" _ value:string { return { name:name, value:value } } string = '"' '"' _ { return ""; } / "'" "'" _ { return ""; } / '"' text:quoted '"' _ { return text; } / "'" text:quoted "'" _ { return text; } quoted = chars:[^<>'" \t\n\r]+ { return chars.join(""); } text = chars:[^<> \t\n\r]+ { return chars.join(""); } id = chars:[^<>/'"=? \t\n\r]+ { return chars.join(""); } _ "whitespace" = whitespace* whitespace = [ \t\n\r]