Merge to trunk, add version note
[catagits/Catalyst-View-ContentNegotiation-XHTML.git] / lib / Catalyst / View / ContentNegotiation / XHTML.pm
1 package Catalyst::View::ContentNegotiation::XHTML;
2
3 use Moose::Role;
4 use MooseX::Types::Moose qw/Num Str ArrayRef/;
5 use MooseX::Types::Structured qw/Tuple/;
6 use HTTP::Negotiate qw/choose/;
7
8 use namespace::clean -except => 'meta';
9
10 our $VERSION = '1.100';
11
12 has variants => (
13     is      => 'ro',
14     isa     => ArrayRef[Tuple[Str, Num, Str]],
15     lazy    => 1,
16     builder => '_build_variants',
17 );
18
19 sub _build_variants {
20     return [
21         [qw| xhtml 1.000 application/xhtml+xml |],
22         [qw| html  0.900 text/html             |],
23     ];
24 }
25
26 after process => sub {
27     my ($self, $c) = @_;
28     if ($c->request->header('Accept') && $c->response->headers->{'content-type'} =~ m|text/html|) {
29         $self->pragmatic_accept($c);
30         my $var = choose($self->variants, $c->request->headers);
31         if ($var eq 'xhtml') {
32             $c->response->headers->{'content-type'} =~ s|text/html|application/xhtml+xml|;
33         }
34     }
35 };
36
37 sub pragmatic_accept {
38     my ($self, $c) = @_;
39     my $accept = $c->request->header('Accept');
40     if ($accept =~ m|text/html|) {
41         $accept =~ s!\*/\*\s*([,]+|$)!*/*;q=0.5$1!;
42     } else {
43         $accept =~ s!\*/\*\s*([,]+|$)!text/html,*/*;q=0.5$1!;
44     }
45     $c->request->header('Accept' => $accept);
46 }
47
48 1;
49
50 __END__
51
52 =head1 NAME
53
54 Catalyst::View::ContentNegotiation::XHTML - A Moose Role to apply to
55 Catalyst views adjusts the response Content-Type header to 
56 application/xhtml+xml content if the browser accepts it.
57
58 =head1 SYNOPSIS
59
60     package Catalyst::View::TT;
61
62     use Moose;
63     use namespace::clean -except => 'meta';
64
65     extends qw/Catalyst::View::TT/;
66     with qw/Catalyst::View::ContentNegotiation::XHTML/;
67
68     1;
69
70 =head1 DESCRIPTION
71
72 This is a very simple Role which uses a method modifier to run after the
73 C<process> method, and sets the response C<Content-Type> to be 
74 C<application/xhtml+xml> if the users browser sends an C<Accept> header 
75 indicating that it is willing to process that MIME type.
76
77 Changing the C<Content-Type> causes browsers to interpret the page as
78 XML, meaning that the markup must be well formed.
79
80 This is useful when you're developing your application, as you know that
81 all pages you view are parsed as XML, so any errors caused by your markup
82 not being well-formed will show up at once.
83
84 =head1 METHOD MODIFIERS
85
86 =head2 after process
87
88 Changes the response C<Content-Type> if appropriate (from the requests C<Accept> header).
89
90 =head1 METHODS
91
92 =head2 pragmatic_accept
93
94 Some browsers (such as Internet Explorer) have a nasty way of sending
95 Accept */* and this claiming to support XHTML just as well as HTML.
96 Saving to a file on disk or opening with another application does
97 count as accepting, but it really should have a lower q value then
98 text/html. This sub takes a pragmatic approach and corrects this mistake
99 by modifying the Accept header before passing it to content negotiation.
100
101 =head1 ATTRIBUTES
102
103 =head2 variants
104
105 Returns an array ref of 3 part arrays, comprising name, priority, output 
106 mime-type, which is used for the content negotiation algorithm.
107
108 =head1 PRIVATE METHODS
109
110 =head2 _build_variants
111
112 Returns the default variant attribute contents.
113
114 =head1 SEE ALSO
115
116 =over
117
118 =item L<Catalyst::View::TT::XHTML> - Trivial Catalyst TT view using this role.
119
120 =item L<http://www.w3.org/Protocols/rfc2616/rfc2616-sec12.html> - Content negotiation RFC.
121
122 =back
123
124 =head1 BUGS
125
126 Will only work with Views which implement a process method.
127
128 Should be split into a base ContentNegotiation role which is consumed by ContentNegotiation::XHTML.
129
130 =head1 AUTHOR
131
132 Tomas Doran (t0m) C<< <bobtfish@bobtfish.net> >>
133
134 =head1 CONTRIBUTORS
135
136 =over
137
138 =item David Dorward - test patches and */* pragmatism. 
139
140 =item Florian Ragwitz (rafl) C<< <rafl@debian.org> >> - Conversion into a Moose Role
141
142 =back
143
144 =head1 COPYRIGHT
145
146 This module itself is copyright (c) 2008 Tomas Doran and is licensed under the same terms as Perl itself.
147
148 =cut