#This class can be used to generate HTML. #===Examples # ele = HtmlGen::Element.new(:a) #=> # # ele.classes << "custom_link" # ele.css["font-weight"] = "bold" # ele.attr[:href] = "http://www.youtube.com" # # b = ele.add_ele(:b) # b.str = "Title of link" # # ele.html #=> "\n\t\n\t\tTitle of link\n\t\n\n" class HtmlGen::Element FORBIDDEN_SHORT = ["script"] #Attributes hash which will be used to generate attributes-elements. #===Example # element.attr[:href] = "http://www.youtube.com" attr_reader :attr #CSS-hash which will be used to generate the 'style'-attribute. #===Example # element.css["font-weight"] = "bold" attr_reader :css #Data hash which will nest keys. attr_reader :data #Classes-array which will be used to generate the 'class'-attribute. #===Example # element.classes += ["class1", "class2"] # element.html #=> ... class="class1 class2"... attr_reader :classes #This string is used to generate the value of an element. It will be HTML-escaped. #===Example # element = HtmlGen::Element.new("b") # element.str = "Test" # element.html(pretty: false) #=> "Te<i>s</i>t" attr_accessor :str #This string is used to generate the value of an element. It will not be HTML-escaped. #===Example # element = HtmlGen::Element.new("b") # element.str_html = "Test" # element.html #=> "Test" attr_accessor :str_html #An array holding all the sub-elements of this element. attr_accessor :eles #The name of the element. "a" for and such. attr_accessor :name #You can give various arguments as shortcuts to calling the methods. You can also decide what should be used for newline and indentation. # HtmlGen::Element.new(:b, { # css: {"font-weight" => "bold"}, # attr: {"href" => "http://www.youtube.com"}, # classes: ["class1", "class2"], # str: "A title", # str_html: "Some custom URL as title", # nl: "\n", # inden: "\t", # eles: [HtmlGen::Element.new("i", str: "Hello world!") # }) def initialize(name, args = {}) raise "'name' should be a string or a symbol but was a '#{name.class.name}'."if !name.is_a?(String) && !name.is_a?(Symbol) @name = name {attr: {}, data: {}, classes: [], str_html: "", str: "", css: {}, eles: [], nl: "\n", inden: "\t"}.each do |arg, default_val| if args[arg] instance_variable_set("@#{arg}", args[arg]) else instance_variable_set("@#{arg}", default_val) end args.delete(arg) end raise "Unknown arguments: '#{args.keys.join(",")}'." if !args.empty? end #Adds a sub-element to the element. #===Examples # element = HtmlGen::Element.new("a") # another_ele = element.add_ele("b") # another_ele.str = "Hello world!" # element.html #=> "\n\t\n\t\tHello world!\n\t\n\n" def add_ele(name, args = {}) ele = HtmlGen::Element.new(name, args.merge(nl: @nl, inden: @inden)) @eles << ele return ele end alias add add_ele #Add a text-element to the element. def add_str(str) ele = HtmlGen::TextEle.new(str: str, inden: @inden, nl: @nl) @eles << ele return ele end # Returns the HTML for the element. # To avoid indentation and newlines you can use the 'pretty'-argument: # element.html(pretty: false) def html(args = {}) if args[:level] level = args[:level] else level = 0 end if args.key?(:pretty) pretty = args[:pretty] else pretty = true end #Used for keeping 'pretty'-value and correct indentation according to parent elements. pass_args = {level: (level + 1), pretty: pretty, inden: @inden} #Clone the attributes-hash since we are going to add stuff to it, and it shouldnt be reflected (if 'html' is called multiple times, it will bug unless we clone). attr = @attr.clone #Start generating the string with HTML (possible go give a custom 'str'-variable where the content should be pushed to). if args[:str] str = args[:str] else str = "" end str << @inden * level if pretty && level > 0 str << "<#{@name}" #Add content from the 'css'-hash to the 'style'-attribute in the right format. unless @css.empty? style = "" @css.each do |key, val| style << "; " unless style.empty? style << "#{key}: #{val};" end if attr[:style] && !attr[:style].empty? attr[:style] << "; #{style}" else attr[:style] = style end end #Add content from the 'classes'-array to the 'class'-attribute in the right format. unless @classes.empty? class_str = @classes.join(" ") if @attr[:class] && !@attr[:class].empty? attr[:class] << " #{class_str}" else attr[:class] = class_str end end #Write out the attributes to the string. attr.each do |key, val| str << " #{key}=\"#{HtmlGen.escape_html(val)}\"" end str << " #{data_attributes(@data, "data")}" if @data.any? forbidden_short = FORBIDDEN_SHORT.include?(@name.to_s) skip_pretty = false if @eles.empty? && @str.empty? && @str_html.empty? && !forbidden_short #If no sub-string, sub-HTML or sub-elements are given, we should end the HTML with " />". str << " />" str << @nl if pretty else #Write end-of-element and then all sub-elements. str << ">" if @eles.empty? && @str.empty? && @str_html.empty? && forbidden_short skip_pretty = true end str << @nl if pretty && !skip_pretty unless @str.empty? str << @inden * (level + 1) if pretty str << HtmlGen.escape_html(@str) str << @nl if pretty end unless @str_html.empty? str << @inden * (level + 1) if pretty str << @str_html str << @nl if pretty end @eles.each do |subele| str << subele.html(pass_args) end str << @inden * level if pretty && level > 0 && !skip_pretty str << "" str << @nl if pretty end #Returns the string. return str end #Returns the names of all sub-elements in an array. def eles_names names = [] @eles.each do |ele| names << ele.name end return names end #Converts the content of the 'style'-attribute to css-hash-content. def convert_style_to_css if !@attr[:style].to_s.strip.empty? style = @attr[:style] elsif !@attr["style"].to_s.strip.empty? style = @attr["style"] else raise "No style set in element." end loop do if match = style.match(/\A\s*(\S+?):\s*(.+?)\s*(;|\Z)/) style.gsub!(match[0], "") key = match[1] val = match[2] raise "Such a key already exists in CSS-hash: '#{key}'." if @css.key?(key) @css[key] = val elsif match = style.slice!(/\A\s*\Z/) break else raise "Dont know what to do with style-variable: '#{style}'." end end end def convert_data_attributes_to_data @attr.delete_if do |key, value| match = key.to_s.match(/\Adata-(.+)\Z/) if match data_keys = match[1].split("-") last_key = data_keys.pop current_data_element = @data data_keys.each do |key_part| current_data_element = current_data_element[key_part] ||= {} end current_data_element[last_key] = value true else false end end end private def data_attributes(data_hash, prev_key) html = "" data_hash.each do |key, value| if value.is_a?(Hash) html << " " unless html.empty? html << "#{data_attributes(value, "#{prev_key}-#{key}")}" else html << " " unless html.empty? html << "#{prev_key}-#{key}=\"#{HtmlGen.escape_html(value)}\"" end end return html end end