File Coverage

blib/lib/ODF/lpOD/Document.pm
Criterion Covered Total %
statement 100 1093 9.1
branch 0 390 0.0
condition 0 147 0.0
subroutine 32 170 18.8
pod 9 58 15.5
total 141 1858 7.5


line stmt bran cond sub pod time code
1             #=============================================================================
2             #
3             # Copyright (c) 2010 Ars Aperta, Itaapy, Pierlis, Talend.
4             # Copyright (c) 2014 Jean-Marie GouarnĂ©.
5             # Author: Jean-Marie GouarnĂ©
6             #
7             #=============================================================================
8 2     2   56 use 5.010_001;
  2         10  
  2         79  
9 2     2   11 use strict;
  2         6  
  2         87  
10 2     2   11 use experimental 'smartmatch';
  2         4  
  2         15  
11             #=============================================================================
12             # The ODF Document class definition
13             #=============================================================================
14             package ODF::lpOD::Document;
15             our $VERSION = '1.013';
16 2     2   191 use constant PACKAGE_DATE => '2014-04-30T08:27:07';
  2         11  
  2         128  
17 2     2   11 use ODF::lpOD::Common;
  2         3  
  2         1484  
18             #-----------------------------------------------------------------------------
19              
20             BEGIN {
21 2     2   8 *forget = *DESTROY;
22 2         4 *container = *get_container;
23 2         4 *body = *get_body;
24 2         3 *add_part = *add_file;
25 2         28255 *register_style = *insert_style;
26             }
27              
28             #--- specific constructors ---------------------------------------------------
29              
30             sub get_from_uri
31             {
32 0     0 0   my $resource = shift;
33 0 0         unless ($resource)
34             {
35 0           alert "Missing source"; return FALSE;
  0            
36             }
37 0           return ODF::lpOD::Document->new(@_, uri => $resource);
38             }
39              
40             sub create_from_template
41             {
42 0     0 0   my $resource = shift;
43 0 0         unless ($resource)
44             {
45 0           alert "Missing template"; return FALSE;
  0            
46             }
47              
48 0           return ODF::lpOD::Document->new(@_, template => $resource);
49             }
50              
51             sub get
52             {
53 0     0 0   my $caller = shift;
54 0           return ODF::lpOD::Document->new(uri => shift, @_);
55             }
56              
57             sub create
58             {
59 0     0 0   my $caller = shift;
60 0           return ODF::lpOD::Document->new(type => shift, @_);
61             }
62              
63             sub _create
64             {
65 0     0     my $type = shift;
66 0 0         unless ($type)
67             {
68 0           alert "Missing document type"; return FALSE;
  0            
69             }
70 0           return ODF::lpOD::Document->new(@_, type => $type);
71             }
72              
73             #--- generic constructor & destructor ----------------------------------------
74              
75             sub new
76             {
77 0     0 0   my $class = shift;
78 0           my $self = { @_ };
79 0           bless $self, $class;
80 0   0       $self->{uri} //= $self->{source}; delete $self->{source};
  0            
81 0 0         if ($self->{type}) # new document, type provided
    0          
    0          
82             {
83 0 0         my $template = ODF::lpOD::Common::template($self->{type})
84             or return undef;
85 0 0         $self->{container} = ODF::lpOD::Container->new
86             (template => $template) or return undef;
87 0           my $meta = $self->get_part(META);
88 0 0         if ($meta)
89             {
90 0           my $d = iso_date;
91 0           $meta->set_creation_date($d);
92 0           $meta->set_modification_date($d);
93 0           $meta->set_editing_duration('PT00H00M00S');
94 0           $meta->set_generator("ODF::lpOD $ODF::lpOD::VERSION");
95 0           $meta->set_initial_creator();
96 0           $meta->set_creator();
97 0           $meta->set_editing_cycles(1);
98             }
99             }
100             elsif ($self->{template})
101             {
102 0 0         $self->{container} = ODF::lpOD::Container->new
103             (template => $self->{template})
104             or return undef;
105             }
106             elsif ($self->{uri}) # existing document, path provided
107             {
108 0 0         $self->{container} = ODF::lpOD::Container->new
109             (uri => $self->{uri}) or return undef;
110             }
111 0   0       $self->{pretty} //= ($self->{indent} // lpod->debug);
      0        
112 0           return $self;
113             }
114              
115             sub DESTROY
116             {
117 0     0     my $self = shift;
118 0           foreach my $part_name ($self->loaded_xmlparts)
119             {
120 0 0 0       next unless $part_name && $self->{$part_name};
121 0           $self->{$part_name}->forget;
122 0           delete $self->{$part_name};
123             }
124 0           delete $self->{xmlparts};
125 0 0         $self->{container} && $self->{container}->DESTROY;
126 0           $self = {};
127             }
128              
129             #--- XML part detection ------------------------------------------------------
130              
131             sub is_xmlpart
132             {
133 0     0 0   my $name = shift;
134 0 0         return ODF::lpOD::XMLPart::class_of($name) ? TRUE : FALSE;
135             }
136              
137             #--- document part accessors -------------------------------------------------
138              
139             sub get_container
140             {
141 0     0 0   my $self = shift;
142 0           my %opt = @_;
143 0           my $container = $self->{container};
144 0 0 0       unless ($container || is_false($opt{warning}))
145             {
146 0           alert "No available container";
147             }
148 0           return $container;
149             }
150              
151             sub contains
152             {
153 0     0 0   my $self = shift;
154 0 0         my $container = $self->get_container or return undef;
155 0           return $container->contains(@_);
156             }
157              
158             sub parts
159             {
160 0     0 0   my $self = shift;
161 0 0         my $container = $self->get_container or return undef;
162 0           return $container->parts;
163             }
164              
165             sub get_stored_part
166             {
167 0     0 0   my $self = shift;
168 0 0         my $container = $self->get_container or return undef;
169 0           return $container->get_stored_part(@_);
170             }
171              
172             sub get_xmlpart
173             {
174 0     0 0   my $self = shift;
175 0 0         my $container = $self->get_container(warning => TRUE)
176             or return FALSE;
177              
178 0 0         my $part_name = shift or return FALSE;
179              
180 0 0         unless ($self->{$part_name})
181             {
182 0           my $xmlpart = ODF::lpOD::XMLPart->new
183             (
184             container => $container,
185             part => $part_name,
186             pretty => $self->{pretty},
187             @_
188             );
189 0 0         unless ($xmlpart)
190             {
191 0           alert "Unavailable part"; return FALSE;
  0            
192             }
193 0           $self->{$part_name} = $xmlpart;
194 0           $self->{$part_name}->{document} = $self;
195 0           push @{$self->{xmlparts}}, $part_name;
  0            
196             }
197 0           return $self->{$part_name};
198             }
199              
200             sub loaded_xmlparts
201             {
202 0     0 0   my $self = shift;
203 0 0         return undef unless $self->{xmlparts};
204 0 0         return wantarray ? @{$self->{xmlparts}} : $self->{xmlparts};
  0            
205             }
206              
207             sub get_body
208             {
209 0     0 0   my $self = shift;
210 0           return $self->content->get_body(@_);
211             }
212              
213             sub get_part
214             {
215 0     0 1   my $self = shift;
216 0 0         my $container = $self->get_container(warning => TRUE)
217             or return FALSE;
218 0           my $part_name = shift;
219 0 0         if (is_xmlpart($part_name))
220             {
221 0           return $self->get_xmlpart($part_name, @_);
222             }
223             else
224             {
225 0           return $container->get_part($part_name, @_);
226             }
227             }
228              
229             sub content
230             {
231 0     0 0   my $self = shift;
232 0           return $self->get_xmlpart(CONTENT, @_);
233             }
234              
235             sub meta
236             {
237 0     0 0   my $self = shift;
238 0           return $self->get_xmlpart(META, @_);
239             }
240              
241             sub styles
242             {
243 0     0 0   my $self = shift;
244 0           return $self->get_xmlpart(STYLES, @_);
245             }
246              
247             sub manifest
248             {
249 0     0 0   my $self = shift;
250 0           return $self->get_xmlpart(MANIFEST, @_);
251             }
252              
253             sub get_parts
254             {
255 0     0 1   my $self = shift;
256 0 0         my $container = $self->get_container(warning => TRUE)
257             or return FALSE;
258 0           return $container->get_parts;
259             }
260              
261             sub set_part
262             {
263 0     0 1   my $self = shift;
264 0 0         unless ($self->{container})
265             {
266 0           alert "No available container";
267 0           return FALSE;
268             }
269 0           return $self->{container}->set_part(@_);
270             }
271              
272             sub del_part
273             {
274 0     0 1   my $self = shift;
275 0 0         unless ($self->{container})
276             {
277 0           alert "No available container";
278 0           return FALSE;
279             }
280 0           return $self->{container}->del_part(@_);
281             }
282              
283             sub add_file
284             {
285 0     0 1   my $self = shift;
286 0 0         unless ($self->{container})
287             {
288 0           alert "No available container";
289 0           return FALSE;
290             }
291 0           my $source = shift;
292 0           my %opt = @_;
293 0   0       my $path = $opt{path} || $opt{part};
294 0           delete @opt{qw(path part)};
295 0 0         unless ($path)
296             {
297 0 0 0       if ($opt{type} && $opt{type} =~ /^image/)
298             {
299 0           my $filename = file_parse($source);
300 0           $path = 'Pictures/' . $filename;
301             }
302             }
303 0           $path = $self->{container}->add_file($source, $path, %opt);
304 0 0         if ($path)
305             {
306 0           my $manifest = $self->get_part(MANIFEST);
307 0 0         if ($manifest)
308             {
309 0   0       my $type = $opt{type} || file_type($source);
310 0           $manifest->set_entry($path, type => $type);
311             }
312             }
313 0           return $path;
314             }
315              
316             sub add_image_file
317             {
318 0     0 1   my $self = shift;
319 0 0         my $source = shift or return undef;
320 0 0         unless ($self->{container})
321             {
322 0           alert "No available ODF container";
323 0           return FALSE;
324             }
325 0           my %opt = @_;
326 0           my ($filename, $sourcepath, $suffix) = file_parse($source);
327 0 0         unless ($filename)
328             {
329 0           alert "No valid file name in $source";
330 0           return FALSE;
331             }
332 0   0       my $type = $opt{type} || file_type($source) || "image/$suffix";
333 0           my $path = 'Pictures/' . $filename;
334 0   0       $suffix //= 'unknown';
335              
336 0           my ($link, $size);
337 0 0         if (wantarray)
338             {
339 0           my $buffer = load_file($source, ':raw');
340 0 0         unless ($buffer)
341             {
342 0           alert "Resource $source not available";
343 0           return undef;
344             }
345 0           $size = image_size(\$buffer);
346 0           $link = $self->add_file
347             (
348             $buffer,
349             string => TRUE,
350             path => $path,
351             type => $type,
352             @_
353             );
354 0           return ($link, $size);
355             }
356             else
357             {
358 0           $link = $self->add_file
359             (
360             $source,
361             string => FALSE,
362             path => $path,
363             type => $type,
364             @_
365             );
366 0           return $link;
367             }
368             }
369              
370             sub get_mimetype
371             {
372 0     0 1   my $self = shift;
373 0 0         unless ($self->{mimetype})
374             {
375 0           $self->{mimetype} = $self->{container}->get_mimetype;
376             }
377 0           return $self->{mimetype};
378             }
379              
380             sub set_mimetype
381             {
382 0     0 1   my $self = shift;
383 0 0         unless ($self->{container})
384             {
385 0           alert "No available container";
386 0           return FALSE;
387             }
388 0           return $self->{container}->set_mimetype(shift);
389             }
390              
391             sub get_type
392             {
393 0     0 0   my $self = shift;
394 0 0         my $mt = $self->get_mimetype or return undef;
395 0           $mt =~ s/.*opendocument\.//;
396 0           return $mt;
397             }
398              
399             sub save
400             {
401 0     0 1   my $self = shift;
402 0 0         my $container = $self->get_container(warning => TRUE)
403             or return FALSE;
404 0           my %opt = @_;
405 0   0       $opt{pretty} //= ($opt{indent} // lpod->debug);
      0        
406 0           my $pretty = $opt{pretty};
407 0           delete @opt{qw(pretty indent)};
408 0           foreach my $part_name ($self->loaded_xmlparts)
409             {
410 0 0         next unless $part_name;
411 0 0         my $part = $self->{$part_name} or next;
412 0 0         $part->store(pretty => $pretty) if is_true($part->{update});
413             }
414 0           return $container->save(%opt);
415             }
416              
417             #--- required insertion context retrieval ------------------------------------
418              
419             sub get_required_context
420             {
421 0     0 0   my $self = shift;
422 0           my $elt = shift;
423 0           my ($part_name, $path) = $elt->context_path;
424 0 0         if ($part_name)
425             {
426 0   0       $path ||= '/';
427 0           return $self->get_element($part_name, $path);
428             }
429 0           return undef;
430             }
431              
432             #--- context import & replacement --------------------------------------------
433              
434             sub substitute_context
435             {
436 0     0 0   my $self = shift;
437 0           my $doc = shift;
438 0           my $part = shift;
439 0           my $path = shift;
440 0 0         my $origin = $doc->get_element($part, $path)
441             or return undef;
442 0 0         my $destination = $self->get_element($part, $path)
443             or return undef;
444 0           return $destination->substitute_children($origin);
445             }
446              
447             #--- direct element retrieval ------------------------------------------------
448              
449             sub get_element
450             {
451 0     0 0   my $self = shift;
452 0           my $part_name = shift;
453 0           my $part = $self->get_part($part_name);
454 0 0         unless ($part)
455             {
456 0           alert "Unknown or not available document part";
457 0           return undef;
458             }
459 0           return $part->get_element(@_);
460             }
461              
462             sub get_elements
463             {
464 0     0 0   my $self = shift;
465 0           my $part_name = shift;
466 0           my $part = $self->get_part($part_name);
467 0 0         unless ($part)
468             {
469 0           alert "Unknown or not available document part";
470 0           return undef;
471             }
472 0           return $part->get_elements(@_);
473             }
474              
475             sub get_headings
476             {
477 0     0 0   my $self = shift;
478 0           return $self->get_part(CONTENT)->get_headings(@_);
479             }
480              
481             sub get_changes
482             {
483 0     0 0   my $self = shift;
484 0 0         my $part = $self->get_part(CONTENT) or return undef;
485 0           return $part->get_changes(@_);
486             }
487              
488             sub get_change
489             {
490 0     0 0   my $self = shift;
491 0 0         my $part = $self->get_part(CONTENT) or return undef;
492 0           return $part->get_change(@_);
493             }
494              
495             #--- style handling ----------------------------------------------------------
496              
497             sub get_default_style
498             {
499 0     0 0   my $self = shift;
500 0           my $family = shift;
501 0           $family =~ s/ /-/g;
502 0           my $xp = '//style:default-style[@style:family="' .
503             $family . '"]';
504 0           return $self->get_element(STYLES, $xp);
505             }
506              
507             sub get_outline_style
508             {
509 0     0 0   my $self = shift;
510 0           my $xp = '//text:outline-style';
511 0           return $self->get_element(STYLES, $xp);
512             }
513              
514             sub get_style
515             {
516 0     0 0   my $self = shift;
517 0           my $family = shift;
518 0 0         unless ($family)
519             {
520 0           alert "Missing style family"; return undef;
  0            
521             }
522 0           my $name = shift;
523 0           $family =~ s/ /-/g;
524 0 0         unless ($name)
525             {
526 0           given ($family)
527             {
528             when ('outline')
529 0           {
530 0           return $self->get_outline_style;
531             }
532             default
533 0           {
534 0           return $self->get_default_style($family);
535             }
536             }
537             }
538 0           my $style; my $xp;
539 0           my $f = $family; $f =~ s/[ _]/-/g;
  0            
540 0           given ($family)
541             {
542             when ('list')
543 0           {
544 0           $xp = '//text:list-style[@style:name="' .
545             $name . '"]';
546             }
547             when (/(master|page-layout)/)
548 0           {
549 0           $xp = '//style:' . $f . '[@style:name="' .
550             $name . '"]';
551             }
552             when ('data')
553 0           {
554 0           my $n = shift;
555 0           $xp = '//number:' . $name . '-style' .
556             '[@style:name="' . $n . '"]';
557             }
558             when ('gradient')
559 0           {
560 0           $xp = '//draw:gradient[@draw:name="' .
561             $name . '"]';
562             }
563             default
564 0           {
565 0           $xp = '//style:style[@style:name="' .
566             $name .
567             '"][@style:family="' .
568             $f .
569             '"]';
570             }
571             }
572             return
573 0   0       $self->get_element(STYLES, $xp)
574             //
575             $self->get_element(CONTENT, $xp);
576             }
577              
578             sub get_styles
579             {
580 0     0 0   my $self = shift;
581 0           my $family = shift;
582 0 0         unless ($family)
583             {
584 0           alert "Missing style family"; return undef;
  0            
585             }
586 0 0         if (ODF::lpOD::DataStyle->is_numeric_family($family))
587             {
588 0           return $self->get_data_styles($family, @_);
589             }
590 0           my $xp;
591 0           my $f = $family; $f =~ s/[ _]/-/g;
  0            
592 0           given ($family)
593             {
594             when('list')
595 0           {
596 0           $xp = '//text:list-style';
597             }
598             when (/(master|page layout)/)
599 0           {
600 0           $xp = '//style:' . $f;
601             }
602             when ('gradient')
603 0           {
604 0           $xp = '//draw:gradient';
605             }
606             default
607 0           {
608 0           $xp = '//style:style[@style:family="' . $f . '"]';
609             }
610             }
611              
612             return (
613 0           $self->get_elements(STYLES, $xp),
614             $self->get_elements(CONTENT, $xp)
615             );
616             }
617              
618             sub get_data_styles
619             {
620 0     0 0   my $self = shift;
621 0           my $family = shift;
622 0 0         my $filter = $family ?
623             'number:' . $family . '-style' :
624             qr'number:.*-style';
625 0           my @ns = ();
626 0           foreach my $part (STYLES, CONTENT)
627             {
628 0           my $r = $self->get_part($part)->get_root;
629             push @ns, $_->get_descendants($filter)
630 0           for $r->get_elements(qr'office:(automatic-|)styles');
631             }
632 0           return @ns;
633             }
634              
635             sub get_data_style
636             {
637 0     0 0   my $self = shift;
638 0           my ($family, $name) = @_;
639 0           my $xp = "//number:$family-style";
640 0           $xp .= '[@style:name="' . $name . '"]';
641 0   0       return $self->get_element(STYLES, $xp)
642             //
643             $self->get_element(CONTENT, $xp);
644             }
645              
646             sub check_stylename
647             {
648 0     0 0   my $self = shift;
649 0           my $style = shift;
650 0   0       my $name = shift || $style->get_name;
651 0           my $family = $style->get_family;
652 0 0 0       unless ($name && $family)
653             {
654 0           alert "Missing style name and/or family";
655 0           return FALSE;
656             }
657 0 0         if ($self->get_style($family, $name))
658             {
659 0           alert "Non unique style";
660 0           return FALSE;
661             }
662 0           return TRUE;
663             }
664              
665             sub select_style_context
666             {
667 0     0 0   my $self = shift;
668 0           my $style = shift;
669 0           my $context = $self->get_required_context($style);
670 0 0         return $context if $context;
671 0           my %opt = @_;
672 0           my $xp;
673 0 0         my $part_name = is_true($opt{default}) ? STYLES : $opt{part};
674 0 0 0       if (is_true($opt{default}) || defined_false($opt{automatic}))
675             {
676 0           $part_name = STYLES; delete $opt{automatic};
  0            
677 0           $xp = '//office:styles';
678             }
679             else
680             {
681 0           $part_name = $opt{part};
682 0 0         if (is_true($opt{automatic}))
683             {
684 0           $xp = '//office:automatic-styles';
685 0   0       $part_name ||= CONTENT;
686             }
687             else
688             {
689 0           given ($part_name)
690             {
691             when (undef)
692 0           {
693 0           $part_name = STYLES;
694 0 0         $xp = is_true($opt{automatic}) ?
695             '//office:automatic-styles' :
696             '//office:styles';
697             }
698             when (STYLES)
699 0           {
700 0 0         $xp = is_true($opt{automatic}) ?
701             '//office:automatic-styles' :
702             '//office:styles';
703             }
704             when (CONTENT)
705 0           {
706 0           $xp = '//office:automatic-styles';
707             }
708             }
709             }
710             }
711 0           $context = $self->get_element($part_name, $xp);
712 0 0         unless ($context)
713             {
714 0           alert "Wrong document structure; style insertion failure";
715 0           return undef;
716             }
717 0           return $context;
718             }
719              
720             sub insert_regular_style
721             {
722 0     0 0   my $self = shift;
723 0           my $style = shift;
724 0           my %opt = @_;
725 0 0         my $context = $self->select_style_context($style, %opt)
726             or return undef;
727 0 0         if (is_true($opt{default}))
728             {
729 0           $style->check_tag('style:default-style');
730 0           $style->set_name(undef);
731             }
732             else
733             {
734 0   0       my $name = $opt{name} || $style->get_name;
735 0 0         return undef unless $self->check_stylename($style, $name);
736 0           $style->check_tag($style->required_tag);
737 0           $style->set_name($name);
738             }
739 0           return $context->insert_element($style);
740             }
741              
742             sub insert_special_style
743             {
744 0     0 0   my $self = shift;
745 0           my $style = shift;
746 0           my %opt = @_;
747 0 0         my $context = $self->select_style_context($style, %opt)
748             or return undef;
749 0   0       my $name = $opt{name} || $style->get_name;
750 0 0         return undef unless $self->check_stylename($style, $name);
751 0           $style->check_tag($style->required_tag);
752 0           $style->set_name($name);
753 0           return $context->insert_element($style);
754             }
755              
756             sub insert_outline_style
757             {
758 0     0 0   my $self = shift;
759 0           my $style = shift;
760 0 0         my $context = $self->select_style_context($style) or return undef;
761 0 0         my $old = $self->get_style('outline'); $old && $old->delete;
  0            
762 0           $style->set_name(undef);
763 0           $style->check_tag($style->required_tag);
764 0           return $context->insert_element($style);
765             }
766              
767             sub insert_default_style
768             {
769 0     0 0   my $self = shift;
770 0           my $style = shift;
771 0           my $context = $self->get_element(STYLES, '//office:styles');
772 0 0         unless ($context)
773             {
774 0           alert "Default style context not available";
775 0           return undef;
776             }
777 0           my $family = $style->get_family;
778 0 0         my $ds = $style->make_default or return FALSE;
779 0           my $old = $self->get_style($family);
780 0 0         $old->delete() if $old;
781 0           return $context->insert_element($ds);
782             }
783              
784             sub insert_style
785             {
786 0     0 0   my $self = shift;
787 0           my $style = shift;
788 0           my $class = ref $style;
789 0 0         if ($class)
790             {
791 0 0         if ($class eq 'ARRAY')
792             {
793 0           $style = ODF::lpOD::Style->create(@$style);
794 0           $class = ref $style;
795             }
796             }
797 0 0 0       unless ($class && $style->isa('ODF::lpOD::Style'))
798             {
799 0           alert "Missing or wrong style element";
800 0           return FALSE;
801             }
802 0           my %opt = @_;
803 0           my $family = $style->get_family;
804 0 0         if (is_true($opt{default}))
805             {
806 0           return $self->insert_default_style($style, $family);
807             }
808 0           given ($family)
809             {
810             when ([
811             'text', 'paragraph', 'graphic', 'gradient',
812             'drawing page', 'number', 'currency', 'date'
813             ])
814 0           {
815 0           return $self->insert_regular_style($style, %opt);
816             }
817             when (/(list|master|page layout)/)
818 0           {
819 0           return $self->insert_special_style($style, %opt);
820             }
821             when ('outline')
822 0           {
823 0           return $self->insert_outline_style($style);
824             }
825             when (/^table/)
826 0           {
827 0 0         $opt{automatic} = TRUE unless exists $opt{automatic};
828 0 0         $opt{part} = CONTENT unless $opt{part};
829 0           return $self->insert_special_style($style, %opt);
830             }
831             default
832 0           {
833 0           alert "Not supported"; return undef;
  0            
834             }
835             }
836             }
837              
838             #--- bulk style replacement by import from another document ------------------
839              
840             sub substitute_styles
841             {
842 0     0 0   my $self = shift;
843 0           my $from = shift;
844 0           my %opt =
845             (
846             common => TRUE,
847             master => TRUE,
848             automatic => TRUE,
849             fonts => TRUE,
850             @_
851             );
852 0           my $source;
853 0 0         if (ref $from)
854             {
855 0 0         $source = $from if $from->isa('ODF::lpOD::Document');
856             }
857             else
858             {
859 0           $source = ODF::lpOD::Document->new(template => $from);
860             }
861 0 0         unless ($source)
862             {
863 0           alert "Malformed or not available source"; return FALSE;
  0            
864             }
865              
866 0           my $count = 0;
867 0           foreach my $part ($self->get_part(CONTENT), $self->get_part(STYLES))
868             {
869 0           $count += $part->substitute_styles($source, %opt);
870             }
871              
872 0 0         $source->forget unless ref $from;
873 0           return $count;
874             }
875              
876             #--- document variable handling ----------------------------------------------
877              
878             sub get_user_variables
879             {
880 0     0 0   my $self = shift;
881 0           my %opt = @_;
882 0   0       my $context = $opt{context} // $self->get_body;
883 0           return $context->get_elements('text:user-field-decl');
884             }
885              
886             sub get_simple_variables
887             {
888 0     0 0   my $self = shift;
889 0           my %opt = @_;
890 0   0       my $context = $opt{context} // $self->get_body;
891 0           return $context->get_elements('text:variable-decl');
892             }
893              
894             sub get_variables
895             {
896 0     0 0   my $self = shift;
897 0           my %opt = @_;
898 0           given ($opt{class})
899             {
900             when (undef)
901 0           {
902             return (
903 0           $self->get_user_variables(@_),
904             $self->get_simple_variables(@_)
905             );
906             }
907             when ('user')
908 0           {
909 0           return $self->get_user_variables;
910             }
911             when ('simple')
912 0           {
913 0           return $self->get_simple_variables;
914             }
915             default
916 0           {
917 0           alert "Unknown variable class $opt{class}";
918 0           return undef;
919             }
920             }
921             }
922              
923             sub get_variable
924             {
925 0     0 0   my $self = shift;
926 0           my $name = shift;
927 0           my %opt = ( class => 'user', @_ );
928 0   0       my $context = $opt{context} // $self->get_body;
929 0           my $tag;
930 0           given ($opt{class})
931             {
932             when (undef)
933 0           {
934 0   0       return $self->get_variable($name, class => 'user')
935             ||
936             $self->get_variable($name, class => 'simple');
937             }
938             when ('user')
939 0           {
940 0           $tag = 'text:user-field-decl';
941             }
942             when ('simple')
943 0           {
944 0           $tag = 'text:variable-decl';
945             }
946             default
947 0           {
948 0           alert "Wrong variable class"; return undef;
  0            
949             }
950             }
951              
952 0           return $context->get_element
953             ($tag, attribute => 'name', value => $name);
954             }
955              
956             sub set_variable
957             {
958 0     0 0   my $self = shift;
959 0           my $name = shift;
960 0 0         unless ($name)
961             {
962 0           alert "Missing variable name"; return FALSE;
  0            
963             }
964 0 0         if ($self->get_variable($name, class => undef))
965             {
966 0           alert "Variable $name already exists"; return FALSE;
  0            
967             }
968 0           my %opt =
969             (
970             name => $name,
971             class => 'user',
972             type => 'string',
973             @_
974             );
975              
976 0           my $class = $opt{class};
977 0           my $context = $opt{context};
978 0           delete @opt{qw(class context)};
979 0           my $var;
980 0           given ($class)
981             {
982             when ('user')
983 0           {
984 0           $var = ODF::lpOD::UserVariable->create(%opt);
985             }
986             when ('simple')
987 0           {
988 0           $var = ODF::lpOD::SimpleVariable->create(%opt);
989             }
990             default
991 0           {
992 0           alert "Unsupported variable class";
993             }
994             }
995 0 0         if ($var)
996             {
997 0           my $tag = $var->context_tag;
998 0   0       $context //= $self->get_body->set_first_child($tag);
999 0 0         if ($context)
1000             {
1001 0           $context->append_element($var);
1002             }
1003             else
1004             {
1005 0           alert "Unknown object insertion context";
1006 0           $var->delete; $var = undef;
  0            
1007             }
1008             }
1009 0           return $var;
1010             }
1011              
1012             #--- table of content handling -----------------------------------------------
1013              
1014             sub get_tocs
1015             {
1016 0     0 0   my $self = shift;
1017 0           return $self->get_part(CONTENT)->get_tocs(@_);
1018             }
1019              
1020             sub get_toc
1021             {
1022 0     0 0   my $self = shift;
1023 0           return $self->get_part(CONTENT)->get_toc(@_);
1024             }
1025              
1026             #--- named range handling ----------------------------------------------------
1027              
1028             sub get_named_range
1029             {
1030 0     0 0   my $self = shift;
1031 0           return $self->get_part(CONTENT)->get_named_range(@_);
1032             }
1033              
1034             sub set_named_range
1035             {
1036 0     0 0   my $self = shift;
1037 0           return $self->get_part(CONTENT)->set_named_range(@_);
1038             }
1039              
1040             #--- font declaration --------------------------------------------------------
1041              
1042             sub set_font_declaration
1043             {
1044 0     0 0   my $self = shift;
1045             return (
1046 0           $self->get_part(CONTENT)->set_font_declaration(@_),
1047             $self->get_part(STYLES)->set_font_declaration(@_)
1048             );
1049             }
1050              
1051             #=============================================================================
1052             package ODF::lpOD::Container;
1053             our $VERSION = '1.004';
1054 2     2   35 use constant PACKAGE_DATE => '2012-02-19T19:08:31';
  2         3  
  2         200  
