###############################################################################
#
# Packager - A class for creating the Excel XLSX package.
#
# Used in conjunction with Excel::Writer::XLSX
#
# Copyright 2000-2024, John McNamara, jmcnamara@cpan.org
#
# SPDX-License-Identifier: Artistic-1.0-Perl OR GPL-1.0-or-later
#
# Documentation after __END__
#
# perltidy with the following options: -mbl=2 -pt=0 -nola
use 5.008002;
use strict;
use Carp;
our @ISA = qw(Exporter);
our $VERSION = '1.14';
###############################################################################
#
# Public and private API methods.
#
###############################################################################
###############################################################################
#
# new()
#
# Constructor.
#
sub new {
my $class = shift;
my $fh = shift;
my $self = Excel::Writer::XLSX::Package::XMLwriter->new( $fh );
$self->{_package_dir} = '';
$self->{_workbook} = undef;
$self->{_worksheet_count} = 0;
$self->{_chartsheet_count} = 0;
$self->{_chart_count} = 0;
$self->{_drawing_count} = 0;
$self->{_table_count} = 0;
$self->{_named_ranges} = [];
bless $self, $class;
return $self;
}
###############################################################################
#
# _set_package_dir()
#
# Set the XLSX OPC package directory.
#
sub _set_package_dir {
my $self = shift;
$self->{_package_dir} = shift;
}
###############################################################################
#
# _add_workbook()
#
# Add the Excel::Writer::XLSX::Workbook object to the package.
#
sub _add_workbook {
my $self = shift;
my $workbook = shift;
$self->{_workbook} = $workbook;
$self->{_chart_count} = scalar @{ $workbook->{_charts} };
$self->{_drawing_count} = scalar @{ $workbook->{_drawings} };
$self->{_num_vml_files} = $workbook->{_num_vml_files};
$self->{_num_comment_files} = $workbook->{_num_comment_files};
$self->{_named_ranges} = $workbook->{_named_ranges};
for my $worksheet ( @{ $self->{_workbook}->{_worksheets} } ) {
if ( $worksheet->{_is_chartsheet} ) {
$self->{_chartsheet_count}++;
}
else {
$self->{_worksheet_count}++;
}
}
}
###############################################################################
#
# _create_package()
#
# Write the xml files that make up the XLXS OPC package.
#
sub _create_package {
my $self = shift;
$self->_write_worksheet_files();
$self->_write_chartsheet_files();
$self->_write_workbook_file();
$self->_write_chart_files();
$self->_write_drawing_files();
$self->_write_vml_files();
$self->_write_comment_files();
$self->_write_table_files();
$self->_write_shared_strings_file();
$self->_write_app_file();
$self->_write_core_file();
$self->_write_custom_file();
$self->_write_content_types_file();
$self->_write_styles_file();
$self->_write_theme_file();
$self->_write_root_rels_file();
$self->_write_workbook_rels_file();
$self->_write_worksheet_rels_files();
$self->_write_chartsheet_rels_files();
$self->_write_drawing_rels_files();
$self->_write_rich_value_rels_files();
$self->_add_image_files();
$self->_add_vba_project();
$self->_write_metadata_file();
$self->_write_rich_value_files();
}
###############################################################################
#
# _write_workbook_file()
#
# Write the workbook.xml file.
#
sub _write_workbook_file {
my $self = shift;
my $dir = $self->{_package_dir};
my $workbook = $self->{_workbook};
_mkdir( $dir . '/xl' );
$workbook->_set_xml_writer( $dir . '/xl/workbook.xml' );
$workbook->_assemble_xml_file();
}
###############################################################################
#
# _write_worksheet_files()
#
# Write the worksheet files.
#
sub _write_worksheet_files {
my $self = shift;
my $dir = $self->{_package_dir};
_mkdir( $dir . '/xl' );
_mkdir( $dir . '/xl/worksheets' );
my $index = 1;
for my $worksheet ( @{ $self->{_workbook}->{_worksheets} } ) {
next if $worksheet->{_is_chartsheet};
$worksheet->_set_xml_writer(
$dir . '/xl/worksheets/sheet' . $index++ . '.xml' );
$worksheet->_assemble_xml_file();
}
}
###############################################################################
#
# _write_chartsheet_files()
#
# Write the chartsheet files.
#
sub _write_chartsheet_files {
my $self = shift;
my $dir = $self->{_package_dir};
my $index = 1;
for my $worksheet ( @{ $self->{_workbook}->{_worksheets} } ) {
next unless $worksheet->{_is_chartsheet};
_mkdir( $dir . '/xl' );
_mkdir( $dir . '/xl/chartsheets' );
$worksheet->_set_xml_writer(
$dir . '/xl/chartsheets/sheet' . $index++ . '.xml' );
$worksheet->_assemble_xml_file();
}
}
###############################################################################
#
# _write_chart_files()
#
# Write the chart files.
#
sub _write_chart_files {
my $self = shift;
my $dir = $self->{_package_dir};
return unless @{ $self->{_workbook}->{_charts} };
_mkdir( $dir . '/xl' );
_mkdir( $dir . '/xl/charts' );
my $index = 1;
for my $chart ( @{ $self->{_workbook}->{_charts} } ) {
$chart->_set_xml_writer(
$dir . '/xl/charts/chart' . $index++ . '.xml' );
$chart->_assemble_xml_file();
}
}
###############################################################################
#
# _write_drawing_files()
#
# Write the drawing files.
#
sub _write_drawing_files {
my $self = shift;
my $dir = $self->{_package_dir};
return unless $self->{_drawing_count};
_mkdir( $dir . '/xl' );
_mkdir( $dir . '/xl/drawings' );
my $index = 1;
for my $drawing ( @{ $self->{_workbook}->{_drawings} } ) {
$drawing->_set_xml_writer(
$dir . '/xl/drawings/drawing' . $index++ . '.xml' );
$drawing->_assemble_xml_file();
}
}
###############################################################################
#
# _write_vml_files()
#
# Write the comment VML files.
#
sub _write_vml_files {
my $self = shift;
my $dir = $self->{_package_dir};
my $index = 1;
for my $worksheet ( @{ $self->{_workbook}->{_worksheets} } ) {
next if !$worksheet->{_has_vml} and !$worksheet->{_has_header_vml};
_mkdir( $dir . '/xl' );
_mkdir( $dir . '/xl/drawings' );
if ( $worksheet->{_has_vml} ) {
my $vml = Excel::Writer::XLSX::Package::VML->new();
$vml->_set_xml_writer(
$dir . '/xl/drawings/vmlDrawing' . $index . '.vml' );
$vml->_assemble_xml_file(
$worksheet->{_vml_data_id}, $worksheet->{_vml_shape_id},
$worksheet->{_comments_array}, $worksheet->{_buttons_array},
undef
);
$index++;
}
if ( $worksheet->{_has_header_vml} ) {
my $vml = Excel::Writer::XLSX::Package::VML->new();
$vml->_set_xml_writer(
$dir . '/xl/drawings/vmlDrawing' . $index . '.vml' );
$vml->_assemble_xml_file(
$worksheet->{_vml_header_id},
$worksheet->{_vml_header_id} * 1024,
undef, undef, $worksheet->{_header_images_array}
);
$self->_write_vml_drawing_rels_file($worksheet, $index);
$index++;
}
}
}
###############################################################################
#
# _write_comment_files()
#
# Write the comment files.
#
sub _write_comment_files {
my $self = shift;
my $dir = $self->{_package_dir};
my $index = 1;
for my $worksheet ( @{ $self->{_workbook}->{_worksheets} } ) {
next unless $worksheet->{_has_comments};
my $comment = Excel::Writer::XLSX::Package::Comments->new();
_mkdir( $dir . '/xl' );
_mkdir( $dir . '/xl/drawings' );
$comment->_set_xml_writer( $dir . '/xl/comments' . $index++ . '.xml' );
$comment->_assemble_xml_file( $worksheet->{_comments_array} );
}
}
###############################################################################
#
# _write_shared_strings_file()
#
# Write the sharedStrings.xml file.
#
sub _write_shared_strings_file {
my $self = shift;
my $dir = $self->{_package_dir};
my $sst = Excel::Writer::XLSX::Package::SharedStrings->new();
my $total = $self->{_workbook}->{_str_total};
my $unique = $self->{_workbook}->{_str_unique};
my $sst_data = $self->{_workbook}->{_str_array};
return unless $total > 0;
_mkdir( $dir . '/xl' );
$sst->_set_string_count( $total );
$sst->_set_unique_count( $unique );
$sst->_add_strings( $sst_data );
$sst->_set_xml_writer( $dir . '/xl/sharedStrings.xml' );
$sst->_assemble_xml_file();
}
###############################################################################
#
# _write_app_file()
#
# Write the app.xml file.
#
sub _write_app_file {
my $self = shift;
my $dir = $self->{_package_dir};
my $properties = $self->{_workbook}->{_doc_properties};
my $app = Excel::Writer::XLSX::Package::App->new();
_mkdir( $dir . '/docProps' );
# Add the Worksheet parts.
my $worksheet_count = 0;
for my $worksheet ( @{ $self->{_workbook}->{_worksheets} } ) {
next if $worksheet->{_is_chartsheet};
# Don't write/count veryHidden sheets.
next if $worksheet->{_hidden} == 2;
$app->_add_part_name( $worksheet->get_name() );
$worksheet_count++;
}
# Add the Worksheet heading pairs.
$app->_add_heading_pair( [ 'Worksheets', $worksheet_count ] );
# Add the Chartsheet heading pairs.
$app->_add_heading_pair( [ 'Charts', $self->{_chartsheet_count} ] );
# Add the Chartsheet parts.
for my $worksheet ( @{ $self->{_workbook}->{_worksheets} } ) {
next unless $worksheet->{_is_chartsheet};
$app->_add_part_name( $worksheet->get_name() );
}
# Add the Named Range heading pairs.
if ( my $range_count = scalar @{ $self->{_named_ranges} } ) {
$app->_add_heading_pair( [ 'Named Ranges', $range_count ] );
}
# Add the Named Ranges parts.
for my $named_range ( @{ $self->{_named_ranges} } ) {
$app->_add_part_name( $named_range );
}
$app->_set_properties( $properties );
$app->{_doc_security} = $self->{_workbook}->{_read_only};
$app->_set_xml_writer( $dir . '/docProps/app.xml' );
$app->_assemble_xml_file();
}
###############################################################################
#
# _write_core_file()
#
# Write the core.xml file.
#
sub _write_core_file {
my $self = shift;
my $dir = $self->{_package_dir};
my $properties = $self->{_workbook}->{_doc_properties};
my $core = Excel::Writer::XLSX::Package::Core->new();
_mkdir( $dir . '/docProps' );
$core->_set_properties( $properties );
$core->_set_xml_writer( $dir . '/docProps/core.xml' );
$core->_assemble_xml_file();
}
###############################################################################
#
# _write_metadata_file()
#
# Write the metadata.xml file.
#
sub _write_metadata_file {
my $self = shift;
my $dir = $self->{_package_dir};
my $metadata = Excel::Writer::XLSX::Package::Metadata->new();
return if !$self->{_workbook}->{_has_metadata};
$metadata->{_has_dynamic_functions} = $self->{_workbook}->{_has_dynamic_functions};
$metadata->{_num_embedded_images} = @{ $self->{_workbook}->{_embedded_images} };
_mkdir( $dir . '/xl' );
$metadata->_set_xml_writer( $dir . '/xl/metadata.xml' );
$metadata->_assemble_xml_file();
}
###############################################################################
#
# _write_rich_value_files()
#
# Write the rdrichvalue(*).xml file.
#
sub _write_rich_value_files {
my $self = shift;
my $dir = $self->{_package_dir};
return if !$self->{_workbook}->{_has_embedded_images};
_mkdir( $dir . '/xl' );
_mkdir( $dir . '/xl/richData' );
$self->_write_rich_value_file();
$self->_write_rich_value_structure_file();
$self->_write_rich_value_types_file();
$self->_write_rich_value_rel();
}
###############################################################################
#
# _write_rich_value_file()
#
# Write the rdrichvalue.xml file.
#
sub _write_rich_value_file {
my $self = shift;
my $dir = $self->{_package_dir};
my $rich_value = Excel::Writer::XLSX::Package::RichValue->new();
$rich_value->{_embedded_images} = $self->{_workbook}->{_embedded_images};
$rich_value->_set_xml_writer( $dir . '/xl/richData/rdrichvalue.xml' );
$rich_value->_assemble_xml_file();
}
###############################################################################
#
# _write_rich_value_structure_file()
#
# Write the rdrichvaluestructure.xml file.
#
sub _write_rich_value_structure_file {
my $self = shift;
my $dir = $self->{_package_dir};
my $rich_value = Excel::Writer::XLSX::Package::RichValueStructure->new();
$rich_value->{_has_embedded_descriptions} = $self->{_workbook}->{_has_embedded_descriptions};
$rich_value->_set_xml_writer( $dir . '/xl/richData/rdrichvaluestructure.xml' );
$rich_value->_assemble_xml_file();
}
###############################################################################
#
# _write_rich_value_types_file()
#
# Write the rdRichValueTypes.xml file.
#
sub _write_rich_value_types_file {
my $self = shift;
my $dir = $self->{_package_dir};
my $rich_value = Excel::Writer::XLSX::Package::RichValueTypes->new();
$rich_value->_set_xml_writer( $dir . '/xl/richData/rdRichValueTypes.xml' );
$rich_value->_assemble_xml_file();
}
###############################################################################
#
# _write_rich_value_rel()
#
# Write the rdrichvalue.xml file.
#
sub _write_rich_value_rel {
my $self = shift;
my $dir = $self->{_package_dir};
my $rich_value = Excel::Writer::XLSX::Package::RichValueRel->new();
$rich_value->{_value_count} = @{ $self->{_workbook}->{_embedded_images} };
$rich_value->_set_xml_writer( $dir . '/xl/richData/richValueRel.xml' );
$rich_value->_assemble_xml_file();
}
###############################################################################
#
# _write_custom_file()
#
# Write the custom.xml file.
#
sub _write_custom_file {
my $self = shift;
my $dir = $self->{_package_dir};
my $properties = $self->{_workbook}->{_custom_properties};
my $custom = Excel::Writer::XLSX::Package::Custom->new();
return if !@$properties;
_mkdir( $dir . '/docProps' );
$custom->_set_properties( $properties );
$custom->_set_xml_writer( $dir . '/docProps/custom.xml' );
$custom->_assemble_xml_file();
}
###############################################################################
#
# _write_content_types_file()
#
# Write the ContentTypes.xml file.
#
sub _write_content_types_file {
my $self = shift;
my $dir = $self->{_package_dir};
my $content = Excel::Writer::XLSX::Package::ContentTypes->new();
$content->_add_image_types( %{ $self->{_workbook}->{_image_types} } );
my $worksheet_index = 1;
my $chartsheet_index = 1;
for my $worksheet ( @{ $self->{_workbook}->{_worksheets} } ) {
if ( $worksheet->{_is_chartsheet} ) {
$content->_add_chartsheet_name( 'sheet' . $chartsheet_index++ );
}
else {
$content->_add_worksheet_name( 'sheet' . $worksheet_index++ );
}
}
for my $i ( 1 .. $self->{_chart_count} ) {
$content->_add_chart_name( 'chart' . $i );
}
for my $i ( 1 .. $self->{_drawing_count} ) {
$content->_add_drawing_name( 'drawing' . $i );
}
if ( $self->{_num_vml_files} ) {
$content->_add_vml_name();
}
for my $i ( 1 .. $self->{_table_count} ) {
$content->_add_table_name( 'table' . $i );
}
for my $i ( 1 .. $self->{_num_comment_files} ) {
$content->_add_comment_name( 'comments' . $i );
}
# Add the sharedString rel if there is string data in the workbook.
if ( $self->{_workbook}->{_str_total} ) {
$content->_add_shared_strings();
}
# Add vbaProject if present.
if ( $self->{_workbook}->{_vba_project} ) {
$content->_add_vba_project();
}
# Add the custom properties if present.
if ( @{ $self->{_workbook}->{_custom_properties} } ) {
$content->_add_custom_properties();
}
# Add the metadata file if present.
if ( $self->{_workbook}->{_has_metadata} ) {
$content->_add_metadata();
}
# Add the RichValue file if present.
if ( $self->{_workbook}->{_has_embedded_images} ) {
$content->_add_richvalue();
}
$content->_set_xml_writer( $dir . '/[Content_Types].xml' );
$content->_assemble_xml_file();
}
###############################################################################
#
# _write_styles_file()
#
# Write the style xml file.
#
sub _write_styles_file {
my $self = shift;
my $dir = $self->{_package_dir};
my $xf_formats = $self->{_workbook}->{_xf_formats};
my $palette = $self->{_workbook}->{_palette};
my $font_count = $self->{_workbook}->{_font_count};
my $num_formats = $self->{_workbook}->{_num_formats};
my $border_count = $self->{_workbook}->{_border_count};
my $fill_count = $self->{_workbook}->{_fill_count};
my $custom_colors = $self->{_workbook}->{_custom_colors};
my $dxf_formats = $self->{_workbook}->{_dxf_formats};
my $has_comments = $self->{_workbook}->{_has_comments};
my $rels = Excel::Writer::XLSX::Package::Styles->new();
_mkdir( $dir . '/xl' );
$rels->_set_style_properties(
$xf_formats,
$palette,
$font_count,
$num_formats,
$border_count,
$fill_count,
$custom_colors,
$dxf_formats,
$has_comments,
);
$rels->_set_xml_writer( $dir . '/xl/styles.xml' );
$rels->_assemble_xml_file();
}
###############################################################################
#
# _write_theme_file()
#
# Write the style xml file.
#
sub _write_theme_file {
my $self = shift;
my $dir = $self->{_package_dir};
my $rels = Excel::Writer::XLSX::Package::Theme->new();
_mkdir( $dir . '/xl' );
_mkdir( $dir . '/xl/theme' );
$rels->_set_xml_writer( $dir . '/xl/theme/theme1.xml' );
$rels->_assemble_xml_file();
}
###############################################################################
#
# _write_table_files()
#
# Write the table files.
#
sub _write_table_files {
my $self = shift;
my $dir = $self->{_package_dir};
my $index = 1;
for my $worksheet ( @{ $self->{_workbook}->{_worksheets} } ) {
my @table_props = @{ $worksheet->{_tables} };
next unless @table_props;
_mkdir( $dir . '/xl' );
_mkdir( $dir . '/xl/tables' );
for my $table_props ( @table_props ) {
my $table = Excel::Writer::XLSX::Package::Table->new();
$table->_set_xml_writer(
$dir . '/xl/tables/table' . $index++ . '.xml' );
$table->_set_properties( $table_props );
$table->_assemble_xml_file();
$self->{_table_count}++;
}
}
}
###############################################################################
#
# _write_root_rels_file()
#
# Write the _rels/.rels xml file.
#
sub _write_root_rels_file {
my $self = shift;
my $dir = $self->{_package_dir};
my $rels = Excel::Writer::XLSX::Package::Relationships->new();
_mkdir( $dir . '/_rels' );
$rels->_add_document_relationship( '/officeDocument', 'xl/workbook.xml' );
$rels->_add_package_relationship( '/metadata/core-properties',
'docProps/core.xml' );
$rels->_add_document_relationship( '/extended-properties',
'docProps/app.xml' );
if ( @{ $self->{_workbook}->{_custom_properties} } ) {
$rels->_add_document_relationship( '/custom-properties',
'docProps/custom.xml' );
}
$rels->_set_xml_writer( $dir . '/_rels/.rels' );
$rels->_assemble_xml_file();
}
###############################################################################
#
# _write_workbook_rels_file()
#
# Write the _rels/.rels xml file.
#
sub _write_workbook_rels_file {
my $self = shift;
my $dir = $self->{_package_dir};
my $rels = Excel::Writer::XLSX::Package::Relationships->new();
_mkdir( $dir . '/xl' );
_mkdir( $dir . '/xl/_rels' );
my $worksheet_index = 1;
my $chartsheet_index = 1;
for my $worksheet ( @{ $self->{_workbook}->{_worksheets} } ) {
if ( $worksheet->{_is_chartsheet} ) {
$rels->_add_document_relationship( '/chartsheet',
'chartsheets/sheet' . $chartsheet_index++ . '.xml' );
}
else {
$rels->_add_document_relationship( '/worksheet',
'worksheets/sheet' . $worksheet_index++ . '.xml' );
}
}
$rels->_add_document_relationship( '/theme', 'theme/theme1.xml' );
$rels->_add_document_relationship( '/styles', 'styles.xml' );
# Add the sharedString rel if there is string data in the workbook.
if ( $self->{_workbook}->{_str_total} ) {
$rels->_add_document_relationship( '/sharedStrings',
'sharedStrings.xml' );
}
# Add vbaProject if present.
if ( $self->{_workbook}->{_vba_project} ) {
$rels->_add_ms_package_relationship( '/vbaProject', 'vbaProject.bin' );
}
# Add the metadata file if required.
if ( $self->{_workbook}->{_has_metadata} ) {
$rels->_add_document_relationship( '/sheetMetadata', 'metadata.xml' );
}
# Add the RichValue files if present.
if ( $self->{_workbook}->{_has_embedded_images} ) {
$rels->_add_rich_value_relationships();
}
$rels->_set_xml_writer( $dir . '/xl/_rels/workbook.xml.rels' );
$rels->_assemble_xml_file();
}
###############################################################################
#
# _write_worksheet_rels_files()
#
# Write the worksheet .rels files for worksheets that contain links to external
# data such as hyperlinks or drawings.
#
sub _write_worksheet_rels_files {
my $self = shift;
my $dir = $self->{_package_dir};
my $index = 0;
for my $worksheet ( @{ $self->{_workbook}->{_worksheets} } ) {
next if $worksheet->{_is_chartsheet};
$index++;
my @external_links = (
@{ $worksheet->{_external_hyper_links} },
@{ $worksheet->{_external_drawing_links} },
@{ $worksheet->{_external_vml_links} },
@{ $worksheet->{_external_background_links} },
@{ $worksheet->{_external_table_links} },
@{ $worksheet->{_external_comment_links} },
);
next unless @external_links;
# Create the worksheet .rels dirs.
_mkdir( $dir . '/xl' );
_mkdir( $dir . '/xl/worksheets' );
_mkdir( $dir . '/xl/worksheets/_rels' );
my $rels = Excel::Writer::XLSX::Package::Relationships->new();
for my $link_data ( @external_links ) {
$rels->_add_worksheet_relationship( @$link_data );
}
# Create the .rels file such as /xl/worksheets/_rels/sheet1.xml.rels.
$rels->_set_xml_writer(
$dir . '/xl/worksheets/_rels/sheet' . $index . '.xml.rels' );
$rels->_assemble_xml_file();
}
}
###############################################################################
#
# _write_rich_value_rels_files()
#
# Write the richValueRel.xml.rels files for worksheets that contain embedded
# images.
#
sub _write_rich_value_rels_files {
my $self = shift;
my $dir = $self->{_package_dir};
return unless @{ $self->{_workbook}->{_embedded_images} };
# Create the .rels dirs.
_mkdir( $dir . '/xl' );
_mkdir( $dir . '/xl/richData' );
_mkdir( $dir . '/xl/richData/_rels' );
my $rels = Excel::Writer::XLSX::Package::Relationships->new();
my $index = 0;
for my $image_data ( @{ $self->{_workbook}->{_embedded_images} } ) {
$index++;
my $file_type = $image_data->[1];
my $image_file = "../media/image$index.$file_type";
$rels->_add_worksheet_relationship( '/image', $image_file );
}
# Create the .rels file.
$rels->_set_xml_writer( $dir . '/xl/richData/_rels/richValueRel.xml.rels' );
$rels->_assemble_xml_file();
}
###############################################################################
#
# _write_chartsheet_rels_files()
#
# Write the chartsheet .rels files for links to drawing files.
#
sub _write_chartsheet_rels_files {
my $self = shift;
my $dir = $self->{_package_dir};
my $index = 0;
for my $worksheet ( @{ $self->{_workbook}->{_worksheets} } ) {
next unless $worksheet->{_is_chartsheet};
$index++;
my @external_links = (
@{ $worksheet->{_external_drawing_links} },
@{ $worksheet->{_external_vml_links} },
);
next unless @external_links;
# Create the chartsheet .rels dir.
_mkdir( $dir . '/xl' );
_mkdir( $dir . '/xl/chartsheets' );
_mkdir( $dir . '/xl/chartsheets/_rels' );
my $rels = Excel::Writer::XLSX::Package::Relationships->new();
for my $link_data ( @external_links ) {
$rels->_add_worksheet_relationship( @$link_data );
}
# Create the .rels file such as /xl/chartsheets/_rels/sheet1.xml.rels.
$rels->_set_xml_writer(
$dir . '/xl/chartsheets/_rels/sheet' . $index . '.xml.rels' );
$rels->_assemble_xml_file();
}
}
###############################################################################
#
# _write_drawing_rels_files()
#
# Write the drawing .rels files for worksheets that contain charts or drawings.
#
sub _write_drawing_rels_files {
my $self = shift;
my $dir = $self->{_package_dir};
my $index = 0;
for my $worksheet ( @{ $self->{_workbook}->{_worksheets} } ) {
if ( @{ $worksheet->{_drawing_links} } || $worksheet->{_has_shapes} ) {
$index++;
}
next unless @{ $worksheet->{_drawing_links} };
# Create the drawing .rels dir.
_mkdir( $dir . '/xl' );
_mkdir( $dir . '/xl/drawings' );
_mkdir( $dir . '/xl/drawings/_rels' );
my $rels = Excel::Writer::XLSX::Package::Relationships->new();
for my $drawing_data ( @{ $worksheet->{_drawing_links} } ) {
$rels->_add_document_relationship( @$drawing_data );
}
# Create the .rels file such as /xl/drawings/_rels/sheet1.xml.rels.
$rels->_set_xml_writer(
$dir . '/xl/drawings/_rels/drawing' . $index . '.xml.rels' );
$rels->_assemble_xml_file();
}
}
###############################################################################
#
# _write_vml_drawing_rels_files()
#
# Write the vmlDdrawing .rels files for worksheets with images in header or
# footers.
#
sub _write_vml_drawing_rels_file {
my $self = shift;
my $worksheet = shift;
my $index = shift;
my $dir = $self->{_package_dir};
# Create the drawing .rels dir.
_mkdir( $dir . '/xl' );
_mkdir( $dir . '/xl/drawings' );
_mkdir( $dir . '/xl/drawings/_rels' );
my $rels = Excel::Writer::XLSX::Package::Relationships->new();
for my $drawing_data ( @{ $worksheet->{_vml_drawing_links} } ) {
$rels->_add_document_relationship( @$drawing_data );
}
# Create the .rels file such as /xl/drawings/_rels/vmlDrawing1.vml.rels.
$rels->_set_xml_writer(
$dir . '/xl/drawings/_rels/vmlDrawing' . $index . '.vml.rels' );
$rels->_assemble_xml_file();
}
###############################################################################
#
# _add_image_files()
#
# Write the /xl/media/image?.xml files.
#
sub _add_image_files {
my $self = shift;
my $dir = $self->{_package_dir};
my $workbook = $self->{_workbook};
my $index = 1;
for my $image ( @{ $workbook->{_embedded_images} } ) {
my $filename = $image->[0];
my $extension = '.' . $image->[1];
_mkdir( $dir . '/xl' );
_mkdir( $dir . '/xl/media' );
copy( $filename, $dir . '/xl/media/image' . $index++ . $extension );
}
for my $image ( @{ $workbook->{_images} } ) {
my $filename = $image->[0];
my $extension = '.' . $image->[1];
_mkdir( $dir . '/xl' );
_mkdir( $dir . '/xl/media' );
copy( $filename, $dir . '/xl/media/image' . $index++ . $extension );
}
}
###############################################################################
#
# _add_vba_project()
#
# Write the vbaProject.bin file.
#
sub _add_vba_project {
my $self = shift;
my $dir = $self->{_package_dir};
my $vba_project = $self->{_workbook}->{_vba_project};
return unless $vba_project;
_mkdir( $dir . '/xl' );
copy( $vba_project, $dir . '/xl/vbaProject.bin' );
}
###############################################################################
#
# _mkdir()
#
# Wrapper function for Perl's mkdir to allow error trapping.
#
sub _mkdir {
my $dir = shift;
return if -e $dir;
my $ret = mkdir( $dir );
if ( !$ret ) {
croak "Couldn't create sub directory $dir: $!";
}
}
1;
__END__
=pod
=head1 NAME
Packager - A class for creating the Excel XLSX package.
=head1 SYNOPSIS
See the documentation for L<Excel::Writer::XLSX>.
=head1 DESCRIPTION
This module is used in conjunction with L<Excel::Writer::XLSX> to create an Excel XLSX container file.
From Wikipedia: I<The Open Packaging Conventions (OPC) is a container-file technology initially created by Microsoft to store a combination of XML and non-XML files that together form a single entity such as an Open XML Paper Specification (OpenXPS) document>. L<http://en.wikipedia.org/wiki/Open_Packaging_Conventions>.
At its simplest an Excel XLSX file contains the following elements:
____ [Content_Types].xml
|
|____ docProps
| |____ app.xml
| |____ core.xml
|
|____ xl
| |____ workbook.xml
| |____ worksheets
| | |____ sheet1.xml
| |
| |____ styles.xml
| |
| |____ theme
| | |____ theme1.xml
| |
| |_____rels
| |____ workbook.xml.rels
|
|_____rels
|____ .rels
The C<Excel::Writer::XLSX::Package::Packager> class co-ordinates the classes that represent the elements of the package and writes them into the XLSX file.
=head1 AUTHOR
John McNamara jmcnamara@cpan.org
=head1 COPYRIGHT
(c) MM-MMXXIV, John McNamara.
All Rights Reserved. This module is free software. It may be used, redistributed and/or modified under the same terms as Perl itself.
=head1 LICENSE
Either the Perl Artistic Licence L<https://dev.perl.org/licenses/artistic.html> or the GNU General Public License v1.0 or later L<https://dev.perl.org/licenses/gpl1.html>.
=head1 DISCLAIMER OF WARRANTY
See the documentation for L<Excel::Writer::XLSX>.
=cut