8
\$\begingroup\$

Emmet is a text-editor and IDE plugin for writing HTML code using much fewer keypresses. You might think of it like a code-golfing HTML preprocessor. Its syntax is based on CSS selectors.

Your challenge is to an implementation of Emmet. Given some Emmet code, output the resulting HTML.

Note that for the purposes of this challenge, you will not have to handle element "shortcuts" like bqblockquote or aa[href]. This version of Emmet does not support item numbering. You also do not need to handle the difference between block and inline elements having different spacing.

All elements should have at least one whitespace character (space, tab, newline, whatever) wherever whitespace is shown here, but you don't need to have the indentation shown in the examples.

Syntax

(Paraphrased from Emmet's documentation)

Emmet basically just describes an HTML document using syntax similar to CSS selectors. It uses symbols to describe where each element should be placed in the document.

Structure operators

  • Child: >

    div>ul>li
    <div>
      <ul>
        <li></li>
      </ul>
    </div>
    
  • Sibling: +

    div+p+blockquote
    <div></div>
    <p></p>
    <blockquote></blockquote>
    
  • Put them together:

    div+div>p>span+em 
    <div></div>
    <div>
      <p>
        <span></span>
        <em></em>
      </p>
    </div>
    
  • Climb-up: ^

    div+div>p>span+em^blockquote
    <div></div>
    <div>
      <p>
        <span></span>
        <em></em>
      </p>
      <blockquote></blockquote>
    </div>
    
    div+div>p>span+em^^blockquote
    <div></div>
    <div>
      <p>
        <span></span>
        <em></em>
      </p>
    </div>
    <blockquote></blockquote>
    
  • Multiplication: *

    ul>li*5
    <ul>
      <li></li>
      <li></li>
      <li></li>
      <li></li>
      <li></li>
    </ul>
    
    p>span*2+a
    <p>
      <span></span>
      <span></span>
      <a></a>
    </p>`
    
    p>span*2^q
    <p>
      <span></span>
      <span></span>
    </p>
    <q></q>
    
  • Grouping: ()

    div>(header>ul>li*2>a)+footer>p
    <div>
      <header>
        <ul>
          <li>
            <a></a>
          </li>
          <li>
            <a></a>
          </li>
        </ul>
      </header>
      <footer>
        <p></p>
      </footer>
    </div>
    

Attribute operators

  • ID # and class .

    div#header+div.page+div#footer.class1.class2.class3
    <div id="header"></div>
    <div class="page"></div>
    <div id="footer" class="class1 class2 class3"></div>
    
  • Custom attributes [attr]

    td[title="Hello world!" colspan=3]
    <td title="Hello world!" colspan="3"></td>
    
    td[title=hello colspan=3]
    <td title="hello" colspan="3"></td>
    
    td[title colspan]
    <td title="" colspan=""></td>
    

Text

Put text in curly braces:

p{Hello, World!}
<p>Hello, World!</p>
a.important[href="https://www.youtube.com/watch?v=dQw4w9WgXcQ"]{click}
<a
  class="important"
  href="https://www.youtube.com/watch?v=dQw4w9WgXcQ"
>
  click
</a>
a[href]{click}+b{here}
<a href="">click</a>
<b>here</b>
a[href]>{click}+b{here}
<a href="">
  click
  <b>
    here
  </b>
</a>
p>{Click}+button{here}+{to continue}
<p>
  Click
  <button>
    here
  </button>
  to continue
</p>
p{(a+b)*c>a+b*c}
<p>{(a+b)*c>a+b*c}</p>

Additional test cases

div>p[block]*2>{abc}+span*2{123}+{def}
<div>
  <p block="">
    abc
    <span>123</span>
    <span>123</span>
    def
  </p>
</div>
a[href="https://example.com/index.html?a[]={1}&a[]={2}"]{This is an example usage of array parameters "a[]" in url.}
<a href="https://example.com/index.html?a[]={1}&a[]={2}">
  This is an example usage of array parameters "a[]" in url.
</a>

Your program should give proper output for all of the examples outlined above. This is , so the shortest code per language, measured in bytes, wins!

\$\endgroup\$
14
  • \$\begingroup\$ Related \$\endgroup\$
    – noodle man
    Oct 17 at 17:25
  • \$\begingroup\$ div+div>p>span+em^^^bq seems illegal since it has three ^s but only two >s. \$\endgroup\$
    – Neil
    Oct 17 at 19:20
  • \$\begingroup\$ @Neil Typo, fixed, thanks! \$\endgroup\$
    – noodle man
    Oct 17 at 19:33
  • \$\begingroup\$ Can we assume that no "[]{} used as literal? For example: a[href="https://example.com/index.html?a[]={1}&a[]={2}"]{This is an example usage of array parameters "a[]" in url.} will not be given as input \$\endgroup\$
    – tsh
    Oct 18 at 3:29
  • 1
    \$\begingroup\$ @Arnauld That was probably a copy-paste mistake, thanks for pointing it out. \$\endgroup\$
    – noodle man
    Oct 19 at 11:20

3 Answers 3

4
\$\begingroup\$

Retina, 465 bytes

\^+
$&+
+`([^>]+>((\()|(?<-3>\))|(?(3)>)|[^()>])+(?(3)^))\^
($1)
\)|$
/$&
{%`^( *[^()>+]+)\*(\d+)(.*)
$2*$($1$3¶)
G`.
%`^(( *)((\()|(?<-4>\))|(?(4)[>+])|[^()>+])+(?(4)^))\+
$1/¶$2
%`^( *)(((\()|(?<-4>\))|(?(4)[>+])|[^()>+])+(?(4)^))\>(.+)
$1$2¶  $1$5¶$1/$2
)%`^( *)\((.+)\)/$
$1$2
%`^( *)((/|())\w+)(.*?)(/)?$
$1<$2>$#4*$5$#6*$(</$2>
>#(\w+)
 id="$1">
