int base_indent; // controlled by the :indent option to Wikitext::Parser#parse
int current_indent; // fluctuates according to currently nested structures
str_t *tabulation; // caching buffer for emitting indentation
+ int base_heading_level;
} parser_t;
const char escaped_no_wiki_start[] = "<nowiki>";
return;
if (NIL_P(target))
target = parser->output;
+
+ // for headings, take base_heading_level into account
+ if (top >= H1_START && top <= H6_START)
+ {
+ top += parser->base_heading_level;
+ // no need to check for underflow (base_heading_level is never negative)
+ if (top > H6_START)
+ top = H6_START;
+ }
+
switch (top)
{
case PRE:
VALUE img_prefix = rb_str_new2("/images/");
VALUE space_to_underscore = Qtrue;
VALUE minimum_fulltext_token_length = INT2NUM(3);
+ VALUE base_heading_level = INT2NUM(0);
// process options hash (override defaults)
if (!NIL_P(options) && TYPE(options) == T_HASH)
img_prefix = OVERRIDE_IF_SET(img_prefix);
space_to_underscore = OVERRIDE_IF_SET(space_to_underscore);
minimum_fulltext_token_length = OVERRIDE_IF_SET(minimum_fulltext_token_length);
+ base_heading_level = OVERRIDE_IF_SET(base_heading_level);
}
// no need to call super here; rb_call_super()
rb_iv_set(self, "@img_prefix", img_prefix);
rb_iv_set(self, "@space_to_underscore", space_to_underscore);
rb_iv_set(self, "@minimum_fulltext_token_length", minimum_fulltext_token_length);
+ rb_iv_set(self, "@base_heading_level", base_heading_level);
return self;
}
// process options hash
int base_indent = 0;
- VALUE indent = Qnil;
+ int base_heading_level = NUM2INT(rb_iv_get(self, "@base_heading_level"));
if (!NIL_P(options) && TYPE(options) == T_HASH)
{
+ // :indent => 0 (or more)
if (rb_funcall(options, rb_intern("has_key?"), 1, ID2SYM(rb_intern("indent"))) == Qtrue)
{
- indent = rb_hash_aref(options, ID2SYM(rb_intern("indent")));
- base_indent = NUM2INT(indent);
+ base_indent = NUM2INT(rb_hash_aref(options, ID2SYM(rb_intern("indent"))));
if (base_indent < 0)
base_indent = 0;
}
+
+ // :base_heading_level => 0/1/2/3/4/5/6
+ if (rb_funcall(options, rb_intern("has_key?"), 1, ID2SYM(rb_intern("base_heading_level"))) == Qtrue)
+ base_heading_level = NUM2INT(rb_hash_aref(options, ID2SYM(rb_intern("base_heading_level"))));
}
+ // normalize, regardless of whether this came from instance variable or override
+ if (base_heading_level < 0)
+ base_heading_level = 0;
+ if (base_heading_level > 6)
+ base_heading_level = 6;
+
// set up scanner
char *p = RSTRING_PTR(string);
long len = RSTRING_LEN(string);
parser->current_indent = 0;
parser->tabulation = str_new();
GC_WRAP_STR(parser->tabulation, tabulation_gc);
+ parser->base_heading_level = base_heading_level;
// this simple looping design leads to a single enormous function,
// but it's faster than doing actual recursive descent and also secure in the face of
ary_push(parser->scope, type);
_Wikitext_indent(parser);
+ // take base_heading_level into account
+ type += base_heading_level;
+ if (type > H6_START) // no need to check for underflow (base_heading_level never negative)
+ type = H6_START;
+
// rather than repeat all that code for each kind of heading, share it and use a conditional here
if (type == H6_START)
rb_str_cat(parser->output, h6_start, sizeof(h6_start) - 1);
--- /dev/null
+#!/usr/bin/env ruby
+# Copyright 2009 Wincent Colaiuta
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+require File.join(File.dirname(__FILE__), 'spec_helper.rb')
+require 'wikitext/parser'
+
+describe Wikitext::Parser, 'base_heading_level' do
+ before do
+ @parser = Wikitext::Parser.new
+ end
+
+ it 'should default to 0' do
+ @parser.base_heading_level.should == 0
+ end
+
+ it 'should be settable after initialization' do
+ @parser.base_heading_level = 4
+ @parser.base_heading_level.should == 4
+ end
+
+ it 'should be overrideable at initialization time' do
+ parser = Wikitext::Parser.new :base_heading_level => 3
+ parser.base_heading_level.should == 3
+ end
+
+ it 'should be overrideable at parse time' do
+ lambda { @parser.parse 'foo', :base_heading_level => 2 }.should_not raise_error
+ end
+
+ it 'should complain if overridden with non-integer value' do
+ lambda { @parser.parse 'foo', :base_heading_level => 'foo' }.should raise_error(TypeError)
+ lambda {
+ @parser.base_heading_level = 'bar'
+ @parser.parse 'foo' # error actually gets raised only at parse time
+ }.should raise_error(TypeError)
+ lambda {
+ parser = Wikitext::Parser.new :base_heading_level => 'baz'
+ parser.parse 'foo' # error actually gets raised only at parse time
+ }.should raise_error(TypeError)
+ end
+
+ # these tested additionally below
+ it 'should handle negative levels' do
+ @parser.base_heading_level = -1
+ @parser.parse('== hello ==').should == "<h2>hello</h2>\n"
+ @parser.base_heading_level = -2
+ @parser.parse('== hello ==').should == "<h2>hello</h2>\n"
+ @parser.base_heading_level = -3
+ @parser.parse('== hello ==').should == "<h2>hello</h2>\n"
+ @parser.base_heading_level = -4
+ @parser.parse('== hello ==').should == "<h2>hello</h2>\n"
+ @parser.base_heading_level = -5
+ @parser.parse('== hello ==').should == "<h2>hello</h2>\n"
+ @parser.base_heading_level = -6
+ @parser.parse('== hello ==').should == "<h2>hello</h2>\n"
+ end
+
+ # these tested additionally below
+ it 'should handle oversized levels (greater than six)' do
+ @parser.base_heading_level = 7
+ @parser.parse('= hello =').should == "<h6>hello</h6>\n"
+ @parser.base_heading_level = 8
+ @parser.parse('= hello =').should == "<h6>hello</h6>\n"
+ @parser.base_heading_level = 9
+ @parser.parse('= hello =').should == "<h6>hello</h6>\n"
+ @parser.base_heading_level = 10
+ @parser.parse('= hello =').should == "<h6>hello</h6>\n"
+ @parser.base_heading_level = 11
+ @parser.parse('= hello =').should == "<h6>hello</h6>\n"
+ @parser.base_heading_level = 12
+ @parser.parse('= hello =').should == "<h6>hello</h6>\n"
+ end
+
+ # here we use a negative value
+ it 'should treat the three different override methods as equivalent (level: -1)' do
+ @parser.base_heading_level = -1
+ @parser.parse('= hello =').should == "<h1>hello</h1>\n"
+ @parser.parse('== hello ==').should == "<h2>hello</h2>\n"
+ @parser.parse('=== hello ===').should == "<h3>hello</h3>\n"
+ @parser.parse('==== hello ====').should == "<h4>hello</h4>\n"
+ @parser.parse('===== hello =====').should == "<h5>hello</h5>\n"
+ @parser.parse('====== hello ======').should == "<h6>hello</h6>\n"
+
+ @parser.parse('= hello =', :base_heading_level => -1).should == "<h1>hello</h1>\n"
+ @parser.parse('== hello ==', :base_heading_level => -1).should == "<h2>hello</h2>\n"
+ @parser.parse('=== hello ===', :base_heading_level => -1).should == "<h3>hello</h3>\n"
+ @parser.parse('==== hello ====', :base_heading_level => -1).should == "<h4>hello</h4>\n"
+ @parser.parse('===== hello =====', :base_heading_level => -1).should == "<h5>hello</h5>\n"
+ @parser.parse('====== hello ======', :base_heading_level => -1).should == "<h6>hello</h6>\n"
+
+ parser = Wikitext::Parser.new :base_heading_level => -1
+ parser.parse('= hello =').should == "<h1>hello</h1>\n"
+ parser.parse('== hello ==').should == "<h2>hello</h2>\n"
+ parser.parse('=== hello ===').should == "<h3>hello</h3>\n"
+ parser.parse('==== hello ====').should == "<h4>hello</h4>\n"
+ parser.parse('===== hello =====').should == "<h5>hello</h5>\n"
+ parser.parse('====== hello ======').should == "<h6>hello</h6>\n"
+ end
+
+ it 'should treat the three different override methods as equivalent (level: 0)' do
+ @parser.base_heading_level = 0
+ @parser.parse('= hello =').should == "<h1>hello</h1>\n"
+ @parser.parse('== hello ==').should == "<h2>hello</h2>\n"
+ @parser.parse('=== hello ===').should == "<h3>hello</h3>\n"
+ @parser.parse('==== hello ====').should == "<h4>hello</h4>\n"
+ @parser.parse('===== hello =====').should == "<h5>hello</h5>\n"
+ @parser.parse('====== hello ======').should == "<h6>hello</h6>\n"
+
+ @parser.parse('= hello =', :base_heading_level => 0).should == "<h1>hello</h1>\n"
+ @parser.parse('== hello ==', :base_heading_level => 0).should == "<h2>hello</h2>\n"
+ @parser.parse('=== hello ===', :base_heading_level => 0).should == "<h3>hello</h3>\n"
+ @parser.parse('==== hello ====', :base_heading_level => 0).should == "<h4>hello</h4>\n"
+ @parser.parse('===== hello =====', :base_heading_level => 0).should == "<h5>hello</h5>\n"
+ @parser.parse('====== hello ======', :base_heading_level => 0).should == "<h6>hello</h6>\n"
+
+ parser = Wikitext::Parser.new :base_heading_level => 0
+ parser.parse('= hello =').should == "<h1>hello</h1>\n"
+ parser.parse('== hello ==').should == "<h2>hello</h2>\n"
+ parser.parse('=== hello ===').should == "<h3>hello</h3>\n"
+ parser.parse('==== hello ====').should == "<h4>hello</h4>\n"
+ parser.parse('===== hello =====').should == "<h5>hello</h5>\n"
+ parser.parse('====== hello ======').should == "<h6>hello</h6>\n"
+ end
+
+ it 'should treat the three different override methods as equivalent (level: 1)' do
+ @parser.base_heading_level = 1
+ @parser.parse('= hello =').should == "<h2>hello</h2>\n"
+ @parser.parse('== hello ==').should == "<h3>hello</h3>\n"
+ @parser.parse('=== hello ===').should == "<h4>hello</h4>\n"
+ @parser.parse('==== hello ====').should == "<h5>hello</h5>\n"
+ @parser.parse('===== hello =====').should == "<h6>hello</h6>\n"
+ @parser.parse('====== hello ======').should == "<h6>hello</h6>\n"
+
+ @parser.parse('= hello =', :base_heading_level => 1).should == "<h2>hello</h2>\n"
+ @parser.parse('== hello ==', :base_heading_level => 1).should == "<h3>hello</h3>\n"
+ @parser.parse('=== hello ===', :base_heading_level => 1).should == "<h4>hello</h4>\n"
+ @parser.parse('==== hello ====', :base_heading_level => 1).should == "<h5>hello</h5>\n"
+ @parser.parse('===== hello =====', :base_heading_level => 1).should == "<h6>hello</h6>\n"
+ @parser.parse('====== hello ======', :base_heading_level => 1).should == "<h6>hello</h6>\n"
+
+ parser = Wikitext::Parser.new :base_heading_level => 1
+ parser.parse('= hello =').should == "<h2>hello</h2>\n"
+ parser.parse('== hello ==').should == "<h3>hello</h3>\n"
+ parser.parse('=== hello ===').should == "<h4>hello</h4>\n"
+ parser.parse('==== hello ====').should == "<h5>hello</h5>\n"
+ parser.parse('===== hello =====').should == "<h6>hello</h6>\n"
+ parser.parse('====== hello ======').should == "<h6>hello</h6>\n"
+ end
+
+ it 'should treat the three different override methods as equivalent (level: 2)' do
+ @parser.base_heading_level = 2
+ @parser.parse('= hello =').should == "<h3>hello</h3>\n"
+ @parser.parse('== hello ==').should == "<h4>hello</h4>\n"
+ @parser.parse('=== hello ===').should == "<h5>hello</h5>\n"
+ @parser.parse('==== hello ====').should == "<h6>hello</h6>\n"
+ @parser.parse('===== hello =====').should == "<h6>hello</h6>\n"
+ @parser.parse('====== hello ======').should == "<h6>hello</h6>\n"
+
+ @parser.parse('= hello =', :base_heading_level => 2).should == "<h3>hello</h3>\n"
+ @parser.parse('== hello ==', :base_heading_level => 2).should == "<h4>hello</h4>\n"
+ @parser.parse('=== hello ===', :base_heading_level => 2).should == "<h5>hello</h5>\n"
+ @parser.parse('==== hello ====', :base_heading_level => 2).should == "<h6>hello</h6>\n"
+ @parser.parse('===== hello =====', :base_heading_level => 2).should == "<h6>hello</h6>\n"
+ @parser.parse('====== hello ======', :base_heading_level => 2).should == "<h6>hello</h6>\n"
+
+ parser = Wikitext::Parser.new :base_heading_level => 2
+ parser.parse('= hello =').should == "<h3>hello</h3>\n"
+ parser.parse('== hello ==').should == "<h4>hello</h4>\n"
+ parser.parse('=== hello ===').should == "<h5>hello</h5>\n"
+ parser.parse('==== hello ====').should == "<h6>hello</h6>\n"
+ parser.parse('===== hello =====').should == "<h6>hello</h6>\n"
+ parser.parse('====== hello ======').should == "<h6>hello</h6>\n"
+ end
+
+ it 'should treat the three different override methods as equivalent (level: 3)' do
+ @parser.base_heading_level = 3
+ @parser.parse('= hello =').should == "<h4>hello</h4>\n"
+ @parser.parse('== hello ==').should == "<h5>hello</h5>\n"
+ @parser.parse('=== hello ===').should == "<h6>hello</h6>\n"
+ @parser.parse('==== hello ====').should == "<h6>hello</h6>\n"
+ @parser.parse('===== hello =====').should == "<h6>hello</h6>\n"
+ @parser.parse('====== hello ======').should == "<h6>hello</h6>\n"
+
+ @parser.parse('= hello =', :base_heading_level => 3).should == "<h4>hello</h4>\n"
+ @parser.parse('== hello ==', :base_heading_level => 3).should == "<h5>hello</h5>\n"
+ @parser.parse('=== hello ===', :base_heading_level => 3).should == "<h6>hello</h6>\n"
+ @parser.parse('==== hello ====', :base_heading_level => 3).should == "<h6>hello</h6>\n"
+ @parser.parse('===== hello =====', :base_heading_level => 3).should == "<h6>hello</h6>\n"
+ @parser.parse('====== hello ======', :base_heading_level => 3).should == "<h6>hello</h6>\n"
+
+ parser = Wikitext::Parser.new :base_heading_level => 3
+ parser.parse('= hello =').should == "<h4>hello</h4>\n"
+ parser.parse('== hello ==').should == "<h5>hello</h5>\n"
+ parser.parse('=== hello ===').should == "<h6>hello</h6>\n"
+ parser.parse('==== hello ====').should == "<h6>hello</h6>\n"
+ parser.parse('===== hello =====').should == "<h6>hello</h6>\n"
+ parser.parse('====== hello ======').should == "<h6>hello</h6>\n"
+ end
+
+ it 'should treat the three different override methods as equivalent (level: 4)' do
+ @parser.base_heading_level = 4
+ @parser.parse('= hello =').should == "<h5>hello</h5>\n"
+ @parser.parse('== hello ==').should == "<h6>hello</h6>\n"
+ @parser.parse('=== hello ===').should == "<h6>hello</h6>\n"
+ @parser.parse('==== hello ====').should == "<h6>hello</h6>\n"
+ @parser.parse('===== hello =====').should == "<h6>hello</h6>\n"
+ @parser.parse('====== hello ======').should == "<h6>hello</h6>\n"
+
+ @parser.parse('= hello =', :base_heading_level => 4).should == "<h5>hello</h5>\n"
+ @parser.parse('== hello ==', :base_heading_level => 4).should == "<h6>hello</h6>\n"
+ @parser.parse('=== hello ===', :base_heading_level => 4).should == "<h6>hello</h6>\n"
+ @parser.parse('==== hello ====', :base_heading_level => 4).should == "<h6>hello</h6>\n"
+ @parser.parse('===== hello =====', :base_heading_level => 4).should == "<h6>hello</h6>\n"
+ @parser.parse('====== hello ======', :base_heading_level => 4).should == "<h6>hello</h6>\n"
+
+ parser = Wikitext::Parser.new :base_heading_level => 4
+ parser.parse('= hello =').should == "<h5>hello</h5>\n"
+ parser.parse('== hello ==').should == "<h6>hello</h6>\n"
+ parser.parse('=== hello ===').should == "<h6>hello</h6>\n"
+ parser.parse('==== hello ====').should == "<h6>hello</h6>\n"
+ parser.parse('===== hello =====').should == "<h6>hello</h6>\n"
+ parser.parse('====== hello ======').should == "<h6>hello</h6>\n"
+ end
+
+ it 'should treat the three different override methods as equivalent (level: 5)' do
+ @parser.base_heading_level = 5
+ @parser.parse('= hello =').should == "<h6>hello</h6>\n"
+ @parser.parse('== hello ==').should == "<h6>hello</h6>\n"
+ @parser.parse('=== hello ===').should == "<h6>hello</h6>\n"
+ @parser.parse('==== hello ====').should == "<h6>hello</h6>\n"
+ @parser.parse('===== hello =====').should == "<h6>hello</h6>\n"
+ @parser.parse('====== hello ======').should == "<h6>hello</h6>\n"
+
+ @parser.parse('= hello =', :base_heading_level => 5).should == "<h6>hello</h6>\n"
+ @parser.parse('== hello ==', :base_heading_level => 5).should == "<h6>hello</h6>\n"
+ @parser.parse('=== hello ===', :base_heading_level => 5).should == "<h6>hello</h6>\n"
+ @parser.parse('==== hello ====', :base_heading_level => 5).should == "<h6>hello</h6>\n"
+ @parser.parse('===== hello =====', :base_heading_level => 5).should == "<h6>hello</h6>\n"
+ @parser.parse('====== hello ======', :base_heading_level => 5).should == "<h6>hello</h6>\n"
+
+ parser = Wikitext::Parser.new :base_heading_level => 5
+ parser.parse('= hello =').should == "<h6>hello</h6>\n"
+ parser.parse('== hello ==').should == "<h6>hello</h6>\n"
+ parser.parse('=== hello ===').should == "<h6>hello</h6>\n"
+ parser.parse('==== hello ====').should == "<h6>hello</h6>\n"
+ parser.parse('===== hello =====').should == "<h6>hello</h6>\n"
+ parser.parse('====== hello ======').should == "<h6>hello</h6>\n"
+ end
+
+ it 'should treat the three different override methods as equivalent (level: 6)' do
+ @parser.base_heading_level = 6
+ @parser.parse('= hello =').should == "<h6>hello</h6>\n"
+ @parser.parse('== hello ==').should == "<h6>hello</h6>\n"
+ @parser.parse('=== hello ===').should == "<h6>hello</h6>\n"
+ @parser.parse('==== hello ====').should == "<h6>hello</h6>\n"
+ @parser.parse('===== hello =====').should == "<h6>hello</h6>\n"
+ @parser.parse('====== hello ======').should == "<h6>hello</h6>\n"
+
+ @parser.parse('= hello =', :base_heading_level => 6).should == "<h6>hello</h6>\n"
+ @parser.parse('== hello ==', :base_heading_level => 6).should == "<h6>hello</h6>\n"
+ @parser.parse('=== hello ===', :base_heading_level => 6).should == "<h6>hello</h6>\n"
+ @parser.parse('==== hello ====', :base_heading_level => 6).should == "<h6>hello</h6>\n"
+ @parser.parse('===== hello =====', :base_heading_level => 6).should == "<h6>hello</h6>\n"
+ @parser.parse('====== hello ======', :base_heading_level => 6).should == "<h6>hello</h6>\n"
+
+ parser = Wikitext::Parser.new :base_heading_level => 6
+ parser.parse('= hello =').should == "<h6>hello</h6>\n"
+ parser.parse('== hello ==').should == "<h6>hello</h6>\n"
+ parser.parse('=== hello ===').should == "<h6>hello</h6>\n"
+ parser.parse('==== hello ====').should == "<h6>hello</h6>\n"
+ parser.parse('===== hello =====').should == "<h6>hello</h6>\n"
+ parser.parse('====== hello ======').should == "<h6>hello</h6>\n"
+ end
+
+ # here we exceed the limit
+ it 'should treat the three different override methods as equivalent (level: 7)' do
+ @parser.base_heading_level = 7
+ @parser.parse('= hello =').should == "<h6>hello</h6>\n"
+ @parser.parse('== hello ==').should == "<h6>hello</h6>\n"
+ @parser.parse('=== hello ===').should == "<h6>hello</h6>\n"
+ @parser.parse('==== hello ====').should == "<h6>hello</h6>\n"
+ @parser.parse('===== hello =====').should == "<h6>hello</h6>\n"
+ @parser.parse('====== hello ======').should == "<h6>hello</h6>\n"
+
+ @parser.parse('= hello =', :base_heading_level => 7).should == "<h6>hello</h6>\n"
+ @parser.parse('== hello ==', :base_heading_level => 7).should == "<h6>hello</h6>\n"
+ @parser.parse('=== hello ===', :base_heading_level => 7).should == "<h6>hello</h6>\n"
+ @parser.parse('==== hello ====', :base_heading_level => 7).should == "<h6>hello</h6>\n"
+ @parser.parse('===== hello =====', :base_heading_level => 7).should == "<h6>hello</h6>\n"
+ @parser.parse('====== hello ======', :base_heading_level => 7).should == "<h6>hello</h6>\n"
+
+ parser = Wikitext::Parser.new :base_heading_level => 7
+ parser.parse('= hello =').should == "<h6>hello</h6>\n"
+ parser.parse('== hello ==').should == "<h6>hello</h6>\n"
+ parser.parse('=== hello ===').should == "<h6>hello</h6>\n"
+ parser.parse('==== hello ====').should == "<h6>hello</h6>\n"
+ parser.parse('===== hello =====').should == "<h6>hello</h6>\n"
+ parser.parse('====== hello ======').should == "<h6>hello</h6>\n"
+ end
+
+ # for bad markup, we adjust any headings that we output (h2 -> h4),
+ # but we show the bad markup (===) as it was entered (not-adjusted)
+ it 'should show unbalanced markup exactly as it was entered' do
+ @parser.base_heading_level = 2
+
+ # missing trailing =
+ @parser.parse('== hello =').should == "<h4>hello =</h4>\n"
+
+ # excess trailing =
+ @parser.parse('== hello ===').should == "<h4>hello ===</h4>\n"
+ end
+
+ it 'should show markup in <nowiki> spans exactly as it was entered' do
+ @parser.base_heading_level = 3
+ @parser.parse("<nowiki>\n== foo ==\n</nowiki>").should == "<p>\n== foo ==\n</p>\n"
+ end
+
+ it 'should show markup in <pre> blocks exactly as it was entered' do
+ @parser.base_heading_level = 1
+ @parser.parse("<pre>\n== bar ==\n</pre>").should == "<pre>\n== bar ==\n</pre>\n"
+ end
+end