Class: Kramdown::Parser::PotMarkdown

Inherits:
GFM
  • Object
show all
Defined in:
lib/kramdown/parser/pot_markdown.rb,
lib/kramdown/parser/pot_markdown/table.rb,
lib/kramdown/parser/pot_markdown/atx_header.rb,
lib/kramdown/parser/pot_markdown/code_block.rb

Constant Summary collapse

TABLE_SEP_LINE =
/^([+|: -]*?-[+|: -]*?)[ \t]*\n/
TABLE_HSEP_ALIGN =
/[ ]?(:?)-+(:?)[ ]?/
TABLE_FSEP_LINE =
/^[+|: =]*?=[+|: =]*?[ \t]*\n/
TABLE_ROW_LINE =
/^(.*?)[ \t]*\n/
TABLE_PIPE_CHECK =
/(?:^\|)/
TABLE_LINE =
/#{TABLE_PIPE_CHECK}.*?\|\s*\n/
TABLE_START =
/^#{OPT_SPACE}(?=\S)#{TABLE_LINE}/
FENCED_CODEBLOCK_MATCH =
/^(([~`]){3,})\s*?((\w[\w\:\.-]*)(?:\?\S*)?)?\s*?\n(.*?)^\1\2*\s*?\n/m

Instance Method Summary collapse

Constructor Details

#initialize(source, options) ⇒ PotMarkdown

Returns a new instance of PotMarkdown.



7
8
9
10
11
12
13
# File 'lib/kramdown/parser/pot_markdown.rb', line 7

def initialize(source, options)
  super

  # replace table
  @block_parsers.insert(@block_parsers.index(:table), :table_pot)
  @block_parsers.delete(:table)
end

Instance Method Details

#parse_atx_headerObject



4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# File 'lib/kramdown/parser/pot_markdown/atx_header.rb', line 4

def parse_atx_header
  # ↓ removed in pot_markdown
  # return false if !after_block_boundary?

  start_line_number = @src.current_line_number
  @src.check(ATX_HEADER_MATCH)
  level = @src[1]
  text = @src[2].to_s.strip
  id = @src[3]
  return false if text.empty?

  @src.pos += @src.matched_size
  el = new_block_el(:header, nil, nil, level: level.length, raw_text: text, location: start_line_number)
  add_text(text, el)
  el.attr['id'] = id if id
  @tree.children << el
  true
end

#parse_codeblock_fencedObject



6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
# File 'lib/kramdown/parser/pot_markdown/code_block.rb', line 6

def parse_codeblock_fenced
  if @src.check(self.class::FENCED_CODEBLOCK_MATCH)
    start_line_number = @src.current_line_number
    @src.pos += @src.matched_size
    el = new_block_el(:codeblock, @src[5], nil, location: start_line_number)
    lang = @src[3].to_s.strip
    unless lang.empty?
      lang, filename = lang.split(':', 2)
      if filename.nil? && lang.include?('.')
        filename = lang
        lang = Rouge::Lexer.guess_by_filename(filename).name.match(/[^\:]+\z/).to_s.downcase
      end
      el.options[:lang] = lang
      el.attr['data-lang'] = lang
      if filename
        el.attr['class'] = "language-#{lang} has-filename"
        el.attr['data-filename'] = filename
      else
        el.attr['class'] = "language-#{lang}"
      end
    end
    @tree.children << el
    true
  else
    false
  end
end

#parse_table_potObject



12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
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
148
149
150
151
152
153
154
155
156
# File 'lib/kramdown/parser/pot_markdown/table.rb', line 12

def parse_table_pot
  return false unless after_block_boundary?

  saved_pos = @src.save_pos
  orig_pos = @src.pos
  table = new_block_el(:table, nil, nil, alignment: [], location: @src.current_line_number)
  leading_pipe = (@src.check(TABLE_LINE) =~ /^\s*\|/)
  @src.scan(TABLE_SEP_LINE)

  rows = []
  has_footer = false
  columns = 0

  add_container = lambda do |type, force|
    if !has_footer || type != :tbody || force
      cont = Element.new(type)
      cont.children = rows
      rows = []
      table.children << cont
    end
  end

  until @src.eos?
    break unless @src.check(TABLE_LINE)
    if @src.scan(TABLE_SEP_LINE)
      if rows.empty?
        # nothing to do, ignoring multiple consecutive separator lines
      elsif table.options[:alignment].empty? && !has_footer
        add_container.call(:thead, false)
        table.options[:alignment] = @src[1].scan(TABLE_HSEP_ALIGN).map do |left, right|
          (left.empty? && right.empty? && :default) || (right.empty? && :left) || (left.empty? && :right) || :center
        end
      else # treat as normal separator line
        add_container.call(:tbody, false)
      end
    elsif @src.scan(TABLE_FSEP_LINE)
      add_container.call(:tbody, true) unless rows.empty?
      has_footer = true
    elsif @src.scan(TABLE_ROW_LINE)
      trow = Element.new(:tr)

      # parse possible code spans on the line and correctly split the line into cells
      env = save_env
      cells = []
      @src[1].split(/(<code.*?>.*?<\/code>)/).each_with_index do |str, i|
        if i.odd?
          (cells.empty? ? cells : cells.last) << str
        else
          reset_env(src: Kramdown::Utils::StringScanner.new(str, @src.current_line_number))
          root = Element.new(:root)
          parse_spans(root, nil, [:codespan])

          root.children.each do |c|
            if c.type == :raw_text
              # Only on Ruby 1.9: f, *l = c.value.split(/(?<!\\)\|/).map {|t| t.gsub(/\\\|/, '|')}
              f, *l = c.value.split(/\\\|/, -1).map { |t| t.split(/\|/, -1) }.inject([]) do |memo, t|
                memo.last << "|#{t.shift}" if memo.size > 0
                memo.concat(t)
              end
              (cells.empty? ? cells : cells.last) << f
              cells.concat(l)
            else
              delim = (c.value.scan(/`+/).max || '') + '`'
              tmp = "#{delim}#{' ' if delim.size > 1}#{c.value}#{' ' if delim.size > 1}#{delim}"
              (cells.empty? ? cells : cells.last) << tmp
            end
          end
        end
      end
      restore_env(env)

      cells.shift if leading_pipe && cells.first.strip.empty?
      cells.pop if cells.last.strip.empty?
      cells.each do |cell_text|
        tcell = Element.new(:td)
        tcell.children << Element.new(:raw_text, cell_text.strip)
        trow.children << tcell
      end
      columns = [columns, cells.length].max
      rows << trow
    else
      break
    end
  end

  unless before_block_boundary?
    @src.revert_pos(saved_pos)
    return false
  end

  # Parse all lines of the table with the code span parser
  env = save_env
  l_src = ::Kramdown::Utils::StringScanner.new(extract_string(orig_pos...(@src.pos - 1), @src),
                                               @src.current_line_number)
  reset_env(src: l_src)
  root = Element.new(:root)
  parse_spans(root, nil, [:codespan, :span_html])
  restore_env(env)

  # Check if each line has at least one unescaped pipe that is not inside a code span/code
  # HTML element
  # Note: It doesn't matter that we parse *all* span HTML elements because the row splitting
  # algorithm above only takes <code> elements into account!
  pipe_on_line = false
  while (c = root.children.shift)
    lines = c.value.split(/\n/)
    if c.type == :codespan
      if lines.size > 2 || (lines.size == 2 && !pipe_on_line)
        break
      elsif lines.size == 2 && pipe_on_line
        pipe_on_line = false
      end
    else
      break if lines.size > 1 && !pipe_on_line && lines.first !~ /^#{TABLE_PIPE_CHECK}/
      pipe_on_line = (lines.size > 1 ? false : pipe_on_line) || (lines.last =~ /^#{TABLE_PIPE_CHECK}/)
    end
  end
  @src.revert_pos(saved_pos) && (return false) unless pipe_on_line

  add_container.call(has_footer ? :tfoot : :tbody, false) unless rows.empty?

  unless table.children.any? { |el| el.type == :tbody }
    warning("Found table without body on line #{table.options[:location]} - ignoring it")
    @src.revert_pos(saved_pos)
    return false
  end

  # adjust all table rows to have equal number of columns, same for alignment defs
  table.children.each do |kind|
    kind.children.each do |row|
      (columns - row.children.length).times do
        row.children << Element.new(:td)
      end
    end
  end
  if table.options[:alignment].length > columns
    table.options[:alignment] = table.options[:alignment][0...columns]
  else
    table.options[:alignment] += [:default] * (columns - table.options[:alignment].length)
  end

  @tree.children << table

  true
end