Class: Kramdown::Converter::Base

Inherits:
Object
  • Object
show all
Defined in:
lib/kramdown/converter/base.rb

Overview

Base class for converters

This class serves as base class for all converters. It provides methods that can/should be used by all converters (like #generate_id) as well as common functionality that is automatically applied to the result (for example, embedding the output into a template).

A converter object is used as a throw-away object, i.e. it is only used for storing the needed state information during conversion. Therefore one can't instantiate a converter object directly but only use the Base::convert method.

Implementing a converter

Implementing a new converter is rather easy: just derive a new class from this class and put it in the Kramdown::Converter module (the latter is only needed if auto-detection should work properly). Then you need to implement the #convert method which has to contain the conversion code for converting an element and has to return the conversion result.

The actual transformation of the document tree can be done in any way. However, writing one method per element type is a straight forward way to do it - this is how the Html and Latex converters do the transformation.

Have a look at the Base::convert method for additional information!

Direct Known Subclasses

HashAST, Html, Kramdown, Latex, Pdf, RemoveHtmlTags, Toc

Constant Summary

SMART_QUOTE_INDICES =

:nodoc:

{:lsquo => 0, :rsquo => 1, :ldquo => 2, :rdquo => 3}

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(root, options) ⇒ Base

Initialize the converter with the given root element and options hash.



55
56
57
58
59
60
# File 'lib/kramdown/converter/base.rb', line 55

def initialize(root, options)
  @options = options
  @root = root
  @data = {}
  @warnings = []
end

Instance Attribute Details

#dataObject (readonly)

Can be used by a converter for storing arbitrary information during the conversion process.



43
44
45
# File 'lib/kramdown/converter/base.rb', line 43

def data
  @data
end

#optionsObject (readonly)

The hash with the conversion options.



46
47
48
# File 'lib/kramdown/converter/base.rb', line 46

def options
  @options
end

#rootObject (readonly)

The root element that is converted.



49
50
51
# File 'lib/kramdown/converter/base.rb', line 49

def root
  @root
end

#warningsObject (readonly)

The warnings array.



52
53
54
# File 'lib/kramdown/converter/base.rb', line 52

def warnings
  @warnings
end

Class Method Details

.apply_template(converter, body) ⇒ Object

Apply the template using body as the body string.

The template is evaluated using ERB and the body is available in the @body instance variable and the converter object in the @converter instance variable.



123
124
125
126
127
128
129
# File 'lib/kramdown/converter/base.rb', line 123

def self.apply_template(converter, body) # :nodoc:
  erb = ERB.new(get_template(converter.options[:template]))
  obj = Object.new
  obj.instance_variable_set(:@converter, converter)
  obj.instance_variable_set(:@body, body)
  erb.result(obj.instance_eval{binding})
end

.convert(tree, options = {}) ⇒ Object

Convert the element tree tree and return the resulting conversion object (normally a string) and an array with warning messages. The parameter options specifies the conversion options that should be used.

Initializes a new instance of the calling class and then calls the #convert method with tree as parameter.

If the template option is specified and non-empty, the template is evaluate with ERB before and/or after the tree conversion depending on the result of #apply_template_before? and #apply_template_after?. If the template is evaluated before, an empty string is used for the body; if evaluated after, the result is used as body. See ::apply_template.

The template resolution is done in the following way (for the converter ConverterName):

  1. Look in the current working directory for the template.

  2. Append .converter_name (e.g. .html) to the template name and look for the resulting file in the current working directory (the form .convertername is deprecated).

  3. Append .converter_name to the template name and look for it in the kramdown data directory (the form .convertername is deprecated).

  4. Check if the template name starts with 'string://' and if so, strip this prefix away and use the rest as template.



101
102
103
104
105
106
107
108
109
110
# File 'lib/kramdown/converter/base.rb', line 101

def self.convert(tree, options = {})
  converter = new(tree, ::Kramdown::Options.merge(options.merge(tree.options[:options] || {})))

  apply_template(converter, '') if !converter.options[:template].empty? && converter.apply_template_before?
  result = converter.convert(tree)
  result.encode!(tree.options[:encoding]) if result.respond_to?(:encode!) && result.encoding != Encoding::BINARY
  result = apply_template(converter, result) if !converter.options[:template].empty? && converter.apply_template_after?

  [result, converter.warnings]
end

.get_template(template) ⇒ Object

Return the template specified by template.



132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
# File 'lib/kramdown/converter/base.rb', line 132

def self.get_template(template)
  #DEPRECATED: use content of #get_template_new in 2.0
  format_ext = '.' + self.name.split(/::/).last.downcase
  shipped = File.join(::Kramdown.data_dir, template + format_ext)
  if File.exist?(template)
    File.read(template)
  elsif File.exist?(template + format_ext)
    File.read(template + format_ext)
  elsif File.exist?(shipped)
    File.read(shipped)
  elsif template.start_with?('string://')
    template.sub(/\Astring:\/\//, '')
  else
    get_template_new(template)
  end
end

.get_template_new(template) ⇒ Object

:nodoc:



149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
# File 'lib/kramdown/converter/base.rb', line 149

def self.get_template_new(template) # :nodoc:
  format_ext = '.' + ::Kramdown::Utils.snake_case(self.name.split(/::/).last)
  shipped = File.join(::Kramdown.data_dir, template + format_ext)
  if File.exist?(template)
    File.read(template)
  elsif File.exist?(template + format_ext)
    File.read(template + format_ext)
  elsif File.exist?(shipped)
    File.read(shipped)
  elsif template.start_with?('string://')
    template.sub(/\Astring:\/\//, '')
  else
    raise "The specified template file #{template} does not exist"
  end
end

Instance Method Details

#apply_template_after?Boolean

Returns whether the template should be applied ater the conversion of the tree.

Defaults to true.

Returns:

  • (Boolean)


73
74
75
# File 'lib/kramdown/converter/base.rb', line 73

def apply_template_after?
  true
end

#apply_template_before?Boolean

Returns whether the template should be applied before the conversion of the tree.

Defaults to false.

Returns:

  • (Boolean)


66
67
68
# File 'lib/kramdown/converter/base.rb', line 66

def apply_template_before?
  false
end

#convert(el) ⇒ Object

Convert the element el and return the resulting object.

This is the only method that has to be implemented by sub-classes!

Raises:

  • (NotImplementedError)


115
116
117
# File 'lib/kramdown/converter/base.rb', line 115

def convert(el)
  raise NotImplementedError
end

#extract_code_language(attr) ⇒ Object

Extract the code block/span language from the attributes.



184
185
186
187
188
# File 'lib/kramdown/converter/base.rb', line 184

def extract_code_language(attr)
  if attr['class'] && attr['class'] =~ /\blanguage-\w[\w-]*\b/
    attr['class'].scan(/\blanguage-(\w[\w-]*)\b/).first.first
  end
end

#extract_code_language!(attr) ⇒ Object

See #extract_code_language

Warning: This version will modify the given attributes if a language is present.



193
194
195
196
197
198
# File 'lib/kramdown/converter/base.rb', line 193

def extract_code_language!(attr)
  lang = extract_code_language(attr)
  attr['class'] = attr['class'].sub(/\blanguage-\w[\w-]*\b/, '').strip if lang
  attr.delete('class') if lang && attr['class'].empty?
  lang
end

#format_math(el, opts = {}) ⇒ Object

Format the given math element with the math engine configured through the option 'math_engine'.



216
217
218
219
220
221
222
223
224
225
226
# File 'lib/kramdown/converter/base.rb', line 216

def format_math(el, opts = {})
  return nil unless @options[:math_engine]

  engine = ::Kramdown::Converter.math_engine(@options[:math_engine])
  if engine
    engine.call(self, el, opts)
  else
    warning("The configured math engine #{@options[:math_engine]} is not available.")
    nil
  end
end

#generate_id(str) ⇒ Object

Generate an unique alpha-numeric ID from the the string str for use as a header ID.

Uses the option auto_id_prefix: the value of this option is prepended to every generated ID.



232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
# File 'lib/kramdown/converter/base.rb', line 232

def generate_id(str)
  str = ::Kramdown::Utils::Unidecoder.decode(str) if @options[:transliterated_header_ids]
  gen_id = str.gsub(/^[^a-zA-Z]+/, '')
  gen_id.tr!('^a-zA-Z0-9 -', '')
  gen_id.tr!(' ', '-')
  gen_id.downcase!
  gen_id = 'section' if gen_id.length == 0
  @used_ids ||= {}
  if @used_ids.has_key?(gen_id)
    gen_id += '-' << (@used_ids[gen_id] += 1).to_s
  else
    @used_ids[gen_id] = 0
  end
  @options[:auto_id_prefix] + gen_id
end

#highlight_code(text, lang, type, opts = {}) ⇒ Object

Highlight the given text in the language lang with the syntax highlighter configured through the option 'syntax_highlighter'.



202
203
204
205
206
207
208
209
210
211
212
# File 'lib/kramdown/converter/base.rb', line 202

def highlight_code(text, lang, type, opts = {})
  return nil unless @options[:syntax_highlighter]

  highlighter = ::Kramdown::Converter.syntax_highlighter(@options[:syntax_highlighter])
  if highlighter
    highlighter.call(self, text, lang, type, opts)
  else
    warning("The configured syntax highlighter #{@options[:syntax_highlighter]} is not available.")
    nil
  end
end

#in_toc?(el) ⇒ Boolean

Return true if the header element el should be used for the table of contents (as specified by the toc_levels option).

Returns:

  • (Boolean)


172
173
174
# File 'lib/kramdown/converter/base.rb', line 172

def in_toc?(el)
  @options[:toc_levels].include?(el.options[:level]) && (el.attr['class'] || '') !~ /\bno_toc\b/
end

#output_header_level(level) ⇒ Object

Return the output header level given a level.

Uses the header_offset option for adjusting the header level.



179
180
181
# File 'lib/kramdown/converter/base.rb', line 179

def output_header_level(level)
  [[level + @options[:header_offset], 6].min, 1].max
end

#smart_quote_entity(el) ⇒ Object

Return the entity that represents the given smart_quote element.



251
252
253
254
# File 'lib/kramdown/converter/base.rb', line 251

def smart_quote_entity(el)
  res = @options[:smart_quotes][SMART_QUOTE_INDICES[el.value]]
  ::Kramdown::Utils::Entities.entity(res)
end

#warning(text) ⇒ Object

Add the given warning text to the warning array.



166
167
168
# File 'lib/kramdown/converter/base.rb', line 166

def warning(text)
  @warnings << text
end