kramdown-2.5.1/ 0000755 0000041 0000041 00000000000 14726466654 013415 5 ustar www-data www-data kramdown-2.5.1/data/ 0000755 0000041 0000041 00000000000 14726466654 014326 5 ustar www-data www-data kramdown-2.5.1/data/kramdown/ 0000755 0000041 0000041 00000000000 14726466654 016150 5 ustar www-data www-data kramdown-2.5.1/data/kramdown/document.latex 0000644 0000041 0000041 00000002364 14726466654 021032 0 ustar www-data www-data <% encmap = { 'UTF-8' => 'utf8x', 'US-ASCII' => 'ascii', 'ISO-8859-1' => 'latin1', 'ISO-8859-2' => 'latin2', 'ISO-8859-3' => 'latin3', 'ISO-8859-4' => 'latin4', 'ISO-8859-5' => 'latin5', 'ISO-8859-9' => 'latin9', 'ISO-8859-10' => 'latin10', 'CP850' => 'cp850', 'CP852' => 'cp852', 'CP858' => 'cp858', 'CP437' => 'cp437', 'CP865' => 'cp865', 'CP1250' => 'cp120', 'CP1252' => 'cp1252', 'CP1257' => 'cp1257' } %> \documentclass{scrartcl} <% if RUBY_VERSION >= '1.9' %> \usepackage[<%= encmap[@body.encoding.name] %>]{inputenc} <% else %> \usepackage[mathletters]{ucs} \usepackage[utf8x]{inputenc} <% end %> \usepackage[T1]{fontenc} \usepackage{listings} <% @converter.data[:packages].each {|pkg| %>\usepackage{<%= pkg %>} <% } %> \usepackage{hyperref} <% if @converter.data[:packages].include?('fancyvrb') %> \VerbatimFootnotes <% end %> <% if @converter.data[:packages].include?('acronym') %> <% @converter.root.options[:abbrev_defs].each_pair do |k,v| %>\acrodef{<%= @converter.normalize_abbreviation_key(k) %>}[<%= k %>]{<%= @converter.escape(v) %>} <% end %> <% end %> \setcounter{footnote}{<%= @converter.options[:footnote_nr] - 1 %>} \hypersetup{colorlinks=true,urlcolor=blue} \begin{document} <%= @body %> \end{document} kramdown-2.5.1/data/kramdown/document.html 0000644 0000041 0000041 00000001173 14726466654 020656 0 ustar www-data www-data
<% if @converter.root.options[:encoding] %> <% end %> <% extend ::Kramdown::Utils::Html title = '' h = @converter.root.children.find {|c| c.type == :header} if h collector = lambda {|c| c.children.collect {|cc| cc.type == :text ? escape_html(cc.value, :text) : collector.call(cc)}.join('')} title = collector.call(h) end %>He said, "'Quoted' words in a larger quote."
[/(\s?)"'(?=\w)/, [1, :ldquo, :lsquo]], [/(\s?)'"(?=\w)/, [1, :lsquo, :ldquo]], # Special case for decade abbreviations (the '80s): [/(\s?)'(?=\d\ds)/, [1, :rsquo]], # Get most opening single/double quotes: [/(\s)('|")(?=\w)/, [1, :lquote2]], # Single/double closing quotes: [/(#{SQ_CLOSE})('|")/, [1, :rquote2]], # Special case for e.g. "Custer's Last Stand." [/("|')(?=\s|s\b|$)/, [:rquote1]], # Any remaining single quotes should be opening ones: [/(.?)'/m, [1, :lsquo]], [/(.?)"/m, [1, :ldquo]], ] # '" SQ_SUBSTS = { [:rquote1, '"'] => :rdquo, [:rquote1, "'"] => :rsquo, [:rquote2, '"'] => :rdquo, [:rquote2, "'"] => :rsquo, [:lquote1, '"'] => :ldquo, [:lquote1, "'"] => :lsquo, [:lquote2, '"'] => :ldquo, [:lquote2, "'"] => :lsquo, } SMART_QUOTES_RE = /[^\\]?["']/ # Parse the smart quotes at current location. def parse_smart_quotes start_line_number = @src.current_line_number substs = SQ_RULES.find {|reg, _subst| @src.scan(reg) }[1] substs.each do |subst| if subst.kind_of?(Integer) add_text(@src[subst]) else val = SQ_SUBSTS[[subst, @src[subst.to_s[-1, 1].to_i]]] || subst @tree.children << Element.new(:smart_quote, val, nil, location: start_line_number) end end end define_parser(:smart_quotes, SMART_QUOTES_RE, '[^\\\\]?["\']') end end end kramdown-2.5.1/lib/kramdown/parser/kramdown/table.rb 0000644 0000041 0000041 00000014301 14726466654 022536 0 ustar www-data www-data # -*- coding: utf-8; frozen_string_literal: true -*- # #-- # Copyright (C) 2009-2019 Thomas Leitner elements into account!
pipe_on_line = false
while (c = root.children.shift)
next unless (lines = c.value)
lines = lines.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}/o
pipe_on_line = (lines.size > 1 ? false : pipe_on_line) || (lines.last =~ /^#{TABLE_PIPE_CHECK}/o)
end
end
@src.revert_pos(saved_pos) and return false unless pipe_on_line
add_container.call(has_footer ? :tfoot : :tbody, false) unless rows.empty?
if table.children.none? {|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
define_parser(:table, TABLE_START)
end
end
end
kramdown-2.5.1/lib/kramdown/parser/kramdown/autolink.rb 0000644 0000041 0000041 00000001564 14726466654 023304 0 ustar www-data www-data # -*- coding: utf-8; frozen_string_literal: true -*-
#
#--
# Copyright (C) 2009-2019 Thomas Leitner
#
# This file is part of kramdown which is licensed under the MIT.
#++
#
module Kramdown
module Parser
class Kramdown
ACHARS = '[[:alnum:]]-_.'
AUTOLINK_START_STR = "<((mailto|https?|ftps?):.+?|[#{ACHARS}]+?@[#{ACHARS}]+?)>"
AUTOLINK_START = /#{AUTOLINK_START_STR}/u
# Parse the autolink at the current location.
def parse_autolink
start_line_number = @src.current_line_number
@src.pos += @src.matched_size
href = (@src[2].nil? ? "mailto:#{@src[1]}" : @src[1])
el = Element.new(:a, nil, {'href' => href}, location: start_line_number)
add_text(@src[1].sub(/^mailto:/, ''), el)
@tree.children << el
end
define_parser(:autolink, AUTOLINK_START, '<')
end
end
end
kramdown-2.5.1/lib/kramdown/parser/kramdown/html.rb 0000644 0000041 0000041 00000014662 14726466654 022425 0 ustar www-data www-data # -*- coding: utf-8; frozen_string_literal: true -*-
#
#--
# Copyright (C) 2009-2019 Thomas Leitner
#
# This file is part of kramdown which is licensed under the MIT.
#++
#
require 'kramdown/parser/html'
module Kramdown
module Parser
class Kramdown
include Kramdown::Parser::Html::Parser
include Kramdown::Utils::Html
# Mapping of markdown attribute value to content model. I.e. :raw when "0", :default when "1"
# (use default content model for the HTML element), :span when "span", :block when block and
# for everything else +nil+ is returned.
HTML_MARKDOWN_ATTR_MAP = {"0" => :raw, "1" => :default, "span" => :span, "block" => :block}
TRAILING_WHITESPACE = /[ \t]*\n/
def handle_kramdown_html_tag(el, closed, handle_body)
if @block_ial
el.options[:ial] = @block_ial
@block_ial = nil
end
content_model = if @tree.type != :html_element || @tree.options[:content_model] != :raw
(@options[:parse_block_html] ? HTML_CONTENT_MODEL[el.value] : :raw)
else
:raw
end
if (val = HTML_MARKDOWN_ATTR_MAP[el.attr.delete('markdown')])
content_model = (val == :default ? HTML_CONTENT_MODEL[el.value] : val)
end
@src.scan(TRAILING_WHITESPACE) if content_model == :block
el.options[:content_model] = content_model
el.options[:is_closed] = closed
if !closed && handle_body
case content_model
when :block
unless parse_blocks(el)
warning("Found no end tag for '#{el.value}' (line #{el.options[:location]}) - auto-closing it")
end
when :span
curpos = @src.pos
if @src.scan_until(/(?=<\/#{el.value}\s*>)/mi)
add_text(extract_string(curpos...@src.pos, @src), el)
@src.scan(HTML_TAG_CLOSE_RE)
else
add_text(@src.rest, el)
@src.terminate
warning("Found no end tag for '#{el.value}' (line #{el.options[:location]}) - auto-closing it")
end
else
parse_raw_html(el) {|iel, ic, ih| handle_kramdown_html_tag(iel, ic, ih) }
end
unless @tree.type == :html_element && @tree.options[:content_model] == :raw
@src.scan(TRAILING_WHITESPACE)
end
end
end
HTML_BLOCK_START = /^#{OPT_SPACE}<(#{REXML::Parsers::BaseParser::UNAME_STR}|!--|\/)/
# Parse the HTML at the current position as block-level HTML.
def parse_block_html
line = @src.current_line_number
if (result = @src.scan(HTML_COMMENT_RE))
@tree.children << Element.new(:xml_comment, result, nil, category: :block, location: line)
@src.scan(TRAILING_WHITESPACE)
true
elsif @src.check(/^#{OPT_SPACE}#{HTML_TAG_RE}/o) && !HTML_SPAN_ELEMENTS.include?(@src[1].downcase)
@src.pos += @src.matched_size
handle_html_start_tag(line) {|iel, ic, ih| handle_kramdown_html_tag(iel, ic, ih) }
Kramdown::Parser::Html::ElementConverter.convert(@root, @tree.children.last) if @options[:html_to_native]
true
elsif @src.check(/^#{OPT_SPACE}#{HTML_TAG_CLOSE_RE}/o) && !HTML_SPAN_ELEMENTS.include?(@src[1].downcase)
name = @src[1].downcase
if @tree.type == :html_element && @tree.value == name
@src.pos += @src.matched_size
throw :stop_block_parsing, :found
else
false
end
else
false
end
end
define_parser(:block_html, HTML_BLOCK_START)
HTML_SPAN_START = /<(#{REXML::Parsers::BaseParser::UNAME_STR}|!--|\/|!\[CDATA\[)/
# Parse the HTML at the current position as span-level HTML.
def parse_span_html
line = @src.current_line_number
if (result = @src.scan(HTML_COMMENT_RE))
@tree.children << Element.new(:xml_comment, result, nil, category: :span, location: line)
elsif @src.scan(HTML_CDATA_RE)
add_text(escape_html(@src[1]))
elsif (result = @src.scan(HTML_TAG_CLOSE_RE))
warning("Found invalidly used HTML closing tag for '#{@src[1]}' on line #{line}")
add_text(result)
elsif (result = @src.scan(HTML_TAG_RE))
tag_name = @src[1]
tag_name.downcase! if HTML_ELEMENT[tag_name.downcase]
if HTML_BLOCK_ELEMENTS.include?(tag_name)
warning("Found block HTML tag '#{tag_name}' in span-level text on line #{line}")
add_text(result)
return
end
attrs = parse_html_attributes(@src[2], line, HTML_ELEMENT[tag_name])
attrs.each_value {|value| value.gsub!(/\n+/, ' ') unless value.empty? }
do_parsing = if HTML_CONTENT_MODEL[tag_name] == :raw || @tree.options[:content_model] == :raw
false
else
@options[:parse_span_html]
end
if (val = HTML_MARKDOWN_ATTR_MAP[attrs.delete('markdown')])
case val
when :block
warning("Cannot use block-level parsing in span-level HTML tag (line #{line}) " \
"- using default mode")
when :span
do_parsing = true
when :default
do_parsing = HTML_CONTENT_MODEL[tag_name] != :raw
when :raw
do_parsing = false
end
end
el = Element.new(:html_element, tag_name, attrs, category: :span, location: line,
content_model: (do_parsing ? :span : :raw), is_closed: !@src[4].nil?)
@tree.children << el
stop_re = /<\/#{Regexp.escape(tag_name)}\s*>/
stop_re = Regexp.new(stop_re.source, Regexp::IGNORECASE) if HTML_ELEMENT[tag_name]
if !@src[4] && !HTML_ELEMENTS_WITHOUT_BODY.include?(el.value)
if parse_spans(el, stop_re, (do_parsing ? nil : [:span_html]))
@src.scan(stop_re)
else
warning("Found no end tag for '#{el.value}' (line #{line}) - auto-closing it")
add_text(@src.rest, el)
@src.terminate
end
end
Kramdown::Parser::Html::ElementConverter.convert(@root, el) if @options[:html_to_native]
else
add_text(@src.getch)
end
end
define_parser(:span_html, HTML_SPAN_START, '<')
end
end
end
kramdown-2.5.1/lib/kramdown/parser/kramdown/blockquote.rb 0000644 0000041 0000041 00000001730 14726466654 023621 0 ustar www-data www-data # -*- coding: utf-8; frozen_string_literal: true -*-
#
#--
# Copyright (C) 2009-2019 Thomas Leitner
#
# This file is part of kramdown which is licensed under the MIT.
#++
#
require 'kramdown/parser/kramdown/blank_line'
require 'kramdown/parser/kramdown/extensions'
require 'kramdown/parser/kramdown/eob'
module Kramdown
module Parser
class Kramdown
BLOCKQUOTE_START = /^#{OPT_SPACE}> ?/
# Parse the blockquote at the current location.
def parse_blockquote
start_line_number = @src.current_line_number
result = @src.scan(PARAGRAPH_MATCH)
until @src.match?(self.class::LAZY_END)
result << @src.scan(PARAGRAPH_MATCH)
end
result.gsub!(BLOCKQUOTE_START, '')
el = new_block_el(:blockquote, nil, nil, location: start_line_number)
@tree.children << el
parse_blocks(el, result)
true
end
define_parser(:blockquote, BLOCKQUOTE_START)
end
end
end
kramdown-2.5.1/lib/kramdown/parser/kramdown/extensions.rb 0000644 0000041 0000041 00000017137 14726466654 023660 0 ustar www-data www-data # -*- coding: utf-8; frozen_string_literal: true -*-
#
#--
# Copyright (C) 2009-2019 Thomas Leitner
#
# This file is part of kramdown which is licensed under the MIT.
#++
#
module Kramdown
module Parser
class Kramdown
IAL_CLASS_ATTR = 'class'
# Parse the string +str+ and extract all attributes and add all found attributes to the hash
# +opts+.
def parse_attribute_list(str, opts)
return if str.strip.empty? || str.strip == ':'
attrs = str.scan(ALD_TYPE_ANY)
attrs.each do |key, sep, val, ref, id_and_or_class, _, _|
if ref
(opts[:refs] ||= []) << ref
elsif id_and_or_class
id_and_or_class.scan(ALD_TYPE_ID_OR_CLASS).each do |id_attr, class_attr|
if class_attr
opts[IAL_CLASS_ATTR] = "#{opts[IAL_CLASS_ATTR]} #{class_attr}".lstrip
else
opts['id'] = id_attr
end
end
else
val.gsub!(/\\(\}|#{sep})/, "\\1")
opts[key] = val
end
end
warning("No or invalid attributes found in IAL/ALD content: #{str}") if attrs.empty?
end
# Update the +ial+ with the information from the inline attribute list +opts+.
def update_ial_with_ial(ial, opts)
(ial[:refs] ||= []).concat(opts[:refs]) if opts.key?(:refs)
opts.each do |k, v|
if k == IAL_CLASS_ATTR
ial[k] = "#{ial[k]} #{v}".lstrip
elsif k.kind_of?(String)
ial[k] = v
end
end
end
# Parse the generic extension at the current point. The parameter +type+ can either be :block
# or :span depending whether we parse a block or span extension tag.
def parse_extension_start_tag(type)
saved_pos = @src.save_pos
start_line_number = @src.current_line_number
@src.pos += @src.matched_size
error_block = lambda do |msg|
warning(msg)
@src.revert_pos(saved_pos)
add_text(@src.getch) if type == :span
false
end
if @src[4] || @src.matched == '{:/}'
name = (@src[4] ? "for '#{@src[4]}' " : '')
return error_block.call("Invalid extension stop tag #{name} found on line " \
"#{start_line_number} - ignoring it")
end
ext = @src[1]
opts = {}
body = nil
parse_attribute_list(@src[2] || '', opts)
unless @src[3]
stop_re = (type == :block ? /#{EXT_BLOCK_STOP_STR % ext}/ : /#{EXT_STOP_STR % ext}/)
if (result = @src.scan_until(stop_re))
body = result.sub!(stop_re, '')
body.chomp! if type == :block
else
return error_block.call("No stop tag for extension '#{ext}' found on line " \
"#{start_line_number} - ignoring it")
end
end
if handle_extension(ext, opts, body, type, start_line_number)
true
else
error_block.call("Invalid extension with name '#{ext}' specified on line " \
"#{start_line_number} - ignoring it")
end
end
def handle_extension(name, opts, body, type, line_no = nil)
case name
when 'comment'
if body.kind_of?(String)
@tree.children << Element.new(:comment, body, nil, category: type, location: line_no)
end
true
when 'nomarkdown'
if body.kind_of?(String)
@tree.children << Element.new(:raw, body, nil, category: type,
location: line_no, type: opts['type'].to_s.split(/\s+/))
end
true
when 'options'
opts.select do |k, v|
k = k.to_sym
if Kramdown::Options.defined?(k)
if @options[:forbidden_inline_options].include?(k) ||
k == :forbidden_inline_options
warning("Option #{k} may not be set inline")
next false
end
begin
val = Kramdown::Options.parse(k, v)
@options[k] = val
(@root.options[:options] ||= {})[k] = val
rescue StandardError
end
false
else
true
end
end.each do |k, _v|
warning("Unknown kramdown option '#{k}'")
end
@tree.children << new_block_el(:eob, :extension) if type == :block
true
else
false
end
end
ALD_ID_CHARS = /[\w-]/
ALD_ANY_CHARS = /\\\}|[^}]/
ALD_ID_NAME = /\w#{ALD_ID_CHARS}*/
ALD_CLASS_NAME = /[^\s.#]+/
ALD_TYPE_KEY_VALUE_PAIR = /(#{ALD_ID_NAME})=("|')((?:\\\}|\\\2|[^}\2])*?)\2/
ALD_TYPE_CLASS_NAME = /\.(#{ALD_CLASS_NAME})/
ALD_TYPE_ID_NAME = /#([A-Za-z][\w:-]*)/
ALD_TYPE_ID_OR_CLASS = /#{ALD_TYPE_ID_NAME}|#{ALD_TYPE_CLASS_NAME}/
ALD_TYPE_ID_OR_CLASS_MULTI = /((?:#{ALD_TYPE_ID_NAME}|#{ALD_TYPE_CLASS_NAME})+)/
ALD_TYPE_REF = /(#{ALD_ID_NAME})/
ALD_TYPE_ANY = /(?:\A|\s)(?:#{ALD_TYPE_KEY_VALUE_PAIR}|#{ALD_TYPE_REF}|#{ALD_TYPE_ID_OR_CLASS_MULTI})(?=\s|\Z)/
ALD_START = /^#{OPT_SPACE}\{:(#{ALD_ID_NAME}):(#{ALD_ANY_CHARS}+)\}\s*?\n/
EXT_STOP_STR = "\\{:/(%s)?\\}"
EXT_START_STR = "\\{::(\\w+)(?:\\s(#{ALD_ANY_CHARS}*?)|)(\\/)?\\}"
EXT_BLOCK_START = /^#{OPT_SPACE}(?:#{EXT_START_STR}|#{EXT_STOP_STR % ALD_ID_NAME})\s*?\n/
EXT_BLOCK_STOP_STR = "^#{OPT_SPACE}#{EXT_STOP_STR}\s*?\n"
IAL_BLOCK = /\{:(?!:|\/)(#{ALD_ANY_CHARS}+)\}\s*?\n/
IAL_BLOCK_START = /^#{OPT_SPACE}#{IAL_BLOCK}/
BLOCK_EXTENSIONS_START = /^#{OPT_SPACE}\{:/
# Parse one of the block extensions (ALD, block IAL or generic extension) at the current
# location.
def parse_block_extensions
if @src.scan(ALD_START)
parse_attribute_list(@src[2], @alds[@src[1]] ||= {})
@tree.children << new_block_el(:eob, :ald)
true
elsif @src.check(EXT_BLOCK_START)
parse_extension_start_tag(:block)
elsif @src.scan(IAL_BLOCK_START)
if (last_child = @tree.children.last) && last_child.type != :blank &&
(last_child.type != :eob ||
[:link_def, :abbrev_def, :footnote_def].include?(last_child.value))
parse_attribute_list(@src[1], last_child.options[:ial] ||= {})
@tree.children << new_block_el(:eob, :ial) unless @src.check(IAL_BLOCK_START)
else
parse_attribute_list(@src[1], @block_ial ||= {})
end
true
else
false
end
end
define_parser(:block_extensions, BLOCK_EXTENSIONS_START)
EXT_SPAN_START = /#{EXT_START_STR}|#{EXT_STOP_STR % ALD_ID_NAME}/
IAL_SPAN_START = /\{:(#{ALD_ANY_CHARS}+)\}/
SPAN_EXTENSIONS_START = /\{:/
# Parse the extension span at the current location.
def parse_span_extensions
if @src.check(EXT_SPAN_START)
parse_extension_start_tag(:span)
elsif @src.check(IAL_SPAN_START)
if (last_child = @tree.children.last) && last_child.type != :text
@src.pos += @src.matched_size
attr = {}
parse_attribute_list(@src[1], attr)
update_ial_with_ial(last_child.options[:ial] ||= {}, attr)
update_attr_with_ial(last_child.attr, attr)
else
warning("Found span IAL after text - ignoring it")
add_text(@src.getch)
end
else
add_text(@src.getch)
end
end
define_parser(:span_extensions, SPAN_EXTENSIONS_START, '\{:')
end
end
end
kramdown-2.5.1/lib/kramdown/parser/kramdown.rb 0000644 0000041 0000041 00000032265 14726466654 021460 0 ustar www-data www-data # -*- coding: utf-8; frozen_string_literal: true -*-
#
#--
# Copyright (C) 2009-2019 Thomas Leitner
#
# This file is part of kramdown which is licensed under the MIT.
#++
#
require 'strscan'
require 'stringio'
require 'kramdown/parser'
# TODO: use [[:alpha:]] in all regexp to allow parsing of international values in 1.9.1
# NOTE: use @src.pre_match only before other check/match?/... operations, otherwise the content is changed
module Kramdown
module Parser
# Used for parsing a document in kramdown format.
#
# If you want to extend the functionality of the parser, you need to do the following:
#
# * Create a new subclass
# * add the needed parser methods
# * modify the @block_parsers and @span_parsers variables and add the names of your parser
# methods
#
# Here is a small example for an extended parser class that parses ERB style tags as raw text if
# they are used as span-level elements (an equivalent block-level parser should probably also be
# made to handle the block case):
#
# require 'kramdown/parser/kramdown'
#
# class Kramdown::Parser::ERBKramdown < Kramdown::Parser::Kramdown
#
# def initialize(source, options)
# super
# @span_parsers.unshift(:erb_tags)
# end
#
# ERB_TAGS_START = /<%.*?%>/
#
# def parse_erb_tags
# @src.pos += @src.matched_size
# @tree.children << Element.new(:raw, @src.matched)
# end
# define_parser(:erb_tags, ERB_TAGS_START, '<%')
#
# end
#
# The new parser can be used like this:
#
# require 'kramdown/document'
# # require the file with the above parser class
#
# Kramdown::Document.new(input_text, :input => 'ERBKramdown').to_html
#
class Kramdown < Base
include ::Kramdown
# Create a new Kramdown parser object with the given +options+.
def initialize(source, options)
super
reset_env
@alds = {}
@footnotes = {}
@link_defs = {}
update_link_definitions(@options[:link_defs])
@block_parsers = [:blank_line, :codeblock, :codeblock_fenced, :blockquote, :atx_header,
:horizontal_rule, :list, :definition_list, :block_html, :setext_header,
:block_math, :table, :footnote_definition, :link_definition,
:abbrev_definition, :block_extensions, :eob_marker, :paragraph]
@span_parsers = [:emphasis, :codespan, :autolink, :span_html, :footnote_marker, :link,
:smart_quotes, :inline_math, :span_extensions, :html_entity,
:typographic_syms, :line_break, :escaped_chars]
@span_pattern_cache ||= Hash.new {|h, k| h[k] = {} }
end
private_class_method(:new, :allocate)
# The source string provided on initialization is parsed into the @root element.
def parse
configure_parser
parse_blocks(@root, adapt_source(source))
update_tree(@root)
correct_abbreviations_attributes
replace_abbreviations(@root)
@footnotes.each do |_name, data|
update_tree(data[:content])
replace_abbreviations(data[:content])
end
footnote_count = 0
@footnotes.each do |name, data|
(footnote_count += 1; next) if data.key?(:marker)
line = data[:content].options[:location]
warning("Footnote definition for '#{name}' on line #{line} is unreferenced - ignoring")
end
@root.options[:footnote_count] = footnote_count
end
protected
# :doc:
#
# Update the parser specific link definitions with the data from +link_defs+ (the value of the
# :link_defs option).
#
# The parameter +link_defs+ is a hash where the keys are possibly unnormalized link IDs and
# the values are two element arrays consisting of the link target and a title (can be +nil+).
def update_link_definitions(link_defs)
link_defs.each {|k, v| @link_defs[normalize_link_id(k)] = v }
end
# Adapt the object to allow parsing like specified in the options.
def configure_parser
@parsers = {}
(@block_parsers + @span_parsers).each do |name|
if self.class.has_parser?(name)
@parsers[name] = self.class.parser(name)
else
raise Kramdown::Error, "Unknown parser: #{name}"
end
end
@span_start, @span_start_re = span_parser_regexps
end
# Create the needed span parser regexps.
def span_parser_regexps(parsers = @span_parsers)
span_start = /#{parsers.map {|name| @parsers[name].span_start }.join('|')}/
[span_start, /(?=#{span_start})/]
end
# Parse all block-level elements in +text+ into the element +el+.
def parse_blocks(el, text = nil)
@stack.push([@tree, @src, @block_ial])
@tree, @block_ial = el, nil
@src = (text.nil? ? @src : ::Kramdown::Utils::StringScanner.new(text, el.options[:location]))
status = catch(:stop_block_parsing) do
until @src.eos?
@block_parsers.any? do |name|
if @src.check(@parsers[name].start_re)
send(@parsers[name].method)
else
false
end
end || begin
warning('Warning: this should not occur - no block parser handled the line')
add_text(@src.scan(/.*\n/))
end
end
end
@tree, @src, @block_ial = *@stack.pop
status
end
# Update the tree by parsing all :+raw_text+ elements with the span-level parser (resets the
# environment) and by updating the attributes from the IALs.
def update_tree(element)
last_blank = nil
element.children.map! do |child|
case child.type
when :raw_text
last_blank = nil
reset_env(src: ::Kramdown::Utils::StringScanner.new(child.value, element.options[:location]),
text_type: :text)
parse_spans(child)
child.children
when :eob
update_attr_with_ial(child.attr, child.options[:ial]) if child.options[:ial]
[]
when :blank
if last_blank
last_blank.value << child.value
[]
else
last_blank = child
child
end
else
last_blank = nil
update_tree(child)
update_attr_with_ial(child.attr, child.options[:ial]) if child.options[:ial]
# DEPRECATED: option auto_id_stripping will be removed in 2.0 because then this will be
# the default behaviour
if child.type == :dt || (child.type == :header && @options[:auto_id_stripping])
update_raw_text(child)
end
child
end
end.flatten!
end
def span_pattern_cache(stop_re, span_start)
@span_pattern_cache[stop_re][span_start] ||= /(?=#{Regexp.union(stop_re, span_start)})/
end
private :span_pattern_cache
# Parse all span-level elements in the source string of @src into +el+.
#
# If the parameter +stop_re+ (a regexp) is used, parsing is immediately stopped if the regexp
# matches and if no block is given or if a block is given and it returns +true+.
#
# The parameter +parsers+ can be used to specify the (span-level) parsing methods that should
# be used for parsing.
#
# The parameter +text_type+ specifies the type which should be used for created text nodes.
def parse_spans(el, stop_re = nil, parsers = nil, text_type = @text_type)
@stack.push([@tree, @text_type]) unless @tree.nil?
@tree, @text_type = el, text_type
span_start = @span_start
span_start_re = @span_start_re
span_start, span_start_re = span_parser_regexps(parsers) if parsers
parsers ||= @span_parsers
used_re = (stop_re.nil? ? span_start_re : span_pattern_cache(stop_re, span_start))
stop_re_found = false
while !@src.eos? && !stop_re_found
if (result = @src.scan_until(used_re))
add_text(result)
if stop_re && @src.check(stop_re)
stop_re_found = (block_given? ? yield : true)
end
processed = parsers.any? do |name|
if @src.check(@parsers[name].start_re)
send(@parsers[name].method)
true
else
false
end
end unless stop_re_found
add_text(@src.getch) if !processed && !stop_re_found
else
(add_text(@src.rest); @src.terminate) unless stop_re
break
end
end
@tree, @text_type = @stack.pop
stop_re_found
end
# Reset the current parsing environment. The parameter +env+ can be used to set initial
# values for one or more environment variables.
def reset_env(opts = {})
opts = {text_type: :raw_text, stack: []}.merge(opts)
@src = opts[:src]
@tree = opts[:tree]
@block_ial = opts[:block_ial]
@stack = opts[:stack]
@text_type = opts[:text_type]
end
# Return the current parsing environment.
def save_env
[@src, @tree, @block_ial, @stack, @text_type]
end
# Restore the current parsing environment.
def restore_env(env)
@src, @tree, @block_ial, @stack, @text_type = *env
end
# Update the given attributes hash +attr+ with the information from the inline attribute list
# +ial+ and all referenced ALDs.
def update_attr_with_ial(attr, ial)
ial[:refs]&.each do |ref|
update_attr_with_ial(attr, ref) if (ref = @alds[ref])
end
ial.each do |k, v|
if k == IAL_CLASS_ATTR
attr[k] = "#{attr[k]} #{v}".lstrip
elsif k.kind_of?(String)
attr[k] = v
end
end
end
# Update the raw text for automatic ID generation.
def update_raw_text(item)
raw_text = +''
append_text = lambda do |child|
if child.type == :text
raw_text << child.value
else
child.children.each {|c| append_text.call(c) }
end
end
append_text.call(item)
item.options[:raw_text] = raw_text
end
# Create a new block-level element, taking care of applying a preceding block IAL if it
# exists. This method should always be used for creating a block-level element!
def new_block_el(*args)
el = Element.new(*args)
if @block_ial
el.options[:ial] = @block_ial
@block_ial = nil
end
el
end
@@parsers = {}
# Struct class holding all the needed data for one block/span-level parser method.
Data = Struct.new(:name, :start_re, :span_start, :method)
# Add a parser method
#
# * with the given +name+,
# * using +start_re+ as start regexp
# * and, for span parsers, +span_start+ as a String that can be used in a regexp and
# which identifies the starting character(s)
#
# to the registry. The method name is automatically derived from the +name+ or can explicitly
# be set by using the +meth_name+ parameter.
def self.define_parser(name, start_re, span_start = nil, meth_name = "parse_#{name}")
raise "A parser with the name #{name} already exists!" if @@parsers.key?(name)
@@parsers[name] = Data.new(name, start_re, span_start, meth_name)
end
# Return the Data structure for the parser +name+.
def self.parser(name = nil)
@@parsers[name]
end
# Return +true+ if there is a parser called +name+.
def self.has_parser?(name)
@@parsers.key?(name)
end
# Regexp for matching indentation (one tab or four spaces)
INDENT = /^(?:\t| {4})/
# Regexp for matching the optional space (zero or up to three spaces)
OPT_SPACE = / {0,3}/
require 'kramdown/parser/kramdown/blank_line'
require 'kramdown/parser/kramdown/eob'
require 'kramdown/parser/kramdown/paragraph'
require 'kramdown/parser/kramdown/header'
require 'kramdown/parser/kramdown/blockquote'
require 'kramdown/parser/kramdown/table'
require 'kramdown/parser/kramdown/codeblock'
require 'kramdown/parser/kramdown/horizontal_rule'
require 'kramdown/parser/kramdown/list'
require 'kramdown/parser/kramdown/link'
require 'kramdown/parser/kramdown/extensions'
require 'kramdown/parser/kramdown/footnote'
require 'kramdown/parser/kramdown/html'
require 'kramdown/parser/kramdown/escaped_chars'
require 'kramdown/parser/kramdown/html_entity'
require 'kramdown/parser/kramdown/line_break'
require 'kramdown/parser/kramdown/typographic_symbol'
require 'kramdown/parser/kramdown/autolink'
require 'kramdown/parser/kramdown/codespan'
require 'kramdown/parser/kramdown/emphasis'
require 'kramdown/parser/kramdown/smart_quotes'
require 'kramdown/parser/kramdown/math'
require 'kramdown/parser/kramdown/abbreviation'
end
end
end
kramdown-2.5.1/lib/kramdown/parser/base.rb 0000644 0000041 0000041 00000011522 14726466654 020541 0 ustar www-data www-data # -*- coding: utf-8; frozen_string_literal: true -*-
#
#--
# Copyright (C) 2009-2019 Thomas Leitner
#
# This file is part of kramdown which is licensed under the MIT.
#++
#
require 'kramdown/utils'
require 'kramdown/parser'
module Kramdown
module Parser
# == \Base class for parsers
#
# This class serves as base class for parsers. It provides common methods that can/should be
# used by all parsers, especially by those using StringScanner(Kramdown) for parsing.
#
# A parser object is used as a throw-away object, i.e. it is only used for storing the needed
# state information during parsing. Therefore one can't instantiate a parser object directly but
# only use the Base::parse method.
#
# == Implementing a parser
#
# Implementing a new parser is rather easy: just derive a new class from this class and put it
# in the Kramdown::Parser module -- the latter is needed so that the auto-detection of the new
# parser works correctly. Then you need to implement the +#parse+ method which has to contain
# the parsing code.
#
# Have a look at the Base::parse, Base::new and Base#parse methods for additional information!
class Base
# The hash with the parsing options.
attr_reader :options
# The array with the parser warnings.
attr_reader :warnings
# The original source string.
attr_reader :source
# The root element of element tree that is created from the source string.
attr_reader :root
# Initialize the parser object with the +source+ string and the parsing +options+.
#
# The @root element, the @warnings array and @text_type (specifies the default type for newly
# created text nodes) are automatically initialized.
def initialize(source, options)
@source = source
@options = Kramdown::Options.merge(options)
@root = Element.new(:root, nil, nil, encoding: (source.encoding rescue nil), location: 1,
options: {}, abbrev_defs: {}, abbrev_attr: {})
@root.options[:abbrev_defs].default_proc = @root.options[:abbrev_attr].default_proc =
lambda do |h, k|
k_mod = k.gsub(/[\s\p{Z}]+/, " ")
k != k_mod ? h[k_mod] : nil
end
@warnings = []
@text_type = :text
end
private_class_method(:new, :allocate)
# Parse the +source+ string into an element tree, possibly using the parsing +options+, and
# return the root element of the element tree and an array with warning messages.
#
# Initializes a new instance of the calling class and then calls the +#parse+ method that must
# be implemented by each subclass.
def self.parse(source, options = {})
parser = new(source, options)
parser.parse
[parser.root, parser.warnings]
end
# Parse the source string into an element tree.
#
# The parsing code should parse the source provided in @source and build an element tree the
# root of which should be @root.
#
# This is the only method that has to be implemented by sub-classes!
def parse
raise NotImplementedError
end
# Add the given warning +text+ to the warning array.
def warning(text)
@warnings << text
# TODO: add position information
end
# Modify the string +source+ to be usable by the parser (unifies line ending characters to
# +\n+ and makes sure +source+ ends with a new line character).
def adapt_source(source)
unless source.valid_encoding?
raise "The source text contains invalid characters for the used encoding #{source.encoding}"
end
source = source.encode('UTF-8')
source.gsub!(/\r\n?/, "\n")
source.chomp!
source << "\n"
end
# This helper method adds the given +text+ either to the last element in the +tree+ if it is a
# +type+ element or creates a new text element with the given +type+.
def add_text(text, tree = @tree, type = @text_type)
last = tree.children.last
if last && last.type == type
last.value << text
elsif !text.empty?
location = (last && last.options[:location] || tree.options[:location])
tree.children << Element.new(type, text, nil, location: location)
end
end
# Extract the part of the StringScanner +strscan+ backed string specified by the +range+. This
# method works correctly under Ruby 1.8 and Ruby 1.9.
def extract_string(range, strscan)
result = nil
begin
enc = strscan.string.encoding
strscan.string.force_encoding('ASCII-8BIT')
result = strscan.string[range].force_encoding(enc)
ensure
strscan.string.force_encoding(enc)
end
result
end
end
end
end
kramdown-2.5.1/lib/kramdown/parser/markdown.rb 0000644 0000041 0000041 00000003721 14726466654 021453 0 ustar www-data www-data # -*- coding: utf-8; frozen_string_literal: true -*-
#
#--
# Copyright (C) 2009-2019 Thomas Leitner
#
# This file is part of kramdown which is licensed under the MIT.
#++
#
require 'kramdown/parser'
module Kramdown
module Parser
# Used for parsing a document in Markdown format.
#
# This parser is based on the kramdown parser and removes the parser methods for the additional
# non-Markdown features. However, since some things are handled differently by the kramdown
# parser methods (like deciding when a list item contains just text), this parser differs from
# real Markdown parsers in some respects.
#
# Note, though, that the parser basically fails just one of the Markdown test cases (some others
# also fail but those failures are negligible).
class Markdown < Kramdown
# Array with all the parsing methods that should be removed from the standard kramdown parser.
EXTENDED = [:codeblock_fenced, :table, :definition_list, :footnote_definition,
:abbrev_definition, :block_math, :block_extensions,
:footnote_marker, :smart_quotes, :inline_math, :span_extensions, :typographic_syms]
def initialize(source, options) # :nodoc:
super
@block_parsers.delete_if {|i| EXTENDED.include?(i) }
@span_parsers.delete_if {|i| EXTENDED.include?(i) }
end
# :stopdoc:
BLOCK_BOUNDARY = /#{BLANK_LINE}|#{EOB_MARKER}|\Z/
LAZY_END = /#{BLANK_LINE}|#{EOB_MARKER}|^#{OPT_SPACE}#{LAZY_END_HTML_STOP}|
^#{OPT_SPACE}#{LAZY_END_HTML_START}|\Z/x
CODEBLOCK_MATCH = /(?:#{BLANK_LINE}?(?:#{INDENT}[ \t]*\S.*\n)+)*/
PARAGRAPH_END = LAZY_END
IAL_RAND_CHARS = (('a'..'z').to_a + ('0'..'9').to_a)
IAL_RAND_STRING = (1..20).collect { IAL_RAND_CHARS[rand(IAL_RAND_CHARS.size)] }.join
LIST_ITEM_IAL = /^\s*(#{IAL_RAND_STRING})?\s*\n/
IAL_SPAN_START = LIST_ITEM_IAL
# :startdoc:
end
end
end
kramdown-2.5.1/lib/kramdown/parser/html.rb 0000644 0000041 0000041 00000056721 14726466654 020605 0 ustar www-data www-data # -*- coding: utf-8; frozen_string_literal: true -*-
#
#--
# Copyright (C) 2009-2019 Thomas Leitner
#
# This file is part of kramdown which is licensed under the MIT.
#++
#
require 'rexml/parsers/baseparser'
require 'strscan'
require 'kramdown/utils'
require 'kramdown/parser'
module Kramdown
module Parser
# Used for parsing an HTML document.
#
# The parsing code is in the Parser module that can also be used by other parsers.
class Html < Base
# Contains all constants that are used when parsing.
module Constants
# :stopdoc:
# The following regexps are based on the ones used by REXML, with some slight modifications.
HTML_DOCTYPE_RE = //im
HTML_COMMENT_RE = //m
HTML_INSTRUCTION_RE = /<\?(.*?)\?>/m
HTML_CDATA_RE = //m
HTML_ATTRIBUTE_RE = /\s*(#{REXML::Parsers::BaseParser::UNAME_STR})(?:\s*=\s*(?:(\p{Word}+)|("|')(.*?)\3))?/m
HTML_TAG_RE = /<((?>#{REXML::Parsers::BaseParser::UNAME_STR}))\s*((?>\s+#{REXML::Parsers::BaseParser::UNAME_STR}(?:\s*=\s*(?:\p{Word}+|("|').*?\3))?)*)\s*(\/)?>/m
HTML_TAG_CLOSE_RE = /<\/(#{REXML::Parsers::BaseParser::UNAME_STR})\s*>/m
HTML_ENTITY_RE = /&([\w:][\w.:-]*);|(\d+);|&\#x([0-9a-fA-F]+);/
HTML_CONTENT_MODEL_BLOCK = %w[address applet article aside blockquote body
dd details div dl fieldset figure figcaption
footer form header hgroup iframe li main
map menu nav noscript object section summary td]
HTML_CONTENT_MODEL_SPAN = %w[a abbr acronym b bdo big button cite caption del dfn dt em
h1 h2 h3 h4 h5 h6 i ins label legend optgroup p q rb rbc
rp rt rtc ruby select small span strong sub sup th tt]
HTML_CONTENT_MODEL_RAW = %w[script style math option textarea pre code kbd samp var]
# The following elements are also parsed as raw since they need child elements that cannot
# be expressed using kramdown syntax: colgroup table tbody thead tfoot tr ul ol
HTML_CONTENT_MODEL = Hash.new {|h, k| h[k] = :raw }
HTML_CONTENT_MODEL_BLOCK.each {|i| HTML_CONTENT_MODEL[i] = :block }
HTML_CONTENT_MODEL_SPAN.each {|i| HTML_CONTENT_MODEL[i] = :span }
HTML_CONTENT_MODEL_RAW.each {|i| HTML_CONTENT_MODEL[i] = :raw }
# Some HTML elements like script belong to both categories (i.e. are valid in block and
# span HTML) and don't appear therefore!
# script, textarea
HTML_SPAN_ELEMENTS = %w[a abbr acronym b big bdo br button cite code del dfn em i img input
ins kbd label mark option q rb rbc rp rt rtc ruby samp select small
span strong sub sup time tt u var]
HTML_BLOCK_ELEMENTS = %w[address article aside applet body blockquote caption col colgroup
dd div dl dt fieldset figcaption footer form h1 h2 h3 h4 h5 h6
header hgroup hr html head iframe legend menu li main map nav ol
optgroup p pre section summary table tbody td th thead tfoot tr ul]
HTML_ELEMENTS_WITHOUT_BODY = %w[area base br col command embed hr img input keygen link
meta param source track wbr]
HTML_ELEMENT = Hash.new(false)
(HTML_SPAN_ELEMENTS + HTML_BLOCK_ELEMENTS + HTML_ELEMENTS_WITHOUT_BODY +
HTML_CONTENT_MODEL.keys).each do |a|
HTML_ELEMENT[a] = true
end
end
# Contains the parsing methods. This module can be mixed into any parser to get HTML parsing
# functionality. The only thing that must be provided by the class are instance variable
# @stack for storing the needed state and @src (instance of StringScanner) for the actual
# parsing.
module Parser
include Constants
# Process the HTML start tag that has already be scanned/checked via @src.
#
# Does the common processing steps and then yields to the caller for further processing
# (first parameter is the created element; the second parameter is +true+ if the HTML
# element is already closed, ie. contains no body; the third parameter specifies whether the
# body - and the end tag - need to be handled in case closed=false).
def handle_html_start_tag(line = nil) # :yields: el, closed, handle_body
name = @src[1]
name.downcase! if HTML_ELEMENT[name.downcase]
closed = !@src[4].nil?
attrs = parse_html_attributes(@src[2], line, HTML_ELEMENT[name])
el = Element.new(:html_element, name, attrs, category: :block)
el.options[:location] = line if line
@tree.children << el
if !closed && HTML_ELEMENTS_WITHOUT_BODY.include?(el.value)
closed = true
end
if name == 'script' || name == 'style'
handle_raw_html_tag(name)
yield(el, false, false)
else
yield(el, closed, true)
end
end
# Parses the given string for HTML attributes and returns the resulting hash.
#
# If the optional +line+ parameter is supplied, it is used in warning messages.
#
# If the optional +in_html_tag+ parameter is set to +false+, attributes are not modified to
# contain only lowercase letters.
def parse_html_attributes(str, line = nil, in_html_tag = true)
attrs = {}
str.scan(HTML_ATTRIBUTE_RE).each do |attr, val, _sep, quoted_val|
attr.downcase! if in_html_tag
if attrs.key?(attr)
warning("Duplicate HTML attribute '#{attr}' on line #{line || '?'} - overwriting previous one")
end
attrs[attr] = val || quoted_val || ""
end
attrs
end
# Handle the raw HTML tag at the current position.
def handle_raw_html_tag(name)
curpos = @src.pos
if @src.scan_until(/(?=<\/#{name}\s*>)/mi)
add_text(extract_string(curpos...@src.pos, @src), @tree.children.last, :raw)
@src.scan(HTML_TAG_CLOSE_RE)
else
add_text(@src.rest, @tree.children.last, :raw)
@src.terminate
warning("Found no end tag for '#{name}' - auto-closing it")
end
end
HTML_RAW_START = /(?=<(#{REXML::Parsers::BaseParser::UNAME_STR}|\/|!--|\?|!\[CDATA\[))/ # :nodoc:
# Parse raw HTML from the current source position, storing the found elements in +el+.
# Parsing continues until one of the following criteria are fulfilled:
#
# - The end of the document is reached.
# - The matching end tag for the element +el+ is found (only used if +el+ is an HTML
# element).
#
# When an HTML start tag is found, processing is deferred to #handle_html_start_tag,
# providing the block given to this method.
def parse_raw_html(el, &block)
@stack.push(@tree)
@tree = el
done = false
while !@src.eos? && !done
if (result = @src.scan_until(HTML_RAW_START))
add_text(result, @tree, :text)
line = @src.current_line_number
if (result = @src.scan(HTML_COMMENT_RE))
@tree.children << Element.new(:xml_comment, result, nil, category: :block, location: line)
elsif (result = @src.scan(HTML_INSTRUCTION_RE))
@tree.children << Element.new(:xml_pi, result, nil, category: :block, location: line)
elsif @src.scan(HTML_CDATA_RE)
@tree.children << Element.new(:text, @src[1], nil, cdata: true, location: line)
elsif @src.scan(HTML_TAG_RE)
if method(:handle_html_start_tag).arity.abs >= 1
handle_html_start_tag(line, &block)
else
handle_html_start_tag(&block) # DEPRECATED: method needs to accept line number in 2.0
end
elsif @src.scan(HTML_TAG_CLOSE_RE)
if @tree.value == (HTML_ELEMENT[@tree.value] ? @src[1].downcase : @src[1])
done = true
else
add_text(@src.matched, @tree, :text)
warning("Found invalidly used HTML closing tag for '#{@src[1]}' on " \
"line #{line} - ignoring it")
end
else
add_text(@src.getch, @tree, :text)
end
else
add_text(@src.rest, @tree, :text)
@src.terminate
if @tree.type == :html_element
warning("Found no end tag for '#{@tree.value}' on line " \
"#{@tree.options[:location]} - auto-closing it")
end
done = true
end
end
@tree = @stack.pop
end
end
# Converts HTML elements to native elements if possible.
class ElementConverter
# :stopdoc:
include Constants
include ::Kramdown::Utils::Entities
REMOVE_TEXT_CHILDREN = %w[html head hgroup ol ul dl table colgroup tbody thead tfoot tr
select optgroup]
WRAP_TEXT_CHILDREN = %w[body section nav article aside header footer address div li dd
blockquote figure figcaption fieldset form]
REMOVE_WHITESPACE_CHILDREN = %w[body section nav article aside header footer address
div li dd blockquote figure figcaption td th fieldset form]
STRIP_WHITESPACE = %w[address article aside blockquote body caption dd div dl dt fieldset
figcaption form footer header h1 h2 h3 h4 h5 h6 legend li nav p
section td th]
SIMPLE_ELEMENTS = %w[em strong blockquote hr br img p thead tbody tfoot tr td th ul ol dl
li dl dt dd]
def initialize(root)
@root = root
end
def self.convert(root, el = root)
new(root).process(el)
end
# Convert the element +el+ and its children.
def process(el, do_conversion = true, preserve_text = false, parent = nil)
case el.type
when :xml_comment, :xml_pi
ptype = if parent.nil?
'div'
else
case parent.type
when :html_element then parent.value
when :code_span then 'code'
when :code_block then 'pre'
when :header then 'h1'
else parent.type.to_s
end
end
el.options.replace(category: (HTML_CONTENT_MODEL[ptype] == :span ? :span : :block))
return
when :html_element
# do nothing
when :root
el.children.map! do |c|
if c.type == :text
process_text(c.value, !do_conversion)
else
process(c)
c
end
end.flatten!
remove_whitespace_children(el)
return
else return
end
mname = "convert_#{el.value}"
if do_conversion && self.class.method_defined?(mname)
send(mname, el)
else
type = el.value
remove_text_children(el) if do_conversion && REMOVE_TEXT_CHILDREN.include?(type)
if do_conversion && SIMPLE_ELEMENTS.include?(type)
set_basics(el, type.intern)
process_children(el, do_conversion, preserve_text)
else
process_html_element(el, do_conversion, preserve_text)
end
if do_conversion
strip_whitespace(el) if STRIP_WHITESPACE.include?(type)
remove_whitespace_children(el) if REMOVE_WHITESPACE_CHILDREN.include?(type)
wrap_text_children(el) if WRAP_TEXT_CHILDREN.include?(type)
end
end
end
def process_children(el, do_conversion = true, preserve_text = false)
el.children.map! do |c|
if c.type == :text
process_text(c.value, preserve_text || !do_conversion)
else
process(c, do_conversion, preserve_text, el)
c
end
end.flatten!
end
# Process the HTML text +raw+: compress whitespace (if +preserve+ is +false+) and convert
# entities in entity elements.
def process_text(raw, preserve = false)
raw.gsub!(/\s+/, ' ') unless preserve
src = Kramdown::Utils::StringScanner.new(raw)
result = []
until src.eos?
if (tmp = src.scan_until(/(?=#{HTML_ENTITY_RE})/o))
result << Element.new(:text, tmp)
src.scan(HTML_ENTITY_RE)
val = src[1] || src[2]&.to_i || src[3].hex
result << if %w[lsquo rsquo ldquo rdquo].include?(val)
Element.new(:smart_quote, val.intern)
elsif %w[mdash ndash hellip laquo raquo].include?(val)
Element.new(:typographic_sym, val.intern)
else
begin
Element.new(:entity, entity(val), nil, original: src.matched)
rescue ::Kramdown::Error
src.pos -= src.matched_size - 1
Element.new(:entity, ::Kramdown::Utils::Entities.entity('amp'))
end
end
else
result << Element.new(:text, src.rest)
src.terminate
end
end
result
end
def process_html_element(el, do_conversion = true, preserve_text = false)
el.options.replace(category: HTML_SPAN_ELEMENTS.include?(el.value) ? :span : :block,
content_model: (do_conversion ? HTML_CONTENT_MODEL[el.value] : :raw))
process_children(el, do_conversion, preserve_text)
end
def remove_text_children(el)
el.children.delete_if {|c| c.type == :text }
end
def wrap_text_children(el)
tmp = []
last_is_p = false
el.children.each do |c|
if !c.block? || c.type == :text
unless last_is_p
tmp << Element.new(:p, nil, nil, transparent: true)
last_is_p = true
end
tmp.last.children << c
tmp
else
tmp << c
last_is_p = false
end
end
el.children = tmp
end
def strip_whitespace(el)
return if el.children.empty?
if el.children.first.type == :text
el.children.first.value.lstrip!
end
if el.children.last.type == :text
el.children.last.value.rstrip!
end
end
def remove_whitespace_children(el)
i = -1
el.children = el.children.reject do |c|
i += 1
c.type == :text && c.value.strip.empty? &&
(i == 0 || i == el.children.length - 1 || (el.children[i - 1].block? &&
el.children[i + 1].block?))
end
end
def set_basics(el, type, opts = {})
el.type = type
el.options.replace(opts)
el.value = nil
end
def extract_text(el, raw)
raw << el.value.to_s if el.type == :text
el.children.each {|c| extract_text(c, raw) }
end
def convert_textarea(el)
process_html_element(el, true, true)
end
def convert_a(el)
if el.attr['href']
set_basics(el, :a)
process_children(el)
else
process_html_element(el, false)
end
end
EMPHASIS_TYPE_MAP = {'em' => :em, 'i' => :em, 'strong' => :strong, 'b' => :strong}
def convert_em(el)
text = +''
extract_text(el, text)
if text =~ /\A\s/ || text =~ /\s\z/
process_html_element(el, false)
else
set_basics(el, EMPHASIS_TYPE_MAP[el.value])
process_children(el)
end
end
%w[b strong i].each do |i|
alias_method("convert_#{i}".to_sym, :convert_em)
end
def convert_h1(el)
set_basics(el, :header, level: el.value[1..1].to_i)
extract_text(el, el.options[:raw_text] = +'')
process_children(el)
end
%w[h2 h3 h4 h5 h6].each do |i|
alias_method("convert_#{i}".to_sym, :convert_h1)
end
def convert_code(el)
raw = +''
extract_text(el, raw)
result = process_text(raw, true)
begin
str = result.inject(+'') do |mem, c|
case c.type
when :text
mem << c.value
when :entity
mem << if [60, 62, 34, 38].include?(c.value.code_point)
c.value.code_point.chr
else
c.value.char
end
when :smart_quote, :typographic_sym
mem << entity(c.value.to_s).char
else
raise "Bug - please report"
end
end
result.clear
result << Element.new(:text, str)
rescue StandardError
end
if result.length > 1 || result.first.type != :text
process_html_element(el, false, true)
else
if el.value == 'code'
set_basics(el, :codespan)
el.attr['class']&.gsub!(/\s+\bhighlighter-\w+\b|\bhighlighter-\w+\b\s*/, '')
else
set_basics(el, :codeblock)
if el.children.size == 1 && el.children.first.value == 'code'
value = (el.children.first.attr['class'] || '').scan(/\blanguage-\S+/).first
el.attr['class'] = "#{value} #{el.attr['class']}".rstrip if value
end
end
el.value = result.first.value
el.children.clear
end
end
alias convert_pre convert_code
def convert_table(el)
unless is_simple_table?(el)
process_html_element(el, false)
return
end
remove_text_children(el)
process_children(el)
set_basics(el, :table)
calc_alignment = lambda do |c|
if c.type == :tr
el.options[:alignment] = c.children.map do |td|
if td.attr['style']
td.attr['style'].slice!(/(?:;\s*)?text-align:\s+(center|left|right)/)
td.attr.delete('style') if td.attr['style'].strip.empty?
$1 ? $1.to_sym : :default
else
:default
end
end
else
c.children.each {|cc| calc_alignment.call(cc) }
end
end
calc_alignment.call(el)
el.children.delete_if {|c| c.type == :html_element }
change_th_type = lambda do |c|
if c.type == :th
c.type = :td
else
c.children.each {|cc| change_th_type.call(cc) }
end
end
change_th_type.call(el)
if el.children.first.type == :tr
tbody = Element.new(:tbody)
tbody.children = el.children
el.children = [tbody]
end
end
def is_simple_table?(el)
only_phrasing_content = lambda do |c|
c.children.all? do |cc|
(cc.type == :text || !HTML_BLOCK_ELEMENTS.include?(cc.value)) && only_phrasing_content.call(cc)
end
end
check_cells = proc do |c|
if c.value == 'th' || c.value == 'td'
return false unless only_phrasing_content.call(c)
else
c.children.each {|cc| check_cells.call(cc) }
end
end
check_cells.call(el)
nr_cells = 0
check_nr_cells = lambda do |t|
if t.value == 'tr'
count = t.children.count {|cc| cc.value == 'th' || cc.value == 'td' }
if count != nr_cells
if nr_cells == 0
nr_cells = count
else
nr_cells = -1
break
end
end
else
t.children.each {|cc| check_nr_cells.call(cc) }
end
end
check_nr_cells.call(el)
return false if nr_cells == -1 || nr_cells == 0
alignment = nil
check_alignment = proc do |t|
if t.value == 'tr'
cur_alignment = t.children.select {|cc| cc.value == 'th' || cc.value == 'td' }.map do |cell|
md = /text-align:\s+(center|left|right|justify|inherit)/.match(cell.attr['style'].to_s)
return false if md && (md[1] == 'justify' || md[1] == 'inherit')
md.nil? ? :default : md[1]
end
alignment = cur_alignment if alignment.nil?
return false if alignment != cur_alignment
else
t.children.each {|cc| check_alignment.call(cc) }
end
end
check_alignment.call(el)
check_rows = lambda do |t, type|
t.children.all? do |r|
(r.value == 'tr' || r.type == :text) && r.children.all? {|c| c.value == type || c.type == :text }
end
end
check_rows.call(el, 'td') ||
(el.children.all? do |t|
t.type == :text || (t.value == 'thead' && check_rows.call(t, 'th')) ||
((t.value == 'tfoot' || t.value == 'tbody') && check_rows.call(t, 'td'))
end && el.children.any? {|t| t.value == 'tbody' })
end
def convert_script(el)
if is_math_tag?(el)
handle_math_tag(el)
else
process_html_element(el)
end
end
def is_math_tag?(el)
el.attr['type'].to_s =~ /\bmath\/tex\b/
end
def handle_math_tag(el)
set_basics(el, :math, category: (el.attr['type'].include?("mode=display") ? :block : :span))
el.value = el.children.shift.value.sub(/\A(?:%\s*)?\z/m, '\1')
el.attr.delete('type')
end
end
include Parser
# Parse the source string provided on initialization as HTML document.
def parse
@stack, @tree = [], @root
@src = Kramdown::Utils::StringScanner.new(adapt_source(source))
while true
if (result = @src.scan(/\s*#{HTML_INSTRUCTION_RE}/o))
@tree.children << Element.new(:xml_pi, result.strip, nil, category: :block)
elsif (result = @src.scan(/\s*#{HTML_DOCTYPE_RE}/o))
# ignore the doctype
elsif (result = @src.scan(/\s*#{HTML_COMMENT_RE}/o))
@tree.children << Element.new(:xml_comment, result.strip, nil, category: :block)
else
break
end
end
tag_handler = lambda do |c, closed, handle_body|
parse_raw_html(c, &tag_handler) if !closed && handle_body
end
parse_raw_html(@tree, &tag_handler)
ElementConverter.convert(@tree)
end
end
end
end
kramdown-2.5.1/lib/kramdown/error.rb 0000644 0000041 0000041 00000000641 14726466654 017464 0 ustar www-data www-data # -*- coding: utf-8; frozen_string_literal: true -*-
#
#--
# Copyright (C) 2009-2019 Thomas Leitner
#
# This file is part of kramdown which is licensed under the MIT.
#++
#
module Kramdown
# This error is raised when an error condition is encountered.
#
# *Note* that this error is only raised by the support framework for the parsers and converters.
class Error < RuntimeError; end
end
kramdown-2.5.1/lib/kramdown/utils/ 0000755 0000041 0000041 00000000000 14726466654 017145 5 ustar www-data www-data kramdown-2.5.1/lib/kramdown/utils/entities.rb 0000644 0000041 0000041 00000063223 14726466654 021324 0 ustar www-data www-data # -*- coding: utf-8; frozen_string_literal: true -*-
#
#--
# Copyright (C) 2009-2019 Thomas Leitner
#
# This file is part of kramdown which is licensed under the MIT.
#++
#
module Kramdown
module Utils
# Provides convenience methods for handling named and numeric entities.
module Entities
# Represents an entity that has a +code_point+ and +name+.
Entity = Struct.new(:code_point, :name) do
# Return the UTF8 representation of the entity.
def char
[code_point].pack('U*') rescue nil
end
end
# Array of arrays. Each sub-array specifies a code point and the associated name.
#
# This table is not used directly -- Entity objects are automatically created from it and put
# into a Hash map when this file is loaded.
ENTITY_TABLE = [
# html4
[913, 'Alpha'],
[914, 'Beta'],
[915, 'Gamma'],
[916, 'Delta'],
[917, 'Epsilon'],
[918, 'Zeta'],
[919, 'Eta'],
[920, 'Theta'],
[921, 'Iota'],
[922, 'Kappa'],
[923, 'Lambda'],
[924, 'Mu'],
[925, 'Nu'],
[926, 'Xi'],
[927, 'Omicron'],
[928, 'Pi'],
[929, 'Rho'],
[931, 'Sigma'],
[932, 'Tau'],
[933, 'Upsilon'],
[934, 'Phi'],
[935, 'Chi'],
[936, 'Psi'],
[937, 'Omega'],
[945, 'alpha'],
[946, 'beta'],
[947, 'gamma'],
[948, 'delta'],
[949, 'epsilon'],
[950, 'zeta'],
[951, 'eta'],
[952, 'theta'],
[953, 'iota'],
[954, 'kappa'],
[955, 'lambda'],
[956, 'mu'],
[957, 'nu'],
[958, 'xi'],
[959, 'omicron'],
[960, 'pi'],
[961, 'rho'],
[963, 'sigma'],
[964, 'tau'],
[965, 'upsilon'],
[966, 'phi'],
[967, 'chi'],
[968, 'psi'],
[969, 'omega'],
[962, 'sigmaf'],
[977, 'thetasym'],
[978, 'upsih'],
[982, 'piv'],
[988, 'Gammad'],
[989, 'gammad'],
[8204, 'zwnj'],
[8205, 'zwj'],
[8206, 'lrm'],
[8207, 'rlm'],
[8230, 'hellip'],
[8242, 'prime'],
[8243, 'Prime'],
[8254, 'oline'],
[8260, 'frasl'],
[8472, 'weierp'],
[8465, 'image'],
[8476, 'real'],
[8501, 'alefsym'],
[8226, 'bull'],
[8482, 'trade'],
[8592, 'larr'],
[8594, 'rarr'],
[8593, 'uarr'],
[8595, 'darr'],
[8596, 'harr'],
[8629, 'crarr'],
[8657, 'uArr'],
[8659, 'dArr'],
[8656, 'lArr'],
[8658, 'rArr'],
[8660, 'hArr'],
[8704, 'forall'],
[8706, 'part'],
[8707, 'exist'],
[8709, 'empty'],
[8711, 'nabla'],
[8712, 'isin'],
[8715, 'ni'],
[8713, 'notin'],
[8721, 'sum'],
[8719, 'prod'],
[8722, 'minus'],
[8727, 'lowast'],
[8730, 'radic'],
[8733, 'prop'],
[8734, 'infin'],
[8736, 'ang'],
[8743, 'and'],
[8744, 'or'],
[8745, 'cap'],
[8746, 'cup'],
[8747, 'int'],
[8756, 'there4'],
[8764, 'sim'],
[8776, 'asymp'],
[8773, 'cong'],
[8800, 'ne'],
[8801, 'equiv'],
[8804, 'le'],
[8805, 'ge'],
[8834, 'sub'],
[8835, 'sup'],
[8838, 'sube'],
[8839, 'supe'],
[8836, 'nsub'],
[8853, 'oplus'],
[8855, 'otimes'],
[8869, 'perp'],
[8901, 'sdot'],
[8942, 'vellip'],
[8968, 'rceil'],
[8969, 'lceil'],
[8970, 'lfloor'],
[8971, 'rfloor'],
[9001, 'rang'],
[9002, 'lang'],
[9674, 'loz'],
[9824, 'spades'],
[9827, 'clubs'],
[9829, 'hearts'],
[9830, 'diams'],
[38, 'amp'],
[34, 'quot'],
[39, 'apos'],
[169, 'copy'],
[60, 'lt'],
[62, 'gt'],
[338, 'OElig'],
[339, 'oelig'],
[352, 'Scaron'],
[353, 'scaron'],
[376, 'Yuml'],
[710, 'circ'],
[732, 'tilde'],
[8211, 'ndash'],
[8212, 'mdash'],
[8216, 'lsquo'],
[8217, 'rsquo'],
[8220, 'ldquo'],
[8221, 'rdquo'],
[8224, 'dagger'],
[8225, 'Dagger'],
[8240, 'permil'],
[8364, 'euro'],
[8249, 'lsaquo'],
[8250, 'rsaquo'],
[160, 'nbsp'],
[161, 'iexcl'],
[163, 'pound'],
[164, 'curren'],
[165, 'yen'],
[166, 'brvbar'],
[167, 'sect'],
[168, 'uml'],
[171, 'laquo'],
[187, 'raquo'],
[174, 'reg'],
[170, 'ordf'],
[172, 'not'],
[173, 'shy'],
[175, 'macr'],
[176, 'deg'],
[177, 'plusmn'],
[180, 'acute'],
[181, 'micro'],
[182, 'para'],
[183, 'middot'],
[184, 'cedil'],
[186, 'ordm'],
[162, 'cent'],
[185, 'sup1'],
[178, 'sup2'],
[179, 'sup3'],
[189, 'frac12'],
[188, 'frac14'],
[190, 'frac34'],
[8531, 'frac13'],
[8532, 'frac23'],
[8533, 'frac15'],
[8534, 'frac25'],
[8535, 'frac35'],
[8536, 'frac45'],
[8537, 'frac16'],
[8538, 'frac56'],
[8539, 'frac18'],
[8540, 'frac38'],
[8541, 'frac58'],
[8542, 'frac78'],
[191, 'iquest'],
[192, 'Agrave'],
[193, 'Aacute'],
[194, 'Acirc'],
[195, 'Atilde'],
[196, 'Auml'],
[197, 'Aring'],
[198, 'AElig'],
[199, 'Ccedil'],
[200, 'Egrave'],
[201, 'Eacute'],
[202, 'Ecirc'],
[203, 'Euml'],
[204, 'Igrave'],
[205, 'Iacute'],
[206, 'Icirc'],
[207, 'Iuml'],
[208, 'ETH'],
[209, 'Ntilde'],
[210, 'Ograve'],
[211, 'Oacute'],
[212, 'Ocirc'],
[213, 'Otilde'],
[214, 'Ouml'],
[215, 'times'],
[216, 'Oslash'],
[217, 'Ugrave'],
[218, 'Uacute'],
[219, 'Ucirc'],
[220, 'Uuml'],
[221, 'Yacute'],
[222, 'THORN'],
[223, 'szlig'],
[224, 'agrave'],
[225, 'aacute'],
[226, 'acirc'],
[227, 'atilde'],
[228, 'auml'],
[229, 'aring'],
[230, 'aelig'],
[231, 'ccedil'],
[232, 'egrave'],
[233, 'eacute'],
[234, 'ecirc'],
[235, 'euml'],
[236, 'igrave'],
[237, 'iacute'],
[238, 'icirc'],
[239, 'iuml'],
[240, 'eth'],
[241, 'ntilde'],
[242, 'ograve'],
[243, 'oacute'],
[244, 'ocirc'],
[245, 'otilde'],
[246, 'ouml'],
[247, 'divide'],
[248, 'oslash'],
[249, 'ugrave'],
[250, 'uacute'],
[251, 'ucirc'],
[252, 'uuml'],
[253, 'yacute'],
[254, 'thorn'],
[255, 'yuml'],
[8218, 'sbquo'],
[402, 'fnof'],
[8222, 'bdquo'],
[128, 8364],
[130, 8218],
[131, 402],
[132, 8222],
[133, 8230],
[134, 8224],
[135, 8225],
[136, 710],
[137, 8240],
[138, 352],
[139, 8249],
[140, 338],
[142, 381],
[145, 8216],
[146, 8217],
[147, 8220],
[148, 8221],
[149, 8226],
[150, 8211],
[151, 8212],
[152, 732],
[153, 8482],
[154, 353],
[155, 8250],
[156, 339],
[158, 382],
[159, 376],
[8194, 'ensp'],
[8195, 'emsp'],
[8201, 'thinsp'],
# html5
[10218, 'Lang'],
[10219, 'Rang'],
[10220, 'loang'],
[10221, 'roang'],
[10229, 'xlarr'],
[10229, 'longleftarrow'],
[10229, 'LongLeftArrow'],
[10230, 'xrarr'],
[10230, 'longrightarrow'],
[10230, 'LongRightArrow'],
[10231, 'xharr'],
[10231, 'longleftrightarrow'],
[10231, 'LongLeftRightArrow'],
[10232, 'xlArr'],
[10232, 'Longleftarrow'],
[10232, 'DoubleLongLeftArrow'],
[10233, 'xrArr'],
[10233, 'Longrightarrow'],
[10233, 'DoubleLongRightArrow'],
[10234, 'xhArr'],
[10234, 'Longleftrightarrow'],
[10234, 'DoubleLongLeftRightArrow'],
[10236, 'xmap'],
[10236, 'longmapsto'],
[10239, 'dzigrarr'],
[10498, 'nvlArr'],
[10499, 'nvrArr'],
[10500, 'nvHarr'],
[10501, 'Map'],
[10508, 'lbarr'],
[10509, 'rbarr'],
[10509, 'bkarow'],
[10510, 'lBarr'],
[10511, 'rBarr'],
[10511, 'dbkarow'],
[10512, 'RBarr'],
[10512, 'drbkarow'],
[10513, 'DDotrahd'],
[10514, 'UpArrowBar'],
[10515, 'DownArrowBar'],
[10518, 'Rarrtl'],
[10521, 'latail'],
[10522, 'ratail'],
[10523, 'lAtail'],
[10524, 'rAtail'],
[10525, 'larrfs'],
[10526, 'rarrfs'],
[10527, 'larrbfs'],
[10528, 'rarrbfs'],
[10531, 'nwarhk'],
[10532, 'nearhk'],
[10533, 'searhk'],
[10533, 'hksearow'],
[10534, 'swarhk'],
[10534, 'hkswarow'],
[10535, 'nwnear'],
[10536, 'nesear'],
[10536, 'toea'],
[10537, 'seswar'],
[10537, 'tosa'],
[10538, 'swnwar'],
[10547, 'rarrc'],
[10549, 'cudarrr'],
[10550, 'ldca'],
[10551, 'rdca'],
[10552, 'cudarrl'],
[10553, 'larrpl'],
[10556, 'curarrm'],
[10557, 'cularrp'],
[10565, 'rarrpl'],
[10568, 'harrcir'],
[10569, 'Uarrocir'],
[10570, 'lurdshar'],
[10571, 'ldrushar'],
[10574, 'LeftRightVector'],
[10575, 'RightUpDownVector'],
[10576, 'DownLeftRightVector'],
[10577, 'LeftUpDownVector'],
[10578, 'LeftVectorBar'],
[10579, 'RightVectorBar'],
[10580, 'RightUpVectorBar'],
[10581, 'RightDownVectorBar'],
[10582, 'DownLeftVectorBar'],
[10583, 'DownRightVectorBar'],
[10584, 'LeftUpVectorBar'],
[10585, 'LeftDownVectorBar'],
[10586, 'LeftTeeVector'],
[10587, 'RightTeeVector'],
[10588, 'RightUpTeeVector'],
[10589, 'RightDownTeeVector'],
[10590, 'DownLeftTeeVector'],
[10591, 'DownRightTeeVector'],
[10592, 'LeftUpTeeVector'],
[10593, 'LeftDownTeeVector'],
[10594, 'lHar'],
[10595, 'uHar'],
[10596, 'rHar'],
[10597, 'dHar'],
[10598, 'luruhar'],
[10599, 'ldrdhar'],
[10600, 'ruluhar'],
[10601, 'rdldhar'],
[10602, 'lharul'],
[10603, 'llhard'],
[10604, 'rharul'],
[10605, 'lrhard'],
[10606, 'udhar'],
[10606, 'UpEquilibrium'],
[10607, 'duhar'],
[10607, 'ReverseUpEquilibrium'],
[10608, 'RoundImplies'],
[10609, 'erarr'],
[10610, 'simrarr'],
[10611, 'larrsim'],
[10612, 'rarrsim'],
[10613, 'rarrap'],
[10614, 'ltlarr'],
[10616, 'gtrarr'],
[10617, 'subrarr'],
[10619, 'suplarr'],
[10620, 'lfisht'],
[10621, 'rfisht'],
[10622, 'ufisht'],
[10623, 'dfisht'],
[10629, 'lopar'],
[10630, 'ropar'],
[10635, 'lbrke'],
[10636, 'rbrke'],
[10637, 'lbrkslu'],
[10638, 'rbrksld'],
[10639, 'lbrksld'],
[10640, 'rbrkslu'],
[10641, 'langd'],
[10642, 'rangd'],
[10643, 'lparlt'],
[10644, 'rpargt'],
[10645, 'gtlPar'],
[10646, 'ltrPar'],
[10650, 'vzigzag'],
[10652, 'vangrt'],
[10653, 'angrtvbd'],
[10660, 'ange'],
[10661, 'range'],
[10662, 'dwangle'],
[10663, 'uwangle'],
[10664, 'angmsdaa'],
[10665, 'angmsdab'],
[10666, 'angmsdac'],
[10667, 'angmsdad'],
[10668, 'angmsdae'],
[10669, 'angmsdaf'],
[10670, 'angmsdag'],
[10671, 'angmsdah'],
[10672, 'bemptyv'],
[10673, 'demptyv'],
[10674, 'cemptyv'],
[10675, 'raemptyv'],
[10676, 'laemptyv'],
[10677, 'ohbar'],
[10678, 'omid'],
[10679, 'opar'],
[10681, 'operp'],
[10683, 'olcross'],
[10684, 'odsold'],
[10686, 'olcir'],
[10687, 'ofcir'],
[10688, 'olt'],
[10689, 'ogt'],
[10690, 'cirscir'],
[10691, 'cirE'],
[10692, 'solb'],
[10693, 'bsolb'],
[10697, 'boxbox'],
[10701, 'trisb'],
[10702, 'rtriltri'],
[10703, 'LeftTriangleBar'],
[10704, 'RightTriangleBar'],
[10716, 'iinfin'],
[10717, 'infintie'],
[10718, 'nvinfin'],
[10723, 'eparsl'],
[10724, 'smeparsl'],
[10725, 'eqvparsl'],
[10731, 'lozf'],
[10731, 'blacklozenge'],
[10740, 'RuleDelayed'],
[10742, 'dsol'],
[10752, 'xodot'],
[10752, 'bigodot'],
[10753, 'xoplus'],
[10753, 'bigoplus'],
[10754, 'xotime'],
[10754, 'bigotimes'],
[10756, 'xuplus'],
[10756, 'biguplus'],
[10758, 'xsqcup'],
[10758, 'bigsqcup'],
[10764, 'qint'],
[10764, 'iiiint'],
[10765, 'fpartint'],
[10768, 'cirfnint'],
[10769, 'awint'],
[10770, 'rppolint'],
[10771, 'scpolint'],
[10772, 'npolint'],
[10773, 'pointint'],
[10774, 'quatint'],
[10775, 'intlarhk'],
[10786, 'pluscir'],
[10787, 'plusacir'],
[10788, 'simplus'],
[10789, 'plusdu'],
[10790, 'plussim'],
[10791, 'plustwo'],
[10793, 'mcomma'],
[10794, 'minusdu'],
[10797, 'loplus'],
[10798, 'roplus'],
[10799, 'Cross'],
[10800, 'timesd'],
[10801, 'timesbar'],
[10803, 'smashp'],
[10804, 'lotimes'],
[10805, 'rotimes'],
[10806, 'otimesas'],
[10807, 'Otimes'],
[10808, 'odiv'],
[10809, 'triplus'],
[10810, 'triminus'],
[10811, 'tritime'],
[10812, 'iprod'],
[10812, 'intprod'],
[10815, 'amalg'],
[10816, 'capdot'],
[10818, 'ncup'],
[10819, 'ncap'],
[10820, 'capand'],
[10821, 'cupor'],
[10822, 'cupcap'],
[10823, 'capcup'],
[10824, 'cupbrcap'],
[10825, 'capbrcup'],
[10826, 'cupcup'],
[10827, 'capcap'],
[10828, 'ccups'],
[10829, 'ccaps'],
[10832, 'ccupssm'],
[10835, 'And'],
[10836, 'Or'],
[10837, 'andand'],
[10838, 'oror'],
[10839, 'orslope'],
[10840, 'andslope'],
[10842, 'andv'],
[10843, 'orv'],
[10844, 'andd'],
[10845, 'ord'],
[10847, 'wedbar'],
[10854, 'sdote'],
[10858, 'simdot'],
[10861, 'congdot'],
[10862, 'easter'],
[10863, 'apacir'],
[10864, 'apE'],
[10865, 'eplus'],
[10866, 'pluse'],
[10867, 'Esim'],
[10868, 'Colone'],
[10869, 'Equal'],
[10871, 'eDDot'],
[10871, 'ddotseq'],
[10872, 'equivDD'],
[10873, 'ltcir'],
[10874, 'gtcir'],
[10875, 'ltquest'],
[10876, 'gtquest'],
[10877, 'les'],
[10877, 'LessSlantEqual'],
[10877, 'leqslant'],
[10878, 'ges'],
[10878, 'GreaterSlantEqual'],
[10878, 'geqslant'],
[10879, 'lesdot'],
[10880, 'gesdot'],
[10881, 'lesdoto'],
[10882, 'gesdoto'],
[10883, 'lesdotor'],
[10884, 'gesdotol'],
[10885, 'lap'],
[10885, 'lessapprox'],
[10886, 'gap'],
[10886, 'gtrapprox'],
[10887, 'lne'],
[10887, 'lneq'],
[10888, 'gne'],
[10888, 'gneq'],
[10889, 'lnap'],
[10889, 'lnapprox'],
[10890, 'gnap'],
[10890, 'gnapprox'],
[10891, 'lEg'],
[10891, 'lesseqqgtr'],
[10892, 'gEl'],
[10892, 'gtreqqless'],
[10893, 'lsime'],
[10894, 'gsime'],
[10895, 'lsimg'],
[10896, 'gsiml'],
[10897, 'lgE'],
[10898, 'glE'],
[10899, 'lesges'],
[10900, 'gesles'],
[10901, 'els'],
[10901, 'eqslantless'],
[10902, 'egs'],
[10902, 'eqslantgtr'],
[10903, 'elsdot'],
[10904, 'egsdot'],
[10905, 'el'],
[10906, 'eg'],
[10909, 'siml'],
[10910, 'simg'],
[10911, 'simlE'],
[10912, 'simgE'],
[10913, 'LessLess'],
[10914, 'GreaterGreater'],
[10916, 'glj'],
[10917, 'gla'],
[10918, 'ltcc'],
[10919, 'gtcc'],
[10920, 'lescc'],
[10921, 'gescc'],
[10922, 'smt'],
[10923, 'lat'],
[10924, 'smte'],
[10925, 'late'],
[10926, 'bumpE'],
[10927, 'pre'],
[10927, 'preceq'],
[10927, 'PrecedesEqual'],
[10928, 'sce'],
[10928, 'succeq'],
[10928, 'SucceedsEqual'],
[10931, 'prE'],
[10932, 'scE'],
[10933, 'prnE'],
[10933, 'precneqq'],
[10934, 'scnE'],
[10934, 'succneqq'],
[10935, 'prap'],
[10935, 'precapprox'],
[10936, 'scap'],
[10936, 'succapprox'],
[10937, 'prnap'],
[10937, 'precnapprox'],
[10938, 'scnap'],
[10938, 'succnapprox'],
[10939, 'Pr'],
[10940, 'Sc'],
[10941, 'subdot'],
[10942, 'supdot'],
[10943, 'subplus'],
[10944, 'supplus'],
[10945, 'submult'],
[10946, 'supmult'],
[10947, 'subedot'],
[10948, 'supedot'],
[10949, 'subE'],
[10949, 'subseteqq'],
[10950, 'supE'],
[10950, 'supseteqq'],
[10951, 'subsim'],
[10952, 'supsim'],
[10955, 'subnE'],
[10955, 'subsetneqq'],
[10956, 'supnE'],
[10956, 'supsetneqq'],
[10959, 'csub'],
[10960, 'csup'],
[10961, 'csube'],
[10962, 'csupe'],
[10963, 'subsup'],
[10964, 'supsub'],
[10965, 'subsub'],
[10966, 'supsup'],
[10967, 'suphsub'],
[10968, 'supdsub'],
[10969, 'forkv'],
[10970, 'topfork'],
[10971, 'mlcp'],
[10980, 'Dashv'],
[10980, 'DoubleLeftTee'],
[10982, 'Vdashl'],
[10983, 'Barv'],
[10984, 'vBar'],
[10985, 'vBarv'],
[10987, 'Vbar'],
[10988, 'Not'],
[10989, 'bNot'],
[10990, 'rnmid'],
[10991, 'cirmid'],
[10992, 'midcir'],
[10993, 'topcir'],
[10994, 'nhpar'],
[10995, 'parsim'],
[11005, 'parsl'],
[64256, 'fflig'],
[64257, 'filig'],
[64258, 'fllig'],
[64259, 'ffilig'],
[64260, 'ffllig'],
[119964, 'Ascr'],
[119966, 'Cscr'],
[119967, 'Dscr'],
[119970, 'Gscr'],
[119973, 'Jscr'],
[119974, 'Kscr'],
[119977, 'Nscr'],
[119978, 'Oscr'],
[119979, 'Pscr'],
[119980, 'Qscr'],
[119982, 'Sscr'],
[119983, 'Tscr'],
[119984, 'Uscr'],
[119985, 'Vscr'],
[119986, 'Wscr'],
[119987, 'Xscr'],
[119988, 'Yscr'],
[119989, 'Zscr'],
[119990, 'ascr'],
[119991, 'bscr'],
[119992, 'cscr'],
[119993, 'dscr'],
[119995, 'fscr'],
[119997, 'hscr'],
[119998, 'iscr'],
[119999, 'jscr'],
[120000, 'kscr'],
[120001, 'lscr'],
[120002, 'mscr'],
[120003, 'nscr'],
[120005, 'pscr'],
[120006, 'qscr'],
[120007, 'rscr'],
[120008, 'sscr'],
[120009, 'tscr'],
[120010, 'uscr'],
[120011, 'vscr'],
[120012, 'wscr'],
[120013, 'xscr'],
[120014, 'yscr'],
[120015, 'zscr'],
[120068, 'Afr'],
[120069, 'Bfr'],
[120071, 'Dfr'],
[120072, 'Efr'],
[120073, 'Ffr'],
[120074, 'Gfr'],
[120077, 'Jfr'],
[120078, 'Kfr'],
[120079, 'Lfr'],
[120080, 'Mfr'],
[120081, 'Nfr'],
[120082, 'Ofr'],
[120083, 'Pfr'],
[120084, 'Qfr'],
[120086, 'Sfr'],
[120087, 'Tfr'],
[120088, 'Ufr'],
[120089, 'Vfr'],
[120090, 'Wfr'],
[120091, 'Xfr'],
[120092, 'Yfr'],
[120094, 'afr'],
[120095, 'bfr'],
[120096, 'cfr'],
[120097, 'dfr'],
[120098, 'efr'],
[120099, 'ffr'],
[120100, 'gfr'],
[120101, 'hfr'],
[120102, 'ifr'],
[120103, 'jfr'],
[120104, 'kfr'],
[120105, 'lfr'],
[120106, 'mfr'],
[120107, 'nfr'],
[120108, 'ofr'],
[120109, 'pfr'],
[120110, 'qfr'],
[120111, 'rfr'],
[120112, 'sfr'],
[120113, 'tfr'],
[120114, 'ufr'],
[120115, 'vfr'],
[120116, 'wfr'],
[120117, 'xfr'],
[120118, 'yfr'],
[120119, 'zfr'],
[120120, 'Aopf'],
[120121, 'Bopf'],
[120123, 'Dopf'],
[120124, 'Eopf'],
[120125, 'Fopf'],
[120126, 'Gopf'],
[120128, 'Iopf'],
[120129, 'Jopf'],
[120130, 'Kopf'],
[120131, 'Lopf'],
[120132, 'Mopf'],
[120134, 'Oopf'],
[120138, 'Sopf'],
[120139, 'Topf'],
[120140, 'Uopf'],
[120141, 'Vopf'],
[120142, 'Wopf'],
[120143, 'Xopf'],
[120144, 'Yopf'],
[120146, 'aopf'],
[120147, 'bopf'],
[120148, 'copf'],
[120149, 'dopf'],
[120150, 'eopf'],
[120151, 'fopf'],
[120152, 'gopf'],
[120153, 'hopf'],
[120154, 'iopf'],
[120155, 'jopf'],
[120156, 'kopf'],
[120157, 'lopf'],
[120158, 'mopf'],
[120159, 'nopf'],
[120160, 'oopf'],
[120161, 'popf'],
[120162, 'qopf'],
[120163, 'ropf'],
[120164, 'sopf'],
[120165, 'topf'],
[120166, 'uopf'],
[120167, 'vopf'],
[120168, 'wopf'],
[120169, 'xopf'],
[120170, 'yopf'],
[120171, 'zopf'],
[60, 'nvlt'],
[61, 'bne'],
[62, 'nvgt'],
[102, 'fjlig'],
[8287, 'ThickSpace'],
[8605, 'nrarrw'],
[8706, 'npart'],
[8736, 'nang'],
[8745, 'caps'],
[8746, 'cups'],
[8764, 'nvsim'],
[8765, 'race'],
[8766, 'acE'],
[8770, 'nesim'],
[8770, 'NotEqualTilde'],
[8779, 'napid'],
[8781, 'nvap'],
[8782, 'nbump'],
[8782, 'NotHumpDownHump'],
[8783, 'nbumpe'],
[8783, 'NotHumpEqual'],
[8784, 'nedot'],
[8801, 'bnequiv'],
[8804, 'nvle'],
[8805, 'nvge'],
[8806, 'nlE'],
[8806, 'nleqq'],
[8807, 'ngE'],
[8807, 'ngeqq'],
[8807, 'NotGreaterFullEqual'],
[8808, 'lvertneqq'],
[8808, 'lvnE'],
[8809, 'gvertneqq'],
[8809, 'gvnE'],
[8810, 'nLtv'],
[8810, 'NotLessLess'],
[8810, 'nLt'],
[8811, 'nGtv'],
[8811, 'NotGreaterGreater'],
[8811, 'nGt'],
[8831, 'NotSucceedsTilde'],
[8834, 'NotSubset'],
[8834, 'nsubset'],
[8834, 'vnsub'],
[8835, 'NotSuperset'],
[8835, 'nsupset'],
[8835, 'vnsup'],
[8842, 'varsubsetneq'],
[8842, 'vsubne'],
[8843, 'varsupsetneq'],
[8843, 'vsupne'],
[8847, 'NotSquareSubset'],
[8848, 'NotSquareSuperset'],
[8851, 'sqcaps'],
[8852, 'sqcups'],
[8884, 'nvltrie'],
[8885, 'nvrtrie'],
[8920, 'nLl'],
[8921, 'nGg'],
[8922, 'lesg'],
[8923, 'gesl'],
[8949, 'notindot'],
[8953, 'notinE'],
[10547, 'nrarrc'],
[10703, 'NotLeftTriangleBar'],
[10704, 'NotRightTriangleBar'],
[10861, 'ncongdot'],
[10864, 'napE'],
[10877, 'nleqslant'],
[10877, 'nles'],
[10877, 'NotLessSlantEqual'],
[10878, 'ngeqslant'],
[10878, 'nges'],
[10878, 'NotGreaterSlantEqual'],
[10913, 'NotNestedLessLess'],
[10914, 'NotNestedGreaterGreater'],
[10924, 'smtes'],
[10925, 'lates'],
[10927, 'NotPrecedesEqual'],
[10927, 'npre'],
[10927, 'npreceq'],
[10928, 'NotSucceedsEqual'],
[10928, 'nsce'],
[10928, 'nsucceq'],
[10949, 'nsubE'],
[10949, 'nsubseteqq'],
[10950, 'nsupE'],
[10950, 'nsupseteqq'],
[10955, 'varsubsetneqq'],
[10955, 'vsubnE'],
[10956, 'varsupsetneqq'],
[10956, 'vsupnE'],
[11005, 'nparsl'],
]
# Contains the mapping of code point (or name) to the actual Entity object.
ENTITY_MAP = Hash.new do |h, k|
if k.kind_of?(Integer)
h[k] = Entity.new(k, nil)
else
raise Kramdown::Error, "Can't handle generic non-integer character reference '#{k}'"
end
end
ENTITY_TABLE.each do |code_point, data|
ENTITY_MAP[code_point] = if data.kind_of?(String)
ENTITY_MAP[data] = Entity.new(code_point, data)
else
ENTITY_MAP[data]
end
end
# Return the entity for the given code point or name +point_or_name+.
def entity(point_or_name)
ENTITY_MAP[point_or_name]
end
module_function :entity
end
end
end
kramdown-2.5.1/lib/kramdown/utils/lru_cache.rb 0000644 0000041 0000041 00000002100 14726466654 021410 0 ustar www-data www-data # -*- coding: utf-8; frozen_string_literal: true -*-
#
#--
# Copyright (C) 2009-2019 Thomas Leitner
#
# This file is part of kramdown which is licensed under the MIT.
#++
#
module Kramdown
module Utils
# A simple least recently used (LRU) cache.
#
# The cache relies on the fact that Ruby's Hash class maintains insertion order. So deleting
# and re-inserting a key-value pair on access moves the key to the last position. When an
# entry is added and the cache is full, the first entry is removed.
class LRUCache
# Creates a new LRUCache that can hold +size+ entries.
def initialize(size)
@size = size
@cache = {}
end
# Returns the stored value for +key+ or +nil+ if no value was stored under the key.
def [](key)
(val = @cache.delete(key)).nil? ? nil : @cache[key] = val
end
# Stores the +value+ under the +key+.
def []=(key, value)
@cache.delete(key)
@cache[key] = value
@cache.shift if @cache.length > @size
end
end
end
end
kramdown-2.5.1/lib/kramdown/utils/string_scanner.rb 0000644 0000041 0000041 00000005172 14726466654 022516 0 ustar www-data www-data # -*- coding: utf-8; frozen_string_literal: true -*-
#
#--
# Copyright (C) 2009-2019 Thomas Leitner
#
# This file is part of kramdown which is licensed under the MIT.
#++
#
require 'strscan'
module Kramdown
module Utils
# This patched StringScanner adds line number information for current scan position and a
# start_line_number override for nested StringScanners.
class StringScanner < ::StringScanner
# The start line number. Used for nested StringScanners that scan a sub-string of the source
# document. The kramdown parser uses this, e.g., for span level parsers.
attr_reader :start_line_number
# Takes the start line number as optional second argument.
#
# Note: The original second argument is no longer used so this should be safe.
def initialize(string, start_line_number = 1)
super(string)
@start_line_number = start_line_number || 1
@previous_pos = 0
@previous_line_number = @start_line_number
end
# Sets the byte position of the scan pointer.
#
# Note: This also resets some internal variables, so always use pos= when setting the position
# and don't use any other method for that!
def pos=(pos)
if self.pos > pos
@previous_line_number = @start_line_number
@previous_pos = 0
end
super
end
# Return information needed to revert the byte position of the string scanner in a performant
# way.
#
# The returned data can be fed to #revert_pos to revert the position to the saved one.
#
# Note: Just saving #pos won't be enough.
def save_pos
[pos, @previous_pos, @previous_line_number]
end
# Revert the position to one saved by #save_pos.
def revert_pos(data)
self.pos = data[0]
@previous_pos, @previous_line_number = data[1], data[2]
end
# Returns the line number for current charpos.
#
# NOTE: Requires that all line endings are normalized to '\n'
#
# NOTE: Normally we'd have to add one to the count of newlines to get the correct line number.
# However we add the one indirectly by using a one-based start_line_number.
def current_line_number
# Not using string[@previous_pos..best_pos].count('\n') because it is slower
strscan = ::StringScanner.new(string)
strscan.pos = @previous_pos
old_pos = pos + 1
@previous_line_number += 1 while strscan.skip_until(/\n/) && strscan.pos <= old_pos
@previous_pos = (eos? ? pos : pos + 1)
@previous_line_number
end
end
end
end
kramdown-2.5.1/lib/kramdown/utils/unidecoder.rb 0000644 0000041 0000041 00000002261 14726466654 021614 0 ustar www-data www-data # -*- coding: utf-8; frozen_string_literal: true -*-
#
#--
# Copyright (C) 2009-2019 Thomas Leitner
#
# This file is part of kramdown which is licensed under the MIT.
#++
#
# This file is based on code originally from the Stringex library and needs the data files from
# Stringex to work correctly.
module Kramdown
module Utils
# Provides the ability to tranliterate Unicode strings into plain ASCII ones.
module Unidecoder
gem 'stringex'
path = $:.find do |dir|
File.directory?(File.join(File.expand_path(dir), "stringex", "unidecoder_data"))
end
if path
CODEPOINTS = Hash.new do |h, k|
h[k] = YAML.load_file(File.join(path, "stringex", "unidecoder_data", "#{k}.yml"))
end
# Transliterate string from Unicode into ASCII.
def self.decode(string)
string.gsub(/[^\x00-\x7f]/u) do |codepoint|
unpacked = codepoint.unpack1("U")
CODEPOINTS[sprintf("x%02x", unpacked >> 8)][unpacked & 255]
rescue StandardError
"?"
end
end
else
def self.decode(string)
string
end
end
end
end
end
kramdown-2.5.1/lib/kramdown/utils/configurable.rb 0000644 0000041 0000041 00000002625 14726466654 022137 0 ustar www-data www-data # -*- coding: utf-8; frozen_string_literal: true -*-
#
#--
# Copyright (C) 2009-2019 Thomas Leitner
#
# This file is part of kramdown which is licensed under the MIT.
#++
#
module Kramdown
module Utils
# Methods for registering configurable extensions.
module Configurable
# Create a new configurable extension called +name+.
#
# Three methods will be defined on the calling object which allow to use this configurable
# extension:
#
# configurables:: Returns a hash of hashes that is used to store all configurables of the
# object.
#
# (ext_name):: Return the configured extension +ext_name+.
#
# add_(ext_name, data=nil, &block):: Define an extension +ext_name+ by specifying either
# the data as argument or by using a block.
def configurable(name)
unless respond_to?(:configurables)
singleton_class.send(:define_method, :configurables) do
@_configurables ||= Hash.new {|h, k| h[k] = {} }
end
end
singleton_class.send(:define_method, name) do |data|
configurables[name][data]
end
singleton_class.send(:define_method, "add_#{name}".intern) do |data, *args, &block|
configurables[name][data] = args.first || block
end
end
end
end
end
kramdown-2.5.1/lib/kramdown/utils/html.rb 0000644 0000041 0000041 00000005573 14726466654 020450 0 ustar www-data www-data # -*- coding: utf-8; frozen_string_literal: true -*-
#
#--
# Copyright (C) 2009-2019 Thomas Leitner
#
# This file is part of kramdown which is licensed under the MIT.
#++
#
require 'rexml/parsers/baseparser'
module Kramdown
module Utils
# Provides convenience methods for HTML related tasks.
#
# *Note* that this module has to be mixed into a class that has a @root (containing an element
# of type :root) and an @options (containing an options hash) instance variable so that some of
# the methods can work correctly.
module Html
# Convert the entity +e+ to a string. The optional parameter +original+ may contain the
# original representation of the entity.
#
# This method uses the option +entity_output+ to determine the output form for the entity.
def entity_to_str(e, original = nil)
entity_output = @options[:entity_output]
if entity_output == :as_char &&
(c = e.char.encode(@root.options[:encoding]) rescue nil) &&
((c = e.char) == '"' || !ESCAPE_MAP.key?(c))
c
elsif (entity_output == :as_input || entity_output == :as_char) && original
original
elsif (entity_output == :symbolic || ESCAPE_MAP.key?(e.char)) && !e.name.nil?
"{e.name};"
else # default to :numeric
"#{e.code_point};"
end
end
# Return the HTML representation of the attributes +attr+.
def html_attributes(attr)
return '' if attr.empty?
attr.map do |k, v|
v.nil? || (k == 'id' && v.strip.empty?) ? '' : " #{k}=\"#{escape_html(v.to_s, :attribute)}\""
end.join
end
# :stopdoc:
ESCAPE_MAP = {
'<' => '<',
'>' => '>',
'&' => '&',
'"' => '"',
}
ESCAPE_ALL_RE = /<|>|&/
ESCAPE_TEXT_RE = Regexp.union(REXML::Parsers::BaseParser::REFERENCE_RE, /<|>|&/)
ESCAPE_ATTRIBUTE_RE = Regexp.union(REXML::Parsers::BaseParser::REFERENCE_RE, /<|>|&|"/)
ESCAPE_RE_FROM_TYPE = {all: ESCAPE_ALL_RE, text: ESCAPE_TEXT_RE, attribute: ESCAPE_ATTRIBUTE_RE}
# :startdoc:
# Escape the special HTML characters in the string +str+. The parameter +type+ specifies what
# is escaped: :all - all special HTML characters except the quotation mark as well as
# entities, :text - all special HTML characters except the quotation mark but no entities and
# :attribute - all special HTML characters including the quotation mark but no entities.
def escape_html(str, type = :all)
str.gsub(ESCAPE_RE_FROM_TYPE[type]) {|m| ESCAPE_MAP[m] || m }
end
REDUNDANT_LINE_BREAK_REGEX = /([\p{Han}\p{Hiragana}\p{Katakana}]+)\n([\p{Han}\p{Hiragana}\p{Katakana}]+)/u
def fix_cjk_line_break(str)
while str.gsub!(REDUNDANT_LINE_BREAK_REGEX, '\1\2')
end
str
end
end
end
end
kramdown-2.5.1/lib/kramdown/document.rb 0000644 0000041 0000041 00000011656 14726466654 020161 0 ustar www-data www-data # -*- coding: utf-8; frozen_string_literal: true -*-
#
#--
# Copyright (C) 2009-2019 Thomas Leitner
#
# This file is part of kramdown which is licensed under the MIT.
#++
#
# = kramdown
#
# kramdown is fast, pure Ruby Markdown superset converter, using a strict syntax definition and
# supporting several common extensions.
#
# The kramdown library is mainly written to support the kramdown-to-HTML conversion chain. However,
# due to its flexibility it supports other input and output formats as well. Here is a list of the
# supported formats:
#
# * input formats: kramdown (a Markdown superset), Markdown, GFM, HTML
# * output formats: HTML, kramdown, LaTeX (and therefore PDF), PDF via Prawn
#
# All the documentation on the available input and output formats is available at
# http://kramdown.gettalong.org.
#
# == Usage
#
# kramdown has a simple API, so using kramdown is as easy as
#
# require 'kramdown'
#
# Kramdown::Document.new(text).to_html
#
# For detailed information have a look at the *\Kramdown::Document* class.
#
# == License
#
# MIT - see the COPYING file.
require 'kramdown/version'
require 'kramdown/element'
require 'kramdown/error'
require 'kramdown/parser'
require 'kramdown/converter'
require 'kramdown/options'
require 'kramdown/utils'
module Kramdown
# Return the data directory for kramdown.
def self.data_dir
unless defined?(@data_dir)
require 'rbconfig'
@data_dir = File.expand_path(File.join(File.dirname(__FILE__), '..', '..', 'data', 'kramdown'))
@data_dir = File.expand_path(File.join(RbConfig::CONFIG["datadir"], "kramdown")) unless File.exist?(@data_dir)
raise "kramdown data directory not found! This is a bug, please report it!" unless File.directory?(@data_dir)
end
@data_dir
end
# The main interface to kramdown.
#
# This class provides a one-stop-shop for using kramdown to convert text into various output
# formats. Use it like this:
#
# require 'kramdown'
# doc = Kramdown::Document.new('This *is* some kramdown text')
# puts doc.to_html
#
# The #to_html method is a shortcut for using the Converter::Html class. See #method_missing for
# more information.
#
# The second argument to the ::new method is an options hash for customizing the behaviour of the
# used parser and the converter. See ::new for more information!
class Document
# The root Element of the element tree. It is immediately available after the ::new method has
# been called.
attr_accessor :root
# The options hash which holds the options for parsing/converting the Kramdown document.
attr_reader :options
# An array of warning messages. It is filled with warnings during the parsing phase (i.e. in
# ::new) and the conversion phase.
attr_reader :warnings
# Create a new Kramdown document from the string +source+ and use the provided +options+. The
# options that can be used are defined in the Options module.
#
# The special options key :input can be used to select the parser that should parse the
# +source+. It has to be the name of a class in the Kramdown::Parser module. For example, to
# select the kramdown parser, one would set the :input key to +Kramdown+. If this key is not
# set, it defaults to +Kramdown+.
#
# The +source+ is immediately parsed by the selected parser so that the root element is
# immediately available and the output can be generated.
def initialize(source, options = {})
@options = Options.merge(options).freeze
parser = (@options[:input] || 'kramdown').to_s
parser = parser[0..0].upcase + parser[1..-1]
try_require('parser', parser)
if Parser.const_defined?(parser)
@root, @warnings = Parser.const_get(parser).parse(source, @options)
else
raise Kramdown::Error, "kramdown has no parser to handle the specified " \
"input format: #{@options[:input]}"
end
end
# Check if a method is invoked that begins with +to_+ and if so, try to instantiate a converter
# class (i.e. a class in the Kramdown::Converter module) and use it for converting the document.
#
# For example, +to_html+ would instantiate the Kramdown::Converter::Html class.
def method_missing(id, *attr, &block)
if id.to_s =~ /^to_(\w+)$/ && (name = Utils.camelize($1)) &&
try_require('converter', name) && Converter.const_defined?(name)
output, warnings = Converter.const_get(name).convert(@root, @options)
@warnings.concat(warnings)
output
else
super
end
end
def inspect # :nodoc:
""
end
# Try requiring a parser or converter class and don't raise an error if the file is not found.
def try_require(type, name)
require("kramdown/#{type}/#{Utils.snake_case(name)}")
true
rescue LoadError
true
end
protected :try_require
end
end
kramdown-2.5.1/lib/kramdown/converter/ 0000755 0000041 0000041 00000000000 14726466654 020014 5 ustar www-data www-data kramdown-2.5.1/lib/kramdown/converter/toc.rb 0000644 0000041 0000041 00000003524 14726466654 021132 0 ustar www-data www-data # -*- coding: utf-8; frozen_string_literal: true -*-
#
#--
# Copyright (C) 2009-2019 Thomas Leitner
#
# This file is part of kramdown which is licensed under the MIT.
#++
#
require 'kramdown/converter'
module Kramdown
module Converter
# Converts a Kramdown::Document to an element tree that represents the table of contents.
#
# The returned tree consists of Element objects of type :toc where the root element is just used
# as container object. Each :toc element contains as value the wrapped :header element and under
# the attribute key :id the header ID that should be used (note that this ID may not exist in
# the wrapped element).
#
# Since the TOC tree consists of special :toc elements, one cannot directly feed this tree to
# other converters!
class Toc < Base
def initialize(root, options)
super
@toc = Element.new(:toc)
@stack = []
@options[:template] = ''
end
def convert(el)
if el.type == :header && in_toc?(el)
attr = el.attr.dup
attr['id'] = generate_id(el.options[:raw_text]) if @options[:auto_ids] && !attr['id']
add_to_toc(el, attr['id']) if attr['id']
else
el.children.each {|child| convert(child) }
end
@toc
end
private
def add_to_toc(el, id)
toc_element = Element.new(:toc, el, id: id)
success = false
until success
if @stack.empty?
@toc.children << toc_element
@stack << toc_element
success = true
elsif @stack.last.value.options[:level] < el.options[:level]
@stack.last.children << toc_element
@stack << toc_element
success = true
else
@stack.pop
end
end
end
end
end
end
kramdown-2.5.1/lib/kramdown/converter/hash_ast.rb 0000644 0000041 0000041 00000001540 14726466654 022133 0 ustar www-data www-data # -*- coding: utf-8; frozen_string_literal: true -*-
#
#--
# Copyright (C) 2009-2019 Thomas Leitner
#
# This file is part of kramdown which is licensed under the MIT.
#++
#
require 'kramdown/parser'
require 'kramdown/converter'
require 'kramdown/utils'
module Kramdown
module Converter
# Converts a Kramdown::Document to a nested hash for further processing or debug output.
class HashAST < Base
def convert(el)
hash = {type: el.type}
hash[:attr] = el.attr unless el.attr.empty?
hash[:value] = el.value unless el.value.nil?
hash[:options] = el.options unless el.options.empty?
unless el.children.empty?
hash[:children] = []
el.children.each {|child| hash[:children] << convert(child) }
end
hash
end
end
HashAst = HashAST
end
end
kramdown-2.5.1/lib/kramdown/converter/math_engine/ 0000755 0000041 0000041 00000000000 14726466654 022272 5 ustar www-data www-data kramdown-2.5.1/lib/kramdown/converter/math_engine/mathjax.rb 0000644 0000041 0000041 00000001622 14726466654 024254 0 ustar www-data www-data # -*- coding: utf-8; frozen_string_literal: true -*-
#
#--
# Copyright (C) 2009-2019 Thomas Leitner
#
# This file is part of kramdown which is licensed under the MIT.
#++
#
module Kramdown::Converter::MathEngine
# Uses the MathJax javascript library for displaying math.
#
# Note that the javascript library itself is not include or linked, this has to be done
# separately. Only the math content is marked up correctly.
module Mathjax
def self.call(converter, el, opts)
value = converter.escape_html(el.value)
result = el.options[:category] == :block ? "\\[#{value}\\]\n" : "\\(#{value}\\)"
if el.attr.empty?
result
elsif el.options[:category] == :block
converter.format_as_block_html('div', el.attr, result, opts[:indent])
else
converter.format_as_span_html('span', el.attr, "$#{el.value}$")
end
end
end
end
kramdown-2.5.1/lib/kramdown/converter/kramdown.rb 0000644 0000041 0000041 00000036211 14726466654 022166 0 ustar www-data www-data # -*- coding: utf-8; frozen_string_literal: true -*-
#
#--
# Copyright (C) 2009-2019 Thomas Leitner
#
# This file is part of kramdown which is licensed under the MIT.
#++
#
require 'kramdown/converter'
require 'kramdown/utils'
module Kramdown
module Converter
# Converts an element tree to the kramdown format.
class Kramdown < Base
# :stopdoc:
include ::Kramdown::Utils::Html
def initialize(root, options)
super
@linkrefs = []
@footnotes = []
@abbrevs = []
@stack = []
@list_indent = @options[:list_indent]
@list_spacing = ' ' * (@list_indent - 2)
end
def convert(el, opts = {indent: 0})
res = send("convert_#{el.type}", el, opts)
res = res.dup if res.frozen?
if ![:html_element, :li, :dt, :dd, :td].include?(el.type) && (ial = ial_for_element(el))
res << ial
res << "\n\n" if el.block?
elsif [:ul, :dl, :ol, :codeblock].include?(el.type) && opts[:next] &&
([el.type, :codeblock].include?(opts[:next].type) ||
(opts[:next].type == :blank && opts[:nnext] &&
[el.type, :codeblock].include?(opts[:nnext].type)))
res << "^\n\n"
elsif el.block? &&
![:li, :dd, :dt, :td, :th, :tr, :thead, :tbody, :tfoot, :blank].include?(el.type) &&
(el.type != :html_element || @stack.last.type != :html_element) &&
(el.type != :p || !el.options[:transparent]) &&
!([:ul, :dl, :ol].include?(el.type) && @stack.last.type == :li)
res << "\n"
end
res
end
def inner(el, opts = {indent: 0})
@stack.push(el)
result = +''
el.children.each_with_index do |inner_el, index|
options = opts.dup
options[:index] = index
options[:prev] = (index == 0 ? nil : el.children[index - 1])
options[:pprev] = (index <= 1 ? nil : el.children[index - 2])
options[:next] = (index == el.children.length - 1 ? nil : el.children[index + 1])
options[:nnext] = (index >= el.children.length - 2 ? nil : el.children[index + 2])
result << convert(inner_el, options)
end
@stack.pop
result
end
def convert_blank(_el, _opts)
""
end
ESCAPED_CHAR_RE = /(\$\$|[\\*_`\[\]{"'|])|^ {0,3}(:)/
def convert_text(el, opts)
if opts[:raw_text] || (@stack.last.type == :html_element && @stack.last.options[:content_model] == :raw)
el.value
else
result = el.value.gsub(/\A\n/) do
opts[:prev] && opts[:prev].type == :br ? '' : "\n"
end
result.gsub!(/\s+/, ' ') unless el.options[:cdata]
result.gsub!(ESCAPED_CHAR_RE) do
$1 || !opts[:prev] || opts[:prev].type == :br ? "\\#{$1 || $2}" : $&
end
result
end
end
def convert_p(el, opts)
w = @options[:line_width] - opts[:indent].to_s.to_i
first, second, *rest = inner(el, opts).strip.gsub(/(.{1,#{w}})( +|$\n?)/, "\\1\n").split(/\n/)
first&.gsub!(/^(?:(#|>)|(\d+)\.(\s)|([+-]\s))/) { $1 || $4 ? "\\#{$1 || $4}" : "#{$2}\\.#{$3}" }
second&.gsub!(/^([=-]+\s*?)$/, "\\\1")
res = [first, second, *rest].compact.join("\n") + "\n"
res.gsub!(/^ {0,3}:/, "\\:")
if el.children.length == 1 && el.children.first.type == :math
res = "\\#{res}"
elsif res.start_with?('\$$') && res.end_with?("\\$$\n")
res.sub!(/^\\\$\$/, '\$\$')
end
res
end
def convert_codeblock(el, _opts)
el.value.split("\n").map {|l| l.empty? ? " " : " #{l}" }.join("\n") + "\n"
end
def convert_blockquote(el, opts)
opts[:indent] += 2
inner(el, opts).chomp.split("\n").map {|l| "> #{l}" }.join("\n") << "\n"
end
def convert_header(el, opts)
opts[:in_header] = true
res = +''
res << "#{'#' * output_header_level(el.options[:level])} #{inner(el, opts)}"
res[-1, 1] = "\\#" if res[-1] == '#'
res << " {##{el.attr['id']}}" if el.attr['id'] && !el.attr['id'].strip.empty?
res << "\n"
end
def convert_hr(_el, _opts)
"* * *\n"
end
def convert_ul(el, opts)
inner(el, opts).sub(/\n+\Z/, "\n")
end
alias convert_ol convert_ul
alias convert_dl convert_ul
def convert_li(el, opts)
sym, width = if @stack.last.type == :ul
['* ' + @list_spacing, el.children.first && el.children.first.type == :codeblock ? 4 : @list_indent]
else
["#{opts[:index] + 1}.".ljust(4), 4]
end
if (ial = ial_for_element(el))
sym << ial << " "
end
opts[:indent] += width
text = inner(el, opts)
newlines = text.scan(/\n*\Z/).first
first, *last = text.split("\n")
last = last.map {|l| " " * width + l }.join("\n")
text = (first.nil? ? "\n" : first + (last.empty? ? "" : "\n") + last + newlines)
if el.children.first && el.children.first.type == :p && !el.children.first.options[:transparent]
res = +"#{sym}#{text}"
res << "^\n" if el.children.size == 1 && @stack.last.children.last == el &&
(@stack.last.children.any? {|c| !c.children.first || c.children.first.type != :p } || @stack.last.children.size == 1)
res
elsif el.children.first && el.children.first.type == :codeblock
"#{sym}\n #{text}"
else
"#{sym}#{text}"
end
end
def convert_dd(el, opts)
sym, width = ": " + @list_spacing, (el.children.first && el.children.first.type == :codeblock ? 4 : @list_indent)
if (ial = ial_for_element(el))
sym << ial << " "
end
opts[:indent] += width
text = inner(el, opts)
newlines = text.scan(/\n*\Z/).first
first, *last = text.split("\n")
last = last.map {|l| " " * width + l }.join("\n")
text = first.to_s + (last.empty? ? "" : "\n") + last + newlines
text.chomp! if text =~ /\n\n\Z/ && opts[:next] && opts[:next].type == :dd
text << "\n" if text !~ /\n\n\Z/ && opts[:next] && opts[:next].type == :dt
text << "\n" if el.children.empty?
if el.children.first && el.children.first.type == :p && !el.children.first.options[:transparent]
"\n#{sym}#{text}"
elsif el.children.first && el.children.first.type == :codeblock
"#{sym}\n #{text}"
else
"#{sym}#{text}"
end
end
def convert_dt(el, opts)
result = +''
if (ial = ial_for_element(el))
result << ial << " "
end
result << inner(el, opts) << "\n"
end
HTML_TAGS_WITH_BODY = %w[div script iframe textarea th td]
HTML_ELEMENT_TYPES = [:entity, :text, :html_element].freeze
private_constant :HTML_ELEMENT_TYPES
def convert_html_element(el, opts)
markdown_attr = el.options[:category] == :block && el.children.any? do |c|
c.type != :html_element &&
(c.type != :p || !c.options[:transparent] ||
c.children.any? {|t| !HTML_ELEMENT_TYPES.member?(t.type) }) &&
c.block?
end
opts[:force_raw_text] = true if %w[script pre code].include?(el.value)
opts[:raw_text] = opts[:force_raw_text] || opts[:block_raw_text] || \
(el.options[:category] != :span && !markdown_attr)
opts[:block_raw_text] = true if el.options[:category] == :block && opts[:raw_text]
res = inner(el, opts)
if el.options[:category] == :span
"<#{el.value}#{html_attributes(el.attr)}" + \
(!res.empty? || HTML_TAGS_WITH_BODY.include?(el.value) ? ">#{res}#{el.value}>" : " />")
else
output = +''
attr = el.attr.dup
attr['markdown'] = '1' if markdown_attr
output << "<#{el.value}#{html_attributes(attr)}"
if !res.empty? && el.options[:content_model] != :block
output << ">#{res}#{el.value}>"
elsif !res.empty?
output << ">\n#{res}" << "#{el.value}>"
elsif HTML_TAGS_WITH_BODY.include?(el.value)
output << ">#{el.value}>"
else
output << " />"
end
output << "\n" if @stack.last.type != :html_element || @stack.last.options[:content_model] != :raw
output
end
end
def convert_xml_comment(el, _opts)
if el.options[:category] == :block &&
(@stack.last.type != :html_element || @stack.last.options[:content_model] != :raw)
el.value + "\n"
else
el.value.dup
end
end
alias convert_xml_pi convert_xml_comment
def convert_table(el, opts)
opts[:alignment] = el.options[:alignment]
inner(el, opts)
end
def convert_thead(el, opts)
rows = inner(el, opts)
if opts[:alignment].all?(:default)
"#{rows}|#{'-' * 10}\n"
else
"#{rows}| " + opts[:alignment].map do |a|
case a
when :left then ":-"
when :right then "-:"
when :center then ":-:"
when :default then "-"
end
end.join(' ') << "\n"
end
end
def convert_tbody(el, opts)
res = +''
res << inner(el, opts)
res << '|' << '-' * 10 << "\n" if opts[:next] && opts[:next].type == :tbody
res
end
def convert_tfoot(el, opts)
"|#{'=' * 10}\n#{inner(el, opts)}"
end
def convert_tr(el, opts)
"| #{el.children.map {|c| convert(c, opts) }.join(' | ')} |\n"
end
def convert_td(el, opts)
inner(el, opts)
end
def convert_comment(el, _opts)
if el.options[:category] == :block
"{::comment}\n#{el.value}\n{:/}\n"
else
"{::comment}#{el.value}{:/}"
end
end
def convert_br(_el, opts)
opts[:in_header] ? "
" : " \n"
end
def convert_a(el, opts)
if el.attr['href'].empty?
"[#{inner(el, opts)}]()"
elsif el.attr['href'] =~ /^(?:http|ftp)/ || el.attr['href'].count("()") > 0
index = if (link_el = @linkrefs.find {|c| c.attr['href'] == el.attr['href'] })
@linkrefs.index(link_el) + 1
else
@linkrefs << el
@linkrefs.size
end
"[#{inner(el, opts)}][#{index}]"
else
title = parse_title(el.attr['title'])
"[#{inner(el, opts)}](#{el.attr['href']}#{title})"
end
end
def convert_img(el, _opts)
alt_text = el.attr['alt'].to_s.gsub(ESCAPED_CHAR_RE) { $1 ? "\\#{$1}" : $2 }
src = el.attr['src'].to_s
if src.empty?
"![#{alt_text}]()"
else
title = parse_title(el.attr['title'])
link = if src.count("()") > 0
"<#{src}>"
else
src
end
""
end
end
def convert_codespan(el, _opts)
delim = (el.value.scan(/`+/).max || '') + '`'
"#{delim}#{' ' if delim.size > 1}#{el.value}#{' ' if delim.size > 1}#{delim}"
end
def convert_footnote(el, _opts)
@footnotes << [el.options[:name], el.value]
"[^#{el.options[:name]}]"
end
def convert_raw(el, _opts)
attr = (el.options[:type] || []).join(' ')
attr = " type=\"#{attr}\"" unless attr.empty?
if @stack.last.type == :html_element
el.value
elsif el.options[:category] == :block
"{::nomarkdown#{attr}}\n#{el.value}\n{:/}\n"
else
"{::nomarkdown#{attr}}#{el.value}{:/}"
end
end
def convert_em(el, opts)
"*#{inner(el, opts)}*" +
(opts[:next] && [:em, :strong].include?(opts[:next].type) && !ial_for_element(el) ? '{::}' : '')
end
def convert_strong(el, opts)
"**#{inner(el, opts)}**" +
(opts[:next] && [:em, :strong].include?(opts[:next].type) && !ial_for_element(el) ? '{::}' : '')
end
def convert_entity(el, _opts)
entity_to_str(el.value, el.options[:original])
end
TYPOGRAPHIC_SYMS = {
mdash: '---', ndash: '--', hellip: '...',
laquo_space: '<< ', raquo_space: ' >>',
laquo: '<<', raquo: '>>'
}
def convert_typographic_sym(el, _opts)
TYPOGRAPHIC_SYMS[el.value]
end
def convert_smart_quote(el, _opts)
el.value.to_s.match?(/[rl]dquo/) ? "\"" : "'"
end
def convert_math(el, _opts)
"$$#{el.value}$$" + (el.options[:category] == :block ? "\n" : '')
end
def convert_abbreviation(el, _opts)
el.value
end
def convert_root(el, opts)
res = inner(el, opts)
res << create_link_defs
res << create_footnote_defs
res << create_abbrev_defs
res
end
def create_link_defs
res = +''
res << "\n\n" unless @linkrefs.empty?
@linkrefs.each_with_index do |el, i|
title = parse_title(el.attr['title'])
res << "[#{i + 1}]: #{el.attr['href']}#{title}\n"
end
res
end
def create_footnote_defs
res = +''
@footnotes.each do |name, data|
res << "[^#{name}]:\n"
res << inner(data).chomp.split("\n").map {|l| " #{l}" }.join("\n") + "\n\n"
end
res
end
def create_abbrev_defs
return '' unless @root.options[:abbrev_defs]
res = +''
@root.options[:abbrev_defs].each do |name, text|
res << "*[#{name}]: #{text}\n"
res << ial_for_element(Element.new(:unused, nil, @root.options[:abbrev_attr][name])).to_s << "\n\n"
end
res
end
# Return the IAL containing the attributes of the element +el+.
def ial_for_element(el)
res = el.attr.map do |k, v|
next if [:img, :a].include?(el.type) && ['href', 'src', 'alt', 'title'].include?(k)
next if el.type == :header && k == 'id' && !v.strip.empty?
if v.nil?
''
elsif k == 'class' && !v.empty? && !v.index(/[.#]/)
" " + v.split(/\s+/).map {|w| ".#{w}" }.join(" ")
elsif k == 'id' && !v.strip.empty?
" ##{v}"
else
" #{k}=\"#{v}\""
end
end.compact.join
res = "toc" + (res.strip.empty? ? '' : " #{res}") if (el.type == :ul || el.type == :ol) &&
el.options.dig(:ial, :refs)&.include?('toc')
res = "footnotes" + (res.strip.empty? ? '' : " #{res}") if (el.type == :ul || el.type == :ol) &&
el.options.dig(:ial, :refs)&.include?('footnotes')
if el.type == :dl && el.options[:ial] && el.options[:ial][:refs]
auto_ids = el.options[:ial][:refs].select {|ref| ref.start_with?('auto_ids') }.join(" ")
res = auto_ids << (res.strip.empty? ? '' : " #{res}") unless auto_ids.empty?
end
res.strip.empty? ? nil : "{:#{res}}"
end
def parse_title(attr)
attr.to_s.empty? ? '' : ' "' + attr.gsub(/"/, '"') + '"'
end
# :startdoc:
end
end
end
kramdown-2.5.1/lib/kramdown/converter/base.rb 0000644 0000041 0000041 00000023166 14726466654 021263 0 ustar www-data www-data # -*- coding: utf-8; frozen_string_literal: true -*-
#
#--
# Copyright (C) 2009-2019 Thomas Leitner
#
# This file is part of kramdown which is licensed under the MIT.
#++
#
require 'erb'
require 'kramdown/utils'
require 'kramdown/document'
module Kramdown
module Converter
# == \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!
class Base
# Can be used by a converter for storing arbitrary information during the conversion process.
attr_reader :data
# The hash with the conversion options.
attr_reader :options
# The root element that is converted.
attr_reader :root
# The warnings array.
attr_reader :warnings
# Initialize the converter with the given +root+ element and +options+ hash.
def initialize(root, options)
@options = options
@root = root
@data = {}
@warnings = []
end
private_class_method(:new, :allocate)
# Returns whether the template should be applied before the conversion of the tree.
#
# Defaults to false.
def apply_template_before?
false
end
# Returns whether the template should be applied after the conversion of the tree.
#
# Defaults to true.
def apply_template_after?
true
end
# 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.
def self.convert(tree, options = {})
converter = new(tree, ::Kramdown::Options.merge(options.merge(tree.options[:options] || {})))
if !converter.options[:template].empty? && converter.apply_template_before?
apply_template(converter, '')
end
result = converter.convert(tree)
if result.respond_to?(:encode!) && result.encoding != Encoding::BINARY
result.encode!(tree.options[:encoding] ||
(raise ::Kramdown::Error, "Missing encoding option on root element"))
end
if !converter.options[:template].empty? && converter.apply_template_after?
result = apply_template(converter, result)
end
[result, converter.warnings]
end
# Convert the element +el+ and return the resulting object.
#
# This is the only method that has to be implemented by sub-classes!
def convert(_el)
raise NotImplementedError
end
# 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.
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
# Return the template specified by +template+.
def self.get_template(template) # :nodoc:
format_ext = '.' + ::Kramdown::Utils.snake_case(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.delete_prefix("string://")
else
raise "The specified template file #{template} does not exist"
end
end
# Add the given warning +text+ to the warning array.
def warning(text)
@warnings << text
end
# Return +true+ if the header element +el+ should be used for the table of contents (as
# specified by the +toc_levels+ option).
def in_toc?(el)
@options[:toc_levels].include?(el.options[:level]) && (el.attr['class'] || '') !~ /\bno_toc\b/
end
# Return the output header level given a level.
#
# Uses the +header_offset+ option for adjusting the header level.
def output_header_level(level)
[[level + @options[:header_offset], 6].min, 1].max
end
# Extract the code block/span language from the attributes.
def extract_code_language(attr)
if attr['class'] && attr['class'] =~ /\blanguage-\S+/
attr['class'].scan(/\blanguage-(\S+)/).first.first
end
end
# See #extract_code_language
#
# *Warning*: This version will modify the given attributes if a language is present.
def extract_code_language!(attr)
lang = extract_code_language(attr)
attr['class'] = attr['class'].sub(/\blanguage-\S+/, '').strip if lang
attr.delete('class') if lang && attr['class'].empty?
lang
end
# Highlight the given +text+ in the language +lang+ with the syntax highlighter configured
# through the option 'syntax_highlighter'.
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
# Format the given math element with the math engine configured through the option
# 'math_engine'.
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 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.
def generate_id(str)
str = ::Kramdown::Utils::Unidecoder.decode(str) if @options[:transliterated_header_ids]
gen_id = basic_generate_id(str)
gen_id = 'section' if gen_id.empty?
@used_ids ||= {}
if @used_ids.key?(gen_id)
gen_id += "-#{@used_ids[gen_id] += 1}"
else
@used_ids[gen_id] = 0
end
@options[:auto_id_prefix] + gen_id
end
# The basic version of the ID generator, without any special provisions for empty or unique
# IDs.
def basic_generate_id(str)
gen_id = str.gsub(/^[^a-zA-Z]+/, '')
gen_id.tr!('^a-zA-Z0-9 -', '')
gen_id.tr!(' ', '-')
gen_id.downcase!
gen_id
end
SMART_QUOTE_INDICES = {lsquo: 0, rsquo: 1, ldquo: 2, rdquo: 3} # :nodoc:
# Return the entity that represents the given smart_quote element.
def smart_quote_entity(el)
res = @options[:smart_quotes][SMART_QUOTE_INDICES[el.value]]
::Kramdown::Utils::Entities.entity(res)
end
end
end
end
kramdown-2.5.1/lib/kramdown/converter/syntax_highlighter.rb 0000644 0000041 0000041 00000004131 14726466654 024244 0 ustar www-data www-data # -*- coding: utf-8; frozen_string_literal: true -*-
#
#--
# Copyright (C) 2009-2019 Thomas Leitner
#
# This file is part of kramdown which is licensed under the MIT.
#++
#
module Kramdown
module Converter
# == Container for Syntax Highlighters
#
# This module serves as container for the syntax highlighters that can be used together with
# kramdown.
#
# A syntax highlighter should not store any data itself but should use the provided converter
# object to do so (See Kramdown::Converter::Base#data).
#
# == Implementing a Syntax Highlighter
#
# Implementing a new syntax highlighter is easy because it is just an object that needs to
# respond to #call.
#
# The method #call needs to take the following arguments:
#
# converter:: This argument contains the converter object that calls the syntax highlighter. It
# can be used, for example, to store data in Kramdown::Converter::Base#data for one
# conversion run.
#
# text:: The raw text that should be highlighted.
#
# lang:: The language that the text should be highlighted for (e.g. ruby, python, ...).
#
# type:: The type of text, either :span for span-level code or :block for a codeblock.
#
# opts:: A Hash with options that may be passed from the converter.
#
# The return value of the method should be the highlighted text, suitable for the given
# converter (e.g. HTML for the HTML converter).
#
# == Special Implementation Details
#
# HTML converter:: If the syntax highlighter is used with an HTML converter, it should return
# :block type text correctly wrapped (i.e. normally inside a pre-tag, but may
# also be a table-tag or just a div-tag) but :span type text *without* a
# code-tag!
#
# Also, a syntax highlighter should store the default highlighting language for
# the invocation in the +opts+ hash under the key :default_lang.
module SyntaxHighlighter
end
end
end
kramdown-2.5.1/lib/kramdown/converter/syntax_highlighter/ 0000755 0000041 0000041 00000000000 14726466654 023720 5 ustar www-data www-data kramdown-2.5.1/lib/kramdown/converter/syntax_highlighter/minted.rb 0000644 0000041 0000041 00000001616 14726466654 025531 0 ustar www-data www-data # -*- coding: utf-8; frozen_string_literal: true -*-
#
#--
# Copyright (C) 2009-2019 Thomas Leitner
#
# This file is part of kramdown which is licensed under the MIT.
#++
#
module Kramdown::Converter::SyntaxHighlighter
# Uses Minted to highlight code blocks and code spans.
module Minted
def self.call(converter, text, lang, type, _opts)
opts = converter.options[:syntax_highlighter_opts]
# Fallback to default language
lang ||= opts[:default_lang]
options = []
options << "breaklines" if opts[:wrap]
options << "linenos" if opts[:line_numbers]
options << "frame=#{opts[:frame]}" if opts[:frame]
if lang && type == :block
"\\begin{minted}[#{options.join(',')}]{#{lang}}\n#{text}\n\\end{minted}"
elsif lang && type == :span
"\\mintinline{#{lang}}{#{text}}"
else
nil
end
end
end
end
kramdown-2.5.1/lib/kramdown/converter/syntax_highlighter/rouge.rb 0000644 0000041 0000041 00000004554 14726466654 025376 0 ustar www-data www-data # -*- coding: utf-8; frozen_string_literal: true -*-
#
#--
# Copyright (C) 2009-2019 Thomas Leitner
#
# This file is part of kramdown which is licensed under the MIT.
#++
#
module Kramdown::Converter::SyntaxHighlighter
# Uses Rouge which is CSS-compatible to Pygments to highlight code blocks and code spans.
module Rouge
begin
require 'rouge'
# Highlighting via Rouge is available if this constant is +true+.
AVAILABLE = true
rescue LoadError, SyntaxError
AVAILABLE = false # :nodoc:
end
def self.call(converter, text, lang, type, call_opts)
opts = options(converter, type)
call_opts[:default_lang] = opts[:default_lang]
return nil unless lang || opts[:default_lang] || opts[:guess_lang]
lexer = ::Rouge::Lexer.find_fancy(lang || opts[:default_lang], text)
return nil if opts[:disable] || !lexer || (lexer.tag == "plaintext" && !opts[:guess_lang])
opts[:css_class] ||= 'highlight' # For backward compatibility when using Rouge 2.0
formatter = formatter_class(opts).new(opts)
formatter.format(lexer.lex(text))
end
def self.options(converter, type)
prepare_options(converter)
converter.data[:syntax_highlighter_rouge][type]
end
def self.prepare_options(converter)
return if converter.data.key?(:syntax_highlighter_rouge)
cache = converter.data[:syntax_highlighter_rouge] = {}
opts = converter.options[:syntax_highlighter_opts].dup
span_opts = opts.delete(:span)&.dup || {}
block_opts = opts.delete(:block)&.dup || {}
normalize_keys(span_opts)
normalize_keys(block_opts)
cache[:span] = opts.merge(span_opts)
cache[:span][:wrap] = false
cache[:block] = opts.merge(block_opts)
end
def self.normalize_keys(hash)
return if hash.empty?
hash.keys.each do |k|
hash[k.kind_of?(String) ? Kramdown::Options.str_to_sym(k) : k] = hash.delete(k)
end
end
def self.formatter_class(opts = {})
case formatter = opts[:formatter]
when Class
formatter
when /\A[[:upper:]][[:alnum:]_]*\z/
::Rouge::Formatters.const_get(formatter, false)
else
# Available in Rouge 2.0 or later
::Rouge::Formatters::HTMLLegacy
end
rescue NameError
# Fallback to Rouge 1.x
::Rouge::Formatters::HTML
end
end
end
kramdown-2.5.1/lib/kramdown/converter/latex.rb 0000644 0000041 0000041 00000046365 14726466654 021474 0 ustar www-data www-data # -*- coding: utf-8; frozen_string_literal: true -*-
#
#--
# Copyright (C) 2009-2019 Thomas Leitner
#
# This file is part of kramdown which is licensed under the MIT.
#++
#
require 'set'
require 'kramdown/converter'
module Kramdown
module Converter
# Converts an element tree to LaTeX.
#
# This converter uses ideas from other Markdown-to-LaTeX converters like Pandoc and Maruku.
#
# You can customize this converter by sub-classing it and overriding the +convert_NAME+ methods.
# Each such method takes the following parameters:
#
# [+el+] The element of type +NAME+ to be converted.
#
# [+opts+] A hash containing processing options that are passed down from parent elements. The
# key :parent is always set and contains the parent element as value.
#
# The return value of such a method has to be a string containing the element +el+ formatted
# correctly as LaTeX markup.
class Latex < Base
# Initialize the LaTeX converter with the +root+ element and the conversion +options+.
def initialize(root, options)
super
@data[:packages] = Set.new
end
# Dispatch the conversion of the element +el+ to a +convert_TYPE+ method using the +type+ of
# the element.
def convert(el, opts = {})
send("convert_#{el.type}", el, opts)
end
# Return the converted content of the children of +el+ as a string.
def inner(el, opts)
result = +''
options = opts.dup.merge(parent: el)
el.children.each_with_index do |inner_el, index|
options[:index] = index
options[:result] = result
result << send("convert_#{inner_el.type}", inner_el, options)
end
result
end
def convert_root(el, opts)
inner(el, opts)
end
def convert_blank(_el, opts)
opts[:result].match?(/\n\n\Z|\A\Z/) ? "" : "\n"
end
def convert_text(el, _opts)
escape(el.value)
end
def convert_p(el, opts)
if el.children.size == 1 && el.children.first.type == :img &&
!(img = convert_img(el.children.first, opts)).empty?
convert_standalone_image(el, opts, img)
else
"#{latex_link_target(el)}#{inner(el, opts)}\n\n"
end
end
# Helper method used by +convert_p+ to convert a paragraph that only contains a single :img
# element.
def convert_standalone_image(el, _opts, img)
attrs = attribute_list(el)
"\\begin{figure}#{attrs}\n\\begin{center}\n#{img}\n\\end{center}\n" \
"\\caption{#{escape(el.children.first.attr['alt'])}}\n" \
"#{latex_link_target(el, true)}\n\\end{figure}#{attrs}\n"
end
def convert_codeblock(el, _opts)
show_whitespace = el.attr['class'].to_s =~ /\bshow-whitespaces\b/
lang = extract_code_language(el.attr)
if @options[:syntax_highlighter] == :minted &&
(highlighted_code = highlight_code(el.value, lang, :block))
@data[:packages] << 'minted'
"#{latex_link_target(el)}#{highlighted_code}\n"
elsif show_whitespace || lang
options = []
options << (show_whitespace ? "showspaces=true,showtabs=true" : "showspaces=false,showtabs=false")
options << "language=#{lang}" if lang
options << "basicstyle=\\ttfamily\\footnotesize,columns=fixed,frame=tlbr"
id = el.attr['id']
options << "label=#{id}" if id
attrs = attribute_list(el)
"#{latex_link_target(el)}\\begin{lstlisting}[#{options.join(',')}]\n" \
"#{el.value}\n\\end{lstlisting}#{attrs}\n"
else
"#{latex_link_target(el)}\\begin{verbatim}#{el.value}\\end{verbatim}\n"
end
end
def convert_blockquote(el, opts)
latex_environment(el.children.size > 1 ? 'quotation' : 'quote', el, inner(el, opts))
end
def convert_header(el, opts)
type = @options[:latex_headers][output_header_level(el.options[:level]) - 1]
if ((id = el.attr['id']) ||
(@options[:auto_ids] && (id = generate_id(el.options[:raw_text])))) && in_toc?(el)
"\\#{type}{#{inner(el, opts)}}\\hypertarget{#{id}}{}\\label{#{id}}\n\n"
else
"\\#{type}*{#{inner(el, opts)}}\n\n"
end
end
def convert_hr(el, _opts)
attrs = attribute_list(el)
"#{latex_link_target(el)}\\begin{center}#{attrs}\n\\rule{3in}{0.4pt}\n\\end{center}#{attrs}\n"
end
def convert_ul(el, opts)
if !@data[:has_toc] && el.options.dig(:ial, :refs)&.include?('toc')
@data[:has_toc] = true
'\tableofcontents'
else
latex_environment(el.type == :ul ? 'itemize' : 'enumerate', el, inner(el, opts))
end
end
alias convert_ol convert_ul
def convert_dl(el, opts)
latex_environment('description', el, inner(el, opts))
end
def convert_li(el, opts)
"\\item{} #{latex_link_target(el, true)}#{inner(el, opts).sub(/\n+\Z/, '')}\n"
end
def convert_dt(el, opts)
"\\item[#{inner(el, opts)}] "
end
def convert_dd(el, opts)
"#{latex_link_target(el)}#{inner(el, opts)}\n\n"
end
def convert_html_element(el, opts)
case el.value
when 'i', 'em'
"\\emph{#{inner(el, opts)}}"
when 'b', 'strong'
"\\textbf{#{inner(el, opts)}}"
else
warning("Can't convert HTML element")
''
end
end
def convert_xml_comment(el, _opts)
el.value.split("\n").map {|l| "% #{l}" }.join("\n") + "\n"
end
def convert_xml_pi(_el, _opts)
warning("Can't convert XML PI")
''
end
TABLE_ALIGNMENT_CHAR = {default: 'l', left: 'l', center: 'c', right: 'r'} # :nodoc:
def convert_table(el, opts)
@data[:packages] << 'longtable'
align = el.options[:alignment].map {|a| TABLE_ALIGNMENT_CHAR[a] }.join('|')
attrs = attribute_list(el)
"#{latex_link_target(el)}\\begin{longtable}{|#{align}|}#{attrs}\n" \
"\\hline\n#{inner(el, opts)}\\hline\n\\end{longtable}#{attrs}\n\n"
end
def convert_thead(el, opts)
"#{inner(el, opts)}\\hline\n"
end
def convert_tbody(el, opts)
inner(el, opts)
end
def convert_tfoot(el, opts)
"\\hline \\hline \n#{inner(el, opts)}"
end
def convert_tr(el, opts)
el.children.map {|c| send("convert_#{c.type}", c, opts) }.join(' & ') << "\\\\\n"
end
def convert_td(el, opts)
inner(el, opts)
end
def convert_comment(el, _opts)
el.value.split("\n").map {|l| "% #{l}" }.join("\n") << "\n"
end
def convert_br(_el, opts)
res = +"\\newline"
res << "\n" if (c = opts[:parent].children[opts[:index] + 1]) &&
(c.type != :text || c.value !~ /^\s*\n/)
res
end
def convert_a(el, opts)
url = el.attr['href']
if url.start_with?('#')
"\\hyperlink{#{url[1..-1].gsub('%', '\\%')}}{#{inner(el, opts)}}"
else
"\\href{#{url.gsub('%', '\\%')}}{#{inner(el, opts)}}"
end
end
def convert_img(el, _opts)
line = el.options[:location]
if el.attr['src'].match?(/^(https?|ftps?):\/\//)
warning("Cannot include non-local image#{line ? " (line #{line})" : ''}")
''
elsif !el.attr['src'].empty?
@data[:packages] << 'graphicx'
"#{latex_link_target(el)}\\includegraphics{#{el.attr['src']}}"
else
warning("Cannot include image with empty path#{line ? " (line #{line})" : ''}")
''
end
end
def convert_codespan(el, _opts)
lang = extract_code_language(el.attr)
if @options[:syntax_highlighter] == :minted &&
(highlighted_code = highlight_code(el.value, lang, :span))
@data[:packages] << 'minted'
"#{latex_link_target(el)}#{highlighted_code}"
else
"\\texttt{#{latex_link_target(el)}#{escape(el.value)}}"
end
end
def convert_footnote(el, opts)
@data[:packages] << 'fancyvrb'
"\\footnote{#{inner(el.value, opts).rstrip}}"
end
def convert_raw(el, _opts)
if !el.options[:type] || el.options[:type].empty? || el.options[:type].include?('latex')
el.value + (el.options[:category] == :block ? "\n" : '')
else
''
end
end
def convert_em(el, opts)
"\\emph{#{latex_link_target(el)}#{inner(el, opts)}}"
end
def convert_strong(el, opts)
"\\textbf{#{latex_link_target(el)}#{inner(el, opts)}}"
end
# Inspired by Maruku: entity conversion table based on the one from htmltolatex
# (http://sourceforge.net/projects/htmltolatex/), with some small adjustments/additions
ENTITY_CONV_TABLE = {
913 => ['$A$'],
914 => ['$B$'],
915 => ['$\Gamma$'],
916 => ['$\Delta$'],
917 => ['$E$'],
918 => ['$Z$'],
919 => ['$H$'],
920 => ['$\Theta$'],
921 => ['$I$'],
922 => ['$K$'],
923 => ['$\Lambda$'],
924 => ['$M$'],
925 => ['$N$'],
926 => ['$\Xi$'],
927 => ['$O$'],
928 => ['$\Pi$'],
929 => ['$P$'],
931 => ['$\Sigma$'],
932 => ['$T$'],
933 => ['$Y$'],
934 => ['$\Phi$'],
935 => ['$X$'],
936 => ['$\Psi$'],
937 => ['$\Omega$'],
945 => ['$\alpha$'],
946 => ['$\beta$'],
947 => ['$\gamma$'],
948 => ['$\delta$'],
949 => ['$\epsilon$'],
950 => ['$\zeta$'],
951 => ['$\eta$'],
952 => ['$\theta$'],
953 => ['$\iota$'],
954 => ['$\kappa$'],
955 => ['$\lambda$'],
956 => ['$\mu$'],
957 => ['$\nu$'],
958 => ['$\xi$'],
959 => ['$o$'],
960 => ['$\pi$'],
961 => ['$\rho$'],
963 => ['$\sigma$'],
964 => ['$\tau$'],
965 => ['$\upsilon$'],
966 => ['$\phi$'],
967 => ['$\chi$'],
968 => ['$\psi$'],
969 => ['$\omega$'],
962 => ['$\varsigma$'],
977 => ['$\vartheta$'],
982 => ['$\varpi$'],
8230 => ['\ldots'],
8242 => ['$\prime$'],
8254 => ['-'],
8260 => ['/'],
8472 => ['$\wp$'],
8465 => ['$\Im$'],
8476 => ['$\Re$'],
8501 => ['$\aleph$'],
8226 => ['$\bullet$'],
8482 => ['$^{\rm TM}$'],
8592 => ['$\leftarrow$'],
8594 => ['$\rightarrow$'],
8593 => ['$\uparrow$'],
8595 => ['$\downarrow$'],
8596 => ['$\leftrightarrow$'],
8629 => ['$\hookleftarrow$'],
8657 => ['$\Uparrow$'],
8659 => ['$\Downarrow$'],
8656 => ['$\Leftarrow$'],
8658 => ['$\Rightarrow$'],
8660 => ['$\Leftrightarrow$'],
8704 => ['$\forall$'],
8706 => ['$\partial$'],
8707 => ['$\exists$'],
8709 => ['$\emptyset$'],
8711 => ['$\nabla$'],
8712 => ['$\in$'],
8715 => ['$\ni$'],
8713 => ['$\notin$'],
8721 => ['$\sum$'],
8719 => ['$\prod$'],
8722 => ['$-$'],
8727 => ['$\ast$'],
8730 => ['$\surd$'],
8733 => ['$\propto$'],
8734 => ['$\infty$'],
8736 => ['$\angle$'],
8743 => ['$\wedge$'],
8744 => ['$\vee$'],
8745 => ['$\cap$'],
8746 => ['$\cup$'],
8747 => ['$\int$'],
8756 => ['$\therefore$', 'amssymb'],
8764 => ['$\sim$'],
8776 => ['$\approx$'],
8773 => ['$\cong$'],
8800 => ['$\neq$'],
8801 => ['$\equiv$'],
8804 => ['$\leq$'],
8805 => ['$\geq$'],
8834 => ['$\subset$'],
8835 => ['$\supset$'],
8838 => ['$\subseteq$'],
8839 => ['$\supseteq$'],
8836 => ['$\nsubset$', 'amssymb'],
8853 => ['$\oplus$'],
8855 => ['$\otimes$'],
8869 => ['$\perp$'],
8901 => ['$\cdot$'],
8968 => ['$\rceil$'],
8969 => ['$\lceil$'],
8970 => ['$\lfloor$'],
8971 => ['$\rfloor$'],
9001 => ['$\rangle$'],
9002 => ['$\langle$'],
9674 => ['$\lozenge$', 'amssymb'],
9824 => ['$\spadesuit$'],
9827 => ['$\clubsuit$'],
9829 => ['$\heartsuit$'],
9830 => ['$\diamondsuit$'],
38 => ['\&'],
34 => ['"'],
39 => ['\''],
169 => ['\copyright'],
60 => ['\textless'],
62 => ['\textgreater'],
338 => ['\OE'],
339 => ['\oe'],
352 => ['\v{S}'],
353 => ['\v{s}'],
376 => ['\"Y'],
710 => ['\textasciicircum'],
732 => ['\textasciitilde'],
8211 => ['--'],
8212 => ['---'],
8216 => ['`'],
8217 => ['\''],
8220 => ['``'],
8221 => ['\'\''],
8224 => ['\dag'],
8225 => ['\ddag'],
8240 => ['\permil', 'wasysym'],
8364 => ['\euro', 'eurosym'],
8249 => ['\guilsinglleft'],
8250 => ['\guilsinglright'],
8218 => ['\quotesinglbase', 'mathcomp'],
8222 => ['\quotedblbase', 'mathcomp'],
402 => ['\textflorin', 'mathcomp'],
381 => ['\v{Z}'],
382 => ['\v{z}'],
160 => ['~'],
161 => ['\textexclamdown'],
163 => ['\pounds'],
164 => ['\currency', 'wasysym'],
165 => ['\textyen', 'textcomp'],
166 => ['\brokenvert', 'wasysym'],
167 => ['\S'],
171 => ['\guillemotleft'],
187 => ['\guillemotright'],
174 => ['\textregistered'],
170 => ['\textordfeminine'],
172 => ['$\neg$'],
173 => ['\-'],
176 => ['$\degree$', 'mathabx'],
177 => ['$\pm$'],
180 => ['\''],
181 => ['$\mu$'],
182 => ['\P'],
183 => ['$\cdot$'],
186 => ['\textordmasculine'],
162 => ['\cent', 'wasysym'],
185 => ['$^1$'],
178 => ['$^2$'],
179 => ['$^3$'],
189 => ['$\frac{1}{2}$'],
188 => ['$\frac{1}{4}$'],
190 => ['$\frac{3}{4}'],
192 => ['\`A'],
193 => ['\\\'A'],
194 => ['\^A'],
195 => ['\~A'],
196 => ['\"A'],
197 => ['\AA'],
198 => ['\AE'],
199 => ['\cC'],
200 => ['\`E'],
201 => ['\\\'E'],
202 => ['\^E'],
203 => ['\"E'],
204 => ['\`I'],
205 => ['\\\'I'],
206 => ['\^I'],
207 => ['\"I'],
208 => ['$\eth$', 'amssymb'],
209 => ['\~N'],
210 => ['\`O'],
211 => ['\\\'O'],
212 => ['\^O'],
213 => ['\~O'],
214 => ['\"O'],
215 => ['$\times$'],
216 => ['\O'],
217 => ['\`U'],
218 => ['\\\'U'],
219 => ['\^U'],
220 => ['\"U'],
221 => ['\\\'Y'],
222 => ['\Thorn', 'wasysym'],
223 => ['\ss'],
224 => ['\`a'],
225 => ['\\\'a'],
226 => ['\^a'],
227 => ['\~a'],
228 => ['\"a'],
229 => ['\aa'],
230 => ['\ae'],
231 => ['\cc'],
232 => ['\`e'],
233 => ['\\\'e'],
234 => ['\^e'],
235 => ['\"e'],
236 => ['\`i'],
237 => ['\\\'i'],
238 => ['\^i'],
239 => ['\"i'],
240 => ['$\eth$'],
241 => ['\~n'],
242 => ['\`o'],
243 => ['\\\'o'],
244 => ['\^o'],
245 => ['\~o'],
246 => ['\"o'],
247 => ['$\divide$'],
248 => ['\o'],
249 => ['\`u'],
250 => ['\\\'u'],
251 => ['\^u'],
252 => ['\"u'],
253 => ['\\\'y'],
254 => ['\thorn', 'wasysym'],
255 => ['\"y'],
8201 => ['\thinspace'],
8194 => ['\hskip .5em\relax'],
8195 => ['\quad'],
} # :nodoc:
ENTITY_CONV_TABLE.each_value {|v| v[0] = "#{v[0]}{}" }
def entity_to_latex(entity)
text, package = ENTITY_CONV_TABLE[entity.code_point]
if text
@data[:packages] << package if package
text
else
warning("Couldn't find entity with code #{entity.code_point} in substitution table!")
''
end
end
def convert_entity(el, _opts)
entity_to_latex(el.value)
end
TYPOGRAPHIC_SYMS = {
mdash: '---', ndash: '--', hellip: '\ldots{}',
laquo_space: '\guillemotleft{}~', raquo_space: '~\guillemotright{}',
laquo: '\guillemotleft{}', raquo: '\guillemotright{}'
} # :nodoc:
def convert_typographic_sym(el, _opts)
if (result = @options[:typographic_symbols][el.value])
escape(result)
else
TYPOGRAPHIC_SYMS[el.value]
end
end
def convert_smart_quote(el, opts)
res = entity_to_latex(smart_quote_entity(el)).chomp('{}')
res << "{}" if ((nel = opts[:parent].children[opts[:index] + 1]) && nel.type == :smart_quote) || res =~ /\w$/
res
end
def convert_math(el, _opts)
@data[:packages] += %w[amssymb amsmath amsthm amsfonts]
if el.options[:category] == :block
if el.value.match?(/\A\s*\\begin\{/)
el.value
else
latex_environment('displaymath', el, el.value)
end
else
"$#{el.value}$"
end
end
def convert_abbreviation(el, _opts)
@data[:packages] += %w[acronym]
"\\ac{#{normalize_abbreviation_key(el.value)}}"
end
# Normalize the abbreviation key so that it only contains allowed ASCII character
def normalize_abbreviation_key(key)
key.gsub(/\W/) {|m| m.unpack1('H*') }
end
# Wrap the +text+ inside a LaTeX environment of type +type+. The element +el+ is passed on to
# the method #attribute_list -- the resulting string is appended to both the \\begin and the
# \\end lines of the LaTeX environment for easier post-processing of LaTeX environments.
def latex_environment(type, el, text)
attrs = attribute_list(el)
"\\begin{#{type}}#{latex_link_target(el)}#{attrs}\n#{text.rstrip}\n\\end{#{type}}#{attrs}\n"
end
# Return a string containing a valid \hypertarget command if the element has an ID defined, or
# +nil+ otherwise. If the parameter +add_label+ is +true+, a \label command will also be used
# additionally to the \hypertarget command.
def latex_link_target(el, add_label = false)
if (id = el.attr['id'])
"\\hypertarget{#{id}}{}#{add_label ? "\\label{#{id}}" : ''}"
else
nil
end
end
# Return a LaTeX comment containing all attributes as 'key="value"' pairs.
def attribute_list(el)
attrs = el.attr.map {|k, v| v.nil? ? '' : " #{k}=\"#{v}\"" }.compact.sort.join
attrs = " % #{attrs}" unless attrs.empty?
attrs
end
ESCAPE_MAP = {
"^" => "\\^{}",
"\\" => "\\textbackslash{}",
"~" => "\\ensuremath{\\sim}",
"|" => "\\textbar{}",
"<" => "\\textless{}",
">" => "\\textgreater{}",
"[" => "{[}",
"]" => "{]}",
}.merge(Hash[*"{}$%&_#".each_char.map {|c| [c, "\\#{c}"] }.flatten]) # :nodoc:
ESCAPE_RE = Regexp.union(*ESCAPE_MAP.collect {|k, _v| k }) # :nodoc:
# Escape the special LaTeX characters in the string +str+.
def escape(str)
str.gsub(ESCAPE_RE) {|m| ESCAPE_MAP[m] }
end
end
end
end
kramdown-2.5.1/lib/kramdown/converter/html.rb 0000644 0000041 0000041 00000046273 14726466654 021321 0 ustar www-data www-data # -*- coding: utf-8; frozen_string_literal: true -*-
#
#--
# Copyright (C) 2009-2019 Thomas Leitner
#
# This file is part of kramdown which is licensed under the MIT.
#++
#
require 'kramdown/parser'
require 'kramdown/converter'
require 'kramdown/utils'
module Kramdown
module Converter
# Converts a Kramdown::Document to HTML.
#
# You can customize the HTML converter by sub-classing it and overriding the +convert_NAME+
# methods. Each such method takes the following parameters:
#
# [+el+] The element of type +NAME+ to be converted.
#
# [+indent+] A number representing the current amount of spaces for indent (only used for
# block-level elements).
#
# The return value of such a method has to be a string containing the element +el+ formatted as
# HTML element.
class Html < Base
include ::Kramdown::Utils::Html
include ::Kramdown::Parser::Html::Constants
# The amount of indentation used when nesting HTML tags.
attr_accessor :indent
# Initialize the HTML converter with the given Kramdown document +doc+.
def initialize(root, options)
super
@footnote_counter = @footnote_start = @options[:footnote_nr]
@footnotes = []
@footnotes_by_name = {}
@footnote_location = nil
@toc = []
@toc_code = nil
@indent = 2
@stack = []
# stash string representation of symbol to avoid allocations from multiple interpolations.
@highlighter_class = " highlighter-#{options[:syntax_highlighter]}"
@dispatcher = Hash.new {|h, k| h[k] = :"convert_#{k}" }
end
# Dispatch the conversion of the element +el+ to a +convert_TYPE+ method using the +type+ of
# the element.
def convert(el, indent = -@indent)
send(@dispatcher[el.type], el, indent)
end
# Return the converted content of the children of +el+ as a string. The parameter +indent+ has
# to be the amount of indentation used for the element +el+.
#
# Pushes +el+ onto the @stack before converting the child elements and pops it from the stack
# afterwards.
def inner(el, indent)
result = +''
indent += @indent
@stack.push(el)
el.children.each do |inner_el|
result << send(@dispatcher[inner_el.type], inner_el, indent)
end
@stack.pop
result
end
def convert_blank(_el, _indent)
"\n"
end
def convert_text(el, _indent)
escaped = escape_html(el.value, :text)
@options[:remove_line_breaks_for_cjk] ? fix_cjk_line_break(escaped) : escaped
end
def convert_p(el, indent)
if el.options[:transparent]
inner(el, indent)
elsif el.children.size == 1 && el.children.first.type == :img &&
el.children.first.options[:ial]&.[](:refs)&.include?('standalone')
convert_standalone_image(el, indent)
else
format_as_block_html("p", el.attr, inner(el, indent), indent)
end
end
# Helper method used by +convert_p+ to convert a paragraph that only contains a single :img
# element.
def convert_standalone_image(el, indent)
figure_attr = el.attr.dup
image_attr = el.children.first.attr.dup
if image_attr.key?('class') && !figure_attr.key?('class')
figure_attr['class'] = image_attr.delete('class')
end
if image_attr.key?('id') && !figure_attr.key?('id')
figure_attr['id'] = image_attr.delete('id')
end
body = "#{' ' * (indent + @indent)}
\n" \
"#{' ' * (indent + @indent)}#{image_attr['alt']} \n"
format_as_indented_block_html("figure", figure_attr, body, indent)
end
def convert_codeblock(el, indent)
attr = el.attr.dup
lang = extract_code_language!(attr)
hl_opts = {}
highlighted_code = highlight_code(el.value, el.options[:lang] || lang, :block, hl_opts)
if highlighted_code
add_syntax_highlighter_to_class_attr(attr, lang || hl_opts[:default_lang])
"#{' ' * indent}#{highlighted_code}#{' ' * indent}\n"
else
result = escape_html(el.value)
result.chomp!
if el.attr['class'].to_s =~ /\bshow-whitespaces\b/
result.gsub!(/(?:(^[ \t]+)|([ \t]+$)|([ \t]+))/) do |m|
suffix = ($1 ? '-l' : ($2 ? '-r' : ''))
m.scan(/./).map do |c|
case c
when "\t" then "\t"
when " " then "⋅"
end
end.join
end
end
code_attr = {}
code_attr['class'] = "language-#{lang}" if lang
"#{' ' * indent}" \
"#{result}\n
\n"
end
end
def convert_blockquote(el, indent)
format_as_indented_block_html("blockquote", el.attr, inner(el, indent), indent)
end
def convert_header(el, indent)
attr = el.attr.dup
if @options[:auto_ids] && !attr['id']
attr['id'] = generate_id(el.options[:raw_text])
end
if @options[:header_links] && attr['id'].to_s.length > 0
link = Element.new(:a, nil, nil)
link.attr['href'] = "##{attr['id']}"
el.children.unshift(link)
end
@toc << [el.options[:level], attr['id'], el.children] if attr['id'] && in_toc?(el)
level = output_header_level(el.options[:level])
format_as_block_html("h#{level}", attr, inner(el, indent), indent)
end
def convert_hr(el, indent)
"#{' ' * indent}
\n"
end
ZERO_TO_ONETWENTYEIGHT = (0..128).to_a.freeze
private_constant :ZERO_TO_ONETWENTYEIGHT
def convert_ul(el, indent)
if !@toc_code && el.options.dig(:ial, :refs)&.include?('toc')
@toc_code = [el.type, el.attr, ZERO_TO_ONETWENTYEIGHT.map { rand(36).to_s(36) }.join]
@toc_code.last
elsif !@footnote_location && el.options.dig(:ial, :refs)&.include?('footnotes')
@footnote_location = ZERO_TO_ONETWENTYEIGHT.map { rand(36).to_s(36) }.join
else
format_as_indented_block_html(el.type, el.attr, inner(el, indent), indent)
end
end
alias convert_ol convert_ul
def convert_dl(el, indent)
format_as_indented_block_html("dl", el.attr, inner(el, indent), indent)
end
def convert_li(el, indent)
output = ' ' * indent << "<#{el.type}" << html_attributes(el.attr) << ">"
res = inner(el, indent)
if el.children.empty? || (el.children.first.type == :p && el.children.first.options[:transparent])
output << res << (res.match?(/\n\Z/) ? ' ' * indent : '')
else
output << "\n" << res << ' ' * indent
end
output << "#{el.type}>\n"
end
alias convert_dd convert_li
def convert_dt(el, indent)
attr = el.attr.dup
@stack.last.options[:ial][:refs].each do |ref|
if ref =~ /\Aauto_ids(?:-([\w-]+))?/
attr['id'] = "#{$1}#{basic_generate_id(el.options[:raw_text])}".lstrip
break
end
end if !attr['id'] && @stack.last.options[:ial] && @stack.last.options[:ial][:refs]
format_as_block_html("dt", attr, inner(el, indent), indent)
end
def convert_html_element(el, indent)
res = inner(el, indent)
if el.options[:category] == :span
"<#{el.value}#{html_attributes(el.attr)}" + \
(res.empty? && HTML_ELEMENTS_WITHOUT_BODY.include?(el.value) ? " />" : ">#{res}#{el.value}>")
else
output = +''
if @stack.last.type != :html_element || @stack.last.options[:content_model] != :raw
output << ' ' * indent
end
output << "<#{el.value}#{html_attributes(el.attr)}"
if el.options[:is_closed] && el.options[:content_model] == :raw
output << " />"
elsif !res.empty? && el.options[:content_model] != :block
output << ">#{res}#{el.value}>"
elsif !res.empty?
output << ">\n#{res.chomp}\n" << ' ' * indent << "#{el.value}>"
elsif HTML_ELEMENTS_WITHOUT_BODY.include?(el.value)
output << " />"
else
output << ">#{el.value}>"
end
output << "\n" if @stack.last.type != :html_element || @stack.last.options[:content_model] != :raw
output
end
end
def convert_xml_comment(el, indent)
if el.options[:category] == :block &&
(@stack.last.type != :html_element || @stack.last.options[:content_model] != :raw)
' ' * indent << el.value << "\n"
else
el.value
end
end
alias convert_xml_pi convert_xml_comment
def convert_table(el, indent)
format_as_indented_block_html(el.type, el.attr, inner(el, indent), indent)
end
alias convert_thead convert_table
alias convert_tbody convert_table
alias convert_tfoot convert_table
alias convert_tr convert_table
ENTITY_NBSP = ::Kramdown::Utils::Entities.entity('nbsp') # :nodoc:
def convert_td(el, indent)
res = inner(el, indent)
type = (@stack[-2].type == :thead ? :th : :td)
attr = el.attr
alignment = @stack[-3].options[:alignment][@stack.last.children.index(el)]
if alignment != :default
attr = el.attr.dup
attr['style'] = (attr.key?('style') ? "#{attr['style']}; " : '') + "text-align: #{alignment}"
end
format_as_block_html(type, attr, res.empty? ? entity_to_str(ENTITY_NBSP) : res, indent)
end
def convert_comment(el, indent)
if el.options[:category] == :block
"#{' ' * indent}\n"
else
""
end
end
def convert_br(_el, _indent)
"
"
end
def convert_a(el, indent)
format_as_span_html("a", el.attr, inner(el, indent))
end
def convert_img(el, _indent)
"
"
end
def convert_codespan(el, _indent)
attr = el.attr.dup
lang = extract_code_language(attr)
hl_opts = {}
result = highlight_code(el.value, lang, :span, hl_opts)
if result
add_syntax_highlighter_to_class_attr(attr, lang || hl_opts[:default_lang])
else
result = escape_html(el.value)
end
format_as_span_html('code', attr, result)
end
def convert_footnote(el, _indent)
repeat = ''
name = @options[:footnote_prefix] + el.options[:name]
if (footnote = @footnotes_by_name[name])
number = footnote[2]
repeat = ":#{footnote[3] += 1}"
else
number = @footnote_counter
@footnote_counter += 1
@footnotes << [name, el.value, number, 0]
@footnotes_by_name[name] = @footnotes.last
end
formatted_link_text = sprintf(@options[:footnote_link_text], number)
"" \
"" \
"#{formatted_link_text}"
end
def convert_raw(el, _indent)
if !el.options[:type] || el.options[:type].empty? || el.options[:type].include?('html')
el.value + (el.options[:category] == :block ? "\n" : '')
else
''
end
end
def convert_em(el, indent)
format_as_span_html(el.type, el.attr, inner(el, indent))
end
alias convert_strong convert_em
def convert_entity(el, _indent)
entity_to_str(el.value, el.options[:original])
end
TYPOGRAPHIC_SYMS = {
mdash: [::Kramdown::Utils::Entities.entity('mdash')],
ndash: [::Kramdown::Utils::Entities.entity('ndash')],
hellip: [::Kramdown::Utils::Entities.entity('hellip')],
laquo_space: [::Kramdown::Utils::Entities.entity('laquo'),
::Kramdown::Utils::Entities.entity('nbsp')],
raquo_space: [::Kramdown::Utils::Entities.entity('nbsp'),
::Kramdown::Utils::Entities.entity('raquo')],
laquo: [::Kramdown::Utils::Entities.entity('laquo')],
raquo: [::Kramdown::Utils::Entities.entity('raquo')],
} # :nodoc:
def convert_typographic_sym(el, _indent)
if (result = @options[:typographic_symbols][el.value])
escape_html(result, :text)
else
TYPOGRAPHIC_SYMS[el.value].map {|e| entity_to_str(e) }.join
end
end
def convert_smart_quote(el, _indent)
entity_to_str(smart_quote_entity(el))
end
def convert_math(el, indent)
if (result = format_math(el, indent: indent))
result
else
attr = el.attr.dup
attr['class'] = "#{attr['class']} kdmath".lstrip
if el.options[:category] == :block
format_as_block_html('div', attr, "$$\n#{el.value}\n$$", indent)
else
format_as_span_html('span', attr, "$#{el.value}$")
end
end
end
def convert_abbreviation(el, _indent)
title = @root.options[:abbrev_defs][el.value]
attr = @root.options[:abbrev_attr][el.value].dup
attr['title'] = title unless title.empty?
format_as_span_html("abbr", attr, el.value)
end
def convert_root(el, indent)
result = inner(el, indent)
if @footnote_location
result.sub!(/#{@footnote_location}/, footnote_content.gsub(/\\/, "\\\\\\\\"))
else
result << footnote_content
end
if @toc_code
toc_tree = generate_toc_tree(@toc, @toc_code[0], @toc_code[1] || {})
text = toc_tree.children.empty? ? '' : convert(toc_tree, 0)
result.sub!(/#{@toc_code.last}/, text.gsub(/\\/, "\\\\\\\\"))
end
result
end
# Format the given element as span HTML.
def format_as_span_html(name, attr, body)
"<#{name}#{html_attributes(attr)}>#{body}#{name}>"
end
# Format the given element as block HTML.
def format_as_block_html(name, attr, body, indent)
"#{' ' * indent}<#{name}#{html_attributes(attr)}>#{body}#{name}>\n"
end
# Format the given element as block HTML with a newline after the start tag and indentation
# before the end tag.
def format_as_indented_block_html(name, attr, body, indent)
"#{' ' * indent}<#{name}#{html_attributes(attr)}>\n#{body}#{' ' * indent}#{name}>\n"
end
# Add the syntax highlighter name to the 'class' attribute of the given attribute hash. And
# overwrites or add a "language-LANG" part using the +lang+ parameter if +lang+ is not nil.
def add_syntax_highlighter_to_class_attr(attr, lang = nil)
(attr['class'] = (attr['class'] || '') + @highlighter_class).lstrip!
attr['class'].sub!(/\blanguage-\S+|(^)/) { "language-#{lang}#{$1 ? ' ' : ''}" } if lang
end
# Generate and return an element tree for the table of contents.
def generate_toc_tree(toc, type, attr)
sections = Element.new(type, nil, attr.dup)
sections.attr['id'] ||= 'markdown-toc'
stack = []
toc.each do |level, id, children|
li = Element.new(:li, nil, nil, level: level)
li.children << Element.new(:p, nil, nil, transparent: true)
a = Element.new(:a, nil)
a.attr['href'] = "##{id}"
a.attr['id'] = "#{sections.attr['id']}-#{id}"
a.children.concat(fix_for_toc_entry(Marshal.load(Marshal.dump(children))))
li.children.last.children << a
li.children << Element.new(type)
success = false
until success
if stack.empty?
sections.children << li
stack << li
success = true
elsif stack.last.options[:level] < li.options[:level]
stack.last.children.last.children << li
stack << li
success = true
else
item = stack.pop
item.children.pop if item.children.last.children.empty?
end
end
end
until stack.empty?
item = stack.pop
item.children.pop if item.children.last.children.empty?
end
sections
end
# Fixes the elements for use in a TOC entry.
def fix_for_toc_entry(elements)
remove_footnotes(elements)
unwrap_links(elements)
elements
end
# Remove all link elements by unwrapping them.
def unwrap_links(elements)
elements.map! do |c|
unwrap_links(c.children)
c.type == :a ? c.children : c
end.flatten!
end
# Remove all footnotes from the given elements.
def remove_footnotes(elements)
elements.delete_if do |c|
remove_footnotes(c.children)
c.type == :footnote
end
end
# Obfuscate the +text+ by using HTML entities.
def obfuscate(text)
result = +''
text.each_byte do |b|
result << (b > 128 ? b.chr : sprintf("%03d;", b))
end
result.force_encoding(text.encoding)
result
end
FOOTNOTE_BACKLINK_FMT = "%s%s"
# Return an HTML ordered list with the footnote content for the used footnotes.
def footnote_content
ol = Element.new(:ol)
ol.attr['start'] = @footnote_start if @footnote_start != 1
i = 0
backlink_text = escape_html(@options[:footnote_backlink], :text)
while i < @footnotes.length
name, data, _, repeat = *@footnotes[i]
li = Element.new(:li, nil, 'id' => "fn:#{name}")
li.children = Marshal.load(Marshal.dump(data.children))
para = nil
if li.children.last.type == :p || @options[:footnote_backlink_inline]
parent = li
while !parent.children.empty? && ![:p, :header].include?(parent.children.last.type)
parent = parent.children.last
end
para = parent.children.last
insert_space = true
end
unless para
li.children << (para = Element.new(:p))
insert_space = false
end
unless @options[:footnote_backlink].empty?
nbsp = entity_to_str(ENTITY_NBSP)
value = sprintf(FOOTNOTE_BACKLINK_FMT, (insert_space ? nbsp : ''), name, backlink_text)
para.children << Element.new(:raw, value)
(1..repeat).each do |index|
value = sprintf(FOOTNOTE_BACKLINK_FMT, nbsp, "#{name}:#{index}",
"#{backlink_text}#{index + 1}")
para.children << Element.new(:raw, value)
end
end
ol.children << Element.new(:raw, convert(li, 4))
i += 1
end
if ol.children.empty?
''
else
format_as_indented_block_html('div', {class: "footnotes", role: "doc-endnotes"}, convert(ol, 2), 0)
end
end
end
end
end
kramdown-2.5.1/lib/kramdown/converter/remove_html_tags.rb 0000644 0000041 0000041 00000003371 14726466654 023704 0 ustar www-data www-data # -*- coding: utf-8; frozen_string_literal: true -*-
#
#--
# Copyright (C) 2009-2019 Thomas Leitner
#
# This file is part of kramdown which is licensed under the MIT.
#++
#
require 'kramdown/converter'
module Kramdown
module Converter
# Removes all block (and optionally span) level HTML tags from the element tree.
#
# This converter can be used on parsed HTML documents to get an element tree that will only
# contain native kramdown elements.
#
# *Note* that the returned element tree may not be fully conformant (i.e. the content models of
# *some elements may be violated)!
#
# This converter modifies the given tree in-place and returns it.
class RemoveHtmlTags < Base
def initialize(root, options)
super
@options[:template] = ''
end
def convert(el)
real_el, el = el, el.value if el.type == :footnote
children = el.children.dup
index = 0
while index < children.length
if children[index].type == :xml_pi ||
(children[index].type == :html_element && (children[index].value == 'style' ||
children[index].value == 'script'))
children[index..index] = []
elsif children[index].type == :html_element &&
((@options[:remove_block_html_tags] && children[index].options[:category] == :block) ||
(@options[:remove_span_html_tags] && children[index].options[:category] == :span))
children[index..index] = children[index].children
else
convert(children[index])
index += 1
end
end
el.children = children
real_el || el
end
end
end
end
kramdown-2.5.1/lib/kramdown/converter/man.rb 0000644 0000041 0000041 00000021620 14726466654 021115 0 ustar www-data www-data # -*- coding: utf-8; frozen_string_literal: true -*-
#
#--
# Copyright (C) 2009-2019 Thomas Leitner
#
# This file is part of kramdown which is licensed under the MIT.
#++
#
require 'kramdown/converter'
module Kramdown
module Converter
# Converts a Kramdown::Document to a manpage in groff format. See man(7), groff_man(7) and
# man-pages(7) for information regarding the output.
class Man < Base
def convert(el, opts = {indent: 0, result: +''}) # :nodoc:
send("convert_#{el.type}", el, opts)
end
private
def inner(el, opts, use = :all)
arr = el.children.reject {|e| e.type == :blank }
arr.each_with_index do |inner_el, index|
next if use == :rest && index == 0
break if use == :first && index > 0
options = opts.dup
options[:parent] = el
options[:index] = index
options[:prev] = (index == 0 ? nil : arr[index - 1])
options[:next] = (index == arr.length - 1 ? nil : arr[index + 1])
convert(inner_el, options)
end
end
def convert_root(el, opts)
@title_done = false
opts[:result] = +".\\\" generated by kramdown\n"
inner(el, opts)
opts[:result]
end
def convert_blank(*)
end
alias convert_hr convert_blank
alias convert_xml_pi convert_blank
def convert_p(el, opts)
if (opts[:index] != 0 && opts[:prev].type != :header) ||
(opts[:parent].type == :blockquote && opts[:index] == 0)
opts[:result] << macro("P")
end
inner(el, opts)
newline(opts[:result])
end
def convert_header(el, opts)
return unless opts[:parent].type == :root
case el.options[:level]
when 1
unless @title_done
@title_done = true
data = el.options[:raw_text].scan(/([^(]+)\s*\((\d\w*)\)(?:\s*-+\s*(.*))?/).first ||
el.options[:raw_text].scan(/([^\s]+)\s*(?:-*\s+)?()(.*)/).first
return unless data && data[0]
name = data[0]
section = (data[1].to_s.empty? ? el.attr['data-section'] || '7' : data[1])
description = (data[2].to_s.empty? ? nil : " - #{data[2]}")
date = el.attr['data-date'] ? quote(el.attr['data-date']) : nil
extra = (el.attr['data-extra'] ? quote(escape(el.attr['data-extra'].to_s)) : nil)
opts[:result] << macro("TH", quote(escape(name.upcase)), quote(section), date, extra)
if description
opts[:result] << macro("SH", "NAME") << escape("#{name}#{description}") << "\n"
end
end
when 2
opts[:result] << macro("SH", quote(escape(el.options[:raw_text])))
when 3
opts[:result] << macro("SS", quote(escape(el.options[:raw_text])))
else
warning("Header levels greater than three are not supported")
end
end
def convert_codeblock(el, opts)
opts[:result] << macro("sp") << macro("RS", 4) << macro("EX")
opts[:result] << newline(escape(el.value, true))
opts[:result] << macro("EE") << macro("RE")
end
def convert_blockquote(el, opts)
opts[:result] << macro("RS")
inner(el, opts)
opts[:result] << macro("RE")
end
def convert_ul(el, opts)
compact = (el.attr['class'] =~ /\bcompact\b/)
opts[:result] << macro("sp") << macro("PD", 0) if compact
inner(el, opts)
opts[:result] << macro("PD") if compact
end
alias convert_dl convert_ul
alias convert_ol convert_ul
def convert_li(el, opts)
sym = (opts[:parent].type == :ul ? '\(bu' : "#{opts[:index] + 1}.")
opts[:result] << macro("IP", sym, 4)
inner(el, opts, :first)
if el.children.size > 1
opts[:result] << macro("RS")
inner(el, opts, :rest)
opts[:result] << macro("RE")
end
end
def convert_dt(el, opts)
opts[:result] << macro(opts[:prev] && opts[:prev].type == :dt ? "TQ" : "TP")
inner(el, opts)
opts[:result] << "\n"
end
def convert_dd(el, opts)
inner(el, opts, :first)
if el.children.size > 1
opts[:result] << macro("RS")
inner(el, opts, :rest)
opts[:result] << macro("RE")
end
opts[:result] << macro("sp") if opts[:next] && opts[:next].type == :dd
end
TABLE_CELL_ALIGNMENT = {left: 'l', center: 'c', right: 'r', default: 'l'}
def convert_table(el, opts)
opts[:alignment] = el.options[:alignment].map {|a| TABLE_CELL_ALIGNMENT[a] }
table_options = ["box"]
table_options << "center" if el.attr['class']&.match?(/\bcenter\b/)
opts[:result] << macro("TS") << "#{table_options.join(' ')} ;\n"
inner(el, opts)
opts[:result] << macro("TE") << macro("sp")
end
def convert_thead(el, opts)
opts[:result] << opts[:alignment].map {|a| "#{a}b" }.join(' ') << " .\n"
inner(el, opts)
opts[:result] << "=\n"
end
def convert_tbody(el, opts)
opts[:result] << ".T&\n" if opts[:index] != 0
opts[:result] << opts[:alignment].join(' ') << " .\n"
inner(el, opts)
opts[:result] << (opts[:next].type == :tfoot ? "=\n" : "_\n") if opts[:next]
end
def convert_tfoot(el, opts)
inner(el, opts)
end
def convert_tr(el, opts)
inner(el, opts)
opts[:result] << "\n"
end
def convert_td(el, opts)
result = opts[:result]
opts[:result] = +''
inner(el, opts)
if opts[:result].include?("\n")
warning("Table cells using links are not supported")
result << "\t"
else
result << opts[:result] << "\t"
end
end
def convert_html_element(*)
warning("HTML elements are not supported")
end
def convert_xml_comment(el, opts)
newline(opts[:result]) << ".\"#{escape(el.value, true).rstrip.gsub(/\n/, "\n.\"")}\n"
end
alias convert_comment convert_xml_comment
def convert_a(el, opts)
if el.children.size == 1 && el.children[0].type == :text &&
el.attr['href'] == el.children[0].value
newline(opts[:result]) << macro("UR", escape(el.attr['href'])) << macro("UE")
elsif el.attr['href'].start_with?('mailto:')
newline(opts[:result]) << macro("MT", escape(el.attr['href'].sub(/^mailto:/, ''))) <<
macro("UE")
else
newline(opts[:result]) << macro("UR", escape(el.attr['href']))
inner(el, opts)
newline(opts[:result]) << macro("UE")
end
end
def convert_img(_el, _opts)
warning("Images are not supported")
end
def convert_em(el, opts)
opts[:result] << '\fI'
inner(el, opts)
opts[:result] << '\fP'
end
def convert_strong(el, opts)
opts[:result] << '\fB'
inner(el, opts)
opts[:result] << '\fP'
end
def convert_codespan(el, opts)
opts[:result] << "\\fB#{escape(el.value)}\\fP"
end
def convert_br(_el, opts)
newline(opts[:result]) << macro("br")
end
def convert_abbreviation(el, opts)
opts[:result] << escape(el.value)
end
def convert_math(el, opts)
if el.options[:category] == :block
convert_codeblock(el, opts)
else
convert_codespan(el, opts)
end
end
def convert_footnote(*)
warning("Footnotes are not supported")
end
def convert_raw(*)
warning("Raw content is not supported")
end
def convert_text(el, opts)
text = escape(el.value)
text.lstrip! if opts[:result][-1] == "\n"
opts[:result] << text
end
def convert_entity(el, opts)
opts[:result] << unicode_char(el.value.code_point)
end
def convert_smart_quote(el, opts)
opts[:result] << unicode_char(::Kramdown::Utils::Entities.entity(el.value.to_s).code_point)
end
TYPOGRAPHIC_SYMS_MAP = {
mdash: '\(em', ndash: '\(em', hellip: '\.\.\.',
laquo_space: '\[Fo]', raquo_space: '\[Fc]', laquo: '\[Fo]', raquo: '\[Fc]'
}
def convert_typographic_sym(el, opts)
opts[:result] << TYPOGRAPHIC_SYMS_MAP[el.value]
end
def macro(name, *args)
".#{[name, *args].compact.join(' ')}\n"
end
def newline(text)
text << "\n" unless text[-1] == "\n"
text
end
def quote(text)
"\"#{text.gsub(/"/, '\\"')}\""
end
def escape(text, preserve_whitespace = false)
text = (preserve_whitespace ? text.dup : text.gsub(/\s+/, ' '))
text.gsub!('\\', "\\e")
text.gsub!(/^\./, '\\\\&.')
text.gsub!(/[.'-]/) {|m| "\\#{m}" }
text
end
def unicode_char(codepoint)
"\\[u#{codepoint.to_s(16).rjust(4, '0')}]"
end
end
end
end
kramdown-2.5.1/lib/kramdown/utils.rb 0000644 0000041 0000041 00000002355 14726466654 017477 0 ustar www-data www-data # -*- coding: utf-8; frozen_string_literal: true -*-
#
#--
# Copyright (C) 2009-2019 Thomas Leitner
#
# This file is part of kramdown which is licensed under the MIT.
#++
#
module Kramdown
# == \Utils Module
#
# This module contains utility class/modules/methods that can be used by both parsers and
# converters.
module Utils
autoload :Entities, 'kramdown/utils/entities'
autoload :Html, 'kramdown/utils/html'
autoload :Unidecoder, 'kramdown/utils/unidecoder'
autoload :StringScanner, 'kramdown/utils/string_scanner'
autoload :Configurable, 'kramdown/utils/configurable'
autoload :LRUCache, 'kramdown/utils/lru_cache'
# Treat +name+ as if it were snake cased (e.g. snake_case) and camelize it (e.g. SnakeCase).
def self.camelize(name)
name.split('_').inject(+'') {|s, x| s << x[0..0].upcase << x[1..-1] }
end
# Treat +name+ as if it were camelized (e.g. CamelizedName) and snake-case it (e.g. camelized_name).
def self.snake_case(name)
name = name.dup
name.gsub!(/([A-Z]+)([A-Z][a-z])/, '\1_\2')
name.gsub!(/([a-z])([A-Z])/, '\1_\2')
name.downcase!
name
end
def self.deep_const_get(str)
::Object.const_get(str)
end
end
end
kramdown-2.5.1/lib/kramdown/options.rb 0000644 0000041 0000041 00000053116 14726466654 020033 0 ustar www-data www-data # -*- coding: utf-8; frozen_string_literal: true -*-
#
#--
# Copyright (C) 2009-2019 Thomas Leitner
#
# This file is part of kramdown which is licensed under the MIT.
#++
#
require 'yaml'
module Kramdown
# This module defines all options that are used by parsers and/or converters as well as providing
# methods to deal with the options.
module Options
# Helper class introducing a boolean type for specifying boolean values (+true+ and +false+) as
# option types.
class Boolean
# Return +true+ if +other+ is either +true+ or +false+
def self.===(other)
FalseClass === other || TrueClass === other
end
end
# ----------------------------
# :section: Option definitions
#
# This sections describes the methods that can be used on the Options module.
# ----------------------------
# Struct class for storing the definition of an option.
Definition = Struct.new(:name, :type, :default, :desc, :validator)
# Allowed option types.
ALLOWED_TYPES = [String, Integer, Float, Symbol, Boolean, Object]
@options = {}
@defaults = nil
# Define a new option called +name+ (a Symbol) with the given +type+ (String, Integer, Float,
# Symbol, Boolean, Object), default value +default+ and the description +desc+. If a block is
# specified, it should validate the value and either raise an error or return a valid value.
#
# The type 'Object' should only be used for complex types for which none of the other types
# suffices. A block needs to be specified when using type 'Object' and it has to cope with
# a value given as string and as the opaque type.
def self.define(name, type, default, desc, &block)
name = name.to_sym
raise ArgumentError, "Option name #{name} is already used" if @options.key?(name)
raise ArgumentError, "Invalid option type #{type} specified" unless ALLOWED_TYPES.include?(type)
raise ArgumentError, "Invalid type for default value" if !(type === default) && !default.nil?
raise ArgumentError, "Missing validator block" if type == Object && block.nil?
@options[name] = Definition.new(name, type, default, desc, block)
@defaults = nil
end
# Return all option definitions.
def self.definitions
@options
end
# Return +true+ if an option called +name+ is defined.
def self.defined?(name)
@options.key?(name.to_sym)
end
# Return a Hash with the default values for all options.
def self.defaults
@defaults ||=
begin
temp = {}
@options.each {|_n, o| temp[o.name] = o.default }
temp.freeze
end
end
# Merge the #defaults Hash with the *parsed* options from the given Hash, i.e. only valid option
# names are considered and their value is run through the #parse method.
def self.merge(hash)
temp = defaults.dup
hash.each do |k, v|
k = k.to_sym
temp[k] = @options.key?(k) ? parse(k, v) : v
end
temp
end
# Parse the given value +data+ as if it was a value for the option +name+ and return the parsed
# value with the correct type.
#
# If +data+ already has the correct type, it is just returned. Otherwise it is converted to a
# String and then to the correct type.
def self.parse(name, data)
name = name.to_sym
raise ArgumentError, "No option named #{name} defined" unless @options.key?(name)
unless @options[name].type === data
data = data.to_s
data = if @options[name].type == String
data
elsif @options[name].type == Integer
Integer(data) rescue raise Kramdown::Error, "Invalid integer value for option '#{name}': '#{data}'"
elsif @options[name].type == Float
Float(data) rescue raise Kramdown::Error, "Invalid float value for option '#{name}': '#{data}'"
elsif @options[name].type == Symbol
str_to_sym(data)
elsif @options[name].type == Boolean
data.downcase.strip != 'false' && !data.empty?
end
end
data = @options[name].validator[data] if @options[name].validator
data
end
# Converts the given String +data+ into a Symbol or +nil+ with the
# following provisions:
#
# - A leading colon is stripped from the string.
# - An empty value or a value equal to "nil" results in +nil+.
def self.str_to_sym(data)
data = data.strip
data = data[1..-1] if data[0] == ':'
(data.empty? || data == 'nil' ? nil : data.to_sym)
end
# ----------------------------
# :section: Option Validators
#
# This sections contains all pre-defined option validators.
# ----------------------------
# Ensures that the option value +val+ for the option called +name+ is a valid array. The
# parameter +val+ can be
#
# - a comma separated string which is split into an array of values
# - or an array.
#
# Optionally, the array is checked for the correct size.
def self.simple_array_validator(val, name, size = nil)
if String === val
val = val.split(",")
elsif !(Array === val)
raise Kramdown::Error, "Invalid type #{val.class} for option #{name}"
end
if size && val.size != size
raise Kramdown::Error, "Option #{name} needs exactly #{size} values"
end
val
end
# Ensures that the option value +val+ for the option called +name+ is a valid hash. The
# parameter +val+ can be
#
# - a hash in YAML format
# - or a Ruby Hash object.
def self.simple_hash_validator(val, name)
if String === val
begin
val = YAML.safe_load(val)
rescue RuntimeError, ArgumentError, SyntaxError
raise Kramdown::Error, "Invalid YAML value for option #{name}"
end
end
raise Kramdown::Error, "Invalid type #{val.class} for option #{name}" unless Hash === val
val
end
# ----------------------------
# :section: Option Definitions
#
# This sections contains all option definitions that are used by the included
# parsers/converters.
# ----------------------------
define(:template, String, '', <<~EOF)
The name of an ERB template file that should be used to wrap the output
or the ERB template itself.
This is used to wrap the output in an environment so that the output can
be used as a stand-alone document. For example, an HTML template would
provide the needed header and body tags so that the whole output is a
valid HTML file. If no template is specified, the output will be just
the converted text.
When resolving the template file, the given template name is used first.
If such a file is not found, the converter extension (the same as the
converter name) is appended. If the file still cannot be found, the
templates name is interpreted as a template name that is provided by
kramdown (without the converter extension). If the file is still not
found, the template name is checked if it starts with 'string://' and if
it does, this prefix is removed and the rest is used as template
content.
kramdown provides a default template named 'document' for each converter.
Default: ''
Used by: all converters
EOF
define(:auto_ids, Boolean, true, <<~EOF)
Use automatic header ID generation
If this option is `true`, ID values for all headers are automatically
generated if no ID is explicitly specified.
Default: true
Used by: HTML/Latex converter
EOF
define(:auto_id_stripping, Boolean, false, <<~EOF)
Strip all formatting from header text for automatic ID generation
If this option is `true`, only the text elements of a header are used
for generating the ID later (in contrast to just using the raw header
text line).
This option will be removed in version 2.0 because this will be the
default then.
Default: false
Used by: kramdown parser
EOF
define(:auto_id_prefix, String, '', <<~EOF)
Prefix used for automatically generated header IDs
This option can be used to set a prefix for the automatically generated
header IDs so that there is no conflict when rendering multiple kramdown
documents into one output file separately. The prefix should only
contain characters that are valid in an ID!
Default: ''
Used by: HTML/Latex converter
EOF
define(:header_links, Boolean, false, <<~EOF)
Adds anchor tags within headers that can be used to generate permalinks
when not using a table of contents.
The anchor tags are empty, but can be styled to your liking.
Default: false
EOF
define(:transliterated_header_ids, Boolean, false, <<~EOF)
Transliterate the header text before generating the ID
Only ASCII characters are used in headers IDs. This is not good for
languages with many non-ASCII characters. By enabling this option
the header text is transliterated to ASCII as good as possible so that
the resulting header ID is more useful.
The stringex library needs to be installed for this feature to work!
Default: false
Used by: HTML/Latex converter
EOF
define(:parse_block_html, Boolean, false, <<~EOF)
Process kramdown syntax in block HTML tags
If this option is `true`, the kramdown parser processes the content of
block HTML tags as text containing block-level elements. Since this is
not wanted normally, the default is `false`. It is normally better to
selectively enable kramdown processing via the markdown attribute.
Default: false
Used by: kramdown parser
EOF
define(:parse_span_html, Boolean, true, <<~EOF)
Process kramdown syntax in span HTML tags
If this option is `true`, the kramdown parser processes the content of
span HTML tags as text containing span-level elements.
Default: true
Used by: kramdown parser
EOF
define(:html_to_native, Boolean, false, <<~EOF)
Convert HTML elements to native elements
If this option is `true`, the parser converts HTML elements to native
elements. For example, when parsing `hallo` the emphasis tag
would normally be converted to an `:html` element with tag type `:em`.
If `html_to_native` is `true`, then the emphasis would be converted to a
native `:em` element.
This is useful for converters that cannot deal with HTML elements.
Default: false
Used by: kramdown parser
EOF
define(:link_defs, Object, {}, <<~EOF) do |val|
Pre-defines link definitions
This option can be used to pre-define link definitions. The value needs
to be a Hash where the keys are the link identifiers and the values are
two element Arrays with the link URL and the link title.
If the value is a String, it has to contain a valid YAML hash and the
hash has to follow the above guidelines.
Default: {}
Used by: kramdown parser
EOF
val = simple_hash_validator(val, :link_defs)
val.each do |_k, v|
if !(Array === v) || v.size > 2 || v.empty?
raise Kramdown::Error, "Invalid structure for hash value of option #{name}"
end
v << nil if v.size == 1
end
val
end
define(:footnote_nr, Integer, 1, <<~EOF)
The number of the first footnote
This option can be used to specify the number that is used for the first
footnote.
Default: 1
Used by: HTML converter
EOF
define(:entity_output, Symbol, :as_char, <<~EOF)
Defines how entities are output
The possible values are :as_input (entities are output in the same
form as found in the input), :numeric (entities are output in numeric
form), :symbolic (entities are output in symbolic form if possible) or
:as_char (entities are output as characters if possible, only available
on Ruby 1.9).
Default: :as_char
Used by: HTML converter, kramdown converter
EOF
TOC_LEVELS_RANGE = (1..6).freeze
TOC_LEVELS_ARRAY = TOC_LEVELS_RANGE.to_a.freeze
private_constant :TOC_LEVELS_RANGE, :TOC_LEVELS_ARRAY
define(:toc_levels, Object, TOC_LEVELS_ARRAY, <<~EOF) do |val|
Defines the levels that are used for the table of contents
The individual levels can be specified by separating them with commas
(e.g. 1,2,3) or by using the range syntax (e.g. 1..3). Only the
specified levels are used for the table of contents.
Default: 1..6
Used by: HTML/Latex converter
EOF
case val
when String
if val =~ /^(\d)\.\.(\d)$/
val = Range.new($1.to_i, $2.to_i).to_a
elsif val.match?(/^\d(?:,\d)*$/)
val = val.split(",").map(&:to_i).uniq
else
raise Kramdown::Error, "Invalid syntax for option toc_levels"
end
when Array
unless val.eql?(TOC_LEVELS_ARRAY)
val = val.map(&:to_i).uniq
end
when Range
val = if val.eql?(TOC_LEVELS_RANGE)
TOC_LEVELS_ARRAY
else
val.map(&:to_i).uniq
end
else
raise Kramdown::Error, "Invalid type #{val.class} for option toc_levels"
end
if val.any? {|i| !TOC_LEVELS_RANGE.cover?(i) }
raise Kramdown::Error, "Level numbers for option toc_levels have to be integers from 1 to 6"
end
val
end
define(:line_width, Integer, 72, <<~EOF)
Defines the line width to be used when outputting a document
Default: 72
Used by: kramdown converter
EOF
define(:latex_headers, Object, %w[section subsection subsubsection paragraph subparagraph subparagraph], <<~EOF) do |val|
Defines the LaTeX commands for different header levels
The commands for the header levels one to six can be specified by
separating them with commas.
Default: section,subsection,subsubsection,paragraph,subparagraph,subparagraph
Used by: Latex converter
EOF
simple_array_validator(val, :latex_headers, 6)
end
SMART_QUOTES_ENTITIES = %w[lsquo rsquo ldquo rdquo].freeze
SMART_QUOTES_STR = SMART_QUOTES_ENTITIES.join(',').freeze
private_constant :SMART_QUOTES_ENTITIES, :SMART_QUOTES_STR
define(:smart_quotes, Object, SMART_QUOTES_ENTITIES, <<~EOF) do |val|
Defines the HTML entity names or code points for smart quote output
The entities identified by entity name or code point that should be
used for, in order, a left single quote, a right single quote, a left
double and a right double quote are specified by separating them with
commas.
Default: lsquo,rsquo,ldquo,rdquo
Used by: HTML/Latex converter
EOF
if val == SMART_QUOTES_STR || val == SMART_QUOTES_ENTITIES
SMART_QUOTES_ENTITIES
else
val = simple_array_validator(val, :smart_quotes, 4)
val.map! {|v| Integer(v) rescue v }
val
end
end
define(:typographic_symbols, Object, {}, <<~EOF) do |val|
Defines a mapping from typographical symbol to output characters
Typographical symbols are normally output using their equivalent Unicode
codepoint. However, sometimes one wants to change the output, mostly to
fallback to a sequence of ASCII characters.
This option allows this by specifying a mapping from typographical
symbol to its output string. For example, the mapping {hellip: ...} would
output the standard ASCII representation of an ellipsis.
The available typographical symbol names are:
* hellip: ellipsis
* mdash: em-dash
* ndash: en-dash
* laquo: left guillemet
* raquo: right guillemet
* laquo_space: left guillemet followed by a space
* raquo_space: right guillemet preceeded by a space
Default: {}
Used by: HTML/Latex converter
EOF
val = simple_hash_validator(val, :typographic_symbols)
val.keys.each do |k|
val[k.kind_of?(String) ? str_to_sym(k) : k] = val.delete(k).to_s
end
val
end
define(:remove_block_html_tags, Boolean, true, <<~EOF)
Remove block HTML tags
If this option is `true`, the RemoveHtmlTags converter removes
block HTML tags.
Default: true
Used by: RemoveHtmlTags converter
EOF
define(:remove_span_html_tags, Boolean, false, <<~EOF)
Remove span HTML tags
If this option is `true`, the RemoveHtmlTags converter removes
span HTML tags.
Default: false
Used by: RemoveHtmlTags converter
EOF
define(:header_offset, Integer, 0, <<~EOF)
Sets the output offset for headers
If this option is c (may also be negative) then a header with level n
will be output as a header with level c+n. If c+n is lower than 1,
level 1 will be used. If c+n is greater than 6, level 6 will be used.
Default: 0
Used by: HTML converter, Kramdown converter, Latex converter
EOF
define(:syntax_highlighter, Symbol, :rouge, <<~EOF)
Set the syntax highlighter
Specifies the syntax highlighter that should be used for highlighting
code blocks and spans. If this option is set to +nil+, no syntax
highlighting is done.
Options for the syntax highlighter can be set with the
syntax_highlighter_opts configuration option.
Default: rouge
Used by: HTML/Latex converter
EOF
define(:syntax_highlighter_opts, Object, {}, <<~EOF) do |val|
Set the syntax highlighter options
Specifies options for the syntax highlighter set via the
syntax_highlighter configuration option.
The value needs to be a hash with key-value pairs that are understood by
the used syntax highlighter.
Default: {}
Used by: HTML/Latex converter
EOF
val = simple_hash_validator(val, :syntax_highlighter_opts)
val.keys.each do |k|
val[k.kind_of?(String) ? str_to_sym(k) : k] = val.delete(k)
end
val
end
define(:math_engine, Symbol, :mathjax, <<~EOF)
Set the math engine
Specifies the math engine that should be used for converting math
blocks/spans. If this option is set to +nil+, no math engine is used and
the math blocks/spans are output as is.
Options for the selected math engine can be set with the
math_engine_opts configuration option.
Default: mathjax
Used by: HTML converter
EOF
define(:math_engine_opts, Object, {}, <<~EOF) do |val|
Set the math engine options
Specifies options for the math engine set via the math_engine
configuration option.
The value needs to be a hash with key-value pairs that are understood by
the used math engine.
Default: {}
Used by: HTML converter
EOF
val = simple_hash_validator(val, :math_engine_opts)
val.keys.each do |k|
val[k.kind_of?(String) ? str_to_sym(k) : k] = val.delete(k)
end
val
end
define(:footnote_backlink, String, '↩', <<~EOF)
Defines the text that should be used for the footnote backlinks
The footnote backlink is just text, so any special HTML characters will
be escaped.
If the footnote backlint text is an empty string, no footnote backlinks
will be generated.
Default: '&8617;'
Used by: HTML converter
EOF
define(:footnote_backlink_inline, Boolean, false, <<~EOF)
Specifies whether the footnote backlink should always be inline
With the default of false the footnote backlink is placed at the end of
the last paragraph if there is one, or an extra paragraph with only the
footnote backlink is created.
Setting this option to true tries to place the footnote backlink in the
last, possibly nested paragraph or header. If this fails (e.g. in the
case of a table), an extra paragraph with only the footnote backlink is
created.
Default: false
Used by: HTML converter
EOF
define(:footnote_prefix, String, '', <<~EOF)
Prefix used for footnote IDs
This option can be used to set a prefix for footnote IDs. This is useful
when rendering multiple documents into the same output file to avoid
duplicate IDs. The prefix should only contain characters that are valid
in an ID!
Default: ''
Used by: HTML
EOF
define(:footnote_link_text, String, '%s', <<~EOF) do |val|
The text used for the footnote number in a footnote link
This option can be used to add additional text to the footnote
link. It should be a format string, and is passed the footnote
number as the only argument to the format string.
e.g. "[footnote %s]" would display as "[footnote 1]".
Default: '%s'
Used by: HTML
EOF
if !val.include?('%s')
raise Kramdown::Error, "option footnote_link_text needs to contain a '%s'"
end
val
end
define(:remove_line_breaks_for_cjk, Boolean, false, <<~EOF)
Specifies whether line breaks should be removed between CJK characters
Default: false
Used by: HTML converter
EOF
define(:forbidden_inline_options, Object, %w[template], <<~EOF) do |val|
Defines the options that may not be set using the {::options} extension
The value needs to be an array of option names.
Default: [template]
Used by: HTML converter
EOF
val.map! {|item| item.kind_of?(String) ? str_to_sym(item) : item }
simple_array_validator(val, :forbidden_inline_options)
end
define(:list_indent, Integer, 2, <<~EOF)
Sets the number of spaces to use for list indentation
Default: 2
Used by: Kramdown converter
EOF
end
end
kramdown-2.5.1/lib/kramdown/version.rb 0000644 0000041 0000041 00000000405 14726466654 020016 0 ustar www-data www-data # -*- coding: utf-8; frozen_string_literal: true -*-
#
#--
# Copyright (C) 2009-2019 Thomas Leitner
#
# This file is part of kramdown which is licensed under the MIT.
#++
#
module Kramdown
# The kramdown version.
VERSION = '2.5.1'
end
kramdown-2.5.1/lib/kramdown/element.rb 0000644 0000041 0000041 00000040656 14726466654 017776 0 ustar www-data www-data # -*- coding: utf-8; frozen_string_literal: true -*-
#
#--
# Copyright (C) 2009-2019 Thomas Leitner
#
# This file is part of kramdown which is licensed under the MIT.
#++
#
module Kramdown
# Represents all elements in the element tree.
#
# kramdown only uses this one class for representing all available elements in an element tree
# (paragraphs, headers, emphasis, ...). The type of element can be set via the #type accessor.
#
# The root of a kramdown element tree has to be an element of type :root. It needs to have certain
# option keys set so that conversions work correctly. If only a part of a tree should be
# converted, duplicate the root node and assign the #children appropriately, e.g:
#
# root = doc.root
# new_root = root.dup
# new_root.children = [root.children[0]] # assign new array with elements to convert
#
# Following is a description of all supported element types.
#
# Note that the option :location may contain the start line number of an element in the source
# document.
#
# == Structural Elements
#
# === :root
#
# [Category] None
# [Usage context] As the root element of a document
# [Content model] Block-level elements
#
# Represents the root of a kramdown document.
#
# The root element contains the following option keys:
#
# :encoding:: When running on Ruby 1.9 this key has to be set to the encoding used for the text
# parts of the kramdown document.
#
# :abbrev_defs:: This key may be used to store the mapping of abbreviation to abbreviation
# definition.
#
# :abbrev_attr:: This key may be used to store the mapping of abbreviation to abbreviation
# attributes.
#
# :options:: This key may be used to store options that were set during parsing of the document.
#
# :footnote_count:: This key stores the number of actually referenced footnotes of the document.
#
# === :blank
#
# [Category] Block-level element
# [Usage context] Where block-level elements are expected
# [Content model] Empty
#
# Represents one or more blank lines. It is not allowed to have two or more consecutive blank
# elements.
#
# The +value+ field may contain the original content of the blank lines.
#
#
# === :p
#
# [Category] Block-level element
# [Usage context] Where block-level elements are expected
# [Content model] Span-level elements
#
# Represents a paragraph.
#
# If the option :transparent is +true+, this element just represents a block of text. I.e. this
# element just functions as a container for span-level elements.
#
#
# === :header
#
# [Category] Block-level element
# [Usage context] Where block-level elements are expected
# [Content model] Span-level elements
#
# Represents a header.
#
# The option :level specifies the header level and has to contain a number between 1 and \6. The
# option :raw_text has to contain the raw header text.
#
#
# === :blockquote
#
# [Category] Block-level element
# [Usage context] Where block-level elements are expected
# [Content model] Block-level elements
#
# Represents a blockquote.
#
#
# === :codeblock
#
# [Category] Block-level element
# [Usage context] Where block-level elements are expected
# [Content model] Empty
#
# Represents a code block, i.e. a block of text that should be used as-is.
#
# The +value+ field has to contain the content of the code block.
#
# The option :lang specifies a highlighting language with possible HTML style options (e.g.
# php?start_inline=1) and should be used instead of a possibly also available language embedded in
# a class name of the form 'language-LANG'.
#
#
# === :ul
#
# [Category] Block-level element
# [Usage context] Where block-level elements are expected
# [Content model] One or more :li elements
#
# Represents an unordered list.
#
#
# === :ol
#
# [Category] Block-level element
# [Usage context] Where block-level elements are expected
# [Content model] One or more :li elements
#
# Represents an ordered list.
#
#
# === :li
#
# [Category] Block-level element
# [Usage context] Inside :ol and :ul elements
# [Content model] Block-level elements
#
# Represents a list item of an ordered or unordered list.
#
# Note that the first child of a list item must not be a :blank element!
#
#
# === :dl
#
# [Category] Block-level element
# [Usage context] Where block-level elements are expected
# [Content model] One or more groups each consisting of one or more :dt elements followed by one
# or more :dd elements.
#
# Represents a definition list which contains groups consisting of terms and definitions for them.
#
#
# === :dt
#
# [Category] Block-level element
# [Usage context] Before :dt or :dd elements inside a :dl elment
# [Content model] Span-level elements
#
# Represents the term part of a term-definition group in a definition list.
#
#
# === :dd
#
# [Category] Block-level element
# [Usage context] After :dt or :dd elements inside a :dl elment
# [Content model] Block-level elements
#
# Represents the definition part of a term-definition group in a definition list.
#
#
# === :hr
#
# [Category] Block-level element
# [Usage context] Where block-level elements are expected
# [Content model] None
#
# Represents a horizontal line.
#
#
# === :table
#
# [Category] Block-level element
# [Usage context] Where block-level elements are expected
# [Content model] Zero or one :thead elements, one or more :tbody elements, zero or one :tfoot
# elements
#
# Represents a table. Each table row (i.e. :tr element) of the table has to contain the same
# number of :td elements.
#
# The option :alignment has to be an array containing the alignment values, exactly one for each
# column of the table. The possible alignment values are :left, :center, :right and :default.
#
#
# === :thead
#
# [Category] None
# [Usage context] As first element inside a :table element
# [Content model] One or more :tr elements
#
# Represents the table header.
#
#
# === :tbody
#
# [Category] None
# [Usage context] After a :thead element but before a :tfoot element inside a :table element
# [Content model] One or more :tr elements
#
# Represents a table body.
#
#
# === :tfoot
#
# [Category] None
# [Usage context] As last element inside a :table element
# [Content model] One or more :tr elements
#
# Represents the table footer.
#
#
# === :tr
#
# [Category] None
# [Usage context] Inside :thead, :tbody and :tfoot elements
# [Content model] One or more :td elements
#
# Represents a table row.
#
#
# === :td
#
# [Category] Block-level element
# [Usage context] Inside :tr elements
# [Content model] As child of :thead/:tr span-level elements, as child of :tbody/:tr and
# :tfoot/:tr block-level elements
#
# Represents a table cell.
#
#
# === :math
#
# [Category] Block/span-level element
# [Usage context] Where block/span-level elements are expected
# [Content model] None
#
# Represents mathematical text that is written in LaTeX.
#
# The +value+ field has to contain the actual mathematical text.
#
# The option :category has to be set to either :span or :block depending on the context where the
# element is used.
#
#
# == Text Markup Elements
#
# === :text
#
# [Category] Span-level element
# [Usage context] Where span-level elements are expected
# [Content model] None
#
# Represents text.
#
# The +value+ field has to contain the text itself.
#
#
# === :br
#
# [Category] Span-level element
# [Usage context] Where span-level elements are expected
# [Content model] None
#
# Represents a hard line break.
#
#
# === :a
#
# [Category] Span-level element
# [Usage context] Where span-level elements are expected
# [Content model] Span-level elements
#
# Represents a link to an URL.
#
# The attribute +href+ has to be set to the URL to which the link points. The attribute +title+
# optionally contains the title of the link.
#
#
# === :img
#
# [Category] Span-level element
# [Usage context] Where span-level elements are expected
# [Content model] None
#
# Represents an image.
#
# The attribute +src+ has to be set to the URL of the image. The attribute +alt+ has to contain a
# text description of the image. The attribute +title+ optionally contains the title of the image.
#
#
# === :codespan
#
# [Category] Span-level element
# [Usage context] Where span-level elements are expected
# [Content model] None
#
# Represents verbatim text.
#
# The +value+ field has to contain the content of the code span.
#
#
# === :footnote
#
# [Category] Span-level element
# [Usage context] Where span-level elements are expected
# [Content model] None
#
# Represents a footnote marker.
#
# The +value+ field has to contain an element whose children are the content of the footnote. The
# option :name has to contain a valid and unique footnote name. A valid footnote name consists of
# a word character or a digit and then optionally followed by other word characters, digits or
# dashes.
#
#
# === :em
#
# [Category] Span-level element
# [Usage context] Where span-level elements are expected
# [Content model] Span-level elements
#
# Represents emphasis of its contents.
#
#
# === :strong
#
# [Category] Span-level element
# [Usage context] Where span-level elements are expected
# [Content model] Span-level elements
#
# Represents strong importance for its contents.
#
#
# === :entity
#
# [Category] Span-level element
# [Usage context] Where span-level elements are expected
# [Content model] None
#
# Represents an HTML entity.
#
# The +value+ field has to contain an instance of Kramdown::Utils::Entities::Entity. The option
# :original can be used to store the original representation of the entity.
#
#
# === :typographic_sym
#
# [Category] Span-level element
# [Usage context] Where span-level elements are expected
# [Content model] None
#
# Represents a typographic symbol.
#
# The +value+ field needs to contain a Symbol representing the specific typographic symbol from
# the following list:
#
# :mdash:: An mdash character (---)
# :ndash:: An ndash character (--)
# :hellip:: An ellipsis (...)
# :laquo:: A left guillemet (<<)
# :raquo:: A right guillemet (>>)
# :laquo_space:: A left guillemet with a space (<< )
# :raquo_space:: A right guillemet with a space ( >>)
#
#
# === :smart_quote
#
# [Category] Span-level element
# [Usage context] Where span-level elements are expected
# [Content model] None
#
# Represents a quotation character.
#
# The +value+ field needs to contain a Symbol representing the specific quotation character:
#
# :lsquo:: Left single quote
# :rsquo:: Right single quote
# :ldquo:: Left double quote
# :rdquo:: Right double quote
#
#
# === :abbreviation
#
# [Category] Span-level element
# [Usage context] Where span-level elements are expected
# [Content model] None
#
# Represents a text part that is an abbreviation.
#
# The +value+ field has to contain the text part that is the abbreviation. The definition of the
# abbreviation is stored in the :root element of the document.
#
#
# == Other Elements
#
# === :html_element
#
# [Category] Block/span-level element
# [Usage context] Where block/span-level elements or raw HTML elements are expected
# [Content model] Depends on the element
#
# Represents an HTML element.
#
# The +value+ field has to contain the name of the HTML element the element is representing.
#
# The option :category has to be set to either :span or :block depending on the whether the
# element is a block-level or a span-level element. The option :content_model has to be set to the
# content model for the element (either :block if it contains block-level elements, :span if it
# contains span-level elements or :raw if it contains raw content).
#
#
# === :xml_comment
#
# [Category] Block/span-level element
# [Usage context] Where block/span-level elements are expected or in raw HTML elements
# [Content model] None
#
# Represents an XML/HTML comment.
#
# The +value+ field has to contain the whole XML/HTML comment including the delimiters.
#
# The option :category has to be set to either :span or :block depending on the context where the
# element is used.
#
#
# === :xml_pi
#
# [Category] Block/span-level element
# [Usage context] Where block/span-level elements are expected or in raw HTML elements
# [Content model] None
#
# Represents an XML/HTML processing instruction.
#
# The +value+ field has to contain the whole XML/HTML processing instruction including the
# delimiters.
#
# The option :category has to be set to either :span or :block depending on the context where the
# element is used.
#
#
# === :comment
#
# [Category] Block/span-level element
# [Usage context] Where block/span-level elements are expected
# [Content model] None
#
# Represents a comment.
#
# The +value+ field has to contain the comment.
#
# The option :category has to be set to either :span or :block depending on the context where the
# element is used. If it is set to :span, then no blank lines are allowed in the comment.
#
#
# === :raw
#
# [Category] Block/span-level element
# [Usage context] Where block/span-level elements are expected
# [Content model] None
#
# Represents a raw string that should not be modified. For example, the element could contain some
# HTML code that should be output as-is without modification and escaping.
#
# The +value+ field has to contain the actual raw text.
#
# The option :category has to be set to either :span or :block depending on the context where the
# element is used. If it is set to :span, then no blank lines are allowed in the raw text.
#
# The option :type can be set to an array of strings to define for which converters the raw string
# is valid.
#
class Element
# A symbol representing the element type. For example, :p or :blockquote.
attr_accessor :type
# The value of the element. The interpretation of this field depends on the type of the element.
# Many elements don't use this field.
attr_accessor :value
# The child elements of this element.
attr_accessor :children
# Create a new Element object of type +type+. The optional parameters +value+, +attr+ and
# +options+ can also be set in this constructor for convenience.
def initialize(type, value = nil, attr = nil, options = nil)
@type, @value, @attr, @options = type, value, attr, options
@children = []
end
# The attributes of the element.
def attr
@attr ||= {}
end
# The options hash for the element. It is used for storing arbitray options.
def options
@options ||= {}
end
def inspect # :nodoc:
"