File Coverage

blib/lib/BeamerReveal/Object/BeamerFrame.pm
Criterion Covered Total %
statement 21 216 9.7
branch 0 82 0.0
condition 0 22 0.0
subroutine 7 13 53.8
pod 3 4 75.0
total 31 337 9.2


line stmt bran cond sub pod time code
1             # -*- cperl -*-
2             # ABSTRACT: BeamerFrame object
3              
4              
5             package BeamerReveal::Object::BeamerFrame;
6             our $VERSION = '20260208.1851'; # VERSION
7              
8 1     1   2909 use parent 'BeamerReveal::Object';
  1         2  
  1         7  
9 1     1   69 use Carp;
  1         2  
  1         79  
10              
11 1     1   4 use Data::Dumper;
  1         3  
  1         70  
12              
13 1     1   6 use BeamerReveal::TemplateStore;
  1         2  
  1         32  
14 1     1   4 use BeamerReveal::MediaManager;
  1         1  
  1         25  
15              
16 1     1   4 use BeamerReveal::Log;
  1         1  
  1         25  
17              
18 1     1   513 use Digest::SHA;
  1         3215  
  1         4720  
19              
20              
21             our $maxRawPage;
22             our $maxNotePage = 0;
23             our $embeddedID;
24              
25 0     0 0   sub nofdigits { length( "$_[0]" ) }
26              
27             sub new {
28 0     0 1   my $class = shift;
29 0           my ( $chunkData, $lines, $lineCtr, $rawpage ) = @_;
30            
31 0 0         $class = (ref $class ? ref $class : $class );
32 0           my $self = {};
33 0           bless $self, $class;
34              
35 0           my $logger = $BeamerReveal::Log::logger;
36            
37 0           $self->{videos} = [];
38 0           $self->{audios} = [];
39 0           $self->{images} = [];
40 0           $self->{iframes} = [];
41 0           $self->{animations} = [];
42 0           $self->{stills} = [];
43 0           $self->{hasnotes} = 0;
44 0           $self->{mtoracle} = MIME::Types->new();
45              
46 0           ++$lineCtr;
47 0           for ( my $i = 0; $i < @$lines; ++$i ) {
48 0 0         ( $lines->[$i] =~ /^-(?\w+):(?.*)$/ )
49             or $logger->fatal( "Error: syntax incorrect in rvl file on line $lineCtr '$lines->[$i]'\n" );
50 0 0         if ( $+{command} eq 'parameters' ) {
    0          
    0          
    0          
    0          
    0          
    0          
    0          
51 0           $self->{parameters} = BeamerReveal::Object::readParameterLine( $+{payload} );
52             # The correctness of this relies on the pages appearing in order in the .rvl file
53             # however, the rawpage counter of LaTeX is not all cases a true arabic counter and
54             # therefore not reliable
55 0           $self->{parameters}->{rawpage} = $rawpage;
56 0           $maxRawPage = $rawpage;
57             } elsif ( $+{command} eq 'video' ) {
58 0           push @{$self->{videos}}, BeamerReveal::Object::readParameterLine( $+{payload} );
  0            
59             } elsif ( $+{command} eq 'audio' ) {
60 0           push @{$self->{audios}}, BeamerReveal::Object::readParameterLine( $+{payload} );
  0            
61             } elsif ( $+{command} eq 'image' ) {
62 0           push @{$self->{images}}, BeamerReveal::Object::readParameterLine( $+{payload} );
  0            
63             } elsif ( $+{command} eq 'iframe' ) {
64 0           push @{$self->{iframes}}, BeamerReveal::Object::readParameterLine( $+{payload} );
  0            
65             } elsif ( $+{command} eq 'animation' ) {
66 0           push @{$self->{animations}}, BeamerReveal::Object::readParameterLine( $+{payload} );
  0            
67 0           $self->{animations}->[-1]->{tex} = $lines->[++$i];
68             } elsif ( $+{command} eq 'still' ) {
69 0           push @{$self->{stills}}, BeamerReveal::Object::readParameterLine( $+{payload} );
  0            
70 0           $self->{stills}->[-1]->{tex} = $lines->[++$i];
71             } elsif ( $+{command} eq 'hasnote' ) {
72 0           $self->{hasnotes} = 1;
73             } else {
74 0           $logger->fatal( "Error: unknown BeamerFrame data on line @{[ $lineCtr + $i ]} '$lines->[$i]'\n" );
  0            
75             }
76             }
77 0 0         ++$maxNotePage if $self->{hasnotes};
78            
79 0 0         $embeddedID = 0 unless defined( $embeddedID );
80            
81 0           return $self;
82             }
83              
84              
85              
86             sub extractGenerationContent {
87 0     0 1   my $self = shift;
88 0           my ( $i, $mediaManager, $presentation ) = @_;
89              
90 0           my $logger = $BeamerReveal::Log::logger;
91 0           $logger->log( 2, "- extracting generation content from slide $i" );
92              
93 0           my $generatedContent = {
94             animations => [],
95             stills => [],
96             };
97            
98             #########################
99             # process all animations
100 0           foreach my $animation (@{$self->{animations}}) {
  0            
101 0           $logger->log( 4, "- extracting animation" );
102            
103             # 1. Generate the animation
104 0           push @{$generatedContent->{animations}}, $mediaManager->animationRegisterInStore( $animation );
  0            
105             }
106              
107             #####################
108             # process all stills
109 0           foreach my $still (@{$self->{stills}}) {
  0            
110 0           $logger->log( 4, "- adding still" );
111              
112             # 1. Generate the animation
113 0           push @{$generatedContent->{stills}}, $mediaManager->stillRegisterInStore( $still );
  0            
114             }
115              
116 0           return $generatedContent;
117             }
118              
119              
120              
121              
122             sub makeSlide {
123 0     0 1   my $self = shift;
124 0           my ( $i, $mediaManager, $presentation, $generatedContent ) = @_;
125              
126 0           my $logger = $BeamerReveal::Log::logger;
127 0           $logger->log( 2, "- making slide $i" );
128            
129 0           my $templateStore = BeamerReveal::TemplateStore->new();
130 0           my $content = '';
131              
132             my $slideEmbed =
133 0 0         exists $self->{parameters}->{embed} or exists $presentation->{parameters}->{embed};
134            
135             ###############################################
136             # process all video material / can be embedded
137 0           foreach my $video (@{$self->{videos}}) {
  0            
138             my %commonStamps = ( X => _topercent( $video->{x} ),
139             Y => _topercent( $video->{y} ),
140             W => _topercent( $video->{width} ),
141             H => _topercent( $video->{height} ),
142             VIDEOID => 'embedded-id-' . $embeddedID++,
143             VIDEOEMBEDDEDB64 => $videoContent,
144             MIMETYPE => $mimeType,
145             FIT => $video->{fit},
146             AUTOPLAY => exists $video->{autoplay} ? 'data-autoplay' : '',
147             CONTROLS => exists $video->{controls} ? 'controls' : '',
148             MUTED => exists $video->{muted} ? 'muted' : '',
149 0 0         LOOP => exists $video->{loop} ? 'loop' : '',
    0          
    0          
    0          
150             );
151 0           my $vStamps;
152             my $vTemplate;
153 0 0 0       if ( exists $video->{embed} or $slideEmbed ) {
154 0           $logger->log( 4, "- adding embedded video" );
155 0           $vTemplate = $templateStore->fetch( 'html', 'video-embedded.html' );
156             my ( $mimeType ) = $mediaManager->videoFromStore( $video->{file},
157 0           to_embed => 1 );
158 0           $vStamps = { %commonStamps,
159             VIDEOID => 'embedded-id-' . $embeddedID++,
160             VIDEOEMBEDDEDB64 => $videoContent,
161             MIMETYPE => $mimeType,
162             };
163             } else {
164 0           $logger->log( 4, "- adding video" );
165 0           $vTemplate = $templateStore->fetch( 'html', 'video.html' );
166 0           my ( $mimeType, $videoFile ) = $mediaManager->videoFromStore( $video->{file} );
167 0           $vStamps = { %commonStamps,
168             VIDEO => $videoFile
169             };
170             }
171 0           $content .= BeamerReveal::TemplateStore::stampTemplate( $vTemplate, $vStamps);
172             }
173              
174             ###############################################
175             # process all audio material / can be embedded
176 0           foreach my $audio (@{$self->{audios}}) {
  0            
177             my %commonStamps = ( X => _topercent( $audio->{x} ),
178             Y => _topercent( $audio->{y} ),
179             W => _topercent( $audio->{width} ),
180             H => _topercent( $audio->{height} ),
181             FIT => $audio->{fit},
182             AUTOPLAY => exists $audio->{autoplay} ? 'data-autoplay' : '',
183             CONTROLS => exists $audio->{controls} ? 'controls' : '',
184             MUTED => exists $audio->{muted} ? 'muted' : '',
185 0 0         LOOP => exists $audio->{loop} ? 'loop' : '' );
    0          
    0          
    0          
186 0           my $aStamps;
187             my $aTemplate;
188 0 0 0       if ( exists $audio->{embed} or $slideEmbed ) {
189 0           $logger->log( 4, "- adding embedded audio" );
190 0           $aTemplate = $templateStore->fetch( 'html', 'audio-embedded.html' );
191             my ( $mimeType, $audioContent ) = $mediaManager->audioFromStore( $audio->{file},
192 0           to_embed => 1 );
193 0           $aStamps = { %commonStamps,
194             AUDIOID => 'embedded-id-' . $embeddedID++,
195             AUDIOEMBEDDEDB64 => $audioContent,
196             MIMETYPE => $mimeType,
197             };
198             } else {
199 0           $logger->log( 4, "- adding audio" );
200 0           $aTemplate = $templateStore->fetch( 'html', 'audio.html' );
201 0           my ( $mimeType, $audioFile ) = $mediaManager->audioFromStore( $audio->{file} );
202 0           $aStamps = { %commonStamps,
203             AUDIO => $audioFile,
204             };
205             }
206 0           $content .= BeamerReveal::TemplateStore::stampTemplate( $aTemplate, $aStamps );
207             }
208              
209             ###############################################
210             # process all image material / can be embedded
211 0           foreach my $image (@{$self->{images}}) {
  0            
212             my %commonStamps = ( X => _topercent( $image->{x} ),
213             Y => _topercent( $image->{y} ),
214             W => _topercent( $image->{width} ),
215             H => _topercent( $image->{height} ),
216             IMAGEID => 'embedded-id-' . $embeddedID++,
217             IMAGEEMBEDDEDB64 => $imageContent,
218             MIMETYPE => $mimeType,
219             FIT => $image->{fit}
220 0           );
221 0           my $iStamps;
222             my $iTemplate;
223 0 0 0       if ( exists $image->{embed} or $slideEmbed ) {
224 0           $logger->log( 4, "- adding embedded image" );
225 0           $iTemplate = $templateStore->fetch( 'html', 'image-embedded.html' );
226             my( $mimeType, $imageContent ) = $mediaManager->imageFromStore( $image->{file},
227 0           to_embed => 1 );
228 0           $iStamps = { %commonStamps,
229             IMAGEID => 'embedded-id-' . $embeddedID++,
230             IMAGEEMBEDDEDB64 => $imageContent,
231             MIMETYPE => $mimeType,
232             };
233             } else {
234 0           $logger->log( 4, "- adding image" );
235 0           $iTemplate = $templateStore->fetch( 'html', 'image.html' );
236 0           my( $mimeType, $imageFile ) = $mediaManager->imageFromStore( $image->{file} );
237 0           $iStamps = { %commonStamps,
238             IMAGE => $imageFile };
239             }
240 0           $content .= BeamerReveal::TemplateStore::stampTemplate( $iTemplate,
241             $iStamps );
242             }
243              
244             ################################################
245             # process all iframe material / can be embedded
246 0           foreach my $iframe (@{$self->{iframes}}) {
  0            
247             my %commonStamps = ( X => _topercent( $iframe->{x} ),
248             Y => _topercent( $iframe->{y} ),
249             W => _topercent( $iframe->{width} ),
250             H => _topercent( $iframe->{height} ),
251             FIT => $iframe->{fit}
252 0           );
253 0           my $iStamps;
254             my $iTemplate;
255 0 0 0       if ( exists $iframe->{embed} or exists $presentation->{parameters}->{embed} ) {
256 0           $logger->log( 4, "- adding embedded iframe" );
257 0           $iTemplate = $templateStore->fetch( 'html', 'iframe-embedded.html' );
258             my ( $mimeType, $iframeContent ) = $mediaManager->iframeFromStore( $iframe->{file},
259 0           to_embed => 1 );
260 0           $iStamps = { %commonStamps,
261             IFRAMEID => 'embedded-id-' . $embeddedID++,
262             IFRAMEEMBEDDEDB64 => $iframeContent,
263             };
264             } else {
265 0           $logger->log( 4, "- adding iframe" );
266 0           $iTemplate = $templateStore->fetch( 'html', 'iframe.html' );
267 0           my ( undef, $iframeFile ) = $mediaManager->iframeFromStore( $iframe->{file} );
268 0           $iStamps = { %commonStamps,
269             IFRAME => $iframeFile,
270             };
271             }
272 0           $content .= BeamerReveal::TemplateStore::stampTemplate( $iTemplate,
273             $iStamps );
274             }
275              
276             ###########################################
277             # process all animations / can be embedded
278 0           my $aCounter = 0;
279 0           foreach my $animation (@{$self->{animations}}) {
  0            
280             my %commonStamps = (X => _topercent( $animation->{x} ),
281             Y => _topercent( $animation->{y} ),
282             W => _topercent( $animation->{width} ),
283             H => _topercent( $animation->{height} ),
284             AUTOPLAY => exists $animation->{autoplay} ? 'data-autoplay' : '',
285             CONTROLS => exists $animation->{controls} ? 'controls' : '',
286             LOOP => exists $animation->{loop} ? 'loop' : '',
287             FIT => $animation->{fit},
288 0 0         );
    0          
    0          
289            
290 0           my $aTemplate;
291             my $aStamps;
292 0 0 0       if ( exists $animation->{embed} or $slideEmbed ) {
293 0           $logger->log( 4, "- adding embedded animation" );
294 0           $aTemplate = $templateStore->fetch( 'html', 'animation-embedded.html' );
295             my ( $mimeType, $videoContent ) =
296 0           $mediaManager->animationFromStore( $generatedContent->{animations}->[$aCounter++],
297             to_embed => 1 );
298             $aStamps =
299             {
300             %commonStamps,
301             ANIMATIONID => 'embedded-id-' . $embeddedID++,
302             ANIMATIONEMBEDDEDB64 => $videoContent,
303             MIMETYPE => $mimeType,
304             FIT => $image->{fit}
305 0           };
306             }
307             else {
308 0           $logger->log( 4, "- adding animation: " . $generatedContent->{animations}->[$aCounter] );
309 0           $aTemplate = $templateStore->fetch( 'html', 'animation.html' );
310             my ( $undef, $file ) =
311 0           $mediaManager->animationFromStore( $generatedContent->{animations}->[$aCounter++] );
312            
313 0           $aStamps =
314             {
315             %commonStamps,
316             ANIMATION => $file,
317             };
318             }
319 0           $content .= BeamerReveal::TemplateStore::stampTemplate( $aTemplate,
320             $aStamps );
321             }
322              
323             #######################################
324             # process all stills / can be embedded
325 0           my $sCounter = 0;
326 0           foreach my $still (@{$self->{stills}}) {
  0            
327             my %commonStamps = ( X => _topercent( $still->{x} ),
328             Y => _topercent( $still->{y} ),
329             W => _topercent( $still->{width} ),
330             H => _topercent( $still->{height} ),
331             FIT => $still->{fit},
332 0           );
333              
334 0           my $sTemplate;
335             my $sStamps;
336 0 0 0       if ( exists $still->{embed} or $slideEmbed ) {
337 0           $logger->log( 4, "- adding embedded still" );
338 0           $sTemplate = $templateStore->fetch( 'html', 'still-embedded.html' );
339             my ( $mimeType, $videoContent ) =
340 0           $mediaManager->stillFromStore( $generatedContent->{stills}->[$sCounter++],
341             to_embed => 1 );
342             $sStamps =
343             {
344             %commonStamps,
345             STILLID => 'embedded-id-' . $embeddedID++,
346             STILLEMBEDDEDB64 => $videoContent,
347             MIMETYPE => $mimeType,
348             FIT => $image->{fit}
349 0           };
350             }
351             else {
352 0           $logger->log( 4, "- adding still: " . $generatedContent->{stills}->[$sCounter] );
353 0           $sTemplate = $templateStore->fetch( 'html', 'still.html' );
354             my ( undef, $file ) =
355 0           $mediaManager->stillFromStore( $generatedContent->{stills}->[$sCounter++] );
356              
357 0           $sStamps =
358             {
359             %commonStamps,
360             STILL => $file,
361             };
362             }
363 0           $content .= BeamerReveal::TemplateStore::stampTemplate( $sTemplate,
364             $sStamps );
365             }
366              
367             ###########################
368             # process the frame itself
369              
370             # title
371 0           my $title = _modernize( $self->{parameters}->{title} );
372 0 0         if ( $title =~ /\\/ ) {
373 0           $logger->fatal( "Error: the title of slide $i contains TeX-like code (observe below between <<< >>>); provide a clean version using \\frametitle[clean-version]{TeX-like code}\n" .
374             "<<< $self->{parameters}->{title} >>>" );
375             } else {
376 0           $self->{parameters}->{title} = $title;
377             }
378             ;
379              
380             # menu entries
381 0           my $menuTitle;
382 0 0         if ( exists $self->{parameters}->{toc} ) {
383 0 0         if ( $self->{parameters}->{toc} eq 'titlepage' ) {
    0          
    0          
384 0           $menuTitle = "%s";
385             } elsif ( $self->{parameters}->{toc} eq 'section' ) {
386 0           $menuTitle = "• %s";
387             } elsif ( $self->{parameters}->{toc} eq 'subsection' ) {
388 0           $menuTitle = "∘ %s";
389             } else {
390 0           $logger->fatal( "Error: invalid toc parameter in rvl file" );
391             }
392             } else {
393 0           $menuTitle = "• %s";
394             }
395             $menuTitle = sprintf( $menuTitle,
396 0           $self->{parameters}->{title} );
397              
398             # autoSlide
399 0           my $autoSlide = '';
400 0 0         if( exists $self->{parameters}->{autoslide} ) {
401 0           $autoSlide = 'data-autoslide="' . $self->{parameters}->{autoslide} . '"';
402             }
403            
404             # notes
405 0           my $notes = ' '; # the space is important as default, if you remove it, the notes will be sticky!
406            
407 0 0         if ( $self->{hasnotes} ) {
408 0 0         if ( $slideEmbed ) {
409 0           my $nTemplate = $templateStore->fetch( 'html', 'note-embedded.html' );
410            
411             # remember: the hasnote entry holds the note number
412             my ( $mimeType, $notesImage ) = $mediaManager->noteFromStore( $self->{hasnotes},
413 0           to_embed => 1 );
414            
415 0           my $nStamps =
416             {
417             NOTEID => 'embedded-id-' . $embeddedID++,
418             NOTEEMBEDDEDB64 => $notesImage,
419             MIMETYPE => $mimeType,
420             };
421            
422 0           $notes = BeamerReveal::TemplateStore::stampTemplate( $nTemplate, $nStamps );
423             }
424             else {
425 0           my $nTemplate = $templateStore->fetch( 'html', 'note.html' );
426            
427             # remember: the hasnote entry holds the note number
428 0           my ( undef, $notesImage ) = $mediaManager->noteFromStore( $self->{hasnotes} );
429            
430 0           my $nStamps =
431             {
432             NOTESIMAGE => $notesImage
433             };
434            
435 0           $notes = BeamerReveal::TemplateStore::stampTemplate( $nTemplate, $nStamps );
436             }
437             }
438              
439             # generate slide itself
440 0 0         if ( $slideEmbed ) {
441 0           my $fTemplate = $templateStore->fetch( 'html', 'beamerframe-embedded.html' );
442            
443             my ( $mimeType, $slideImage ) = $mediaManager->slideFromStore( $self->{parameters}->{rawpage},
444 0           to_embed => 1 );
445             my $fStamps =
446             {
447             DATAMENUTITLE => $menuTitle,
448             SLIDEID => 'embedded-id-' . $embeddedID++,
449             SLIDEEMBEDDEDB64 => $slideImage,
450             MIMETYPE => $mimeType,
451             SLIDECONTENT => $content,
452 0   0       TRANSITION => $self->{parameters}->{transition} || 'fade',
453             AUTOSLIDE => $autoSlide,
454             NOTESIMAGE => $notes
455             };
456            
457 0           return BeamerReveal::TemplateStore::stampTemplate( $fTemplate, $fStamps );
458            
459             } else {
460 0           my $fTemplate = $templateStore->fetch( 'html', 'beamerframe.html' );
461            
462 0           my ( undef, $slideImage ) = $mediaManager->slideFromStore( $self->{parameters}->{rawpage} );
463            
464             my $fStamps =
465             {
466             DATAMENUTITLE => $menuTitle,
467             SLIDEIMAGE => $slideImage,
468             SLIDECONTENT => $content,
469 0   0       TRANSITION => $self->{parameters}->{transition} || 'fade',
470             AUTOSLIDE => $autoSlide,
471             NOTESIMAGE => $notes
472             };
473            
474 0           return BeamerReveal::TemplateStore::stampTemplate( $fTemplate, $fStamps );
475             }
476             }
477              
478             sub _topercent {
479 0 0   0     confess() unless defined $_[0];
480 0           return sprintf( "%.2f%%", $_[0] * 100 );
481             }
482              
483             sub _modernize {
484 0     0     my $string = shift;
485 0 0         defined( $string ) or die();
486 0           my $dictionary = {
487             qr/\\`\{?a\}?/ => 'à',
488             qr/\\'\{?a\}?/ => 'á',
489             qr/\\"\{?a\}?/ => 'ä',
490             qr/\\\^\{?a\}?/ => 'â',
491             qr/\\~\{?a\}?/ => 'ã',
492             qr/\\=\{?a\}?/ => 'ā',
493             qr/\\\.\{?a\}?/ => 'ȧ',
494             qr/\\u\{?a\}?/ => 'ă',
495             qr/\\v\{?a\}?/ => 'ǎ',
496             qr/\\c\{?a\}?/ => 'ą',
497             qr/\\r\{?a\}?/ => 'å',
498             qr/\\`\{?e\}?/ => 'è',
499             qr/\\'\{?e\}?/ => 'é',
500             qr/\\"\{?e\}?/ => 'ë',
501             qr/\\\^\{?e\}?/ => 'ê',
502             qr/\\~\{?e\}?/ => 'ẽ',
503             qr/\\=\{?e\}?/ => 'ē',
504             qr/\\\.\{?e\}?/ => 'ė',
505             qr/\\u\{?e\}?/ => 'ĕ',
506             qr/\\v\{?e\}?/ => 'ě',
507             qr/\\c\{?e\}?/ => 'ę',
508             qr/\\`\{?\\?i(?:\{\})?\}?/ => 'ì',
509             qr/\\'\{?\\?i(?:\{\})?\}?/ => 'í',
510             qr/\\"\{?\\?i(?:\{\})?\}?/ => 'ï',
511             qr/\\\^\{?\\?i(?:\{\})?\}?/ => 'î',
512             qr/\\~\{?\\?i(?:\{\})?\}?/ => 'ĩ',
513             qr/\\=\{?\\?i(?:\{\})?\}?/ => 'ī',
514             qr/\\u\{?\\?i(?:\{\})?\}?/ => 'ĭ',
515             qr/\\v\{?\\?i(?:\{\})?\}?/ => 'ǐ',
516             qr/\\c\{?\\?i(?:\{\})?\}?/ => 'į',
517             qr/\\`\{?o\}?/ => 'ò',
518             qr/\\'\{?o\}?/ => 'ó',
519             qr/\\"\{?o\}?/ => 'ö',
520             qr/\\\^\{?o\}?/ => 'ô',
521             qr/\\~\{?o\}?/ => 'õ',
522             qr/\\=\{?o\}?/ => 'ō',
523             qr/\\\.\{?o\}?/ => 'ȯ',
524             qr/\\u\{?o\}?/ => 'ŏ',
525             qr/\\v\{?o\}?/ => 'ǒ',
526             qr/\\c\{?o\}?/ => 'ǫ',
527             qr/\\`\{?u\}?/ => 'ù',
528             qr/\\'\{?u\}?/ => 'ú',
529             qr/\\"\{?u\}?/ => 'ü',
530             qr/\\\^\{?u\}?/ => 'û',
531             qr/\\~\{?u\}?/ => 'ũ',
532             qr/\\=\{?u\}?/ => 'ū',
533             qr/\\u\{?u\}?/ => 'ŭ',
534             qr/\\v\{?u\}?/ => 'ǔ',
535             qr/\\c\{?u\}?/ => 'ų',
536             qr/\\r\{?u\}?/ => 'ů',
537             qr/\\c\{?c\}?/ => 'ç',
538             qr/\\~\{?n\}?/ => 'ñ',
539             qr/\\oe/ => 'œ',
540             qr/\\OE/ => 'Œ',
541             qr/\\&/ => '&',
542             };
543 0           while ( my ( $regexp, $rep ) = each ( %$dictionary ) ) {
544 0           $string =~ s/$regexp/$rep/g;
545             }
546 0           return $string;
547             }
548              
549             1;
550              
551             __END__