MsOffice-Word-Surgeon

 view release on metacpan or  search on metacpan

lib/MsOffice/Word/Surgeon.pm  view on Meta::CPAN

package MsOffice::Word::Surgeon;
use 5.24.0;
use Moose;
use MooseX::StrictConstructor;
use Moose::Util::TypeConstraints          qw(union);
use Archive::Zip                          qw(AZ_OK);
use Encode                                qw(encode_utf8 decode_utf8);
use Scalar::Util                          qw(openhandle);
use MsOffice::Word::Surgeon::Carp;
use MsOffice::Word::Surgeon::Revision;
use MsOffice::Word::Surgeon::PackagePart;

# syntactic sugar for attributes
sub has_lazy  ($@) {my $attr = shift; has($attr => @_, lazy => 1, builder => "_$attr")}
sub has_inner ($@) {my $attr = shift; has_lazy($attr => @_, init_arg => undef)}


use namespace::clean -except => 'meta';

our $VERSION = '2.11';


#======================================================================
# TYPES AND ATTRIBUTES
#======================================================================

my $DocxSource = union([qw/Str FileHandle/]);

# how to access the document
has      'docx'           => (is => 'ro', isa => $DocxSource);    # filename or filehandle, or ..
has_lazy 'zip'            => (is => 'ro', isa => 'Archive::Zip'); # .. an already opened zip archive

# syntax to show embedded fields -- used by PackagePart::replace_field
has 'show_embedded_field' => (is => 'ro', isa => 'Str', default => '{%s}');

# inner attributes lazily constructed by the module
has_inner 'parts'         => (is => 'ro', isa => 'HashRef[MsOffice::Word::Surgeon::PackagePart]',
                              traits => ['Hash'], handles => {part => 'get'});
                          
has_inner 'document'      => (is => 'ro', isa => 'MsOffice::Word::Surgeon::PackagePart',
                             handles => [qw/contents original_contents indented_contents plain_text replace/]);
  # Note: this attribute is equivalent to $self->part('document'); made into an attribute
  # for convenience and for automatic delegation of methods through the 'handles' declaration

# just a slot for internal storage
has 'next_rev_id'         => (is => 'bare', isa => 'Num', default => 1, init_arg => undef);
   # used by the revision() method for creating *::Revision objects -- each instance
   # gets a fresh value


#======================================================================
# BUILDING INSTANCES
#======================================================================


# syntactic sugar for supporting ->new($path) instead of ->new(docx => $path)
around BUILDARGS => sub {
  my $orig  = shift;
  my $class = shift;

  unshift @_, 'docx' if scalar(@_) % 2 and $DocxSource->check($_[0]);

  $class->$orig(@_);
};


# make sure that the constructor got either a 'docx' or a 'zip' attribute
sub BUILD {
  my $self = shift;

  my $class = ref $self;

  $self->{docx} || $self->{zip}
    or croak "$class->new() : need either 'docx' or 'zip' attribute";
  not ($self->{docx} && $self->{zip})
    or croak "$class->new() : can't have both 'docx' and 'zip' attributes";
}


#======================================================================
# LAZY ATTRIBUTE CONSTRUCTORS
#======================================================================

sub _zip {
  my $self = shift;

  my $docx = $self->docx;
  my ($meth, $source_name) = openhandle($docx) ? (readFromFileHandle => 'filehandle')
                                               : (read               => $docx);
  my $zip                  = Archive::Zip->new;
  my $result               = $zip->$meth($docx);
  $result == AZ_OK  or croak "cannot unzip from $source_name";

  return $zip;
}




( run in 0.604 second using v1.01-cache-2.11-cpan-ceb78f64989 )