Maruku Cleanup Branch

Use Nathan Weizenbaum's "cleanup" branch of
Maruku. There were a few evident problems
with his branch, so please report any
anomalies you see.
This commit is contained in:
Jacques Distler 2010-06-04 21:36:31 -05:00
parent dead710e69
commit 06ae79322a
36 changed files with 1871 additions and 3100 deletions

View file

@ -955,7 +955,7 @@ class WikiControllerTest < ActionController::TestCase
end end
def test_recursive_include def test_recursive_include
@wiki.write_page('wiki1', 'HomePage', 'Self-include: [[!include HomePage]]', Time.now, @wiki.write_page('wiki1', 'HomePage', "Self-include:\n\n [[!include HomePage]] ", Time.now,
Author.new('AnotherAuthor', '127.0.0.2'), x_test_renderer) Author.new('AnotherAuthor', '127.0.0.2'), x_test_renderer)
r = process('show', 'id' => 'HomePage', 'web' => 'wiki1') r = process('show', 'id' => 'HomePage', 'web' => 'wiki1')

261
vendor/plugins/maruku/data/entities.xml vendored Normal file
View file

@ -0,0 +1,261 @@
<!-- A conversion chart for html elements, courtesy of text2html -->
<chars>
<char num='913' name='Alpha' convertTo='$A$' />
<char num='914' name='Beta' convertTo='$B$' />
<char num='915' name='Gamma' convertTo='$\Gamma$' />
<char num='916' name='Delta' convertTo='$\Delta$' />
<char num='917' name='Epsilon' convertTo='$E$' />
<char num='918' name='Zeta' convertTo='$Z$' />
<char num='919' name='Eta' convertTo='$H$' />
<char num='920' name='Theta' convertTo='$\Theta$' />
<char num='921' name='Iota' convertTo='$I$' />
<char num='922' name='Kappa' convertTo='$K$' />
<char num='923' name='Lambda' convertTo='$\Lambda$' />
<char num='924' name='Mu' convertTo='$M$' />
<char num='925' name='Nu' convertTo='$N$' />
<char num='926' name='Xi' convertTo='$\Xi$' />
<char num='927' name='Omicron' convertTo='$O$' />
<char num='928' name='Pi' convertTo='$\Pi$' />
<char num='929' name='Rho' convertTo='$P$' />
<char num='931' name='Sigma' convertTo='$\Sigma$' />
<char num='932' name='Tau' convertTo='$T$' />
<char num='933' name='Upsilon' convertTo='$Y$' />
<char num='934' name='Phi' convertTo='$\Phi$' />
<char num='935' name='Chi' convertTo='$X$' />
<char num='936' name='Psi' convertTo='$\Psi$' />
<char num='937' name='Omega' convertTo='$\Omega$' />
<char num='945' name='alpha' convertTo='$\alpha$' />
<char num='946' name='beta' convertTo='$\beta$' />
<char num='947' name='gamma' convertTo='$\gamma$' />
<char num='948' name='delta' convertTo='$\delta$' />
<char num='949' name='epsilon' convertTo='$\epsilon$' />
<char num='950' name='zeta' convertTo='$\zeta$' />
<char num='951' name='eta' convertTo='$\eta$' />
<char num='952' name='theta' convertTo='$\theta$' />
<char num='953' name='iota' convertTo='$\iota$' />
<char num='954' name='kappa' convertTo='$\kappa$' />
<char num='955' name='lambda' convertTo='$\lambda$' />
<char num='956' name='mu' convertTo='$\mu$' />
<char num='957' name='nu' convertTo='$\nu$' />
<char num='958' name='xi' convertTo='$\xi$' />
<char num='959' name='omicron' convertTo='$o$' />
<char num='960' name='pi' convertTo='$\pi$' />
<char num='961' name='rho' convertTo='$\rho$' />
<char num='963' name='sigma' convertTo='$\sigma$' />
<char num='964' name='tau' convertTo='$\tau$' />
<char num='965' name='upsilon' convertTo='$\upsilon$' />
<char num='966' name='phi' convertTo='$\phi$' />
<char num='967' name='chi' convertTo='$\chi$' />
<char num='968' name='psi' convertTo='$\psi$' />
<char num='969' name='omega' convertTo='$\omega$' />
<char num='962' name='sigmaf' convertTo='$\varsigma$' />
<char num='977' name='thetasym' convertTo='$\vartheta$' />
<char num='982' name='piv' convertTo='$\varpi$' />
<char num='8230' name='hellip' convertTo='\ldots' />
<char num='8242' name='prime' convertTo='$\prime$' />
<char num='8254' name='oline' convertTo='-' />
<char num='8260' name='frasl' convertTo='/' />
<char num='8472' name='weierp' convertTo='$\wp$' />
<char num='8465' name='image' convertTo='$\Im$' />
<char num='8476' name='real' convertTo='$\Re$' />
<char num='8501' name='alefsym' convertTo='$\aleph$' />
<char num='8226' name='bull' convertTo='$\bullet$' />
<char num='8482' name='trade' convertTo='$^{\rm TM}$' /> <!-- exttrademark -->
<char num='8592' name='larr' convertTo='$\leftarrow$' />
<char num='8594' name='rarr' convertTo='$\rightarrow$' />
<char num='8593' name='uarr' convertTo='$\uparrow$' />
<char num='8595' name='darr' convertTo='$\downarrow$' />
<char num='8596' name='harr' convertTo='$\leftrightarrow$' />
<char num='8629' name='crarr' convertTo='$\hookleftarrow$' />
<char num='8657' name='uArr' convertTo='$\Uparrow$' />
<char num='8659' name='dArr' convertTo='$\Downarrow$' />
<char num='8656' name='lArr' convertTo='$\Leftarrow$' />
<char num='8658' name='rArr' convertTo='$\Rightarrow$' />
<char num='8660' name='hArr' convertTo='$\Leftrightarrow$' />
<char num='8704' name='forall' convertTo='$\forall$' />
<char num='8706' name='part' convertTo='$\partial$' />
<char num='8707' name='exist' convertTo='$\exists$' />
<char num='8709' name='empty' convertTo='$\emptyset$' />
<char num='8711' name='nabla' convertTo='$\nabla$' />
<char num='8712' name='isin' convertTo='$\in$' />
<char num='8715' name='ni' convertTo='$\ni$' />
<char num='8713' name='notin' convertTo='$\notin$' />
<char num='8721' name='sum' convertTo='$\sum$' />
<char num='8719' name='prod' convertTo='$\prod$' />
<char num='8722' name='minus' convertTo='$-$' />
<char num='8727' name='lowast' convertTo='$\ast$' />
<char num='8730' name='radic' convertTo='$\surd$' />
<char num='8733' name='prop' convertTo='$\propto$' />
<char num='8734' name='infin' convertTo='$\infty$' />
<char num='8736' name='ang' convertTo='$\angle$' />
<char num='8743' name='and' convertTo='$\wedge$' />
<char num='8744' name='or' convertTo='$\vee$' />
<char num='8745' name='cup' convertTo='$\cup$' />
<char num='8746' name='cap' convertTo='$\cap$' />
<char num='8747' name='int' convertTo='$\int$' />
<char num='8756' name='there4' convertTo='$\therefore$' package='amssymb' /> <!-- only AMS -->
<char num='8764' name='sim' convertTo='$\sim$' />
<char num='8776' name='asymp' convertTo='$\approx$' />
<char num='8773' name='cong' convertTo='$\cong$' />
<char num='8800' name='ne' convertTo='$\neq$' />
<char num='8801' name='equiv' convertTo='$\equiv$' />
<char num='8804' name='le' convertTo='$\leq$' />
<char num='8805' name='ge' convertTo='$\geq$' />
<char num='8834' name='sub' convertTo='$\subset$' />
<char num='8835' name='sup' convertTo='$\supset$' />
<!-- <char num='8838' name='sube' convertTo='$\subseteq$' />-->
<char num='8839' name='supe' convertTo='$\supseteq$' />
<!-- <char num='8836' name='nsub' convertTo='$\nsubset$' /> only AMS -->
<char num='8853' name='oplus' convertTo='$\oplus$' />
<char num='8855' name='otimes' convertTo='$\otimes$' />
<char num='8869' name='perp' convertTo='$\perp$' />
<char num='8901' name='sdot' convertTo='$\cdot$' />
<char num='8968' name='rceil' convertTo='$\rceil$' />
<char num='8969' name='lceil' convertTo='$\lceil$' />
<char num='8970' name='lfloor' convertTo='$\lfloor$' />
<char num='8971' name='rfloor' convertTo='$\rfloor$' />
<char num='9001' name='rang' convertTo='$\rangle$' />
<char num='9002' name='lang' convertTo='$\langle$' />
<char num='9674' name='loz' convertTo='$\lozenge$' package='amssymb' /> <!-- only AMS -->
<char num='9824' name='spades' convertTo='$\spadesuit$' />
<char num='9827' name='clubs' convertTo='$\clubsuit$' />
<char num='9829' name='hearts' convertTo='$\heartsuit$' />
<char num='9830' name='diams' convertTo='$\diamondsuit$' />
<char num='38' name='amp' convertTo='\@AMP' />
<!-- <char num='34' name='quot' convertTo='\@DOUBLEQUOT' /> XXX -->
<char num='34' name='quot' convertTo='&quot;' />
<char num='39' name='apos' convertTo="'" />
<char num='169' name='copy' convertTo='\copyright' />
<char num='60' name='lt' convertTo='$@LT$' />
<char num='62' name='gt' convertTo='$@GT$' />
<char num='338' name='OElig' convertTo='\OE' />
<char num='339' name='oelig' convertTo='\oe' />
<char num='352' name='Scaron' convertTo='\v{S}' />
<char num='353' name='scaron' convertTo='\v{s}' />
<char num='376' name='Yuml' convertTo='\"Y' />
<char num='710' name='circ' convertTo='\textasciicircum' />
<char num='732' name='tilde' convertTo='\textasciitilde' />
<char num='8211' name='ndash' convertTo='--' />
<char num='8212' name='mdash' convertTo='---' />
<char num='8216' name='lsquo' convertTo='`' />
<char num='8217' name='rsquo' convertTo="'" /> <!-- XXXX -->
<char num='8220' name='ldquo' convertTo='``' />
<char num='8221' name='rdquo' convertTo="''" /> <!-- XXXX -->
<char num='8224' name='dagger' convertTo='\dag' />
<char num='8225' name='Dagger' convertTo='\ddag' />
<char num='8240' name='permil' convertTo='\permil' package='wasysym' /> <!-- wasysym package -->
<char num='8364' name='euro' convertTo='\euro' package='eurosym' /> <!-- eurosym package -->
<char num='8249' name='lsaquo' convertTo='\guilsinglleft' package='aeguill'/>
<char num='8250' name='rsaquo' convertTo='\guilsinglright' package='aeguill' />
<!-- <char num='160' name='nbsp' convertTo='\nolinebreak' />-->
<char num='160' name='nbsp' convertTo='~' />
<char num='161' name='iexcl' convertTo='\textexclamdown' />
<char num='163' name='pound' convertTo='\pounds' />
<char num='164' name='curren' convertTo='\currency' package='wasysym' /> <!-- wasysym package -->
<char num='165' name='yen' convertTo='\textyen' package='textcomp'/> <!-- textcomp -->
<char num='166' name='brvbar' convertTo='\brokenvert' /> <!-- wasysym -->
<char num='167' name='sect' convertTo='\S' />
<char num='171' name='laquo' convertTo='\guillemotleft' package='aeguill'/>
<char num='187' name='raquo' convertTo='\guillemotright' package='aeguill'/>
<char num='174' name='reg' convertTo='\textregistered' />
<char num='170' name='ordf' convertTo='\textordfeminine' />
<char num='172' name='not' convertTo='$\neg$' />
<!-- <char num='176' name='deg' convertTo='$\degree$' /> mathabx -->
<char num='176' name='deg' convertTo='\textdegree' package='textcomp'/>
<char num='177' name='plusmn' convertTo='$\pm$' />
<char num='180' name='acute' convertTo='@QUOT' />
<char num='181' name='micro' convertTo='$\mu$' />
<char num='182' name='para' convertTo='\P' />
<char num='183' name='middot' convertTo='$\cdot$' />
<char num='186' name='ordm' convertTo='\textordmasculine' />
<char num='162' name='cent' convertTo='\cent' package='wasysym' />
<char num='185' name='sup1' convertTo='$^1$' />
<char num='178' name='sup2' convertTo='$^2$' />
<char num='179' name='sup3' convertTo='$^3$' />
<char num='189' name='frac12' convertTo='$\frac{1}{2}$' />
<char num='188' name='frac14' convertTo='$\frac{1}{4}$' />
<char num='190' name='frac34' convertTo='$\frac{3}{4}$' />
<char num='192' name='Agrave' convertTo='\`A' />
<char num='193' name='Aacute' convertTo='\@QUOTA' />
<char num='194' name='Acirc' convertTo='\^A' />
<char num='195' name='Atilde' convertTo='\~A' />
<char num='196' name='Auml' convertTo='\@DOUBLEQUOTA' />
<char num='197' name='Aring' convertTo='\AA' />
<char num='198' name='AElig' convertTo='\AE' />
<char num='199' name='Ccedil' convertTo='\c{C}' />
<char num='200' name='Egrave' convertTo='\`E' />
<char num='201' name='Eacute' convertTo='\@QUOTE' />
<char num='202' name='Ecirc' convertTo='\^E' />
<char num='203' name='Euml' convertTo='\@DOUBLEQUOTE' />
<char num='204' name='Igrave' convertTo='\`I' />
<char num='205' name='Iacute' convertTo='\@QUOTI' />
<char num='206' name='Icirc' convertTo='\^I' />
<char num='207' name='Iuml' convertTo='\"I' />
<char num='208' name='ETH' convertTo='$\eth$' /> <!-- AMS -->
<char num='209' name='Ntilde' convertTo='\~N' />
<char num='210' name='Ograve' convertTo='\`O' />
<char num='211' name='Oacute' convertTo='\@QUOT O' />
<char num='212' name='Ocirc' convertTo='\^O' />
<char num='213' name='Otilde' convertTo='\~O' />
<char num='214' name='Ouml' convertTo='\@DOUBLEQUOTO' />
<char num='215' name='times' convertTo='$\times$' />
<char num='216' name='Oslash' convertTo='\O' />
<char num='217' name='Ugrave' convertTo='\`U' />
<char num='218' name='Uacute' convertTo='\@QUOTU' />
<char num='219' name='Ucirc' convertTo='\^U' />
<char num='220' name='Uuml' convertTo='\@DOUBLEQUOTU' />
<char num='221' name='Yacute' convertTo='\@QUOTY' />
<char num='223' name='szlig' convertTo='\ss' />
<char num='224' name='agrave' convertTo='\`a' />
<char num='225' name='aacute' convertTo='\@QUOTa' />
<char num='226' name='acirc' convertTo='\^a' />
<char num='227' name='atilde' convertTo='\~a' />
<char num='228' name='auml' convertTo='\@DOUBLEQUOTa' />
<char num='229' name='aring' convertTo='\aa' />
<char num='230' name='aelig' convertTo='\ae' />
<char num='231' name='ccedil' convertTo='\c{c}' />
<char num='232' name='egrave' convertTo='\`e' />
<char num='233' name='eacute' convertTo='\@QUOTe' />
<char num='234' name='ecirc' convertTo='\^e' />
<char num='235' name='euml' convertTo='\@DOUBLEQUOTe' />
<char num='236' name='igrave' convertTo='\`i' />
<char num='237' name='iacute' convertTo='\@QUOTi' />
<char num='238' name='icirc' convertTo='\^i' />
<char num='239' name='iuml' convertTo='\@DOUBLEQUOTi' />
<char num='240' name='eth' convertTo='$\eth$' package='amssymb'/> <!-- -->
<char num='241' name='ntilde' convertTo='\~n' />
<char num='242' name='ograve' convertTo='\`o' />
<char num='243' name='oacute' convertTo='\@QUOTo' />
<char num='244' name='ocirc' convertTo='\^o' />
<char num='245' name='otilde' convertTo='\~o' />
<char num='246' name='ouml' convertTo='\@DOUBLEQUOTo' />
<!-- <char num='247' name='divide' convertTo='$\divide$' /> -->
<char num='248' name='oslash' convertTo='\o' />
<char num='249' name='ugrave' convertTo='\`u' />
<char num='250' name='uacute' convertTo='\@QUOTu' />
<char num='251' name='ucirc' convertTo='\^u' />
<char num='252' name='uuml' convertTo='\@DOUBLEQUOTu' />
<char num='253' name='yacute' convertTo='\@QUOTy' />
<char num='255' name='yuml' convertTo='\@DOUBLEQUOTy' />
<char num='222' name='THORN' convertTo='\Thorn' package='wasysym' />
<char num='254' name='thorn' convertTo='\thorn' package='wasysym' />
</chars>

View file

@ -1,4 +1,3 @@
#--
# Copyright (C) 2006 Andrea Censi <andrea (at) rubyforge.org> # Copyright (C) 2006 Andrea Censi <andrea (at) rubyforge.org>
# #
# This file is part of Maruku. # This file is part of Maruku.
@ -16,31 +15,26 @@
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with Maruku; if not, write to the Free Software # along with Maruku; if not, write to the Free Software
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
#++
dir = File.dirname(__FILE__)
$LOAD_PATH.unshift dir unless $LOAD_PATH.include?(dir)
require 'rexml/document' require 'rexml/document'
# :include:MaRuKu.txt
module MaRuKu module MaRuKu
module In module In
module Markdown module Markdown
module SpanLevelParser; end module SpanLevelParser; end
module BlockLevelParser; end module BlockLevelParser; end
end end
# more to come?
end end
module Out module Out
# Functions for exporting to MarkDown.
module Markdown; end module Markdown; end
# Functions for exporting to HTML.
module HTML; end module HTML; end
# Functions for exporting to Latex
module Latex; end module Latex; end
end end
# These are strings utilities.
module Strings; end module Strings; end
module Helpers; end module Helpers; end
@ -66,13 +60,9 @@ module MaRuKu
end end
end end
# This is the public interface
class Maruku < MaRuKu::MDDocument; end class Maruku < MaRuKu::MDDocument; end
require 'rexml/document'
# Structures definition # Structures definition
require 'maruku/structures' require 'maruku/structures'
require 'maruku/structures_inspect' require 'maruku/structures_inspect'
@ -106,7 +96,7 @@ require 'maruku/attributes'
require 'maruku/structures_iterators' require 'maruku/structures_iterators'
require 'maruku/errors_management' require 'maruku/errors'
# Code for creating a table of contents # Code for creating a table of contents
require 'maruku/toc' require 'maruku/toc'
@ -140,4 +130,3 @@ require 'maruku/output/to_s'
# class Maruku is the global interface # class Maruku is the global interface
require 'maruku/maruku' require 'maruku/maruku'

View file

