Solve View

Match maker

Given a CSS query, output the unindented html needed that will produce a match. For example, a.foo>b#bar should produce <a class=‘foo’><b id=‘bar’></b></a>

The html should have full tags, not self closing ones. The query follows the following grammar:

query   ::= element (">" element)*
element ::= name [id] class*
id      ::= "#" name
class   ::= "." name
name    ::= [a-z0-9]+

However for the test cases you should expect at most 9 levels of nesting elements.

Judge

(async function* (context: Context): Challenge {
  // Single Test
  const testcases: [string, string][] = [
    ["h3#title3.class3", "<h3 id='title3' class='class3'></h3>"],
    ["figure>figcaption", "<figure><figcaption></figcaption></figure>"],
    ["span.flair", "<span class='flair'></span>"],
    ["a.b.c.d", "<a class='b c d'></a>"],
    ["main>audio.rickroll", "<main><audio class='rickroll'></audio></main>"],
    [
      "table#scores>tbody.bytes>tr.me",
      "<table id='scores'><tbody class='bytes'><tr class='me'></tr></tbody></table>",
    ],
    ["div>div>div", "<div><div><div></div></div></div>"],
    [
      "html>body>main>div.warning",
      "<html><body><main><div class='warning'></div></main></body></html>",
    ],
    [
      "html#html>html.html",
      "<html id='html'><html class='html'></html></html>",
    ],
    [
      "nav#menu>ul.navlist>li.navitem",
      "<nav id='menu'><ul class='navlist'><li class='navitem'></li></ul></nav>",
    ],
    [
      "form#login>input#user.username",
      "<form id='login'><input id='user' class='username'></input></form>",
    ],
    [
      "article.blogpost>h1.title",
      "<article class='blogpost'><h1 class='title'></h1></article>",
    ],
    [
      "section#about>div.container>p.text",
      "<section id='about'><div class='container'><p class='text'></p></div></section>",
    ],
    [
      "div#wrapper>header.main>h2#subtitle",
      "<div id='wrapper'><header class='main'><h2 id='subtitle'></h2></header></div>",
    ],
    [
      "footer>div.footercontent>p>a#discord",
      "<footer><div class='footercontent'><p><a id='discord'></a></p></div></footer>",
    ],
    [
      "aside#sidebar>div.widget>ul.links",
      "<aside id='sidebar'><div class='widget'><ul class='links'></ul></div></aside>",
    ],
    [
      "main#content>section.hero>img.banner",
      "<main id='content'><section class='hero'><img class='banner'></img></section></main>",
    ],
    [
      "div.grid>div.cell>span.value",
      "<div class='grid'><div class='cell'><span class='value'></span></div></div>",
    ],
    [
      "header#siteheader.siteheader>nav#mainnav>ul.menu>li.item",
      "<header id='siteheader' class='siteheader'><nav id='mainnav'><ul class='menu'><li class='item'></li></ul></nav></header>",
    ],
    [
      "alphabet.a.b.c.d.e.f.g.h.i.j.k.l.m.n.o.p.q.r.s.t.u.v.w.x.y.z",
      "<alphabet class='a b c d e f g h i j k l m n o p q r s t u v w x y z'></alphabet>",
    ],
  ];

  const randomName = () => {
    const alphabet = "abcdefghijklmnopqrstuvwxyz0123456789";
    const length = rand(5) + 5;
    return Array(length)
      .fill()
      .map(() => alphabet[rand(alphabet.length)])
      .join("");
  };

  const populateCase = (k: string, v: string): [string, string] => {
    const names = range(10).map(() => randomName());
    for (let i = 0; i < 10; i++) {
      k = k.replaceAll("$" + i.toString(), names[i]);
      v = v.replaceAll("$" + i.toString(), names[i]);
    }

    return [k, v];
  };

  const randCases = [
    ["$1>$2", "<$1><$2></$2></$1>"],
    ["$1#$2.$3>$4", "<$1 id='$2' class='$3'><$4></$4></$1>"],
  ];

  for (const randomCase of randCases) {
    testcases.push(populateCase(randomCase[0], randomCase[1]));
  }

  const hands = context.runTestCases(testcases);
  for await (const hand of hands) {
    yield context.registerTestCase(hand);
  }

  // Finally, the challenge is passed if no test cases failed
  return context.noFailures();
});

Example Code

const testcases = require('fs').readFileSync(0).toString().split('\n');

for (const testcase of testcases) {
    const parts = testcase.split('>');
    const tagStack = [];
    let result = '';
    
    for (const part of parts) {
        const [tag, ...attributes] = part.split(/(?=[.#])/);
        tagStack.push(tag);
        const id = attributes.find(attr => attr.startsWith('#'))?.slice(1);
        const classList = attributes.filter(attr => attr.startsWith('.')).map(attr => attr.slice(1)).join(' ');
        
        const html = `<${tag}${id ? ` id='${id}'` : ''}${classList ? ` class='${classList}'` : ''}>`;
        result += html;
    }

    result += tagStack.reverse().map(tag => `</${tag}>`).join('');
    console.log(result);
}

Comments