The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.
package Spreadsheet::Template::Generator::Parser::Excel;
BEGIN {
  $Spreadsheet::Template::Generator::Parser::Excel::AUTHORITY = 'cpan:DOY';
}
$Spreadsheet::Template::Generator::Parser::Excel::VERSION = '0.03';
use Moose::Role;

use DateTime::Format::Excel;
use JSON;

with 'Spreadsheet::Template::Generator::Parser';

requires '_create_workbook';

has _excel_dt => (
    is      => 'ro',
    isa     => 'DateTime::Format::Excel',
    lazy    => 1,
    default => sub { DateTime::Format::Excel->new },
);

sub parse {
    my $self = shift;
    my ($filename) = @_;
    my $book = $self->_create_workbook($filename);
    return $self->_parse_workbook($book);
}

sub _parse_workbook {
    my $self = shift;
    my ($book) = @_;

    my $data = {
        selection  => $book->{SelectedSheet}, # XXX
        worksheets => [],
    };

    if ($book->using_1904_date) {
        $self->_excel_dt->epoch_mac;
    }

    for my $sheet ($book->worksheets) {
        push @{ $data->{worksheets} }, $self->_parse_worksheet($book, $sheet);
    }

    return $data;
}

sub _parse_worksheet {
    my $self = shift;
    my ($book, $sheet) = @_;

    my $data = {
        name          => $sheet->get_name,
        row_heights   => [ $sheet->get_row_heights ],
        column_widths => [ $sheet->get_col_widths ],
        selection     => $sheet->{Selection}, # XXX
        cells         => [],
    };

    my ($rmin, $rmax) = $sheet->row_range;
    my ($cmin, $cmax) = $sheet->col_range;

    splice @{ $data->{row_heights} }, $rmax + 1;
    splice @{ $data->{column_widths} }, $cmax + 1;

    for my $row (0..$rmin - 1) {
        push @{ $data->{cells} }, [];
    }

    for my $row ($rmin..$rmax) {
        my $row_data = [];
        for my $col (0..$cmin - 1) {
            push @$row_data, {};
        }
        for my $col ($cmin..$cmax) {
            if (my $cell = $sheet->get_cell($row, $col)) {
                push @$row_data, $self->_parse_cell($book, $cell);
            }
            else {
                push @$row_data, {};
            }
        }
        push @{ $data->{cells} }, $row_data
    }

    return $data;
}

sub _parse_cell {
    my $self = shift;
    my ($book, $cell) = @_;

    my $contents = $cell->unformatted;
    my $type = $cell->type;
    my $formula = $cell->{Formula}; # XXX
    my $format = $cell->get_format;

    if ($type eq 'Numeric') {
        $type = 'number';
    }
    elsif ($type eq 'Text') {
        $type = 'string';
    }
    elsif ($type eq 'Date') {
        $type = 'date_time';
        $contents = $self->_excel_dt->parse_datetime($contents)->iso8601
            if defined $contents && length $contents;
    }
    else {
        die "unknown type $type";
    }

    my $format_data = {};
    if ($format) {
        my %halign = (
            0 => 'none',
            1 => 'left',
            2 => 'center',
            3 => 'right',
            4 => 'fill',
            5 => 'justify',
            6 => 'center_across',
            # XXX this isn't supported by Spreadsheet::WriteExcel
            7 => 'distributed',
        );

        my %valign = (
            0 => 'top',
            1 => 'vcenter',
            2 => 'bottom',
            3 => 'vjustify',
            # XXX this isn't supported by Spreadsheet::WriteExcel
            4 => 'vdistributed',
        );

        my %border = (
            0  => 'none',
            1  => 'thin',
            2  => 'medium',
            3  => 'dashed',
            4  => 'dotted',
            5  => 'thick',
            6  => 'double',
            7  => 'hair',
            8  => 'medium_dashed',
            9  => 'dash_dot',
            10 => 'medium_dash_dot',
            11 => 'dash_dot_dot',
            12 => 'medium_dash_dot_dot',
            13 => 'slant_dash_dot',
        );

        my %fill = (
            0  => 'none',
            1  => 'solid',
            2  => 'medium_gray',
            3  => 'dark_gray',
            4  => 'light_gray',
            5  => 'dark_horizontal',
            6  => 'dark_vertical',
            7  => 'dark_down',
            8  => 'dark_up',
            9  => 'dark_grid',
            10 => 'dark_trellis',
            11 => 'light_horizontal',
            12 => 'light_vertical',
            13 => 'light_down',
            14 => 'light_up',
            15 => 'light_grid',
            16 => 'light_trellis',
            17 => 'gray_125',
            18 => 'gray_0625',
        );

        if (!$format->{IgnoreFont}) {
            $format_data->{size} = $format->{Font}{Height};
            $format_data->{color} = $format->{Font}{Color}
                unless lc($format->{Font}{Color}) eq '#ffffff'; # XXX
            $format_data->{bold} = JSON::true
                if $format->{Font}{Bold};
            $format_data->{italic} = JSON::true
                if $format->{Font}{Italic};
        }
        if (!$format->{IgnoreFill}) {
            if ($format->{Fill}[0] != 0) {
                $format_data->{pattern}  = $fill{$format->{Fill}[0]};
                # XXX this seems pretty wrong, but... not sure what the
                # actually right way is, and this works for now
                if ($format_data->{pattern} eq 'solid') {
                    $format_data->{bg_color} = $format->{Fill}[1];
                }
                else {
                    $format_data->{fg_color} = $format->{Fill}[1];
                    $format_data->{bg_color} = $format->{Fill}[2];
                }
            }
        }
        if (!$format->{IgnoreBorder}) {
            $format_data->{border_color} = $format->{BdrColor};
            if (grep { $_ != 0 } @{ $format->{BdrStyle} }) { # XXX
                $format_data->{border} = [
                    map { $border{$_} } @{ $format->{BdrStyle} }
                ];
            }
        }
        if (!$format->{IgnoreAlignment}) {
            $format_data->{align} = $halign{$format->{AlignH}}
                unless $format->{AlignH} == 0;
            $format_data->{valign} = $valign{$format->{AlignV}}
                unless $format->{AlignV} == 2;
            $format_data->{text_wrap} = JSON::true
                if $format->{Wrap};
        }
        if (!$format->{IgnoreNumberFormat}) {
            $format_data->{num_format} = $book->{FormatStr}{$format->{FmtIdx}}
                unless $book->{FormatStr}{$format->{FmtIdx}} eq 'GENERAL';
        }
    }

    my $data = {
        contents => $contents,
        type     => $type,
        ($formula ? (formula => $formula) : ()),
        (keys %$format_data ? (format => $format_data) : ()),
    };

    return $data;
}

no Moose::Role;

=begin Pod::Coverage

  parse

=end Pod::Coverage

=cut

1;