jessedoyle/prawn-icon

View on GitHub
lib/prawn/icon/parser.rb

Summary

Maintainability
A
1 hr
Test Coverage
# frozen_string_literal: true

# parser.rb: Prawn icon tag text parser (pseudo-HTML).
#
# Copyright October 2014, Jesse Doyle. All rights reserved.
#
# This is free software. Please see the LICENSE and COPYING files for details.

module Prawn
  class Icon
    # Provides the necessary methods to enable the parsing
    # of <icon> tags from input text.
    #
    # = Supported Tags:
    # <icon></icon>::
    #   Place an icon key between the tags and the output
    #   will be translated into: <font name="fa">unicode</font>.
    #
    # = Supported Attributes:
    #
    # Various attributes will be extracted from +<icon>+ tags:
    #
    # color::
    #   The hex representation of a color that the icon should
    #   be rendered as. If left nil, the document's fill color
    #   will be used.
    #
    # size::
    #   The font size of a particular icon. If left nil, the
    #   document's font size will be used.
    #
    class Parser
      PARSER_REGEX  = Regexp.new \
        '<icon[^>]*>|</icon>',
        Regexp::IGNORECASE |
        Regexp::MULTILINE

      CONTENT_REGEX = /<icon[^>]*>(?<content>[^<]*)<\/icon>/mi.freeze

      TAG_REGEX     = /<icon[^>]*>[^<]*<\/icon>/mi.freeze

      # rubocop:disable Lint/MixedRegexpCaptureTypes
      ATTR_REGEX    = /(?<attr>[a-zA-Z]*)=["|'](?<val>(\w*[^["|']]))["|']/mi.freeze
      # rubocop:enable Lint/MixedRegexpCaptureTypes

      class << self
        def format(document, string)
          tokens  = string.scan(PARSER_REGEX)
          config  = config_from_tokens(tokens)
          content = string.scan(CONTENT_REGEX).flatten
          icons   = keys_to_unicode(document, content, config)
          tags    = icon_tags(icons)

          string.gsub(TAG_REGEX).with_index { |_, i| tags[i] }
        end

        def config_from_tokens(tokens)
          [].tap do |array|
            tokens.each do |token|
              # Skip the closing tag
              next if token =~ /<\/icon>/i

              # Convert [[1,2], [3,4]] to { :1 => 2, :3 => 4 }
              attrs = token.scan(ATTR_REGEX).inject({}) do |k, v|
                val = attr_hash(v)
                k.merge!(val)
              end

              array << attrs
            end
          end
        end

        def icon_tags(icons)
          [].tap do |tags|
            icons.each do |icon|
              # Mandatory
              content = icon[:content]
              set     = icon[:set]

              # Optional
              color = icon[:color]
              size  = icon[:size]

              opening = "<font name=\"#{set}\""

              unless color || size
                tags << "#{opening}>#{content}</font>"
                next
              end

              opening += " size=\"#{size}\"" if size
              content = "<color rgb=\"#{color}\">#{content}</color>" if color

              opening += '>'
              tags << "#{opening}#{content}</font>"
            end
          end
        end

        def keys_to_unicode(document, content, config)
          [].tap do |icons|
            content.each_with_index do |icon, index|
              key = Compatibility.new(key: icon).translate
              options ||= {}
              options = config[index] if config.any?
              info = {
                set: FontData.specifier_from_key(key),
                size: options[:size],
                color: options[:color],
                content: FontData.unicode_from_key(document, key)
              }
              icons << info
            end
          end
        end

        private

        def attr_hash(value) # :nodoc:
          # If attr == size, we must cast value to float,
          # else symbolize the key and map it to value
          if value[0] =~ /size/i
            { size: value[1].to_f }
          else
            { value[0].to_sym => value[1] }
          end
        end
      end
    end
  end
end