module Sablon class Configuration # Stores the information for a single HTML tag. This information # is used by the HTMLConverter. An optional AST class can be defined, # and if so conversion stops there and it is assumed the AST class # will handle any child nodes unless the element is a block level tag. # In the case of a block level tag the child nodes are processed by the # AST builder again. If the AST class is omitted it is assumed the node # should be "passed through" only transferring it's properties onto # children. A block level tag must have an AST class associated with # it. The block and inline status of tags is not affected by CSS. # Permitted child tags are specified using the :allowed_children optional # arg. The default value is [:_inline, :ul, :ol]. :_inline is a special # reference to all inline type tags, :_block is equivalent for block # type tags. # # == Parameters # * name - symbol or string of the HTML element tag name # * type - The type of HTML tag needs to be :inline or :block # * ast_class - class instance or symbol, the AST class or it's name # used to process the HTML node # * options - collects all other keyword arguments, Current kwargs are # `:properties`, `:attributes` and `:allowed_children`. # # Example # HTMLTag.new(:div, :block, ast_class: Sablon::HTMLConverter::Paragraph, # properties: { pStyle: 'Normal' }) class HTMLTag attr_reader :name, :type, :ast_class, :attributes, :properties, :allowed_children # Setup HTML tag information def initialize(name, type, ast_class: nil, **options) # Set basic params converting some args to symbols for consistency @name = name.to_sym @type = type.to_sym @ast_class = nil # use self.ast_class to trigger setter method self.ast_class = ast_class if ast_class # Ensure block level tags have an AST class if @type == :block && @ast_class.nil? raise ArgumentError, "Block level tag #{name} must have an AST class." end # Set attributes from optinos hash, currently unused during AST generation @attributes = options.fetch(:attributes, {}) # WordML properties defined by the tag, i.e. for the tag, # etc. All the keys need to be symbols to avoid getting reparsed # with the element's CSS attributes. @properties = options.fetch(:properties, {}) @properties = Hash[@properties.map { |k, v| [k.to_s, v] }] # Set permitted child tags or tag groups self.allowed_children = options[:allowed_children] end # checks if the given tag is a permitted child element def allowed_child?(tag) if @allowed_children.include?(tag.name) true elsif @allowed_children.include?(:_inline) && tag.type == :inline true elsif @allowed_children.include?(:_block) && tag.type == :block true else false end end private def allowed_children=(value) if value.nil? @allowed_children = %i[_inline ol ul] return else value = [value] unless value.is_a? Array end @allowed_children = value.map(&:to_sym) end # converts a string or symbol to a class defined under # Sablon::HTMLConverter def ast_class=(value) if value.is_a? Class @ast_class = value return else value = value.to_s end # camel case the word and get class, similar logic to # ActiveSupport::Inflector.constantize but refactored to be specific # to the HTMLConverter class value.gsub!(/(?:^|_)([a-z])/) { Regexp.last_match[1].capitalize } @ast_class = Sablon::HTMLConverter.const_get(value) end end end end