3

So I had this crazy idea. Right now, I'm generating a table in Word using Text::CSV_XS like so:

use Win32::OLE;
use Win32::OLE::Const 'Microsoft Word';
use Text::CSV_XS;

# instantiation of word and Text::CSV_XS omitted for clarity
my $select = $word->Selection;
foreach my $row (@rows) { # @rows is an array of arrayrefs
    $csv->combine(@{$row});
    $select->InsertAfter($csv->string);
    $select->InsertParagraphAfter;
}
$select->convertToTable(({'Separator' => wdSeparateByCommas});

But it would be cooler if I could do something like this:

use Win32::OLE;
use Win32::OLE::Const 'Microsoft Word';
use Text::CSV_XS;

# instantiation of word and Text::CSV_XS omitted for clarity
my $select = $word->Selection;
foreach my $row (@rows) { # @rows is an array of arrayrefs
    $csv->bind_columns($row);
    $csv->print( 
        sub {
            my $something; # Should be the combine()'d string
            $select->InsertAfter($something);
            $select->InsertParagraphAfter;
        },
        undef
    ); 
}
$select->convertToTable(({'Separator' => wdSeparateByCommas});

My questions, then, are how do I get the $something out of my second code sample, and what do I need to do to make the closure look like a handle?

Aurelia Peters
  • 2,169
  • 1
  • 20
  • 34

2 Answers2

2

Text::CSV::print doesn't necessarily need a handle. The documentation just says it needs something with a print method.

{
    package Print::After::Selection;
    sub new {
        my ($pkg, $selection) = @_;
        my $self = { selection => $selection };
        return bless $self, $pkg;
    }
    sub print {
        my ($self, $string) = @_;
        my $selection = $self->{selection};
        $selection->InsertAfter($string);
        $selection->InsertParagraphAfter;
        return 1;  # return true or Text::CSV will think print failed
    }
}

...
$csv->print( Print::After::Selection->new( $word->Selection ), undef );
...

(not tested)

mob
  • 117,087
  • 18
  • 149
  • 283
2

I believe you are actually asking how to make data end up in Word using the following interface:

my $fh = SelectionHandle->new($selection);
my $csv = Text::CSV_XS->new({ binary => 1 });
$csv->print($fh, $_) for @rows;

(I think that's a bad idea. It's inside out. Create an object that formats using an encapsulated CSV object instead.)

If you want to given an object the interface of a variable (scalar, array, hash, file handle), you want tie. When you do, you end up with

use SelectionHandle qw( );
use Text::CSV_XS    qw( );

my $selection = ...;
my @rows      = ...;

my $fh = SelectionHandle->new($selection);
my $csv = Text::CSV_XS->new({ binary => 1 });
$csv->print($fh, $_) for @rows;

And the module looks like:

package SelectionHandle;

use strict;
use warnings;

use Symbol      qw( gensym );
use Tie::Handle qw( );

our @ISA = qw( Tie::Handle );

sub new {
   my ($class, $selection) = @_;
   my $fh = gensym();
   tie(*$fh, $class, $selection);
   return $fh;
}

sub TIEHANDLE {
   my ($class, $selection) = @_;
   return bless({
      selection => $selection,
   }, $class);
}

sub PRINT {
   my ($self, $str) = @_;
   my $selection = $self->{selection};
   $selection->InsertAfter($str);
   $selection->InsertParagraphAfter();
   return 1;
}

1;
ikegami
  • 367,544
  • 15
  • 269
  • 518
  • You're right, in part, and it's my mistake in trying to fold two questions into one. Ultimately, I do want this data to end up in Word, but I'm curious to know if I can create some kind of anonymous class - in this case something like a closure with a print() method - that I can wrap the Word-specific code in. – Aurelia Peters Feb 01 '13 at 17:58
  • 1
    I could technically change my class so its constructor takes a closure... `my $fh = callback_handle { ...code to call on PRINT... };` – ikegami Feb 01 '13 at 18:01