src: use `ASuperExpr::mpropdef` instead of asking the frame or visitors
[nit.git] / lib / html.nit
index 6e8f8a6..48de688 100644 (file)
@@ -39,6 +39,7 @@ class HTMLPage
 
        # Render the page as a html string
        fun render: String do
+               root.children.clear
                open("head")
                head
                close("head")
@@ -92,7 +93,13 @@ end
 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
@@ -123,7 +130,13 @@ class HTMLTag
                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
+               self.classes.add_all(classes)
+               return self
+       end
 
        # Set a CSS 'value' for 'prop'
        # var img = new HTMLTag("img")
@@ -148,71 +161,125 @@ class HTMLTag
        # 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.html      ==  "<p>Hello World!</p>"
+       # Text is escaped see: `standard::String::html_escape`
        fun text(txt: String): HTMLTag do
-               content = txt
+
+               children.clear
+               append(txt)
                return self
        end
-       private var content: String = ""
 
        # Append text to element
-       # var p = new HTMLTag("p")
-       # p.append("Hello").append("<br/>").append("World!")
+       #     var p = new HTMLTag("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
-               text("{content}{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("<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 content = render_content
-               if tag != "script" and content.is_empty then return "<{tag}{render_attrs}/>"
-               return "<{tag}{render_attrs}>{content}</{tag}>"
+               var res = new Array[String]
+               render_in(res)
+               return res.to_s
        end
 
-       private fun render_attrs: String do
-               var str = "{render_classes}{render_css}"
-               for key, value in attrs do
-                       if key == "class" or key == "style" then continue
-                       str = "{str} {key}='{value}'"
+       # 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
-               return str
+               out.close
        end
 
-       private fun render_css: String do
-               var css = ""
-               if attrs.has_key("style") then css = attrs["style"]
-               for key, value in self.css_props do
-                       css = "{css}; {key}: {value}"
+       # 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 css.is_empty then return ""
-               return " style='{css}'"
        end
 
-       private fun render_classes: String do
-               var cls = ""
-               if attrs.has_key("class") then cls = attrs["class"]
-               if not classes.is_empty then cls = " class='{cls} {classes.join(" ")}'"
-               if cls.is_empty then return ""
-               return cls
-       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
+                       if res.last == " " then res.pop
+                       res.add "\""
+               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_content: String do
-               var str = content.html_escape
-               for child in children do
-                       str += child.html
+               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 "\""
                end
-               return str
        end
 end
 
 private class HTMLRaw
        super HTMLTag
 
+       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