1055 2     2   14 use ODF::lpOD::Common;
  2         3  
  2         1620  
1056             #-----------------------------------------------------------------------------
1057 2     2   2498 use Archive::Zip 1.30 qw ( :DEFAULT :CONSTANTS :ERROR_CODES );
  2         181816  
  2         448  
1058             #=============================================================================
1059              
1060             BEGIN {
1061 2     2   7 *forget = *DESTROY;
1062 2         4 *get_parts = *parts;
1063 2         4678 *add_part = *add_file;
1064             }
1065              
1066             #=== parameters ==============================================================
1067              
1068             our %ODF_PARTS =
1069             (
1070             content => CONTENT,
1071             styles => STYLES,
1072             meta => META,
1073             manifest => MANIFEST,
1074             settings => SETTINGS,
1075             mimetype => MIMETYPE
1076             );
1077              
1078             our %PARTS_ODF = reverse %ODF_PARTS;
1079              
1080             sub translate_part_name
1081             {
1082 0 0   0     my $name = shift or return undef;
1083 0 0         return $ODF_PARTS{$name} ? $ODF_PARTS{$name} : $name;
1084             }
1085              
1086             our %COMPRESSION = # compression rule for some parts
1087             (
1088             MIMETYPE => FALSE,
1089             META => FALSE,
1090             CONTENT => TRUE,
1091             STYLES => TRUE,
1092             MANIFEST => TRUE,
1093             SETTINGS => TRUE
1094             );
1095              
1096             #=============================================================================
1097              
1098             sub get_from_uri
1099             {
1100 0     0     return ODF::lpOD::Container->new(uri => shift);
1101             }
1102              
1103             #-----------------------------------------------------------------------------
1104              
1105             sub create_from_template
1106             {
1107 0     0     return ODF::lpOD::Container->new(template => shift);
1108             }
1109              
1110             #-----------------------------------------------------------------------------
1111              
1112             sub create
1113             {
1114 0     0     return ODF::lpOD::Container->new(type => shift);
1115             }
1116              
1117             #=============================================================================
1118              
1119             sub new
1120             {
1121 0     0     my $class = shift;
1122 0           my $self =
1123             {
1124             type => undef,
1125             uri => undef,
1126             read_only => undef,
1127             zip => undef,
1128             deleted => [],
1129             stored => {},
1130             @_
1131             };
1132              
1133 0 0         if ($self->{type})
    0          
1134             {
1135 0 0         $self->{uri} = ODF::lpOD::Common::template($self->{type})
1136             or return undef;
1137 0           $self->{read_only} = TRUE;
1138 0           $self->{create} = TRUE;
1139             }
1140             elsif ($self->{template})
1141             {
1142 0           $self->{uri} = $self->{template};
1143 0           $self->{read_only} = TRUE;
1144 0           $self->{create} = FALSE;
1145             }
1146             else
1147             {
1148 0           $self->{create} = FALSE;
1149             }
1150              
1151 0           my $source = $self->{uri};
1152 0 0         my $zip = defined $self->{zip} ?
1153             $self->{zip} : Archive::Zip->new;
1154              
1155 0 0         if (UNIVERSAL::isa($source, 'IO::File'))
1156             {
1157 0 0         if ($zip->readFromFileHandle($source) != AZ_OK)
1158             {
1159 0           alert("Handle read error");
1160 0           return FALSE;
1161             }
1162             }
1163             else
1164             {
1165 0 0         unless (-r -f -e $source)
1166             {
1167 0           alert("Missing source");
1168 0           return FALSE;
1169             }
1170 0 0         if ($zip->read($source) != AZ_OK)
1171             {
1172 0           alert("File read error");
1173 0           return FALSE;
1174             }
1175             }
1176              
1177 0           $self->{zip} = $zip;
1178 0           bless $self, $class;
1179 0           return $self;
1180             }
1181              
1182             #-----------------------------------------------------------------------------
1183              
1184             sub DESTROY
1185             {
1186 0     0     my $self = shift;
1187 0           undef $self->{zip};
1188 0           $self = {};
1189             }
1190              
1191             #-----------------------------------------------------------------------------
1192              
1193             sub get_mimetype
1194             {
1195 0     0     my $self = shift;
1196 0           return $self->get_part(MIMETYPE);
1197             }
1198              
1199             sub set_mimetype
1200             {
1201 0     0     my $self = shift;
1202 0           return $self->set_part(
1203             MIMETYPE, shift, compress => FALSE, string => TRUE
1204             );
1205             }
1206              
1207             #-----------------------------------------------------------------------------
1208              
1209             sub parts
1210             {
1211 0     0     my $self = shift;
1212 0           return $self->{zip}->memberNames;
1213             }
1214              
1215             #-----------------------------------------------------------------------------
1216              
1217             sub contains
1218             {
1219 0     0     my $self = shift;
1220 0 0         my $part_name = shift or return FALSE;
1221 0 0         return (grep $_ eq $part_name, $self->parts) ? TRUE : FALSE;
1222             }
1223              
1224             #-----------------------------------------------------------------------------
1225              
1226             sub raw_set_part
1227             {
1228 0     0     my $self = shift;
1229 0           my $part_name = shift;
1230              
1231 0           my $data = shift;
1232 0           my %opt =
1233             (
1234             string => TRUE,
1235             compress => undef,
1236             compression_method => COMPRESSION_DEFLATED,
1237             compression_level => COMPRESSION_LEVEL_BEST_COMPRESSION,
1238             @_
1239             );
1240              
1241 0   0       my $compress = $opt{compress} // $COMPRESSION{$part_name} // FALSE;
      0        
1242 0           my $zip = $self->{zip};
1243 0 0         my $buffer = is_true($opt{string}) ? $data : load_file($data, ':raw');
1244 0           my $p = $zip->addString($buffer, $part_name);
1245              
1246 0 0         if ($p)
1247             {
1248 0 0         if (is_true($compress))
1249             {
1250 0           $p->desiredCompressionMethod($opt{compression_method});
1251 0           $p->desiredCompressionLevel($opt{compression_level});
1252             }
1253             else
1254             {
1255 0           $p->desiredCompressionMethod(COMPRESSION_STORED);
1256             }
1257 0           return TRUE;
1258             }
1259             else
1260             {
1261 0           alert("Data storage error");
1262 0           return FALSE;
1263             }
1264             }
1265              
1266             #-----------------------------------------------------------------------------
1267              
1268             sub raw_del_part
1269             {
1270 0     0     my $self = shift;
1271 0           my $part_name = shift;
1272 0 0         return FALSE unless $self->contains($part_name);
1273              
1274 0           my $status = $self->{zip}->removeMember($part_name);
1275 0 0         unless ($status)
1276             {
1277 0           alert("$part_name removal failed");
1278 0           return FALSE;
1279             }
1280 0           return TRUE;
1281             }
1282              
1283             #=============================================================================
1284              
1285             sub set_part
1286             {
1287 0     0     my $self = shift;
1288 0 0         my $part_name = translate_part_name(shift) or return FALSE;
1289 0   0       my $data = shift // "";
1290 0           my %opt =
1291             (
1292             string => FALSE,
1293             compress => FALSE,
1294             @_
1295             );
1296              
1297 0           $self->{stored}{$part_name}{data} = $data;
1298 0           $self->{stored}{$part_name}{string} = $opt{string};
1299 0           $self->{stored}{$part_name}{compress} = $opt{compress};
1300              
1301 0           $self->del_part($part_name);
1302              
1303 0           return $part_name;
1304             }
1305              
1306             #-----------------------------------------------------------------------------
1307              
1308             sub add_file
1309             {
1310 0     0     my $self = shift;
1311 0 0         my $path = shift or return undef;
1312 0           my $destination = shift;
1313 0           my %opt =
1314             (
1315             string => FALSE,
1316             @_
1317             );
1318 0 0         unless ($destination)
1319             {
1320 0           my $mimetype = file_type($path);
1321 0           my $filename = file_parse($path);
1322 0 0 0       if ($mimetype && $mimetype =~ /^image/)
1323             {
1324 0           $destination = 'Pictures/' . $filename;
1325 0           $opt{compress} = FALSE;
1326             }
1327             else
1328             {
1329 0           $destination = $filename;
1330 0           $opt{compress} = TRUE;
1331             }
1332             }
1333 0           return $self->set_part($destination, $path, %opt);
1334             }
1335              
1336             #-----------------------------------------------------------------------------
1337              
1338             sub get_stored_part
1339             {
1340 0     0     my $self = shift;
1341 0           my $part_name = shift;
1342 0           return $self->{stored}{$part_name};
1343             }
1344              
1345             sub get_part
1346             {
1347 0     0     my $self = shift;
1348 0           my $part_name = translate_part_name(shift);
1349 0 0         unless ($part_name)
1350             {
1351 0           alert "Missing part name";
1352 0           return FALSE
1353             }
1354 0 0         unless ($self->contains($part_name))
1355             {
1356 0           alert("Unknown part $part_name");
1357 0           return FALSE;
1358             }
1359 0           my ($result, $status) = $self->{'zip'}->contents($part_name);
1360 0 0         return $status == AZ_OK ? $result : undef;
1361             }
1362              
1363             #-----------------------------------------------------------------------------
1364              
1365             sub del_part
1366             {
1367 0     0     my $self = shift;
1368 0 0         my $part_name = translate_part_name(shift) or return FALSE;
1369 0           push @{$self->{deleted}}, $part_name;
  0            
1370 0           return TRUE;
1371             }
1372              
1373             #-----------------------------------------------------------------------------
1374              
1375             sub save
1376             {
1377 0     0     my $self = shift;
1378 0           my %opt =
1379             (
1380             target => undef,
1381             packaging => 'zip',
1382             @_
1383             );
1384 0 0         if (is_true($self->{read_only}))
1385             {
1386 0 0 0       unless (
1387             (defined $opt{target}) &&
1388             $opt{target} ne $self->{uri}
1389             )
1390             {
1391 0           alert("Read-only container");
1392 0           return undef;
1393             }
1394             }
1395 0           my $target = $opt{target};
1396 0           my $packaging = $opt{packaging};
1397              
1398 0           $self->raw_del_part($_) for @{$self->{deleted}};
  0            
1399              
1400 0           foreach my $part_name (keys %{$self->{stored}})
  0            
1401             {
1402 0           my $data = $self->{stored}{$part_name}{data};
1403 0           my $compress = $self->{stored}{$part_name}{compress};
1404 0           my $string = $self->{stored}{$part_name}{string};
1405 0           $self->raw_del_part($part_name);
1406 0           $self->raw_set_part
1407             (
1408             $part_name, $data,
1409             compress => $compress,
1410             string => $string
1411             );
1412             }
1413              
1414 0           my $status = undef;
1415 0 0         unless (defined $target)
    0          
1416             {
1417 0           $status = $self->{zip}->overwrite();
1418             }
1419             elsif (UNIVERSAL::isa($target, 'IO::File'))
1420             {
1421 0           $status = $self->{zip}->writeToFileHandle($target);
1422             }
1423             else
1424             {
1425 0           $status = $self->{zip}->writeToFileNamed($target);
1426             }
1427              
1428 0 0         unless ($status == AZ_OK)
1429             {
1430 0           alert("Zip I/O error");
1431 0           return FALSE;
1432             }
1433              
1434 0           $self->{deleted} = [];
1435 0           $self->{stored} = {};
1436 0           return TRUE;
1437             }
1438              
1439             #=============================================================================
1440             package ODF::lpOD::XMLPart;
1441             our $VERSION = '1.007';
1442 2     2   26 use constant PACKAGE_DATE => '2012-05-15T08:36:28';
  2         4  
  2         140  
