Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Read MS Word table data row wise using win32:ole perl

I am new to win32:ole module in perl. I am trying to print MS word table data row wise on command prompt. But I am able to print only last row of the table. Can you please help me to solve this problem? Thanks in advance.

Below is my code:

#!/usr/bin/perl 
use strict;
use warnings; 
use File::Spec::Functions qw( catfile );
use Win32::OLE qw(in); 
use Win32::OLE::Const 'Microsoft Word'; 
$Win32::OLE::Warn = 3; 

my $word = get_word(); 
$word->{DisplayAlerts} = wdAlertsNone; 
$word->{Visible} = 1;
my $doc = $word->{Documents}->Open('C:\\PerlScripts\\myTest.docx');
my $tables = $word->ActiveDocument->{'Tables'};

for my $table (in $tables)
{
   my $tableText = $table->ConvertToText({ Separator => wdSeparateByTabs });
   print "Table: ". $tableText->Text(). "\n";
}

$doc->Close(0); 

sub get_word 
{ 
    my $word; 
    eval { $word = Win32::OLE->GetActiveObject('Word.Application');}; 
    die "$@\n" if $@;
    unless(defined $word) 
    { 
       $word = Win32::OLE->new('Word.Application', sub { $_[0]->Quit }) 
       or die "Oops, cannot start Word: ", Win32::OLE->LastError, "\n"; 
    } 
  return $word; 
} 
like image 767
Aparna Savant Avatar asked Nov 01 '12 21:11

Aparna Savant


2 Answers

Not a perfect solution by any means but here's an advancement on the problem.

I used a string separator "\n\n" which produces the following output ...

