Class: Banzai::Filter::References::ReferenceFilter

Inherits:
HTML::Pipeline::Filter
  • Object
show all
Includes:
Concerns::HtmlWriter, Concerns::OutputSafety, Concerns::PipelineTimingCheck, Concerns::TextReplacer, RequestStoreReferenceCache
Defined in:
lib/banzai/filter/references/reference_filter.rb

Overview

Base class for GitLab Flavored Markdown reference filters.

References within <pre>, <code>, <a>, and <style> elements are ignored.

Context options:

:project (required) - Current project, ignored if reference is cross-project.
:only_path          - Generate path-only links.

Constant Summary collapse

REFERENCE_TYPE_ATTRIBUTE =
:reference_type
REFERENCE_TYPE_DATA_ATTRIBUTE_NAME =
"data-#{REFERENCE_TYPE_ATTRIBUTE.to_s.dasherize}".freeze

Constants included from Concerns::TextReplacer

Concerns::TextReplacer::REFERENCE_PLACEHOLDER, Concerns::TextReplacer::REFERENCE_PLACEHOLDER_PATTERN

Constants included from Concerns::PipelineTimingCheck

Concerns::PipelineTimingCheck::MAX_PIPELINE_SECONDS

Class Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Methods included from Concerns::TextReplacer

#replace_references_in_text_with_html

Methods included from Concerns::HtmlWriter

#write_opening_tag

Methods included from Concerns::OutputSafety

#escape_once

Methods included from RequestStoreReferenceCache

#cached_call, #get_or_set_cache

Methods included from Concerns::PipelineTimingCheck

#exceeded_pipeline_max?

Constructor Details

#initialize(doc, context = nil, result = nil) ⇒ ReferenceFilter

Returns a new instance of ReferenceFilter.



38
39
40
41
42
43
# File 'lib/banzai/filter/references/reference_filter.rb', line 38

def initialize(doc, context = nil, result = nil)
  super

  @new_nodes = {}
  @nodes = self.result[:reference_filter_nodes]
end

Class Attribute Details

.object_classObject

Implement in child class Example: self.object_class = MergeRequest



31
32
33
# File 'lib/banzai/filter/references/reference_filter.rb', line 31

def object_class
  @object_class
end

.reference_typeObject

Implement in child class Example: self.reference_type = :merge_request



27
28
29
# File 'lib/banzai/filter/references/reference_filter.rb', line 27

def reference_type
  @reference_type
end

Class Method Details

.call(doc, context = nil, result = nil) ⇒ Object



33
34
35
# File 'lib/banzai/filter/references/reference_filter.rb', line 33

def call(doc, context = nil, result = nil)
  new(doc, context, result).call_and_update_nodes
end

Instance Method Details

#callObject



49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
# File 'lib/banzai/filter/references/reference_filter.rb', line 49

def call
  ref_pattern_start = /\A#{object_reference_pattern}\z/

  nodes.each_with_index do |node, index|
    if text_node?(node)
      replace_node_when_text_matches(node, index, object_reference_pattern) do |content|
        object_link_filter(content, object_reference_pattern)
      end
    elsif element_node?(node)
      yield_valid_link(node) do |link, _text, inner_html|
        if ref_pattern_start.match?(link)
          html = object_link_filter(link, ref_pattern_start, link_content_html: inner_html)
          replace_node_with_html(node, index, html) if html
        end
      end
    end
  end

  doc
end

#call_and_update_nodesObject



45
46
47
# File 'lib/banzai/filter/references/reference_filter.rb', line 45

def call_and_update_nodes
  with_update_nodes { call }
end

#each_nodeObject

Iterates over all <a> and text() nodes in a document.

Nodes are skipped whenever their ancestor is one of the nodes returned by ‘ignore_ancestor_query`. Link tags are not processed if they have a “gfm” class or the “href” attribute is empty.



121
122
123
124
125
126
127
# File 'lib/banzai/filter/references/reference_filter.rb', line 121

def each_node
  return to_enum(__method__) unless block_given?

  doc.xpath(query).each do |node|
    yield node
  end
end

#groupObject



146
147
148
# File 'lib/banzai/filter/references/reference_filter.rb', line 146

def group
  context[:group]
end

#nodesObject

Returns an Array containing all HTML nodes.



130
131
132
# File 'lib/banzai/filter/references/reference_filter.rb', line 130

def nodes
  @nodes ||= each_node.to_a
end

#nodes?Boolean

Returns:

  • (Boolean)


134
135
136
# File 'lib/banzai/filter/references/reference_filter.rb', line 134

def nodes?
  @nodes.present?
end

#object_classObject



138
139
140
# File 'lib/banzai/filter/references/reference_filter.rb', line 138

def object_class
  self.class.object_class
end

#projectObject



142
143
144
# File 'lib/banzai/filter/references/reference_filter.rb', line 142

def project
  context[:project]
end

#references_in(text, pattern = object_reference_pattern) ⇒ Object

Public: Find references in text (like ‘!123` for merge requests).

Simplified usage (see AbstractReferenceFilter#object_link_filter for a full example):

references_in(text) do |match_text, id, project_ref, namespace_ref, matches|
  object = find_object(namespace_ref, project_ref, id)
  if object
    write_opening_tag("a", {
      "href" => "...",
      # ... other attributes ...
    }) << CGI.escapeHTML(object.to_reference) << "</a>"
  else
    CGI.escapeHTML(match_text)
  end
end

text - String text to make replacements in. This is the content of a DOM text node, and cannot be returned without modification.

Yields each String text match as the first argument; the remaining arguments depend on the particular subclass of ReferenceFilter being used. The block must return HTML, and not text.

Returns a HTML String replaced with replacements made, or nil if no replacements were made.

Notes:

  • The input is text – we expect to match e.g. &1 or %“Git 2.5” without having to deal with HTML entities, as our object reference patterns are not compatible with entities.

  • Likewise, any strings yielded to the block are text, but the block must return HTML, whether it’s a successful resolution or not. Any text input used in the block must be escaped for return.

  • The return value is HTML, as the whole point is to create links (and possibly other elements). Care must be taken that any input text is escaped before reaching the output. This must be respected in all subclasses of ReferenceFilter.

See AbstractReferenceFilter#references_in for a reference implementation that safely handles user input.

Raises:

  • (NotImplementedError)


112
113
114
# File 'lib/banzai/filter/references/reference_filter.rb', line 112

def references_in(text, pattern = object_reference_pattern)
  raise NotImplementedError, "#{self.class} must implement method: #{__callee__}"
end

#requires_unescaping?Boolean

Returns:

  • (Boolean)


150
151
152
# File 'lib/banzai/filter/references/reference_filter.rb', line 150

def requires_unescaping?
  false
end