1443 2     2   13 use ODF::lpOD::Common;
  2         4  
  2         1643  
1444             #-----------------------------------------------------------------------------
1445              
1446             BEGIN {
1447 2     2   6 *forget = *DESTROY;
1448 2         10 *body = *get_body;
1449 2         3 *get_container = *container;
1450 2         4 *get_document = *document;
1451 2         4 *root = *get_root;
1452 2         4 *get_element_list = *get_elements;
1453 2         4495 *export = *serialize;
1454             }
1455              
1456             sub class_of
1457             {
1458 0     0     my $part = shift;
1459 0 0         return ref $part if ref $part;
1460 0           given($part)
1461             {
1462 0           when (CONTENT) { return 'ODF::lpOD::Content' }
  0            
1463 0           when (STYLES) { return 'ODF::lpOD::Styles' }
  0            
1464 0           when (META) { return 'ODF::lpOD::Meta' }
  0            
1465 0           when (SETTINGS) { return 'ODF::lpOD::Settings' }
  0            
1466 0           when (MANIFEST) { return 'ODF::lpOD::Manifest' }
  0            
1467 0           default { return undef }
  0            
1468             }
1469             }
1470              
1471             our %CLASS =
1472             (
1473             content => 'ODF::lpOD::Content',
1474             styles => 'ODF::lpOD::Styles',
1475             meta => 'ODF::lpOD::Meta',
1476             manifest => 'ODF::lpOD::Manifest',
1477             settings => 'ODF::lpOD::Settings'
1478             );
1479              
1480 0     0     sub pre_load {}
1481             sub post_load
1482             {
1483 0     0     my $self = shift;
1484 0           $self->get_root->set_classes;
1485             }
1486              
1487             #=== exported part ===========================================================
1488              
1489             sub get
1490             {
1491 0     0     my $container = shift;
1492 0 0 0       unless (ref $container && $container->isa('ODF::lpOD::Container'))
1493             {
1494 0           alert "Missing or not valid container";
1495 0           return FALSE;
1496             }
1497 0           my $part_name = shift;
1498 0 0         unless (class_of($part_name))
1499             {
1500 0           alert "Missing or unknown document part";
1501 0           return FALSE;
1502             }
1503 0           return ODF::lpOD::XMLPart->new
1504             (
1505             part => $part_name,
1506             container => $container,
1507             @_
1508             );
1509             }
1510              
1511             #=============================================================================
1512             #--- constructor and associated utilities ------------------------------------
1513              
1514             sub new
1515             {
1516 0     0     my $class = shift;
1517 0           my $self =
1518             {
1519             container => undef,
1520             part => undef,
1521             load => TRUE,
1522             elt_class => 'ODF::lpOD::Element',
1523             twig => undef,
1524             context => undef,
1525             @_
1526             };
1527              
1528 0 0         unless (defined $self->{update})
1529             {
1530 0 0         $self->{update} = $self->{roots} ? FALSE : TRUE;
1531             }
1532 0           my $part_class = class_of($self->{part});
1533 0 0         unless ($class)
1534             {
1535 0           alert "Unknown ODF XML part"; return FALSE;
  0            
1536             }
1537 0   0       $self->{pretty} //= ($self->{indent} // lpod->debug);
      0        
1538 0 0         $self->{pretty_print} = PRETTY_PRINT if is_true($self->{pretty});
1539 0   0       $self->{twig} //= XML::Twig->new # twig init
1540             (
1541             twig_handlers => $self->{handlers},
1542             twig_roots => $self->{roots},
1543             elt_class => $self->{elt_class},
1544             pretty_print => $self->{pretty_print},
1545             output_encoding => TRUE,
1546             id => $ODF::lpOD::Common::LPOD_ID
1547             );
1548 0           $self->{twig}->set_output_encoding('UTF-8');
1549              
1550 0           bless $self, $part_class;
1551 0 0         if ($self->{load})
1552             {
1553 0           my $status = $self->load();
1554 0 0         unless (is_true($status))
1555             {
1556 0           alert("Part load failed");
1557 0           return FALSE;
1558             }
1559             }
1560 0           return $self;
1561             }
1562              
1563             sub load
1564             {
1565 0     0     my $self = shift;
1566 0   0       my $xml = shift || $self->{container}->get_part($self->{part});
1567              
1568 0 0         unless (defined $xml)
1569             {
1570 0           alert("No content");
1571 0           return FALSE;
1572             }
1573              
1574 0           $self->pre_load;
1575 0 0         my $r = UNIVERSAL::isa($xml, 'IO::File') ?
1576             $self->{twig}->safe_parsefile($xml) :
1577             $self->{twig}->safe_parse($xml);
1578 0 0         unless ($r)
1579             {
1580 0           alert "No valid XML content";
1581 0           return FALSE;
1582             }
1583 0           $self->{context} = $self->{twig}->root;
1584 0           $self->{context}->lpod_part($self);
1585 0           $self->post_load;
1586 0           return TRUE;
1587             }
1588              
1589             sub needs_update
1590             {
1591 0     0     my $self = shift;
1592 0           my $arg = shift;
1593 0           given ($arg)
1594             {
1595 0           when (undef) {}
1596 0           when (TRUE) { $self->{update} = TRUE }
  0            
1597 0           when (FALSE) { $self->{update} = FALSE }
  0            
1598             }
1599 0           return $self->{update};
1600             }
1601            
1602             sub get_name
1603             {
1604 0     0     my $self = shift;
1605 0           return $self->{part};
1606             }
1607              
1608             #--- destructor --------------------------------------------------------------
1609              
1610             sub DESTROY
1611             {
1612 0     0     my $self = shift;
1613 0 0         $self->{context} &&
1614             $self->{context}->del_att($ODF::lpOD::Common::LPOD_PART);
1615 0 0         $self->{context} && $self->{context}->delete;
1616 0           delete $self->{context};
1617 0 0         $self->{twig} && $self->{twig}->dispose;
1618 0           delete $self->{twig};
1619 0           delete $self->{container};
1620 0           delete $self->{part};
1621 0           $self = {};
1622             }
1623              
1624             #--- basic individual node selection -----------------------------------------
1625              
1626             sub find_node
1627             {
1628 0     0     my $self = shift;
1629 0           my $tag = shift;
1630 0   0       my $context = shift || $self->{context};
1631              
1632 0           return $context->first_descendant($tag);
1633             }
1634              
1635             #=== public part =============================================================
1636             #--- general document management ---------------------------------------------
1637              
1638             sub get_class
1639             {
1640 0     0     my $self = shift;
1641 0           return ref $self;
1642             }
1643              
1644             sub get_root
1645             {
1646 0     0     my $self = shift;
1647 0           return $self->{twig}->root;
1648             }
1649              
1650             sub get_body
1651             {
1652 0     0     my $self = shift;
1653 0           my $tag = shift;
1654 0           my $root = $self->get_root;
1655 0 0         if ($tag)
1656             {
1657 0 0         $tag = 'office:' . $tag unless $tag =~ /:/;
1658 0           return $root->get_xpath(('//office:body/' . $tag), 0);
1659             }
1660 0           my $context = $root->get_xpath('//office:body', 0);
1661 0 0         return $context ?
1662             $context->first_child
1663             (qr'office:(text|spreadsheet|presentation|drawing)')
1664             :
1665             $root->first_child
1666             (qr'office:(body|meta|master-styles|settings)');
1667             }
1668              
1669             sub container
1670             {
1671 0     0     my $self = shift;
1672 0           return $self->{container};
1673             }
1674              
1675             sub document
1676             {
1677 0     0     my $self = shift;
1678 0           return $self->{document};
1679             }
1680              
1681             sub serialize
1682             {
1683 0     0     my $self = shift;
1684 0           my %opt =
1685             (
1686             empty_tags => EMPTY_TAGS,
1687             output => undef,
1688             @_
1689             );
1690 0   0       $opt{pretty} //= ($self->{indent} // lpod->debug);
      0        
1691 0 0         $opt{pretty_print} = PRETTY_PRINT if is_true($opt{pretty});
1692 0           my $output = $opt{output};
1693 0           delete @opt{qw(pretty output indent)};
1694 0 0         return (defined $output) ?
1695             $self->{twig}->print($output, %opt) :
1696             $self->{twig}->sprint(%opt);
1697             }
1698              
1699             sub store
1700             {
1701 0     0     my $self = shift;
1702 0 0         unless ($self->{container})
1703             {
1704 0           alert "No associated container";
1705 0           return FALSE;
1706             }
1707 0           my %opt = @_;
1708 0           my %storage = ();
1709 0 0         if ($opt{storage})
1710             {
1711 0           %storage = %{$opt{storage}};
  0            
1712 0           delete $opt{storage};
1713             }
1714             else
1715             {
1716 0           %storage = (compress => TRUE, string => TRUE);
1717             }
1718             return
1719 0           $self->{container}->set_part
1720             (
1721             $self->{part},
1722             $self->serialize(%opt),
1723             %storage
1724             );
1725             }
1726              
1727             #--- general element management ----------------------------------------------
1728              
1729             sub get_elements
1730             {
1731 0     0     my ($self, $xpath) = @_;
1732 0           return $self->{context}->get_xpath($xpath);
1733             }
1734              
1735             sub get_element
1736             {
1737 0     0     my $self = shift;
1738 0           my $xpath = shift;
1739 0   0       my $offset = shift || 0;
1740 0           return $self->{context}->get_xpath($xpath, $offset);
1741             }
1742              
1743             sub append_element
1744             {
1745 0     0     my $self = shift;
1746 0           my $context = $self->get_root;
1747 0           return $context->append_element(@_);
1748             }
1749              
1750             sub insert_element
1751             {
1752 0     0     my $self = shift;
1753 0           my $context = $self->get_root;
1754 0           return $context->insert_element(@_);
1755             }
1756              
1757             sub delete_element
1758             {
1759 0     0     my ($self, $element) = @_;
1760 0           return $element->delete;
1761             }
1762              
1763             #--- tracked change handling -------------------------------------------------
1764              
1765             sub get_tracked_changes_root
1766             {
1767 0     0     my $self = shift;
1768 0 0         unless ($self->{tracked_changes})
1769             {
1770 0           $self->{tracked_changes} =
1771             $self->find_node('text:tracked-changes');
1772             }
1773 0           return $self->{tracked_changes};
1774             }
1775              
1776             sub get_changes
1777             {
1778 0     0     my $self = shift;
1779 0           my $context = $self->get_tracked_changes_root;
1780 0 0         unless ($context)
1781             {
1782 0           alert "Not valid tracked change retrieval context";
1783 0           return FALSE;
1784             }
1785 0           return $context->get_changes(@_);
1786             }
1787              
1788             sub get_change
1789             {
1790 0     0     my $self = shift;
1791 0           my $context = $self->get_tracked_changes_root;
1792 0 0         unless ($context)
1793             {
1794 0           alert "Not valid tracked change retrieval context";
1795 0           return FALSE;
1796             }
1797 0           return $context->get_change(shift);
1798             }
1799              
1800             #=============================================================================
1801             package ODF::lpOD::StyleContainer;
1802 2     2   17 use base 'ODF::lpOD::XMLPart';
  2         5  
  2         1216  
