Moved vim-markdown-preview to submodule

This commit is contained in:
yan 2011-12-07 00:44:28 -08:00 committed by Yan Pritzker
parent a7daefcb86
commit 9cb89b4179
51 changed files with 5 additions and 6135 deletions

3
.gitmodules vendored
View File

@ -31,3 +31,6 @@
[submodule "vim/bundle/tomtom-tcomment_vim"]
path = vim/bundle/tomtom-tcomment_vim
url = https://github.com/tomtom/tcomment_vim.git
[submodule "vim/bundle/nelstrom-vim-markdown-preview"]
path = vim/bundle/nelstrom-vim-markdown-preview
url = https://github.com/nelstrom/vim-markdown-preview

View File

@ -145,6 +145,7 @@ Included vim plugins
* vim-ruby-conque - helpers to run ruby,rspec,rake within ConqueTerm - use \rr (ruby), \ss (rspec), \ll (rspec line), \RR (rake)
* AnsiEsc - inteprets ansi color codes inside log files. great for looking at Rails logs
* ruby-debug-ide - not quite working for me, but maybe it will for you. supposedly a graphical debugger you can step through
* tComment - gcc to comment a line, gcp to comment blocks, nuff said
Adding your own vim plugins
---

@ -0,0 +1 @@
Subproject commit 98844ac2d201fc4e9cfa0a43dfdba6f8dd39a181

View File

@ -1,44 +0,0 @@
body div#content {
margin : 0 auto;
width : 920px;
background-color : #f8f8f8;
padding : .7em;
font-size : 13.34px;
font-family : verdana, sans-serif;
border : 1px #E0E0E0 solid;
}
body div#content h2, body div#content h3, body div#content h4 {
padding-top : 10px;
border-top : 4px solid #E0E0E0;
}
body div#content pre {
padding : 5px;
border-style : solid;
border-width : 1px;
border-color : #E0E0E0;
background-color : #F8F8FF;
}
body div#content pre code {
padding : 5px;
background-color : #F8F8FF;
border : none;
}
body div#content code {
font-family : courier, fixed;
display : inline-block;
padding : 0px 2px 0px 2px;
background-color : #F8F8FF;
border : 1px #E0E0E0 solid;
}
body h4#title {
font-family : verdana, sans-serif;
display : block;
margin : 0 auto;
width : 920px;
}

View File

@ -1,24 +0,0 @@
kramdown - fast, pure-Ruby Markdown-superset converter
Copyright (C) 2009 Thomas Leitner <t_leitner@gmx.at>
kramdown is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
Some test cases and the benchmark files are based on test cases from
the MDTest test suite:
MDTest
Copyright (c) 2007 Michel Fortin
<http://www.michelf.com/>

View File

@ -1,34 +0,0 @@
# -*- coding: utf-8 -*-
#
#--
# Copyright (C) 2009-2010 Thomas Leitner <t_leitner@gmx.at>
#
# This file is part of kramdown.
#
# kramdown is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#++
#
# All the code in this file is backported from Ruby 1.8.7 sothat kramdown works under 1.8.5
if RUBY_VERSION == '1.8.5'
require 'rexml/parsers/baseparser'
module REXML
module Parsers
class BaseParser
UNAME_STR= "(?:#{NCNAME_STR}:)?#{NCNAME_STR}"
end
end
end
end

View File

@ -1,42 +0,0 @@
# -*- coding: utf-8 -*-
#
#--
# Copyright (C) 2009-2010 Thomas Leitner <t_leitner@gmx.at>
#
# This file is part of kramdown.
#
# kramdown is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#++
#
module Kramdown
# == Converter Module
#
# This module contains all available converters, i.e. classes that take a document and convert the
# document tree to a specific output format, normally a string. For example, the Html module
# converts the document tree into HTML.
#
# Converters use the Base class for common functionality (like applying a template to the output)-
# see its API documentation for how to create a converter class.
module Converter
autoload :Base, 'kramdown/converter/base'
autoload :Html, 'kramdown/converter/html'
autoload :Latex, 'kramdown/converter/latex'
autoload :Kramdown, 'kramdown/converter/kramdown'
end
end

View File

@ -1,111 +0,0 @@
# -*- coding: utf-8 -*-
#
#--
# Copyright (C) 2009-2010 Thomas Leitner <t_leitner@gmx.at>
#
# This file is part of kramdown.
#
# kramdown is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#++
#
require 'erb'
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).
#
# == Implementing a converter
#
# Implementing a new converter is rather easy: just create a new sub 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(tree) method which takes a document
# tree and should return the converted output.
#
# The document instance is automatically set as @doc in Base#initialize. Furthermore, the
# document instance provides a hash called `conversion_infos` that is also automatically cleared
# and can be used to store information about the conversion process.
#
# The actual transformation of the document tree can be done in any way. However, writing one
# method per tree element type is a straight forward way to do it - this is how the Html and
# Latex converters do the transformation.
class Base
# Initialize the converter with the given Kramdown document +doc+.
def initialize(doc)
@doc = doc
@doc.conversion_infos.clear
end
private_class_method(:new, :allocate)
# Convert the Kramdown document +doc+ to the output format implemented by a subclass.
#
# Initializes a new instance of the calling class and then calls the #convert method that must
# be implemented by each subclass. If the +template+ option is specified and non-empty, the
# result is rendered into the specified template.
def self.convert(doc)
result = new(doc).convert(doc.tree)
result = apply_template(doc, result) if !doc.options[:template].empty?
result
end
# Apply the template specified in the +doc+ options, using +body+ as the body string.
def self.apply_template(doc, body)
erb = ERB.new(get_template(doc.options[:template]))
obj = Object.new
obj.instance_variable_set(:@doc, doc)
obj.instance_variable_set(:@body, body)
erb.result(obj.instance_eval{binding})
end
# Return the template specified by +template+.
def self.get_template(template)
format_ext = '.' + self.name.split(/::/).last.downcase
shipped = File.join(::Kramdown.data_dir, template + format_ext)
if File.exist?(template)
File.read(template)
elsif File.exist?(template + format_ext)
File.read(template + format_ext)
elsif File.exist?(shipped)
File.read(shipped)
else
raise "The specified template file #{template} does not exist"
end
end
# Generate an unique alpha-numeric ID from the the string +str+ for use as header ID.
def generate_id(str)
gen_id = str.gsub(/[^a-zA-Z0-9 -]/, '').gsub(/^[^a-zA-Z]*/, '').gsub(' ', '-').downcase
gen_id = 'section' if gen_id.length == 0
@used_ids ||= {}
if @used_ids.has_key?(gen_id)
gen_id += '-' + (@used_ids[gen_id] += 1).to_s
else
@used_ids[gen_id] = 0
end
@doc.options[:auto_id_prefix] + gen_id
end
end
end
end

View File

