X-Git-Url: http://nitlanguage.org diff --git a/lib/html.nit b/lib/html.nit index 75acbfa..4fc8530 100644 --- a/lib/html.nit +++ b/lib/html.nit @@ -27,6 +27,7 @@ module html # HTMLPage use fluent interface so you can chain calls as: # add("div").attr("id", "mydiv").text("My Div") class HTMLPage + super Streamable # Define head content fun head do end @@ -37,8 +38,7 @@ class HTMLPage private var current: HTMLTag = root private var stack = new List[HTMLTag] - # Render the page as a html string - fun render: String do + redef fun write_to(stream) do root.children.clear open("head") head @@ -46,7 +46,8 @@ class HTMLPage open("body") body close("body") - return "{root.html}" + stream.write "" + root.write_to(stream) end # Add a html tag to the current element @@ -81,19 +82,23 @@ class HTMLPage end current = stack.pop end - - # Save html page in the specified file - fun save(file: String) do - var out = new OFStream.open(file) - out.write(self.render) - out.close - end end class HTMLTag + super Streamable + # HTML tagname: 'div' for
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? + # + # assert (new HTMLTag("img")).is_void == true + # assert (new HTMLTag("p")).is_void == false + var is_void: Bool init with_attrs(tag: String, attrs: Map[String, String]) do self.tag = tag @@ -104,31 +109,48 @@ class HTMLTag var attrs: Map[String, String] = new HashMap[String, String] # Get the attributed value of 'prop' or null if 'prop' is undifened + # var img = new HTMLTag("img") + # img.attr("src", "./image.png").attr("alt", "image") + # assert img.get_attr("src") == "./image.png" fun get_attr(key: String): nullable String do if not attrs.has_key(key) then return null return attrs[key] end # Set a 'value' for 'key' - # var img = new HTMLTag("img") - # img.attr("src", "./image.png").attr("alt", "image") + # var img = new HTMLTag("img") + # img.attr("src", "./image.png").attr("alt", "image") + # assert img.write_to_string == """image""" fun attr(key: String, value: String): HTMLTag do attrs[key] = value return self end # Add a CSS class to the HTML tag - # var img = new HTMLTag("img") - # img.add_class("logo").add_class("fullpage") + # var img = new HTMLTag("img") + # img.add_class("logo").add_class("fullpage") + # assert img.write_to_string == """""" fun add_class(klass: String): HTMLTag do classes.add(klass) return self end - private var classes: Set[String] = new HashSet[String] + + # CSS classes + var classes: Set[String] = new HashSet[String] + + # Add multiple CSS classes + # var img = new HTMLTag("img") + # img.add_classes(["logo", "fullpage"]) + # assert img.write_to_string == """""" + fun add_classes(classes: Collection[String]): HTMLTag do + self.classes.add_all(classes) + return self + end # Set a CSS 'value' for 'prop' - # var img = new HTMLTag("img") - # img.css("border", "2px solid black").css("position", "absolute") + # var img = new HTMLTag("img") + # img.css("border", "2px solid black").css("position", "absolute") + # assert img.write_to_string == """""" fun css(prop: String, value: String): HTMLTag do css_props[prop] = value return self @@ -136,114 +158,175 @@ class HTMLTag private var css_props: Map[String, String] = new HashMap[String, String] # Get CSS value for 'prop' + # var img = new HTMLTag("img") + # img.css("border", "2px solid black").css("position", "absolute") + # assert img.get_css("border") == "2px solid black" + # assert img.get_css("color") == null fun get_css(prop: String): nullable String do if not css_props.has_key(prop) then return null return css_props[prop] end + # Replace `self` by `parent`. + # + # var elem = new HTMLTag("li") + # elem.add_outer(new HTMLTag("ul")) + # assert elem.write_to_string == "" + fun add_outer(parent: HTMLTag) do + # copy self in new object + var child = new HTMLTag(self.tag) + child.attrs = self.attrs + child.classes = self.classes + child.css_props = self.css_props + child.children = self.children + # add copy in parent children elements + parent.children.add(child) + # replace self by parent + self.tag = parent.tag + self.attrs = parent.attrs + self.classes = parent.classes + self.css_props = parent.css_props + self.is_void = parent.is_void + self.children = parent.children + end + # Add a HTML 'child' to self - # var ul = new HTMLTag("ul") - # ul.add(new HTMLTag("li")) - fun add(child: HTMLTag) do children.add(child) + # var ul = new HTMLTag("ul") + # ul.add(new HTMLTag("li")) + # assert ul.write_to_string == "" + # returns `self` for fluent programming + fun add(child: HTMLTag): HTMLTag + do + children.add(child) + return self + end + + # Create a new HTMLTag child and return it + # + # var ul = new HTMLTag("ul") + # ul.open("li").append("1").append("2") + # ul.open("li").append("3").append("4") + # assert ul.write_to_string == "" + fun open(tag: String): HTMLTag + do + var res = new HTMLTag(tag) + add(res) + return res + end # List of children HTML elements var children: Set[HTMLTag] = new HashSet[HTMLTag] - # Set text of element - # var p = new HTMLTag("p") - # p.text("Hello World!") - # Text is escaped see: standard::String::html_escape + # Clear all child and set the text of element + # var p = new HTMLTag("p") + # p.text("Hello World!") + # assert p.write_to_string == "

Hello World!

" + # Text is escaped see: `standard::String::html_escape` fun text(txt: String): HTMLTag do - content.clear - content.append(txt) + + children.clear + append(txt) return self end - private var content = new Buffer # Append text to element - # var p = new HTMLTag("p") - # p.append("Hello").append("
").append("World!") + # var p = new HTMLTag("p") + # p.append("Hello") + # p.add(new HTMLTag("br")) + # p.append("World!") + # assert p.write_to_string == "

Hello
World!

" # Text is escaped see: standard::String::html_escape fun append(txt: String): HTMLTag do - content.append(txt) + 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("foo") + # assert p.write_to_string == "

Hellofoo

" + # + # 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("/>") + redef fun write_to(stream) do + var res = new Array[String] + render_in(res) + for r in res do + stream.write(r) + end + 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 - str.append(">") - str.append(content) - str.append("") + res.add ">" + for child in children do child.render_in(res) + res.add "" end - return str.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) + 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 + if res.last == " " then res.pop + res.add "\"" end - if not css.is_empty then - str.append(" ") - str.append(render_css) + + if attrs.has_key("style") or not css_props.is_empty then + res.add " style=\"" + for k, v in css_props 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 - if not attrs.is_empty then str.append(" ") - var count = 0 + + if attrs.is_empty then return + 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 - count += 1 - 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 - - 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 - - private fun render_content: String do - var str = new Buffer - str.append(content.to_s.html_escape) - for child in children do - str.append(child.html) + res.add " " + res.add key.html_escape + res.add "=\"" + res.add value.html_escape + res.add "\"" end - return str.to_s end end private class HTMLRaw super HTMLTag - init(content: String) do self.content.append(content) - redef fun html do return content.to_s + private var content: String + init(content: String) do self.content = content + redef fun render_in(res) do res.add content end