1803             our $VERSION = '1.001';
1804 2     2   13 use constant PACKAGE_DATE => '2011-05-29T16:05:40';
  2         3  
  2         103  
1805 2     2   10 use ODF::lpOD::Common;
  2         4  
  2         7294  
1806             #-----------------------------------------------------------------------------
1807              
1808             sub get_font_declarations
1809             {
1810 0     0     my $self = shift;
1811 0           return $self->get_elements
1812             ('//office:font-face-decls/style:font-face');
1813             }
1814              
1815             sub get_font_declaration
1816             {
1817 0     0     my $self = shift;
1818 0 0         my $name = shift or return undef;
1819 0           my $xp = '//office:font-face-decls' .
1820             '/style:font-face[@style:name="' .
1821             $name .
1822             '"]';
1823 0           return $self->get_element($xp);
1824             }
1825              
1826             sub set_font_declaration
1827             {
1828 0     0     my $self = shift;
1829 0           my $name = shift;
1830 0 0         unless ($name)
1831             {
1832 0           alert "Missing font name"; return undef;
  0            
1833             }
1834 0           my %opt = process_options(@_);
1835 0   0       $opt{family} ||= $name;
1836 0           my $fd = $self->get_font_declaration($name);
1837 0 0         $fd->delete if $fd;
1838 0           return $self
1839             ->get_root
1840             ->set_child('office:font-face-decls')
1841             ->append_element
1842             (ODF::lpOD::FontDeclaration->create($name, %opt));
1843             }
1844              
1845             sub substitute_context
1846             {
1847 0     0     my $self = shift;
1848 0           my $doc = shift;
1849 0           my $path = shift;
1850              
1851 0           my $part = $self->get_name;
1852 0 0         my $origin = $doc->get_element($part, $path)
1853             or return undef;
1854 0 0         my $destination = $self->get_element($path)
1855             or return undef;
1856 0           return $destination->substitute_children($origin);
1857             }
1858              
1859             sub substitute_styles
1860             {
1861 0     0     my $self = shift;
1862 0           my $from = shift;
1863              
1864 0           my %opt =
1865             (
1866             common => TRUE,
1867             master => TRUE,
1868             automatic => TRUE,
1869             fonts => TRUE,
1870             @_
1871             );
1872 0           my $part = $self->get_name;
1873 0 0         if ($part ne STYLES)
1874             {
1875 0           delete @opt{qw(common master)};
1876             }
1877 0           my $source;
1878 0 0         if (ref $from)
1879             {
1880 0 0         $source = $from if $from->isa('ODF::lpOD::Document');
1881             }
1882             else
1883             {
1884 0           $source = ODF::lpOD::Document->new(template => $from);
1885             }
1886 0 0         unless ($source)
1887             {
1888 0           alert "Malformed or not available source"; return FALSE;
  0            
1889             }
1890              
1891 0           my $count = 0;
1892              
1893 0 0         if (is_true($opt{automatic}))
1894             {
1895 0           $count += $self->substitute_context
1896             ($source, '//office:automatic-styles');
1897             }
1898 0 0         if (is_true($opt{common}))
1899             {
1900 0           $count += $self->substitute_context
1901             ($source, '//office:styles');
1902             }
1903 0 0         if (is_true($opt{master}))
1904             {
1905 0           $count += $self->substitute_context
1906             ($source, '//office:master-styles');
1907             }
1908 0 0         if (is_true($opt{fonts}))
1909             {
1910 0           $count += $self->substitute_context
1911             ($source, '//office:font-face-decls');
1912             }
1913              
1914 0 0         $source->forget unless ref $from;
1915 0           return $count;
1916             }
1917              
1918             #=============================================================================
1919             package ODF::lpOD::Content;
1920 2     2   23 use base 'ODF::lpOD::StyleContainer';
  2         10  
  2         1252  
