class HTMLTag
# HTML tagname: 'div' for <div></div>
var tag: String
- init(tag: String) do self.tag = tag
+ init(tag: String) do
+ self.tag = tag
+ self.is_void = (once ["area", "base", "br", "col", "command", "embed", "hr", "img", "input", "keygen", "link", "meta", "param", "source", "track", "wbr"]).has(tag)
+ end
+
+ # Is the HTML element a void element?
+ var is_void: Bool
init with_attrs(tag: String, attrs: Map[String, String]) do
self.tag = tag
classes.add(klass)
return self
end
- private var classes: Set[String] = new HashSet[String]
+ var classes: Set[String] = new HashSet[String]
# Add multiple CSS classes
fun add_classes(classes: Collection[String]): HTMLTag do
# Clear all child and set the text of element
# var p = new HTMLTag("p")
# p.text("Hello World!")
- # p.html # -> "<p>Hello World!</p>"
+ # assert p.html == "<p>Hello World!</p>"
# Text is escaped see: `standard::String::html_escape`
fun text(txt: String): HTMLTag do
# Append text to element
# var p = new HTMLTag("p")
- # p.append("Hello").add(new HTMLTag("br")).append("World!")
- # p.html # -> "<p>Hello<br/>World!</p>"
+ # p.append("Hello")
+ # p.add(new HTMLTag("br"))
+ # p.append("World!")
+ # assert p.html == "<p>Hello<br/>World!</p>"
# Text is escaped see: standard::String::html_escape
fun append(txt: String): HTMLTag do
add(new HTMLRaw(txt.html_escape))
return self
end
+ # Append raw HTML to element
+ # var p = new HTMLTag("p")
+ # p.append("Hello")
+ # p.add_raw_html("<bla/>")
+ # p.html #- "<p>Hello<bla/></p>"
+ # Note: the HTML in insered as it, no verification is done
+ fun add_raw_html(txt: String): HTMLTag do
+ add(new HTMLRaw(txt))
+ return self
+ end
+
# Render the element as HTML string
fun html: String do
- var attrs = render_attrs
- var content = render_content
- var str = new Buffer
- str.append("<{tag}")
- str.append(attrs)
- if tag != "script" and content.is_empty then
- str.append("/>")
- else
- str.append(">")
- str.append(content)
- str.append("</{tag}>")
- end
- return str.to_s
+ var res = new Array[String]
+ render_in(res)
+ return res.to_s
end
- private fun render_attrs: String do
- var cls = render_classes
- var css = render_css
- var str = new Buffer
- if not cls.is_empty then
- str.append(" ")
- str.append(render_classes)
+ # Save html page in the specified file
+ fun save(file: String) do
+ var out = new OFStream.open(file)
+ var res = new Array[String]
+ render_in(res)
+ for r in res do
+ out.write(r)
end
- if not css.is_empty then
- str.append(" ")
- str.append(render_css)
+ out.close
+ end
+
+ # In order to avoid recursive concatenation,
+ # this function collects in `res` all the small fragments of `String`
+ private fun render_in(res: Sequence[String])
+ do
+ res.add "<"
+ res.add tag
+ render_attrs_in(res)
+ if is_void and children.is_empty then
+ res.add "/>"
+ else
+ res.add ">"
+ for child in children do child.render_in(res)
+ res.add "</"
+ res.add tag
+ res.add ">"
end
- if not attrs.is_empty then str.append(" ")
- var count = 0
- for key, value in attrs do
- if key == "class" or key == "style" then continue
- str.append("{key}=\"{value}\"")
- if count < attrs.length - 1 then
- str.append(" ")
+ end
+
+ private fun render_attrs_in(res: Sequence[String]) do
+ if attrs.has_key("class") or not classes.is_empty then
+ res.add " class=\""
+ for cls in classes do
+ res.add cls.html_escape
+ res.add " "
+ end
+ if attrs.has_key("class") then
+ res.add attrs["class"].html_escape
+ res.add " "
end
- count += 1
+ if res.last == " " then res.pop
+ res.add "\""
end
- return str.to_s
- end
- private fun render_css: String do
- if not attrs.has_key("style") and css_props.is_empty then return ""
- var css = new Buffer
- css.append("style=\"")
- if attrs.has_key("style") then css.append("{attrs["style"]}; ")
- css.append(css_props.join("; ", ": "))
- css.append("\"")
- return css.to_s
- end
+ if attrs.has_key("style") or not css_props.is_empty then
+ res.add " style=\""
+ for k, v in attrs do
+ res.add k.html_escape
+ res.add ": "
+ res.add v.html_escape
+ res.add "; "
+ end
+ if attrs.has_key("style") then
+ res.add(attrs["style"].html_escape)
+ end
+ if res.last == "; " then res.pop
+ res.add "\""
+ end
- private fun render_classes: String do
- if not attrs.has_key("class") and classes.is_empty then return ""
- var cls = new Buffer
- cls.append("class=\"")
- if attrs.has_key("class") then cls.append("{attrs["class"]} ")
- cls.append(classes.join(" "))
- cls.append("\"")
- return cls.to_s
- end
+ if attrs.is_empty then return
- private fun render_content: String do
- var str = new Buffer
- for child in children do
- str.append(child.html)
+ for key, value in attrs do
+ if key == "class" or key == "style" then continue
+ res.add " "
+ res.add key.html_escape
+ res.add "=\""
+ res.add value.html_escape
+ res.add "\""
end
- return str.to_s
end
end
private var content: String
init(content: String) do self.content = content
redef fun html do return content
+ redef fun render_in(res) do res.add content
end