+`>\.(\w+)
 class="$1">
+`(class=".+)" class="
$1 
]
 ]
+`>\[(\w+)(="([^"]*)"|=([^ ]*))? 
 $1="$3$4">[
[]{[]|}/?

Try it online! Link includes test cases. Explanation:

\^+
$&+

Add a + after any ^s.

+`([^>]+>((\()|(?<-3>\))|(?(3)>)|[^()>])+(?(3)^))\^
($1)

Convert ^s into the equivalent brackets.

\)|$
/$&

Innermost elements should be on a single line.

{`
)`

Process as much as possible.

%`^( *[^()>+]+)\*(\d+)(.*)
$2*$($1$3¶)
G`.

Process multiplications.

%`^(( *)((\()|(?<-4>\))|(?(4)[>+])|[^()>+])+(?(4)^))\+
$1/¶$2

Process siblings.

%`^( *)(((\()|(?<-4>\))|(?(4)[>+])|[^()>+])+(?(4)^))\>(.+)
$1$2¶  $1$5¶$1/$2

Process children, appending a closing tag after them.

%`^( *)\((.+)\)/$
$1$2

Unwrap lines starting and ending in brackets.

%`^( *)((/|())\w+)(.*?)(/)?$
$1<$2>$#4*$5$#6*$(</$2>

Surround tags with <>s, removing attributes from closing tags, and add closing tags for single-line tags.

>#(\w+)
 id="$1">

Process ids.

+`>\.(\w+)
 class="$1">
+`(class=".+)" class="
$1 

Process classes.

]
 ]