1921             our $VERSION = '1.003';
1922 2     2   14 use constant PACKAGE_DATE => '2012-03-29T08:25:40';
  2         4  
  2         105  
1923 2     2   11 use ODF::lpOD::Common;
  2         3  
  2         2028  
1924             #-----------------------------------------------------------------------------
1925              
1926             sub get_tocs
1927             {
1928 0     0     my $self = shift;
1929 0           my $context = $self->get_body;
1930 0           return $context->get_elements('text:table-of-content');
1931             }
1932              
1933             sub get_toc
1934             {
1935 0     0     my $self = shift;
1936 0           my $name = shift;
1937 0           my $context = $self->get_body;
1938 0           return $context->get_element_by_name('text:table-of-content', $name);
1939             }
1940              
1941             sub get_named_range
1942             {
1943 0     0     my $self = shift;
1944 0           my $context = $self->get_body('spreadsheet');
1945 0 0         unless ($context)
1946             {
1947 0           alert "Not in spreadsheet context"; return undef;
  0            
1948             }
1949 0           return $context->get_element_by_name('table:named-range', @_);
1950             }
1951              
1952             sub set_named_range
1953             {
1954 0     0     my $self = shift;
1955 0           my $body = $self->get_body('spreadsheet');
1956 0 0         unless ($body)
1957             {
1958 0           alert "Not in spreadsheet context"; return undef;
  0            
1959             }
1960 0           my $name = shift;
1961 0           my $old = $self->get_named_range($name);
1962 0 0         if ($old)
1963             {
1964 0           alert "Named range $name already exists"; return undef;
  0            
1965             }
1966 0           my $context = $body->set_last_child('table:named-expressions');
1967 0           my $nr = ODF::lpOD::NamedRange->create($name, @_);
1968 0           $context->append_element($nr);
1969 0           return $nr;
1970             }
1971              
1972             sub get_headings
1973             {
1974 0     0     my $self = shift;
1975 0           return $self->get_body->get_headings(@_);
1976             }
1977              
1978             #=============================================================================
1979             package ODF::lpOD::Styles;
1980 2     2   22 use base 'ODF::lpOD::StyleContainer';
  2         10  
  2         947  
