class Sablon::Configuration

Handles storing configuration data for the sablon module

Attributes

defined_style_conversions[RW]
permitted_html_tags[RW]

Public Class Methods

new() click to toggle source
# File lib/sablon/configuration/configuration.rb, line 11
def initialize
  initialize_html_tags
  initialize_css_style_conversion
end

Public Instance Methods

register_html_tag(tag_name, type = :inline, **options) click to toggle source

Adds a new tag to the permitted tags hash or replaces an existing one

# File lib/sablon/configuration/configuration.rb, line 17
def register_html_tag(tag_name, type = :inline, **options)
  tag = HTMLTag.new(tag_name, type, **options)
  @permitted_html_tags[tag.name] = tag
end
register_style_converter(ast_node, prop_name, converter) click to toggle source

Adds a new style property converter for the specified ast class and CSS property name. The ast_class variable should be the class name in lowercased snakecase as a symbol, i.e. MyClass -> :my_class. The converter passed in must be a proc that accepts a single argument (the value) and returns two values: the WordML property name and its value. The converted property value can be a string, hash or array.

# File lib/sablon/configuration/configuration.rb, line 34
def register_style_converter(ast_node, prop_name, converter)
  # create a new ast node hash if needed
  unless @defined_style_conversions[ast_node]
    @defined_style_conversions[ast_node] = {}
  end
  # add the style converter to the node's hash
  @defined_style_conversions[ast_node][prop_name] = converter
end
remove_html_tag(tag_name) click to toggle source

Removes a tag from the permitted tgs hash, returning it

# File lib/sablon/configuration/configuration.rb, line 23
def remove_html_tag(tag_name)
  @permitted_html_tags.delete(tag_name)
end
remove_style_converter(ast_node, prop_name) click to toggle source

Deletes a CSS converter from the hash by specifying the AST class in lowercased snake case and the property name.

# File lib/sablon/configuration/configuration.rb, line 45
def remove_style_converter(ast_node, prop_name)
  @defined_style_conversions[ast_node].delete(prop_name)
end

Private Instance Methods

initialize_css_style_conversion() click to toggle source

Defines an initial set of CSS -> WordML conversion lambdas stored in a nested hash structure where the first key is the AST class and the second is the conversion lambda

# File lib/sablon/configuration/configuration.rb, line 107
def initialize_css_style_conversion
  @defined_style_conversions = {
    # styles shared or common logic across all node types go here.
    # Special conversion lambdas such as :_border can be
    # defined here for reuse across several AST nodes. Care must
    # be taken to avoid possible naming conflicts, hence the underscore.
    # AST class keys should be stored with their names converted from
    # camelcase to lowercased snakecase, i.e. TestCase = test_case
    node: {
      'background-color' => lambda { |v|
        return 'shd', { val: 'clear', fill: v.delete('#') }
      },
      _border: lambda { |v|
        props = { sz: 2, val: 'single', color: '000000' }
        vals = v.split
        vals[1] = 'single' if vals[1] == 'solid'
        #
        props[:sz] = @defined_style_conversions[:node][:_sz].call(vals[0])
        props[:val] = vals[1] if vals[1]
        props[:color] = vals[2].delete('#') if vals[2]
        #
        return props
      },
      _sz: lambda { |v|
        return nil unless v
        (2 * Float(v.gsub(/[^\d.]/, '')).ceil).to_s
      },
      'text-align' => ->(v) { return 'jc', v }
    },
    # Styles specific to the Table AST class
    table: {
      'border' => lambda { |v|
        props = @defined_style_conversions[:node][:_border].call(v)
        #
        return 'tblBorders', [
          { top: props }, { start: props }, { bottom: props },
          { end: props }, { insideH: props }, { insideV: props }
        ]
      },
      'margin' => lambda { |v|
        vals = v.split.map do |s|
          @defined_style_conversions[:node][:_sz].call(s)
        end
        #
        props = [vals[0], vals[0], vals[0], vals[0]] if vals.length == 1
        props = [vals[0], vals[1], vals[0], vals[1]] if vals.length == 2
        props = [vals[0], vals[1], vals[2], vals[1]] if vals.length == 3
        props = [vals[0], vals[1], vals[2], vals[3]] if vals.length > 3
        return 'tblCellMar', [
          { top: { w: props[0], type: 'dxa' } },
          { end: { w: props[1], type: 'dxa' } },
          { bottom: { w: props[2], type: 'dxa' } },
          { start: { w: props[3], type: 'dxa' } }
        ]
      },
      'cellspacing' => lambda { |v|
        v = @defined_style_conversions[:node][:_sz].call(v)
        return 'tblCellSpacing', { w: v, type: 'dxa' }
      },
      'width' => lambda { |v|
        v = @defined_style_conversions[:node][:_sz].call(v)
        return 'tblW', { w: v, type: 'dxa' }
      }
    },
    # Styles specific to the TableCell AST class
    table_cell: {
      'border' => lambda { |v|
        value = @defined_style_conversions[:table]['border'].call(v)[1]
        return 'tcBorders', value
      },
      'colspan' => ->(v) { return 'gridSpan', v },
      'margin' => lambda { |v|
        value = @defined_style_conversions[:table]['margin'].call(v)[1]
        return 'tcMar', value
      },
      'rowspan' => lambda { |v|
        return 'vMerge', 'restart' if v == 'start'
        return 'vMerge', v if v == 'continue'
        return 'vMerge', nil if v == 'end'
      },
      'vertical-align' => ->(v) { return 'vAlign', v },
      'white-space' => lambda { |v|
        return 'noWrap', nil if v == 'nowrap'
        return 'tcFitText', 'true' if v == 'fit'
      },
      'width' => lambda { |v|
        value = @defined_style_conversions[:table]['width'].call(v)[1]
        return 'tcW', value
      }
    },
    # Styles specific to the Paragraph AST class
    paragraph: {
      'border' => lambda { |v|
        props = @defined_style_conversions[:node][:_border].call(v)
        #
        return 'pBdr', [
          { top: props }, { bottom: props },
          { left: props }, { right: props }
        ]
      },
      'vertical-align' => ->(v) { return 'textAlignment', v }
    },
    # Styles specific to a run of text
    run: {
      'color' => ->(v) { return 'color', v.delete('#') },
      'font-size' => lambda { |v|
        return 'sz', @defined_style_conversions[:node][:_sz].call(v)
      },
      'font-style' => lambda { |v|
        return 'b', nil if v =~ /bold/
        return 'i', nil if v =~ /italic/
      },
      'font-weight' => ->(v) { return 'b', nil if v =~ /bold/ },
      'text-decoration' => lambda { |v|
        supported = %w[line-through underline]
        props = v.split
        return props[0], 'true' unless supported.include? props[0]
        return 'strike', 'true' if props[0] == 'line-through'
        return 'u', 'single' if props.length == 1
        return 'u', { val: props[1], color: 'auto' } if props.length == 2
        return 'u', { val: props[1], color: props[2].delete('#') }
      },
      'vertical-align' => lambda { |v|
        return 'vertAlign', 'subscript' if v =~ /sub/
        return 'vertAlign', 'superscript' if v =~ /super/
      }
    }
  }
