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