module Decidim::ContentProcessor

This module contains all logic related to decidim's ability for process a content. Their main job is to {ContentProcessor#parse parse} or {ContentProcessor#render render} a content calling all the registered processors in the Decidim.content_processors config.

Note that to render a content this must have been parsed before.

When creating a new processor, the both sides must be declared: parser and renderer e.g. If we are creating a processor to parse and render user mentions, we can call this the `user` processor, so we will declare the parser and renderer classes like that:

Decidim::ContentParsers::UserParser
Decidim::ContentRenderers::UserRenderer

and register it in an initializer, so it is executed:

Decidim.content_processors += [:user]

If for some reason you only want to do something in one of the both sides, please, also declare the other side making it “transparent” (declaring the class and leaving it empty).

@example How to parse a content

parsed = Decidim::ContentProcessor.parse(content, context)
parsed.rewrite # contains rewritten content
parsed.metadata # contains the merged metadata of all parsers

@example How to render a content (must have been parsed before)

rendered = Decidim::ContentProcessor.render(content)
puts rendered

Constants

Result

Class that represents the result of processing a text

@!attribute rewrite

@return [String] the rewritten content

@!attribute metadata

@return [Hash<Symbol, Metadata>] a hash where the keys are the parsers
  names, and the values are the Metadata object returned by the parser

Public Class Methods

parse(content, context) click to toggle source

This calls all registered processors one after the other and collects the metadata for each one and the resulting modified content

@param content [String] already rewritten content or regular content @param context [Hash] with information to inject to the parsers as context

@return [Result] a Result object with the content rewritten and the metadata

# File lib/decidim/content_processor.rb, line 53
def self.parse(content, context)
  Decidim.content_processors.each_with_object(Result.new(content, {})) do |type, result|
    parse_with_processor(type, result, context)
  end
end
parse_with_processor(type, content, context) click to toggle source

Public: Calls the specified processors to process the given content with it. For example, to convert hashtags to its Global ID representation.

@param type [String] the name of the processor to use. @param content [String] already rewritten content or regular content @param context [Hash] with information to inject to the parsers as context

@return [Result] a Result object with the content rewritten and the metadata

# File lib/decidim/content_processor.rb, line 67
def self.parse_with_processor(type, content, context)
  result = if content.is_a?(Result)
             content
           else
             Result.new(content, {})
           end

  if result.rewrite.is_a?(Hash)
    result.rewrite.each do |key, value|
      child_result = Result.new(value, {})
      child_result = parse_with_processor(type, child_result, context)

      result.rewrite.update(key => child_result.rewrite)
      result.metadata.update(child_result.metadata)
    end
  else
    parser = parser_klass(type).constantize.new(result.rewrite, context)
    result.rewrite = parser.rewrite
    result.metadata.update(type => parser.metadata)
  end

  result
end
parser_klass(type) click to toggle source

Guess the class name of the parser for a processor represented by the given symbol

@api private @return [String] the content parser class name

# File lib/decidim/content_processor.rb, line 140
def self.parser_klass(type)
  "Decidim::ContentParsers::#{type.to_s.camelize}Parser"
end
render(content, wrapper_tag = "p", options = {}) click to toggle source

This calls all registered processors one after the other and returns the processed content ready to display.

@param content [String] with the content to be rendered. @param wrapper_tag [String] with the HTML tag to wrap the content. @param options [Hash] with options to pass to the renderer.

@return [String] the content processed and ready to display (it is expected to include HTML)

# File lib/decidim/content_processor.rb, line 99
def self.render(content, wrapper_tag = "p", options = {})
  simple_format(
    render_without_format(content, options),
    {},
    wrapper_tag: wrapper_tag
  )
end
render_without_format(content, options = {}) click to toggle source

This calls all registered processors one after the other and returns the processed content ready to display without wrapping the content in HTML.

@param content [String] with the content to be rendered. @param options [Hash] with options to pass to the renderer.

@return [String] the content processed and ready to display.

# File lib/decidim/content_processor.rb, line 115
def self.render_without_format(content, options = {})
  return content if content.blank?

  Decidim.content_processors.reduce(content) do |result, type|
    renderer_klass(type).constantize.new(result).render(**options)
  end
end
renderer_klass(type) click to toggle source

Guess the class name of the renderer for a processor represented by the given symbol

@api private @return [String] the content renderer class name

# File lib/decidim/content_processor.rb, line 149
def self.renderer_klass(type)
  "Decidim::ContentRenderers::#{type.to_s.camelize}Renderer"
end
sanitize(text, options = {}) click to toggle source

This method overwrites the views `sanitize` method. This is required to ensure the content does not include any weird HTML that could harm the end user.

@return [String] sanitized content.

# File lib/decidim/content_processor.rb, line 128
def self.sanitize(text, options = {})
  Rails::Html::WhiteListSanitizer.new.sanitize(
    text,
    { scrubber: Decidim::UserInputScrubber.new }.merge(options)
  ).try(:html_safe)
end