@ -1,462 +0,0 @@
# Copyright (C) 2006 Andrea Censi <andrea (at) rubyforge.org>
#
# This file is part of Maruku.
#
# 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
# NOTE: this is the old span-level regexp-based parser.
#
# The new parser is a real parser and is defined with functions in parse_span_better.rb
# The new parser is faster, handles syntax errors, but it's absolutely not readable.
#
# Also, regexp parsers simply CANNOT handle inline HTML properly.
# There are two black-magic methods `match_couple_of` and `map_match`,
# defined at the end of the file, that make the function
# `parse_lines_as_span` so elegant.
class Maruku
# Takes care of all span-level formatting, links, images, etc.
#
# Lines must not contain block-level elements.
def parse_lines_as_span(lines)
# first, get rid of linebreaks
res = resolve_linebreaks(lines)
span = MDElement.new(:dummy, res)
# encode all escapes
span.replace_each_string { |s| s.escape_md_special }
# The order of processing is significant:
# 1. inline code
# 2. immediate links
# 3. inline HTML
# 4. everything else
# search for ``code`` markers
span.match_couple_of('``') { |children, match1, match2|
e = create_md_element(:inline_code)
# this is now opaque to processing
e.meta[:raw_code] = children.join('').it_was_a_code_block
e
}
# Search for `single tick` code markers
span.match_couple_of('`') { |children, match1, match2|
e = create_md_element(:inline_code)
# this is now opaque to processing
e.meta[:raw_code] = children.join('').it_was_a_code_block
# this is now opaque to processing
e
}
# Detect any immediate link: <http://www.google.com>
# we expect an http: or something: at the beginning
span.map_match( /<(\w+:[^\>]+)>/) { |match|
url = match[1]
e = create_md_element(:immediate_link, [])
e.meta[:url] = url
e
}
# Search for inline HTML (the support is pretty basic for now)
# this searches for a matching block
inlineHTML1 = %r{
( # put everything in 1
< # open
(\w+) # opening tag in 2
> # close
.* # anything
</\2> # match closing tag
)
}x
# this searches for only one block
inlineHTML2 = %r{
( # put everything in 1
< # open
\w+ #
# close
[^<>]* # anything except
/> # closing tag
)
}x
for reg in [inlineHTML1, inlineHTML2]
span.map_match(reg) { |match|
raw_html = match[1]
convert_raw_html_in_list(raw_html)
}
end
# Detect footnotes references: [^1]
span.map_match(/\[(\^[^\]]+)\]/) { |match|
id = match[1].strip.downcase
e = create_md_element(:footnote_reference)
e.meta[:footnote_id] = id
e
}
# Detect any image like ![Alt text][url]
span.map_match(/\!\[([^\]]+)\]\s?\[([^\]]*)\]/) { |match|
alt = match[1]
id = match[2].strip.downcase
if id.size == 0
id = text.strip.downcase
end
e = create_md_element(:image)
e.meta[:ref_id] = id
e
}
# Detect any immage with immediate url: ![Alt](url "title")
# a dummy ref is created and put in the symbol table
link1 = /!\[([^\]]+)\]\s?\(([^\s\)]*)(?:\s+["'](.*)["'])?\)/
span.map_match(link1) { |match|
alt = match[1]
url = match[2]
title = match[3]
url = url.strip
# create a dummy id
id="dummy_#{@refs.size}"
@refs[id] = {:url=>url, :title=>title}
e = create_md_element(:image)
e.meta[:ref_id] = id
e
}
# an id reference: "[id]", "[ id ]"
reg_id_ref = %r{
\[ # opening bracket
([^\]]*) # 0 or more non-closing bracket (this is too permissive)
\] # closing bracket
}x
# validates a url, only $1 is set to the url
reg_url =
/((?:\w+):\/\/(?:\w+:{0,1}\w*@)?(?:\S+)(?::[0-9]+)?(?:\/|\/([\w#!:.?+=&%@!\-\/]))?)/
reg_url = %r{([^\s\]\)]+)}
# A string enclosed in quotes.
reg_title = %r{
" # opening
[^"]* # anything = 1
" # closing
}x
# [bah](http://www.google.com "Google.com"),
# [bah](http://www.google.com),
# [empty]()
reg_url_and_title = %r{
\( # opening
\s* # whitespace
#{reg_url}? # url = 1 might be empty
(?:\s+["'](.*)["'])? # optional title = 2
\s* # whitespace
\) # closing
}x
# Detect a link like ![Alt text][id]
span.map_match(/\[([^\]]+)\]\s?\[([^\]]*)\]/) { |match|
text = match[1]
id = match[2].strip.downcase
if id.size == 0
id = text.strip.downcase
end
children = parse_lines_as_span(text)
e = create_md_element(:link, children)
e.meta[:ref_id] = id
e
}
# Detect any immage with immediate url: ![Alt](url "title")
# a dummy ref is created and put in the symbol table
link1 = /!\[([^\]]+)\]\s?\(([^\s\)]*)(?:\s+["'](.*)["'])?\)/
span.map_match(link1) { |match|
text = match[1]
children = parse_lines_as_span(text)
url = match[2]
title = match[3]
url = url.strip
# create a dummy id
id="dummy_#{@refs.size}"
@refs[id] = {:url=>url, :title=>title}
@refs[id][:title] = title if title
e = create_md_element(:link, children)
e.meta[:ref_id] = id
e
}
# Detect any link like [Google engine][google]
span.match_couple_of('[', # opening bracket
%r{\] # closing bracket
[ ]? # optional whitespace
#{reg_id_ref} # ref id, with $1 being the reference
}x
) { |children, match1, match2|
id = match2[1]
id = id.strip.downcase
if id.size == 0
id = children.join.strip.downcase
end
e = create_md_element(:link, children)
e.meta[:ref_id] = id
e
}
# Detect any link with immediate url: [Google](http://www.google.com)
# XXX Note that the url can be empty: [Empty]()
# a dummy ref is created and put in the symbol table
span.match_couple_of('[', # opening bracket
%r{\] # closing bracket
[ ]? # optional whitespace
#{reg_url_and_title} # ref id, with $1 being the url and $2 being the title
}x
) { |children, match1, match2|
url = match2[1]
title = match2[3] # XXX? Is it a bug? I would use [2]
# create a dummy id
id="dummy_#{@refs.size}"
@refs[id] = {:url=>url}
@refs[id][:title] = title if title
e = create_md_element(:link, children)
e.meta[:ref_id] = id
e
}
# Detect an email address <andrea@invalid.it>
span.map_match(EMailAddress) { |match|
email = match[1]
e = create_md_element(:email_address, [])
e.meta[:email] = email
e
}
# Detect HTML entitis
span.map_match(/&([\w\d]+);/) { |match|
entity_name = match[1]
e = create_md_element(:entity, [])
e.meta[:entity_name] = entity_name
e
}
# And now the easy stuff
# search for ***strong and em***
span.match_couple_of('***') { |children,m1,m2|
create_md_element(:strong, [create_md_element(:emphasis, children)] ) }
span.match_couple_of('___') { |children,m1,m2|
create_md_element(:strong, [create_md_element(:emphasis, children)] ) }
# search for **strong**
span.match_couple_of('**') { |children,m1,m2| create_md_element(:strong, children) }
# search for __strong__
span.match_couple_of('__') { |children,m1,m2| create_md_element(:strong, children) }
# search for *emphasis*
span.match_couple_of('*') { |children,m1,m2| create_md_element(:emphasis, children) }
# search for _emphasis_
span.match_couple_of('_') { |children,m1,m2| create_md_element(:emphasis, children) }
# finally, unescape the special characters
span.replace_each_string { |s| s.unescape_md_special}
span.children
end
# returns array containing Strings or :linebreak elements
def resolve_linebreaks(lines)
res = []
s = ""
lines.each do |l|
s += (s.size>0 ? " " : "") + l.strip
if force_linebreak?(l)
res << s
res << create_md_element(:linebreak)
s = ""
end
end
res << s if s.size > 0
res
end
# raw_html is something like
# <em> A</em> dopwkk *maruk* <em>A</em>
def convert_raw_html_in_list(raw_html)
e = create_md_element(:raw_html)
e.meta[:raw_html] = raw_html
begin
e.meta[:parsed_html] = Document.new(raw_html)
rescue
$stderr.puts "convert_raw_html_in_list Malformed HTML:\n#{raw_html}"
end
e
end
end
# And now the black magic that makes the part above so elegant
class MDElement
# Try to match the regexp to each string in the hierarchy
# (using `replace_each_string`). If the regexp match, eliminate
# the matching string and substitute it with the pre_match, the
# result of the block, and the post_match
#
# ..., matched_string, ... -> ..., pre_match, block.call(match), post_match
#
# the block might return arrays.
#
def map_match(regexp, &block)
replace_each_string { |s|
processed = []
while (match = regexp.match(s))
# save the pre_match
processed << match.pre_match if match.pre_match && match.pre_match.size>0
# transform match
result = block.call(match)
# and append as processed
[*result].each do |e| processed << e end
# go on with the rest of the string
s = match.post_match
end
processed << s if s.size > 0
processed
}
end
# Finds couple of delimiters in a hierarchy of Strings and MDElements
#
# Open and close are two delimiters (like '[' and ']'), or two Regexp.
#
# If you don't pass close, it defaults to open.
#
# Each block is called with |contained children, match1, match2|
def match_couple_of(open, close=nil, &block)
close = close || open
open_regexp = open.kind_of?(Regexp) ? open : Regexp.new(Regexp.escape(open))
close_regexp = close.kind_of?(Regexp) ? close : Regexp.new(Regexp.escape(close))
# Do the same to children first
for c in @children; if c.kind_of? MDElement
c.match_couple_of(open_regexp, close_regexp, &block)
end end
processed_children = []
until @children.empty?
c = @children.shift
if c.kind_of? String
match1 = open_regexp.match(c)
if not match1
processed_children << c
else # we found opening, now search closing
# puts "Found opening (#{marker}) in #{c.inspect}"
# pre match is processed
processed_children.push match1.pre_match if
match1.pre_match && match1.pre_match.size > 0
# we will process again the post_match
@children.unshift match1.post_match if
match1.post_match && match1.post_match.size>0
contained = []; found_closing = false
until @children.empty? || found_closing
c = @children.shift
if c.kind_of? String
match2 = close_regexp.match(c)
if not match2
contained << c
else
# we found closing
found_closing = true
# pre match is contained
contained.push match2.pre_match if
match2.pre_match && match2.pre_match.size>0
# we will process again the post_match
@children.unshift match2.post_match if
match2.post_match && match2.post_match.size>0
# And now we call the block
substitute = block.call(contained, match1, match2)
processed_children << substitute
# puts "Found closing (#{marker}) in #{c.inspect}"
# puts "Children: #{contained.inspect}"
# puts "Substitute: #{substitute.inspect}"
end
else
contained << c
end
end
if not found_closing
# $stderr.puts "##### Could not find closing for #{open}, #{close} -- ignoring"
processed_children << match1.to_s
contained.reverse.each do |c|
@children.unshift c
end
end
end
else
processed_children << c
end
end
raise "BugBug" unless @children.empty?
rebuilt = []
# rebuild strings
processed_children.each do |c|
if c.kind_of?(String) && rebuilt.last && rebuilt.last.kind_of?(String)
rebuilt.last << c
else
rebuilt << c
end
end
@children = rebuilt
end
end

View file

@ -1,4 +1,3 @@
#--
# Copyright (C) 2006 Andrea Censi <andrea (at) rubyforge.org> # Copyright (C) 2006 Andrea Censi <andrea (at) rubyforge.org>
# #
# This file is part of Maruku. # This file is part of Maruku.
@ -16,160 +15,124 @@
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with Maruku; if not, write to the Free Software # along with Maruku; if not, write to the Free Software
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
#++
class String module MaRuKu
def quote_if_needed # This represents a list of attributes specified in the Markdown document
if /[\s\'\"]/.match self # that apply to a Markdown-generated tag.
inspect # What was `{#id .class key="val" ref}` in the Markdown
else # is parsed into `[[:id, 'id'], [:class, 'id'], ['key', 'val'], [:ref, 'ref']]`.
self
end
end
end
module MaRuKu;
MagicChar = ':'
class AttributeList < Array class AttributeList < Array
# An attribute list becomes
# {#id .cl key="val" ref}
# [ [:id, 'id'], [:class, 'id'], ['key', 'val'], [ :ref, 'ref' ]]
private :push
def push_key_val(key, val);
raise "Bad #{key.inspect}=#{val.inspect}" if not key and val
push [key, val]
end
def push_ref(ref_id);
raise "Bad :ref #{ref_id.inspect}" if not ref_id
push [:ref, ref_id+""]
# p "Now ", self ########################################
end
def push_class(val);
raise "Bad :id #{val.inspect}" if not val
push [:class, val]
end
def push_id(val);
raise "Bad :id #{val.inspect}" if not val
push [:id, val]
end
def to_s def to_s
map do |k, v| map do |k, v|
case k case k
when :id; "#" + v.quote_if_needed when :id; "#" + quote_if_needed(v)
when :class; "." + v.quote_if_needed when :class; "." + quote_if_needed(v)
when :ref; v.quote_if_needed when :ref; quote_if_needed(v)
else k.quote_if_needed + "=" + v.quote_if_needed else quote_if_needed(k) + "=" + quote_if_needed(v)
end end
end.join(' ') end.join(' ')
end end
alias to_md to_s alias to_md to_s
private
def quote_if_needed(str)
return str unless str =~ /[\s'"]/
str.inspect
end
end end
end module In::Markdown::SpanLevelParser
def md_al(s = []); AttributeList.new(s); end
module MaRuKu; module In; module Markdown; module SpanLevelParser # @return [AttributeList, nil]
def md_al(s=[]); AttributeList.new(s) end
# returns nil or an AttributeList
def read_attribute_list(src, con, break_on_chars) def read_attribute_list(src, con, break_on_chars)
separators = break_on_chars + [?=, ?\s, ?\t]
separators = break_on_chars + [?=,?\ ,?\t]
escaped = Maruku::EscapedCharInQuotes escaped = Maruku::EscapedCharInQuotes
al = AttributeList.new al = AttributeList.new
while true loop do
src.consume_whitespace src.consume_whitespace
break if break_on_chars.include? src.cur_char break if break_on_chars.include? src.cur_char
case src.cur_char case src.cur_char
when nil when nil
maruku_error "Attribute list terminated by EOF:\n "+ maruku_error "Attribute list terminated by EOF:\n #{al.inspect}", src, con
"#{al.inspect}" , src, con tell_user "Returning partial attribute list:\n #{al.inspect}"
tell_user "I try to continue and return partial attribute list:\n"+
al.inspect
break break
when ?= # error when ?= # error
maruku_error "In attribute lists, cannot start identifier with `=`."
tell_user "I try to continue"
src.ignore_char src.ignore_char
maruku_error "In attribute lists, cannot start identifier with `=`."
tell_user "Ignoring and continuing."
when ?# # id definition when ?# # id definition
src.ignore_char src.ignore_char
if id = read_quoted_or_unquoted(src, con, escaped, separators) if id = read_quoted_or_unquoted(src, con, escaped, separators)
al.push_id id al << [:id, id]
else else
maruku_error 'Could not read `id` attribute.', src, con maruku_error 'Could not read `id` attribute.', src, con
tell_user 'Trying to ignore bad `id` attribute.' tell_user 'Ignoring bad `id` attribute.'
end end
when ?. # class definition when ?. # class definition
src.ignore_char src.ignore_char
if klass = read_quoted_or_unquoted(src, con, escaped, separators) if klass = read_quoted_or_unquoted(src, con, escaped, separators)
al.push_class klass al << [:class, klass]
else else
maruku_error 'Could not read `class` attribute.', src, con maruku_error 'Could not read `class` attribute.', src, con
tell_user 'Trying to ignore bad `class` attribute.' tell_user 'Ignoring bad `class` attribute.'
end end
else else
if key = read_quoted_or_unquoted(src, con, escaped, separators) unless key = read_quoted_or_unquoted(src, con, escaped, separators)
if src.cur_char == ?= maruku_error 'Could not read key or reference.'
next
end
if src.cur_char != ?=
al << [:ref, key]
next
end
src.ignore_char # skip the = src.ignore_char # skip the =
if val = read_quoted_or_unquoted(src, con, escaped, separators) if val = read_quoted_or_unquoted(src, con, escaped, separators)
al.push_key_val(key, val) al << [key, val]
else else
maruku_error "Could not read value for key #{key.inspect}.", maruku_error "Could not read value for key #{key.inspect}.", src, con
src, con tell_user "Ignoring key #{key.inspect}"
tell_user "Ignoring key #{key.inspect}."
end end
else
al.push_ref key
end end
else
maruku_error 'Could not read key or reference.'
end end
end # case
end # while true
al al
end end
# We need a helper
def is_ial(e); e.kind_of? MDElement and e.node_type == :ial end
def merge_ial(elements, src, con) def merge_ial(elements, src, con)
# Apply each IAL to the element before # Apply each IAL to the element before
elements.each_with_index do |e, i| (elements + [nil]).each_cons(3) do |before, e, after|
if is_ial(e) && i>= 1 then next unless ial?(e)
before = elements[i-1]
after = elements[i+1]
if before.kind_of? MDElement if before.kind_of? MDElement
before.al = e.ial before.al = e.ial
elsif after.kind_of? MDElement elsif after.kind_of? MDElement
after.al = e.ial after.al = e.ial
else else
maruku_error "It is not clear to me what element this IAL {:#{e.ial.to_md}} \n"+ maruku_error <<ERR, src, con
"is referring to. The element before is a #{before.class.to_s}, \n"+ It's unclear which element the attribute list {:#{e.ial.to_md}}
"the element after is a #{after.class.to_s}.\n"+ is referring to. The element before is a #{before.class},
"\n before: #{before.inspect}"+ the element after is a #{after.class}.
"\n after: #{after.inspect}", before: #{before.inspect}
src, con after: #{after.inspect}
# xxx dire se c'è empty vicino ERR
end
end
end
if not Globals[:debug_keep_ials]
elements.delete_if {|x| is_ial(x) unless x == elements.first}
end end
end end
end end end end unless Globals[:debug_keep_ials]
#module MaRuKu; module In; module Markdown; module SpanLevelParser elements.delete_if {|x| ial?(x) && x != elements.first}
end
end
private
def ial?(e)
e.is_a?(MDElement) && e.node_type == :ial
end
end
end

View file

@ -1,4 +1,3 @@
#--
# Copyright (C) 2006 Andrea Censi <andrea (at) rubyforge.org> # Copyright (C) 2006 Andrea Censi <andrea (at) rubyforge.org>
# #
# This file is part of Maruku. # This file is part of Maruku.
@ -16,7 +15,6 @@
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with Maruku; if not, write to the Free Software # along with Maruku; if not, write to the Free Software
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
#++
module MaRuKu module MaRuKu
@ -25,7 +23,6 @@ Globals = {
:unsafe_features => false, :unsafe_features => false,
:on_error => :warning, :on_error => :warning,
:use_numbered_headers => false, :use_numbered_headers => false,
:maruku_signature => false, :maruku_signature => false,
@ -55,17 +52,13 @@ Globals = {
class MDElement class MDElement
def get_setting(sym) def get_setting(sym)
if self.attributes.has_key?(sym) then return attributes[sym] if attributes.has_key?(sym)
return self.attributes[sym] return doc.attributes[sym] if doc && doc.attributes.has_key?(sym)
elsif self.doc && self.doc.attributes.has_key?(sym) then return MaRuKu::Globals[sym] if MaRuKu::Globals.has_key?(sym)
return self.doc.attributes[sym]
elsif MaRuKu::Globals.has_key?(sym)
return MaRuKu::Globals[sym]
else
$stderr.puts "Bug: no default for #{sym.inspect}" $stderr.puts "Bug: no default for #{sym.inspect}"
nil nil
end end
end end
end
end end

View file

@ -0,0 +1,89 @@
# Copyright (C) 2006 Andrea Censi <andrea (at) rubyforge.org>
#
# This file is part of Maruku.
#
# 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
module MaRuKu
class Exception < RuntimeError; end
module Errors
FRAME_WIDTH = 75
# Properly handles a formatting error.
# All such errors go through this method.
#
# The behavior depends on {MaRuKu::Globals `MaRuKu::Globals[:on_error]`}.
# If this is `:warning`, this prints the error to stderr
# (or `@error_stream if` it's defined) and tries to continue.
# If `:on_error` is `:ignore`, this doesn't print anything
# and tries to continue. If it's `:raise`, this raises a {MaRuKu::Exception}.
#
# By default, `:on_error` is set to `:warning`.
#
# @overload def maruku_error(s, src = nil, con = nil)
# @param s [String] The text of the error
# @param src [#describe, nil] The source of the error
# @param con [#describe, nil] The context of the error
# @raise [MaRuKu::Exception] If `:on_error` is set to `:raise`
def maruku_error(*args)
policy = get_setting(:on_error)
case policy
when :ignore
when :raise
raise_error create_frame(describe_error(*args))
when :warning
tell_user create_frame(describe_error(*args))
else
raise "Unknown on_error policy: #{policy.inspect}"
end
end
def maruku_recover(*args)
tell_user create_frame(describe_error(*args))
end
alias error maruku_error
def raise_error(s)
raise MaRuKu::Exception, s, caller
end
def tell_user(s)
(self.attributes[:error_stream] || $stderr) << s
end
private
def create_frame(s)
"\n" + <<FRAME
#{"_" * FRAME_WIDTH}
| Maruku tells you:
+#{"-" * FRAME_WIDTH}
#{s.gsub(/^/, '| ').rstrip}
+#{"-" * FRAME_WIDTH}
#{caller[0...5].join("\n").gsub(/^/, '!')}
\\#{"_" * FRAME_WIDTH}
FRAME
end
def describe_error(s, src = nil, con = nil)
s += "\n#{src.describe}\n" if src
s += "\n#{con.describe}\n" if con
s
end
end
end

View file

@ -1,92 +0,0 @@
#--
# Copyright (C) 2006 Andrea Censi <andrea (at) rubyforge.org>
#
# This file is part of Maruku.
#
# 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
#++
#m Any method that detects formatting error calls the
#m maruku_error() method.
#m if @meta[:on_error] ==
#m
#m - :warning write on the standard err (or @error_stream if defined),
#m then do your best.
#m - :ignore be shy and try to continue
#m - :raise raises a MarukuException
#m
#m default is :raise
module MaRuKu
class Exception < RuntimeError
end
module Errors
def maruku_error(s,src=nil,con=nil)
policy = get_setting(:on_error)
case policy
when :ignore
when :raise
raise_error create_frame(describe_error(s,src,con))
when :warning
tell_user create_frame(describe_error(s,src,con))
else
raise "BugBug: policy = #{policy.inspect}"
end
end
def maruku_recover(s,src=nil,con=nil)
tell_user create_frame(describe_error(s,src,con))
end
alias error maruku_error
def raise_error(s)
raise MaRuKu::Exception, s, caller
end
def tell_user(s)
error_stream = self.attributes[:error_stream] || $stderr
error_stream << s
end
def create_frame(s)
n = 75
"\n" +
" "+"_"*n + "\n"+
"| Maruku tells you:\n" +
"+" + ("-"*n) +"\n"+
add_tabs(s,1,'| ') + "\n" +
"+" + ("-"*n) + "\n" +
add_tabs(caller[0, 5].join("\n"),1,'!') + "\n" +
"\\" + ("_"*n) + "\n"
end
def describe_error(s,src,con)
t = s
src && (t += "\n#{src.describe}\n")
con && (t += "\n#{con.describe}\n")
t
end
end # Errors
end # MaRuKu

View file

@ -1,5 +1,3 @@
#+-----------------------------------{.warning}------ #+-----------------------------------{.warning}------
#| this is the last warning! #| this is the last warning!
#| #|
@ -10,29 +8,25 @@
#| +-------------------------------------------------- #| +--------------------------------------------------
#+--------------------------------------------------- #+---------------------------------------------------
#OpenDiv = /^[ ]{0,3}\+\-\-+\s*(.*)$/ # TODO: Scope these properly
#CloseDiv = /^[ ]{0,3}\=\-\-+\s*(.*)$/
OpenDiv = /^[ ]{0,3}\+\-\-+\s*(\{([^{}]*?|".*?"|'.*?')*\})?\s*\-*\s*$/ OpenDiv = /^[ ]{0,3}\+\-\-+\s*(\{([^{}]*?|".*?"|'.*?')*\})?\s*\-*\s*$/
CloseDiv = /^[ ]{0,3}\=\-\-+\s*(\{([^{}]*?|".*?"|'.*?')*\})?\s*\-*\s*$/ CloseDiv = /^[ ]{0,3}\=\-\-+\s*(\{([^{}]*?|".*?"|'.*?')*\})?\s*\-*\s*$/
StartPipe = /^[ ]{0,3}\|(.*)$/ # $1 is rest of line StartPipe = /^[ ]{0,3}\|(.*)$/ # $1 is rest of line
DecorativeClosing = OpenDiv DecorativeClosing = OpenDiv
MaRuKu::In::Markdown::register_block_extension( MaRuKu::In::Markdown.register_block_extension(
:regexp => OpenDiv, :regexp => OpenDiv,
:handler => lambda { |doc, src, context| :handler => lambda do |doc, src, context|
# return false if not doc.is_math_enabled?
first = src.shift_line first = src.shift_line
first =~ OpenDiv ial_at_beginning = first[OpenDiv, 1]
ial_at_beginning = $1
ial_at_end = nil ial_at_end = nil
lines = [] lines = []
# if second line starts with "|" # if second line starts with "|"
if src.cur_line =~ StartPipe if src.cur_line =~ StartPipe
# then we read until no more "|" # then we read until no more "|"
while src.cur_line && (src.cur_line =~ StartPipe) while src.cur_line && src.cur_line =~ StartPipe
content = $1 lines.push $1
lines.push content
src.shift_line src.shift_line
end end
if src.cur_line =~ DecorativeClosing if src.cur_line =~ DecorativeClosing
@ -42,7 +36,7 @@ MaRuKu::In::Markdown::register_block_extension(
else else
# else we read until CloseDiv # else we read until CloseDiv
divs_open = 1 divs_open = 1
while src.cur_line && (divs_open>0) while src.cur_line && divs_open > 0
if src.cur_line =~ CloseDiv if src.cur_line =~ CloseDiv
divs_open -= 1 divs_open -= 1
if divs_open == 0 if divs_open == 0
@ -61,72 +55,67 @@ MaRuKu::In::Markdown::register_block_extension(
end end
if divs_open > 0 if divs_open > 0
e = "At end of input, I still have #{divs_open} DIVs open." doc.maruku_error("At end of input, I still have #{divs_open} DIVs open.",
doc.maruku_error(e, src, context) src, context)
return true next true
end end
end end
ial_at_beginning = nil unless ial_at_beginning = nil unless ial_at_beginning && ial_at_beginning.size > 0
(ial_at_beginning&&ial_at_beginning.size > 0) ial_at_end = nil unless ial_at_end && ial_at_end.size > 0
ial_at_end = nil unless (ial_at_end && ial_at_end.size > 0)
if ial_at_beginning && ial_at_end if ial_at_beginning && ial_at_end
e = "Found two conflicting IALs: #{ial_at_beginning.inspect} and #{ial_at_end.inspect}" doc.maruku_error("Found two conflicting IALs: #{ial_at_beginning.inspect} and #{ial_at_end.inspect}",
doc.maruku_error(e, src, context) src, context)
end end
al_string = ial_at_beginning || ial_at_end al_string = ial_at_beginning || ial_at_end
al = nil al = nil
if al_string =~ /^\{(.*)\}\s*$/ if al_string =~ /^\{(.*)\}\s*$/
inside = $1 al = al_string && doc.read_attribute_list(
cs = MaRuKu::In::Markdown::SpanLevelParser::CharSource MaRuKu::In::Markdown::SpanLevelParser::CharSource.new($1),
al = al_string && nil, [nil])
doc.read_attribute_list(cs.new(inside), its_context=nil, break_on=[nil])
end end
src = MaRuKu::In::Markdown::BlockLevelParser::LineSource.new(lines) context.push(
children = doc.parse_blocks(src) doc.md_div(
doc.parse_blocks(
context.push doc.md_div(children, al) MaRuKu::In::Markdown::BlockLevelParser::LineSource.new(lines)),
al))
true true
}) end)
module MaRuKu; class MDElement
module MaRuKu
class MDElement
def md_div(children, al = nil) def md_div(children, al = nil)
type = label = num = nil type = label = num = nil
doc.refid2ref ||= {} doc.refid2ref ||= {}
if al if al
al.each do |k, v| al.each do |k, v|
case k case k
when :class when :class; type = $1 if v =~ /^num_(\w*)/
type = $1 if v =~ /^num_(\w*)/ when :id; label = v
when :id
label = v
end end
end end
end end
if type if type
doc.refid2ref[type] ||= {} doc.refid2ref[type] ||= {}
num = doc.refid2ref[type].length + 1 || 1 num = doc.refid2ref[type].length + 1
end
e = self.md_el(:div, children, meta={:label => label, :type => type, :num => num}, al)
if type && label
doc.refid2ref[type].update({label => e})
end end
e = self.md_el(:div, children, {:label => label, :type => type, :num => num}, al)
doc.refid2ref[type].update(label => e) if type && label
e e
end end
end
end end module Out
module HTML
module MaRuKu; module Out; module HTML
def to_html_div def to_html_div
add_ws wrap_as_element('div') add_ws wrap_as_element('div')
end end
end
end end end end
end

View file

@ -1,5 +1,3 @@
require 'maruku/ext/math/elements' require 'maruku/ext/math/elements'
require 'maruku/ext/math/parsing' require 'maruku/ext/math/parsing'
require 'maruku/ext/math/to_latex' require 'maruku/ext/math/to_latex'
@ -10,7 +8,6 @@ require 'maruku/ext/math/mathml_engines/ritex'
require 'maruku/ext/math/mathml_engines/itex2mml' require 'maruku/ext/math/mathml_engines/itex2mml'
require 'maruku/ext/math/mathml_engines/blahtex' require 'maruku/ext/math/mathml_engines/blahtex'
=begin maruku_doc =begin maruku_doc
Attribute: math_enabled Attribute: math_enabled
Scope: global, document Scope: global, document
@ -37,5 +34,4 @@ Array containing any of `'\\['`, `'\\begin{equation}'`, `'$$'`.
=end =end
MaRuKu::Globals[:math_numbered] = [] MaRuKu::Globals[:math_numbered] = []

View file

@ -1,27 +1,21 @@
module MaRuKu; class MDElement module MaRuKu
class MDElement
def md_inline_math(math) def md_inline_math(math)
self.md_el(:inline_math, [], meta={:math=>math}) self.md_el(:inline_math, [], :math => math)
end end
def md_equation(math, label, numerate) def md_equation(math, label, numerate)
reglabel = /\\label\{(\w+)\}/ reglabel = /\\label\{(\w+)\}/
if math =~ reglabel math = math.gsub(reglabel, '') if label = math[reglabel, 1]
label = $1
math.gsub!(reglabel,'')
end
# puts "Found label = #{label} math #{math.inspect} "
num = nil num = nil
if (label || numerate) && @doc # take number if (label || numerate) && @doc # take number
@doc.eqid2eq ||= {} @doc.eqid2eq ||= {}
num = @doc.eqid2eq.size + 1 num = @doc.eqid2eq.size + 1
label = "eq#{num}" if not label # FIXME do id for document label = "eq#{num}" if not label # TODO do id for document
end
e = self.md_el(:equation, [], meta={:math=>math, :label=>label,:num=>num})
if label && @doc #take number
@doc.eqid2eq[label] = e
end end
e = self.md_el(:equation, [], :math => math, :label => label, :num => num)
@doc.eqid2eq[label] = e if label && @doc # take number
e e
end end
end
end end end

View file

@ -1,107 +1,93 @@
require 'tempfile' require 'tempfile'
require 'fileutils' require 'fileutils'
require 'digest/md5' require 'digest/md5'
require 'pstore' require 'pstore'
module MaRuKu; module Out; module HTML module MaRuKu
module Out
module HTML
PNG = Struct.new(:src, :depth, :height) PNG = Struct.new(:src, :depth, :height)
def convert_to_png_blahtex(kind, tex) def convert_to_png_blahtex(kind, tex)
begin FileUtils.mkdir_p get_setting(:html_png_dir)
FileUtils::mkdir_p get_setting(:html_png_dir)
# first, we check whether this image has already been processed # first, we check whether this image has already been processed
md5sum = Digest::MD5.hexdigest(tex + " params: ") md5sum = Digest::MD5.hexdigest(tex + " params: ")
result_file = File.join(get_setting(:html_png_dir), md5sum + ".txt") result_file = File.join(get_setting(:html_png_dir), md5sum + ".txt")
if not File.exists?(result_file) if not File.exists?(result_file)
tmp_in = Tempfile.new('maruku_blahtex') Tempfile.open('maruku_blahtex') do |tmp_in|
f = tmp_in.open tmp_in.write tex
f.write tex tmp_in.close
f.close
resolution = get_setting(:html_png_resolution) # It's important taht we don't replace *all* newlines,
# because newlines in arguments get escaped as "'\n'".
options = "--png --use-preview-package --shell-dvipng 'dvipng -D #{resolution}' " system <<COMMAND.gsub("\n ", " ")
options += "--displaymath " if kind == :equation blahtex --png --use-preview-package
options += ("--temp-directory '%s' " % get_setting(:html_png_dir)) --shell-dvipng #{shellescape("dvipng -D #{shellescape(get_setting(:html_png_resolution).to_s)}")}
options += ("--png-directory '%s'" % get_setting(:html_png_dir)) #{'--displaymath' if kind == :equation}
--temp-directory #{shellescape(get_setting(:html_png_dir))}
cmd = "blahtex #{options} < #{tmp_in.path} > #{result_file}" --png-directory #{shellescape(get_setting(:html_png_dir))}
#$stderr.puts "$ #{cmd}" < #{shellescape(tmp_in.path)}
system cmd > #{shellescape(result_file)}
tmp_in.delete COMMAND
end
end end
result = File.read(result_file) result = File.read(result_file)
if result.nil? || result.empty? if result.nil? || result.empty?
raise "Blahtex error: empty output" maruku_error "Blahtex error: empty output"
return
end end
doc = Document.new(result, {:respect_whitespace =>:all}) doc = Document.new(result, :respect_whitespace => :all)
png = doc.root.elements[1] png = doc.root.elements[1]
if png.name != 'png' if png.name != 'png'
raise "Blahtex error: \n#{doc}" maruku_error "Blahtex error: \n#{doc}"
return
end end
depth = png.elements['depth'] || (raise "No depth element in:\n #{doc}")
height = png.elements['height'] || (raise "No height element in:\n #{doc}") raise "No depth element in:\n #{doc}" unless depth = png.elements['depth']
md5 = png.elements['md5'] || (raise "No md5 element in:\n #{doc}") raise "No height element in:\n #{doc}" unless height = png.elements['height']
raise "No md5 element in:\n #{doc}" unless md5 = png.elements['md5']
depth = depth.text.to_f depth = depth.text.to_f
height = height.text.to_f # XXX check != 0 height = height.text.to_f # TODO: check != 0
md5 = md5.text md5 = md5.text
dir_url = get_setting(:html_png_url) PNG.new("#{get_setting(:html_png_url)}#{md5}.png", depth, height)
return PNG.new("#{dir_url}#{md5}.png", depth, height)
rescue Exception => e rescue Exception => e
maruku_error "Error: #{e}" maruku_error "Error: #{e}"
end end
nil
end
def convert_to_mathml_blahtex(kind, tex) def convert_to_mathml_blahtex(kind, tex)
@@BlahtexCache = PStore.new(get_setting(:latex_cache_file)) @@BlahtexCache ||= PStore.new(get_setting(:latex_cache_file))
begin
@@BlahtexCache.transaction do @@BlahtexCache.transaction do
if @@BlahtexCache[tex].nil? if @@BlahtexCache[tex].nil?
tmp_in = Tempfile.new('maruku_blahtex') Tempfile.open('maruku_blahtex') do |tmp_in|
f = tmp_in.open tmp_in.write tex
f.write tex
f.close
tmp_out = Tempfile.new('maruku_blahtex')
options = "--mathml" Tempfile.new('maruku_blahtex') do |tmp_out|
cmd = "blahtex #{options} < #{tmp_in.path} > #{tmp_out.path}" system "blahtex --mathml < #{shellescape(tmp_in.path)} > #{shellescape(tmp_out.path)}"
#$stderr.puts "$ #{cmd}" @@BlahtexCache[tex] = tmp_out.read
system cmd end
tmp_in.delete end
result = nil
File.open(tmp_out.path) do |f| result=f.read end
puts result
@@BlahtexCache[tex] = result
end end
blahtex = @@BlahtexCache[tex] blahtex = @@BlahtexCache[tex]
doc = Document.new(blahtex, {:respect_whitespace =>:all}) doc = Document.new(blahtex, :respect_whitespace => :all)
mathml = doc.root.elements['mathml'] unless mathml = doc.root.elements['mathml']
if not mathml
maruku_error "Blahtex error: \n#{doc}" maruku_error "Blahtex error: \n#{doc}"
return nil return
else
return mathml
end
end end
return mathml
end
rescue Exception => e rescue Exception => e
maruku_error "Error: #{e}" maruku_error "Error: #{e}"
end end
nil
end end
end
end end end end

View file

@ -1,30 +1,34 @@
module MaRuKu
module MaRuKu; module Out; module HTML module Out
module HTML
def convert_to_mathml_itex2mml(kind, tex) def convert_to_mathml_itex2mml(kind, tex)
begin return if $already_warned_itex2mml
if not $itex2mml_parser
require 'stringsupport'
require 'itextomml' require 'itextomml'
$itex2mml_parser = Itex2MML::Parser.new require 'stringsupport'
parser = Itex2MML::Parser.new
mathml =
case kind
when :equation; parser.block_filter(tex)
when :inline; parser.inline_filter(tex)
else
maruku_error "Unknown itex2mml kind: #{kind}"
return
end end
itex_method = {:equation=>:block_filter,:inline=>:inline_filter} return Document.new(mathml.to_utf8, :respect_whitespace => :all).root
mathml = $itex2mml_parser.send(itex_method[kind], tex).to_utf8
doc = Document.new(mathml, {:respect_whitespace =>:all}).root
return doc
rescue LoadError => e rescue LoadError => e
maruku_error "Could not load package 'itex2mml'.\n"+ "Please install it." unless $already_warned_itex2mml # TODO: Properly scope this global
maruku_error "Could not load package 'itex2mml'.\nPlease install it." unless $already_warned_itex2mml
$already_warned_itex2mml = true $already_warned_itex2mml = true
nil
rescue REXML::ParseException => e rescue REXML::ParseException => e
maruku_error "Invalid MathML TeX: \n#{add_tabs(tex,1,'tex>')}"+ maruku_error "Invalid MathML TeX: \n#{tex.gsub(/^/, 'tex>')}\n\n #{e.inspect}"
"\n\n #{e.inspect}" nil
rescue rescue
maruku_error "Could not produce MathML TeX: \n#{tex}"+ maruku_error "Could not produce MathML TeX: \n#{tex}\n\n #{e.inspect}"
"\n\n #{e.inspect}"
end
nil nil
end end
end
end end end end
end

View file

@ -1,7 +1,8 @@
module MaRuKu module MaRuKu
class MDDocument class MDDocument
# Hash equation id (String) to equation element (MDElement) # A hash of equation ids to equation elements
#
# @return [String => MDElement]
attr_accessor :eqid2eq attr_accessor :eqid2eq
def is_math_enabled? def is_math_enabled?
@ -10,26 +11,20 @@ module MaRuKu
end end
end end
# TODO: Properly scope all these regexps
# Everything goes; takes care of escaping the "\$" inside the expression # Everything goes; takes care of escaping the "\$" inside the expression
RegInlineMath = /\${1}((?:[^\$]|\\\$)+)\$/ RegInlineMath = /\${1}((?:[^\$]|\\\$)+)\$/
MaRuKu::In::Markdown::register_span_extension( MaRuKu::In::Markdown.register_span_extension(
:chars => ?$, :chars => ?$,
:regexp => RegInlineMath, :regexp => RegInlineMath,
:handler => lambda { |doc, src, con| :handler => lambda do |doc, src, con|
return false if not doc.is_math_enabled? next false unless doc.is_math_enabled?
next false unless m = src.read_regexp(RegInlineMath)
if m = src.read_regexp(RegInlineMath)
math = m.captures.compact.first math = m.captures.compact.first
con.push doc.md_inline_math(math) con.push doc.md_inline_math(math)
true true
else end)
#puts "not math: #{src.cur_chars 10}"
false
end
}
)
MathOpen1 = Regexp.escape('\\begin{equation}') MathOpen1 = Regexp.escape('\\begin{equation}')
@ -50,70 +45,67 @@ end
# $1 is opening, $2 is tex, $3 is closing, $4 is label # $1 is opening, $2 is tex, $3 is closing, $4 is label
OneLineEquation = /^[ ]{0,3}(#{EquationOpen})(.*)(#{EquationClose})\s*#{EqLabel}?\s*$/ OneLineEquation = /^[ ]{0,3}(#{EquationOpen})(.*)(#{EquationClose})\s*#{EqLabel}?\s*$/
MaRuKu::In::Markdown::register_block_extension( MaRuKu::In::Markdown.register_block_extension(
:regexp => EquationStart, :regexp => EquationStart,
:handler => lambda { |doc, src, con| :handler => lambda do |doc, src, con|
return false if not doc.is_math_enabled? next false unless doc.is_math_enabled?
first = src.shift_line first = src.shift_line
if first =~ OneLineEquation if first =~ OneLineEquation
opening, tex, closing, label = $1, $2, $3, $4 opening, tex, closing, label = $1, $2, $3, $4
numerate = doc.get_setting(:math_numbered).include?(opening) numerate = doc.get_setting(:math_numbered).include?(opening)
con.push doc.md_equation(tex, label, numerate) con.push doc.md_equation(tex, label, numerate)
else next true
first =~ EquationStart end
opening, tex = $1, $2
opening, tex = first.scan(EquationStart).first
numerate = doc.get_setting(:math_numbered).include?(opening) numerate = doc.get_setting(:math_numbered).include?(opening)
label = nil label = nil
while true loop do
if not src.cur_line unless src.cur_line
doc.maruku_error("Stream finished while reading equation\n\n"+ doc.maruku_error(
doc.add_tabs(tex,1,'$> '), src, con) "Stream finished while reading equation\n\n" + tex.gsub(/^/, '$> '),
src, con)
break break
end end
line = src.shift_line line = src.shift_line
if line =~ EquationEnd if line =~ EquationEnd
tex_line, closing = $1, $2 tex_line, closing = $1, $2
label = $3 if $3 label = $3 if $3
tex += tex_line + "\n" tex << tex_line << "\n"
break break
else
tex += line + "\n"
end end
tex << line << "\n"
end end
con.push doc.md_equation(tex, label, numerate) con.push doc.md_equation(tex, label, numerate)
end
true true
}) end)
# This adds support for \eqref # This adds support for \eqref
RegEqrefLatex = /\\eqref\{(\w+)\}/ RegEqrefLatex = /\\eqref\{(\w+)\}/
RegEqPar = /\(eq:(\w+)\)/ RegEqPar = /\(eq:(\w+)\)/
RegEqref = Regexp::union(RegEqrefLatex, RegEqPar) RegEqref = Regexp.union(RegEqrefLatex, RegEqPar)
MaRuKu::In::Markdown::register_span_extension( MaRuKu::In::Markdown.register_span_extension(
:chars => [?\\, ?(], :chars => [?\\, ?(],
:regexp => RegEqref, :regexp => RegEqref,
:handler => lambda { |doc, src, con| :handler => lambda do |doc, src, con|
return false if not doc.is_math_enabled? return false unless doc.is_math_enabled?
eqid = src.read_regexp(RegEqref).captures.compact.first eqid = src.read_regexp(RegEqref).captures.compact.first
r = doc.md_el(:eqref, [], meta={:eqid=>eqid}) con.push doc.md_el(:eqref, [], :eqid => eqid)
con.push r
true true
} end)
)
# This adds support for \ref # This adds support for \ref
RegRef = /\\ref\{(\w*)\}/ RegRef = /\\ref\{(\w*)\}/
MaRuKu::In::Markdown::register_span_extension( MaRuKu::In::Markdown.register_span_extension(
:chars => [?\\, ?(], :chars => [?\\, ?(],
:regexp => RegRef, :regexp => RegRef,
:handler => lambda { |doc, src, con| :handler => lambda do |doc, src, con|
return false if not doc.is_math_enabled? return false unless doc.is_math_enabled?
refid = src.read_regexp(RegRef).captures.compact.first refid = src.read_regexp(RegRef).captures.compact.first
r = doc.md_el(:divref, [], meta={:refid=>refid}) con.push doc.md_el(:divref, [], :refid => refid)
con.push r
true true
} end)
)

View file

@ -1,4 +1,3 @@
=begin maruku_doc =begin maruku_doc
Extension: math Extension: math
Attribute: html_math_engine Attribute: html_math_engine
@ -39,41 +38,42 @@ Same thing as `html_math_engine`, only for PNG output.
=end =end
module MaRuKu; module Out; module HTML module MaRuKu
module Out
module HTML
# Creates an xml Mathml document of this node's TeX code.
# Creates an xml Mathml document of self.math #
# @return [REXML::Document]
def render_mathml(kind, tex) def render_mathml(kind, tex)
engine = get_setting(:html_math_engine) engine = get_setting(:html_math_engine)
method = "convert_to_mathml_#{engine}".to_sym method = "convert_to_mathml_#{engine}"
if self.respond_to? method if self.respond_to? method
mathml = self.send(method, kind, tex) mathml = self.send(method, kind, tex)
return mathml || convert_to_mathml_none(kind, tex) return mathml || convert_to_mathml_none(kind, tex)
else end
# TODO: Warn here
puts "A method called #{method} should be defined." puts "A method called #{method} should be defined."
return convert_to_mathml_none(kind, tex) return convert_to_mathml_none(kind, tex)
end end
end
# Creates an xml Mathml document of self.math # Renders a PNG image of this node's TeX code.
# Returns
#
# @return [MaRuKu::Out::HTML::PNG, nil]
# A struct describing the location and size of the image,
# or nil if no library is loaded that can render PNGs.
def render_png(kind, tex) def render_png(kind, tex)
engine = get_setting(:html_png_engine) engine = get_setting(:html_png_engine)
method = "convert_to_png_#{engine}".to_sym method = "convert_to_png_#{engine}".to_sym
if self.respond_to? method return self.send(method, kind, tex) if self.respond_to? method
return self.send(method, kind, tex)
else
puts "A method called #{method} should be defined." puts "A method called #{method} should be defined."
return nil return nil
end end
end
def pixels_per_ex def pixels_per_ex
if not $pixels_per_ex $pixels_per_ex ||= render_png(:inline, "x").height
x = render_png(:inline, "x")
$pixels_per_ex = x.height # + x.depth
end
$pixels_per_ex
end end
def adjust_png(png, use_depth) def adjust_png(png, use_depth)
@ -85,8 +85,9 @@ module MaRuKu; module Out; module HTML
depth_in_ex = depth_in_px / pixels_per_ex depth_in_ex = depth_in_px / pixels_per_ex
total_height_in_ex = height_in_ex + depth_in_ex total_height_in_ex = height_in_ex + depth_in_ex
style = "" style = ""
style += "vertical-align: -#{depth_in_ex}ex;" if use_depth style << "vertical-align: -#{depth_in_ex}ex;" if use_depth
style += "height: #{total_height_in_ex}ex;" style << "height: #{total_height_in_ex}ex;"
img = Element.new 'img' img = Element.new 'img'
img.attributes['src'] = src img.attributes['src'] = src
img.attributes['style'] = style img.attributes['style'] = style
@ -107,12 +108,12 @@ module MaRuKu; module Out; module HTML
end end
if png if png
img = adjust_png(png, use_depth=true) img = adjust_png(png, true)
add_class_to(img, 'maruku-png') add_class_to(img, 'maruku-png')
span << img span << img
end end
span
span
end end
def to_html_equation def to_html_equation
@ -125,8 +126,7 @@ module MaRuKu; module Out; module HTML
if self.label # then numerate if self.label # then numerate
span = Element.new 'span' span = Element.new 'span'
span.attributes['class'] = 'maruku-eq-number' span.attributes['class'] = 'maruku-eq-number'
num = self.num span << Text.new("(#{self.num})")
span << Text.new("(#{num})")
div << span div << span
div.attributes['id'] = "eq:#{self.label}" div.attributes['id'] = "eq:#{self.label}"
end end
@ -135,14 +135,13 @@ module MaRuKu; module Out; module HTML
end end
if png if png
img = adjust_png(png, use_depth=false) img = adjust_png(png, false)
add_class_to(img, 'maruku-png') add_class_to(img, 'maruku-png')
div << img div << img
if self.label # then numerate if self.label # then numerate
span = Element.new 'span' span = Element.new 'span'
span.attributes['class'] = 'maruku-eq-number' span.attributes['class'] = 'maruku-eq-number'
num = self.num span << Text.new("(#{self.num})")
span << Text.new("(#{num})")
div << span div << span
div.attributes['id'] = "eq:#{self.label}" div.attributes['id'] = "eq:#{self.label}"
end end
@ -159,37 +158,31 @@ module MaRuKu; module Out; module HTML
end end
def to_html_eqref def to_html_eqref
if eq = self.doc.eqid2eq[self.eqid] unless eq = self.doc.eqid2eq[self.eqid]
num = eq.num maruku_error "Cannot find equation #{self.eqid.inspect}"
return Text.new("(eq:#{self.eqid})")
end
a = Element.new 'a' a = Element.new 'a'
a.attributes['class'] = 'maruku-eqref' a.attributes['class'] = 'maruku-eqref'
a.attributes['href'] = "#eq:#{self.eqid}" a.attributes['href'] = "#eq:#{self.eqid}"
a << Text.new("(#{num})") a << Text.new("(#{eq.num})")
a a
else
maruku_error "Cannot find equation #{self.eqid.inspect}"
Text.new "(eq:#{self.eqid})"
end
end end
def to_html_divref def to_html_divref
ref= nil unless hash = self.doc.refid2ref.values.find {|h| h.has_key?(self.refid)}
self.doc.refid2ref.each_value { |h| maruku_error "Cannot find div #{self.refid.inspect}"
ref = h[self.refid] if h.has_key?(self.refid) return Text.new("\\ref{#{self.refid}}")
} end
if ref ref= hash[self.refid]
num = ref.num
a = Element.new 'a' a = Element.new 'a'
a.attributes['class'] = 'maruku-ref' a.attributes['class'] = 'maruku-ref'
a.attributes['href'] = "#" + self.refid a.attributes['href'] = "#" + self.refid
a << Text.new(num.to_s) a << Text.new(ref.num.to_s)
a a
else
maruku_error "Cannot find div #{self.refid.inspect}"
Text.new "\\ref{#{self.refid}}"
end end
end end
end
end end end end

View file

@ -1,17 +1,17 @@
require 'maruku/ext/math/latex_fix' require 'maruku/ext/math/latex_fix'
module MaRuKu; module Out; module Latex module MaRuKu
module Out
module Latex
def to_latex_inline_math def to_latex_inline_math
"$#{self.math.strip}$".fix_latex fix_latex("$#{self.math.strip}$")
end end
def to_latex_equation def to_latex_equation
if self.label if self.label
l = "\\label{#{self.label}}" fix_latex("\\begin{equation}\n#{self.math.strip}\n\\label{#{self.label}}\\end{equation}\n")
"\\begin{equation}\n#{self.math.strip}\n#{l}\\end{equation}\n".fix_latex
else else
"\\begin{displaymath}\n#{self.math.strip}\n\\end{displaymath}\n".fix_latex fix_latex("\\begin{displaymath}\n#{self.math.strip}\n\\end{displaymath}\n")
end end
end end
@ -23,4 +23,14 @@ module MaRuKu; module Out; module Latex
"\\ref{#{self.refid}}" "\\ref{#{self.refid}}"
end end
end end end private
def fix_latex(str)
return str unless self.get_setting(:html_math_engine) == 'itex2mml'
s = str.gsub("\\mathop{", "\\operatorname{")
s.gsub!(/\\begin\{svg\}.*?\\end\{svg\}/m, " ")
s.gsub("\\space{", "\\itexspace{")
end
end
end
end

View file

@ -1,4 +1,3 @@
#--
# Copyright (C) 2006 Andrea Censi <andrea (at) rubyforge.org> # Copyright (C) 2006 Andrea Censi <andrea (at) rubyforge.org>
# #
# This file is part of Maruku. # This file is part of Maruku.
@ -16,34 +15,31 @@
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with Maruku; if not, write to the Free Software # along with Maruku; if not, write to the Free Software
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
#++
# A series of helper functions for creating elements: they hide the
# particular internal representation.
#
# Please, always use these instead of creating MDElement.
#
module MaRuKu module MaRuKu
# A collection of helper functions for creating Markdown elements.
# They hide the particular internal representations.
#
# Always use these rather than creating an {MDElement} directly.
module Helpers module Helpers
# @param children [Array<MDElement, String>]
# if the first is a md_ial, it is used as such # The child nodes.
# If the first child is a \{#md\_ial}, it's merged with `al`
def md_el(node_type, children = [], meta = {}, al = nil) def md_el(node_type, children = [], meta = {}, al = nil)
if (e=children.first).kind_of?(MDElement) and first = children.first
e.node_type == :ial then if first.is_a?(MDElement) && first.node_type == :ial
if al if al
al += e.ial al += first.ial
else else
al = e.ial al = first.ial
end end
children.shift children.shift
end end
e = MDElement.new(node_type, children, meta, al) e = MDElement.new(node_type, children, meta, al)
e.doc = @doc e.doc = @doc
return e e
end end
def md_header(level, children, al = nil) def md_header(level, children, al = nil)
@ -77,25 +73,21 @@ module Helpers
end end
def md_abbr(abbr, title) def md_abbr(abbr, title)
md_el(:abbr, [abbr], {:title=>title}) md_el(:abbr, [abbr], :title => title)
end end
def md_html(raw_html, al = nil) def md_html(raw_html, al = nil)
e = md_el(:raw_html, [], {:raw_html=>raw_html}) e = md_el(:raw_html, [], :raw_html => raw_html)
begin begin
# remove newlines and whitespace at begin e.instance_variable_set("@parsed_html",
# end end of string, or else REXML gets confused REXML::Document.new("<marukuwrap>#{raw_html.strip}</marukuwrap>"))
raw_html = raw_html.gsub(/\A\s*</,'<').
gsub(/>[\s\n]*\Z/,'>')
raw_html = "<marukuwrap>#{raw_html}</marukuwrap>"
e.instance_variable_set :@parsed_html,
REXML::Document.new(raw_html)
rescue REXML::ParseException => ex rescue REXML::ParseException => ex
e.instance_variable_set :@parsed_html, nil e.instance_variable_set "@parsed_html", nil
maruku_recover "REXML cannot parse this block of HTML/XML:\n"+ maruku_recover <<ERR
add_tabs(raw_html,1,'|') + "\n"+ex.inspect REXML cannot parse this block of HTML/XML:
# " #{raw_html.inspect}\n\n"+ex.inspect #{raw_html.gsub(/^/, '|').rstrip}
#{ex.inspect}
ERR
end end
e e
end end
@ -120,11 +112,11 @@ module Helpers
md_el(:emphasis, [children].flatten, {}, al) md_el(:emphasis, [children].flatten, {}, al)
end end
def md_br() def md_br
md_el(:linebreak, [], {}, nil) md_el(:linebreak, [], {}, nil)
end end
def md_hrule() def md_hrule
md_el(:hrule, [], {}, nil) md_el(:hrule, [], {}, nil)
end end
@ -136,13 +128,13 @@ module Helpers
md_strong(md_em(children), al) md_strong(md_em(children), al)
end end
# <http://www.example.com/> # A URL to be linkified (e.g. `<http://www.example.com/>`).
def md_url(url, al = nil) def md_url(url, al = nil)
md_el(:immediate_link, [], {:url => url}, al) md_el(:immediate_link, [], {:url => url}, al)
end end
# <andrea@rubyforge.org> # An email to be linkified
# <mailto:andrea@rubyforge.org> # (e.g. `<andrea@rubyforge.org>` or `<mailto:andrea@rubyforge.org>`).
def md_email(email, al = nil) def md_email(email, al = nil)
md_el(:email_address, [], {:email => email}, al) md_el(:email_address, [], {:email => email}, al)
end end
@ -160,7 +152,7 @@ module Helpers
md_el(:paragraph, children, meta = {}, al) md_el(:paragraph, children, meta = {}, al)
end end
# [1]: http://url [properties] # A definition of a reference (e.g. `[1]: http://url [properties]`).
def md_ref_def(ref_id, url, title = nil, meta = {}, al = nil) def md_ref_def(ref_id, url, title = nil, meta = {}, al = nil)
meta[:url] = url meta[:url] = url
meta[:ref_id] = ref_id meta[:ref_id] = ref_id
@ -170,91 +162,53 @@ module Helpers
# inline attribute list # inline attribute list
def md_ial(al) def md_ial(al)
al = Maruku::AttributeList.new(al) if al = Maruku::AttributeList.new(al) unless al.is_a?(Maruku::AttributeList)
not al.kind_of?Maruku::AttributeList md_el(:ial, [], :ial => al)
md_el(:ial, [], {:ial=>al})
end end
# Attribute list definition # Attribute list definition
def md_ald(id, al) def md_ald(id, al)
md_el(:ald, [], {:ald_id=>id,:ald=>al}) md_el(:ald, [], :ald_id => id, :ald => al)
end end
# Server directive <?target code... ?> # A server directive (e.g. `<?target code... ?>`)
def md_xml_instr(target, code) def md_xml_instr(target, code)
md_el(:xml_instr, [], {:target=>target, :code=>code}) md_el(:xml_instr, [], :target => target, :code => code)
end
end end
end end
module MaRuKu
class MDElement class MDElement
# outputs abbreviated form (this should be eval()uable to get the document) INSPECT2_FORMS = {
:paragraph => ["par", :children],
:footnote_reference => ["foot_ref", :footnote_id],
:entity => ["entity", :entity_name],
:email_address => ["email", :email],
:inline_code => ["code", :raw_code],
:raw_html => ["html", :raw_html],
:emphasis => ["em", :children],
:strong => ["strong", :children],
:immediate_link => ["url", :url],
:image => ["image", :children, :ref_id],
:im_image => ["im_image", :children, :url, :title],
:link => ["link", :children, :ref_id],
:im_link => ["im_link", :children, :url, :title],
:ref_definition => ["ref_def", :ref_id, :url, :title],
:ial => ["ial", :ial]
}
# Outputs the abbreviated form of an element
# (this should be `eval`-able to get a copy of the original element).
def inspect2 def inspect2
s = name, *params = INSPECT2_FORMS[@node_type]
case @node_type return nil unless name
when :paragraph
"md_par(%s)" % children_inspect params = params.map do |p|
when :footnote_reference next children_inspect if p == :children
"md_foot_ref(%s)" % self.footnote_id.inspect send(p).inspect
when :entity
"md_entity(%s)" % self.entity_name.inspect
when :email_address
"md_email(%s)" % self.email.inspect
when :inline_code
"md_code(%s)" % self.raw_code.inspect
when :raw_html
"md_html(%s)" % self.raw_html.inspect
when :emphasis
"md_em(%s)" % children_inspect
when :strong
"md_strong(%s)" % children_inspect
when :immediate_link
"md_url(%s)" % self.url.inspect
when :image
"md_image(%s, %s)" % [
children_inspect,
self.ref_id.inspect]
when :im_image
"md_im_image(%s, %s, %s)" % [
children_inspect,
self.url.inspect,
self.title.inspect]
when :link
"md_link(%s,%s)" % [
children_inspect, self.ref_id.inspect]
when :im_link
"md_im_link(%s, %s, %s)" % [
children_inspect,
self.url.inspect,
self.title.inspect,
]
when :ref_definition
"md_ref_def(%s, %s, %s)" % [
self.ref_id.inspect,
self.url.inspect,
self.title.inspect
]
when :ial
"md_ial(%s)" % self.ial.inspect
else
return nil
end end
if @al and not @al.empty? then params << @al.inspect if @al && !@al.empty?
s = s.chop + ", #{@al.inspect})"
"md_#{name}(#{params.join(', ')})"
end end
s
end end
end end
end

View file

@ -191,7 +191,7 @@ def describe_pos(buffer, buffer_index)
pre + "+--- Byte #{buffer_index}\n"+ pre + "+--- Byte #{buffer_index}\n"+
"Shown bytes [#{index_start} to #{size}] of #{buffer.size}:\n"+ "Shown bytes [#{index_start} to #{size}] of #{buffer.size}:\n"+
add_tabs(buffer,1,">") buffer.gsub(/^/, ">")
# "CharSource: At character #{@buffer_index} of block "+ # "CharSource: At character #{@buffer_index} of block "+
# " beginning with:\n #{@buffer[0,50].inspect} ...\n"+ # " beginning with:\n #{@buffer[0,50].inspect} ...\n"+

View file

@ -168,9 +168,9 @@ module MaRuKu; module In; module Markdown; module SpanLevelParser
"match=#{@m.to_s.inspect}\n"+ "match=#{@m.to_s.inspect}\n"+
"Tag stack = #{@tag_stack.inspect} \n"+ "Tag stack = #{@tag_stack.inspect} \n"+
"Before:\n"+ "Before:\n"+
add_tabs(@already,1,'|')+"\n"+ @already.gsub(/^/, '|')+"\n"+
"After:\n"+ "After:\n"+
add_tabs(@rest,1,'|')+"\n" @rest.gsub(/^/, '|')+"\n"
end end

View file

@ -71,7 +71,7 @@ class LineSource
# if @parent # if @parent
# s << "Parent context is: \n" # s << "Parent context is: \n"
# s << add_tabs(@parent.describe,1,'|') # s << @parent.describe.gsub(/^/, '|')
# end # end
s s
end end

View file

@ -198,8 +198,8 @@ module MaRuKu; module In; module Markdown; module BlockLevelParser
ial = $2 ial = $2
al = read_attribute_list(CharSource.new(ial,src), context=nil, break_on=[nil]) al = read_attribute_list(CharSource.new(ial,src), context=nil, break_on=[nil])
end end
level = num_leading_hashes(line) level = line[/^#+/].size
text = parse_lines_as_span [strip_hashes(line)] text = parse_lines_as_span [line.gsub(/\A#+|#+\Z/, '')]
return md_header(level, text, al) return md_header(level, text, al)
end end
@ -214,7 +214,7 @@ module MaRuKu; module In; module Markdown; module BlockLevelParser
if not code =~ (/\?>\s*$/) if not code =~ (/\?>\s*$/)
garbage = (/\?>(.*)$/.match(code))[1] garbage = (/\?>(.*)$/.match(code))[1]
maruku_error "Trailing garbage on last line: #{garbage.inspect}:\n"+ maruku_error "Trailing garbage on last line: #{garbage.inspect}:\n"+
add_tabs(code, 1, '|'), src code.gsub(/^/, '|'), src
end end
code.gsub!(/\?>\s*$/, '') code.gsub!(/\?>\s*$/, '')
@ -244,7 +244,7 @@ module MaRuKu; module In; module Markdown; module BlockLevelParser
end end
rescue Exception => e rescue Exception => e
ex = e.inspect + e.backtrace.join("\n") ex = e.inspect + e.backtrace.join("\n")
maruku_error "Bad block-level HTML:\n#{add_tabs(ex,1,'|')}\n", src maruku_error "Bad block-level HTML:\n#{ex.gsub(/^/, '|')}\n", src
end end
if not (h.rest =~ /^\s*$/) if not (h.rest =~ /^\s*$/)
maruku_error "Could you please format this better?\n"+ maruku_error "Could you please format this better?\n"+
@ -271,7 +271,6 @@ module MaRuKu; module In; module Markdown; module BlockLevelParser
lines << src.shift_line lines << src.shift_line
end end
# dbg_describe_ary(lines, 'PAR')
children = parse_lines_as_span(lines, src) children = parse_lines_as_span(lines, src)
return md_par(children) return md_par(children)
@ -296,7 +295,6 @@ module MaRuKu; module In; module Markdown; module BlockLevelParser
stripped = first[indentation, first.size-1] stripped = first[indentation, first.size-1]
lines.unshift stripped lines.unshift stripped
# dbg_describe_ary(lines, 'LIST ITEM ')
src2 = LineSource.new(lines, src, parent_offset) src2 = LineSource.new(lines, src, parent_offset)
children = parse_blocks(src2) children = parse_blocks(src2)
@ -347,7 +345,6 @@ module MaRuKu; module In; module Markdown; module BlockLevelParser
# add first line # add first line
if text && text.strip != "" then lines.unshift text end if text && text.strip != "" then lines.unshift text end
# dbg_describe_ary(lines, 'FOOTNOTE')
src2 = LineSource.new(lines, src, parent_offset) src2 = LineSource.new(lines, src, parent_offset)
children = parse_blocks(src2) children = parse_blocks(src2)
@ -405,7 +402,6 @@ module MaRuKu; module In; module Markdown; module BlockLevelParser
want_my_paragraph = saw_anything_after || want_my_paragraph = saw_anything_after ||
(saw_empty && (src.cur_line && (src.cur_line.md_type == item_type))) (saw_empty && (src.cur_line && (src.cur_line.md_type == item_type)))
# dbg_describe_ary(lines, 'LI')
# create a new context # create a new context
while lines.last && (lines.last.md_type == :empty) while lines.last && (lines.last.md_type == :empty)
@ -424,7 +420,6 @@ module MaRuKu; module In; module Markdown; module BlockLevelParser
while src.cur_line && src.cur_line.md_type == :quote while src.cur_line && src.cur_line.md_type == :quote
lines << unquote(src.shift_line) lines << unquote(src.shift_line)
end end
# dbg_describe_ary(lines, 'QUOTE')
src2 = LineSource.new(lines, src, parent_offset) src2 = LineSource.new(lines, src, parent_offset)
children = parse_blocks(src2) children = parse_blocks(src2)
@ -451,7 +446,6 @@ module MaRuKu; module In; module Markdown; module BlockLevelParser
source = lines.join("\n") source = lines.join("\n")
# dbg_describe_ary(lines, 'CODE')
return md_codeblock(source) return md_codeblock(source)
end end
@ -568,7 +562,6 @@ module MaRuKu; module In; module Markdown; module BlockLevelParser
while src.cur_line && src.cur_line.md_type == :text while src.cur_line && src.cur_line.md_type == :text
terms << md_el(:definition_term, parse_lines_as_span([src.shift_line])) terms << md_el(:definition_term, parse_lines_as_span([src.shift_line]))
end end
# dbg_describe_ary(terms, 'DT')
want_my_paragraph = false want_my_paragraph = false
@ -599,7 +592,6 @@ module MaRuKu; module In; module Markdown; module BlockLevelParser
lines.unshift first lines.unshift first
# dbg_describe_ary(lines, 'DD')
src2 = LineSource.new(lines, src, parent_offset) src2 = LineSource.new(lines, src, parent_offset)
children = parse_blocks(src2) children = parse_blocks(src2)
definitions << md_el(:definition_data, children) definitions << md_el(:definition_data, children)

View file

@ -141,19 +141,19 @@ Disabled by default because of security concerns.
return object.instance_eval(code) return object.instance_eval(code)
rescue Exception => e rescue Exception => e
maruku_error "Exception while executing this:\n"+ maruku_error "Exception while executing this:\n"+
add_tabs(code, 1, ">")+ code.gsub(/^/, ">")+
"\nThe error was:\n"+ "\nThe error was:\n"+
add_tabs(e.inspect+"\n"+e.caller.join("\n"), 1, "|") (e.inspect+"\n"+e.caller.join("\n")).gsub(/^/, "|")
rescue RuntimeError => e rescue RuntimeError => e
maruku_error "2: Exception while executing this:\n"+ maruku_error "2: Exception while executing this:\n"+
add_tabs(code, 1, ">")+ code.gsub(/^/, ">")+
"\nThe error was:\n"+ "\nThe error was:\n"+
add_tabs(e.inspect, 1, "|") e.inspect.gsub(/^/, "|")
rescue SyntaxError => e rescue SyntaxError => e
maruku_error "2: Exception while executing this:\n"+ maruku_error "2: Exception while executing this:\n"+
add_tabs(code, 1, ">")+ code.gsub(/^/, ">")+
"\nThe error was:\n"+ "\nThe error was:\n"+
add_tabs(e.inspect, 1, "|") e.inspect.gsub(/^/, "|")
end end
nil nil
end end

View file

@ -500,7 +500,7 @@ module MaRuKu; module In; module Markdown; module SpanLevelParser
# end # end
rescue Exception => e rescue Exception => e
maruku_error "Bad html: \n" + maruku_error "Bad html: \n" +
add_tabs(e.inspect+e.backtrace.join("\n"),1,'>'), (e.inspect+e.backtrace.join("\n")).gsub(/^/, '>'),
src,con src,con
maruku_recover "I will try to continue after bad HTML.", src, con maruku_recover "I will try to continue after bad HTML.", src, con
con.push_char src.shift_char con.push_char src.shift_char
@ -733,7 +733,7 @@ module MaRuKu; module In; module Markdown; module SpanLevelParser
def describe def describe
lines = @elements.map{|x| x.inspect}.join("\n") lines = @elements.map{|x| x.inspect}.join("\n")
s = "Elements read in span: \n" + s = "Elements read in span: \n" +
add_tabs(lines,1, ' -')+"\n" lines.gsub(/^/, ' -')+"\n"
if @cur_string.size > 0 if @cur_string.size > 0
s += "Current string: \n #{@cur_string.inspect}\n" s += "Current string: \n #{@cur_string.inspect}\n"

View file

@ -1,4 +1,3 @@
#--
# Copyright (C) 2006 Andrea Censi <andrea (at) rubyforge.org> # Copyright (C) 2006 Andrea Censi <andrea (at) rubyforge.org>
# #
# This file is part of Maruku. # This file is part of Maruku.
@ -16,18 +15,17 @@
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with Maruku; if not, write to the Free Software # along with Maruku; if not, write to the Free Software
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
#++
# The Maruku class is the public interface # The public interface for Maruku.
# #
# @example Render a document fragment
class Maruku # Maruku.new("## Header ##").to_html
# # => "<h2 id='header'>header</h2>"
class Maruku < MaRuKu::MDDocument
def initialize(s = nil, meta = {}) def initialize(s = nil, meta = {})
super(nil) super(nil)
self.attributes.merge! meta self.attributes.merge! meta
if s parse_doc(s) if s
parse_doc(s)
end
end end
end end

View file

@ -828,7 +828,7 @@ If true, raw HTML is discarded from the output.
else # invalid else # invalid
# Creates red box with offending HTML # Creates red box with offending HTML
tell_user "Wrapping bad html in a PRE with class 'markdown-html-error'\n"+ tell_user "Wrapping bad html in a PRE with class 'markdown-html-error'\n"+
add_tabs(raw_html,1,'|') raw_html.gsub(/^/, '|')
pre = Element.new('pre') pre = Element.new('pre')
pre.attributes['style'] = 'border: solid 3px red; background-color: pink' pre.attributes['style'] = 'border: solid 3px red; background-color: pink'
pre.attributes['class'] = 'markdown-html-error' pre.attributes['class'] = 'markdown-html-error'

View file

@ -55,10 +55,10 @@ module MaRuKu; module Out; module Latex
end end
class LatexEntity class LatexEntity
safe_attr_accessor :html_num, Fixnum attr_accessor :html_num
safe_attr_accessor :html_entity, String attr_accessor :html_entity
safe_attr_accessor :latex_string, String attr_accessor :latex_string
safe_attr_accessor :latex_packages, Array attr_accessor :latex_packages
end end
def Latex.need_entity_table def Latex.need_entity_table
@ -69,7 +69,7 @@ module MaRuKu; module Out; module Latex
def Latex.init_entity_table def Latex.init_entity_table
# $stderr.write "Creating entity table.." # $stderr.write "Creating entity table.."
# $stderr.flush # $stderr.flush
doc = Document.new XML_TABLE doc = Document.new(File.read(File.dirname(__FILE__) + "/../../../data/entities.xml"))
doc.elements.each("//char") do |c| doc.elements.each("//char") do |c|
num = c.attributes['num'].to_i num = c.attributes['num'].to_i
name = c.attributes['name'] name = c.attributes['name']
@ -97,271 +97,5 @@ module MaRuKu; module Out; module Latex
ENTITY_TABLE = {} ENTITY_TABLE = {}
# The following is a conversion chart for html elements, courtesy of
# text2html
XML_TABLE ="
<chars>
<char num='913' name='Alpha' convertTo='$A$' />
<char num='914' name='Beta' convertTo='$B$' />
<char num='915' name='Gamma' convertTo='$\\Gamma$' />
<char num='916' name='Delta' convertTo='$\\Delta$' />
<char num='917' name='Epsilon' convertTo='$E$' />
<char num='918' name='Zeta' convertTo='$Z$' />
<char num='919' name='Eta' convertTo='$H$' />
<char num='920' name='Theta' convertTo='$\\Theta$' />
<char num='921' name='Iota' convertTo='$I$' />
<char num='922' name='Kappa' convertTo='$K$' />
<char num='923' name='Lambda' convertTo='$\\Lambda$' />
<char num='924' name='Mu' convertTo='$M$' />
<char num='925' name='Nu' convertTo='$N$' />
<char num='926' name='Xi' convertTo='$\\Xi$' />
<char num='927' name='Omicron' convertTo='$O$' />
<char num='928' name='Pi' convertTo='$\\Pi$' />
<char num='929' name='Rho' convertTo='$P$' />
<char num='931' name='Sigma' convertTo='$\\Sigma$' />
<char num='932' name='Tau' convertTo='$T$' />
<char num='933' name='Upsilon' convertTo='$Y$' />
<char num='934' name='Phi' convertTo='$\\Phi$' />
<char num='935' name='Chi' convertTo='$X$' />
<char num='936' name='Psi' convertTo='$\\Psi$' />
<char num='937' name='Omega' convertTo='$\\Omega$' />
<char num='945' name='alpha' convertTo='$\\alpha$' />
<char num='946' name='beta' convertTo='$\\beta$' />
<char num='947' name='gamma' convertTo='$\\gamma$' />
<char num='948' name='delta' convertTo='$\\delta$' />
<char num='949' name='epsilon' convertTo='$\\epsilon$' />
<char num='950' name='zeta' convertTo='$\\zeta$' />
<char num='951' name='eta' convertTo='$\\eta$' />
<char num='952' name='theta' convertTo='$\\theta$' />
<char num='953' name='iota' convertTo='$\\iota$' />
<char num='954' name='kappa' convertTo='$\\kappa$' />
<char num='955' name='lambda' convertTo='$\\lambda$' />
<char num='956' name='mu' convertTo='$\\mu$' />
<char num='957' name='nu' convertTo='$\\nu$' />
<char num='958' name='xi' convertTo='$\\xi$' />
<char num='959' name='omicron' convertTo='$o$' />
<char num='960' name='pi' convertTo='$\\pi$' />
<char num='961' name='rho' convertTo='$\\rho$' />
<char num='963' name='sigma' convertTo='$\\sigma$' />
<char num='964' name='tau' convertTo='$\\tau$' />
<char num='965' name='upsilon' convertTo='$\\upsilon$' />
<char num='966' name='phi' convertTo='$\\phi$' />
<char num='967' name='chi' convertTo='$\\chi$' />
<char num='968' name='psi' convertTo='$\\psi$' />
<char num='969' name='omega' convertTo='$\\omega$' />
<char num='962' name='sigmaf' convertTo='$\\varsigma$' />
<char num='977' name='thetasym' convertTo='$\\vartheta$' />
<char num='982' name='piv' convertTo='$\\varpi$' />
<char num='8230' name='hellip' convertTo='\\ldots' />
<char num='8242' name='prime' convertTo='$\\prime$' />
<char num='8254' name='oline' convertTo='-' />
<char num='8260' name='frasl' convertTo='/' />
<char num='8472' name='weierp' convertTo='$\\wp$' />
<char num='8465' name='image' convertTo='$\\Im$' />
<char num='8476' name='real' convertTo='$\\Re$' />
<char num='8501' name='alefsym' convertTo='$\\aleph$' />
<char num='8226' name='bull' convertTo='$\\bullet$' />
<char num='8482' name='trade' convertTo='$^{\\rm TM}$' /> <!-- \texttrademark -->
<char num='8592' name='larr' convertTo='$\\leftarrow$' />
<char num='8594' name='rarr' convertTo='$\\rightarrow$' />
<char num='8593' name='uarr' convertTo='$\\uparrow$' />
<char num='8595' name='darr' convertTo='$\\downarrow$' />
<char num='8596' name='harr' convertTo='$\\leftrightarrow$' />
<char num='8629' name='crarr' convertTo='$\\hookleftarrow$' />
<char num='8657' name='uArr' convertTo='$\\Uparrow$' />
<char num='8659' name='dArr' convertTo='$\\Downarrow$' />
<char num='8656' name='lArr' convertTo='$\\Leftarrow$' />
<char num='8658' name='rArr' convertTo='$\\Rightarrow$' />
<char num='8660' name='hArr' convertTo='$\\Leftrightarrow$' />
<char num='8704' name='forall' convertTo='$\\forall$' />
<char num='8706' name='part' convertTo='$\\partial$' />
<char num='8707' name='exist' convertTo='$\\exists$' />
<char num='8709' name='empty' convertTo='$\\emptyset$' />
<char num='8711' name='nabla' convertTo='$\\nabla$' />
<char num='8712' name='isin' convertTo='$\\in$' />
<char num='8715' name='ni' convertTo='$\\ni$' />
<char num='8713' name='notin' convertTo='$\\notin$' />
<char num='8721' name='sum' convertTo='$\\sum$' />
<char num='8719' name='prod' convertTo='$\\prod$' />
<char num='8722' name='minus' convertTo='$-$' />
<char num='8727' name='lowast' convertTo='$\\ast$' />
<char num='8730' name='radic' convertTo='$\\surd$' />
<char num='8733' name='prop' convertTo='$\\propto$' />
<char num='8734' name='infin' convertTo='$\\infty$' />
<char num='8736' name='ang' convertTo='$\\angle$' />
<char num='8743' name='and' convertTo='$\\wedge$' />
<char num='8744' name='or' convertTo='$\\vee$' />
<char num='8745' name='cup' convertTo='$\\cup$' />
<char num='8746' name='cap' convertTo='$\\cap$' />
<char num='8747' name='int' convertTo='$\\int$' />
<char num='8756' name='there4' convertTo='$\\therefore$' package='amssymb' /> <!-- only AMS -->
<char num='8764' name='sim' convertTo='$\\sim$' />
<char num='8776' name='asymp' convertTo='$\\approx$' />
<char num='8773' name='cong' convertTo='$\\cong$' />
<char num='8800' name='ne' convertTo='$\\neq$' />
<char num='8801' name='equiv' convertTo='$\\equiv$' />
<char num='8804' name='le' convertTo='$\\leq$' />
<char num='8805' name='ge' convertTo='$\\geq$' />
<char num='8834' name='sub' convertTo='$\\subset$' />
<char num='8835' name='sup' convertTo='$\\supset$' />
<!-- <char num='8838' name='sube' convertTo='$\\subseteq$' />-->
<char num='8839' name='supe' convertTo='$\\supseteq$' />
<!-- <char num='8836' name='nsub' convertTo='$\\nsubset$' /> --><!-- only AMS -->
<char num='8853' name='oplus' convertTo='$\\oplus$' />
<char num='8855' name='otimes' convertTo='$\\otimes$' />
<char num='8869' name='perp' convertTo='$\\perp$' />
<char num='8901' name='sdot' convertTo='$\\cdot$' />
<char num='8968' name='rceil' convertTo='$\\rceil$' />
<char num='8969' name='lceil' convertTo='$\\lceil$' />
<char num='8970' name='lfloor' convertTo='$\\lfloor$' />
<char num='8971' name='rfloor' convertTo='$\\rfloor$' />
<char num='9001' name='rang' convertTo='$\\rangle$' />
<char num='9002' name='lang' convertTo='$\\langle$' />
<char num='9674' name='loz' convertTo='$\\lozenge$' package='amssymb' /> <!-- only AMS -->
<char num='9824' name='spades' convertTo='$\\spadesuit$' />
<char num='9827' name='clubs' convertTo='$\\clubsuit$' />
<char num='9829' name='hearts' convertTo='$\\heartsuit$' />
<char num='9830' name='diams' convertTo='$\\diamondsuit$' />
<char num='38' name='amp' convertTo='\\@AMP' />
<!-- <char num='34' name='quot' convertTo='\\@DOUBLEQUOT' /> XXX -->
<char num='34' name='quot' convertTo='\"' />
<char num='39' name='apos' convertTo=\"'\" />
<char num='169' name='copy' convertTo='\\copyright' />
<char num='60' name='lt' convertTo='$@LT$' />
<char num='62' name='gt' convertTo='$@GT$' />
<char num='338' name='OElig' convertTo='\\OE' />
<char num='339' name='oelig' convertTo='\\oe' />
<char num='352' name='Scaron' convertTo='\\v{S}' />
<char num='353' name='scaron' convertTo='\\v{s}' />
<char num='376' name='Yuml' convertTo='\\\"Y' />
<char num='710' name='circ' convertTo='\\textasciicircum' />
<char num='732' name='tilde' convertTo='\\textasciitilde' />
<char num='8211' name='ndash' convertTo='--' />
<char num='8212' name='mdash' convertTo='---' />
<char num='8216' name='lsquo' convertTo='`' />
<char num='8217' name='rsquo' convertTo=\"'\" /> <!-- XXXX -->
<char num='8220' name='ldquo' convertTo='``' />
<char num='8221' name='rdquo' convertTo=\"''\" /> <!-- XXXX -->
<char num='8224' name='dagger' convertTo='\\dag' />
<char num='8225' name='Dagger' convertTo='\\ddag' />
<char num='8240' name='permil' convertTo='\\permil' package='wasysym' /> <!-- wasysym package -->
<char num='8364' name='euro' convertTo='\\euro' package='eurosym' /> <!-- eurosym package -->
<char num='8249' name='lsaquo' convertTo='\\guilsinglleft' package='aeguill'/>
<char num='8250' name='rsaquo' convertTo='\\guilsinglright' package='aeguill' />
<!-- <char num='160' name='nbsp' convertTo='\\nolinebreak' />-->
<char num='160' name='nbsp' convertTo='~' />
<char num='161' name='iexcl' convertTo='\\textexclamdown' />
<char num='163' name='pound' convertTo='\\pounds' />
<char num='164' name='curren' convertTo='\\currency' package='wasysym' /> <!-- wasysym package -->
<char num='165' name='yen' convertTo='\\textyen' package='textcomp'/> <!-- textcomp -->
<char num='166' name='brvbar' convertTo='\\brokenvert' /> <!-- wasysym -->
<char num='167' name='sect' convertTo='\\S' />
<char num='171' name='laquo' convertTo='\\guillemotleft' package='aeguill'/>
<char num='187' name='raquo' convertTo='\\guillemotright' package='aeguill'/>
<char num='174' name='reg' convertTo='\\textregistered' />
<char num='170' name='ordf' convertTo='\\textordfeminine' />
<char num='172' name='not' convertTo='$\\neg$' />
<!-- <char num='176' name='deg' convertTo='$\\degree$' /> --><!-- mathabx -->
<char num='176' name='deg' convertTo='\\textdegree' package='textcomp'/>
<char num='177' name='plusmn' convertTo='$\\pm$' />
<char num='180' name='acute' convertTo='@QUOT' />
<char num='181' name='micro' convertTo='$\\mu$' />
<char num='182' name='para' convertTo='\\P' />
<char num='183' name='middot' convertTo='$\\cdot$' />
<char num='186' name='ordm' convertTo='\\textordmasculine' />
<char num='162' name='cent' convertTo='\\cent' package='wasysym' />
<char num='185' name='sup1' convertTo='$^1$' />
<char num='178' name='sup2' convertTo='$^2$' />
<char num='179' name='sup3' convertTo='$^3$' />
<char num='189' name='frac12' convertTo='$\\frac{1}{2}$' />
<char num='188' name='frac14' convertTo='$\\frac{1}{4}$' />
<char num='190' name='frac34' convertTo='$\\frac{3}{4}$' />
<char num='192' name='Agrave' convertTo='\\`A' />
<char num='193' name='Aacute' convertTo='\\@QUOTA' />
<char num='194' name='Acirc' convertTo='\\^A' />
<char num='195' name='Atilde' convertTo='\\~A' />
<char num='196' name='Auml' convertTo='\\@DOUBLEQUOTA' />
<char num='197' name='Aring' convertTo='\\AA' />
<char num='198' name='AElig' convertTo='\\AE' />
<char num='199' name='Ccedil' convertTo='\\c{C}' />
<char num='200' name='Egrave' convertTo='\\`E' />
<char num='201' name='Eacute' convertTo='\\@QUOTE' />
<char num='202' name='Ecirc' convertTo='\\^E' />
<char num='203' name='Euml' convertTo='\\@DOUBLEQUOTE' />
<char num='204' name='Igrave' convertTo='\\`I' />
<char num='205' name='Iacute' convertTo='\\@QUOTI' />
<char num='206' name='Icirc' convertTo='\\^I' />
<char num='207' name='Iuml' convertTo='\\\"I' />
<char num='208' name='ETH' convertTo='$\\eth$' /> <!-- AMS -->
<char num='209' name='Ntilde' convertTo='\\~N' />
<char num='210' name='Ograve' convertTo='\\`O' />
<char num='211' name='Oacute' convertTo='\\@QUOT O' />
<char num='212' name='Ocirc' convertTo='\\^O' />
<char num='213' name='Otilde' convertTo='\\~O' />
<char num='214' name='Ouml' convertTo='\\@DOUBLEQUOTO' />
<char num='215' name='times' convertTo='$\\times$' />
<char num='216' name='Oslash' convertTo='\\O' />
<char num='217' name='Ugrave' convertTo='\\`U' />
<char num='218' name='Uacute' convertTo='\\@QUOTU' />
<char num='219' name='Ucirc' convertTo='\\^U' />
<char num='220' name='Uuml' convertTo='\\@DOUBLEQUOTU' />
<char num='221' name='Yacute' convertTo='\\@QUOTY' />
<char num='223' name='szlig' convertTo='\\ss' />
<char num='224' name='agrave' convertTo='\\`a' />
<char num='225' name='aacute' convertTo='\\@QUOTa' />
<char num='226' name='acirc' convertTo='\\^a' />
<char num='227' name='atilde' convertTo='\\~a' />
<char num='228' name='auml' convertTo='\\@DOUBLEQUOTa' />
<char num='229' name='aring' convertTo='\\aa' />
<char num='230' name='aelig' convertTo='\\ae' />
<char num='231' name='ccedil' convertTo='\\c{c}' />
<char num='232' name='egrave' convertTo='\\`e' />
<char num='233' name='eacute' convertTo='\\@QUOTe' />
<char num='234' name='ecirc' convertTo='\\^e' />
<char num='235' name='euml' convertTo='\\@DOUBLEQUOTe' />
<char num='236' name='igrave' convertTo='\\`i' />
<char num='237' name='iacute' convertTo='\\@QUOTi' />
<char num='238' name='icirc' convertTo='\\^i' />
<char num='239' name='iuml' convertTo='\\@DOUBLEQUOTi' />
<char num='240' name='eth' convertTo='$\\eth$' package='amssymb'/> <!-- -->
<char num='241' name='ntilde' convertTo='\\~n' />
<char num='242' name='ograve' convertTo='\\`o' />
<char num='243' name='oacute' convertTo='\\@QUOTo' />
<char num='244' name='ocirc' convertTo='\\^o' />
<char num='245' name='otilde' convertTo='\\~o' />
<char num='246' name='ouml' convertTo='\\@DOUBLEQUOTo' />
<!-- <char num='247' name='divide' convertTo='$\\divide$' /> -->
<char num='248' name='oslash' convertTo='\\o' />
<char num='249' name='ugrave' convertTo='\\`u' />
<char num='250' name='uacute' convertTo='\\@QUOTu' />
<char num='251' name='ucirc' convertTo='\\^u' />
<char num='252' name='uuml' convertTo='\\@DOUBLEQUOTu' />
<char num='253' name='yacute' convertTo='\\@QUOTy' />
<char num='255' name='yuml' convertTo='\\@DOUBLEQUOTy' />
<char num='222' name='THORN' convertTo='\\Thorn' package='wasysym' />
<char num='254' name='thorn' convertTo='\\thorn' package='wasysym' />
</chars>"
end end end end end end

View file

@ -47,7 +47,7 @@ module MaRuKu; module Out; module Markdown
def to_md_li_span(context) def to_md_li_span(context)
len = (context[:line_length] || DefaultLineLength) - 2 len = (context[:line_length] || DefaultLineLength) - 2
s = add_tabs(wrap(@children, len-2, context), 1, ' ') s = wrap(@children, len-2, context).rstrip.gsub(/^/, ' ')
s[0] = ?* s[0] = ?*
s + "\n" s + "\n"
end end
@ -60,7 +60,7 @@ module MaRuKu; module Out; module Markdown
len = (context[:line_length] || DefaultLineLength) - 2 len = (context[:line_length] || DefaultLineLength) - 2
md = "" md = ""
self.children.each_with_index do |li, i| self.children.each_with_index do |li, i|
s = add_tabs(w=wrap(li.children, len-2, context), 1, ' ')+"\n" s = (w=wrap(li.children, len-2, context)).rstrip.gsub(/^/, ' ')+"\n"
s[0,4] = "#{i+1}. "[0,4] s[0,4] = "#{i+1}. "[0,4]
# puts w.inspect # puts w.inspect
md += s md += s

View file

@ -18,150 +18,176 @@
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
#++ #++
require 'strscan'
# Boring stuff with strings. module MaRuKu
module MaRuKu; module Strings # Utility functions for dealing with strings.
module Strings
def add_tabs(s,n=1,char="\t") TAB_SIZE = 4
s.split("\n").map{|x| char*n+x }.join("\n")
end
TabSize = 4;
# Split a string into multiple lines,
# on line feeds and/or carriage returns.
#
# @param s [String]
# @return [String]
def split_lines(s) def split_lines(s)
s.gsub("\r","").split("\n") s.split(/\r\n|\r|\n/)
end end
# This parses email headers. Returns an hash. # Parses email headers, returning a hash.
# `hash[:data]` is the message;
# that is, anything past the headers.
# #
# +hash['data']+ is the message. # Keys are downcased and converted to symbols;
# # spaces become underscores. For example:
# Keys are downcased, space becomes underscore, converted to symbols.
# #
# !!!plain
# My key: true # My key: true
# #
# becomes: # becomes:
# #
# {:my_key => true} # {:my_key => true}
# #
# @param s [String] The email
# @return [Symbol => String] The header values
def parse_email_headers(s) def parse_email_headers(s)
keys={} headers = {}
match = (s =~ /\A((\w[\w \t\_\-]+: .*?\n)+)\s*?\n/) scanner = StringScanner.new(s)
if match != 0
keys[:data] = s while scanner.scan(/(\w[\w\s\-]+): +(.*)\n/)
else k, v = normalize_key_and_value(scanner[1], scanner[2])
keys[:data] = $' headers[k.to_sym] = v
headers = $1
headers.split("\n").each do |l|
# Fails if there are other ':' characters.
# k, v = l.split(':')
k, v = l.split(':', 2)
k, v = normalize_key_and_value(k, v)
k = k.to_sym
# puts "K = #{k}, V=#{v}"
keys[k] = v
end
end
keys
end end
# Keys are downcased, space becomes underscore, converted to symbols. headers[:data] = scanner.rest
def normalize_key_and_value(k,v) headers
v = v ? v.strip : true # no value defaults to true
k = k.strip
# check synonyms
v = true if ['yes','true'].include?(v.to_s.downcase)
v = false if ['no','false'].include?(v.to_s.downcase)
k = k.downcase.gsub(' ','_')
return k, v
end end
# Returns the number of leading spaces, considering that # Returns the number of leading spaces,
# a tab counts as `TabSize` spaces. # considering that a tab counts as {TAB_SIZE} spaces.
def number_of_leading_spaces(s)
s[/^[ \t]*/].gsub("\t", ' '*TabSize).length
end
# This returns the position of the first real char in a list item
# #
# For example: # @param s [String]
# '*Hello' # => 1 # @return [Fixnum]
# '* Hello' # => 2 def number_of_leading_spaces(s)
# ' * Hello' # => 3 spaces = s.scan(/^\s*/).first
# ' * Hello' # => 5 spaces.count(" ") + spaces.count("\t") * TAB_SIZE
# '1.Hello' # => 2 end
# ' 1. Hello' # => 5
# This returns the position of the first non-list character
# in a list item.
#
# @example
# spaces_before_first_char('*Hello') #=> 1
# spaces_before_first_char('* Hello') #=> 2
# spaces_before_first_char(' * Hello') #=> 3
# spaces_before_first_char(' * Hello') #=> 5
# spaces_before_first_char('1.Hello') #=> 2
# spaces_before_first_char(' 1. Hello') #=> 5
#
# @param s [String]
# @return [Fixnum]
def spaces_before_first_char(s) def spaces_before_first_char(s)
match =
case s.md_type case s.md_type
when :ulist when :ulist
# whitespace, followed by ('*'|'+'|'-') followed by # whitespace, followed by ('*'|'+'|'-') followed by
# more whitespace, followed by an optional IAL, followed # more whitespace, followed by an optional IAL, followed
# by yet more whitespace # by yet more whitespace
h=s[/^\s*(\*|\+|\-)\s*(\{.*?\})?\s*/] s[/^\s*(\*|\+|\-)\s*(\{.*?\})?\s*/]
when :olist when :olist
# whitespace, followed by a number, followed by a period, # whitespace, followed by a number, followed by a period,
# more whitespace, an optional IAL, and more whitespace # more whitespace, an optional IAL, and more whitespace
h=s[/^\s*\d+\.\s*(\{.*?\})?\s*/] s[/^\s*\d+\.\s*(\{.*?\})?\s*/]
else else
tell_user "BUG (my bad): '#{s}' is not a list" tell_user "BUG (my bad): '#{s}' is not a list"
h='' ''
end end
ial = h[/\{.*\}/] ial = match[/\{.*\}/]
return [h.length, ial] return [match.length, ial]
end end
# Counts the number of leading '#' in the string # Replace spaces with underscores and remove non-word characters.
def num_leading_hashes(s) #
h = s[/^#*/] # @param s [String]
h ? h.length : 0 # @return [String]
def sanitize_ref_id(s)
s.strip.downcase.gsub(' ', '_').gsub(/[^\w]/, '')
end end
# Strips initial and final hashes # Remove line-initial `>` characters for a quotation.
def strip_hashes(s) #
s.sub(/^#*(.*?)(#|\s)*$/, '\1').strip # @param s [String]
end # @return [String]
# change space to "_" and remove any non-word character
def sanitize_ref_id(x)
x.downcase.gsub(' ','_').gsub(/[^\w]/,'')
end
# removes initial quote
def unquote(s) def unquote(s)
s.gsub(/^>\s?/, '') s.gsub(/^>\s?/, '')
end end
# toglie al massimo n caratteri # Removes indentation from the beginning of `s`,
# up to at most `n` spaces.
# Tabs are counted as {TAB_SIZE} spaces.
#
# @param s [String]
# @param n [Fixnum]
# @return [String]
def strip_indent(s, n) def strip_indent(s, n)
i = 0 while n > 0
while i < s.size && n>0 case s[0]
c = s[i,1] when ?\s; n -= 1
if c == ' ' when ?\t; n -= TAB_SIZE
n-=1; else; return s
elsif c == "\t"
n-=TabSize;
else
break
end end
i+=1 s = s[1..-1]
end end
s[i, s.size] return s
end end
def dbg_describe_ary(a, prefix='') # Escapes a string so that it can be safely used in a Bourne shell command line.
i = 0 #
a.each do |l| # Note that a resulted string should be used unquoted
puts "#{prefix} (#{i+=1})# #{l.inspect}" # and is not intended for use in double quotes nor in single quotes.
end #
# This is a copy of the Shellwords.shellescape function in Ruby 1.8.7.
# It's included for Ruby 1.8.6 compatibility.
#
# @param str [String]
# @return [String]
def shellescape(str)
# An empty argument will be skipped, so return empty quotes.
return "''" if str.empty?
str = str.dup
# Process as a single byte sequence because not all shell
# implementations are multibyte aware.
str.gsub!(/([^A-Za-z0-9_\-.,:\/@\n])/n, "\\\\\\1")
# A LF cannot be escaped with a backslash because a backslash + LF
# combo is regarded as line continuation and simply ignored.
str.gsub!(/\n/, "'\n'")
return str
end end
def force_linebreak?(l) private
l =~ / $/
end
# Normalize the key/value pairs for email headers.
# Keys are downcased and converted to symbols;
# spaces become underscores.
#
# Values of `"yes"`, `"true"`, `"no"`, and `"false"`
# are converted to appropriate booleans.
#
# @param k [String]
# @param v [String]
# @return [Array(String, String or Boolean)]
def normalize_key_and_value(k, v)
k = k.strip.downcase.gsub(/\s+/, '_')
v = v.strip
# check synonyms
return k, true if %w[yes true].include?(v.downcase)
return k, false if %w[no false].include?(v.downcase)
return k, v
end
end end
end end

View file

@ -1,4 +1,3 @@
#--
# Copyright (C) 2006 Andrea Censi <andrea (at) rubyforge.org> # Copyright (C) 2006 Andrea Censi <andrea (at) rubyforge.org>
# #
# This file is part of Maruku. # This file is part of Maruku.
@ -16,143 +15,120 @@
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with Maruku; if not, write to the Free Software # along with Maruku; if not, write to the Free Software
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
#++
class Module
def safe_attr_accessor1(symbol, klass)
attr_reader symbol
code = <<-EOF
def #{symbol}=(val)
if not val.kind_of? #{klass}
s = "\nCould not assign an object of type \#{val.class} to #{symbol}.\n\n"
s += "Tried to assign object of class \#{val.class}:\n"+
"\#{val.inspect}\n"+
"to \#{self.class}::#{symbol} constrained to be of class #{klass}.\n"
raise s
end
@#{symbol} = val
end
EOF
module_eval code
end
def safe_attr_accessor2(symbol, klass)
attr_accessor symbol
end
alias safe_attr_accessor safe_attr_accessor2
end
module MaRuKu module MaRuKu
# Rather than having a separate class for every possible element,
# I did not want to have a class for each possible element. # Maruku has a single {MDElement} class
# Instead I opted to have only the class "MDElement"
# that represents eveything in the document (paragraphs, headers, etc). # that represents eveything in the document (paragraphs, headers, etc).
# The type of each element is available via \{#node\_type}.
class MDElement
# The type of this node (e.g. `:quote`, `:image`, `:abbr`).
# See {Helpers} for a list of possible values.
# #
# You can tell what it is by the variable `node_type`. # @return [Symbol]
attr_accessor :node_type
# The child nodes of this element.
# #
# In the instance-variable `children` there are the children. These # @return [Array<String or MDElement>]
# can be of class 1) String or 2) MDElement. attr_accessor :children
# An attribute list. May not be nil.
# #
# The @doc variable points to the document to which the MDElement # @return [AttributeList]
# belongs (which is an instance of Maruku, subclass of MDElement). attr_accessor :al
# The processed attributes.
# #
# Attributes are contained in the hash `attributes`. # For the {Maruku document root},
# Keys are symbols (downcased, with spaces substituted by underscores) # this contains properties listed
# # at the beginning of the document.
# For example, if you write in the source document. # The properties will be downcased and any spaces
# will be converted to underscores.
# For example, if you write in the source document:
# #
# !!!text
# Title: test document # Title: test document
# My property: value # My property: value
# #
# content content # content content
# #
# You can access `value` by writing: # Then \{#attributes} will return:
# #
# @doc.attributes[:my_property] # => 'value' # {:title => "test document", :my_property => "value"}
# #
# from whichever MDElement in the hierarchy. # @return [{Symbol => String}]
attr_accessor :attributes
# The root element of the document
# to which this element belongs.
# #
class MDElement # @return [Maruku]
# See helpers.rb for the list of allowed #node_type values
safe_attr_accessor :node_type, Symbol
# Children are either Strings or MDElement
safe_attr_accessor :children, Array
# An attribute list, may not be nil
safe_attr_accessor :al, Array #Maruku::AttributeList
# These are the processed attributes
safe_attr_accessor :attributes, Hash
# Reference of the document (which is of class Maruku)
attr_accessor :doc attr_accessor :doc
def initialize(node_type=:unset, children=[], meta={}, def initialize(node_type = :unset, children = [], meta = {}, al = nil)
al=MaRuKu::AttributeList.new )
super();
self.children = children self.children = children
self.node_type = node_type self.node_type = node_type
self.attributes = {}
@attributes = {}
meta.each do |symbol, value| meta.each do |symbol, value|
self.instance_eval " self.instance_eval <<RUBY
def #{symbol}; @#{symbol}; end def #{symbol}; @#{symbol}; end
def #{symbol}=(val); @#{symbol}=val; end" def #{symbol}=(val); @#{symbol} = val; end
RUBY
self.send "#{symbol}=", value self.send "#{symbol}=", value
end end
self.al = al || AttributeList.new self.al = al || AttributeList.new
self.meta_priv = meta self.meta_priv = meta
end end
# @private
attr_accessor :meta_priv attr_accessor :meta_priv
def ==(o) def ==(o)
ok = o.kind_of?(MDElement) && o.is_a?(MDElement) &&
(self.node_type == o.node_type) && self.node_type == o.node_type &&
(self.meta_priv == o.meta_priv) && self.meta_priv == o.meta_priv &&
(self.children == o.children) self.children == o.children
if not ok
# puts "This:\n"+self.inspect+"\nis different from\n"+o.inspect+"\n\n"
end
ok
end end
end end
# This represents the whole document and holds global data. # This represents the whole document and holds global data.
class MDDocument class MDDocument
# @return [{String => {:url => String, :title => String}}]
attr_accessor :refs
safe_attr_accessor :refs, Hash # @return [{String => MDElement}]
safe_attr_accessor :footnotes, Hash attr_accessor :footnotes
# This is an hash. The key might be nil. # @return [{String => String}]
safe_attr_accessor :abbreviations, Hash attr_accessor :abbreviations
# Attribute lists definition # Attribute definition lists.
safe_attr_accessor :ald, Hash #
# @return [{String => AttributeList}]
attr_accessor :ald
# The order in which footnotes are used. Contains the id. # The order in which footnotes are used. Contains the id.
safe_attr_accessor :footnotes_order, Array #
# @return [Array<String>]
attr_accessor :footnotes_order
safe_attr_accessor :latex_required_packages, Array # @return [Array<String>]
attr_accessor :latex_required_packages
safe_attr_accessor :refid2ref, Hash # @return [{String => {String => MDElement}}]
# A counter for generating unique IDs attr_accessor :refid2ref
safe_attr_accessor :id_counter, Integer
# A counter for generating unique IDs [Integer]
attr_accessor :id_counter
def initialize(s=nil) def initialize(s=nil)
super(:document) super(:document)
@doc = self
self.doc = self
self.refs = {} self.refs = {}
self.footnotes = {} self.footnotes = {}
self.footnotes_order = [] self.footnotes_order = []
@ -164,7 +140,4 @@ class MDDocument
parse_doc(s) if s parse_doc(s) if s
end end
end end
end
end # MaRuKu

View file

@ -1,4 +1,3 @@
#--
# Copyright (C) 2006 Andrea Censi <andrea (at) rubyforge.org> # Copyright (C) 2006 Andrea Censi <andrea (at) rubyforge.org>
# #
# This file is part of Maruku. # This file is part of Maruku.
@ -16,41 +15,8 @@
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with Maruku; if not, write to the Free Software # along with Maruku; if not, write to the Free Software
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
#++
class String
def inspect_more(a=nil,b=nil)
inspect
end
end
class Object
def inspect_more(a=nil,b=nil)
inspect
end
end
class Array
def inspect_more(compact, join_string, add_brackets=true)
s = map {|x|
x.kind_of?(String) ? x.inspect :
x.kind_of?(MaRuKu::MDElement) ? x.inspect(compact) :
(raise "WTF #{x.class} #{x.inspect}")
}.join(join_string)
add_brackets ? "[#{s}]" : s
end
end
class Hash
def inspect_ordered(a=nil,b=nil)
"{"+keys.map{|x|x.to_s}.sort.map{|x|x.to_sym}.
map{|k| k.inspect + "=>"+self[k].inspect}.join(',')+"}"
end
end
module MaRuKu module MaRuKu
class MDElement class MDElement
def inspect(compact=true) def inspect(compact=true)
@ -59,29 +25,27 @@ class MDElement
return i2 if i2 return i2 if i2
end end
"md_el(:%s,%s,%s,%s)" % # Make sure the attributes are lexically ordered
[ meta_ordered = "{" + @meta_priv.keys.
self.node_type, map {|x| x.to_s}.sort.map {|x| x.to_sym}.
map {|k| k.inspect + "=>" + @meta_priv[k].inspect}.
join(',') + "}"
"md_el(%s,%s,%s,%s)" % [
self.node_type.inspect,
children_inspect(compact), children_inspect(compact),
@meta_priv.inspect_ordered, meta_ordered,
self.al.inspect self.al.inspect
] ]
end end
def children_inspect(compact=true) def children_inspect(compact=true)
s = @children.inspect_more(compact,', ') kids = @children.map {|x| x.is_a?(MDElement) ? x.inspect(compact) : x.inspect}
if @children.empty? comma = kids.join(", ")
"[]"
elsif s.size < 70 return "[#{comma}]" if comma.size < 70
s "[\n\t#{kids.join(",\n\t")}\n]"
else
"[\n"+
add_tabs(@children.inspect_more(compact,",\n",false))+
"\n]"
end
end end
end end
end end

View file

@ -1,4 +1,3 @@
#--
# Copyright (C) 2006 Andrea Censi <andrea (at) rubyforge.org> # Copyright (C) 2006 Andrea Censi <andrea (at) rubyforge.org>
# #
# This file is part of Maruku. # This file is part of Maruku.
@ -16,46 +15,34 @@
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with Maruku; if not, write to the Free Software # along with Maruku; if not, write to the Free Software
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
#++
module MaRuKu module MaRuKu
class MDElement class MDElement
# Iterates through each {MDElement} child node of this element.
# Yields to each element of specified node_type # This includes deeply-nested child nodes.
# All elements if e_node_type is nil. # If `e_node_type` is specified, only yields nodes of that type.
def each_element(e_node_type=nil, &block) def each_element(e_node_type=nil, &block)
@children.each do |c| @children.each do |c|
if c.kind_of? MDElement next unless c.is_a? MDElement
if (not e_node_type) || (e_node_type == c.node_type) yield c if e_node_type.nil? || c.node_type == e_node_type
block.call c
end
c.each_element(e_node_type, &block) c.each_element(e_node_type, &block)
end end
end end
end
# Apply passed block to each String in the hierarchy. # Iterates through each String child node of this element,
# replacing it with the result of the block.
# This includes deeply-nested child nodes.
#
# This destructively modifies this node and its children.
#
# @todo Make this non-destructive
def replace_each_string(&block) def replace_each_string(&block)
for c in @children @children.map! do |c|
if c.kind_of? MDElement next yield c unless c.is_a?(MDElement)
c.replace_each_string(&block) c.replace_each_string(&block)
c
end.flatten!
end end
end end
processed = []
until @children.empty?
c = @children.shift
if c.kind_of? String
result = block.call(c)
[*result].each do |e| processed << e end
else
processed << c
end
end
@children = processed
end
end
end end

View file

@ -1,82 +0,0 @@
#--
# Copyright (C) 2006 Andrea Censi <andrea (at) rubyforge.org>
#
# This file is part of Maruku.
#
# 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
#++
require 'maruku'
#require 'bluecloth'
data = $stdin.read
num = 10
if ARGV.size > 0 && ((n=ARGV[0].to_i) != 0)
num = n
end
methods =
[
[Maruku, :to_html],
# [BlueCloth, :to_html],
[Maruku, :to_latex]
]
#methods = [[Maruku, :class]]
#num = 10
stats =
methods .map do |c, method|
puts "Computing for #{c}"
start = Time.now
doc = nil
for i in 1..num
$stdout.write "#{i} "; $stdout.flush
doc = c.new(data)
end
stop = Time.now
parsing = (stop-start)/num
start = Time.now
for i in 1..num
$stdout.write "#{i} "; $stdout.flush
s = doc.send method
end
stop = Time.now
rendering = (stop-start)/num
puts ("%s (%s): parsing %0.2f sec + rendering %0.2f sec "+
"= %0.2f sec ") % [c, method, parsing,rendering,parsing+rendering]
[c, method, parsing, rendering]
end
puts "\n\n\n"
stats.each do |x| x.push(x[2]+x[3]) end
max = stats.map{|x|x[4]}.max
stats.sort! { |x,y| x[4] <=> y[4] } . reverse!
for c, method, parsing, rendering, tot in stats
puts ("%20s: parsing %0.2f sec + rendering %0.2f sec "+
"= %0.2f sec (%0.2fx)") %
["#{c} (#{method})", parsing,rendering,tot,max/tot]
end

View file

@ -1,373 +0,0 @@
#--
# Copyright (C) 2006 Andrea Censi <andrea (at) rubyforge.org>
#
# This file is part of Maruku.
#
# 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
#++
require 'maruku'
require 'maruku/ext/math'
module MaRuKu; module Tests
# 5 accented letters in italian, encoded as UTF-8
AccIta8 = "\303\240\303\250\303\254\303\262\303\271"
# Same letters, written in ISO-8859-1 (one byte per letter)
AccIta1 = "\340\350\354\362\371"
# The word MA-RU-KU, written in katakana using UTF-8
Maruku8 = "\343\203\236\343\203\253\343\202\257"
def test_span_parser(verbose, break_on_first_error, quiet)
good_cases = [
["", [], 'Empty string gives empty list'],
["a", ["a"], 'Easy char'],
[" a", ["a"], 'First space in the paragraph is ignored'],
["a\n \n", ["a"], 'Last spaces in the paragraphs are ignored'],
[' ', [], 'One char => nothing'],
[' ', [], 'Two chars => nothing'],
['a b', ['a b'], 'Spaces are compressed'],
['a b', ['a b'], 'Newlines are spaces'],
["a\nb", ['a b'], 'Newlines are spaces'],
["a\n b", ['a b'], 'Compress newlines 1'],
["a \nb", ['a b'], 'Compress newlines 2'],
[" \nb", ['b'], 'Compress newlines 3'],
["\nb", ['b'], 'Compress newlines 4'],
["b\n", ['b'], 'Compress newlines 5'],
["\n", [], 'Compress newlines 6'],
["\n\n\n", [], 'Compress newlines 7'],
[nil, :throw, "Should throw on nil input"],
# Code blocks
["`" , :throw, 'Unclosed single ticks'],
["``" , :throw, 'Unclosed double ticks'],
["`a`" , [md_code('a')], 'Simple inline code'],
["`` ` ``" , [md_code('`')], ],
["`` \\` ``" , [md_code('\\`')], ],
["``a``" , [md_code('a')], ],
["`` a ``" , [md_code('a')], ],
# Newlines
["a \n", ['a',md_el(:linebreak)], 'Two spaces give br.'],
["a \n", ['a'], 'Newlines 2'],
[" \n", [md_el(:linebreak)], 'Newlines 3'],
[" \n \n", [md_el(:linebreak),md_el(:linebreak)],'Newlines 3'],
[" \na \n", [md_el(:linebreak),'a',md_el(:linebreak)],'Newlines 3'],
# Inline HTML
["a < b", ['a < b'], '< can be on itself'],
["<hr>", [md_html('<hr />')], 'HR will be sanitized'],
["<hr/>", [md_html('<hr />')], 'Closed tag is ok'],
["<hr />", [md_html('<hr />')], 'Closed tag is ok 2'],
["<hr/>a", [md_html('<hr />'),'a'], 'Closed tag is ok 2'],
["<em></em>a", [md_html('<em></em>'),'a'], 'Inline HTML 1'],
["<em>e</em>a", [md_html('<em>e</em>'),'a'], 'Inline HTML 2'],
["a<em>e</em>b", ['a',md_html('<em>e</em>'),'b'], 'Inline HTML 3'],
["<em>e</em>a<em>f</em>",
[md_html('<em>e</em>'),'a',md_html('<em>f</em>')],
'Inline HTML 4'],
["<em>e</em><em>f</em>a",
[md_html('<em>e</em>'),md_html('<em>f</em>'),'a'],
'Inline HTML 5'],
["<img src='a' />", [md_html("<img src='a' />")], 'Attributes'],
["<img src='a'/>"],
# emphasis
["**", :throw, 'Unclosed double **'],
["\\*", ['*'], 'Escaping of *'],
["a *b* ", ['a ', md_em('b')], 'Emphasis 1'],
["a *b*", ['a ', md_em('b')], 'Emphasis 2'],
["a * b", ['a * b'], 'Emphasis 3'],
["a * b*", :throw, 'Unclosed emphasis'],
# same with underscore
["__", :throw, 'Unclosed double __'],
["\\_", ['_'], 'Escaping of _'],
["a _b_ ", ['a ', md_em('b')], 'Emphasis 4'],
["a _b_", ['a ', md_em('b')], 'Emphasis 5'],
["a _ b", ['a _ b'], 'Emphasis 6'],
["a _ b_", :throw, 'Unclosed emphasis'],
["_b_", [md_em('b')], 'Emphasis 7'],
["_b_ _c_", [md_em('b'),' ',md_em('c')], 'Emphasis 8'],
["_b__c_", [md_em('b'),md_em('c')], 'Emphasis 9'],
# underscores in word
["mod_ruby", ['mod_ruby'], 'Word with underscore'],
# strong
["**a*", :throw, 'Unclosed double ** 2'],
["\\**a*", ['*', md_em('a')], 'Escaping of *'],
["a **b** ", ['a ', md_strong('b')], 'Emphasis 1'],
["a **b**", ['a ', md_strong('b')], 'Emphasis 2'],
["a ** b", ['a ** b'], 'Emphasis 3'],
["a ** b**", :throw, 'Unclosed emphasis'],
["**b****c**", [md_strong('b'),md_strong('c')], 'Emphasis 9'],
# strong (with underscore)
["__a_", :throw, 'Unclosed double __ 2'],
# ["\\__a_", ['_', md_em('a')], 'Escaping of _'],
["a __b__ ", ['a ', md_strong('b')], 'Emphasis 1'],
["a __b__", ['a ', md_strong('b')], 'Emphasis 2'],
["a __ b", ['a __ b'], 'Emphasis 3'],
["a __ b__", :throw, 'Unclosed emphasis'],
["__b____c__", [md_strong('b'),md_strong('c')], 'Emphasis 9'],
# extra strong
["***a**", :throw, 'Unclosed triple *** '],
["\\***a**", ['*', md_strong('a')], 'Escaping of *'],
["a ***b*** ", ['a ', md_emstrong('b')], 'Strong elements'],
["a ***b***", ['a ', md_emstrong('b')]],
["a *** b", ['a *** b']],
["a ** * b", ['a ** * b']],
["***b******c***", [md_emstrong('b'),md_emstrong('c')]],
["a *** b***", :throw, 'Unclosed emphasis'],
# same with underscores
["___a__", :throw, 'Unclosed triple *** '],
# ["\\___a__", ['_', md_strong('a')], 'Escaping of _'],
["a ___b___ ", ['a ', md_emstrong('b')], 'Strong elements'],
["a ___b___", ['a ', md_emstrong('b')]],
["a ___ b", ['a ___ b']],
["a __ _ b", ['a __ _ b']],
["___b______c___", [md_emstrong('b'),md_emstrong('c')]],
["a ___ b___", :throw, 'Unclosed emphasis'],
# mixing is bad
["*a_", :throw, 'Mixing is bad'],
["_a*", :throw],
["**a__", :throw],
["__a**", :throw],
["___a***", :throw],
["***a___", :throw],
# links of the form [text][ref]
["\\[a]", ["[a]"], 'Escaping 1'],
["\\[a\\]", ["[a]"], 'Escaping 2'],
# This is valid in the new Markdown version
# ["[a]", ["a"], 'Not a link'],
["[a]", [ md_link(["a"],'a')], 'Empty link'],
["[a][]", ],
["[a][]b", [ md_link(["a"],'a'),'b'], 'Empty link'],
["[a\\]][]", [ md_link(["a]"],'a')], 'Escape inside link (throw ?] away)'],
["[a", :throw, 'Link not closed'],
["[a][", :throw, 'Ref not closed'],
# links of the form [text](url)
["\\[a](b)", ["[a](b)"], 'Links'],
["[a](url)c", [md_im_link(['a'],'url'),'c'], 'url'],
["[a]( url )c" ],
["[a] ( url )c" ],
["[a] ( url)c" ],
["[a](ur:/l/ 'Title')", [md_im_link(['a'],'ur:/l/','Title')],
'url and title'],
["[a] ( ur:/l/ \"Title\")" ],
["[a] ( ur:/l/ \"Title\")" ],
["[a]( ur:/l/ Title)", :throw, "Must quote title" ],
["[a](url 'Tit\\\"l\\\\e')", [md_im_link(['a'],'url','Tit"l\\e')],
'url and title escaped'],
["[a] ( url \"Tit\\\"l\\\\e\")" ],
["[a] ( url \"Tit\\\"l\\\\e\" )" ],
['[a] ( url "Tit\\"l\\\\e" )' ],
["[a]()", [md_im_link(['a'],'')], 'No URL is OK'],
["[a](\"Title\")", :throw, "No url specified" ],
["[a](url \"Title)", :throw, "Unclosed quotes" ],
["[a](url \"Title\\\")", :throw],
["[a](url \"Title\" ", :throw],
["[a](url \'Title\")", :throw, "Mixing is bad" ],
["[a](url \"Title\')"],
["[a](/url)", [md_im_link(['a'],'/url')], 'Funny chars in url'],
["[a](#url)", [md_im_link(['a'],'#url')]],
["[a](</script?foo=1&bar=2>)", [md_im_link(['a'],'/script?foo=1&bar=2')]],
# Images
["\\![a](url)", ['!', md_im_link(['a'],'url') ], 'Escaping images'],
["![a](url)", [md_im_image(['a'],'url')], 'Image no title'],
["![a]( url )" ],
["![a] ( url )" ],
["![a] ( url)" ],
["![a](url 'ti\"tle')", [md_im_image(['a'],'url','ti"tle')], 'Image with title'],
['![a]( url "ti\\"tle")' ],
["![a](url", :throw, 'Invalid images'],
["![a( url )" ],
["![a] ('url )" ],
["![a][imref]", [md_image(['a'],'imref')], 'Image with ref'],
["![a][ imref]"],
["![a][ imref ]"],
["![a][\timref\t]"],
['<http://example.com/?foo=1&bar=2>',
[md_url('http://example.com/?foo=1&bar=2')], 'Immediate link'],
['a<http://example.com/?foo=1&bar=2>b',
['a',md_url('http://example.com/?foo=1&bar=2'),'b'] ],
['<andrea@censi.org>',
[md_email('andrea@censi.org')], 'Email address'],
['<mailto:andrea@censi.org>'],
["Developmen <http://rubyforge.org/projects/maruku/>",
["Developmen ", md_url("http://rubyforge.org/projects/maruku/")]],
["a<!-- -->b", ['a',md_html('<!-- -->'),'b'],
'HTML Comment'],
["a<!--", :throw, 'Bad HTML Comment'],
["a<!-- ", :throw, 'Bad HTML Comment'],
["<?xml <?!--!`3 ?>", [md_xml_instr('xml','<?!--!`3')], 'XML processing instruction'],
["<? <?!--!`3 ?>", [md_xml_instr('','<?!--!`3')] ],
["<? ", :throw, 'Bad Server directive'],
["a <b", :throw, 'Bad HTML 1'],
["<b", :throw, 'Bad HTML 2'],
["<b!", :throw, 'Bad HTML 3'],
['`<div>`, `<table>`, `<pre>`, `<p>`',
[md_code('<div>'),', ',md_code('<table>'),', ',
md_code('<pre>'),', ',md_code('<p>')],
'Multiple HTLM tags'],
["&andrea", ["&andrea"], 'Parsing of entities'],
# no escaping is allowed
# ["\\&andrea;", ["&andrea;"]],
["l&andrea;", ["l", md_entity('andrea')] ],
["&&andrea;", ["&", md_entity('andrea')] ],
["&123;;&amp;",[md_entity('123'),';',md_entity('amp')]],
["a\nThe [syntax page] [s] provides",
['a The ', md_link(['syntax page'],'s'), ' provides'], 'Regression'],
['![a](url "ti"tle")', [md_im_image(['a'],'url','ti"tle')],
"Image with quotes"],
['![a](url \'ti"tle\')' ],
['[bar](/url/ "Title with "quotes" inside")',
[md_im_link(["bar"],'/url/', 'Title with "quotes" inside')],
"Link with quotes"],
# We dropped this idea
# ['$20,000 and $30,000', ['$20,000 and $30,000'], 'Math: spaces'],
['$20,000$', [md_inline_math('20,000')]],
# ['$ 20,000$', ['$ 20,000$']],
# ['$20,000 $ $20,000$', ['$20,000 $ ', md_inline_math('20,000')]],
["#{Maruku8}", [Maruku8], "Reading UTF-8"],
# ["#{AccIta1}", [AccIta8], "Converting ISO-8859-1 to UTF-8",
# {:encoding => 'iso-8859-1'}],
]
good_cases = unit_tests_for_attribute_lists + good_cases
count = 1; last_comment=""; last_expected=:throw
good_cases.each do |t|
if not t[1]
t[1] = last_expected
else
last_expected = t[1]
end
if not t[2]
t[2] = last_comment + " #{count+=1}"
else
last_comment = t[2]; count=1
end
end
@verbose = verbose
m = Maruku.new
m.attributes[:on_error] = :raise
Globals[:debug_keep_ials] = true
num_ok = 0
good_cases.each do |input, expected, comment|
output = nil
begin
output = m.parse_span_better(input)
#lines = Maruku.split_lines input
#output = m.parse_lines_as_span(lines)
rescue Exception => e
if not expected == :throw
ex = e.inspect+ "\n"+ e.backtrace.join("\n")
s = comment+describe_difference(input, expected, output)
print_status(comment,'CRASHED :-(', ex+s)
raise e if @break_on_first_error
else
quiet || print_status(comment,'OK')
num_ok += 1
end
end
if not expected == :throw
if not (expected == output)
s = comment+describe_difference(input, expected, output)
print_status(comment, 'FAILED', s)
break if break_on_first_error
else
num_ok += 1
quiet || print_status(comment, 'OK')
end
else # I expected a raise
if output
s = comment+describe_difference(input, expected, output)
print_status(comment, 'FAILED (no throw)', s)
break if break_on_first_error
end
end
end # do
if num_ok != good_cases.size
return false
else
return true
end
end
PAD=40
def print_status(comment, status, verbose_text=nil)
if comment.size < PAD
comment = comment + (" "*(PAD-comment.size))
end
puts "- #{comment} #{status}"
if @verbose and verbose_text
puts verbose_text
end
end
def describe_difference(input, expected, output)
"\nInput:\n #{input.inspect}" +
"\nExpected:\n #{expected.inspect}" +
"\nOutput:\n #{output.inspect}\n"
end
end end
class Maruku
include MaRuKu::Tests
end
verbose = ARGV.include? 'v'
break_on_first = ARGV.include? 'b'
quiet = ARGV.include? 'q'
ok = Maruku.new.test_span_parser(verbose, break_on_first, quiet)
exit (ok ? 0 : 1)

View file

@ -1,136 +0,0 @@
#--
# Copyright (C) 2006 Andrea Censi <andrea (at) rubyforge.org>
#
# This file is part of Maruku.
#
# 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
#++
require 'maruku'
class Maruku
def Maruku.failed(test, doc, s)
raise "Test failed: #{s}\n*****\n#{test}\n*****\n"+
"#{doc.inspect}\n*****\n{doc.to_html}"
end
def Maruku.metaTests
ref = {:id => 'id1', :class => ['class1','class2'],
:style=> 'Style is : important = for all } things'}
tests = MetaTests.split('***')
for test in tests
#puts "Test: #{test.inspect}"
doc = Maruku.new(test)
doc.children.size == 1 ||
failed(test, doc, "children != 1")
h = doc.children[0]
h.node_type==:header ||
failed(test, doc, "child not header")
# puts doc.inspect
# puts doc.to_html
end
end
MetaTests = <<EOF
# Head # {ref1 ref2 ref3}
{ref1}: id: id1; class: class1
{ref2}: class: class2
{ref3}: style: "Style is : important = for all } things"
***
# Head # {ref1 ref3 ref2}
{ref1}: id: id1; class: class1
{ref2}: class: class2
{ref3}: style: "Style is : important = for all } things"
***
# Head # {ref1 ref2 ref3}
{ref1}: id= id1; class=class1
{ref2}: class=class2
{ref3}: style="Style is : important = for all } things"
***
# Head # {ref1 ref2 ref3}
{ref1}: id=id1 class=class1
{ref2}: class=class2
{ref3}: style="Style is : important = for all } things"
***
# Head # {ref1 ref2 ref3}
{ref1}: id:id1 class:class1
{ref2}: class : class2
{ref3}: style = "Style is : important = for all } things"
***
# Head # {ref1 ref2 ref3}
{ref1}: id:id1 class:class1
{ref2}: class : class2
{ref3}: style = "Style is : important = for all } things"
***
# Head # {#id1 .class1 ref2 ref3}
{ref2}: class : class2
{ref3}: style = "Style is : important = for all } things"
***
# Head # { #id1 .class1 ref2 ref3 }
{ref2}: class : class2
{ref3}: style = "Style is : important = for all } things"
***
# Head # { id=id1 class=class1 ref2 ref3 }
{ref2}: class : class2
{ref3}: style = "Style is : important = for all } things"
***
# Head # { id:id1 class="class1" class:"class2" style="Style is : important = for all } things"}
EOF
end
if File.basename($0) == 'tests.rb'
Maruku.metaTests
end

View file

@ -1,4 +1,3 @@
#--
# Copyright (C) 2006 Andrea Censi <andrea (at) rubyforge.org> # Copyright (C) 2006 Andrea Censi <andrea (at) rubyforge.org>
# #
# This file is part of Maruku. # This file is part of Maruku.
@ -16,78 +15,100 @@
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with Maruku; if not, write to the Free Software # along with Maruku; if not, write to the Free Software
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
#++
module MaRuKu module MaRuKu
# A section in the table of contents of a document.
class MDDocument
# an instance of Section (see below)
attr_accessor :toc
end
# This represents a section in the TOC.
class Section class Section
# a Fixnum, is == header_element.level # The depth of the section (0 for toplevel).
#
# Equivalent to `header_element.level`.
#
# @return [Fixnum]
attr_accessor :section_level attr_accessor :section_level
# An array of fixnum, like [1,2,5] for Section 1.2.5 # The nested section number, e.g. `[1, 2, 5]` for Section 1.2.5.
#
# @return [Array<Fixnum>]
attr_accessor :section_number attr_accessor :section_number
# reference to header (header has h.meta[:section] to self) # The `:header` node for this section.
# The value of `meta[:section]` for the header will be this node.
#
# @return [MDElement]
attr_accessor :header_element attr_accessor :header_element
# Array of immediate children of this element # The immediate child nodes of this section.
#
# @todo Why does this never contain Strings?
#
# @return [Array<MDElement>]
attr_accessor :immediate_children attr_accessor :immediate_children
# Array of Section inside this section # The subsections of this section.
#
# @return [Array<Section>]
attr_accessor :section_children attr_accessor :section_children
def initialize def initialize
@immediate_children = [] @immediate_children = []
@section_children = [] @section_children = []
end end
end
class Section
def inspect(indent = 1) def inspect(indent = 1)
s = "" s = ""
if @header_element if @header_element
s += "\_"*indent + "(#{@section_level})>\t #{@section_number.join('.')} : " s << "\_" * indent <<
s += @header_element.children_to_s + "(#{@section_level})>\t #{@section_number.join('.')} : " <<
@header_element.children_to_s <<
" (id: '#{@header_element.attributes[:id]}')\n" " (id: '#{@header_element.attributes[:id]}')\n"
else else
s += "Master\n" s << "Master\n"
end end
@section_children.each {|c| s << c.inspect(indent+1)}
@section_children.each do |c|
s+=c.inspect(indent+1)
end
s s
end end
# Numerate this section and its children # Assign \{#section\_number section numbers}
# to this section and its children.
# This also assigns the section number attribute
# to the sections' headers.
#
# This should only be called on the root section.
#
# @overload def numerate
def numerate(a = []) def numerate(a = [])
self.section_number = a self.section_number = a
section_children.each_with_index do |c,i| section_children.each_with_index {|c, i| c.numerate(a + [i + 1])}
c.numerate(a.clone.push(i+1))
end
if h = self.header_element if h = self.header_element
h.attributes[:section_number] = self.section_number h.attributes[:section_number] = self.section_number
end end
end end
include REXML include REXML
# Creates an HTML toc.
# Call this on the root # Returns an HTML representation of the table of contents.
#
# This should only be called on the root section.
def to_html def to_html
div = Element.new 'div' div = Element.new 'div'
div.attributes['class'] = 'maruku_toc' div.attributes['class'] = 'maruku_toc'
div << create_toc div << _to_html
div div
end end
def create_toc # Returns a LaTeX representation of the table of contents.
#
# This should only be called on the root section.
def to_latex
_to_latex + "\n\n"
end
protected
def _to_html
ul = Element.new 'ul' ul = Element.new 'ul'
# let's remove the bullets # let's remove the bullets
ul.attributes['style'] = 'list-style: none;' ul.attributes['style'] = 'list-style: none;'
@ -96,56 +117,54 @@ end
if span = c.header_element.render_section_number if span = c.header_element.render_section_number
li << span li << span
end end
a = c.header_element.wrap_as_element('a') a = c.header_element.wrap_as_element('a')
a.delete_attribute 'id' a.delete_attribute 'id'
a.attributes['href'] = "##{c.header_element.attributes[:id]}" a.attributes['href'] = "##{c.header_element.attributes[:id]}"
li << a li << a
li << c.create_toc if c.section_children.size>0 li << c._to_html if c.section_children.size > 0
ul << li ul << li
end end
ul ul
end end
# Creates a latex toc. def _to_latex
# Call this on the root
def to_latex
to_latex_rec + "\n\n"
end
def to_latex_rec
s = "" s = ""
@section_children.each do |c| @section_children.each do |c|
s += "\\noindent" s << "\\noindent"
number = c.header_element.section_number if number = c.header_element.section_number
s += number if number s << number
text = c.header_element.children_to_latex end
id = c.header_element.attributes[:id] id = c.header_element.attributes[:id]
s += "\\hyperlink{#{id}}{#{text}}" text = c.header_element.children_to_latex
s += "\\dotfill \\pageref*{#{id}} \\linebreak\n" s << "\\hyperlink{#{id}}{#{text}}"
s += c.to_latex_rec if c.section_children.size>0 s << "\\dotfill \\pageref*{#{id}} \\linebreak\n"
s << c._to_latex if c.section_children.size > 0
end end
s s
end end
end end
class MDDocument class MDDocument
# The table of contents for the document.
#
# @return [Section]
attr_accessor :toc
def create_toc def create_toc
each_element(:header) do |h| each_element(:header) {|h| h.attributes[:id] ||= h.generate_id}
h.attributes[:id] ||= h.generate_id
end
stack = [] stack = []
# the ancestor section # The root section
s = Section.new s = Section.new
s.section_level = 0 s.section_level = 0
stack.push s stack.push s
i = 0; # TODO: Clean up the logic here once we have better tests
i = 0
while i < @children.size while i < @children.size
while i < @children.size while i < @children.size
if @children[i].node_type == :header if @children[i].node_type == :header
@ -181,14 +200,10 @@ end
# this level is a parent # this level is a parent
stack.pop stack.pop
end end
end end
# If there is only one big header, then assume # If there is only one big header, then assume it is the master
# it is the master s = s.section_children.first if s.section_children.size == 1
if s.section_children.size == 1
s = s.section_children.first
end
# Assign section numbers # Assign section numbers
s.numerate s.numerate

View file

@ -1,4 +1,3 @@
#--
# Copyright (C) 2006 Andrea Censi <andrea (at) rubyforge.org> # Copyright (C) 2006 Andrea Censi <andrea (at) rubyforge.org>
# #
# This file is part of Maruku. # This file is part of Maruku.
@ -16,25 +15,40 @@
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with Maruku; if not, write to the Free Software # along with Maruku; if not, write to the Free Software
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
#++
module MaRuKu module MaRuKu
Version = '0.6.0' # The Maruku version.
VERSION = '0.6.0'
MarukuURL = 'http://maruku.rubyforge.org/' # @deprecated Exists for backwards compatibility. Use {VERSION}
# @private
Version = VERSION
# If true, use also PHP Markdown extra syntax # The URL of the Maruku website.
MARUKU_URL = 'http://maruku.rubyforge.org/'
# @deprecated Exists for backwards compatibility. Use {MARUKU_URL}
# @private
MarukuURL = MARUKU_URL
# Whether Markdown implements the PHP Markdown extra syntax.
# #
# Note: it is not guaranteed that if it's false # Note: it is not guaranteed that if this is false,
# then no special features will be used. # then no special features will be used.
# #
# So please, ignore it for now. # @return [Boolean]
def markdown_extra? def markdown_extra?
true true
end end
# Whether Markdown implements the new meta-data proposal.
#
# Note: it is not guaranteed that if this is false,
# then no special features will be used.
#
# @return [Boolean]
def new_meta_data? def new_meta_data?
true true
end end
end end