require "crafty/safety" module Crafty module Tools class << self ESCAPE_SEQUENCE = { "&" => "&", ">" => ">", "<" => "<", '"' => """ } # Escape HTML/XML unsafe characters. def escape(content) return content if content.html_safe? content.gsub(/[&><"]/) { |char| ESCAPE_SEQUENCE[char] } end # Formats the given hash of attributes into a string that can be used # directly inside an HTML/XML tag. def format_attributes(attributes) return if attributes.nil? attributes.collect do |name, value| value = if value.kind_of? Array value.flatten.compact * " " else value.to_s end next if value == "" %Q{ #{name}="#{escape(value)}"} end.join end # Formats the given array of parameters into a string that can be used # directly inside an HTML/XML (entity) declaration. def format_parameters(parameters) return if parameters.nil? parameters.collect do |name| name = name.inspect if name.kind_of? String %Q{ #{name}} end.join end # Creates a safe string buffer or wraps the given object in an object # that acts like a safe string buffer. def create_stream(base) if base.respond_to? :<< SafeWrapper.new(base) else SafeString.new end end end # Write an element with the given name, content and attributes. If # there is no content and no block is given, a self-closing element is # created. Provide an empty string as content to create an empty, # non-self-closing element. def element!(name, content = nil, attributes = nil) build! do if content or block_given? @_crafted << "<#{name}#{Tools.format_attributes(attributes)}>" if block_given? value = yield content = value if !@_appended or value.kind_of? String end content = content.to_s @_crafted << Tools.escape(content) if content != "" @_crafted << "</#{name}>" else @_crafted << "<#{name}#{Tools.format_attributes(attributes)}/>" end end end # Write a comment with the given content. def comment!(content) build! do @_crafted << "<!-- " @_crafted << Tools.escape(content.to_s) @_crafted << " -->" end end # Write a processing instruction with the given name and attributes. # Without arguments, it creates a default xml processing instruction. def instruct!(name = nil, attributes = {}) unless name name = "xml" attributes = { :version => "1.0", :encoding => "UTF-8" } end build! do @_crafted << "<?#{name}#{Tools.format_attributes(attributes)}?>" end end # Write a (doctype or entity) declaration with the given name and # parameters. def declare!(name, *parameters) build! do @_crafted << "<!#{name}#{Tools.format_parameters(parameters)}>" end end # Write the given text, escaping it if necessary. def text!(content) build! do @_crafted << Tools.escape(content.to_s) end end alias_method :write!, :text! # Collects all elements built inside the given block into a single string. def build! @_appended = false if @_crafted yield @_appended = true nil else begin @_crafted = Tools.create_stream(self) yield @_crafted.render ensure @_crafted = nil end end end end end