Class: ComfortableMexicanSofa::Content::Renderer

Inherits:
Object
  • Object
show all
Defined in:
lib/comfortable_mexican_sofa/content/renderer.rb

Overview

Processing content follows these stages:

string        - Text with tags. like this: "some {{cms:fragment content}} text"
tokenization  - Splits string into a list of strings and hashes that define tags
                Example: ["some ", {tag_class: "fragment", tag_params: ""}, " text"]
nodefying     - Initializes Tag instances from tag hashes and returns list
                like this: ["some ", (FragmentTagInstance), " text"]
rendering     - Recursively iterates through nodes. Tag instances get their
                `render` method called. Result of that is tokenized, nodefied
                and rendered once again until there are no tags to expand.
                Resulting list is flattened and joined into a final rendered string.

Defined Under Namespace

Classes: Error, SyntaxError

Constant Summary collapse

MAX_DEPTH =
100
TAG_REGEX =

tags are in this format: cms:tag_class params }

%r{\{\{\s*?cms:(?<class>\w+)(?<params>.*?)\}\}}

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(context) ⇒ Renderer

Returns a new instance of Renderer.

Parameters:


43
44
45
46
# File 'lib/comfortable_mexican_sofa/content/renderer.rb', line 43

def initialize(context)
  @context = context
  @depth   = 0
end

Class Method Details

.register_tag(name, klass) ⇒ Object

Parameters:


36
37
38
# File 'lib/comfortable_mexican_sofa/content/renderer.rb', line 36

def register_tag(name, klass)
  tags[name.to_s] = klass
end

.tagsHash<String, Class<ComfortableMexicanSofa::Content::Tag>>

Returns:


30
31
32
# File 'lib/comfortable_mexican_sofa/content/renderer.rb', line 30

def tags
  @tags ||= {}
end

Instance Method Details

#nodes(tokens) ⇒ Array<String, ComfortableMexicanSofa::Content::Tag>

Constructing node tree for content. It's a list of strings and tags with their own `nodes` method that has array of strings and tags with their own `nodes` method that… you get the idea.

Parameters:

  • tokens (Array<String, {Symbol => String}>)

Returns:


101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
# File 'lib/comfortable_mexican_sofa/content/renderer.rb', line 101

def nodes(tokens)
  nodes = [[]]
  tokens.each do |token|
    case token

    # tag signature
    when Hash
      case tag_class = token[:tag_class]

      # This handles {{cms:end}} tag. Stopping collecting block nodes.
      when "end"
        if nodes.count == 1
          raise SyntaxError, "closing unopened block"
        end
        nodes.pop

      else
        # @type [Class<ComfortableMexicanSofa::Content::Tag>]
        klass = self.class.tags[tag_class] ||
          raise(SyntaxError, "Unrecognized tag: #{token[:source]}")

        # @type [ComfortableMexicanSofa::Content::Tag]
        tag = klass.new(
          context:  @context,
          params:   ComfortableMexicanSofa::Content::ParamsParser.new(token[:tag_params]).params,
          source:   token[:source]
        )
        nodes.last << tag

        # If it's a block tag we start collecting nodes into it
        if tag.is_a?(ComfortableMexicanSofa::Content::Block)
          nodes << tag.nodes
        end
      end

    # text
    else
      nodes.last << token
    end
  end

  if nodes.count > 1
    raise SyntaxError, "unclosed block detected"
  end

  nodes.flatten
end

#render(nodes, allow_erb = ComfortableMexicanSofa.config.allow_erb) ⇒ Object

This is how we render content out. Takes context (cms page) and content nodes

Parameters:


52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
# File 'lib/comfortable_mexican_sofa/content/renderer.rb', line 52

def render(nodes, allow_erb = ComfortableMexicanSofa.config.allow_erb)
  if (@depth += 1) > MAX_DEPTH
    raise Error, "Deep tag nesting or recursive nesting detected"
  end

  nodes.map do |node|
    case node
    when String
      sanitize_erb(node, allow_erb)
    else
      tokens  = tokenize(node.render)
      nodes   = nodes(tokens)
      render(nodes, allow_erb || node.allow_erb?)
    end
  end.flatten.join
end

#sanitize_erb(string, allow_erb) ⇒ Object


69
70
71
72
73
74
75
# File 'lib/comfortable_mexican_sofa/content/renderer.rb', line 69

def sanitize_erb(string, allow_erb)
  if allow_erb
    string.to_s
  else
    string.to_s.gsub("<%", "&lt;%").gsub("%>", "%&gt;")
  end
end

#tokenize(string) ⇒ Array<String, {Symbol => String}>

Splitting text with tags into tokens we can process down the line

Returns:

  • (Array<String, {Symbol => String}>)

79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
# File 'lib/comfortable_mexican_sofa/content/renderer.rb', line 79

def tokenize(string)
  tokens = []
  ss = StringScanner.new(string.to_s)
  while (string = ss.scan_until(TAG_REGEX))
    text = string.sub(ss[0], "")
    tokens << text unless text.empty?
    tokens << {
      tag_class:  ss[:class],
      tag_params: ss[:params].strip,
      source:     ss[0]
    }
  end
  text = ss.rest
  tokens << text if text.present?
  tokens
end