end
initialize_html_tags() click to toggle source

Defines all of the initial HTML tags to be used by HTMLconverter

# File lib/sablon/configuration/configuration.rb, line 52
def initialize_html_tags
  @permitted_html_tags = {}
  tags = {
    # special tag used for elements with no parent, i.e. top level
    '#document-fragment' => { type: :block, ast_class: :root, allowed_children: %i[_block _inline] },

    # block level tags
    table: { type: :block, ast_class: :table, allowed_children: %i[caption thead tbody tfoot tr ]},
    tr: { type: :block, ast_class: :table_row, allowed_children: %i[th td] },
    th: { type: :block, ast_class: :table_cell, properties: { b: nil, jc: 'center' }, allowed_children: %i[_block _inline] },
    td: { type: :block, ast_class: :table_cell, allowed_children: %i[_block _inline] },
    div: { type: :block, ast_class: :paragraph, properties: { pStyle: 'Normal' }, allowed_children: :_inline },
    p: { type: :block, ast_class: :paragraph, properties: { pStyle: 'Paragraph' }, allowed_children: :_inline },
    caption: { type: :block, ast_class: :paragraph, properties: { pStyle: 'Caption' }, allowed_children: :_inline },
    h1: { type: :block, ast_class: :paragraph, properties: { pStyle: 'Heading1' }, allowed_children: :_inline },
    h2: { type: :block, ast_class: :paragraph, properties: { pStyle: 'Heading2' }, allowed_children: :_inline },
    h3: { type: :block, ast_class: :paragraph, properties: { pStyle: 'Heading3' }, allowed_children: :_inline },
    h4: { type: :block, ast_class: :paragraph, properties: { pStyle: 'Heading4' }, allowed_children: :_inline },
    h5: { type: :block, ast_class: :paragraph, properties: { pStyle: 'Heading5' }, allowed_children: :_inline },
    h6: { type: :block, ast_class: :paragraph, properties: { pStyle: 'Heading6' }, allowed_children: :_inline },
    ol: { type: :block, ast_class: :list, properties: { pStyle: 'ListNumber' }, allowed_children: %i[ol li] },
    ul: { type: :block, ast_class: :list, properties: { pStyle: 'ListBullet' }, allowed_children: %i[ul li] },
    li: { type: :block, ast_class: :list_paragraph },

    # inline style tags for tables
    thead: { type: :inline, ast_class: nil, properties: { tblHeader: nil }, allowed_children: :tr },
    tbody: { type: :inline, ast_class: nil, properties: {}, allowed_children: :tr },
    tfoot: { type: :inline, ast_class: nil, properties: {}, allowed_children: :tr },

    # inline style tags
    span: { type: :inline, ast_class: nil, properties: {} },
    strong: { type: :inline, ast_class: nil, properties: { b: nil } },
    b: { type: :inline, ast_class: nil, properties: { b: nil } },
    em: { type: :inline, ast_class: nil, properties: { i: nil } },
    i: { type: :inline, ast_class: nil, properties: { i: nil } },
    u: { type: :inline, ast_class: nil, properties: { u: 'single' } },
    s: { type: :inline, ast_class: nil, properties: { strike: 'true' } },
    sub: { type: :inline, ast_class: nil, properties: { vertAlign: 'subscript' } },
    sup: { type: :inline, ast_class: nil, properties: { vertAlign: 'superscript' } },

    # inline content tags
    a: { type: :inline, ast_class: :hyperlink, properties: { rStyle: 'Hyperlink' } },
    text: { type: :inline, ast_class: :run, properties: {}, allowed_children: [] },
    br: { type: :inline, ast_class: :newline, properties: {}, allowed_children: [] }
  }
  # add all tags to the config object
  tags.each do |tag_name, settings|
    type = settings.delete(:type)
    register_html_tag(tag_name, type, **settings)
  end
end