Skip to content

Instantly share code, notes, and snippets.

@taylorlapeyre
Last active August 29, 2015 14:08
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save taylorlapeyre/7739c361a4f98d280722 to your computer and use it in GitHub Desktop.
Save taylorlapeyre/7739c361a4f98d280722 to your computer and use it in GitHub Desktop.
A clever problem for an interview or just for fun

Generating HTML

Say that one day we decide to write a new templating library for generating HTML.

In this new library, you are passed an array where the first element is the name of an HTML tag, the second element is optionally a hash containing the element's HTML attributes, and the rest of the elements are the HTML elements that are children of it.

For example:

html = [:html,
         [:body,
           [:div, {class: "container"},
             [:h1, "Hello World"],
             [:h2, "Awesome!"]
           ]
         ]
       ]

Your task is to write a function that accepts such an array and outputs a valid HTML string.

transform(html)
=> "<html><body><div class=\"container\"><h1>Hello World</h1><h2>Awesome!</h2></div></body></html>"

Assumptions:

  • You will always be passed a valid array
  • The tag name will always come in the form of a symbol (or a string if your language doesn't have symbols).
  • There exists an array named void_tags that contains every void HTML tag.
void_tags = [
    :area,
    :base,
    :br,
    :col,
    :command,
    :embed,
    :hr,
    :img,
    :input,
    :keygen,
    :link,
    :meta,
    :param,
    :source,
    :track,
    :wbr
]
(defn create-attrs
"Takes a map of HTML attributes and values, returns a valid HTML
attribute string."
[attrs]
(let [attr-html #(str " " (name (first %)) "=\"" (second %) "\"")]
(reduce str (map attr-html attrs))))
(defn transform
"Takes a normal HTML tree in clojure form and recursively generates
a valid HTML string with the contents of the tree."
[el]
(cond
(nil? el) ""
(string? el) el
(coll? el)
(let [[tag & body] el
tagname (name tag)]
(if (contains? void-tags tag)
(str "<" tagname (create-attrs (first body)) ">")
(if (map? (first body))
(str
"<" tagname (create-attrs (first body)) ">"
(reduce str (map transform (rest body)))
"</" tagname ">")
(str
"<" tagname ">"
(reduce str (map transform body))
"</" tagname ">"))))))
function createAttrs(attrs) {
return Object.keys(attrs).map(function(key) {
return " " + key + '="' + attrs[key] + '"';
}).join("");
};
function transform(el) {
if (el === null) {
return "";
}
if (typeof el === "string") {
return el;
}
if (el instanceof Array) {
var tag = el[0];
var body = el.slice(1);
if (voidTags.indexOf(tag) != -1) {
return "<" + tag + createAttrs(body[0]) + ">";
}
if (body[0] instanceof Object) {
return [
"<" + tag + createAttrs(body[0]) + ">",
body.slice(1).map(function(child) { return transform(child) }).join(""),
"</" + tag + ">"
].join("");
} else {
return [
"<" + tag + ">",
body.map(function(child) { return transform(child) }).join(""),
"</" + tag + ">"
].join("");
}
}
};
def create_attrs(attrs):
return "".join([
'{0}="{1}"'.format(key, val) for key, val in attrs.items()
])
def transform(el):
if not el: return ""
if isinstance(el, str): return el
if isinstance(el, list):
tag = el[0]
body = el[1:]
if tag in void_tags:
return "<{0} {1}>".format(tag, create_attrs(body[0]))
if isinstance(body[0], dict):
return "".join([
"<{0} {1}>".format(tag, create_attrs(body[0])),
"".join([transform(child) for child in body[1:]]),
"</{0}>".format(tag)
])
else:
return "".join([
"<{0}>".format(tag),
"".join([transform(child) for child in body]),
"</{0}>".format(tag)
])
def create_attrs(attrs)
attrs.map { |key, val|
" #{key}=\"#{val}\""
}.join
end
def transform(root)
return "" if root.nil?
return root if root.is_a? String
if root.is_a? Array
tag = root.first
body = root[1..-1]
if $void_tags.include?(tag)
return "<#{tag}#{create_attrs(body.first)}>"
end
if body.first.is_a? Hash
[
"<#{tag}#{create_attrs(body.first)}>",
body[1..-1].map { |el| transform(el) }.join,
"</#{tag}>"
].join
else
[
"<#{tag}>",
body.map { |el| transform(el) }.join,
"</#{tag}>"
].join
end
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment