Merge: doc: fixed some typos and other misc. corrections
[nit.git] / lib / html / html.nit
index 83ff255..9c1753b 100644 (file)
@@ -19,15 +19,20 @@ module html
 #
 # You can define subclass and override methods head and body
 #
+# ~~~nitish
 # class MyPage
 #      super HTMLPage
 #      redef body do add("p").text("Hello World!")
 # end
+# ~~~
 #
 # HTMLPage use fluent interface so you can chain calls as:
-#      add("div").attr("id", "mydiv").text("My Div")
+#
+# ~~~nitish
+# add("div").attr("id", "mydiv").text("My Div")
+# ~~~
 class HTMLPage
-       super Streamable
+       super Writable
 
        # Define head content
        fun head do end
@@ -51,7 +56,10 @@ class HTMLPage
        end
 
        # Add a html tag to the current element
+       #
+       # ~~~nitish
        # add("div").attr("id", "mydiv").text("My Div")
+       # ~~~
        fun add(tag: String): HTMLTag do
                var node = new HTMLTag(tag)
                current.add(node)
@@ -59,14 +67,20 @@ class HTMLPage
        end
 
        # Add a raw html string
+       #
+       # ~~~nitish
        # add_html("<a href='#top'>top</a>")
+       # ~~~
        fun add_html(html: String) do current.add(new HTMLRaw("", html))
 
        # Open a html tag
+       #
+       # ~~~nitish
        # open("ul")
        # add("li").text("item1")
        # add("li").text("item2")
        # close("ul")
+       # ~~~
        fun open(tag: String): HTMLTag do
                stack.push(current)
                current = add(tag)
@@ -84,13 +98,21 @@ class HTMLPage
        end
 end
 
+# An HTML element.
 class HTMLTag
-       super Streamable
+       super Writable
 
-       # HTML tagname: 'div' for <div></div>
+       # HTML element type.
+       #
+       # `"div"` for `<div></div>`.
        var tag: String
        init do
-               self.is_void = (once ["area", "base", "br", "col", "command", "embed", "hr", "img", "input", "keygen", "link", "meta", "param", "source", "track", "wbr"]).has(tag)
+               self.is_void = (once void_list).has(tag)
+       end
+
+       private fun void_list: Set[String]
+       do
+               return new HashSet[String].from(["area", "base", "br", "col", "command", "embed", "hr", "img", "input", "keygen", "link", "meta", "param", "source", "track", "wbr"])
        end
 
        # Is the HTML element a void element?
@@ -99,15 +121,17 @@ class HTMLTag
        #     assert (new HTMLTag("p")).is_void      == false
        var is_void: Bool is noinit
 
+       # Create a HTML elements with the specifed type and attributes.
        init with_attrs(tag: String, attrs: Map[String, String]) do
-               self.tag = tag
+               init(tag)
                self.attrs = attrs
        end
 
        # Tag attributes map
-       var attrs: Map[String, String] = new HashMap[String, String]
+       var attrs: Map[String, String] = new ArrayMap[String, String] is lazy
 
        # 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"
@@ -117,6 +141,7 @@ class HTMLTag
        end
 
        # Set a 'value' for 'key'
+       #
        #     var img = new HTMLTag("img")
        #     img.attr("src", "./image.png").attr("alt", "image")
        #     assert img.write_to_string      == """<img src=".&#47;image.png" alt="image"/>"""
@@ -126,6 +151,7 @@ class HTMLTag
        end
 
        # Add a CSS class to the HTML tag
+       #
        #     var img = new HTMLTag("img")
        #     img.add_class("logo").add_class("fullpage")
        #     assert img.write_to_string      == """<img class="logo fullpage"/>"""
@@ -135,9 +161,10 @@ class HTMLTag
        end
 
        # CSS classes
-       var classes: Set[String] = new HashSet[String]
+       var classes: Set[String] = new ArraySet[String] is lazy
 
        # Add multiple CSS classes
+       #
        #     var img = new HTMLTag("img")
        #     img.add_classes(["logo", "fullpage"])
        #     assert img.write_to_string      == """<img class="logo fullpage"/>"""
@@ -147,6 +174,7 @@ class HTMLTag
        end
 
        # Set a CSS 'value' for 'prop'
+       #
        #     var img = new HTMLTag("img")
        #     img.css("border", "2px solid black").css("position", "absolute")
        #     assert img.write_to_string      == """<img style="border: 2px solid black; position: absolute"/>"""
