root/branches/release-36/plugins/Textile/textile2.pl @ 2035

Revision 2035, 13.9 kB (checked in by bchoate, 19 months ago)

Updated help url for Textile. BugId:78945

  • Property svn:executable set to *
  • Property svn:keywords set to Id Revision
Line 
1# ---------------------------------------------------------------------------
2# MT-Textile Text Formatter
3# A plugin for Movable Type
4#
5# Release 2.05
6#
7# Brad Choate
8# http://www.bradchoate.com/
9# ---------------------------------------------------------------------------
10# This software is provided as-is.
11# You may use it for commercial or personal use.
12# If you distribute it, please keep this notice intact.
13#
14# Copyright (c) 2003-2008 Brad Choate
15# ---------------------------------------------------------------------------
16# $Id$
17# ---------------------------------------------------------------------------
18
19package MT::Plugin::Textile;
20
21use strict;
22use MT;
23use base qw( MT::Plugin );
24
25our $VERSION = 2.05;
26our ($_initialized, $Have_SmartyPants);
27
28MT->add_plugin(__PACKAGE__->new({
29    name => "Textile",
30    description => '<MT_TRANS phrase="A humane web text generator.">',
31    author_name => "Brad Choate",
32    author_link => "http://bradchoate.com/",
33    version => $VERSION,
34    registry => {
35        text_filters => {
36            textile_2 => {
37                label => "Textile 2",
38                code => \&textile_2,
39                docs => "http://www.movabletype.org/documentation/author/textile-2-syntax.html",
40            },
41        },
42        tags => {
43            help_url => sub { MT->translate('http://www.movabletype.org/documentation/appendices/tags/%t.html') },
44            block => {
45                Textile => \&Textile,
46            },
47            function => {
48                TextileOptions => \&TextileOptions,
49                TextileHeadOffset => \&TextileHeadOffset,
50            },
51        },
52    },
53}));
54
55sub _init {
56    require Text::Textile;
57    @MT::Textile::ISA = qw(Text::Textile);
58    $Have_SmartyPants = defined &SmartyPants::SmartyPants ? 1 : 0;
59    $_initialized = 1;
60}
61
62sub textile_2 {
63    my ($str, $ctx) = @_;
64
65    _init() unless $_initialized;
66
67    my $textile;
68    if ((defined $ctx)  && (ref($ctx) eq 'MT::Template::Context')) {
69        $textile = $ctx->stash('TextileObj');
70        unless ($textile) {
71            $textile = _new_textile($ctx);
72            $ctx->stash('TextileObj', $textile);
73        }
74        $textile->head_offset($ctx->stash('TextileHeadOffsetStart') || 0);
75        if (my $opts = $ctx->stash('TextileOptions')) {
76            $textile->set($_, $opts->{$_}) foreach keys %$opts;
77            # now clear the options from the stash so we don't
78            # repeat this for each invocation of textile...
79            $ctx->stash('TextileOptions', undef);
80        }
81
82        $textile->filter_param($ctx);
83    } else {
84        # no Context object...
85        $textile = _new_textile();
86    }
87
88    require MT::I18N;
89    $str = MT::I18N::encode_text( $str, MT->instance->config->PublishCharset, 'utf-8' );
90
91    $str = $textile->process($str);
92
93    if ((defined $ctx) && (ref($ctx) eq 'MT::Template::Context')) {
94        my $entry = $ctx->stash('entry');
95        if ($entry && $entry->id) {
96            my $link = $entry->permalink;
97            $link =~ s/#.+$//;
98            $str =~ s/(<a .*?(?<=[ ])href=")(#fn(?:\d)+".*?>)/$1$link$2/g;
99        }
100    }
101
102    # invoke MT-CodeBeautifier for any <code> or <blockcode> tags that
103    # specify a 'language' attribute:
104    my $beautifier = defined &Voisen::CodeBeautifier::beautifier;
105    if ($beautifier) {
106        $str =~ s|<((block)?code)([^>]*?) language="([^"]+?)"([^>]*?)>(.+?)</\1>|_highlight($1, $3, $5, $4, $textile->decode_html($6))|ges; # "
107    }
108
109    $str = MT::I18N::encode_text( $str, 'utf-8', MT->instance->config->PublishCharset );
110
111    $str;
112}
113
114sub _new_textile {
115    my ($ctx) = @_;
116
117    my $textile = new MT::Textile;
118
119    # this copies the named filters from MT to TextileFormat
120    my %list;
121    my $filters = MT::all_text_filters();
122    foreach my $name (keys %$filters) {
123        $list{$name} = $filters->{$name}{on_format};
124    }
125    $textile->filters(\%list);
126
127    my $cfg = MT::ConfigMgr->instance;
128
129    if ($cfg->NoHTMLEntities) {
130        $textile->char_encoding(0);
131    }
132
133    $textile->charset('utf-8');
134
135    $textile;
136}
137
138sub Textile {
139    my ($ctx, $args, $cond) = @_;
140    _init() unless $_initialized;
141    local $ctx->{__stash}{TextileObj} = _new_textile($ctx);
142    local $ctx->{__stash}{TextileOptions} = $args if keys %$args;
143    my $str = $ctx->slurp;
144    textile_2($str, $ctx);
145}
146
147sub TextileHeadOffset {
148    my ($ctx, $args, $cond) = @_;
149    my $start = $args->{start};
150    if ($start && $start =~ m/^\d+$/ && $start >= 1 && $start <= 6) {
151        $ctx->stash('TextileHeadOffsetStart', $start);
152    }
153    '';
154}
155
156sub TextileOptions {
157    my ($ctx, $args, $cond) = @_;
158    $ctx->stash('TextileOptions', $args);
159    '';
160}
161
162sub _highlight {
163    my ($tag, $attr1, $attr2, $lang, $code) = @_;
164    my $tagopen = '<'.$tag;
165    $tagopen .= $attr1 if defined $attr1;
166    $tagopen .= $attr2 if defined $attr2;
167    $tagopen .= '>';
168    if ($lang =~ m/perl/i) {
169        $code = Voisen::CodeBeautifier::highlight_perl($code);
170    } elsif ($lang =~ m/php/i) {
171        $code = Voisen::CodeBeautifier::highlight_php3($code);
172    } elsif ($lang =~ m/java/i) {
173        $code = Voisen::CodeBeautifier::highlight_java($code);
174    } elsif (($lang =~ m/actionscript/i) || ($lang =~ m/as/i)) {
175        $code = Voisen::CodeBeautifier::highlight_as($code);
176    } elsif ($lang =~ m/scheme/i) {
177        $code = Voisen::CodeBeautifier::highlight_scheme($code);
178    }
179    $code =~ s!^<pre>!!;
180    $code =~ s!</pre>$!!;
181    return $tagopen . $code .'</'.$tag.'>';
182}
183
184# This is a Text::Textile subclass that provides enhanced
185# functionality for Movable Type integration
186
187package MT::Textile;
188
189sub image_size {
190    my $self = shift;
191    my ($src) = @_;
192    my $ctx = $self->filter_param;
193    if ($src !~ m|^http:| && $ctx) {
194        my $blog = $ctx->stash('blog');
195        if ($blog) {
196            require File::Spec;
197            # local image -- calc size
198            my $file;
199            if (($src =~ m!^/!) && (exists $ENV{DOCUMENT_ROOT})) {
200                $file = File::Spec->catfile($ENV{DOCUMENT_ROOT}, $src);
201            } else {
202                $file = File::Spec->catfile($blog->site_path, $src);
203            }
204            if (-f $file) {
205                eval {require MT::Image;};
206                if (!$@) {
207                    my $img = MT::Image->new(Filename => $file);
208                    if ($img) {
209                        return $img->get_dimensions;
210                    }
211                }
212            }
213        }
214    }
215    undef;
216}
217
218sub format_link {
219    my $self = shift;
220    my (%args) = @_;
221    my $title = exists $args{title} ? $args{title} : '';
222    my $url = exists $args{url} ? $args{url} : '';
223    my $ctx = $self->filter_param;
224    if ($url =~ m/^\d+$/ && $ctx) {
225        my $blog = $ctx->stash('blog');
226        if ($blog) {
227            require MT::Entry;
228            my $entry = MT::Entry->load({ blog_id => $blog->id, id => $url });
229            if ($entry) {
230                local $ctx->{__stash}{entry} = $entry;
231                my $relurl = MT::Template::Context::_hdlr_blog_url($ctx);
232                my $regrelurl = quotemeta($relurl);
233                $args{url} = MT::Template::Context::_hdlr_entry_permalink($ctx);
234                $args{url} =~ s/^.*?($regrelurl)/$1/;
235                if ((!exists $args{title}) && ($entry->title)) {
236                    require MT::Util;
237                    my $title = MT::Util::remove_html($entry->title); # strip HTML
238                    $title =~ s/"/&quot;/g; # convert double quotes to entities
239                    $args{title} = $title;
240                }
241            }
242        }
243    }
244
245    $self->SUPER::format_link(%args);
246}
247
248sub process_quotes {
249    my $self = shift;
250    my ($str) = @_;
251    return $str unless $self->{do_quotes};
252    if ($plugins::textile2::Have_SmartyPants) {
253        $str = SmartyPants::SmartyPants($str, $self->{smarty_mode});
254    }
255    $str;
256}
257
258sub format_url {
259    my $self = shift;
260    my (%args) = @_;
261    my $url = exists $args{url} ? $args{url} : '';
262    my $ctx = $self->filter_param;
263    if ($url =~ m/^\d+$/ && $ctx) {
264        # looks like an entry id, so let's link it
265        my $blog = $ctx->stash('blog');
266        if ($blog) {
267            require MT::Entry;
268            my $entry = MT::Entry->load({'blog_id' => $blog->id, 'id'=>$url});
269            if ($entry) {
270                local $ctx->{__stash}{entry} = $entry;
271                my $relurl = MT::Template::Context::_hdlr_blog_relative_url($ctx);
272                my $regrelurl = quotemeta($relurl);
273                $args{url} = MT::Template::Context::_hdlr_entry_permalink($ctx);
274                $args{url} =~ s/^.+?($regrelurl)/$1/;
275            }
276        }
277    } elsif ($url =~ m/^imdb(?::(.+))?$/) {
278        my $term = $1;
279        $term ||= MT::Util::remove_html($args{linktext}||'');
280        $args{url} = 'http://www.imdb.com/Find?for=' . $term;
281    } elsif ($url =~ m/^google(?::(.+))?$/) {
282        my $term = $1;
283        $term ||= MT::Util::remove_html($args{linktext}||'');
284        $args{url} = 'http://www.google.com/search?q=' . $term;
285    } elsif ($url =~ m/^dict(?::(.+))?$/) {
286        my $term = $1;
287        $term ||= MT::Util::remove_html($args{linktext}||'');
288        $args{url} = 'http://www.dictionary.com/search?q=' . $term;
289    } elsif ($url =~ m/^amazon(?::(.+))?$/) {
290        my $term = $1;
291        $term ||= MT::Util::remove_html($args{linktext}||'');
292        $args{url} = 'http://www.amazon.com/exec/obidos/external-search?index=blended&keyword=' . $term;
293    }
294    $self->SUPER::format_url(%args);
295}
296
2971;
298
299__END__
300
301=head1 NAME
302
303Textile - A plugin for Movable Type.
304
305=head1 DESCRIPTION
306
307This plugin integrates the Perl Text::Textile module with Movable
308Type.
309
310=head1 INSTALLATION
311
312To install, place the 'textile2.pl' file in your Movable Type
313'plugins' directory. Install the 'Textile.pm' file in a 'Text'
314subdirectory underneath the Movable Type 'extlib' directory.
315
316Your installation should look like this:
317
318    (mt home)/plugins/textile2.pl
319    (mt home)/extlib/Text/Textile.pm
320
321You may also consider installing the Smarty Pants plugin by
322John Gruber. MT-Textile uses this plugin to translate normal
323quote characters into typographic quotes.
324
325The Code Beautifier plugin by Sean Voisen is also supported
326by MT-Textile. If you specify a language modifier for "bc"
327blocks or for "@...@" strings, it will invoke the Code Beautifier
328to colorize the code.
329
330=head1 TEXT FORMATTERS
331
332=head2 textile_2
333
334The text formatter identified by "textile_2" invokes the Textile
335formatter. You can select "Textile 2" from the Movable Type interface
336to use this option to format your entries and/or comments.
337
338You may also invoke the formatter on any Movable Type tag, like this:
339
340    <$MTBlogDescription filters="textile_2"$>
341
342For specific documentation on how this text formatter processes
343text (a writing guide), please refer to the online documentation
344available here:
345
346L<http://www.bradchoate.com/mt/docs/mtmanual_textile2.html>
347
348A copy of that document is available in the distribution of this
349plugin. Located in the file "docs/mtmanual_textile2.html".
350
351=head1 TAGS
352
353=head2 E<lt>MTTextileE<gt>
354
355A container tag that also allows you to invoke the Textile formatter.
356Anything placed within this tag will be fed into the formatter for
357processing. Additionally, all attributes passed to this tag are
358processed as options for the Textile engine.
359
360Attributes available:
361
362=over
363
364=item charset
365
366Set this to the character set of the incoming data. This value
367will default to the "PublishCharset" value in your mt.cfg file.
368If no charset is given anywhere, it will default to ISO-8859-1.
369
370=item char_encoding
371
372Set to 1 to encode special characters to HTML entities. If
373you're outputting utf-8 data, this can be set to '0' to
374output plaintext instead. In fact, if you set your PublishCharset
375for Movable Type to utf-8, it will effectively set this
376setting to '0'. Otherwise, the default value is 1.
377
378=item do_quotes
379
380Sets the option for processing quotes into curly quotes. Defaults
381to 1. The "Smarty" plugin will be used as a default, if available.
382
383=item trim_spaces
384
385Set to '1' to cause any trailing whitespace on lines to be
386trimmed. Default is 0 (disabled).
387
388=item smarty_mode
389
390Controls the Smarty plugin for processing quotes. The value
391given is passed through to the Smarty plugin to control how it
392behaves. Please refer to the documentation for the Smarty Pants
393plugin for the available values for this attribute. You can
394simply specify '1' for the default mode of operation.
395
396=item preserve_spaces
397
398Set to '1' to cause multiple, continuous spaces to be preserved
399by changing them to the HTML entity &#8195;. If '0', spaces
400are left as-is, which means they will appear as a single space
401when rendered to HTML. Default is '0'.
402
403=item head_offset
404
405Set to a number from "1" to "5" to control the numbering of the
406"h1" to "h6" paragraph block codes. For example, if a head_offset
407of "1" is given, any "h1" paragraph blocks will produce "h2" HTML
408tags. Or "h3" HTML tags if a head_offset of "2" is specified.
409
410=item flavor
411
412Use the flavor attribute to identify whether the HTML produced
413should be HTML, XHTML 1.x or XHTML 2 compliant. Available
414values include:
415
416    html
417    html/css
418    xhtml1
419    xhtml2
420
421The default flavor is "xhtml1".
422               
423=item css
424
425Set to '1' to allow the Textile formatter to use CSS classes
426and style attributes to format any output. Specifying '0' will
427avoid the use of style and class attributes. This attribute
428will default to '1' when a flavor of "html/css" or "xhtml1"
429or "xhtml2" is given.
430
431=back
432
433=head2 E<lt>$MTTextileOptions$E<gt>
434
435A standalone tag that can also be used to configure the Textile
436text formatter. See the attributes available to the E<lt>MTTextileE<gt>
437tag for what is available.
438
439=head2 E<lt>$MTTextileHeadOffset$E<gt>
440
441Another tag that can be used to set the "head_offset" attribute.
442This tag is left for backward compability. Please use the
443E<lt>MTTextileOptionsE<gt> tag in the future.
444
445=head1 LICENSE
446
447Released under the MIT license. Please see
448
449L<http://www.opensource.org/licenses/mit-license.php>
450
451for details.
452
453=head1 SUPPORT
454
455If you have questions or need assistance with this plugin, please
456use the following link:
457
458L<http://www.bradchoate.com/mt-plugins/textile>
459
460=head1 COPYRIGHT
461
462Copyright 2002-2004, Brad Choate
463
464=cut
Note: See TracBrowser for help on using the browser.