@ -1,391 +0,0 @@
# -*- coding: utf-8 -*-
#
#--
# Copyright (C) 2009-2010 Thomas Leitner <t_leitner@gmx.at>
#
# This file is part of kramdown.
#
# kramdown is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#++
#
require 'rexml/parsers/baseparser'
module Kramdown
module Converter
# Converts a Kramdown::Document to HTML.
class Html < Base
include ::Kramdown::Utils::HTML
# DEPRECATED: use #html_attributes
def options_for_element(el)
warn("DEPRECATION WARNING: this method will be deprecated in the next release, use #html_attributes instead")
html_attributes(el)
end
# :stopdoc:
# Defines the amount of indentation used when nesting HTML tags.
INDENTATION = 2
begin
require 'coderay'
# Highlighting via coderay is available if this constant is +true+.
HIGHLIGHTING_AVAILABLE = true
rescue LoadError => e
HIGHLIGHTING_AVAILABLE = false
end
# Initialize the HTML converter with the given Kramdown document +doc+.
def initialize(doc)
super
@footnote_counter = @footnote_start = @doc.options[:footnote_nr]
@footnotes = []
@toc = []
@toc_code = nil
end
def convert(el, indent = -INDENTATION, opts = {})
send("convert_#{el.type}", el, indent, opts)
end
def inner(el, indent, opts)
result = ''
indent += INDENTATION
el.children.each do |inner_el|
result << send("convert_#{inner_el.type}", inner_el, indent, opts)
end
result
end
def convert_blank(el, indent, opts)
"\n"
end
def convert_text(el, indent, opts)
escape_html(el.value, :text)
end
def convert_p(el, indent, opts)
if el.options[:transparent]
"#{inner(el, indent, opts)}"
else
"#{' '*indent}<p#{html_attributes(el)}>#{inner(el, indent, opts)}</p>\n"
end
end
def convert_codeblock(el, indent, opts)
if el.options[:attr] && el.options[:attr]['lang'] && HIGHLIGHTING_AVAILABLE
el = Marshal.load(Marshal.dump(el)) # so that the original is not changed
opts = {:wrap => @doc.options[:coderay_wrap], :line_numbers => @doc.options[:coderay_line_numbers],
:line_number_start => @doc.options[:coderay_line_number_start], :tab_width => @doc.options[:coderay_tab_width],
:bold_every => @doc.options[:coderay_bold_every], :css => @doc.options[:coderay_css]}
result = CodeRay.scan(el.value, el.options[:attr].delete('lang').to_sym).html(opts).chomp + "\n"
"#{' '*indent}<div#{html_attributes(el)}>#{result}#{' '*indent}</div>\n"
else
result = escape_html(el.value)
if el.options[:attr] && el.options[:attr].has_key?('class') && el.options[:attr]['class'] =~ /\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 "<span class=\"ws-tab#{suffix}\">\t</span>"
when " " then "<span class=\"ws-space#{suffix}\">&#8901;</span>"
end
end.join('')
end
end
"#{' '*indent}<pre#{html_attributes(el)}><code>#{result}#{result =~ /\n\Z/ ? '' : "\n"}</code></pre>\n"
end
end
def convert_blockquote(el, indent, opts)
"#{' '*indent}<blockquote#{html_attributes(el)}>\n#{inner(el, indent, opts)}#{' '*indent}</blockquote>\n"
end
def convert_header(el, indent, opts)
el = Marshal.load(Marshal.dump(el)) # so that the original is not changed
if @doc.options[:auto_ids] && !(el.options[:attr] && el.options[:attr]['id'])
(el.options[:attr] ||= {})['id'] = generate_id(el.options[:raw_text])
end
@toc << [el.options[:level], el.options[:attr]['id'], el.children] if el.options[:attr] && el.options[:attr]['id'] && within_toc_depth?(el)
"#{' '*indent}<h#{el.options[:level]}#{html_attributes(el)}>#{inner(el, indent, opts)}</h#{el.options[:level]}>\n"
end
def within_toc_depth?(el)
@doc.options[:toc_depth] <= 0 || el.options[:level] <= @doc.options[:toc_depth]
end
def convert_hr(el, indent, opts)
"#{' '*indent}<hr />\n"
end
def convert_ul(el, indent, opts)
if !@toc_code && (el.options[:ial][:refs].include?('toc') rescue nil) && (el.type == :ul || el.type == :ol)
@toc_code = [el.type, el.options[:attr], (0..128).to_a.map{|a| rand(36).to_s(36)}.join]
@toc_code.last
else
"#{' '*indent}<#{el.type}#{html_attributes(el)}>\n#{inner(el, indent, opts)}#{' '*indent}</#{el.type}>\n"
end
end
alias :convert_ol :convert_ul
alias :convert_dl :convert_ul
def convert_li(el, indent, opts)
output = ' '*indent << "<#{el.type}" << html_attributes(el) << ">"
res = inner(el, indent, opts)
if el.children.empty? || (el.children.first.type == :p && el.children.first.options[:transparent])
output << res << (res =~ /\n\Z/ ? ' '*indent : '')
else
output << "\n" << res << ' '*indent
end
output << "</#{el.type}>\n"
end
alias :convert_dd :convert_li
def convert_dt(el, indent, opts)
"#{' '*indent}<dt#{html_attributes(el)}>#{inner(el, indent, opts)}</dt>\n"
end
HTML_TAGS_WITH_BODY=['div', 'script']
def convert_html_element(el, indent, opts)
res = inner(el, indent, opts)
if el.options[:category] == :span
"<#{el.value}#{html_attributes(el)}" << (!res.empty? ? ">#{res}</#{el.value}>" : " />")
else
output = ''
output << ' '*indent if !el.options[:parent_is_raw]
output << "<#{el.value}#{html_attributes(el)}"
if !res.empty? && el.options[:parse_type] != :block
output << ">#{res}</#{el.value}>"
elsif !res.empty?
output << ">\n#{res}" << ' '*indent << "</#{el.value}>"
elsif HTML_TAGS_WITH_BODY.include?(el.value)
output << "></#{el.value}>"
else
output << " />"
end
output << "\n" if el.options[:outer_element] || !el.options[:parent_is_raw]
output
end
end
def convert_xml_comment(el, indent, opts)
if el.options[:category] == :block && !el.options[:parent_is_raw]
' '*indent + el.value + "\n"
else
el.value
end
end
alias :convert_xml_pi :convert_xml_comment
alias :convert_html_doctype :convert_xml_comment
def convert_table(el, indent, opts)
if el.options[:alignment].all? {|a| a == :default}
alignment = ''
else
alignment = el.options[:alignment].map do |a|
"#{' '*(indent + INDENTATION)}" + (a == :default ? "<col />" : "<col align=\"#{a}\" />") + "\n"
end.join('')
end
"#{' '*indent}<table#{html_attributes(el)}>\n#{alignment}#{inner(el, indent, opts)}#{' '*indent}</table>\n"
end
def convert_thead(el, indent, opts)
"#{' '*indent}<#{el.type}#{html_attributes(el)}>\n#{inner(el, indent, opts)}#{' '*indent}</#{el.type}>\n"
end
alias :convert_tbody :convert_thead
alias :convert_tfoot :convert_thead
alias :convert_tr :convert_thead
def convert_td(el, indent, opts)
res = inner(el, indent, opts)
"#{' '*indent}<#{el.type}#{html_attributes(el)}>#{res.empty? ? "&nbsp;" : res}</#{el.type}>\n"
end
alias :convert_th :convert_td
def convert_comment(el, indent, opts)
if el.options[:category] == :block
"#{' '*indent}<!-- #{el.value} -->\n"
else
"<!-- #{el.value} -->"
end
end
def convert_br(el, indent, opts)
"<br />"
end
def convert_a(el, indent, opts)
do_obfuscation = el.options[:attr]['href'] =~ /^mailto:/
if do_obfuscation
el = Marshal.load(Marshal.dump(el)) # so that the original is not changed
href = obfuscate(el.options[:attr]['href'].sub(/^mailto:/, ''))
mailto = obfuscate('mailto')
el.options[:attr]['href'] = "#{mailto}:#{href}"
end
res = inner(el, indent, opts)
res = obfuscate(res) if do_obfuscation
"<a#{html_attributes(el)}>#{res}</a>"
end
def convert_img(el, indent, opts)
"<img#{html_attributes(el)} />"
end
def convert_codespan(el, indent, opts)
"<code#{html_attributes(el)}>#{escape_html(el.value)}</code>"
end
def convert_footnote(el, indent, opts)
number = @footnote_counter
@footnote_counter += 1
@footnotes << [el.options[:name], @doc.parse_infos[:footnotes][el.options[:name]]]
"<sup id=\"fnref:#{el.options[:name]}\"><a href=\"#fn:#{el.options[:name]}\" rel=\"footnote\">#{number}</a></sup>"
end
def convert_raw(el, indent, opts)
el.value + (el.options[:category] == :block ? "\n" : '')
end
def convert_em(el, indent, opts)
"<#{el.type}#{html_attributes(el)}>#{inner(el, indent, opts)}</#{el.type}>"
end
alias :convert_strong :convert_em
def convert_entity(el, indent, opts)
entity_to_str(el.value)
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')]
}
def convert_typographic_sym(el, indent, opts)
TYPOGRAPHIC_SYMS[el.value].map {|e| entity_to_str(e)}.join('')
end
def convert_smart_quote(el, indent, opts)
entity_to_str(::Kramdown::Utils::Entities.entity(el.value.to_s))
end
def convert_math(el, indent, opts)
el = Marshal.load(Marshal.dump(el)) # so that the original is not changed
el.options[:attr] ||= {}
el.options[:attr]['class'] ||= ''
el.options[:attr]['class'] += (el.options[:attr]['class'].empty? ? '' : ' ') + 'math'
type = 'span'
type = 'div' if el.options[:category] == :block
"<#{type}#{html_attributes(el)}>#{escape_html(el.value)}</#{type}>#{type == 'div' ? "\n" : ''}"
end
def convert_abbreviation(el, indent, opts)
title = @doc.parse_infos[:abbrev_defs][el.value]
title = nil if title.empty?
"<abbr#{title ? " title=\"#{title}\"" : ''}>#{el.value}</abbr>"
end
def convert_root(el, indent, opts)
result = inner(el, indent, opts)
result << footnote_content
if @toc_code
toc_tree = generate_toc_tree(@toc, @toc_code[0], @toc_code[1] || {})
text = if toc_tree.children.size > 0
convert(toc_tree, 0)
else
''
end
result.sub!(/#{@toc_code.last}/, text)
end
result
end
def generate_toc_tree(toc, type, attr)
sections = Element.new(type, nil, {:attr => {'id' => 'markdown-toc'}.merge(attr)})
stack = []
toc.each do |level, id, children|
li = Element.new(:li, nil, {:level => level})
li.children << Element.new(:p, nil, {:transparent => true})
a = Element.new(:a, nil, {:attr => {:href => "##{id}"}})
a.children += children
li.children.last.children << a
li.children << Element.new(type)
success = false
while !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 unless item.children.last.children.size > 0
end
end
end
while !stack.empty?
item = stack.pop
item.children.pop unless item.children.last.children.size > 0
end
sections
end
# Helper method for obfuscating the +text+ by using HTML entities.
def obfuscate(text)
result = ""
text.each_byte do |b|
result += (b > 128 ? b.chr : "&#%03d;" % b)
end
result.force_encoding(text.encoding) if RUBY_VERSION >= '1.9'
result
end
# Return a HTML list with the footnote content for the used footnotes.
def footnote_content
ol = Element.new(:ol)
ol.options[:attr] = {'start' => @footnote_start} if @footnote_start != 1
@footnotes.each do |name, data|
li = Element.new(:li, nil, {:attr => {:id => "fn:#{name}"}, :first_is_block => true})
li.children = Marshal.load(Marshal.dump(data[:content].children)) #TODO: probably remove this!!!!
ol.children << li
ref = Element.new(:raw, "<a href=\"#fnref:#{name}\" rev=\"footnote\">&#8617;</a>")
if li.children.last.type == :p
para = li.children.last
else
li.children << (para = Element.new(:p))
end
para.children << ref
end
(ol.children.empty? ? '' : "<div class=\"footnotes\">\n#{convert(ol, 2)}</div>\n")
end
end
end
end

View File

@ -1,398 +0,0 @@
# -*- coding: utf-8 -*-
#
#--
# Copyright (C) 2009-2010 Thomas Leitner <t_leitner@gmx.at>
#
# This file is part of kramdown.
#
# kramdown is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#++
#
require 'rexml/parsers/baseparser'
module Kramdown
module Converter
# Converts a Kramdown::Document to the kramdown format.
class Kramdown < Base
# :stopdoc:
include ::Kramdown::Utils::HTML
def initialize(doc)
super
@linkrefs = []
@footnotes = []
@abbrevs = []
@stack = []
end
def convert(el, opts = {})
res = send("convert_#{el.type}", el, opts)
if el.type != :html_element && el.type != :li && el.type != :dd && (ial = ial_for_element(el))
res << ial
res << "\n\n" if el.options[:category] == :block
end
res
end
def inner(el, opts = {})
@stack.push([el, opts])
result = ''
el.children.each_with_index do |inner_el, index|
options = opts.dup
#p [index, inner_el]
options[:index] = index
options[:prev] = (index == 0 ? nil : el.children[index-1])
options[:next] = (index == el.children.length - 1 ? nil : el.children[index+1])
result << convert(inner_el, options)
end
@stack.pop
result
end
def convert_blank(el, opts)
"\n"
end
ESCAPED_CHAR_RE = /(\$\$|[\\*_`\[\]\{\}"'])|^[ ]{0,3}(:)/
def convert_text(el, opts)
if opts[:raw_text]
el.value
else
nl = (el.value =~ /\n$/)
el.value.gsub(/\s+/, ' ').gsub(ESCAPED_CHAR_RE) { "\\#{$1 || $2}" } + (nl ? "\n" : '')
end
end
def convert_p(el, opts)
res = inner(el, opts).strip.gsub(/\A(?:([#|])|(\d+)\.|([+-]\s))/) do
$1 || $3 ? "\\#{$1 || $3}" : "#{$2}\\."
end + "\n"
if opts[:next] && opts[:next].type == :p && !ial_for_element(el)
res += "\n"
end
res
end
CODEBLOCK_PREV_EL = [:ul, :ol, :dl, :codeblock]
def convert_codeblock(el, opts)
res = ''
res << "^\n" if opts[:prev] && ((CODEBLOCK_PREV_EL.include?(opts[:prev].type) && !ial_for_element(opts[:prev])) ||
(opts[:prev].type == :blank &&
opts[:index]-2 >= 0 &&
(tmp = @stack.last.first.children[opts[:index]-2]) &&
CODEBLOCK_PREV_EL.include?(tmp.type) && !ial_for_element(tmp)))
res << el.value.split(/\n/).map {|l| l.empty? ? " " : " #{l}"}.join("\n") + "\n"
end
def convert_blockquote(el, opts)
res = ''
res << "\n" if opts[:prev] && opts[:prev].type == :blockquote
res << inner(el, opts).chomp.split(/\n/).map {|l| "> #{l}"}.join("\n") << "\n"
end
def convert_header(el, opts)
res = ''
res << "\n" if opts[:prev] && opts[:prev].type != :blank
res << "#{'#' * el.options[:level]} #{inner(el, opts)}"
res << " {##{el.options[:attr]['id']}}" if el.options[:attr] && el.options[:attr]['id']
res << "\n" if opts[:next] && opts[:next].type != :blank
res << "\n"
end
def convert_hr(el, opts)
"* * *\n"
end
def convert_ul(el, opts)
res = ''
res << "\n" if opts[:prev] && (opts[:prev].type == :p && !opts[:prev].options[:transparent])
res << "^\n" if opts[:prev] && ((opts[:prev].type == el.type && !ial_for_element(opts[:prev])) ||
(opts[:prev].type == :blank && opts[:index]-2 >= 0 &&
(tmp = @stack.last.first.children[opts[:index]-2]) &&
tmp.type == el.type && !ial_for_element(tmp)))
res + 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.first.type == :ul
['* ', el.children.first.type == :codeblock ? 4 : 2]
else
["#{opts[:index] + 1}.".ljust(4), 4]
end
if ial = ial_for_element(el)
sym += ial + " "
end
first, *last = inner(el, opts).chomp.split(/\n/)
last = last.map {|l| " "*width + l}.join("\n")
last = last.empty? ? "\n" : "\n#{last}\n"
if el.children.first.type == :p && !el.children.first.options[:transparent]
res = "#{sym}#{first}\n#{last}"
res << "^\n" if el.children.size == 1 && @stack.last.first.children.last == el &&
(@stack.last.first.children.any? {|c| c.children.first.type != :p} || @stack.last.first.children.size == 1)
res
elsif el.children.first.type == :codeblock
"#{sym}\n #{first}#{last}"
else
"#{sym}#{first}#{last}"
end
end
def convert_dd(el, opts)
sym, width = ": ", (el.children.first.type == :codeblock ? 4 : 2)
if ial = ial_for_element(el)
sym += ial + " "
end
first, *last = inner(el, opts).chomp.split(/\n/)
last = last.map {|l| " "*width + l}.join("\n")
text = first + (last.empty? ? '' : "\n" + last)
if el.children.first.type == :p && !el.children.first.options[:transparent]
"\n#{sym}#{text}\n"
elsif el.children.first.type == :codeblock
"#{sym}\n #{text}\n"
else
"#{sym}#{text}\n"
end
end
def convert_dt(el, opts)
res = ''
res << inner(el, opts) << "\n"
end
HTML_TAGS_WITH_BODY=['div', 'script']
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.options[:category] == :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)}" << (!res.empty? ? ">#{res}</#{el.value}>" : " />")
else
output = ''
output << "<#{el.value}#{html_attributes(el)}"
output << " markdown=\"1\"" if markdown_attr
if !res.empty? && el.options[:parse_type] != :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 el.options[:outer_element] || !el.options[:parent_is_raw]
output
end
end
def convert_xml_comment(el, opts)
if el.options[:category] == :block && !el.options[:parent_is_raw]
el.value + "\n"
else
el.value
end
end
alias :convert_xml_pi :convert_xml_comment
alias :convert_html_doctype :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? {|a| a == :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).gsub(/\|/, '\\|')
end
alias :convert_th :convert_td
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)
" \n"
end
def convert_a(el, opts)
if el.options[:attr]['href'].empty?
"[#{inner(el, opts)}]()"
else
@linkrefs << el
"[#{inner(el, opts)}][#{@linkrefs.size}]"
end
end
def convert_img(el, opts)
title = (el.options[:attr]['title'] ? ' "' + el.options[:attr]['title'].gsub(/"/, "&quot;") + '"' : '')
"![#{el.options[:attr]['alt']}](<#{el.options[:attr]['src']}>#{title})"
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], @doc.parse_infos[:footnotes][el.options[:name]]]
"[^#{el.options[:name]}]"
end
def convert_raw(el, opts)
if @stack.last.first.type == :html_element
el.value
elsif el.options[:category] == :block
"{::nomarkdown}\n#{el.value}\n{:/}\n"
else
"{::nomarkdown}#{el.value}{:/}"
end
end
def convert_em(el, opts)
"*#{inner(el, opts)}*"
end
def convert_strong(el, opts)
"**#{inner(el, opts)}**"
end
def convert_entity(el, opts)
entity_to_str(el.value)
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 =~ /[rl]dquo/ ? "\"" : "'"
end
def convert_math(el, opts)
(@stack.last.first.type == :p && opts[:prev].nil? ? "\\" : '') + "$$#{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" if @linkrefs.size > 0
@linkrefs.each_with_index do |el, i|
link = (el.type == :a ? el.options[:attr]['href'] : el.options[:attr]['src'])
link = "<#{link}>" if link =~ / /
title = el.options[:attr]['title']
res << "[#{i+1}]: #{link} #{title ? '"' + title.gsub(/"/, "&quot;") + '"' : ''}\n"
end
res
end
def create_footnote_defs
res = ''
res = "\n" if @footnotes.size > 0
@footnotes.each do |name, data|
res << "\n[^#{name}]:\n"
res << inner(data[:content]).chomp.split(/\n/).map {|l| " #{l}"}.join("\n")
end
res
end
def create_abbrev_defs
return '' unless @doc.parse_infos[:abbrev_defs]
res = ''
@doc.parse_infos[:abbrev_defs].each do |name, text|
res << "*[#{name}]: #{text}\n"
end
res
end
# Return the IAL containing the attributes of the element +el+.
def ial_for_element(el)
res = (el.options[: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.nil? ? '' : " #{k}=\"#{v.to_s}\""
end.compact.sort.join('')
res = "toc" + (res.strip.empty? ? '' : " #{res}") if (el.type == :ul || el.type == :ol) &&
(el.options[:ial][:refs].include?('toc') rescue nil)
res.strip.empty? ? nil : "{:#{res}}"
end
end
end
end

View File

@ -1,553 +0,0 @@
# -*- coding: utf-8 -*-
#
#--
# Copyright (C) 2009-2010 Thomas Leitner <t_leitner@gmx.at>
#
# This file is part of kramdown.
#
# kramdown is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#++
#
require 'set'
module Kramdown
module Converter
# Converts a Kramdown::Document to LaTeX. This converter uses ideas from other Markdown-to-LaTeX
# converters like Pandoc and Maruku.
class Latex < Base
# :stopdoc:
# Initialize the LaTeX converter with the given Kramdown document +doc+.
def initialize(doc)
super
#TODO: set the footnote counter at the beginning of the document
@doc.options[:footnote_nr]
@doc.conversion_infos[:packages] = Set.new
end
def convert(el, opts = {})
send("convert_#{el.type}", el, opts)
end
def inner(el, opts)
result = ''
el.children.each do |inner_el|
result << send("convert_#{inner_el.type}", inner_el, opts)
end
result
end
def convert_root(el, opts)
inner(el, opts)
end
def convert_blank(el, opts)
""
end
def convert_text(el, opts)
escape(el.value)
end
def convert_p(el, opts)
"#{inner(el, opts)}\n\n"
end
def convert_codeblock(el, opts)
show_whitespace = el.options[:attr] && el.options[:attr]['class'].to_s =~ /\bshow-whitespaces\b/
lang = el.options[:attr] && el.options[:attr]['lang']
if show_whitespace || lang
result = "\\lstset{showspaces=%s,showtabs=%s}\n" % (show_whitespace ? ['true', 'true'] : ['false', 'false'])
result += "\\lstset{language=#{lang}}\n" if lang
result += "\\lstset{basicstyle=\\ttfamily\\footnotesize}\\lstset{columns=fixed,frame=tlbr}\n"
"#{result}\\begin{lstlisting}#{attribute_list(el)}\n#{el.value}\n\\end{lstlisting}\n"
else
"\\begin{verbatim}#{el.value}\\end{verbatim}\n"
end
end
def latex_environment(type, el, text)
"\\begin{#{type}}#{attribute_list(el)}\n#{text}\n\\end{#{type}}\n"
end
def convert_blockquote(el, opts)
latex_environment('quote', el, inner(el, opts))
end
HEADER_TYPES = {
1 => 'section',
2 => 'subsection',
3 => 'subsubsection',
4 => 'paragraph',
5 => 'subparagraph',
6 => 'subparagraph'
}
def convert_header(el, opts)
type = HEADER_TYPES[el.options[:level]]
if ((el.options[:attr] && (id = el.options[:attr]['id'])) ||
(@doc.options[:auto_ids] && (id = generate_id(el.options[:raw_text])))) &&
(@doc.options[:toc_depth] <= 0 || el.options[:level] <= @doc.options[:toc_depth])
"\\hypertarget{#{id}}{}\\#{type}{#{inner(el, opts)}}\\label{#{id}}\n\n"
else
"\\#{type}*{#{inner(el, opts)}}\n\n"
end
end
def convert_hr(el, opts)
"\\begin{center}#{attribute_list(el)}\n\\rule{3in}{0.4pt}\n\\end{center}\n"
end
def convert_ul(el, opts)
if !@doc.conversion_infos[:has_toc] && (el.options[:ial][:refs].include?('toc') rescue nil)
@doc.conversion_infos[: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 #{inner(el, opts).sub(/\n+\Z/, '')}\n"
end
def convert_dt(el, opts)
"\\item[#{inner(el, opts)}] "
end
def convert_dd(el, opts)
"#{inner(el, opts)}\n\n"
end
def convert_html_element(el, opts)
if el.value == 'i'
"\\emph{#{inner(el, opts)}}"
elsif el.value == 'b'
"\\emph{#{inner(el, opts)}}"
else
@doc.warnings << "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)
@doc.warnings << "Can't convert XML PI/HTML document type"
''
end
alias :convert_html_doctype :convert_xml_pi
TABLE_ALIGNMENT_CHAR = {:default => 'l', :left => 'l', :center => 'c', :right => 'r'}
def convert_table(el, opts)
align = el.options[:alignment].map {|a| TABLE_ALIGNMENT_CHAR[a]}.join('|')
"\\begin{tabular}{|#{align}|}#{attribute_list(el)}\n\\hline\n#{inner(el, opts)}\\hline\n\\end{tabular}\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
alias :convert_th :convert_td
def convert_comment(el, opts)
el.value.split(/\n/).map {|l| "% #{l}"}.join("\n") + "\n"
end
def convert_br(el, opts)
"\\newline\n"
end
def convert_a(el, opts)
url = el.options[:attr]['href']
if url =~ /^#/
"\\hyperlink{#{url[1..-1]}}{#{inner(el, opts)}}"
else
"\\href{#{url}}{#{inner(el, opts)}}"
end
end
def convert_img(el, opts)
if el.options[:attr]['src'] =~ /^(https?|ftps?):\/\//
@doc.warnings << "Cannot include non-local image"
''
elsif !el.options[:attr]['src'].empty?
@doc.conversion_infos[:packages] << 'graphicx'
"\\includegraphics{#{el.options[:attr]['src']}}"
else
@doc.warnings << "Cannot include image with empty path"
''
end
end
def convert_codespan(el, opts)
"{\\tt #{escape(el.value)}}"
end
def convert_footnote(el, opts)
@doc.conversion_infos[:packages] << 'fancyvrb'
"\\footnote{#{inner(@doc.parse_infos[:footnotes][el.options[:name]][:content], opts)}}"
end
def convert_raw(el, opts)
escape(el.value)
end
def convert_em(el, opts)
"\\emph{#{inner(el, opts)}}"
end
def convert_strong(el, opts)
"\\textbf{#{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 => ['$\cup$'],
8746 => ['$\cap$'],
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 => ['\nolinebreak'],
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$'],
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'],
}
ENTITY_CONV_TABLE.each {|k,v| ENTITY_CONV_TABLE[k] = v.unshift(v.shift + '{}')}
def convert_entity(el, opts)
text, package = ENTITY_CONV_TABLE[el.value.code_point]
if text
@doc.conversion_infos[:packages] << package if package
text
else
@doc.warnings << "Couldn't find entity in substitution table!"
''
end
end
TYPOGRAPHIC_SYMS = {
:mdash => '---', :ndash => '--', :hellip => '\ldots{}',
:laquo_space => '\guillemotleft{}~', :raquo_space => '~\guillemotright{}',
:laquo => '\guillemotleft{}', :raquo => '\guillemotright{}'
}
def convert_typographic_sym(el, opts)
TYPOGRAPHIC_SYMS[el.value]
end
SMART_QUOTE_SYMS = {:lsquo => '`', :rsquo => '\'', :ldquo => '``', :rdquo => '\'\''}
def convert_smart_quote(el, opts)
SMART_QUOTE_SYMS[el.value]
end
def convert_math(el, opts)
@doc.conversion_infos[:packages] += %w[amssymb amsmath amsthm amsfonts]
if el.options[:category] == :block
if el.value =~ /\A\s*\\begin\{/
el.value
else
latex_environment('displaymath', el, el.value)
end
else
"$#{el.value}$"
end
end
def convert_abbreviation(el, opts)
el.value
end
ESCAPE_MAP = {
"^" => "\\^{}",
"\\" => "\\textbackslash{}",
"~" => "\\ensuremath{\\sim}",
"|" => "\\textbar{}",
"<" => "\\textless{}",
">" => "\\textgreater{}"
}.merge(Hash[*("{}$%&_#".scan(/./).map {|c| [c, "\\#{c}"]}.flatten)])
ESCAPE_RE = Regexp.union(*ESCAPE_MAP.collect {|k,v| k})
def escape(str)
str.gsub(ESCAPE_RE) {|m| ESCAPE_MAP[m]}
end
def attribute_list(el)
attrs = (el.options[:attr] || {}).map {|k,v| v.nil? ? '' : " #{k}=\"#{v.to_s}\""}.compact.sort.join('')
attrs = " % #{attrs}" if !attrs.empty?
attrs
end
end
end
end

View File

@ -1,166 +0,0 @@
# -*- coding: utf-8 -*-
#
#--
# Copyright (C) 2009-2010 Thomas Leitner <t_leitner@gmx.at>
#
# This file is part of kramdown.
#
# kramdown is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#++
#
require 'kramdown/compatibility'
require 'kramdown/version'
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(Config::CONFIG["datadir"], "kramdown")) if !File.exists?(@@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.
#
# The second argument to the #new method is an options hash for customizing the behaviour of the
# used parser and the converter. See Document#new for more information!
class Document
# The element tree of the document. It is immediately available after the #new method has been
# called.
attr_accessor :tree
# The options hash which holds the options for parsing/converting the Kramdown document. It is
# possible that these values get changed during the parsing phase.
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
# Holds needed parse information which is dependent on the used parser, like ALDs, link
# definitions and so on. This information may be used by converters afterwards.
attr_reader :parse_infos
# Holds conversion information which is dependent on the used converter. A converter clears this
# variable before doing the conversion.
attr_reader :conversion_infos
# 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 <tt>:input</tt> 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 <tt>:input</tt> 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 document tree is
# immediately available and the output can be generated.
def initialize(source, options = {})
@options = Options.merge(options)
@warnings = []
@parse_infos = {}
@parse_infos[:encoding] = source.encoding if RUBY_VERSION >= '1.9'
@conversion_infos = {}
parser = (options[:input] || 'kramdown').to_s
parser = parser[0..0].upcase + parser[1..-1]
if Parser.const_defined?(parser)
@tree = Parser.const_get(parser).parse(source, self)
else
raise Kramdown::Error.new("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+)$/
Converter.const_get($1[0..0].upcase + $1[1..-1]).convert(self)
else
super
end
end
def inspect #:nodoc:
"<KD:Document: options=#{@options.inspect} tree=#{@tree.inspect} warnings=#{@warnings.inspect}>"
end
end
# Represents all elements in the parse tree.
#
# kramdown only uses this one class for representing all available elements in a parse tree
# (paragraphs, headers, emphasis, ...). The type of element can be set via the #type accessor.
class Element
# A symbol representing the element type. For example, <tt>:p</tt> or <tt>:blockquote</tt>.
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 options hash for the element. It is used for storing arbitray options as well as the
# following special contents:
#
# - *Attributes* of the element under the <tt>:attr</tt> key
# - Category of the element, either <tt>:block</tt> or <tt>:span</tt>, under the
# <tt>:category</tt> key. If this key is absent, it can be assumed that the element is in the
# <tt>:span</tt> category.
attr_accessor :options
# The child elements of this element.
attr_accessor :children
# Create a new Element object of type +type+. The optional parameters +value+ and +options+ can
# also be set in this constructor for convenience.
def initialize(type, value = nil, options = {})
@type, @value, @options = type, value, options
@children = []
end
def inspect #:nodoc:
"<kd:#{@type}#{@value.nil? ? '' : ' ' + @value.inspect}#{options.empty? ? '' : ' ' + @options.inspect}#{@children.empty? ? '' : ' ' + @children.inspect}>"
end
end
end

View File

@ -1,27 +0,0 @@
# -*- coding: utf-8 -*-
#
#--
# Copyright (C) 2009-2010 Thomas Leitner <t_leitner@gmx.at>
#
# This file is part of kramdown.
#
# kramdown is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#++
#
module Kramdown
class Error < RuntimeError; end
end

View File

@ -1,23 +0,0 @@
# -*- coding: utf-8 -*-
#
#--
# Copyright (C) 2009-2010 Thomas Leitner <t_leitner@gmx.at>
#
# This file is part of kramdown.
#
# kramdown is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#++
#
require 'kramdown/document'

View File

@ -1,283 +0,0 @@
# -*- coding: utf-8 -*-
#
#--
# Copyright (C) 2009-2010 Thomas Leitner <t_leitner@gmx.at>
#
# This file is part of kramdown.
#
# kramdown is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#++
#
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 informs describes the methods that can be used on the Options module.
# ----------------------------
# Contains the definition of an option.
Definition = Struct.new(:name, :type, :default, :desc)
# Allowed option types.
ALLOWED_TYPES = [String, Integer, Float, Symbol, Boolean, Array, Object]
@options = {}
# Define a new option called +name+ (a Symbol) with the given +type+ (String, Integer, Float,
# Symbol, Boolean, Array, Object), default value +default+ and the description +desc+.
#
# The type 'Object' should only be used if none of the other types suffices because such an
# option will be opaque and cannot be used, for example, by CLI command!
def self.define(name, type, default, desc)
raise ArgumentError, "Option name #{name} is already used" if @options.has_key?(name)
raise ArgumentError, "Invalid option type #{type} specified" if !ALLOWED_TYPES.include?(type)
raise ArgumentError, "Invalid type for default value" if !(type === default) && !default.nil?
@options[name] = Definition.new(name, type, default, desc)
end
# Return all option definitions.
def self.definitions
@options
end
# Return +true+ if an option called +name+ is defined.
def self.defined?(name)
@options.has_key?(name)
end
# Return a Hash with the default values for all options.
def self.defaults
temp = {}
@options.each {|n, o| temp[o.name] = o.default}
temp
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
hash.each do |k,v|
next unless @options.has_key?(k)
temp[k] = parse(k, 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)
raise ArgumentError, "No option named #{name} defined" if !@options.has_key?(name)
return data if @options[name].type === data
data = data.to_s
if @options[name].type == String
data
elsif @options[name].type == Integer
Integer(data)
elsif @options[name].type == Float
Float(data)
elsif @options[name].type == Symbol
(data.strip.empty? ? nil : data.to_sym)
elsif @options[name].type == Boolean
data.downcase.strip != 'false' && !data.empty?
elsif @options[name].type == Array
data.split(/\s+/)
end
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
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 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).
kramdown provides a default template named 'default' 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_prefix, String, '', <<EOF)
Prefix used for automatically generated heaer 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(: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 `<em>hallo</em>` 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(: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(:coderay_wrap, Symbol, :div, <<EOF)
Defines how the highlighted code should be wrapped
The possible values are :span, :div or nil.
Default: :div
Used by: HTML converter
EOF
define(:coderay_line_numbers, Symbol, :inline, <<EOF)
Defines how and if line numbers should be shown
The possible values are :table, :inline, :list or nil. If this option is
nil, no line numbers are shown.
Default: :inline
Used by: HTML converter
EOF
define(:coderay_line_number_start, Integer, 1, <<EOF)
The start value for the line numbers
Default: 1
Used by: HTML converter
EOF
define(:coderay_tab_width, Integer, 8, <<EOF)
The tab width used in highlighted code
Used by: HTML converter
EOF
define(:coderay_bold_every, Integer, 10, <<EOF)
Defines how often a line number should be made bold
Default: 10
Used by: HTML converter
EOF
define(:coderay_css, Symbol, :style, <<EOF)
Defines how the highlighted code gets styled
Possible values are :class (CSS classes are applied to the code
elements, one must supply the needed CSS file) or :style (default CSS
styles are directly applied to the code elements).
Default: style
Used by: HTML converter
EOF
define(:numeric_entities, Boolean, false, <<EOF)
Defines whether entities are output using names or numeric values
Default: false
Used by: HTML converter, kramdown converter
EOF
define(:toc_depth, Integer, 0, <<EOF)
Defines the maximum level of headers which will be used to generate the table of
contents. For instance, with a value of 2, toc entries will be generated for h1
and h2 headers but not for h3, h4, etc. A value of 0 uses all header levels.
Default: 0
Used by: HTML/Latex converter
EOF
end
end

View File

@ -1,39 +0,0 @@
# -*- coding: utf-8 -*-
#
#--
# Copyright (C) 2009-2010 Thomas Leitner <t_leitner@gmx.at>
#
# This file is part of kramdown.
#
# kramdown is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#++
#
module Kramdown
# == Parser Module
#
# This module contains all available parsers. Currently, there two parsers:
#
# * Kramdown for parsing documents in kramdown format
# * Html for parsing HTML documents
module Parser
autoload :Base, 'kramdown/parser/base'
autoload :Kramdown, 'kramdown/parser/kramdown'
autoload :Html, 'kramdown/parser/html'
end
end

View File

@ -1,94 +0,0 @@
# -*- coding: utf-8 -*-
#
#--
# Copyright (C) 2009-2010 Thomas Leitner <t_leitner@gmx.at>
#
# This file is part of kramdown.
#
# kramdown is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#++
#
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 for parsing.
#
class Base
# Initialize the parser with the given Kramdown document +doc+.
def initialize(doc)
@doc = doc
@text_type = :text
end
private_class_method(:new, :allocate)
# Parse the +source+ string into an element tree, using the information provided by the
# Kramdown document +doc+.
#
# 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, doc)
new(doc).parse(source)
end
# Add the given warning +text+ to the warning array of the Kramdown document.
def warning(text)
@doc.warnings << text
#TODO: add position information
end
# Modify the string +source+ to be usable by the parser.
def adapt_source(source)
source.gsub(/\r\n?/, "\n").chomp + "\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)
if tree.children.last && tree.children.last.type == type
tree.children.last.value << text
elsif !text.empty?
tree.children << Element.new(type, text)
end
end
# Extract the part of the StringScanner +srcscan+ backed string specified by the +range+. This
# method also works correctly under Ruby 1.9.
def extract_string(range, strscan)
result = nil
if RUBY_VERSION >= '1.9'
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
else
result = strscan.string[range]
end
result
end
end
end
end

View File

@ -1,482 +0,0 @@
# -*- coding: utf-8 -*-
#
#--
# Copyright (C) 2009-2010 Thomas Leitner <t_leitner@gmx.at>
#
# This file is part of kramdown.
#
# kramdown is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#++
#
require 'rexml/parsers/baseparser'
require 'strscan'
module Kramdown
module Parser
# Used for parsing a HTML document.
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 = /<!DOCTYPE.*?>/m
HTML_COMMENT_RE = /<!--(.*?)-->/m
HTML_INSTRUCTION_RE = /<\?(.*?)\?>/m
HTML_ATTRIBUTE_RE = /\s*(#{REXML::Parsers::BaseParser::UNAME_STR})\s*=\s*(["'])(.*?)\2/m
HTML_TAG_RE = /<((?>#{REXML::Parsers::BaseParser::UNAME_STR}))\s*((?>\s+#{REXML::Parsers::BaseParser::UNAME_STR}\s*=\s*(["']).*?\3)*)\s*(\/)?>/m
HTML_TAG_CLOSE_RE = /<\/(#{REXML::Parsers::BaseParser::NAME_STR})\s*>/m
HTML_ENTITY_RE = /&([\w:][\-\w\d\.:]*);|&#(\d+);|&\#x([0-9a-fA-F]+);/
HTML_PARSE_AS_BLOCK = %w{applet button blockquote colgroup dd div dl fieldset form iframe li
map noscript object ol table tbody thead tfoot tr td ul}
HTML_PARSE_AS_SPAN = %w{a abbr acronym address b bdo big cite caption del dfn dt em
h1 h2 h3 h4 h5 h6 i ins kbd label legend optgroup p q rb rbc
rp rt rtc ruby samp select small span strong sub sup th tt var}
HTML_PARSE_AS_RAW = %w{script math option textarea pre code}
HTML_PARSE_AS = Hash.new {|h,k| h[k] = :raw}
HTML_PARSE_AS_BLOCK.each {|i| HTML_PARSE_AS[i] = :block}
HTML_PARSE_AS_SPAN.each {|i| HTML_PARSE_AS[i] = :span}
HTML_PARSE_AS_RAW.each {|i| HTML_PARSE_AS[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!
HTML_SPAN_ELEMENTS = %w{a abbr acronym b big bdo br button cite code del dfn em i img input
ins kbd label option q rb rbc rp rt rtc ruby samp select small span
strong sub sup textarea tt var}
HTML_BLOCK_ELEMENTS = %w{address article aside applet body button 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 listing menu
li 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}
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
# <tt>@stack</tt> for storing needed state and <tt>@src</tt> (instance of StringScanner) for
# the actual parsing.
module Parser
include Constants
# Process the HTML start tag that has already be scanned/checked. Does the common processing
# steps and then yields to the caller for further processing.
def handle_html_start_tag
name = @src[1]
closed = !@src[4].nil?
attrs = {}
@src[2].scan(HTML_ATTRIBUTE_RE).each {|attr,sep,val| attrs[attr] = val}
el = Element.new(:html_element, name, :attr => attrs, :category => :block)
@tree.children << el
if !closed && HTML_ELEMENTS_WITHOUT_BODY.include?(el.value)
warning("The HTML tag '#{el.value}' cannot have any content - auto-closing it")
closed = true
end
if name == 'script'
handle_html_script_tag
yield(el, true)
else
yield(el, closed)
end
end
def handle_html_script_tag
curpos = @src.pos
if result = @src.scan_until(/(?=<\/script\s*>)/m)
add_text(extract_string(curpos...@src.pos, @src), @tree.children.last, :raw)
@src.scan(HTML_TAG_CLOSE_RE)
else
add_text(@src.scan(/.*/m), @tree.children.last, :raw)
warning("Found no end tag for 'script' - auto-closing it")
end
end
HTML_RAW_START = /(?=<(#{REXML::Parsers::BaseParser::UNAME_STR}|\/|!--|\?))/
# 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)
if result = @src.scan(HTML_COMMENT_RE)
@tree.children << Element.new(:xml_comment, result, :category => :block, :parent_is_raw => true)
elsif result = @src.scan(HTML_INSTRUCTION_RE)
@tree.children << Element.new(:xml_pi, result, :category => :block, :parent_is_raw => true)
elsif @src.scan(HTML_TAG_RE)
handle_html_start_tag(&block)
elsif @src.scan(HTML_TAG_CLOSE_RE)
if @tree.value == @src[1]
done = true
else
warning("Found invalidly used HTML closing tag for '#{@src[1]}' - ignoring it")
end
else
add_text(@src.scan(/./), @tree, :text)
end
else
result = @src.scan(/.*/m)
add_text(result, @tree, :text)
warning("Found no end tag for '#{@tree.value}' - auto-closing it") if @tree.type == :html_element
done = true
end
end
@tree = @stack.pop
end
end
# Converts HTML elements to native elements if possible.
class ElementConverter
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 a img p thead tbody tfoot tr td th ul ol dl li dl dt dd}
def initialize(doc)
@doc = doc
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, :html_doctype
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 = {:category => HTML_PARSE_AS_SPAN.include?(ptype) ? :span : :block}
return
when :html_element
else return
end
type = el.value
remove_text_children(el) if REMOVE_TEXT_CHILDREN.include?(type)
mname = "convert_#{el.value}"
if do_conversion && self.class.method_defined?(mname)
send(mname, el)
elsif do_conversion && SIMPLE_ELEMENTS.include?(type)
set_basics(el, type.intern, HTML_SPAN_ELEMENTS.include?(type) ? :span : :block)
process_children(el, do_conversion, preserve_text)
else
process_html_element(el, do_conversion, preserve_text)
end
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
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)
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 = StringScanner.new(raw)
result = []
while !src.eos?
if tmp = src.scan_until(/(?=#{HTML_ENTITY_RE})/)
result << Element.new(:text, tmp)
src.scan(HTML_ENTITY_RE)
val = src[1] || (src[2] && 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
Element.new(:entity, entity(val))
end
else
result << Element.new(:text, src.scan(/.*/m))
end
end
result
end
def process_html_element(el, do_conversion = true, preserve_text = false)
el.options = {:category => HTML_SPAN_ELEMENTS.include?(el.value) ? :span : :block,
:parse_type => HTML_PARSE_AS[el.value],
:attr => el.options[:attr]
}
process_children(el, do_conversion, preserve_text)
end
def remove_text_children(el)
el.children.delete_if {|c| c.type == :text}
end
SPAN_ELEMENTS = [:em, :strong, :br, :a, :img, :codespan, :entity, :smart_quote, :typographic_sym, :math]
def wrap_text_children(el)
tmp = []
last_is_p = false
el.children.each do |c|
if c.options[:category] != :block || c.type == :text
if !last_is_p
tmp << Element.new(:p, 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.delete_if do |c|
i += 1
c.type == :text && c.value.strip.empty? &&
(i == 0 || i == el.children.length - 1 || (el.children[i-1].options[:category] == :block &&
el.children[i+1].options[:category] == :block))
end
end
def set_basics(el, type, category, opts = {})
el.type = type
el.options = {:category => category, :attr => el.options[:attr]}.merge(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_h1(el)
set_basics(el, :header, :block, :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|
if c.type == :text
mem << c.value
elsif c.type == :entity
if RUBY_VERSION >= '1.9'
mem << c.value.char.encode(@doc.parse_infos[:encoding])
elsif [60, 62, 34, 38].include?(c.value.code_point)
mem << c.value.code_point.chr
end
elsif c.type == :smart_quote || c.type == :typographic_sym
mem << entity(c.value.to_s).char.encode(@doc.parse_infos[:encoding])
else
raise "Bug - please report"
end
end
result.clear
result << Element.new(:text, str)
rescue
end
if result.length > 1 || result.first.type != :text
process_html_element(el, false, true)
else
if el.value == 'code'
set_basics(el, :codespan, :span)
else
set_basics(el, :codeblock, :block)
end
el.value = result.first.value
end
end
alias :convert_pre :convert_code
def convert_table(el)
if !is_simple_table?(el)
process_html_element(el, false)
return
end
process_children(el)
set_basics(el, :table, :block)
el.options[:alignment] = []
calc_alignment = lambda do |c|
if c.type == :tr && el.options[:alignment].empty?
el.options[:alignment] = [:default] * c.children.length
break
else
c.children.each {|cc| calc_alignment.call(cc)}
end
end
calc_alignment.call(el)
if el.children.first.type == :tr
tbody = Element.new(:tbody, nil, :category => :block)
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.new do |c|
if c.value == 'th' || c.value == 'td'
return false if !only_phrasing_content.call(c)
else
c.children.each {|cc| check_cells.call(cc)}
end
end
check_cells.call(el)
check_rows = lambda do |t, type|
t.children.all? {|r| (r.value == 'tr' || r.type == :text) && r.children.all? {|c| c.value == type || c.type == :text}}
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_div(el)
if !is_math_tag?(el)
process_html_element(el)
else
handle_math_tag(el)
end
end
alias :convert_span :convert_div
def is_math_tag?(el)
el.options[:attr] && el.options[:attr]['class'].to_s =~ /\bmath\b/ &&
el.children.size == 1 && el.children.first.type == :text
end
def handle_math_tag(el)
set_basics(el, :math, (el.value == 'div' ? :block : :span))
el.value = el.children.shift.value
if el.options[:attr]['class'] =~ /^\s*math\s*$/
el.options[:attr].delete('class')
else
el.options[:attr]['class'].sub!(/\s?math/, '')
end
el.value.gsub!(/&(amp|quot|gt|lt);/) do |m|
case m
when '&amp;' then '&'
when '&quot;' then '"'
when '&gt;' then '>'
when '&lt;' then '<'
end
end
end
end
include Parser
# Parse +source+ as HTML document and return the created +tree+.
def parse(source)
@stack = []
@tree = Element.new(:root)
@src = StringScanner.new(adapt_source(source))
while true
if result = @src.scan(/\s*#{HTML_INSTRUCTION_RE}/)
@tree.children << Element.new(:xml_pi, result.strip, :category => :block)
elsif result = @src.scan(/\s*#{HTML_DOCTYPE_RE}/)
@tree.children << Element.new(:html_doctype, result.strip, :category => :block)
elsif result = @src.scan(/\s*#{HTML_COMMENT_RE}/)
@tree.children << Element.new(:xml_comment, result.strip, :category => :block)
else
break
end
end
tag_handler = lambda do |c, closed|
parse_raw_html(c, &tag_handler) if !closed
end
parse_raw_html(@tree, &tag_handler)
ec = ElementConverter.new(@doc)
@tree.children.each {|c| ec.process(c)}
ec.remove_whitespace_children(@tree)
@tree
end
end
end
end

View File

@ -1,303 +0,0 @@
# -*- coding: utf-8 -*-
#
#--
# Copyright (C) 2009-2010 Thomas Leitner <t_leitner@gmx.at>
#
# This file is part of kramdown.
#
# kramdown is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#++
#
require 'strscan'
require 'stringio'
#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 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(doc)
# super(doc)
# @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
attr_reader :tree
attr_reader :doc
attr_reader :options
# Create a new Kramdown parser object for the Kramdown::Document +doc+.
def initialize(doc)
super(doc)
@src = nil
@tree = nil
@stack = []
@text_type = :raw_text
@block_ial = nil
@doc.parse_infos[:ald] = {}
@doc.parse_infos[:link_defs] = {}
@doc.parse_infos[:abbrev_defs] = {}
@doc.parse_infos[:footnotes] = {}
@block_parsers = [:blank_line, :codeblock, :codeblock_fenced, :blockquote, :table, :atx_header,
:setext_header, :horizontal_rule, :list, :definition_list, :link_definition, :block_html,
:footnote_definition, :abbrev_definition, :ald, :block_math,
:block_extension, :block_ial, :eob_marker, :paragraph]
@span_parsers = [:emphasis, :codespan, :autolink, :span_html, :footnote_marker, :link, :smart_quotes, :inline_math,
:span_extension, :span_ial, :html_entity, :typographic_syms, :line_break, :escaped_chars]
end
private_class_method(:new, :allocate)
# The source string provided on initialization is parsed and the created +tree+ is returned.
def parse(source)
configure_parser
tree = Element.new(:root)
parse_blocks(tree, adapt_source(source))
update_tree(tree)
replace_abbreviations(tree)
@doc.parse_infos[:footnotes].each do |name, data|
update_tree(data[:content])
end
tree
end
#######
protected
#######
# 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])
@tree, @src = el, (text.nil? ? @src : StringScanner.new(text))
status = catch(:stop_block_parsing) do
while !@src.eos?
block_ial_set = @block_ial
@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
@block_ial = nil if block_ial_set
end
end
@tree, @src = *@stack.pop
status
end
# Update the tree by parsing all <tt>:raw_text</tt> elements with the span level parser
# (resets +@tree+, +@src+ and the +@stack+) and by updating the attributes from the IALs.
def update_tree(element)
element.children.map! do |child|
if child.type == :raw_text
@stack, @tree, @text_type = [], nil, :text
@src = StringScanner.new(child.value)
parse_spans(child)
child.children
elsif child.type == :eob
[]
else
update_tree(child)
update_attr_with_ial(child.options[:attr] ||= {}, child.options[:ial]) if child.options[:ial]
child
end
end.flatten!
end
# Parse all span level elements in the source string.
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 = parsers || @span_parsers
used_re = (stop_re.nil? ? span_start_re : /(?=#{Regexp.union(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 && (stop_re_matched = @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.scan(/./)) if !processed && !stop_re_found
else
add_text(@src.scan(/.*/m)) unless stop_re
break
end
end
@tree, @text_type = @stack.pop
stop_re_found
end
# Update the attributes with the information from the inline attribute list and all referenced ALDs.
def update_attr_with_ial(attr, ial)
ial[:refs].each do |ref|
update_attr_with_ial(attr, ref) if ref = @doc.parse_infos[:ald][ref]
end if ial[:refs]
attr['class'] = ((attr['class'] || '') + " #{ial['class']}").lstrip if ial['class']
ial.each {|k,v| attr[k] = v if k.kind_of?(String) && k != 'class' }
end
# Create a new block level element, taking care of applying a preceding block IAL if it exists.
def new_block_el(*args)
el = Element.new(*args)
el.options[:category] ||= :block
el.options[:ial] = @block_ial if @block_ial && el.type != :blank && el.type != :eob
el
end
@@parsers = {}
# Holds all the needed data for one block/span level parser.
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.has_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.has_key?(name)
end
INDENT = /^(?:\t| {4})/
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/attribute_list'
require 'kramdown/parser/kramdown/extension'
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

View File

@ -1,65 +0,0 @@
# -*- coding: utf-8 -*-
#
#--
# Copyright (C) 2009-2010 Thomas Leitner <t_leitner@gmx.at>
#
# This file is part of kramdown.
#
# kramdown is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#++
#
module Kramdown
module Parser
class Kramdown
ABBREV_DEFINITION_START = /^#{OPT_SPACE}\*\[(.+?)\]:(.*?)\n/
# Parse the link definition at the current location.
def parse_abbrev_definition
@src.pos += @src.matched_size
abbrev_id, abbrev_text = @src[1], @src[2].strip
warning("Duplicate abbreviation ID '#{abbrev_id}' - overwriting") if @doc.parse_infos[:abbrev_defs][abbrev_id]
@doc.parse_infos[:abbrev_defs][abbrev_id] = abbrev_text
true
end
define_parser(:abbrev_definition, ABBREV_DEFINITION_START)
# Replace the abbreviation text with elements.
def replace_abbreviations(el, regexps = nil)
return if @doc.parse_infos[:abbrev_defs].empty?
if !regexps
regexps = [Regexp.union(*@doc.parse_infos[:abbrev_defs].keys.map {|k| /#{Regexp.escape(k)}/})]
regexps << /(?=(?:\W|^)#{regexps.first}(?!\w))/ # regexp should only match on word boundaries
end
el.children.map! do |child|
if child.type == :text
result = []
strscan = StringScanner.new(child.value)
while temp = strscan.scan_until(regexps.last)
temp += strscan.scan(/\W|^/)
abbr = strscan.scan(regexps.first)
result += [Element.new(:text, temp), Element.new(:abbreviation, abbr)]
end
result + [Element.new(:text, extract_string(strscan.pos..-1, strscan))]
else
replace_abbreviations(child, regexps)
child
end
end.flatten!
end
end
end
end

View File

@ -1,105 +0,0 @@
# -*- coding: utf-8 -*-
#
#--
# Copyright (C) 2009-2010 Thomas Leitner <t_leitner@gmx.at>
#
# This file is part of kramdown.
#
# kramdown is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#++
#
module Kramdown
module Parser
class Kramdown
# Parse the string +str+ and extract all attributes and add all found attributes to the hash
# +opts+.
def parse_attribute_list(str, opts)
str.scan(ALD_TYPE_ANY).each do |key, sep, val, id_attr, class_attr, ref|
if ref
(opts[:refs] ||= []) << ref
elsif class_attr
opts['class'] = ((opts['class'] || '') + " #{class_attr}").lstrip
elsif id_attr
opts['id'] = id_attr
else
opts[key] = val.gsub(/\\(\}|#{sep})/, "\\1")
end
end
end
# Update the +ial+ with the information from the inline attribute list +opts+.
def update_ial_with_ial(ial, opts)
(ial[:refs] ||= []) << opts[:refs]
ial['class'] = ((ial['class'] || '') + " #{opts['class']}").lstrip if opts['class']
opts.each {|k,v| ial[k] = v if k != :refs && k != 'class' }
end
ALD_ID_CHARS = /[\w\d-]/
ALD_ANY_CHARS = /\\\}|[^\}]/
ALD_ID_NAME = /(?:\w|\d)#{ALD_ID_CHARS}*/
ALD_TYPE_KEY_VALUE_PAIR = /(#{ALD_ID_NAME})=("|')((?:\\\}|\\\2|[^\}\2])*?)\2/
ALD_TYPE_CLASS_NAME = /\.(#{ALD_ID_NAME})/
ALD_TYPE_ID_NAME = /#(#{ALD_ID_NAME})/
ALD_TYPE_REF = /(#{ALD_ID_NAME})/
ALD_TYPE_ANY = /(?:\A|\s)(?:#{ALD_TYPE_KEY_VALUE_PAIR}|#{ALD_TYPE_ID_NAME}|#{ALD_TYPE_CLASS_NAME}|#{ALD_TYPE_REF})(?=\s|\Z)/
ALD_START = /^#{OPT_SPACE}\{:(#{ALD_ID_NAME}):(#{ALD_ANY_CHARS}+)\}\s*?\n/
# Parse the attribute list definition at the current location.
def parse_ald
@src.pos += @src.matched_size
parse_attribute_list(@src[2], @doc.parse_infos[:ald][@src[1]] ||= {})
@tree.children << Element.new(:eob)
true
end
define_parser(:ald, ALD_START)
IAL_BLOCK_START = /^#{OPT_SPACE}\{:(?!:)(#{ALD_ANY_CHARS}+)\}\s*?\n/
# Parse the inline attribute list at the current location.
def parse_block_ial
@src.pos += @src.matched_size
if @tree.children.last && @tree.children.last.type != :blank && @tree.children.last.type != :eob
parse_attribute_list(@src[1], @tree.children.last.options[:ial] ||= {})
else
parse_attribute_list(@src[1], @block_ial = {})
end
@tree.children << Element.new(:eob) unless @src.check(IAL_BLOCK_START)
true
end
define_parser(:block_ial, IAL_BLOCK_START)
IAL_SPAN_START = /\{:(#{ALD_ANY_CHARS}+)\}/
# Parse the inline attribute list at the current location.
def parse_span_ial
@src.pos += @src.matched_size
if @tree.children.last && @tree.children.last.type != :text
attr = {}
parse_attribute_list(@src[1], attr)
update_ial_with_ial(@tree.children.last.options[:ial] ||= {}, attr)
update_attr_with_ial(@tree.children.last.options[:attr] ||= {}, attr)
else
warning("Ignoring span IAL because preceding element is just text")
end
end
define_parser(:span_ial, IAL_SPAN_START, '\{:')
end
end
end

View File

@ -1,47 +0,0 @@
# -*- coding: utf-8 -*-
#
#--
# Copyright (C) 2009-2010 Thomas Leitner <t_leitner@gmx.at>
#
# This file is part of kramdown.
#
# kramdown is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#++
#
module Kramdown
module Parser
class Kramdown
if RUBY_VERSION == '1.8.5'
ACHARS = '\x80-\xFF'
else
ACHARS = ''
end
AUTOLINK_START = /<((mailto|https?|ftps?):.+?|[-.\w#{ACHARS}]+@[-\w#{ACHARS}]+(\.[-\w#{ACHARS}]+)*\.[a-z]+)>/u
# Parse the autolink at the current location.
def parse_autolink
@src.pos += @src.matched_size
href = @src[1]
href= "mailto:#{href}" if @src[2].nil?
el = Element.new(:a, nil, {:attr => {'href' => href}})
add_text(@src[1].sub(/^mailto:/, ''), el)
@tree.children << el
end
define_parser(:autolink, AUTOLINK_START, '<')
end
end
end

View File

@ -1,43 +0,0 @@
# -*- coding: utf-8 -*-
#
#--
# Copyright (C) 2009-2010 Thomas Leitner <t_leitner@gmx.at>
#
# This file is part of kramdown.
#
# kramdown is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#++
#
module Kramdown
module Parser
class Kramdown
BLANK_LINE = /(?:^\s*\n)+/
# Parse the blank line at the current postition.
def parse_blank_line
@src.pos += @src.matched_size
if @tree.children.last && @tree.children.last.type == :blank
@tree.children.last.value += @src.matched
else
@tree.children << new_block_el(:blank, @src.matched)
end
true
end
define_parser(:blank_line, BLANK_LINE)
end
end
end

View File

@ -1,42 +0,0 @@
# -*- coding: utf-8 -*-
#
#--
# Copyright (C) 2009-2010 Thomas Leitner <t_leitner@gmx.at>
#
# This file is part of kramdown.
#
# kramdown is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#++
#
module Kramdown
module Parser
class Kramdown
BLOCKQUOTE_START = /^#{OPT_SPACE}> ?/
BLOCKQUOTE_MATCH = /(^#{OPT_SPACE}>.*?\n)+/
# Parse the blockquote at the current location.
def parse_blockquote
result = @src.scan(BLOCKQUOTE_MATCH).gsub(BLOCKQUOTE_START, '')
el = new_block_el(:blockquote)
@tree.children << el
parse_blocks(el, result)
true
end
define_parser(:blockquote, BLOCKQUOTE_START)
end
end
end

View File

@ -1,58 +0,0 @@
# -*- coding: utf-8 -*-
#
#--
# Copyright (C) 2009-2010 Thomas Leitner <t_leitner@gmx.at>
#
# This file is part of kramdown.
#
# kramdown is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#++
#
require 'kramdown/parser/kramdown/blank_line'
module Kramdown
module Parser
class Kramdown
CODEBLOCK_START = INDENT
CODEBLOCK_LINE = /(?:#{INDENT}.*?\S.*?\n)+/
CODEBLOCK_MATCH = /(?:#{BLANK_LINE}?#{CODEBLOCK_LINE})*/
# Parse the indented codeblock at the current location.
def parse_codeblock
@tree.children << new_block_el(:codeblock, @src.scan(CODEBLOCK_MATCH).gsub!(INDENT, ''))
true
end
define_parser(:codeblock, CODEBLOCK_START)
FENCED_CODEBLOCK_START = /^~{3,}/
FENCED_CODEBLOCK_MATCH = /^(~{3,})\s*?\n(.*?)^\1~*\s*?\n/m
# Parse the fenced codeblock at the current location.
def parse_codeblock_fenced
if @src.check(FENCED_CODEBLOCK_MATCH)
@src.pos += @src.matched_size
@tree.children << new_block_el(:codeblock, @src[2])
true
else
false
end
end
define_parser(:codeblock_fenced, FENCED_CODEBLOCK_START)
end
end
end

View File

@ -1,57 +0,0 @@
# -*- coding: utf-8 -*-
#
#--
# Copyright (C) 2009-2010 Thomas Leitner <t_leitner@gmx.at>
#
# This file is part of kramdown.
#
# kramdown is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#++
#
module Kramdown
module Parser
class Kramdown
CODESPAN_DELIMITER = /`+/
# Parse the codespan at the current scanner location.
def parse_codespan
result = @src.scan(CODESPAN_DELIMITER)
simple = (result.length == 1)
reset_pos = @src.pos
if simple && @src.pre_match =~ /\s\Z/ && @src.match?(/\s/)
add_text(result)
return
end
text = @src.scan_until(/#{result}/)
if text
text.sub!(/#{result}\Z/, '')
if !simple
text = text[1..-1] if text[0..0] == ' '
text = text[0..-2] if text[-1..-1] == ' '
end
@tree.children << Element.new(:codespan, text)
else
@src.pos = reset_pos
add_text(result)
end
end
define_parser(:codespan, CODESPAN_DELIMITER, '`')
end
end
end

View File

@ -1,70 +0,0 @@
# -*- coding: utf-8 -*-
#
#--
# Copyright (C) 2009-2010 Thomas Leitner <t_leitner@gmx.at>
#
# This file is part of kramdown.
#
# kramdown is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#++
#
module Kramdown
module Parser
class Kramdown
EMPHASIS_START = /(?:\*\*?|__?)/
# Parse the emphasis at the current location.
def parse_emphasis
result = @src.scan(EMPHASIS_START)
element = (result.length == 2 ? :strong : :em)
type = (result =~ /_/ ? '_' : '*')
reset_pos = @src.pos
if (type == '_' && @src.pre_match =~ /[[:alpha:]]\z/ && @src.check(/[[:alpha:]]/)) || @src.check(/\s/) ||
@tree.type == element || @stack.any? {|el, _| el.type == element}
add_text(result)
return
end
sub_parse = lambda do |delim, elem|
el = Element.new(elem)
stop_re = /#{Regexp.escape(delim)}/
found = parse_spans(el, stop_re) do
(@src.pre_match[-1, 1] !~ /\s/) &&
(elem != :em || !@src.match?(/#{Regexp.escape(delim*2)}(?!#{Regexp.escape(delim)})/)) &&
(type != '_' || !@src.match?(/#{Regexp.escape(delim)}[[:alpha:]]/)) && el.children.size > 0
end
[found, el, stop_re]
end
found, el, stop_re = sub_parse.call(result, element)
if !found && element == :strong && @tree.type != :em
@src.pos = reset_pos - 1
found, el, stop_re = sub_parse.call(type, :em)
end
if found
@src.scan(stop_re)
@tree.children << el
else
@src.pos = reset_pos
add_text(result)
end
end
define_parser(:emphasis, EMPHASIS_START, '\*|_')
end
end
end

View File

@ -1,39 +0,0 @@
# -*- coding: utf-8 -*-
#
#--
# Copyright (C) 2009-2010 Thomas Leitner <t_leitner@gmx.at>
#
# This file is part of kramdown.
#
# kramdown is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#++
#
module Kramdown
module Parser
class Kramdown
EOB_MARKER = /^\^\s*?\n/
# Parse the EOB marker at the current location.
def parse_eob_marker
@src.pos += @src.matched_size
@tree.children << new_block_el(:eob)
true
end
define_parser(:eob_marker, EOB_MARKER)
end
end
end

View File

@ -1,38 +0,0 @@
# -*- coding: utf-8 -*-
#
#--
# Copyright (C) 2009-2010 Thomas Leitner <t_leitner@gmx.at>
#
# This file is part of kramdown.
#
# kramdown is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#++
#
module Kramdown
module Parser
class Kramdown
ESCAPED_CHARS = /\\([\\.*_+`()\[\]{}#!:|"'\$-])/
# Parse the backslash-escaped character at the current location.
def parse_escaped_chars
@src.pos += @src.matched_size
add_text(@src[1])
end
define_parser(:escaped_chars, ESCAPED_CHARS, '\\\\')
end
end
end

View File

@ -1,102 +0,0 @@
# -*- coding: utf-8 -*-
#
#--
# Copyright (C) 2009-2010 Thomas Leitner <t_leitner@gmx.at>
#
# This file is part of kramdown.
#
# kramdown is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#++
#
require 'kramdown/parser/kramdown/attribute_list'
module Kramdown
module Parser
class Kramdown
def parse_extension_start_tag(type)
@src.pos += @src.matched_size
if @src[4] || @src.matched == '{:/}'
name = (@src[4] ? "for '#{@src[4]}' " : '')
warning("Invalid extension stop tag #{name}found - ignoring it")
return
end
ext = @src[1]
opts = {}
body = nil
parse_attribute_list(@src[2] || '', opts)
if !@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
warning("No stop tag for extension '#{ext}' found - treating it as extension without body")
end
end
handle_extension(ext, opts, body, type)
end
def handle_extension(name, opts, body, type)
case name
when 'comment'
@tree.children << Element.new(:comment, body, :category => type) if body.kind_of?(String)
when 'nomarkdown'
@tree.children << Element.new(:raw, body, :category => type) if body.kind_of?(String)
when 'options'
opts.select do |k,v|
k = k.to_sym
if Kramdown::Options.defined?(k)
@doc.options[k] = Kramdown::Options.parse(k, v) rescue @doc.options[k]
false
else
true
end
end.each do |k,v|
warning("Unknown kramdown option '#{k}'")
end
else
warning("Invalid extension name '#{name}' specified - ignoring extension")
end
end
EXT_STOP_STR = "\\{:/(%s)?\\}"
EXT_START_STR = "\\{::(\\w+)(?:\\s(#{ALD_ANY_CHARS}*?)|)(\\/)?\\}"
EXT_SPAN_START = /#{EXT_START_STR}|#{EXT_STOP_STR % ALD_ID_NAME}/
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"
# Parse the extension block at the current location.
def parse_block_extension
parse_extension_start_tag(:block)
true
end
define_parser(:block_extension, EXT_BLOCK_START)
# Parse the extension span at the current location.
def parse_span_extension
parse_extension_start_tag(:span)
end
define_parser(:span_extension, EXT_SPAN_START, '\{:[:/]')
end
end
end

View File

@ -1,73 +0,0 @@
# -*- coding: utf-8 -*-
#
#--
# Copyright (C) 2009-2010 Thomas Leitner <t_leitner@gmx.at>
#
# This file is part of kramdown.
#
# kramdown is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#++
#
require 'kramdown/parser/kramdown/attribute_list'
require 'kramdown/parser/kramdown/blank_line'
require 'kramdown/parser/kramdown/codeblock'
module Kramdown
module Parser
class Kramdown
FOOTNOTE_DEFINITION_START = /^#{OPT_SPACE}\[\^(#{ALD_ID_NAME})\]:\s*?(.*?\n(?:#{BLANK_LINE}?#{CODEBLOCK_LINE})*)/
# Parse the foot note definition at the current location.
def parse_footnote_definition
@src.pos += @src.matched_size
el = Element.new(:footnote_def)
parse_blocks(el, @src[2].gsub(INDENT, ''))
warning("Duplicate footnote name '#{@src[1]}' - overwriting") if @doc.parse_infos[:footnotes][@src[1]]
(@doc.parse_infos[:footnotes][@src[1]] = {})[:content] = el
true
end
define_parser(:footnote_definition, FOOTNOTE_DEFINITION_START)
FOOTNOTE_MARKER_START = /\[\^(#{ALD_ID_NAME})\]/
# Parse the footnote marker at the current location.
def parse_footnote_marker
@src.pos += @src.matched_size
fn_def = @doc.parse_infos[:footnotes][@src[1]]
if fn_def
valid = fn_def[:marker] && fn_def[:marker].options[:stack][0..-2].zip(fn_def[:marker].options[:stack][1..-1]).all? do |par, child|
par.children.include?(child)
end
if !fn_def[:marker] || !valid
fn_def[:marker] = Element.new(:footnote, nil, :name => @src[1])
fn_def[:marker].options[:stack] = [@stack.map {|s| s.first}, @tree, fn_def[:marker]].flatten.compact
@tree.children << fn_def[:marker]
else
warning("Footnote marker '#{@src[1]}' already appeared in document, ignoring newly found marker")
add_text(@src.matched)
end
else
warning("Footnote definition for '#{@src[1]}' not found")
add_text(@src.matched)
end
end
define_parser(:footnote_marker, FOOTNOTE_MARKER_START, '\[')
end
end
end

View File

@ -1,66 +0,0 @@
# -*- coding: utf-8 -*-
#
#--
# Copyright (C) 2009-2010 Thomas Leitner <t_leitner@gmx.at>
#
# This file is part of kramdown.
#
# kramdown is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#++
#
module Kramdown
module Parser
class Kramdown
HEADER_ID=/(?:[ \t]\{#((?:\w|\d)[\w\d-]*)\})?/
SETEXT_HEADER_START = /^(#{OPT_SPACE}[^ \t].*?)#{HEADER_ID}[ \t]*?\n(-|=)+\s*?\n/
# Parse the Setext header at the current location.
def parse_setext_header
if @tree.children.last && @tree.children.last.type != :blank
return false
end
@src.pos += @src.matched_size
text, id, level = @src[1].strip, @src[2], @src[3]
el = new_block_el(:header, nil, :level => (level == '-' ? 2 : 1), :raw_text => text)
add_text(text, el)
el.options[:attr] = {'id' => id} if id
@tree.children << el
true
end
define_parser(:setext_header, SETEXT_HEADER_START)
ATX_HEADER_START = /^\#{1,6}/
ATX_HEADER_MATCH = /^(\#{1,6})(.+?)\s*?#*#{HEADER_ID}\s*?\n/
# Parse the Atx header at the current location.
def parse_atx_header
if @tree.children.last && @tree.children.last.type != :blank
return false
end
result = @src.scan(ATX_HEADER_MATCH)
level, text, id = @src[1], @src[2].strip, @src[3]
el = new_block_el(:header, nil, :level => level.length, :raw_text => text)
add_text(text, el)
el.options[:attr] = {'id' => id} if id
@tree.children << el
true
end
define_parser(:atx_header, ATX_HEADER_START)
end
end
end

View File

@ -1,39 +0,0 @@
# -*- coding: utf-8 -*-
#
#--
# Copyright (C) 2009-2010 Thomas Leitner <t_leitner@gmx.at>
#
# This file is part of kramdown.
#
# kramdown is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#++
#
module Kramdown
module Parser
class Kramdown
HR_START = /^#{OPT_SPACE}(\*|-|_)[ \t]*\1[ \t]*\1[ \t]*(\1|[ \t])*\n/
# Parse the horizontal rule at the current location.
def parse_horizontal_rule
@src.pos += @src.matched_size
@tree.children << new_block_el(:hr)
true
end
define_parser(:horizontal_rule, HR_START)
end
end
end

View File

@ -1,173 +0,0 @@
# -*- coding: utf-8 -*-
#
#--
# Copyright (C) 2009-2010 Thomas Leitner <t_leitner@gmx.at>
#
# This file is part of kramdown.
#
# kramdown is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#++
#
require 'kramdown/parser/html'
module Kramdown
module Parser
class Kramdown
include Kramdown::Parser::Html::Parser
def handle_kramdown_html_tag(el, closed)
parse_type = if @tree.type != :html_element || @tree.options[:parse_type] != :raw
(@doc.options[:parse_block_html] ? HTML_PARSE_AS[el.value] : :raw)
else
:raw
end
if val = html_parse_type(el.options[:attr].delete('markdown'))
parse_type = (val == :default ? HTML_PARSE_AS[el.value] : val)
end
@src.scan(/[ \t]*\n/) if parse_type == :block
el.options[:outer_element] = true if @tree.type != :html_element
el.options[:parent_is_raw] = true if @tree.type == :html_element && @tree.options[:parse_type] == :raw
el.options[:parse_type] = parse_type
if !closed
if parse_type == :block
end_tag_found = parse_blocks(el)
if !end_tag_found
warning("Found no end tag for '#{el.value}' - auto-closing it")
end
elsif parse_type == :span
curpos = @src.pos
if result = @src.scan_until(/(?=<\/#{el.value}\s*>)/m)
add_text(extract_string(curpos...@src.pos, @src), el)
@src.scan(HTML_TAG_CLOSE_RE)
else
add_text(@src.scan(/.*/m), el)
warning("Found no end tag for '#{el.value}' - auto-closing it")
end
else
parse_raw_html(el, &method(:handle_kramdown_html_tag))
end
@src.scan(/[ \t]*\n/) unless (@tree.type == :html_element && @tree.options[:parse_type] == :raw)
end
end
# Return the HTML parse type defined by the string +val+, i.e. raw when "0", default parsing
# (return value +nil+) when "1", span parsing when "span" and block parsing when "block". If
# +val+ is nil, then the default parsing mode is used.
def html_parse_type(val)
case val
when "0" then :raw
when "1" then :default
when "span" then :span
when "block" then :block
when NilClass then nil
else
warning("Invalid markdown attribute val '#{val}', using default")
nil
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
if result = @src.scan(HTML_COMMENT_RE)
@tree.children << Element.new(:xml_comment, result, :category => :block)
@src.scan(/[ \t]*\n/)
true
elsif result = @src.scan(HTML_INSTRUCTION_RE)
@tree.children << Element.new(:xml_pi, result, :category => :block)
@src.scan(/[ \t]*\n/)
true
else
if result = @src.check(/^#{OPT_SPACE}#{HTML_TAG_RE}/) && !HTML_SPAN_ELEMENTS.include?(@src[1])
@src.pos += @src.matched_size
handle_html_start_tag(&method(:handle_kramdown_html_tag))
Kramdown::Parser::Html::ElementConverter.new(@doc).process(@tree.children.last) if @doc.options[:html_to_native]
true
elsif result = @src.check(/^#{OPT_SPACE}#{HTML_TAG_CLOSE_RE}/) && !HTML_SPAN_ELEMENTS.include?(@src[1])
@src.pos += @src.matched_size
name = @src[1]
if @tree.type == :html_element && @tree.value == name
throw :stop_block_parsing, :found
else
warning("Found invalidly used HTML closing tag for '#{name}' - ignoring it")
true
end
else
false
end
end
end
define_parser(:block_html, HTML_BLOCK_START)
HTML_SPAN_START = /<(#{REXML::Parsers::BaseParser::UNAME_STR}|\?|!--|\/)/
# Parse the HTML at the current position as span level HTML.
def parse_span_html
if result = @src.scan(HTML_COMMENT_RE)
@tree.children << Element.new(:xml_comment, result, :category => :span)
elsif result = @src.scan(HTML_INSTRUCTION_RE)
@tree.children << Element.new(:xml_pi, result, :category => :span)
elsif result = @src.scan(HTML_TAG_CLOSE_RE)
warning("Found invalidly used HTML closing tag for '#{@src[1]}' - ignoring it")
elsif result = @src.scan(HTML_TAG_RE)
return if HTML_BLOCK_ELEMENTS.include?(@src[1])
reset_pos = @src.pos
attrs = {}
@src[2].scan(HTML_ATTRIBUTE_RE).each {|name,sep,val| attrs[name] = val.gsub(/\n+/, ' ')}
do_parsing = (HTML_PARSE_AS_RAW.include?(@src[1]) || @tree.options[:parse_type] == :raw ? false : @doc.options[:parse_span_html])
if val = html_parse_type(attrs.delete('markdown'))
if val == :block
warning("Cannot use block level parsing in span level HTML tag - using default mode")
elsif val == :span
do_parsing = true
elsif val == :default
do_parsing = !HTML_PARSE_AS_RAW.include?(@src[1])
elsif val == :raw
do_parsing = false
end
end
el = Element.new(:html_element, @src[1], :attr => attrs, :category => :span, :parse_type => (do_parsing ? :span : :raw))
@tree.children << el
stop_re = /<\/#{Regexp.escape(@src[1])}\s*>/
if !@src[4] && HTML_ELEMENTS_WITHOUT_BODY.include?(el.value)
warning("The HTML tag '#{el.value}' cannot have any content - auto-closing it")
elsif !@src[4]
if parse_spans(el, stop_re, (do_parsing ? nil : [:span_html]))
@src.scan(stop_re)
else
warning("Found no end tag for '#{el.value}' - auto-closing it")
add_text(@src.scan(/.*/m), el)
end
end
Kramdown::Parser::Html::ElementConverter.new(@doc).process(el) if @doc.options[:html_to_native]
else
add_text(@src.scan(/./))
end
end
define_parser(:span_html, HTML_SPAN_START, '<')
end
end
end

View File

@ -1,38 +0,0 @@
# -*- coding: utf-8 -*-
#
#--
# Copyright (C) 2009-2010 Thomas Leitner <t_leitner@gmx.at>
#
# This file is part of kramdown.
#
# kramdown is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#++
#
require 'kramdown/parser/html'
module Kramdown
module Parser
class Kramdown
# Parse the HTML entity at the current location.
def parse_html_entity
@src.pos += @src.matched_size
@tree.children << Element.new(:entity, ::Kramdown::Utils::Entities.entity(@src[1] || (@src[2] && @src[2].to_i) || @src[3].hex))
end
define_parser(:html_entity, Kramdown::Parser::Html::Constants::HTML_ENTITY_RE, '&')
end
end
end

View File

@ -1,38 +0,0 @@
# -*- coding: utf-8 -*-
#
#--
# Copyright (C) 2009-2010 Thomas Leitner <t_leitner@gmx.at>
#
# This file is part of kramdown.
#
# kramdown is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#++
#
module Kramdown
module Parser
class Kramdown
LINE_BREAK = /( |\\\\)(?=\n)/
# Parse the line break at the current location.
def parse_line_break
@src.pos += @src.matched_size
@tree.children << Element.new(:br)
end
define_parser(:line_break, LINE_BREAK, '( |\\\\)(?=\n)')
end
end
end

View File

@ -1,156 +0,0 @@
# -*- coding: utf-8 -*-
#
#--
# Copyright (C) 2009-2010 Thomas Leitner <t_leitner@gmx.at>
#
# This file is part of kramdown.
#
# kramdown is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#++
#
module Kramdown
module Parser
class Kramdown
PUNCTUATION_CHARS = "_.:,;!?-"
LINK_ID_CHARS = /[a-zA-Z0-9 #{PUNCTUATION_CHARS}]/
LINK_ID_NON_CHARS = /[^a-zA-Z0-9 #{PUNCTUATION_CHARS}]/
LINK_DEFINITION_START = /^#{OPT_SPACE}\[(#{LINK_ID_CHARS}+)\]:[ \t]*(?:<(.*?)>|([^\s]+))[ \t]*?(?:\n?[ \t]*?(["'])(.+?)\4[ \t]*?)?\n/
# Parse the link definition at the current location.
def parse_link_definition
@src.pos += @src.matched_size
link_id, link_url, link_title = @src[1].downcase, @src[2] || @src[3], @src[5]
warning("Duplicate link ID '#{link_id}' - overwriting") if @doc.parse_infos[:link_defs][link_id]
@doc.parse_infos[:link_defs][link_id] = [link_url, link_title]
true
end
define_parser(:link_definition, LINK_DEFINITION_START)
# This helper methods adds the approriate attributes to the element +el+ of type +a+ or +img+
# and the element itself to the <tt>@tree</tt>.
def add_link(el, href, title, alt_text = nil)
el.options[:attr] ||= {}
el.options[:attr]['title'] = title if title
if el.type == :a
el.options[:attr]['href'] = href
else
el.options[:attr]['src'] = href
el.options[:attr]['alt'] = alt_text
el.children.clear
end
@tree.children << el
end
LINK_TEXT_BRACKET_RE = /\\\[|\\\]|\[|\]/
LINK_INLINE_ID_RE = /\s*?\[(#{LINK_ID_CHARS}+)?\]/
LINK_INLINE_TITLE_RE = /\s*?(["'])(.+?)\1\s*?\)/
LINK_START = /!?\[(?=[^^])/
# Parse the link at the current scanner position. This method is used to parse normal links as
# well as image links.
def parse_link
result = @src.scan(LINK_START)
reset_pos = @src.pos
link_type = (result =~ /^!/ ? :img : :a)
# no nested links allowed
if link_type == :a && (@tree.type == :img || @tree.type == :a || @stack.any? {|t,s| t && (t.type == :img || t.type == :a)})
add_text(result)
return
end
el = Element.new(link_type)
stop_re = /\]|!?\[/
count = 1
found = parse_spans(el, stop_re) do
case @src.matched
when "[", "!["
count += 1
when "]"
count -= 1
end
count - el.children.select {|c| c.type == :img}.size == 0
end
if !found || (link_type == :a && el.children.empty?)
@src.pos = reset_pos
add_text(result)
return
end
alt_text = extract_string(reset_pos...@src.pos, @src)
conv_link_id = alt_text.gsub(/(\s|\n)+/m, ' ').gsub(LINK_ID_NON_CHARS, '').downcase
@src.scan(stop_re)
# reference style link or no link url
if @src.scan(LINK_INLINE_ID_RE) || !@src.check(/\(/)
link_id = (@src[1] || conv_link_id).downcase
if link_id.empty?
@src.pos = reset_pos
add_text(result)
elsif @doc.parse_infos[:link_defs].has_key?(link_id)
add_link(el, @doc.parse_infos[:link_defs][link_id].first, @doc.parse_infos[:link_defs][link_id].last, alt_text)
else
warning("No link definition for link ID '#{link_id}' found")
@src.pos = reset_pos
add_text(result)
end
return
end
# link url in parentheses
if @src.scan(/\(<(.*?)>/)
link_url = @src[1]
if @src.scan(/\)/)
add_link(el, link_url, nil, alt_text)
return
end
else
link_url = ''
re = /\(|\)|\s/
nr_of_brackets = 0
while temp = @src.scan_until(re)
link_url += temp
case @src.matched
when /\s/
break
when '('
nr_of_brackets += 1
when ')'
nr_of_brackets -= 1
break if nr_of_brackets == 0
end
end
link_url = link_url[1..-2]
if nr_of_brackets == 0
add_link(el, link_url, nil, alt_text)
return
end
end
if @src.scan(LINK_INLINE_TITLE_RE)
add_link(el, link_url, @src[2], alt_text)
else
@src.pos = reset_pos
add_text(result)
end
end
define_parser(:link, LINK_START, '!?\[')
end
end
end

View File

@ -1,232 +0,0 @@
# -*- coding: utf-8 -*-
#
#--
# Copyright (C) 2009-2010 Thomas Leitner <t_leitner@gmx.at>
#
# This file is part of kramdown.
#
# kramdown is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#++
#
require 'kramdown/parser/kramdown/blank_line'
require 'kramdown/parser/kramdown/eob'
require 'kramdown/parser/kramdown/horizontal_rule'
require 'kramdown/parser/kramdown/attribute_list'
module Kramdown
module Parser
class Kramdown
# Used for parsing the first line of a list item or a definition, i.e. the line with list item
# marker or the definition marker.
def parse_first_list_line(indentation, content)
if content =~ /^\s*(#{IAL_SPAN_START})?\s*\n/
indentation = 4
else
while content =~ /^ *\t/
temp = content.scan(/^ */).first.length + indentation
content.sub!(/^( *)(\t+)/) {$1 + " "*(4 - (temp % 4)) + " "*($2.length - 1)*4}
end
indentation += content.scan(/^ */).first.length
end
content.sub!(/^\s*/, '')
indent_re = /^ {#{indentation}}/
content_re = /^(?:(?:\t| {4}){#{indentation / 4}} {#{indentation % 4}}|(?:\t| {4}){#{indentation / 4 + 1}}).*?\n/
[content, indentation, content_re, indent_re]
end
LIST_START_UL = /^(#{OPT_SPACE}[+*-])([\t| ].*?\n)/
LIST_START_OL = /^(#{OPT_SPACE}\d+\.)([\t| ].*?\n)/
LIST_START = /#{LIST_START_UL}|#{LIST_START_OL}/
# Parse the ordered or unordered list at the current location.
def parse_list
if @tree.children.last && @tree.children.last.type == :p # last element must not be a paragraph
return false
end
type, list_start_re = (@src.check(LIST_START_UL) ? [:ul, LIST_START_UL] : [:ol, LIST_START_OL])
list = new_block_el(type)
item = nil
indent_re = nil
content_re = nil
eob_found = false
nested_list_found = false
while !@src.eos?
if @src.check(HR_START)
break
elsif @src.scan(list_start_re)
item = Element.new(:li)
item.value, indentation, content_re, indent_re = parse_first_list_line(@src[1].length, @src[2])
list.children << item
item.value.sub!(/^#{IAL_SPAN_START}\s*/) do |match|
parse_attribute_list($~[1], item.options[:ial] ||= {})
''
end
list_start_re = (type == :ul ? /^( {0,#{[3, indentation - 1].min}}[+*-])([\t| ].*?\n)/ :
/^( {0,#{[3, indentation - 1].min}}\d+\.)([\t| ].*?\n)/)
nested_list_found = false
elsif result = @src.scan(content_re)
result.sub!(/^(\t+)/) { " "*4*($1 ? $1.length : 0) }
result.sub!(indent_re, '')
if !nested_list_found && result =~ LIST_START
parse_blocks(item, item.value)
if item.children.length == 1 && item.children.first.type == :p
item.value = ''
else
item.children.clear
end
nested_list_found = true
end
item.value << result
elsif result = @src.scan(BLANK_LINE)
nested_list_found = true
item.value << result
elsif @src.scan(EOB_MARKER)
eob_found = true
break
else
break
end
end
@tree.children << list
last = nil
list.children.each do |it|
temp = Element.new(:temp)
parse_blocks(temp, it.value)
it.children += temp.children
it.value = nil
next if it.children.size == 0
if it.children.first.type == :p && (it.children.length < 2 || it.children[1].type != :blank ||
(it == list.children.last && it.children.length == 2 && !eob_found)) &&
(list.children.last != it || list.children.size == 1 ||
list.children[0..-2].any? {|cit| cit.children.first.type != :p || cit.children.first.options[:transparent]})
it.children.first.children.first.value += "\n" if it.children.size > 1 && it.children[1].type != :blank
it.children.first.options[:transparent] = true
end
if it.children.last.type == :blank
last = it.children.pop
else
last = nil
end
end
@tree.children << last if !last.nil? && !eob_found
true
end
define_parser(:list, LIST_START)
DEFINITION_LIST_START = /^(#{OPT_SPACE}:)([\t| ].*?\n)/
# Parse the ordered or unordered list at the current location.
def parse_definition_list
children = @tree.children
if !children.last || (children.length == 1 && children.last.type != :p ) ||
(children.length >= 2 && children[-1].type != :p && (children[-1].type != :blank || children[-1].value != "\n" || children[-2].type != :p))
return false
end
first_as_para = false
deflist = new_block_el(:dl)
para = @tree.children.pop
if para.type == :blank
para = @tree.children.pop
first_as_para = true
end
para.children.first.value.split("\n").each do |term|
el = Element.new(:dt)
el.children << Element.new(:raw_text, term)
deflist.children << el
end
item = nil
indent_re = nil
content_re = nil
def_start_re = DEFINITION_LIST_START
while !@src.eos?
if @src.scan(def_start_re)
item = Element.new(:dd)
item.options[:first_as_para] = first_as_para
item.value, indentation, content_re, indent_re = parse_first_list_line(@src[1].length, @src[2])
deflist.children << item
item.value.sub!(/^#{IAL_SPAN_START}\s*/) do |match|
parse_attribute_list($~[1], item.options[:ial] ||= {})
''
end
def_start_re = /^( {0,#{[3, indentation - 1].min}}:)([\t| ].*?\n)/
first_as_para = false
elsif result = @src.scan(content_re)
result.sub!(/^(\t+)/) { " "*4*($1 ? $1.length : 0) }
result.sub!(indent_re, '')
item.value << result
first_as_para = false
elsif result = @src.scan(BLANK_LINE)
first_as_para = true
item.value << result
else
break
end
end
last = nil
deflist.children.each do |it|
next if it.type == :dt
parse_blocks(it, it.value)
it.value = nil
next if it.children.size == 0
if it.children.last.type == :blank
last = it.children.pop
else
last = nil
end
if it.children.first.type == :p && !it.options.delete(:first_as_para)
it.children.first.children.first.value += "\n" if it.children.size > 1
it.children.first.options[:transparent] = true
end
end
if @tree.children.length >= 1 && @tree.children.last.type == :dl
@tree.children[-1].children += deflist.children
elsif @tree.children.length >= 2 && @tree.children[-1].type == :blank && @tree.children[-2].type == :dl
@tree.children.pop
@tree.children[-1].children += deflist.children
else
@tree.children << deflist
end
@tree.children << last if !last.nil?
true
end
define_parser(:definition_list, DEFINITION_LIST_START)
end
end
end

View File

@ -1,53 +0,0 @@
# -*- coding: utf-8 -*-
#
#--
# Copyright (C) 2009-2010 Thomas Leitner <t_leitner@gmx.at>
#
# This file is part of kramdown.
#
# kramdown is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#++
#
module Kramdown
module Parser
class Kramdown
BLOCK_MATH_START = /^#{OPT_SPACE}(\\)?\$\$(.*?)\$\$\s*?\n/m
# Parse the math block at the current location.
def parse_block_math
if @src[1]
@src.scan(/^#{OPT_SPACE}\\/)
return false
end
@src.pos += @src.matched_size
@tree.children << new_block_el(:math, @src[2], :category => :block)
true
end
define_parser(:block_math, BLOCK_MATH_START)
INLINE_MATH_START = /\$\$(.*?)\$\$/
# Parse the inline math at the current location.
def parse_inline_math
@src.pos += @src.matched_size
@tree.children << Element.new(:math, @src[1], :category => :span)
end
define_parser(:inline_math, INLINE_MATH_START, '\$')
end
end
end

View File

@ -1,44 +0,0 @@
# -*- coding: utf-8 -*-
#
#--
# Copyright (C) 2009-2010 Thomas Leitner <t_leitner@gmx.at>
#
# This file is part of kramdown.
#
# kramdown is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#++
#
module Kramdown
module Parser
class Kramdown
PARAGRAPH_START = /^#{OPT_SPACE}[^ \t].*?\n/
# Parse the paragraph at the current location.
def parse_paragraph
@src.pos += @src.matched_size
if @tree.children.last && @tree.children.last.type == :p
@tree.children.last.children.first.value << "\n" << @src.matched.chomp
else
@tree.children << new_block_el(:p)
add_text(@src.matched.lstrip.chomp, @tree.children.last)
end
true
end
define_parser(:paragraph, PARAGRAPH_START)
end
end
end

View File

@ -1,214 +0,0 @@
# -*- coding: utf-8 -*-
#
#--
# Copyright (C) 2009-2010 Thomas Leitner <t_leitner@gmx.at>
#
# This file is part of kramdown.
#
# kramdown is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#++
#
#--
# Parts of this file are based on code from Maruku by Andrea Censi.
# The needed license statements follow:
#
# Copyright (C) 2006 Andrea Censi <andrea (at) rubyforge.org>
#
# Maruku is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# Maruku is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with Maruku; if not, write to the Free Software
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
#
# NOTA BENE:
#
# The following algorithm is a rip-off of RubyPants written by
# Christian Neukirchen.
#
# RubyPants is a Ruby port of SmartyPants written by John Gruber.
#
# This file is distributed under the GPL, which I guess is compatible
# with the terms of the RubyPants license.
#
# -- Andrea Censi
#
# = RubyPants -- SmartyPants ported to Ruby
#
# Ported by Christian Neukirchen <mailto:chneukirchen@gmail.com>
# Copyright (C) 2004 Christian Neukirchen
#
# Incooporates ideas, comments and documentation by Chad Miller
# Copyright (C) 2004 Chad Miller
#
# Original SmartyPants by John Gruber
# Copyright (C) 2003 John Gruber
#
#
# = RubyPants -- SmartyPants ported to Ruby
#
#
# [snip]
#
# == Authors
#
# John Gruber did all of the hard work of writing this software in
# Perl for Movable Type and almost all of this useful documentation.
# Chad Miller ported it to Python to use with Pyblosxom.
#
# Christian Neukirchen provided the Ruby port, as a general-purpose
# library that follows the *Cloth API.
#
#
# == Copyright and License
#
# === SmartyPants license:
#
# Copyright (c) 2003 John Gruber
# (http://daringfireball.net)
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions
# are met:
#
# * Redistributions of source code must retain the above copyright
# notice, this list of conditions and the following disclaimer.
#
# * Redistributions in binary form must reproduce the above copyright
# notice, this list of conditions and the following disclaimer in
# the documentation and/or other materials provided with the
# distribution.
#
# * Neither the name "SmartyPants" nor the names of its contributors
# may be used to endorse or promote products derived from this
# software without specific prior written permission.
#
# This software is provided by the copyright holders and contributors
# "as is" and any express or implied warranties, including, but not
# limited to, the implied warranties of merchantability and fitness
# for a particular purpose are disclaimed. In no event shall the
# copyright owner or contributors be liable for any direct, indirect,
# incidental, special, exemplary, or consequential damages (including,
# but not limited to, procurement of substitute goods or services;
# loss of use, data, or profits; or business interruption) however
# caused and on any theory of liability, whether in contract, strict
# liability, or tort (including negligence or otherwise) arising in
# any way out of the use of this software, even if advised of the
# possibility of such damage.
#
# === RubyPants license
#
# RubyPants is a derivative work of SmartyPants and smartypants.py.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions
# are met:
#
# * Redistributions of source code must retain the above copyright
# notice, this list of conditions and the following disclaimer.
#
# * Redistributions in binary form must reproduce the above copyright
# notice, this list of conditions and the following disclaimer in
# the documentation and/or other materials provided with the
# distribution.
#
# This software is provided by the copyright holders and contributors
# "as is" and any express or implied warranties, including, but not
# limited to, the implied warranties of merchantability and fitness
# for a particular purpose are disclaimed. In no event shall the
# copyright owner or contributors be liable for any direct, indirect,
# incidental, special, exemplary, or consequential damages (including,
# but not limited to, procurement of substitute goods or services;
# loss of use, data, or profits; or business interruption) however
# caused and on any theory of liability, whether in contract, strict
# liability, or tort (including negligence or otherwise) arising in
# any way out of the use of this software, even if advised of the
# possibility of such damage.
#
# == Links
#
# John Gruber:: http://daringfireball.net
# SmartyPants:: http://daringfireball.net/projects/smartypants
#
# Chad Miller:: http://web.chad.org
#
# Christian Neukirchen:: http://kronavita.de/chris
#
#++
#
module Kramdown
module Parser
class Kramdown
SQ_PUNCT = '[!"#\$\%\'()*+,\-.\/:;<=>?\@\[\\\\\]\^_`{|}~]'
SQ_CLOSE = %![^\ \\\\\t\r\n\\[{(-]!
SQ_RULES = [
[/("|')(?=#{SQ_PUNCT}\B)/, [:rquote1]],
# Special case for double sets of quotes, e.g.:
# <p>He said, "'Quoted' words in a larger quote."</p>
[/(\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. "<i>Custer</i>'s Last Stand."
[/("|')(\s|s\b|$)/, [:rquote1, 2]],
# 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
regexp, substs = SQ_RULES.find {|reg, subst| @src.scan(reg)}
substs.each do |subst|
if subst.kind_of?(Integer)
add_text(@src[subst].to_s)
else
val = SQ_SUBSTS[[subst, @src[subst.to_s[-1,1].to_i]]] || subst
@tree.children << Element.new(:smart_quote, val)
end
end
end
define_parser(:smart_quotes, SMART_QUOTES_RE, '[^\\\\]?["\']')
end
end
end

View File

@ -1,126 +0,0 @@
# -*- coding: utf-8 -*-
#
#--
# Copyright (C) 2009-2010 Thomas Leitner <t_leitner@gmx.at>
#
# This file is part of kramdown.
#
# kramdown is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#++
#
require 'kramdown/parser/kramdown/blank_line'
require 'kramdown/parser/kramdown/eob'
require 'kramdown/parser/kramdown/horizontal_rule'
module Kramdown
module Parser
class Kramdown
TABLE_SEP_LINE = /^#{OPT_SPACE}(?:\||\+)([ ]?:?-[+|: -]*)[ \t]*\n/
TABLE_HSEP_ALIGN = /[ ]?(:?)-+(:?)[ ]?/
TABLE_FSEP_LINE = /^#{OPT_SPACE}(\||\+)[ ]?:?=[+|: =]*[ \t]*\n/
TABLE_ROW_LINE = /^#{OPT_SPACE}\|(.*?)[ \t]*\n/
TABLE_START = /^#{OPT_SPACE}\|(?:-|(?!=))/
# Parse the table at the current location.
def parse_table
orig_pos = @src.pos
table = new_block_el(:table, nil, :alignment => [])
@src.scan(TABLE_SEP_LINE)
rows = []
has_footer = false
columns = 0
add_container = lambda do |type, force|
if force || type != :tbody || !has_footer
cont = Element.new(type)
cont.children, rows = rows, []
table.children << cont
end
end
while !@src.eos?
if @src.scan(TABLE_SEP_LINE) && !rows.empty?
if table.options[:alignment].empty? && !has_footer
add_container.call(:thead, false)
table.options[:alignment] = @src[1].scan(TABLE_HSEP_ALIGN).map do |left, right|
(left.empty? && right.empty? && :default) || (right.empty? && :left) || (left.empty? && :right) || :center
end
else # treat as normal separator line
add_container.call(:tbody, false)
end
elsif @src.scan(TABLE_FSEP_LINE)
add_container.call(:tbody, true) if !rows.empty?
has_footer = true
elsif @src.scan(TABLE_ROW_LINE)
trow = Element.new(:tr)
cells = (@src[1] + ' ').split(/\|/)
i = 0
while i < cells.length - 1
backslashes = cells[i].scan(/\\+$/).first
if backslashes && backslashes.length % 2 == 1
cells[i] = cells[i].chop + '|' + cells[i+1]
cells.delete_at(i+1)
else
i += 1
end
end
cells.pop if cells.last.strip.empty?
cells.each do |cell_text|
tcell = Element.new(:td)
tcell.children << Element.new(:raw_text, cell_text.strip)
trow.children << tcell
end
columns = [columns, cells.length].max
rows << trow
else
break
end
end
add_container.call(has_footer ? :tfoot : :tbody, false) if !rows.empty?
if !table.children.any? {|c| c.type == :tbody}
warning("Found table without body - ignoring it")
@src.pos = orig_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
row.children.each {|el| el.type = :th} if kind.type == :thead
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

View File

@ -1,52 +0,0 @@
# -*- coding: utf-8 -*-
#
#--
# Copyright (C) 2009-2010 Thomas Leitner <t_leitner@gmx.at>
#
# This file is part of kramdown.
#
# kramdown is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#++
#
module Kramdown
module Parser
class Kramdown
TYPOGRAPHIC_SYMS = [['---', :mdash], ['--', :ndash], ['...', :hellip],
['\\<<', '&lt;&lt;'], ['\\>>', '&gt;&gt;'],
['<< ', :laquo_space], [' >>', :raquo_space],
['<<', :laquo], ['>>', :raquo]]
TYPOGRAPHIC_SYMS_SUBST = Hash[*TYPOGRAPHIC_SYMS.flatten]
TYPOGRAPHIC_SYMS_RE = /#{TYPOGRAPHIC_SYMS.map {|k,v| Regexp.escape(k)}.join('|')}/
# Parse the typographic symbols at the current location.
def parse_typographic_syms
@src.pos += @src.matched_size
val = TYPOGRAPHIC_SYMS_SUBST[@src.matched]
if val.kind_of?(Symbol)
@tree.children << Element.new(:typographic_sym, val)
elsif @src.matched == '\\<<'
@tree.children << Element.new(:entity, ::Kramdown::Utils::Entities.entity('lt'))
@tree.children << Element.new(:entity, ::Kramdown::Utils::Entities.entity('lt'))
else
@tree.children << Element.new(:entity, ::Kramdown::Utils::Entities.entity('gt'))
@tree.children << Element.new(:entity, ::Kramdown::Utils::Entities.entity('gt'))
end
end
define_parser(:typographic_syms, TYPOGRAPHIC_SYMS_RE, '--|\\.\\.\\.|(?:\\\\| )?(?:<<|>>)')
end
end
end

View File

@ -1,36 +0,0 @@
# -*- coding: utf-8 -*-
#
#--
# Copyright (C) 2009-2010 Thomas Leitner <t_leitner@gmx.at>
#
# This file is part of kramdown.
#
# kramdown is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#++
#
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'
end
end

View File

@ -1,338 +0,0 @@
# -*- coding: utf-8 -*-
#
#--
# Copyright (C) 2009-2010 Thomas Leitner <t_leitner@gmx.at>
#
# This file is part of kramdown.
#
# kramdown is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#++
#
module Kramdown
module Utils
module Entities
class Entity < Struct.new(:code_point, :name)
def char
[code_point].pack('U*') rescue nil
end
end
ENTITY_TABLE = [
[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'],
[982, 'piv'],
[8230, 'hellip'],
[8242, '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, 'cup'],
[8746, 'cap'],
[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'],
[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'],
[171, 'laquo'],
[187, 'raquo'],
[174, 'reg'],
[170, 'ordf'],
[172, 'not'],
[176, 'deg'],
[177, 'plusmn'],
[180, 'acute'],
[181, 'micro'],
[182, 'para'],
[183, 'middot'],
[186, 'ordm'],
[162, 'cent'],
[185, 'sup1'],
[178, 'sup2'],
[179, 'sup3'],
[189, 'frac12'],
[188, 'frac14'],
[190, 'frac34'],
[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'],
[381, 'Zcaron'],
[382, 'zcaron'],
[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],
]
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|
if data.kind_of?(String)
ENTITY_MAP[code_point] = ENTITY_MAP[data] = Entity.new(code_point, data)
else
raise "No entity object for code point #{data} found" unless ENTITY_MAP.has_key?(data)
ENTITY_MAP[code_point] = ENTITY_MAP[data]
end
end
# Return the entity for the given +point_or_name+.
def entity(point_or_name)
ENTITY_MAP[point_or_name]
end
module_function :entity
end
end
end

View File

@ -1,72 +0,0 @@
# -*- coding: utf-8 -*-
#
#--
# Copyright (C) 2009-2010 Thomas Leitner <t_leitner@gmx.at>
#
# This file is part of kramdown.
#
# kramdown is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#++
#
module Kramdown
module Utils
module HTML
# Convert the +entity+ to a string.
def entity_to_str(e)
if RUBY_VERSION >= '1.9' && (c = e.char.encode(@doc.parse_infos[:encoding]) rescue nil) && !ESCAPE_MAP.has_key?(c)
c
elsif @doc.options[:numeric_entities] || e.name.nil?
"&##{e.code_point};"
else
"&#{e.name};"
end
end
# Return the string with the attributes of the element +el+.
def html_attributes(el)
(el.options[:attr] || {}).map {|k,v| v.nil? ? '' : " #{k}=\"#{escape_html(v.to_s, :no_entities)}\"" }.sort.join('')
end
ESCAPE_MAP = {
'<' => '&lt;',
'>' => '&gt;',
'&' => '&amp;',
'"' => '&quot;'
}
ESCAPE_ALL_RE = Regexp.union(*ESCAPE_MAP.collect {|k,v| k})
ESCAPE_NO_ENTITIES_RE = Regexp.union(REXML::Parsers::BaseParser::REFERENCE_RE, ESCAPE_ALL_RE)
ESCAPE_NORMAL = Regexp.union(REXML::Parsers::BaseParser::REFERENCE_RE, /<|>|&/)
ESCAPE_RE_FROM_TYPE = {
:all => ESCAPE_ALL_RE,
:no_entities => ESCAPE_NO_ENTITIES_RE,
:text => ESCAPE_NORMAL
}
# Escape the special HTML characters in the string +str+. The parameter +type+ specifies what
# is escaped: <tt>:all</tt> - all special HTML characters as well as entities,
# <tt>:no_entities</tt> - all special HTML characters but no entities, <tt>:text</tt> - all
# special HTML characters except 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
end
end
end

View File

@ -1,28 +0,0 @@
# -*- coding: utf-8 -*-
#
#--
# Copyright (C) 2009-2010 Thomas Leitner <t_leitner@gmx.at>
#
# This file is part of kramdown.
#
# kramdown is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#++
#
module Kramdown
# The kramdown version.
VERSION = '0.9.0'
end

View File

@ -1,44 +0,0 @@
body div#container {
margin : 0 auto;
max-width : 920px;
background-color : #f8f8f8;
padding : .7em;
font-size : 13.34px;
font-family : verdana, sans-serif;
border : 1px #E0E0E0 solid;
}
body div#container h2, body div#container h3, body div#content h4 {
padding-top : 10px;
border-top : 4px solid #E0E0E0;
}
body div#container pre {
padding : 5px;
border-style : solid;
border-width : 1px;
border-color : #E0E0E0;
background-color : #F8F8FF;
}
body div#container pre code {
padding : 5px;
background-color : #F8F8FF;
border : none;
}
body div#container code {
font-family : courier, fixed;
display : inline-block;
padding : 0px 2px 0px 2px;
background-color : #F8F8FF;
border : 1px #E0E0E0 solid;
}
body h4#title {
font-family : verdana, sans-serif;
display : block;
margin : 0 auto;
width : 920px;
}

View File

@ -1,487 +0,0 @@
@media print {
/* print.css from blueprint CSS framework */
.page {
margin-left: 0.1in;
margin-right: 0.1in;
margin-top: 2in;
}
body {
line-height:1.5;
font-family:"Helvetica Neue", Arial, Helvetica, sans-serif;
font-family: Georgia, Times, serif;
color:#000;
background:none;
font-size:12pt;
}
.container {
background:none;
}
hr {
background:#ccc;
color:#ccc;
width:100%;
height:2px;
margin:2em 0;
padding:0;
border:none;
}
hr.space {
background:#fff;
color:#fff;
visibility:hidden;
}
h1, h2, h3, h4, h5, h6 {
font-family:"Helvetica Neue", Arial, "Lucida Grande", sans-serif;
}
code {
font:.9em "Courier New", Monaco, Courier, monospace;
}
pre {
line-height:0.8;
}
a img {
border:none;
}
p img.top {
margin-top:0;
}
blockquote {
margin:1.5em;
padding:1em;
font-style:italic;
font-size:.9em;
}
.small {
font-size:.9em;
}
.large {
font-size:1.1em;
}
.quiet {
color:#999;
}
.hide {display:none;}
a:link, a:visited {
background:transparent;font-weight:700;text-decoration:underline;
}
a:link:after, a:visited:after {
content:" (" attr(href) ")";font-size:90%;
}
table {
page-break-inside: avoid;
}
}
@media screen {
/* screen.css from Safari Reader */
h1.title {
font-family: Palatino, Georgia, Times, "Times New Roman", serif;
font-weight: bold;
font-size: 1.33em;
line-height: 1.25em;
}
h1 {
font-size: 1.25em;
}
h2 {
font-size: 1.125em;
}
h3 {
font-size: 1.05em;
}
.page a {
text-decoration: none;
color: rgb(32, 0, 127);
}
.page a:visited {
color: rgb(32, 0, 127);
}
#article img {
/* Float images to the left, so that text will nicely flow around them. */
float: left;
margin-right: 12px;
}
#article img.reader-image-tiny {
/* Don't float very small images -- let them display where they occur in the text. */
float: none;
margin: 0;
}
#article img.reader-image-large {
float: none;
margin: auto;
display: block;
}
.float {
margin: 8px 0;
font-size: 65%;
line-height: 1.4;
text-align: left;
}
.float.left {
float: left;
margin-right: 20px;
}
.float.right {
float: right;
margin-left: 20px;
}
.float.full-width {
float: none;
display: block;
}
.page {
font: 20px Palatino, Georgia, Times, "Times New Roman", serif;
line-height: 160%;
text-align: justify;
}
.page:first-of-type .title {
display: block;
}
.page table {
font-size: 0.9em;
text-align: left;
}
.page.rtl table {
text-align: right;
}
.page-number {
display: none;
}
.title {
display: none;
}
body {
margin: 0;
padding: 0;
background-color: transparent;
-webkit-user-select: none;
}
.cached embed, .cached applet, .cached object {
display: none !important;
}
#background {
background-color: rgba(0, 0, 0, 0.8);
-webkit-transform: translateZ(0);
position: fixed;
top: 0;
bottom: 0;
left: 0;
right: 0;
}
#container {
margin-left: -431px;
left: 50%;
width: 862px;
height: 100%;
position: absolute;
pointer-events: none;
}
#centered {
position: absolute;
top: 0;
width: 100%;
height: 100%;
z-index: 0;
}
.preloading #background {
opacity: 0;
}
.preloading #centered {
-webkit-transform: translate3d(0, 100%, 0);
}
.activating #background {
-webkit-transition: opacity 0.40s ease-out;
opacity: 1.0;
}
.activating #fade-top {
-webkit-animation-name: fadeTopActivationFadeIn;
-webkit-animation-duration: 0.40s;
-webkit-animation-timing-function: ease-out;
}
.activating.skip-transition #fade-top {
-webkit-animation: none !important;
}
@-webkit-keyframes fadeTopActivationFadeIn {
0% {
opacity: 0;
}
80% {
opacity: 0;
}
100% {
opacity: 1;
}
}
.activating.skip-transition #background {
-webkit-transition: none !important;
}
.activating #centered {
-webkit-transition: -webkit-transform 0.40s ease-out;
-webkit-transform: translate3d(0, 0, 0);
}
.activating.skip-transition #centered {
-webkit-transition: none !important;
}
.deactivating #background {
-webkit-transition: opacity 0.40s ease-in;
opacity: 0;
}
.deactivating #fade-top {
opacity: 0;
}
.deactivating #fade-bottom {
-webkit-transition: opacity 0.40s ease-in;
opacity: 0;
}
.deactivating #centered {
-webkit-transition: -webkit-transform 0.40s ease-in;
-webkit-transform: translate3d(0, 100%, 0);
}
.deactivating #hud {
-webkit-transition: opacity 0.25s ease-in;
opacity: 0 !important;
}
#drop-shadow {
position: absolute;
top: 0;
bottom: 0;
width: 800px;
left: 10px;
border-width: 24px 24px;
-webkit-border-image: url(safari-resource:/ReaderDropShadow.png) 24 24 24 24 stretch stretch;
opacity: 0;
}
#hud {
position: fixed;
width: 314px;
height: 72px;
left: 50%;
margin-left: -157px;
bottom: 30px;
background: rgba(0, 0, 0, 0.75);
-webkit-border-radius: 12px;
z-index: 100;
opacity: 0;
-webkit-transition: opacity 0.75s;
pointer-events: auto;
zoom: reset;
}
#hud button {
display: block;
float: left;
width: 48px;
height: 48px;
padding: 0;
border: none;
margin: 12px 5px;
}
#hud button:first-of-type {
margin-left: 12px;
}
#hud button:last-of-type {
margin-right: 12px;
}
#hud-zoom-out {
background: url(safari-resource:/ReaderHUDZoomOutInactive.png) no-repeat;
}
#hud-zoom-out:active {
background: url(safari-resource:/ReaderHUDZoomOutActive.png) no-repeat;
}
#hud-zoom-in {
background: url(safari-resource:/ReaderHUDZoomInInactive.png) no-repeat;
}
#hud-zoom-in:active {
background: url(safari-resource:/ReaderHUDZoomInActive.png) no-repeat;
}
#hud-mail {
background: url(safari-resource:/ReaderHUDMailContentsInactive.png) no-repeat;
}
#hud-mail:active {
background: url(safari-resource:/ReaderHUDMailContentsActive.png) no-repeat;
}
#hud-print {
background: url(safari-resource:/ReaderHUDPrintInactive.png) no-repeat;
}
#hud-print:active {
background: url(safari-resource:/ReaderHUDPrintActive.png) no-repeat;
}
#hud-exit {
background: url(safari-resource:/ReaderHUDCloseInactive.png) no-repeat;
}
#hud-exit:active {
background: url(safari-resource:/ReaderHUDCloseActive.png) no-repeat;
}
#article {
/* The width of 819px here includes 19px for the WebKit scrollbar's width. */
/* The padding-right of 8px separates the scrollbar from the article itself. */
position: absolute;
height: 100%;
left: 34px;
width: 819px;
padding-right: 8px;
overflow: scroll;
z-index: 0;
outline: none;
pointer-events: auto;
-webkit-user-select: auto;
-webkit-transform: translateZ(0);
}
.article-fade {
position: absolute;
left: 34px;
height: 36px;
width: 800px;
z-index: 10;
pointer-events: none;
}
#fade-top {
top: 0;
background: url(safari-resource:/ReaderFadeTop.png) repeat-x;
}
#fade-bottom {
bottom: 0;
background: url(safari-resource:/ReaderFadeBottom.png) repeat-x;
}
#resize-indicator {
position: fixed;
bottom: 0;
right: 0;
width: 12px;
height: 12px;
background: url(safari-resource:/TopSitesCornerResize.png);
}
.page:only-of-type .page-number {
display: none;
}
.page-number {
display: block;
font: bold 11px Helvetica, sans-serif;
margin-left: 12px;
color: #B2B2B2;
position: absolute;
right: 10px;
top: 10px;
-webkit-user-select: none;
}
.page:first-of-type {
margin-top: 22px;
}
.page:last-of-type {
margin-bottom: 22px;
}
.page {
width: 658px;
margin-left: auto;
margin-right: auto;
margin-top: 10px;
padding: 45px 70px;
color: black;
background: white;
border: 1px solid #c3c3c3;
position: relative;
overflow: hidden;
-webkit-transition: height .5s ease-out;
}
.page.rtl {
direction: rtl;
}
#incoming-page-placeholder {
height: 30px;
margin-bottom: 0;
}
#incoming-page-corner {
position: absolute;
right: 10px;
top: 8px;
}
#incoming-page-spinner {
width: 16px;
height: 16px;
float: right;
background: url(safari-resource:/ReaderSpinner.png);
}
#incoming-page-text {
float: right;
margin-top: 2px;
margin-left: 8px;
color: #B2B2B2;
font: bold 11px Helvetica, sans-serif;
-webkit-user-select: none;
}
#next-page-container {
position:absolute;
display: none;
}
.no-transition {
-webkit-transition: none !important;
}
}

View File

@ -1,50 +0,0 @@
body {
font-size:13px;
font-family:verdana, sans-serif;
line-height:1.5em;
}
h1 { font-size:1.6em; }
h2 { font-size:1.5em; }
h3 { font-size:1.4em; font-weight:300; }
h4 { font-size:1em; }
h1, h2, h3, h4 {
font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif;
margin-top:20px;
}
h2 {
padding-top:5px;
border-top:1px solid #AAA;
margin-top:40px;
}
p {
margin:15px 15px;
}
pre {
padding:5px;
border:1px solid #EEEEEE;
background-color:#F6F6F6;
margin:0 25px;
}
pre code {
padding:5px;
background-color:#F6F6F6;
border:none;
}
code {
font-family:courier, fixed;
display:inline-block;
padding:0px 2px 0px 2px;
background-color: #F6F6F6;
border:1px #E0E0E0 solid;
}
ul li {
margin:2px 0 0 0;
}

View File

@ -1,96 +0,0 @@
if !exists('g:VMPoutputformat')
let g:VMPoutputformat = 'html'
endif
if !exists('g:VMPoutputdirectory')
let g:VMPoutputdirectory = '/tmp'
endif
if !exists('g:VMPhtmlreader')
if has('mac')
let g:VMPhtmlreader = 'open'
elseif has('win32') || has('win64')
let g:VMPhtmlreader = 'start'
elseif has('unix') && executable('xdg-open')
let g:VMPhtmlreader = 'xdg-open'
else
let g:VMPhtmlreader = ''
end
endif
if !exists('g:VMPstylesheet')
let g:VMPstylesheet = 'github.css'
endif
function! PreviewMKD()
ruby << RUBY
runtime = Vim.evaluate('&runtimepath').split(',')
runtime.each { |path| $LOAD_PATH.unshift(File.join(path, 'plugin', 'vim-markdown-preview')) }
css_base = runtime.detect { |path| File.exists? File.join(path, 'plugin', 'vmp.vim') }
stylesheet = File.join(css_base, 'plugin', 'vim-markdown-preview', 'stylesheets',
Vim.evaluate('g:VMPstylesheet'))
name = Vim::Buffer.current.name.nil? ? 'Untitled' : File.basename(Vim::Buffer.current.name)
output_dir = Vim.evaluate('g:VMPoutputdirectory')
contents = Array.new(VIM::Buffer.current.count) { |i| VIM::Buffer.current[i + 1] }.join("\n")
require('kramdown/kramdown')
layout = <<-LAYOUT
<!DOCTYPE html
PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<link rel="stylesheet"
href="#{stylesheet}">
</link>
<title> #{name} </title>
</head>
<body>
<div id="container">
<div id="centered">
<div id="article">
<div class="page">
#{Kramdown::Document.new(contents).to_html}
</div>
</div>
</div>
</div>
</body>
</html>
LAYOUT
case Vim.evaluate('g:VMPoutputformat')
when 'html'
reader = Vim.evaluate('g:VMPhtmlreader')
if reader == ''
Vim.message('No suitable HTML reader found! Please set g:VMPhtmlreader.')
else
file = File.join(output_dir, name + '.html')
File.open(file, 'w') { |f| f.write(layout) }
Vim.command("silent ! #{reader} '%s'" % [ file ])
Vim.command 'redraw!'
end
when 'pdf'
Vim.message('output format not implemented yet.')
else
Vim.message('Unrecongized output format! Check g:VMPoutputformat.')
end
RUBY
endfunction
:command! Mm :call PreviewMKD()