ownlifeful has asked for the wisdom of the Perl Monks concerning the following question:

Greetings, esteemed Perl Monks!

I have spent several hours searching for an answer. I'd sincerely appreciate any help.

I'm working on a script using Strawberry Perl and Win32::OLE, to read and write (manipulate) Excel spreadsheets. The input files to be processed by this script don't have all the rows numbers. I assumed that spreadsheet rows are always numbered with contiguous natural numbers, beginning with 1. Ex: A1,A2,A3,A4,A5,A6...

Well, the spreadsheets I need to process have rows numbered: A1,A3,A4,A5,A6,A217,A219,A220,A221,A350, ...

( What fresh hell is this?!??! )

So my question is -- How do I do "some computation" for every cell in a given column, given noncontiguous row numbers? Sure, I can do the following:

### Get the size of the area to process. my $min_row = get_min_row(); my $max_row = get_max_row(); ### Get something like D1-E23 my $range = "D$min_row:F$max_row"; my $selected_range = $sheet->Range($range); my $array_ref = $selected_range->{'Value'}; my $current_row = $min_row; foreach my $row_ref ( @$array_ref ) { ### Get values from columns, D, E, and F. my ( $d_val, $e_val, $f_val ) = @$row_ref; ### Do some stuff. if ( $d_val > 7 ) { my @array = split /$e_val/, $f_val; my $copied_range = "$current_row:$current_row"; foreach my $a ( @array ) { $sheet->rows($copied_range)->copy(); $sheet->Cells($current_row, 1)->EntireRow->Insert; $sheet->Range($copied_range)->select(); $sheet->Paste(); $sheet->Range("S" . $current_row)->{Value} = $a; } } $current_row++; ### This doesn't work. }

This won't always work. I'm trying to keep track of the current row number in a counter variable ( $current_row ). Simply incrementing the counter won't work. If only there were something like a "row_number" attribute that I could peek at, to determine which row is currently being processed. Any ideas?

Thanks in advance!

  • Comment on Win32::OLE and Excel: Iterating when certain row numbers are missing
  • Download Code

Replies are listed 'Best First'.
Re: Win32::OLE and Excel: Iterating when certain row numbers are missing
by davies (Monsignor) on Jun 21, 2021 at 13:22 UTC

    A quick first impression: your approach is never going to work. In your ### Do some stuff block, you are inserting a row. This will create the same problem as deleting a row, something I have analysed in RFC Tutorial - Deleting Excel Rows, Columns and Sheets. Start at the end and work backwards and you will eliminate at least one of your problems.

    Almost immediate update: the rows are numbered sequentially in every implementation of Excel (and every other spreadsheet - dozens) I have seen. However, rows and columns may be hidden. If they are hidden, that is from the human eye, not VBA or Perl. Your code will go from row 1 to row 2 regardless of whether row 2 is visible to you.

    Update 2: some code. Update 3 changed the code to include a demonstration of testing whether a row is hidden.

    use strict; use warnings; use Win32::OLE; my $xl = Win32::OLE->new('Excel.Application'); $xl->{Visible} = 1; my $wb = $xl->Workbooks->Add; # Make sure there is precisely 1 sheet for my $nSht (2..$wb->Sheets->{Count}) { $wb->Sheets(2)->Delete; } # Put some data in my $sht = $wb->Sheets(1); $sht->Cells(1, 1)->{Value} = "A"; $sht->Cells(2, 1)->{Value} = "B"; $sht->Cells(3, 1)->{Value} = "C"; # Hide row 2 $sht->Cells(2, 1)->EntireRow->{Hidden} = 1; # Process 3 rows, demonstrating a bug for my $row (1..3) { if ("A" ne $sht->Cells($row, 1)->{Value}) { $sht->Cells($row + 1, 1)->EntireRow->Insert; $sht->Cells($row, 1)->EntireRow->Copy($sht->Cells($row + 1, 1) +); } } use feature 'say'; for my $row (1..5) { if ($sht->Cells($row, 1)->EntireRow->{Hidden}) { say "Row $row is hidden"; } }

    Run this code and you will see three hidden rows. This is because row 2 (B) has been copied to row 3. After that, row 3 (a copy of B) has been copied to row 4. This is the loop error bug I mention in my post about deleting rows.

    To reveal the rows, select rows 1 to 5, the two visible rows and the three hidden ones. Doing this with the mouse can be awkward, but I always prefer the keyboard anyway. Alt-H (Home), O (Format), U (Hide & Unhide), O (Unhide Rows) will show the three copies of what was row 2. If you are using a version with the old menu tree, it's Alt-O, R, U.

    Regards,

    John Davies

      Thanks for your prompt response!

      Well, I knew that my approach would not work as-is, because inserting/deleting rows/columns will cause a one-off error, which the counter would have to account for by incrementing/decrementing.

      I'm glad you pointed out that the spreadsheet does indeed have sequential rows, and the "missing" rows are only "invisible" rows. With this knowledge, I'm thinking that I could look for a "Hidden" attribute on rows/cells.

      for (my $i = $min_row; $i <= $max_row; $i++) { my $current_cell = $sheet->cells($i, 1); if ( $current_cell->("Hidden") ) { next; } ... ### Do clever row insertions here. ### Increment $i as needed. }

      Of course, a little bit of googling suggests that Win32::OLE does not surface a "hidden" or "visible" property. Any other ideas?

        I think you posted your reply as I was posting my second update. There is indeed a "hidden" property exposed, as demonstrated in my line $sht->Cells(2, 1)->EntireRow->{Hidden} = 1;. You can test this with something like if ($row->{Hidden}) {do stuff}.

        Update: I've now updated my code to demonstrate testing the hidden property.

        Regards,

        John Davies