source: main/trunk/greenstone2/perllib/cpan/Mojolicious/Routes/Pattern.pm@ 32205

Last change on this file since 32205 was 32205, checked in by ak19, 6 years ago

First set of commits to do with implementing the new 'paged_html' output option of PDFPlugin that uses using xpdftools' new pdftohtml. So far tested only on Linux (64 bit), but things work there so I'm optimistically committing the changes since they work. 2. Committing the pre-built Linux binaries of XPDFtools for both 32 and 64 bit built by the XPDF group. 2. To use the correct bitness variant of xpdftools, setup.bash now exports the BITNESS env var, consulted by gsConvert.pl. 3. All the perl code changes to do with using xpdf tools' pdftohtml to generate paged_html and feed it in the desired form into GS(3): gsConvert.pl, PDFPlugin.pm and its parent ConvertBinaryPFile.pm have been modified to make it all work. xpdftools' pdftohtml generates a folder containing an html file and a screenshot for each page in a PDF (as well as an index.html linking to each page's html). However, we want a single html file that contains each individual 'page' html's content in a div, and need to do some further HTML style, attribute and structure modifications to massage the xpdftool output to what we want for GS. In order to parse and manipulate the HTML 'DOM' to do this, we're using the Mojo::DOM package that Dr Bainbridge found and which he's compiled up. Mojo::DOM is therefore also committed in this revision. Some further changes and some display fixes are required, but need to check with the others about that.

File size: 9.3 KB
Line 
1package Mojolicious::Routes::Pattern;
2use Mojo::Base -base;
3
4use Carp 'croak';
5use Mojo::Util 'deprecated';
6
7has [qw(constraints defaults types)] => sub { {} };
8has [qw(placeholder_start type_start)] => ':';
9has [qw(placeholders tree)] => sub { [] };
10has quote_end => '>';
11has quote_start => '<';
12has [qw(regex unparsed)];
13has relaxed_start => '#';
14has wildcard_start => '*';
15
16sub match {
17 my ($self, $path, $detect) = @_;
18 my $captures = $self->match_partial(\$path, $detect);
19 return !$path || $path eq '/' ? $captures : undef;
20}
21
22sub match_partial {
23 my ($self, $pathref, $detect) = @_;
24
25 # Compile on demand
26 $self->_compile($detect) unless $self->{regex};
27
28 return undef unless my @captures = $$pathref =~ $self->regex;
29 $$pathref = ${^POSTMATCH};
30 @captures = () if $#+ == 0;
31 my $captures = {%{$self->defaults}};
32 for my $placeholder (@{$self->placeholders}, 'format') {
33 last unless @captures;
34 my $capture = shift @captures;
35 $captures->{$placeholder} = $capture if defined $capture;
36 }
37
38 return $captures;
39}
40
41sub new { @_ > 1 ? shift->SUPER::new->parse(@_) : shift->SUPER::new }
42
43sub parse {
44 my $self = shift;
45
46 my $pattern = @_ % 2 ? (shift // '/') : '/';
47 $pattern =~ s!^/*|/+!/!g;
48 return $self->constraints({@_}) if $pattern eq '/';
49
50 $pattern =~ s!/$!!;
51 return $self->constraints({@_})->_tokenize($pattern);
52}
53
54sub render {
55 my ($self, $values, $endpoint) = @_;
56
57 my $start = $self->type_start;
58
59 # Placeholders can only be optional without a format
60 my $optional = !(my $format = $values->{format});
61
62 my $str = '';
63 for my $token (reverse @{$self->tree}) {
64 my ($op, $value) = @$token;
65 my $part = '';
66
67 # Text
68 if ($op eq 'text') { ($part, $optional) = ($value, 0) }
69
70 # Slash
71 elsif ($op eq 'slash') { $part = '/' unless $optional }
72
73 # Placeholder
74 else {
75 my $name = (split $start, $value)[0] // '';
76 my $default = $self->defaults->{$name};
77 $part = $values->{$name} // $default // '';
78 if (!defined $default || ($default ne $part)) { $optional = 0 }
79 elsif ($optional) { $part = '' }
80 }
81
82 $str = $part . $str;
83 }
84
85 # Format can be optional
86 return $endpoint && $format ? "$str.$format" : $str;
87}
88
89sub _compile {
90 my ($self, $detect) = @_;
91
92 my $placeholders = $self->placeholders;
93 my $constraints = $self->constraints;
94 my $defaults = $self->defaults;
95 my $start = $self->type_start;
96 my $types = $self->types;
97
98 my $block = my $regex = '';
99 my $optional = 1;
100 for my $token (reverse @{$self->tree}) {
101 my ($op, $value, $type) = @$token;
102 my $part = '';
103
104 # Text
105 if ($op eq 'text') { ($part, $optional) = (quotemeta $value, 0) }
106
107 # Slash
108 elsif ($op eq 'slash') {
109 $regex = ($optional ? "(?:/$block)?" : "/$block") . $regex;
110 ($block, $optional) = ('', 1);
111 next;
112 }
113
114 # Placeholder
115 else {
116 if ($value =~ /^(.+)\Q$start\E(.+)$/) {
117 ($value, $part) = ($1, _compile_req($types->{$2} // '?!'));
118 }
119 else {
120 $part = $type ? $type eq 'relaxed' ? '([^/]+)' : '(.+)' : '([^/.]+)';
121 }
122 unshift @$placeholders, $value;
123
124 # Custom regex
125 if (my $c = $constraints->{$value}) { $part = _compile_req($c) }
126
127 # Optional placeholder
128 exists $defaults->{$value} ? ($part .= '?') : ($optional = 0);
129 }
130
131 $block = $part . $block;
132 }
133
134 # Not rooted with a slash
135 $regex = $block . $regex if $block;
136
137 # Format
138 $regex .= _compile_format($constraints->{format}, $defaults->{format})
139 if $detect;
140
141 $self->regex(qr/^$regex/ps);
142}
143
144sub _compile_format {
145 my ($format, $default) = @_;
146
147 # Default regex
148 return '/?(?:\.([^/]+))?$' unless defined $format;
149
150 # No regex
151 return '' unless $format;
152
153 # Compile custom regex
154 my $regex = '\.' . _compile_req($format);
155 return $default ? "/?(?:$regex)?\$" : "/?$regex\$";
156}
157
158sub _compile_req {
159 my $req = shift;
160 return "($req)" if ref $req ne 'ARRAY';
161 return '(' . join('|', map {quotemeta} reverse sort @$req) . ')';
162}
163
164sub _tokenize {
165 my ($self, $pattern) = @_;
166
167 # DEPRECATED!
168 deprecated 'Placeholder quoting with "(placeholder)" is DEPRECATED'
169 . ' in favor of "<placeholder>"'
170 if $pattern =~ tr/()/<>/;
171
172 my $quote_end = $self->quote_end;
173 my $quote_start = $self->quote_start;
174 my $start = $self->placeholder_start;
175 my $relaxed = $self->relaxed_start;
176 my $wildcard = $self->wildcard_start;
177
178 my (@tree, $spec, $more);
179 for my $char (split '', $pattern) {
180
181 # Quoted
182 if ($char eq $quote_start) { push @tree, ['placeholder', ''] if ++$spec }
183 elsif ($char eq $quote_end) { $spec = $more = 0 }
184
185 # Placeholder
186 elsif (!$more && $char eq $start) {
187 push @tree, ['placeholder', ''] unless $spec++;
188 }
189
190 # Relaxed or wildcard (upgrade when quoted)
191 elsif (!$more && ($char eq $relaxed || $char eq $wildcard)) {
192 push @tree, ['placeholder', ''] unless $spec++;
193 $tree[-1][2] = $char eq $relaxed ? 'relaxed' : 'wildcard';
194 }
195
196 # Slash
197 elsif ($char eq '/') {
198 push @tree, ['slash'];
199 $spec = $more = 0;
200 }
201
202 # Placeholder
203 elsif ($spec && ++$more) { $tree[-1][1] .= $char }
204
205 # Text (optimize slash+text and *+text+slash+text)
206 elsif ($tree[-1][0] eq 'text') { $tree[-1][-1] .= $char }
207 elsif (!$tree[-2] && $tree[-1][0] eq 'slash') {
208 @tree = (['text', "/$char"]);
209 }
210 elsif ($tree[-2] && $tree[-2][0] eq 'text' && $tree[-1][0] eq 'slash') {
211 pop @tree && ($tree[-1][-1] .= "/$char");
212 }
213 else { push @tree, ['text', $char] }
214 }
215
216 return $self->unparsed($pattern)->tree(\@tree);
217}
218
2191;
220
221=encoding utf8
222
223=head1 NAME
224
225Mojolicious::Routes::Pattern - Route pattern
226
227=head1 SYNOPSIS
228
229 use Mojolicious::Routes::Pattern;
230
231 # Create pattern
232 my $pattern = Mojolicious::Routes::Pattern->new('/test/:name');
233
234 # Match routes
235 my $captures = $pattern->match('/test/sebastian');
236 say $captures->{name};
237
238=head1 DESCRIPTION
239
240L<Mojolicious::Routes::Pattern> is the core of L<Mojolicious::Routes>.
241
242=head1 ATTRIBUTES
243
244L<Mojolicious::Routes::Pattern> implements the following attributes.
245
246=head2 constraints
247
248 my $constraints = $pattern->constraints;
249 $pattern = $pattern->constraints({foo => qr/\w+/});
250
251Regular expression constraints.
252
253=head2 defaults
254
255 my $defaults = $pattern->defaults;
256 $pattern = $pattern->defaults({foo => 'bar'});
257
258Default parameters.
259
260=head2 placeholder_start
261
262 my $start = $pattern->placeholder_start;
263 $pattern = $pattern->placeholder_start(':');
264
265Character indicating a placeholder, defaults to C<:>.
266
267=head2 placeholders
268
269 my $placeholders = $pattern->placeholders;
270 $pattern = $pattern->placeholders(['foo', 'bar']);
271
272Placeholder names.
273
274=head2 quote_end
275
276 my $end = $pattern->quote_end;
277 $pattern = $pattern->quote_end('}');
278
279Character indicating the end of a quoted placeholder, defaults to C<E<gt>>.
280
281=head2 quote_start
282
283 my $start = $pattern->quote_start;
284 $pattern = $pattern->quote_start('{');
285
286Character indicating the start of a quoted placeholder, defaults to C<E<lt>>.
287
288=head2 regex
289
290 my $regex = $pattern->regex;
291 $pattern = $pattern->regex($regex);
292
293Pattern in compiled regular expression form.
294
295=head2 relaxed_start
296
297 my $start = $pattern->relaxed_start;
298 $pattern = $pattern->relaxed_start('*');
299
300Character indicating a relaxed placeholder, defaults to C<#>.
301
302=head2 tree
303
304 my $tree = $pattern->tree;
305 $pattern = $pattern->tree([['text', '/foo']]);
306
307Pattern in parsed form. Note that this structure should only be used very
308carefully since it is very dynamic.
309
310=head2 type_start
311
312 my $start = $pattern->type_start;
313 $pattern = $pattern->type_start('|');
314
315Character indicating the start of a placeholder type, defaults to C<:>.
316
317=head2 types
318
319 my $types = $pattern->types;
320 $pattern = $pattern->types({int => qr/[0-9]+/});
321
322Placeholder types.
323
324=head2 unparsed
325
326 my $unparsed = $pattern->unparsed;
327 $pattern = $pattern->unparsed('/:foo/:bar');
328
329Raw unparsed pattern.
330
331=head2 wildcard_start
332
333 my $start = $pattern->wildcard_start;
334 $pattern = $pattern->wildcard_start('*');
335
336Character indicating the start of a wildcard placeholder, defaults to C<*>.
337
338=head1 METHODS
339
340L<Mojolicious::Routes::Pattern> inherits all methods from L<Mojo::Base> and
341implements the following new ones.
342
343=head2 match
344
345 my $captures = $pattern->match('/foo/bar');
346 my $captures = $pattern->match('/foo/bar', 1);
347
348Match pattern against entire path, format detection is disabled by default.
349
350=head2 match_partial
351
352 my $captures = $pattern->match_partial(\$path);
353 my $captures = $pattern->match_partial(\$path, 1);
354
355Match pattern against path and remove matching parts, format detection is
356disabled by default.
357
358=head2 new
359
360 my $pattern = Mojolicious::Routes::Pattern->new;
361 my $pattern = Mojolicious::Routes::Pattern->new('/:action');
362 my $pattern
363 = Mojolicious::Routes::Pattern->new('/:action', action => qr/\w+/);
364 my $pattern = Mojolicious::Routes::Pattern->new(format => 0);
365
366Construct a new L<Mojolicious::Routes::Pattern> object and L</"parse"> pattern
367if necessary.
368
369=head2 parse
370
371 $pattern = $pattern->parse('/:action');
372 $pattern = $pattern->parse('/:action', action => qr/\w+/);
373 $pattern = $pattern->parse(format => 0);
374
375Parse pattern.
376
377=head2 render
378
379 my $path = $pattern->render({action => 'foo'});
380 my $path = $pattern->render({action => 'foo'}, 1);
381
382Render pattern into a path with parameters, format rendering is disabled by
383default.
384
385=head1 SEE ALSO
386
387L<Mojolicious>, L<Mojolicious::Guides>, L<https://mojolicious.org>.
388
389=cut
Note: See TracBrowser for help on using the repository browser.