require 'rexml/document'
require 'rubygems'
require 'syntax'
require 'syntax/convertors/html'
class Maruku
include REXML
# Render as an HTML fragment (no head, just the content of BODY). (returns a string)
def to_html
div = Element.new 'dummy'
children_to_html.each do |e|
div << e
end
# render footnotes
if @doc.meta[:footnotes_used]
div << render_footnotes
end
# REXML Bug? if indent!=-1 whitespace is not respected for 'pre' elements
# containing code.
xml =""
div.write_children(xml,indent=-1,transitive=false,ie_hack=true)
xml
end
# Render to a complete HTML document (returns a string)
def to_html_document
doc = to_html_document_tree
xml = ""
# REXML Bug? if indent!=-1 whitespace is not respected for 'pre' elements
# containing code.
doc.write(xml,indent=-1,transitive=false,ie_hack=true);
xhtml10strict = "
\n"
xhtml10strict + xml
end
# Render to a complete HTML document (returns a REXML document tree)
def to_html_document_tree
doc = Document.new(nil,{:respect_whitespace =>:all})
# doc << XMLDecl.new
root = Element.new('html', doc)
root.add_namespace('http://www.w3.org/1999/xhtml')
lang = @meta[:lang] || 'en'
root.attributes['lang'] = lang
root.attributes['xml:lang'] = lang
head = Element.new 'head', root
# Create title element
doc_title = @meta[:title] || @meta[:subject] || ""
title = Element.new 'title'
title << Text.new(doc_title)
head << title
css = @meta[:css]
if css
#
link = Element.new 'link'
link.attributes['type'] = 'text/css'
link.attributes['rel'] = 'stylesheet'
link.attributes['href'] = css
head << link
end
body = Element.new 'body'
children_to_html.each do |e|
body << e
end
# render footnotes
if @doc.meta[:footnotes_used]
body << render_footnotes
end
# When we are rendering a whole document, we add a signature
# at the bottom.
body << maruku_html_signature
root << head
root << body
doc
end
# returns "st","nd","rd" or "th" as appropriate
def day_suffix(day)
case day%10
when 1; 'st'
when 2; 'nd'
when 3; 'rd'
else 'th'
end
end
# formats a nice date
def nice_date
t = Time.now
t.strftime(" at %H:%M on ")+
t.strftime("%A, %B %d")+
day_suffix(t.day)+
t.strftime(", %Y")
end
def maruku_html_signature
div = Element.new 'div'
div.attributes['class'] = 'maruku_signature'
Element.new 'hr', div
span = Element.new 'span', div
span.attributes['style'] = 'font-size: small; font-style: italic'
span << Text.new('Created by ')
a = Element.new('a', span)
a.attributes['href'] = 'http://maruku.rubyforge.org'
a.attributes['title'] = 'Maruku: a Markdown interpreter'
a << Text.new('Maruku')
span << Text.new(nice_date+".")
div
end
def render_footnotes
div = Element.new 'div'
div.attributes['class'] = 'footnotes'
div << Element.new('hr')
ol = Element.new 'ol'
@doc.meta[:footnotes_used].each_with_index do |fid, i| num = i+1
f = @footnotes[fid]
if f
li = f.wrap_as_element('li')
li.attributes['id'] = "fn:#{num}"
a = Element.new 'a'
a.attributes['href'] = "#fnref:#{num}"
a.attributes['rev'] = 'footnote'
a<< Text.new('↩', true, nil, true)
li.children.last << a
ol << li
else
$stderr.puts "Could not find footnote '#{fid}'"
end
end
div << ol
div
end
end
class String
# A string is rendered into HTML by creating
# a REXML::Text node. REXML takes care of all the encoding.
def to_html
REXML::Text.new(self)
end
end
class MDElement
def to_html_hrule; Element.new 'hr' end
def to_html_linebreak; Element.new 'br' end
# renders children as html and wraps into an element of given name
#
# Sets 'id' if meta is set
def wrap_as_element(name)
m = create_html_element name
children_to_html.each do |e| m << e; end
m
end
def create_html_element(name)
m = Element.new name
if @meta[:id] then m.attributes['id'] = @meta[:id].to_s end
if @meta[:style] then m.attributes['style'] = @meta[:style].to_s end
if @meta[:class] then m.attributes['class'] = @meta[:class].to_s end
m
end
def to_html_paragraph; wrap_as_element('p') end
def to_html_ul
if @meta[:toc]
# render toc
html_toc = @doc.toc.to_html
return html_toc
else
wrap_as_element('ul')
end
end
def to_html_ol; wrap_as_element('ol') end
def to_html_li; wrap_as_element('li') end
def to_html_li_span; wrap_as_element('li') end
def to_html_quote; wrap_as_element('blockquote') end
def to_html_strong; wrap_as_element('strong') end
def to_html_emphasis; wrap_as_element('em') end
# nil if not applicable, else string
def section_number
return nil if not @doc.meta[:use_numbered_headers]
if (s = @meta[:section]) and not s.section_number.empty?
s.section_number.join('.')+". "
else
nil
end
end
# nil if not applicable, else SPAN element
def render_section_number
# if we are bound to a section, add section number
if num = section_number
span = Element.new 'span'
span.attributes['class'] = 'maruku_section_number'
span << Text.new(section_number)
span
else
nil
end
end
def to_html_header
element_name = "h#{@meta[:level]}"
h = wrap_as_element element_name
if span = render_section_number
h.insert_before(h.children.first, span)
end
h
end
def source2html(source)
source = source.gsub(/&/,'&')
source = Text.normalize(source)
Text.new(source, true, nil, false )
end
def to_html_code;
source = self.meta[:raw_code]
lang = self.meta[:lang] || @doc.meta[:code_lang]
lang = 'xml' if lang=='html'
use_syntax = @doc.meta[:html_use_syntax]
element =
if use_syntax && lang
begin
convertor = Syntax::Convertors::HTML.for_syntax lang
html = convertor.convert( source )
show_spaces = get_setting(:code_show_spaces)
if show_spaces
s.gsub!(/\t/,'»'+' '*3)
s.gsub!(/ /,'¬')
end
pre = Document.new(html, {:respect_whitespace =>:all}).root
pre.attributes['class'] = lang
pre
rescue Object => e
$stderr.puts "Error while using the syntax library for code:\n#{source.inspect}"
$stderr.puts "Lang is #{lang} object is: "
$stderr.puts @meta.inspect
$stderr.puts "Exception: #{e.class}: #{e.message}\n\t#{e.backtrace.join("\n\t")}"
to_html_code_using_pre(source)
end
else
to_html_code_using_pre(source)
end
color = get_setting(:code_background_color,DEFAULT_CODE_COLOR)
if color
element.attributes['style'] = "background-color: #{color};"
end
element
end
def to_html_code_using_pre(source)
pre = Element.new 'pre'
s = source
s = s.gsub(/&/,'&')
s = Text.normalize(s)
show_spaces = get_setting(:code_show_spaces)
if show_spaces
s.gsub!(/\t/,'»'+' '*3)
s.gsub!(/ /,'¬')
end
text = Text.new(s, true, nil, false )
pre << text
pre
end
def to_html_inline_code;
pre = Element.new 'tt'
source = self.meta[:raw_code]
pre << source2html(source)
color = get_setting(:code_background_color, DEFAULT_CODE_COLOR)
if color
pre.attributes['style'] = "background-color: #{color};"
end
pre
end
def to_html_immediate_link
a = Element.new 'a'
url = @meta[:url]
text = url
text = text.gsub(/^mailto:/,'') # don't show mailto
a << Text.new(text)
a.attributes['href'] = url
a
end
def to_html_link
a = wrap_as_element 'a'
id = @meta[:ref_id]
ref = @doc.refs[id]
if not ref
$stderr.puts "Could not find id = '#{id}'"
else
url = ref[:url]
title = ref[:title]
a.attributes['href'] = url
a.attributes['title'] = title if title
end
a
end
##### Email address
def obfuscate(s)
res = ''
s.each_byte do |char|
res += "%03d;" % char
end
res
end
def to_html_email_address
email = @meta[:email]
a = Element.new 'a'
#a.attributes['href'] = Text.new("mailto:"+obfuscate(email),false,nil,true)
#a.attributes.add Attribute.new('href',Text.new(
#"mailto:"+obfuscate(email),false,nil,true))
# Sorry, for the moment it doesn't work
a.attributes['href'] = "mailto:#{email}"
a << Text.new(obfuscate(email),false,nil,true)
a
end
##### Images
def to_html_image
a = Element.new 'img'
id = @meta[:ref_id]
ref = @doc.refs[id]
if not ref
$stderr.puts "Could not find id = '#{id}'"
else
url = ref[:url]
a.attributes['src'] = url
# puts ref.inspect
[:title, :class, :style].each do |s|
if ref[s] then
a.attributes[s.to_s] = ref[s]
end
end
end
a
end
def to_html_raw_html
if @meta[:parsed_html]
return @meta[:parsed_html].root
else # invalid
raw_html = @meta[:raw_html]
# Creates red box with offending HTML
$stderr.puts "Malformed HTML: #{raw_html}"
div = Element.new('pre')
div.attributes['style'] = 'border: solid 3px red; background-color: pink'
div.attributes['class'] = 'markdown-html-error'
div << Text.new("HTML parse error: \n#{raw_html}", true)
return div
end
end
def to_html_abbreviation
abbr = Element.new 'abbr'
abbr << Text.new(children[0])
abbr.attributes['title'] = self.meta[:title] if self.meta[:title]
abbr
end
def to_html_footnote_reference
id = @meta[:footnote_id]
# save the order of used footnotes
order = (@doc.meta[:footnotes_used] ||= [])
# take next number
order << id
num = order.size;
sup = Element.new 'sup'
sup.attributes['id'] = "fnref:#{num}"
a = Element.new 'a'
a << Text.new(num.to_s)
a.attributes['href'] = "\#fn:#{num}"
a.attributes['rel'] = 'footnote'
sup << a
sup
end
## Definition lists ###
def to_html_definition_list
wrap_as_element('dl')
end
def to_html_definition
children_to_html
end
def to_html_definition_term; wrap_as_element('dt') end
def to_html_definition_data; wrap_as_element('dd') end
## Table ###
def to_html_table
align = @meta[:align]
num_columns = align.size
head = @children.slice(0, num_columns)
rows = []
i = num_columns
while i<@children.size
rows << @children.slice(i, num_columns)
i+=num_columns
end
table = create_html_element 'table'
thead = Element.new 'thead'
tr = Element.new 'tr'
array_to_html(head).each do |x| tr<