@@ -154,9 +182,10 @@ class HTMLTag
                css_props[prop] = value
                return self
        end
-       private var css_props: Map[String, String] = new HashMap[String, String]
+       private var css_props: Map[String, String] = new HashMap[String, String] is lazy
 
        # 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"
@@ -190,6 +219,7 @@ class HTMLTag
         end
 
        # Add a HTML 'child' to self
+       #
        #     var ul = new HTMLTag("ul")
        #     ul.add(new HTMLTag("li"))
        #     assert ul.write_to_string    == "<ul><li></li></ul>"
@@ -214,27 +244,29 @@ class HTMLTag
        end
 
        # List of children HTML elements
-       var children: Set[HTMLTag] = new HashSet[HTMLTag]
+       var children: Array[HTMLTag] = new Array[HTMLTag] is lazy
 
        # Clear all child and set the text of element
+       #
        #     var p = new HTMLTag("p")
        #     p.text("Hello World!")
        #     assert p.write_to_string      ==  "<p>Hello World!</p>"
-       # Text is escaped see: `standard::String::html_escape`
+       # Text is escaped see: `core::String::html_escape`
        fun text(txt: String): HTMLTag do
 
-               children.clear
+               if isset _children then children.clear
                append(txt)
                return self
        end
 
        # Append text to element
+       #
        #     var p = new HTMLTag("p")
        #     p.append("Hello")
        #     p.add(new HTMLTag("br"))
        #     p.append("World!")
        #     assert p.write_to_string      ==  "<p>Hello<br/>World!</p>"
-       # Text is escaped see: standard::String::html_escape
+       # Text is escaped see: core::String::html_escape
        fun append(txt: String): HTMLTag do
                add(new HTMLRaw("", txt.html_escape))
                return self
@@ -254,70 +286,61 @@ class HTMLTag
        end
 
        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 "/>"
+               stream.write "<"
+               stream.write tag
+               render_attrs_in(stream)
+               if is_void and (not isset _children or children.is_empty) then
+                       stream.write "/>"
                else
-                       res.add ">"
-                       for child in children do child.render_in(res)
-                       res.add "</"
-                       res.add tag
-                       res.add ">"
+                       stream.write ">"
+                       if isset _children then for child in children do child.write_to(stream)
+                       stream.write "</"
+                       stream.write tag
+                       stream.write ">"
                end
        end
 
-       private fun render_attrs_in(res: Sequence[String]) do
+       private fun render_attrs_in(stream: Writer) do
+               if not isset _attrs and not isset _classes and not isset _css_props then return
                if attrs.has_key("class") or not classes.is_empty then
-                       res.add " class=\""
+                       stream.write " class=\""
+                       var first = true
                        for cls in classes do
-                               res.add cls.html_escape
-                               res.add " "
+                               if not first then stream.write " " else first = false
+                               stream.write cls.html_escape
                        end
                        if attrs.has_key("class") then
-                               res.add attrs["class"].html_escape
-                               res.add " "
+                               if not first then stream.write " " else first = false
+                               stream.write attrs["class"].html_escape
                        end
-                       if res.last == " " then res.pop
-                       res.add "\""
+                       stream.write "\""
                end
 
                if attrs.has_key("style") or not css_props.is_empty then
-                       res.add " style=\""
+                       stream.write " style=\""
+                       var first = true
                        for k, v in css_props do
-                               res.add k.html_escape
-                               res.add ": "
-                               res.add v.html_escape
-                               res.add "; "
+                               if not first then stream.write "; " else first = false
+                               stream.write k.html_escape
+                               stream.write ": "
+                               stream.write v.html_escape
                        end
                        if attrs.has_key("style") then
-                               res.add(attrs["style"].html_escape)
+                               if not first then stream.write "; " else first = false
+                               stream.write(attrs["style"].html_escape)
                        end
-                       if res.last == "; " then res.pop
-                       res.add "\""
+                       stream.write "\""
                end
 
                if attrs.is_empty then return
 
                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 "\""
+                       stream.write " "
+                       stream.write key.html_escape
+                       stream.write "=\""
+                       stream.write value.html_escape
+                       stream.write "\""
                end
        end
 end
@@ -326,5 +349,5 @@ private class HTMLRaw
        super HTMLTag
 
        var content: String
-       redef fun render_in(res) do res.add content
+       redef fun write_to(stream) do stream.write content
 end