1981             our $VERSION = '1.000';
1982 2     2   13 use constant PACKAGE_DATE => '2010-12-24T13:51:47';
  2         3  
  2         103  
1983 2     2   11 use ODF::lpOD::Common;
  2         4  
  2         1486  
1984             #=============================================================================
1985             package ODF::lpOD::Meta;
1986 2     2   13 use base 'ODF::lpOD::XMLPart';
  2         4  
  2         989  
1987             our $VERSION = '1.000';
1988 2     2   10 use constant PACKAGE_DATE => '2010-12-24T13:51:58';
  2         4  
  2         84  
1989 2     2   11 use ODF::lpOD::Common;
  2         4  
  2         1238  
1990             #-----------------------------------------------------------------------------
1991              
1992             BEGIN {
1993 2     2   3650 *get_element_list = *get_elements;
1994             }
1995              
1996 0     0     sub post_load {}
1997              
1998             #-----------------------------------------------------------------------------
1999              
2000             our %META =
2001             (
2002             creation_date => 'meta:creation-date',
2003             creator => 'dc:creator',
2004             description => 'dc:description',
2005             editing_cycles => 'meta:editing-cycles',
2006             editing_duration => 'meta:editing-duration',
2007             generator => 'meta:generator',
2008             initial_creator => 'meta:initial-creator',
2009             language => 'dc:language',
2010             modification_date => 'dc:date',
2011             printed_by => 'meta:printed-by',
2012             print_date => 'meta:print-date',
2013             subject => 'dc:subject',
2014             title => 'dc:title'
2015             );
2016              
2017             #-----------------------------------------------------------------------------
2018              
2019             sub get_body
2020             {
2021 0     0     my $self = shift;
2022 0 0         unless ($self->{body})
2023             {
2024 0           $self->{body} = $self->SUPER::get_element('//office:meta');
2025             }
2026 0           return $self->{body};
2027             }
2028              
2029             sub get_element
2030             {
2031 0     0     my $self = shift;
2032 0           return $self->get_body->get_element(@_);
2033             }
2034              
2035             sub get_elements
2036             {
2037 0     0     my $self = shift;
2038 0           return $self->get_body->get_element_list(@_);
2039             }
2040              
2041             sub append_element
2042             {
2043 0     0     my $self = shift;
2044 0           return $self->get_body->append_element(@_);
2045             }
2046              
2047             #-----------------------------------------------------------------------------
2048              
2049             sub get_statistics
2050             {
2051 0     0     my $self = shift;
2052 0           my $stat = $self->get_element('meta:document-statistic');
2053 0 0         return $stat ? $stat->get_attributes() : undef;
2054             }
2055              
2056             sub set_statistics
2057             {
2058 0     0     my $self = shift;
2059 0   0       my $stat = $self->get_element('meta:document-statistic') ||
2060             $self->append_element('meta:document-statistic');
2061 0           return $stat->set_attributes(@_);
2062             }
2063              
2064             #-----------------------------------------------------------------------------
2065              
2066             sub get_keyword_list
2067             {
2068 0     0     my $self = shift;
2069 0           my $expr = shift;
2070 0           return $self->get_element_list
2071             ('meta:keyword', content => $expr);
2072             }
2073              
2074             sub get_keywords
2075             {
2076 0     0     my $self = shift;
2077 0           my @kwl = ();
2078 0           for ($self->get_keyword_list(@_))
2079             {
2080 0           push @kwl, $_->get_text;
2081             }
2082 0 0         return wantarray ? @kwl : join (', ', @kwl);
2083             }
2084              
2085             sub set_keyword
2086             {
2087 0     0     my $self = shift;
2088 0   0       my $kw = shift // return undef;
2089 0           for ($self->get_keyword_list)
2090             {
2091 0 0         return FALSE if $_->get_text() eq $kw;
2092             }
2093 0           my $e = $self->append_element('meta:keyword');
2094 0           $e->set_text($kw);
2095 0           return $e;
2096             }
2097              
2098             sub set_keywords
2099             {
2100 0     0     my $self = shift;
2101 0           my $input = join(',', @_);
2102 0           foreach my $kw (split(',', $input))
2103             {
2104 0           $kw =~ s/^ *//; $kw =~ s/ *$//;
  0            
2105 0           $self->set_keyword($kw);
2106             }
2107 0           return $self->get_keywords;
2108             }
2109              
2110             sub check_keyword
2111             {
2112 0     0     my $self = shift;
2113 0 0         my $expr = shift or return undef;
2114              
2115 0           return scalar $self->get_keyword_list($expr);
2116             }
2117              
2118             sub remove_keyword
2119             {
2120 0     0     my $self = shift;
2121 0 0         my $expr = shift or return undef;
2122 0           my $count = 0;
2123 0           for ($self->get_keyword_list($expr))
2124             {
2125 0           $_->delete; $count++;
  0            
2126             }
2127 0           return $count;
2128             }
2129              
2130             #-----------------------------------------------------------------------------
2131              
2132             sub get_user_field
2133             {
2134 0     0     my $self = shift;
2135 0 0         my $name = shift or return undef;
2136 0 0         my $e = ref $name ?
2137             $name
2138             :
2139             $self->get_element
2140             (
2141             'meta:user-defined',
2142             attribute => 'name',
2143             value => $name
2144             );
2145 0 0         return undef unless $e;
2146             return wantarray ?
2147             (
2148 0 0 0       $e->get_text(),
2149             $e->get_attribute('value type') || 'string'
2150             )
2151             :
2152             $e->get_text;
2153             }
2154              
2155             sub set_user_field
2156             {
2157 0     0     my $self = shift;
2158 0           my $name = shift;
2159 0           my $value = shift;
2160 0   0       my $type = shift || 'string';
2161 0 0         unless (is_odf_datatype($type))
2162             {
2163 0           alert "Wrong data type $type";
2164 0           return FALSE;
2165             }
2166 0 0         unless ($name)
2167             {
2168 0           alert "Missing user field name";
2169 0           return FALSE;
2170             }
2171 0           $value = check_odf_value($value, $type);
2172 0   0       my $e = $self->get_element
2173             (
2174             'meta:user-defined',
2175             attribute => 'name',
2176             value => $name
2177             )
2178             //
2179             $self->append_element('meta:user-defined');
2180 0           $e->set_attribute('name' => $name);
2181 0           $e->set_attribute('value type' => $type);
2182 0           $e->set_text($value);
2183             return wantarray ?
2184 0 0         ($e->get_text(), $e->get_attribute('value type'))
2185             :
2186             $e->get_text;
2187             }
2188              
2189             sub get_user_fields
2190             {
2191 0     0     my $self = shift;
2192 0           my @result = ();
2193 0           foreach my $e ($self->get_element_list('meta:user-defined'))
2194             {
2195 0           my $f;
2196 0           $f->{name} = $e->get_attribute('name');
2197 0   0       $f->{type} = $e->get_attribute('value type') // 'string';
2198 0   0       $f->{value} = $e->get_text() // "";
2199 0           push @result, $f;
2200             }
2201 0           return @result;
2202             }
2203              
2204             sub set_user_fields
2205             {
2206 0     0     my $self = shift;
2207 0           foreach my $f (@_)
2208             {
2209 0           $self->set_user_field($f->{name}, $f->{value}, $f->{type});
2210             }
2211 0           return $self->get_user_fields;
2212             }
2213              
2214             #-----------------------------------------------------------------------------
2215              
2216             our $AUTOLOAD;
2217             sub AUTOLOAD
2218             {
2219 0     0     my $self = shift;
2220 0           $AUTOLOAD =~ /.*:(.*)/;
2221 0           my $method = $1;
2222 0           $method =~ /^([gs]et)_(.*)/;
2223 0           my $action = $1;
2224 0           my $object = $META{$2};
2225              
2226 0 0 0       unless ($action && $object)
2227             {
2228 0           alert "Unsupported method $method";
2229 0           return undef;
2230             }
2231              
2232 0           my $e = $self->get_element($object);
2233 0           given ($action)
2234             {
2235             when (undef)
2236 0           {
2237 0           alert "Unsupported action";
2238             }
2239             when ('get')
2240 0           {
2241 0 0         return $e ? $e->get_text() : undef;
2242             }
2243             when ('set')
2244 0           {
2245 0 0         unless ($e)
2246             {
2247 0           my $body = $self->get_body;
2248 0           $e = $body->append_element($object);
2249             }
2250 0           my $v = shift;
2251 0 0         if ($object =~ /date$/)
    0          
    0          
    0          
2252             {
2253 0 0         unless ($v)
2254             {
2255 0           $v = iso_date;
2256             }
2257             else
2258             {
2259 0           $v = check_odf_value($v, 'date');
2260             }
2261             }
2262             elsif ($object =~ /creator$/)
2263             {
2264 0 0 0       $v =
2265             $v = (scalar getlogin()) ||
2266             (scalar getpwuid($<)) ||
2267             $<
2268             unless $v;
2269             }
2270             elsif ($object =~ /generator$/)
2271             {
2272 0 0 0       $v = $0 || $$ unless $v;
2273             }
2274             elsif ($object =~ /cycles$/)
2275             {
2276 0 0         unless ($v)
2277             {
2278 0   0       $v = $e->get_text() || 0;
2279 0           $v++;
2280             }
2281             }
2282 0           return $e->set_text($v);
2283             }
2284             }
2285 0           return undef;
2286             }
2287              
2288             #-----------------------------------------------------------------------------
2289              
2290             sub store
2291             {
2292 0     0     my $self = shift;
2293 0           my %opt =
2294             (
2295             storage => { compress => FALSE, string => TRUE },
2296             @_
2297             );
2298 0           return $self->SUPER::store(%opt);
2299             }
2300              
2301             #=============================================================================
2302             package ODF::lpOD::Settings;
2303 2     2   25 use base 'ODF::lpOD::XMLPart';
  2         4  
  2         1030  