Further hacking required :(

C:\StackOverflow>perl  word.pl meTest.docx
Table: Header1
Header2
Header3
Header4
Row1-Cell1
Row1-Cell2
Row1-Cell3
Row1-Cell4
Row2-Cell1
Row2-Cell2
Row2-Cell3
Row2-Cell4
Row2-Cell5

Here's the code. I have commented out some other code in the tables loop that I used to hack on the data returned by $tableRange->{Text} Uncomment to experiment further.

#!/usr/bin/perl 
use strict;
use warnings; 
use File::Spec::Functions qw( catfile );
use Win32::OLE qw(in); 
use Win32::OLE::Const 'Microsoft Word'; 
$Win32::OLE::Warn = 3; 

my $word = get_word(); 
$word->{DisplayAlerts} = wdAlertsNone; 
$word->{Visible} = 1;
my $doc = $word->{Documents}->Open('meTest.docx');
my $tables = $word->ActiveDocument->{'Tables'};

for my $table (in $tables)
{
   my $tableRange = $table->ConvertToText({ Separator => "\n\n" });
   print "Table: \n" . $tableRange->{Text}. "\n";
   # foreach $word (split/\n/, $tableRange->{Text}) {
   #  print $word . "\n" ;
   #  # $userinput =  <STDIN>;
   # }
}

$doc->Close(0); 

sub get_word 
{ 
    my $word; 
    eval { $word = Win32::OLE->GetActiveObject('Word.Application');}; 
    die "$@\n" if $@;
    unless(defined $word) 
    { 
       $word = Win32::OLE->new('Word.Application', sub { $_[0]->Quit }) 
       or die "Oops, cannot start Word: ", Win32::OLE->LastError, "\n"; 
    } 
  return $word; 
} 

Sorry I couldn't be of more help.

like image 182
Rob Kielty Avatar answered Nov 15 '22 03:11

Rob Kielty


extract all the doc tables into a single xls file

     sub doParseDoc {

           my $msg     = '' ; 
           my $ret     = 1 ; # assume failure at the beginning ...

           $msg        = 'START --- doParseDoc' ; 
           $objLogger->LogDebugMsg( $msg );
           $msg        = 'using the following DocFile: "' . $DocFile . '"' ; 
           $objLogger->LogInfoMsg( $msg );
           #-----------------------------------------------------------------------
           #Using OLE + OLE constants for Variants and OLE enumeration for Enumerations


           # Create a new Excel workbook
           my $objWorkBook = Spreadsheet::WriteExcel->new("$DocFile" . '.xls');

           # Add a worksheet
           my $objWorkSheet = $objWorkBook->add_worksheet();


           my $var1 = Win32::OLE::Variant->new(VT_BOOL, 'true');

           Win32::OLE->Option(Warn => \&Carp::croak);
           use constant true => 0;

           # at this point you should have the Word application opened in UI with t
           # the DocFile
           # build the MS Word object during run-time 
           my $objMSWord = Win32::OLE->GetActiveObject('Word.Application')
                             or Win32::OLE->new('Word.Application', 'Quit');  

           # build the doc object during run-time 
           my $objDoc   = $objMSWord->Documents->Open($DocFile)
                 or die "Could not open ", $DocFile, " Error:", Win32::OLE->LastError();

           #Set the screen to Visible, so that you can see what is going on
           $objMSWord->{'Visible'} = 1;
           # try NOT printing directly to the file


            #$objMSWord->ActiveDocument->SaveAs({Filename => 'AlteredTest.docx', 
                                        #FileFormat => wdFormatDocument});

           my $tables        = $objMSWord->ActiveDocument->Tables();
           my $tableText     = '' ;   
           my $xlsRow        = 1 ; 

           for my $table (in $tables){
              # extract the table text as a single string
              #$tableText = $table->ConvertToText({ Separator => 'wdSeparateByTabs' });
              # cheated those properties from here: 
              # https://msdn.microsoft.com/en-us/library/aa537149(v=office.11).aspx#officewordautomatingtablesdata_populateatablewithdata
              my $RowsCount = $table->{'Rows'}->{'Count'} ; 
              my $ColsCount = $table->{'Columns'}->{'Count'} ; 

              # disgard the tables having different than 5 columns count
              next unless ( $ColsCount == 5 ) ;

              $msg           = "Rows Count: $RowsCount " ; 
              $msg           .= "Cols Count: $ColsCount " ; 
              $objLogger->LogDebugMsg ( $msg ) ; 

              #my $tableRange = $table->ConvertToText({ Separator => '##' });
              # OBS !!! simple print WILL print to your doc file use Select ?!
              #$objLogger->LogDebugMsg ( $tableRange . "\n" );
              # skip the header row
              foreach my $row ( 0..$RowsCount ) {
                 foreach my $col (0..$ColsCount) {

                    # nope ... $table->cell($row,$col)->->{'WrapText'} = 1 ; 
                    # nope $table->cell($row,$col)->{'WordWrap'} = 1  ;
                    # so so $table->cell($row,$col)->WordWrap() ; 

                    my $txt = ''; 
                    # well some 1% of the values are so nasty that we really give up on them ... 
                    eval {
                       $txt = $table->cell($row,$col)->range->{'Text'}; 
                       #replace all the ctrl chars by space
                       $txt =~ s/\r/ /g   ; 
                       $txt =~ s/[^\040-\176]/ /g  ; 
                       # perform some cleansing - ColName<primary key>=> ColName
                       #$txt =~ s#^(.[a-zA-Z_0-9]*)(\<.*)#$1#g ; 

                       # this will most probably brake your cmd ... 
                       # $objLogger->LogDebugMsg ( "row: $row , col: $col with txt: $txt \n" ) ; 
                    } or $txt = 'N/A' ; 

                    # Write a formatted and unformatted string, row and column notation.
                    $objWorkSheet->write($xlsRow, $col, $txt);

                 } #eof foreach col

                 # we just want to dump all the tables into the one sheet
                 $xlsRow++ ; 
               } #eof foreach row
               sleep 1 ; 
           }  #eof foreach table

           # close the opened in the UI document
           $objMSWord->ActiveDocument->Close;

           # OBS !!! now we are able to print 
           $objLogger->LogDebugMsg ( $tableText . "\n" );

           # exit the whole Word application
           $objMSWord->Quit;

           return ( $ret , $msg ) ; 
     }
     #eof sub doParseDoc
like image 42
Yordan Georgiev Avatar answered Nov 15 '22 04:11

Yordan Georgiev