1 # This file is part of NIT ( http://www.nitlanguage.org ).
3 # Licensed under the Apache License, Version 2.0 (the "License");
4 # you may not use this file except in compliance with the License.
5 # You may obtain a copy of the License at
7 # http://www.apache.org/licenses/LICENSE-2.0
9 # Unless required by applicable law or agreed to in writing, software
10 # distributed under the License is distributed on an "AS IS" BASIS,
11 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 # See the License for the specific language governing permissions and
13 # limitations under the License.
15 # HTML output facilities
20 # You can define subclass and override methods head and body
24 # redef body do add("p").text("Hello World!")
27 # HTMLPage use fluent interface so you can chain calls as:
28 # add("div").attr("id", "mydiv").text("My Div")
37 private var root
= new HTMLTag("html")
38 private var current
: HTMLTag = root
39 private var stack
= new List[HTMLTag]
41 redef fun write_to
(stream
) do
49 stream
.write
"<!DOCTYPE html>"
53 # Add a html tag to the current element
54 # add("div").attr("id", "mydiv").text("My Div")
55 fun add
(tag
: String): HTMLTag do
56 var node
= new HTMLTag(tag
)
61 # Add a raw html string
62 # add_html("<a href='#top'>top</a>")
63 fun add_html
(html
: String) do current
.add
(new HTMLRaw(html
))
67 # add("li").text("item1")
68 # add("li").text("item2")
70 fun open
(tag
: String): HTMLTag do
76 # Close previously opened tag
77 # Ensure: tag = previous.tag
78 fun close
(tag
: String) do
79 if not tag
== current
.tag
then
80 print
"Error: Trying to close '{tag}', last opened tag was '{current.tag}'."
90 # HTML tagname: 'div' for <div></div>
94 self.is_void
= (once
["area", "base", "br", "col", "command", "embed", "hr", "img", "input", "keygen", "link", "meta", "param", "source", "track", "wbr"]).has
(tag
)
97 # Is the HTML element a void element?
100 init with_attrs
(tag
: String, attrs
: Map[String, String]) do
106 var attrs
: Map[String, String] = new HashMap[String, String]
108 # Get the attributed value of 'prop' or null if 'prop' is undifened
109 fun get_attr
(key
: String): nullable String do
110 if not attrs
.has_key
(key
) then return null
114 # Set a 'value' for 'key'
115 # var img = new HTMLTag("img")
116 # img.attr("src", "./image.png").attr("alt", "image")
117 fun attr
(key
: String, value
: String): HTMLTag do
122 # Add a CSS class to the HTML tag
123 # var img = new HTMLTag("img")
124 # img.add_class("logo").add_class("fullpage")
125 fun add_class
(klass
: String): HTMLTag do
129 var classes
: Set[String] = new HashSet[String]
131 # Add multiple CSS classes
132 fun add_classes
(classes
: Collection[String]): HTMLTag do
133 self.classes
.add_all
(classes
)
137 # Set a CSS 'value' for 'prop'
138 # var img = new HTMLTag("img")
139 # img.css("border", "2px solid black").css("position", "absolute")
140 fun css
(prop
: String, value
: String): HTMLTag do
141 css_props
[prop
] = value
144 private var css_props
: Map[String, String] = new HashMap[String, String]
146 # Get CSS value for 'prop'
147 fun get_css
(prop
: String): nullable String do
148 if not css_props
.has_key
(prop
) then return null
149 return css_props
[prop
]
152 # Add a HTML 'child' to self
153 # var ul = new HTMLTag("ul")
154 # ul.add(new HTMLTag("li"))
155 fun add
(child
: HTMLTag) do children
.add
(child
)
157 # List of children HTML elements
158 var children
: Set[HTMLTag] = new HashSet[HTMLTag]
160 # Clear all child and set the text of element
161 # var p = new HTMLTag("p")
162 # p.text("Hello World!")
163 # assert p.write_to_string == "<p>Hello World!</p>"
164 # Text is escaped see: `standard::String::html_escape`
165 fun text
(txt
: String): HTMLTag do
172 # Append text to element
173 # var p = new HTMLTag("p")
175 # p.add(new HTMLTag("br"))
177 # assert p.write_to_string == "<p>Hello<br/>World!</p>"
178 # Text is escaped see: standard::String::html_escape
179 fun append
(txt
: String): HTMLTag do
180 add
(new HTMLRaw(txt
.html_escape
))
184 # Append raw HTML to element
185 # var p = new HTMLTag("p")
187 # p.add_raw_html("<bla/>")
188 # p.html #- "<p>Hello<bla/></p>"
189 # Note: the HTML in insered as it, no verification is done
190 fun add_raw_html
(txt
: String): HTMLTag do
191 add
(new HTMLRaw(txt
))
195 redef fun write_to
(stream
) do
196 var res
= new Array[String]
203 # In order to avoid recursive concatenation,
204 # this function collects in `res` all the small fragments of `String`
205 private fun render_in
(res
: Sequence[String])
210 if is_void
and children
.is_empty
then
214 for child
in children
do child
.render_in
(res
)
221 private fun render_attrs_in
(res
: Sequence[String]) do
222 if attrs
.has_key
("class") or not classes
.is_empty
then
224 for cls in classes do
225 res.add cls.html_escape
228 if attrs.has_key("class") then
229 res.add attrs["class"].html_escape
232 if res.last == " " then res.pop
236 if attrs
.has_key
("style") or not css_props
.is_empty
then
239 res.add k.html_escape
241 res.add v.html_escape
244 if attrs.has_key("style
") then
245 res.add(attrs["style
"].html_escape)
247 if res.last == "; " then res.pop
251 if attrs
.is_empty
then return
253 for key
, value
in attrs
do
254 if key
== "class" or key
== "style" then continue
256 res
.add key
.html_escape
258 res.add value.html_escape
264 private class HTMLRaw
267 private var content
: String
268 init(content
: String) do self.content
= content
269 redef fun render_in
(res
) do res
.add content