2304             our $VERSION = '1.000';
2305 2     2   11 use constant PACKAGE_DATE => '2010-12-24T13:52:14';
  2         5  
  2         107  
2306 2     2   11 use ODF::lpOD::Common;
  2         3  
  2         1241  
2307             #-----------------------------------------------------------------------------
2308              
2309 0     0     sub post_load {}
2310              
2311             #=============================================================================
2312             package ODF::lpOD::Manifest;
2313 2     2   14 use base 'ODF::lpOD::XMLPart';
  2         50  
  2         869  
2314             our $VERSION = '1.001';
2315 2     2   10 use constant PACKAGE_DATE => '2010-12-30T08:34:26';
  2         3  
  2         80  
2316 2     2   11 use ODF::lpOD::Common;
  2         2  
  2         2064  
2317             #-----------------------------------------------------------------------------
2318              
2319 0     0     sub post_load {}
2320              
2321             #-----------------------------------------------------------------------------
2322              
2323             sub get_entries
2324             {
2325 0     0     my $self = shift;
2326 0           my %opt = @_;
2327 0           my @all_entries = $self->{context}->get_element_list
2328             ('manifest:file-entry');
2329 0 0         unless (defined $opt{type})
2330             {
2331 0           return @all_entries;
2332             }
2333 0           my @selected_entries = ();
2334 0           ENTRY: foreach my $e (@all_entries)
2335             {
2336 0           my $type = $e->get_attribute('media type');
2337 0 0         next ENTRY unless defined $type;
2338 0 0         if ($opt{type} eq "")
2339             {
2340 0 0         push @selected_entries, $e if $type eq "";
2341 0           next ENTRY;
2342             }
2343 0 0         push @selected_entries, $e if $type =~ /$opt{type}/;
2344             }
2345 0           return @selected_entries;
2346             }
2347              
2348             sub get_entry
2349             {
2350 0     0     my $self = shift;
2351 0           return $self->{context}->get_element(
2352             'manifest:file-entry',
2353             attribute => 'full path',
2354             value => shift
2355             );
2356             }
2357              
2358             sub set_entry
2359             {
2360 0     0     my $self = shift;
2361 0           my $path = shift;
2362 0 0         unless ($path)
2363             {
2364 0           alert "Missing entry path"; return FALSE;
  0            
2365             }
2366 0           my $e = $self->get_entry($path);
2367 0           my %opt = @_;
2368 0 0         unless ($e)
2369             {
2370 0           $e = ODF::lpOD::Element->create('manifest:file-entry');
2371 0           $e->set_attribute('full path' => $path);
2372 0           $e->paste_last_child($self->{context});
2373             }
2374 0           $e->set_type($opt{type});
2375 0           return $e;
2376             }
2377              
2378             sub del_entry
2379             {
2380 0     0     my $self = shift;
2381 0           my $e = $self->get_entry(@_);
2382 0 0         $e->delete() if $e;
2383 0           return $e;
2384             }
2385              
2386             #=============================================================================
2387             1;