class Phlex::SGML
**Standard Generalized Markup Language** for behaviour common to {HTML} and {SVG}.
def self.method_added(method_name)
def self.method_added(method_name) iew_template _method(method_name).source_location[0] /" | "." ribute_cache__(location)
def __attributes__(attributes, buffer = +"")
def __attributes__(attributes, buffer = +"") attributes.each do |k, v| next unless v name = case k when String then k when Symbol then k.name.tr("_", "-") else raise Phlex::ArgumentError.new("Attribute keys should be Strings or Symbols.") end value = case v when true true when String v.gsub('"', """) when Symbol v.name.tr("_", "-").gsub('"', """) when Integer, Float v.to_s when Hash case k when :style __styles__(v).gsub('"', """) else __nested_attributes__(v, "#{name}-", buffer) end when Array case k when :style __styles__(v).gsub('"', """) else __nested_tokens__(v) end when Set case k when :style __styles__(v).gsub('"', """) else __nested_tokens__(v.to_a) end when Phlex::SGML::SafeObject v.to_s.gsub('"', """) else raise Phlex::ArgumentError.new("Invalid attribute value for #{k}: #{v.inspect}.") end lower_name = name.downcase unless Phlex::SGML::SafeObject === v normalized_name = lower_name.delete("^a-z-") if value != true && REF_ATTRIBUTES.include?(normalized_name) case value when String if value.downcase.delete("^a-z:").start_with?("javascript:") # We just ignore these because they were likely not specified by the developer. next end else raise Phlex::ArgumentError.new("Invalid attribute value for #{k}: #{v.inspect}.") end end if normalized_name.bytesize > 2 && normalized_name.start_with?("on") && !normalized_name.include?("-") raise Phlex::ArgumentError.new("Unsafe attribute name detected: #{k}.") end if UNSAFE_ATTRIBUTES.include?(normalized_name) raise Phlex::ArgumentError.new("Unsafe attribute name detected: #{k}.") end end if name.match?(/[<>&"']/) raise Phlex::ArgumentError.new("Unsafe attribute name detected: #{k}.") end if lower_name.to_sym == :id && k != :id raise Phlex::ArgumentError.new(":id attribute should only be passed as a lowercase symbol.") end case value when true buffer << " " << name when String buffer << " " << name << '="' << value << '"' end end buffer end
def __context__ = @_context
def __context__ = @_context
def __element_method__?(method_name)
def __element_method__?(method_name) if instance_methods.include?(method_name) owner = instance_method(method_name).owner if Phlex::SGML::Elements === owner && owner.__registered_elements__[method_name] true else false end else false end end
def __implicit_output__(content)
def __implicit_output__(content) context = @_context return true if context.fragments && !context.in_target_fragment case content when Phlex::SGML::SafeObject context.buffer << content.to_s when String context.buffer << Phlex::Escape.html_escape(content) when Symbol context.buffer << Phlex::Escape.html_escape(content.name) when nil nil else if (formatted_object = format_object(content)) context.buffer << Phlex::Escape.html_escape(formatted_object) else return false end end true end
def __nested_attributes__(attributes, base_name, buffer = +"")
Provides the nested-attributes case for serializing out attributes.
def __nested_attributes__(attributes, base_name, buffer = +"") attributes.each do |k, v| next unless v name = case k when String then k when Symbol then k.name.tr("_", "-") else raise Phlex::ArgumentError.new("Attribute keys should be Strings or Symbols") end if name.match?(/[<>&"']/) raise Phlex::ArgumentError.new("Unsafe attribute name detected: #{k}.") end case v when true buffer << " " << base_name << name when String buffer << " " << base_name << name << '="' << v.gsub('"', """) << '"' when Symbol buffer << " " << base_name << name << '="' << v.name.tr("_", "-").gsub('"', """) << '"' when Integer, Float buffer << " " << base_name << name << '="' << v.to_s << '"' when Hash __nested_attributes__(v, "#{base_name}#{name}-", buffer) when Array buffer << " " << base_name << name << '="' << __nested_tokens__(v) << '"' when Set buffer << " " << base_name << name << '="' << __nested_tokens__(v.to_a) << '"' when Phlex::SGML::SafeObject buffer << " " << base_name << name << '="' << v.to_s.gsub('"', """) << '"' else raise Phlex::ArgumentError.new("Invalid attribute value #{v.inspect}.") end buffer end end
def __nested_tokens__(tokens)
def __nested_tokens__(tokens) buffer = +"" i, length = 0, tokens.length while i < length token = tokens[i] case token when String if i > 0 buffer << " " << token else buffer << token end when Symbol if i > 0 buffer << " " << token.name.tr("_", "-") else buffer << token.name.tr("_", "-") end when Integer, Float, Phlex::SGML::SafeObject if i > 0 buffer << " " << token.to_s else buffer << token.to_s end when Array if token.length > 0 if i > 0 buffer << " " << __nested_tokens__(token) else buffer << __nested_tokens__(token) end end when nil # Do nothing else raise Phlex::ArgumentError.new("Invalid token type: #{token.class}.") end i += 1 end buffer.gsub('"', """) end
def __styles__(styles)
def __styles__(styles) case styles when Array, Set styles.filter_map do |s| case s when String if s == "" || s.end_with?(";") s else "#{s};" end when Phlex::SGML::SafeObject value = s.to_s value.end_with?(";") ? value : "#{value};" when Hash next __styles__(s) when nil next nil else raise Phlex::ArgumentError.new("Invalid style: #{s.inspect}.") end end.join(" ") when Hash buffer = +"" i = 0 styles.each do |k, v| prop = case k when String k when Symbol k.name.tr("_", "-") else raise Phlex::ArgumentError.new("Style keys should be Strings or Symbols.") end value = case v when String v when Symbol v.name.tr("_", "-") when Integer, Float, Phlex::SGML::SafeObject v.to_s when nil nil else raise Phlex::ArgumentError.new("Invalid style value: #{v.inspect}") end if value if i == 0 buffer << prop << ": " << value << ";" else buffer << " " << prop << ": " << value << ";" end end i += 1 end buffer end end
def __text__(content)
def __text__(content) context = @_context return true if context.fragments && !context.in_target_fragment case content when String context.buffer << Phlex::Escape.html_escape(content) when Symbol context.buffer << Phlex::Escape.html_escape(content.name) when nil nil else if (formatted_object = format_object(content)) context.buffer << Phlex::Escape.html_escape(formatted_object) else return false end end true end
def __yield_content__
def __yield_content__ return unless block_given? buffer = @_context.buffer original_length = buffer.bytesize content = yield(self) __implicit_output__(content) if original_length == buffer.bytesize nil end
def __yield_content_with_args__(*a)
def __yield_content_with_args__(*a) return unless block_given? buffer = @_context.buffer original_length = buffer.bytesize content = yield(*a) __implicit_output__(content) if original_length == buffer.bytesize nil end
def __yield_content_with_no_args__
def __yield_content_with_no_args__ return unless block_given? buffer = @_context.buffer original_length = buffer.bytesize content = yield __implicit_output__(content) if original_length == buffer.bytesize nil end
def after_template
def after_template nil end
def around_template
def around_template yield nil end
def before_template
def before_template nil end
def call(...)
def call(...) new(...).call end
def call(buffer = +"", context: {}, view_context: nil, parent: nil, fragments: nil, &block)
def call(buffer = +"", context: {}, view_context: nil, parent: nil, fragments: nil, &block) @_buffer = buffer @_context = phlex_context = parent&.__context__ || Phlex::Context.new(user_context: context, view_context:) @_parent = parent raise Phlex::DoubleRenderError.new("You can't render a #{self.class.name} more than once.") if @_rendered @_rendered = true if fragments phlex_context.target_fragments(fragments) end block ||= @_content_block return "" unless render? Thread.current[:__phlex_component__] = [self, Fiber.current.object_id] phlex_context.around_render do before_template(&block) around_template do if block view_template do |*args| if args.length > 0 __yield_content_with_args__(*args, &block) else __yield_content__(&block) end end else view_template end end after_template(&block) end unless parent buffer << phlex_context.buffer end ensure Thread.current[:__phlex_component__] = [parent, Fiber.current.object_id] end
def capture(*args, &block)
def capture(*args, &block) return "" unless block if args.length > 0 @_context.capturing_into(+"") { __yield_content_with_args__(*args, &block) } else @_context.capturing_into(+"") { __yield_content__(&block) } end end
def comment(&)
Wrap the output in an HTML comment.
def comment(&) context = @_context return if context.fragments && !context.in_target_fragment buffer = context.buffer buffer << "<!-- " __yield_content__(&) buffer << " -->" nil end
def context
def context @_context.user_context end
def flush
def flush return if @_context.capturing buffer = @_context.buffer @_buffer << buffer.dup buffer.clear end
def format_object(object)
def format_object(object) case object when Float, Integer object.to_s end end
def new(*a, **k, &block)
- Note: - The block will not be delegated {#initialize}. Instead, it will be sent to {#template} when rendering.
def new(*a, **k, &block) if block object = super(*a, **k, &nil) object.instance_exec { @_content_block = block } object else super end end
def plain(content)
def plain(content) unless __text__(content) raise Phlex::ArgumentError.new("You've passed an object to plain that is not handled by format_object. See https://rubydoc.info/gems/phlex/Phlex/SGML#format_object-instance_method for more information") end nil end
def raw(content)
def raw(content) case content when Phlex::SGML::SafeObject context = @_context return if context.fragments && !context.in_target_fragment context.buffer << content.to_s when nil, "" # do nothing else raise Phlex::ArgumentError.new("You passed an unsafe object to `raw`.") end nil end
def render(renderable = nil, &)
def render(renderable = nil, &) case renderable when Phlex::SGML renderable.call(@_buffer, parent: self, &) when Class if renderable < Phlex::SGML renderable.new.call(@_buffer, parent: self, &) end when Enumerable renderable.each { |r| render(r, &) } when Proc, Method if renderable.arity == 0 __yield_content_with_no_args__(&renderable) else __yield_content__(&renderable) end when String plain(renderable) when nil __yield_content__(&) if block_given? else raise Phlex::ArgumentError.new("You can't render a #{renderable.inspect}.") end nil end
def render?
def render? true end
def safe(value)
def safe(value) case value when String Phlex::SGML::SafeValue.new(value) else raise Phlex::ArgumentError.new("Expected a String.") end end
def to_proc
def to_proc proc { |c| c.render(self) } end
def vanish(*args)
def vanish(*args) return unless block_given? if args.length > 0 @_context.capturing_into(Phlex::Vanish) { yield(*args) } else @_context.capturing_into(Phlex::Vanish) { yield(self) } end nil end
def view_template
def view_template if block_given? yield end end
def whitespace(&)
def whitespace(&) context = @_context return if context.fragments && !context.in_target_fragment buffer = context.buffer buffer << " " if block_given? __yield_content__(&) buffer << " " end nil end