root/branches/release-38/plugins/Textile/textile2.pl @ 2348

Revision 2348, 14.1 kB (checked in by bchoate, 19 months ago)

Fixed circular reference in MT/Textile. BugId:79687

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