From 8eb0b708cc1d5f99415f7ec9c5e34d2cf01b645d Mon Sep 17 00:00:00 2001 From: Daisuke Murase Date: Wed, 25 Jul 2012 09:00:52 +0900 Subject: [PATCH] initial commit --- .gitignore | 6 + .shipit | 4 + .travis.yml | 3 + MANIFEST.SKIP | 19 +++ Makefile.PL | 38 ++++++ lib/Data/XLSX/Parser.pm | 61 +++++++++ lib/Data/XLSX/Parser/DocumentArchive.pm | 40 ++++++ lib/Data/XLSX/Parser/SharedStrings.pm | 66 ++++++++++ lib/Data/XLSX/Parser/Sheet.pm | 158 ++++++++++++++++++++++++ lib/Data/XLSX/Parser/Styles.pm | 139 +++++++++++++++++++++ lib/Data/XLSX/Parser/Workbook.pm | 56 +++++++++ t/1_____loreyna126.t | 33 +++++ t/1_____loreyna126.xlsx | Bin 0 -> 30722 bytes t/2_____with_chart.t | 36 ++++++ t/2_____with_chart.xlsx | Bin 0 -> 10457 bytes xt/sharedstrings.t | 23 ++++ xt/sheet.t | 24 ++++ xt/styles.t | 19 +++ xt/workbook.t | 22 ++++ 19 files changed, 747 insertions(+) create mode 100644 .gitignore create mode 100644 .shipit create mode 100644 .travis.yml create mode 100644 MANIFEST.SKIP create mode 100644 Makefile.PL create mode 100644 lib/Data/XLSX/Parser.pm create mode 100644 lib/Data/XLSX/Parser/DocumentArchive.pm create mode 100644 lib/Data/XLSX/Parser/SharedStrings.pm create mode 100644 lib/Data/XLSX/Parser/Sheet.pm create mode 100644 lib/Data/XLSX/Parser/Styles.pm create mode 100644 lib/Data/XLSX/Parser/Workbook.pm create mode 100644 t/1_____loreyna126.t create mode 100755 t/1_____loreyna126.xlsx create mode 100644 t/2_____with_chart.t create mode 100755 t/2_____with_chart.xlsx create mode 100644 xt/sharedstrings.t create mode 100644 xt/sheet.t create mode 100644 xt/styles.t create mode 100644 xt/workbook.t diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..7b0c909 --- /dev/null +++ b/.gitignore @@ -0,0 +1,6 @@ +Makefile +README +blib/ +inc/ +test.pl +private-* \ No newline at end of file diff --git a/.shipit b/.shipit new file mode 100644 index 0000000..2235740 --- /dev/null +++ b/.shipit @@ -0,0 +1,4 @@ +steps = FindVersion, ChangeVersion, CheckChangeLog, DistTest, Commit, Tag, MakeDist, UploadCPAN + +git.tagpattern = %v +git.push_to = origin diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..73e8e2a --- /dev/null +++ b/.travis.yml @@ -0,0 +1,3 @@ +language: "perl" +before_install: + - perl Makefile.PL | cpanm diff --git a/MANIFEST.SKIP b/MANIFEST.SKIP new file mode 100644 index 0000000..aa7d6df --- /dev/null +++ b/MANIFEST.SKIP @@ -0,0 +1,19 @@ +\bRCS\b +\bCVS\b +^MANIFEST\. +^Makefile$ +~$ +\.old$ +^blib/ +^pm_to_blib +^MakeMaker-\d +\.gz$ +\.cvsignore +^9\d_.*\.t +^\.git/ +^\.shipit$ +\.gitignore$ +/\.git/ +^\.gitmodules$ +^\.travis.yml$ +^README\.md$ diff --git a/Makefile.PL b/Makefile.PL new file mode 100644 index 0000000..0a55b12 --- /dev/null +++ b/Makefile.PL @@ -0,0 +1,38 @@ +use strict; +use warnings; + +BEGIN { + my @devmods = qw( + Module::Install::AuthorTests + Module::Install::ReadmeFromPod + Module::Install::Repository + ); + my @not_available; + + eval qq{use inc::Module::Install; 1;} or push @not_available, 'inc::Module::Install'; + for my $mod (@devmods) { + eval qq{require $mod} or push @not_available, $mod; + } + if (@not_available) { + print qq{# The following modules are not available.\n}; + print qq{# `$^X $0 | cpanm` will install them:\n}; + print $_, "\n" for @not_available; + print "\n"; + exit -1; + } +} + +use inc::Module::Install; + +name 'Data-XLSX-Parser'; +all_from 'lib/Data/XLSX/Parser.pm'; + +readme_from 'lib/Data/XLSX/Parser.pm'; +author_tests 'xt'; +auto_set_repository; + +requires 'Archive::Zip'; +requires 'XML::Parser::Expat'; +requires 'File::Temp'; + +WriteAll; diff --git a/lib/Data/XLSX/Parser.pm b/lib/Data/XLSX/Parser.pm new file mode 100644 index 0000000..d52514f --- /dev/null +++ b/lib/Data/XLSX/Parser.pm @@ -0,0 +1,61 @@ +package Data::XLSX::Parser; +use strict; +use warnings; + +use Data::XLSX::Parser::DocumentArchive; +use Data::XLSX::Parser::Workbook; +use Data::XLSX::Parser::SharedStrings; +use Data::XLSX::Parser::Styles; +use Data::XLSX::Parser::Sheet; + +sub new { + my ($class) = @_; + + bless { + _row_event_handler => [], + _archive => undef, + _workbook => undef, + _shared_strings => undef, + }, $class; +} + +sub add_row_event_handler { + my ($self, $handler) = @_; + push @{ $self->{_row_event_handler} }, $handler; +} + +sub open { + my ($self, $file) = @_; + $self->{_archive} = Data::XLSX::Parser::DocumentArchive->new($file); +} + +sub workbook { + my ($self) = @_; + $self->{_workbook} ||= Data::XLSX::Parser::Workbook->new($self->{_archive}); +} + +sub shared_strings { + my ($self) = @_; + $self->{_shared_strings} ||= Data::XLSX::Parser::SharedStrings->new($self->{_archive}); +} + +sub styles { + my ($self) = @_; + $self->{_styles} ||= Data::XLSX::Parser::Styles->new($self->{_archive}); +} + +sub sheet { + my ($self, $sheet_id) = @_; + $self->{_sheet} ||= Data::XLSX::Parser::Sheet->new($self, $self->{_archive}, $sheet_id); +} + +sub _row_event { + my ($self, $row) = @_; + + my $row_vals = [map { $_->{v} } @$row]; + for my $handler (@{ $self->{_row_event_handler} }) { + $handler->($row_vals); + } +} + +1; diff --git a/lib/Data/XLSX/Parser/DocumentArchive.pm b/lib/Data/XLSX/Parser/DocumentArchive.pm new file mode 100644 index 0000000..86a55ef --- /dev/null +++ b/lib/Data/XLSX/Parser/DocumentArchive.pm @@ -0,0 +1,40 @@ +package Data::XLSX::Parser::DocumentArchive; +use strict; +use warnings; + +use Archive::Zip; + +sub new { + my ($class, $filename) = @_; + + my $zip = Archive::Zip->new; + if ($zip->read($filename) != Archive::Zip::AZ_OK) { + die "Cannot open file: $filename"; + } + + bless { + _zip => $zip, + }, $class; +} + +sub workbook { + my ($self) = @_; + $self->{_zip}->memberNamed('xl/workbook.xml'); +} + +sub sheet { + my ($self, $id) = @_; + $self->{_zip}->memberNamed(sprintf 'xl/worksheets/sheet%s.xml', $id); +} + +sub shared_strings { + my ($self) = @_; + $self->{_zip}->memberNamed('xl/sharedStrings.xml'); +} + +sub styles { + my ($self) = @_; + $self->{_zip}->memberNamed('xl/styles.xml'); +} + +1; diff --git a/lib/Data/XLSX/Parser/SharedStrings.pm b/lib/Data/XLSX/Parser/SharedStrings.pm new file mode 100644 index 0000000..28d3c4f --- /dev/null +++ b/lib/Data/XLSX/Parser/SharedStrings.pm @@ -0,0 +1,66 @@ +package Data::XLSX::Parser::SharedStrings; +use strict; +use warnings; + +use XML::Parser::Expat; +use Archive::Zip (); +use File::Temp; + +sub new { + my ($class, $archive) = @_; + + my $self = bless { + _data => [], + + _is_string => 0, + _buf => '', + }, $class; + + my $fh = File::Temp->new( SUFFIX => '.xml' ); + + my $handle = $archive->shared_strings or return $self; + die 'Failed to write temporally file: ', $fh->filename + unless $handle->extractToFileNamed($fh->filename) == Archive::Zip::AZ_OK; + + my $parser = XML::Parser::Expat->new; + $parser->setHandlers( + Start => sub { $self->_start(@_) }, + End => sub { $self->_end(@_) }, + Char => sub { $self->_char(@_) }, + ); + $parser->parse($fh); + + $self; +} + +sub count { + my ($self) = @_; + scalar @{ $self->{_data} }; +} + +sub get { + my ($self, $index) = @_; + $self->{_data}->[$index]; +} + +sub _start { + my ($self, $parser, $name, %attrs) = @_; + $self->{_is_string} = 1 if $name eq 'si'; +} + +sub _end { + my ($self, $parser, $name) = @_; + $self->{_is_string} = 0; + + if ($name eq 'si') { + push @{ $self->{_data} }, $self->{_buf}; + $self->{_buf} = ''; + } +} + +sub _char { + my ($self, $parser, $data) = @_; + $self->{_buf} .= $data if $self->{_is_string}; +} + +1; diff --git a/lib/Data/XLSX/Parser/Sheet.pm b/lib/Data/XLSX/Parser/Sheet.pm new file mode 100644 index 0000000..ad8dd97 --- /dev/null +++ b/lib/Data/XLSX/Parser/Sheet.pm @@ -0,0 +1,158 @@ +package Data::XLSX::Parser::Sheet; +use strict; +use warnings; + +use File::Temp; +use XML::Parser::Expat; +use Archive::Zip (); + +use constant { + STYLE_IDX => 'i', + STYLE => 's', + FMT => 'f', + REF => 'r', + COLUMN => 'c', + VALUE => 'v', + TYPE => 't', + TYPE_SHARED_STRING => 's', + GENERATED_CELL => 'g', +}; + +sub new { + my ($class, $doc, $archive, $sheet_id) = @_; + + my $self = bless { + _document => $doc, + + _data => '', + _is_sheetdata => 0, + _row_count => 0, + _current_row => [], + _cell => undef, + _is_value => 0, + + _shared_strings => $doc->shared_strings, + _styles => $doc->styles, + + }, $class; + + my $fh = File::Temp->new( SUFFIX => '.xml' ); + + my $handle = $archive->sheet($sheet_id); + die 'Failed to write temporally file: ', $fh->filename + unless $handle->extractToFileNamed($fh->filename) == Archive::Zip::AZ_OK; + + my $parser = XML::Parser::Expat->new; + $parser->setHandlers( + Start => sub { $self->_start(@_) }, + End => sub { $self->_end(@_) }, + Char => sub { $self->_char(@_) }, + ); + $parser->parse($fh); + + $self; +} + +sub _start { + my ($self, $parser, $name, %attrs) = @_; + + if ($name eq 'sheetData') { + $self->{_is_sheetdata} = 1; + } + elsif ($self->{_is_sheetdata} and $name eq 'row') { + $self->{_current_row} = []; + } + elsif ($name eq 'c') { + $self->{_cell} = { + STYLE_IDX() => $attrs{ STYLE_IDX() }, + TYPE() => $attrs{ TYPE() }, + REF() => $attrs{ REF() }, + COLUMN() => scalar(@{ $self->{_current_row} }) + 1, + }; + } + elsif ($name eq 'v') { + $self->{_is_value} = 1; + } +} + +sub _end { + my ($self, $parser, $name) = @_; + + if ($name eq 'sheetData') { + $self->{_is_sheetdata} = 0; + } + elsif ($self->{_is_sheetdata} and $name eq 'row') { + $self->{_row_count}++; + $self->{_document}->_row_event( delete $self->{_current_row} ); + } + elsif ($name eq 'c') { + my $c = $self->{_cell}; + $self->_parse_rel($c); + + if (($c->{ TYPE() } || '') eq TYPE_SHARED_STRING()) { + my $idx = int($self->{_data}); + $c->{ VALUE() } = $self->{_shared_strings}->get($idx); + } + else { + $c->{ VALUE() } = $self->{_data}; + } + + $c->{ STYLE() } = $self->{_styles}->cell_style( $c->{ STYLE_IDX() } ); + $c->{ FMT() } = my $cell_type = + $self->{_styles}->cell_type_from_style($c->{ STYLE() }); + + my $v = $c->{ VALUE() }; + if (defined $v and $c->{ FMT() } =~ /^datetime\.(date)?(time)?$/) { + # datetime + warn 'datetime'; + } + else { + if (!defined $v) { + $c->{ VALUE() } = ''; + } + elsif ($cell_type ne 'unicode') { + warn 'not unicode'; + $c->{ VALUE() } = $v; + } + } + + push @{ $self->{_current_row} }, $c; + + $self->{_data} = ''; + $self->{_cell} = undef; + } + elsif ($name eq 'v') { + $self->{_is_value} = 0; + } +} + +sub _char { + my ($self, $parser, $data) = @_; + + if ($self->{_is_value}) { + $self->{_data} .= $data; + } +} + +sub _parse_rel { + my ($self, $cell) = @_; + + my ($column, $row) = $cell->{ REF() } =~ /([A-Z]+)(\d+)/; + + my $v = 0; + my $i = 0; + for my $ch (split '', $column) { + my $s = length($column) - $i++ - 1; + $v += (ord($ch) - ord('A') + 1) * (26**$s); + } + + $cell->{ REF() } = [$v, $row]; + + if ($cell->{ COLUMN() } > $v) { + die sprintf 'Detected smaller index than current cell, something is wrong! (row %s): %s <> %s', $row, $v, $cell->{ COLUMN() }; + } +} + +1; + + diff --git a/lib/Data/XLSX/Parser/Styles.pm b/lib/Data/XLSX/Parser/Styles.pm new file mode 100644 index 0000000..8bf872c --- /dev/null +++ b/lib/Data/XLSX/Parser/Styles.pm @@ -0,0 +1,139 @@ +package Data::XLSX::Parser::Styles; +use strict; +use warnings; + +use XML::Parser::Expat; +use Archive::Zip (); +use File::Temp; + +use constant BUILTIN_FMT => 0; +use constant BUILTIN_TYPE => 1; + +use constant BUILTIN_NUM_FMTS => [ + ['@', 'unicode'], # 0x00 + ['0', 'int'], # 0x01 + ['0.00', 'float'], # 0x02 + ['#,##0', 'float'], # 0x03 + ['#,##0.00', 'float'], # 0x04 + ['($#,##0_);($#,##0)', 'float'], # 0x05 + ['($#,##0_);[RED]($#,##0)', 'float'], # 0x06 + ['($#,##0.00_);($#,##0.00_)', 'float'], # 0x07 + ['($#,##0.00_);[RED]($#,##0.00_)', 'float'], # 0x08 + ['0%', 'int'], # 0x09 + ['0.00%', 'float'], # 0x0a + ['0.00E+00', 'float'], # 0x0b + ['# ?/?', 'float'], # 0x0c + ['# ??/??', 'float'], # 0x0d + ['m-d-yy', 'datetime.date'], # 0x0e + ['d-mmm-yy', 'datetime.date'], # 0x0f + ['d-mmm', 'datetime.date'], # 0x10 + ['mmm-yy', 'datetime.date'], # 0x11 + ['h:mm AM/PM', 'datetime.time'], # 0x12 + ['h:mm:ss AM/PM', 'datetime.time'], # 0x13 + ['h:mm', 'datetime.time'], # 0x14 + ['h:mm:ss', 'datetime.time'], # 0x15 + ['m-d-yy h:mm', 'datetime.datetime'], # 0x16 + #0x17-0x24 -- Differs in Natinal + undef, # 0x17 + undef, # 0x18 + undef, # 0x19 + undef, # 0x1a + undef, # 0x1b + undef, # 0x1c + undef, # 0x1d + undef, # 0x1e + undef, # 0x1f + undef, # 0x20 + undef, # 0x21 + undef, # 0x22 + undef, # 0x23 + undef, # 0x24 + ['(#,##0_);(#,##0)', 'int'], # 0x25 + ['(#,##0_);[RED](#,##0)', 'int'], # 0x26 + ['(#,##0.00);(#,##0.00)', 'float'], # 0x27 + ['(#,##0.00);[RED](#,##0.00)', 'float'], # 0x28 + ['_(*#,##0_);_(*(#,##0);_(*"-"_);_(@_)', 'float'], # 0x29 + ['_($*#,##0_);_($*(#,##0);_(*"-"_);_(@_)', 'float'], # 0x2a + ['_(*#,##0.00_);_(*(#,##0.00);_(*"-"??_);_(@_)', 'float'], # 0x2b + ['_($*#,##0.00_);_($*(#,##0.00);_(*"-"??_);_(@_)', 'float'], # 0x2c + ['mm:ss', 'datetime.timedelta'], # 0x2d + ['[h]:mm:ss', 'datetime.timedelta'], # 0x2e + ['mm:ss.0', 'datetime.timedelta'], # 0x2f + ['##0.0E+0', 'float'], # 0x30 + ['@', 'unicode'], # 0x31 +]; + +sub new { + my ($class, $archive) = @_; + + my $self = bless { + _number_formats => [], + + _is_cell_xfs => 0, + _current_style => undef, + }, $class; + + my $fh = File::Temp->new( SUFFIX => '.xml' ); + + my $handle = $archive->styles; + die 'Failed to write temporally file: ', $fh->filename + unless $handle->extractToFileNamed($fh->filename) == Archive::Zip::AZ_OK; + + my $parser = XML::Parser::Expat->new; + $parser->setHandlers( + Start => sub { $self->_start(@_) }, + End => sub { $self->_end(@_) }, + Char => sub { }, + ); + $parser->parse($fh); + + $self; +} + +sub cell_style { + my ($self, $style_id) = @_; + $style_id ||= 0; + $self->{_number_formats}[int $style_id]; +} + +sub cell_type_from_style { + my ($self, $style) = @_; + BUILTIN_NUM_FMTS->[ $style->{numFmt} ][BUILTIN_TYPE]; +} + +sub cell_format_from_style { + my ($self, $style) = @_; + BUILTIN_NUM_FMTS->[ $style->{numFmt} ][BUILTIN_FMT]; +} + +sub _start { + my ($self, $parser, $name, %attrs) = @_; + + if ($name eq 'cellXfs') { + $self->{_is_cell_xfs} = 1; + } + elsif ($self->{_is_cell_xfs} and $name eq 'xf') { + $self->{_current_style} = { + numFmt => int($attrs{numFmtId}) || 0, + exists $attrs{fontId} ? ( font => $attrs{fontId} ) : (), + exists $attrs{fillId} ? ( fill => $attrs{fillId} ) : (), + exists $attrs{borderId} ? ( border => $attrs{borderId} ) : (), + exists $attrs{xfId} ? ( xf => $attrs{xfId} ) : (), + exists $attrs{applyFont} ? ( applyFont => $attrs{applyFont} ) : (), + exists $attrs{applyNumberFormat} ? ( applyNumFmt => $attrs{applyNumberFormat} ) : (), + }; + } +} + +sub _end { + my ($self, $parser, $name) = @_; + + if ($name eq 'cellXfs') { + $self->{_is_cell_xfs} = 0; + } + elsif ($self->{_current_style} and $name eq 'xf') { + push @{ $self->{_number_formats } }, delete $self->{_current_style}; + } +} + +1; diff --git a/lib/Data/XLSX/Parser/Workbook.pm b/lib/Data/XLSX/Parser/Workbook.pm new file mode 100644 index 0000000..f6bf326 --- /dev/null +++ b/lib/Data/XLSX/Parser/Workbook.pm @@ -0,0 +1,56 @@ +package Data::XLSX::Parser::Workbook; +use strict; +use warnings; + +use XML::Parser::Expat; +use Archive::Zip (); +use File::Temp; + +sub new { + my ($class, $archive) = @_; + + my $self = bless [], $class; + + my $fh = File::Temp->new( SUFFIX => '.xml' ); + + my $handle = $archive->workbook; + die 'Failed to write temporally file: ', $fh->filename + unless $handle->extractToFileNamed($fh->filename) == Archive::Zip::AZ_OK; + + my $parser = XML::Parser::Expat->new; + $parser->setHandlers( + Start => sub { $self->_start(@_) }, + End => sub {}, + Char => sub {}, + ); + $parser->parse($fh); + + $self; +} + +sub names { + my ($self) = @_; + map { $_->{name} } @$self; +} + +sub sheet_id { + my ($self, $name) = @_; + + my ($meta) = grep { $_->{name} eq $name } @$self + or return; + + if ($meta->{'r:id'}) { + (my $r = $meta->{'r:id'}) =~ s/^rId//; + return $r; + } + else { + return $meta->{sheetId}; + } +} + +sub _start { + my ($self, $parser, $el, %attr) = @_; + push @$self, \%attr if $el eq 'sheet'; +} + +1; diff --git a/t/1_____loreyna126.t b/t/1_____loreyna126.t new file mode 100644 index 0000000..5bf360f --- /dev/null +++ b/t/1_____loreyna126.t @@ -0,0 +1,33 @@ +use strict; +use warnings; +use utf8; + +use FindBin; +use Test::More; + +use Data::XLSX::Parser; + +my $parser = Data::XLSX::Parser->new; + +my $fn = __FILE__; +$fn =~ s{t$}{xlsx}; + +$parser->open($fn); + +my @sheets = $parser->workbook->names; +is scalar @sheets, 3, '3 worksheets ok'; + +is $sheets[0], 'POST_DSENDS', 'sheet1 name ok'; + +my $cells = []; +$parser->add_row_event_handler(sub { + my ($row) = @_; + push @$cells, $row; +}); + +$parser->sheet(1); + +is $cells->[112][0], 'RCS Thrust Vector Uncertainties ', 'val ok'; + +done_testing; + diff --git a/t/1_____loreyna126.xlsx b/t/1_____loreyna126.xlsx new file mode 100755 index 0000000000000000000000000000000000000000..5619fb4b17c816409eae600f372e864c27c1b6e9 GIT binary patch literal 30722 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 + +my $fn = __FILE__; +$fn =~ s{t$}{xlsx}; + +$parser->open($fn); + +my @sheets = $parser->workbook->names; +is scalar @sheets, 4, '4 worksheets ok'; + +is $sheets[0], 'Tabelle1', 'sheet1 name ok'; + +my $cells = []; +$parser->add_row_event_handler(sub { + my ($row) = @_; + push @$cells, $row; +}); + +$parser->sheet($parser->workbook->sheet_id($sheets[0])); + +is $cells->[0][0], 1, 'val 0,0 ok'; +is $cells->[0][1], 10, 'val 0,1 ok'; +is $cells->[1][0], 2, 'val 1,0 ok'; +is $cells->[1][1], 20, 'val 1,1 ok'; + +done_testing; + diff --git a/t/2_____with_chart.xlsx b/t/2_____with_chart.xlsx new file mode 100755 index 0000000000000000000000000000000000000000..f550b0b01dda121961cbd37d7c6efb899bce9d53 GIT binary patch literal 10457 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 +isa_ok $parser, 'Data::XLSX::Parser'; + +$parser->open("$FindBin::Bin/../private-data-20120717.xlsx"); + +my $shared_strings = $parser->shared_strings; + +is $shared_strings->count, 8161, 'count ok'; + +is $shared_strings->get(1), '問題文', 'get 1 ok'; +is $shared_strings->get(1000), 'リオデジャネイロ', 'get 1000 ok'; + + +done_testing; diff --git a/xt/sheet.t b/xt/sheet.t new file mode 100644 index 0000000..d368013 --- /dev/null +++ b/xt/sheet.t @@ -0,0 +1,24 @@ +use strict; +use warnings; +use utf8; +use FindBin; + +use Test::More; + +use_ok 'Data::XLSX::Parser'; + +my $parser = Data::XLSX::Parser->new; +isa_ok $parser, 'Data::XLSX::Parser'; + +$parser->open("$FindBin::Bin/../private-data-20120717.xlsx"); + +my $workbook = $parser->workbook; +isa_ok $workbook, 'Data::XLSX::Parser::Workbook'; + +my @names = $workbook->names; +is scalar @names, 2, '2 workbook ok'; + +my $sheet_id = $workbook->sheet_id($names[0]); +my $sheet = $parser->sheet($sheet_id); + +done_testing; diff --git a/xt/styles.t b/xt/styles.t new file mode 100644 index 0000000..78ed0ef --- /dev/null +++ b/xt/styles.t @@ -0,0 +1,19 @@ +use strict; +use warnings; +use utf8; +use FindBin; + +use Test::More; + +use_ok 'Data::XLSX::Parser'; + +my $parser = Data::XLSX::Parser->new; +isa_ok $parser, 'Data::XLSX::Parser'; + +$parser->open("$FindBin::Bin/../private-data-20120717.xlsx"); + +my $styles = $parser->styles; + + + +done_testing; diff --git a/xt/workbook.t b/xt/workbook.t new file mode 100644 index 0000000..8aba7c9 --- /dev/null +++ b/xt/workbook.t @@ -0,0 +1,22 @@ +use strict; +use warnings; +use Test::More; +use FindBin; + +use_ok 'Data::XLSX::Parser'; + +my $parser = Data::XLSX::Parser->new; +isa_ok $parser, 'Data::XLSX::Parser'; + +$parser->open("$FindBin::Bin/../private-data-20120717.xlsx"); + +my $workbook = $parser->workbook; +isa_ok $workbook, 'Data::XLSX::Parser::Workbook'; + +my @names = $workbook->names; +is scalar @names, 2, '2 workbook ok'; + +is $workbook->sheet_id($names[0]), 1, 'sheet_id 1 ok'; +is $workbook->sheet_id($names[1]), 2, 'sheet_id 2 ok'; + +done_testing;