+`>\[(\w+)(="([^"]*)"|=([^ ]*))? 
 $1="$3$4">[

Process attributes. I can do this on one line but it turns out to be a byte longer.

[]{[]|}/?

Remove the remaining syntax.

\$\endgroup\$
3
\$\begingroup\$

Python3, 1152 bytes:

import re
R=re.findall
def U(t):
 [N]=R('^\w+',t);n='<'+N
 if(i:=R('(?<=#)\w+',t)):n+=f' id="{i[0]}"'
 if(c:=R('(?<=\.)\w+',t)):n+=' class="'+' '.join(c)+'"'
 if(a:=R('(?<=\[)[^\]]+',t)):n+=' '+' '.join(i+'=""'if'='not in i else re.sub('(?<=\=)[^"]+',lambda x:f'"{x.group()}"',i)for i in R('(?:\w+\="[^"]+")+|(?:\w+\=[^\s\]]+)+|\w+',a[0]))
 e=''
 if(o:=re.findall('(?<=\{)[^\}]+',t)):e=o[0]+' '
 return n+'>'+e,f'</{N}>'
F=lambda t,I:t if type(t)==list else '  '*I+''.join(U(t))
def f(s,I,c,l):
 if'('==s[0]:s.pop(0);t=[];f(s,I+1,t,[]);s.insert(0,t);
 t=s.pop(0)
 if')'!=t:
  if[]!=s:
   S=s.pop(0)
   if')'==S:c.append('  '*I+f'<{t}></{t}>')
   if'>'==S:Q,W=U(t);C=[];c.append(T:=['  '*I+Q,C,'  '*I+W]);l.append(T);f(s,I+1,C,l)
   if'+'==S:C=[];f(s,I,C,l);c.append(F(t,I));c.extend(C)
   if'^'==S[0]:c.append(F(t,I));C=[];f(s,len(l)-len(S),C,l);l[-len(S)].extend(C)
   if'*'==S:x=s.pop(0);s.insert(0,t);C=[];f(s,I,C,l);c.extend(T:=[C for _ in range(int(x))])
  else:Q,W=U(t);c.append('  '*I+Q+W)
P=lambda x:'\n'.join(i if type(i)==str else P(i)for i in x)
def M(s):S=R('\>|\+|\*|\+|\^+|\(|\)|[^(?:\>\+\*\+\^\(\))]+',s);l,c=[],[];f(S,0,c,l);return P(c)

Try it online!

\$\endgroup\$
3
  • \$\begingroup\$ Note that the indentation is actually optional, there just has to be at least one whitespace character wherever I showed whitespace in the examples. Will try to emphasize this a bit more in the post. \$\endgroup\$
    – noodle man
    Oct 17 at 20:40
  • \$\begingroup\$ -2 bytes by moving the e='' line in U to the declaration, and removing the space between else and ' in F \$\endgroup\$ Oct 18 at 8:04
  • \$\begingroup\$ Doesn't work on the last test case \$\endgroup\$ Oct 20 at 16:06
2
\$\begingroup\$

JavaScript (Node.js), 425 bytes

i=>(s=i.match(/\w+|"[^"]*"|\{[^}]*\}|\S/g),S=_=>L=s.shift()||"",U=_=>s.unshift(L)&&"",T=v=>S()==v?' ':U(),D=c=>T`#`?' id='+S()+D(c):T`.`?D(c+(c&&' ')+S()):T`[`?A()+D(c):c&&` class="${c}"`,A=_=>T`]`?'':' '+S()+(T`=`?L+S():'=""')+A(),W=_=>S()[0]=="{"?L.slice(1,-1):U(),P=(r='',l=W()||(T`(`?P()+T`)`:T`^`?U():(r=`</${S()}> `,` <${L}${D('')}>`+W())))=>Array(T`*`?-~S():2).join(l+W()+(T`>`&&P()+(T`^`&&U(L='+')))+r)+(T`+`&&P()))()

Try it online!

Following commented code based on a previous version:

i=>(
  // tokenize the input
  //   <word>: \w+
  //   <string>: "[^"]*"
  //   <text>: \{[^}]*\}
  //   other operator: \S
  s=i.match(/\w+|"[^"]*"|\{[^}]*\}|\S/g),
  // parse the input
  P())
// Get first token, the token is consumed
S=_=>L=s.shift()||""
// Undo last get, the token is placed back
U=_=>s.unshift(L)&&""
// Test if next token is given value
// Consume the token and return truthy whitespace string if match
// Or return falsy empty string if not match
T=v=>S()==v?' ':U()
// P (whole program) ::= V "+" P | V ">" P (lookahead != "^") | V ">" P "^" P | <empty>
// V (an HTML tag) ::= <text> | "(" P ")" | <word> D | V "*" <word>
P=(
  // right part
  r='',
  // left part
  l=
    W()|| // V -> <text>
    (T`(`?P()+T`)`: // V -> "(" P ")"
     T`^`?U(): // reduce
     (r=`</${S()}> `,` <${L}${D``}>`+W())) // V -> <word> D
)=>
  T`+`?l+r+P(): // P -> V "+" P
  T`>`?l+P()+r+(T`^`&&P()): // P -> V ">" P ["^" P]?
  T`*`?Array(-~S()).join(P(r,l)): // V -> V "*" <word>
  l+r // reduce
// D (all atributes) ::= "#" <word> | "." <word> | "[" A | <empty>
D=c=> // `c` is all class names collected yet
  T`#`?' id='+S()+D(c): // D -> "#" <word> D
  T`.`?D(c+' '+S()): // D -> "." <word> D
  T`[`?A()+D(c): // D -> "[" A D
  c+c&&` class="${c}"` // reduce
// A (`[]` style attributes) ::= <word> A | <word> "=" <word> A | <word> "=" <string> A | "]"
A=_=>
  T`]`?'': // "]"
  ' '+S()+(T`=`?L+S():'=""')+A() // <word> ["=" (<word> | <string>)]? A
// W slice <text> to remove "{}" or return empty string if next token is not <text>
W=_=>S()[0]=="{"?L.slice(1,-1):U()
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.