File Coverage

blib/lib/PDL/Char.pm
Criterion Covered Total %
statement 93 96 96.8
branch 29 38 76.3
condition 9 11 81.8
subroutine 10 11 90.9
pod 4 5 80.0
total 145 161 90.0


line stmt bran cond sub pod time code
1             package PDL::Char;
2              
3 1     1   420 use strict;
  1         2  
  1         27  
4 1     1   4 use warnings;
  1         1  
  1         129  
5             our @ISA = qw (PDL);
6 1     1   5 use overload '""' => \&PDL::Char::string;
  1         1  
  1         9  
7              
8       1     sub import {} # override the PDL one to avoid the big import list
9              
10             =head1 NAME
11              
12             PDL::Char -- PDL subclass which allows reading and writing of fixed-length character strings as byte PDLs
13              
14             =head1 SYNOPSIS
15              
16             use PDL;
17             use PDL::Char;
18              
19             my $pchar = PDL::Char->new( [['abc', 'def', 'ghi'],['jkl', 'mno', 'pqr']] );
20            
21             $pchar->setstr(1,0,'foo');
22            
23             print $pchar; # 'string' bound to "", perl stringify function
24             # Prints:
25             # [
26             # ['abc' 'foo' 'ghi']
27             # ['jkl' 'mno' 'pqr']
28             # ]
29              
30             print $pchar->atstr(2,0);
31             # Prints:
32             # ghi
33              
34             =head1 DESCRIPTION
35              
36             This subclass of PDL allows one to manipulate PDLs of 'byte' type as if they were made of fixed
37             length strings, not just numbers.
38              
39             This type of behavior is useful when you want to work with character grids. The indexing is done
40             on a string level and not a character level for the 'setstr' and 'atstr' commands.
41              
42             This module is in particular useful for writing NetCDF files that include character data using the
43             L module.
44              
45             =head1 FUNCTIONS
46              
47             =head2 new
48              
49             =for ref
50              
51             Function to create a byte PDL from a string, list of strings, list of list of strings, etc.
52              
53             =for usage
54              
55             # create a new PDL::Char from a perl array of strings
56             $strpdl = PDL::Char->new( ['abc', 'def', 'ghij'] );
57              
58             # Convert a PDL of type 'byte' to a PDL::Char
59             $strpdl1 = PDL::Char->new (sequence (byte, 4, 5)+99);
60              
61             =for example
62              
63             $pdlchar3d = PDL::Char->new([['abc','def','ghi'],['jkl', 'mno', 'pqr']]);
64              
65             =cut
66              
67             sub new {
68 5     5 1 187493 my $type = shift;
69 5 100       15 my $value = (scalar(@_)>1 ? [@_] : shift); # ref thyself
70             # re-bless byte PDLs as PDL::Char
71 5 100       22 if (ref($value) =~ /PDL/) {
72 1 50       7 PDL::Core::barf('Cannot convert a non-byte PDL to PDL::Char')
73             if ($value->get_datatype != $PDL::Types::PDL_B);
74 1         3 return bless $value, $type;
75             }
76 4         6 my $ptype = $PDL::Types::PDL_B;
77 4         28 my $self = PDL->initialize();
78 4         19 $self->set_datatype($ptype);
79 4 50       11 $value = 0 if !defined($value);
80 4         5 my $maxlength; # max length seen for all character strings
81 4         7 my $samelen = 1; # Flag = 1 if all character strings are the same length
82             # 1st Pass thru the perl array structure, assume all strings the same length
83 4         6 my @dims;
84 4         12 my $str = _rcharpack($value,\$maxlength,\$samelen,0,\@dims);
85 4 100       11 unless( $samelen){ # Strings weren't the same length, go thru again and null pad to
86             # the max length.
87 1         6 $str = _rcharpack2($value,$maxlength,0,\@dims);
88             }
89 4         30 $self->setdims([reverse @dims]);
90 4         36 $self->update_data_from($str);
91 4         15 return bless $self, $type;
92             }
93              
94             # Take an N-D perl array of strings and pack it into a single string,
95             # Used by the 'char' constructor
96             #
97             # References supplied so $maxlength and $samelen are updated along the way as well.
98             #
99             #
100             # This version (_rcharpack) is for the 1st pass thru the N-d string array.
101             # It assumes that all strings are the same length, but also checks to see if they aren't
102             sub _rcharpack {
103 44     44   45 my $w = shift; # Input string
104 44         58 my ($maxlenref, $samelenref, $level, $dims) = @_; # reference to $maxlength, $samelen
105 44         41 my ($ret,$type);
106 44         36 $ret = "";
107 44 100       103 if (ref($w) eq "ARRAY") {
    50          
108 13 50 66     31 PDL::Core::barf('Array is not rectangular') if (defined($dims->[$level]) and
109             $dims->[$level] != scalar(@$w));
110 13         13 $dims->[$level] = scalar (@$w);
111 13         12 $level++;
112 13         15 $type = ref($$w[0]);
113 13         18 for(@$w) {
114 40 50       53 PDL::Core::barf('Array is not rectangular') unless $type eq ref($_); # Equal types
115 40         52 $ret .= _rcharpack($_,$maxlenref, $samelenref, $level, $dims);
116             }
117             }elsif (ref(\$w) eq "SCALAR") {
118 31         34 my $len = length($w);
119             # Check for this length being different then the others:
120 31 100 100     70 $$samelenref = 0 if( defined($$maxlenref) && ($len != $$maxlenref) );
121             # Save the max length:
122 31 100 66     85 $$maxlenref = $len if( !defined($$maxlenref) || $len > $$maxlenref); # see if this is the max length seen so far
123 31         33 $dims->[$level] = $len;
124 31         32 $ret = $w;
125             }else{
126 0         0 PDL::Core::barf("Don't know how to make a PDL object from passed argument");
127             }
128 44         79 return $ret;
129             }
130              
131             # This version (_rcharpack2) is for the 2nd pass (if required) thru the N-d string array.
132             # If the 1st pass thru (_rcharpack) finds that all strings were not the same length,
133             # this routine will go thru and null-pad all strings to the max length seen.
134             # Note: For efficiency, the error checking is not repeated here, because any errors will
135             # already be detected in the 1st pass.
136             #
137             sub _rcharpack2 {
138 9     9   12 my $w = shift; # Input string
139 9         12 my ($maxlen, $level, $dims) = @_; # Length to pad strings to
140 9         8 my ($ret,$type);
141 9         11 $ret = "";
142 9 100       21 if (ref($w) eq "ARRAY") {
    50          
143             # Checks not needed the second time thru (removed)
144 3         7 $dims->[$level] = scalar (@$w);
145 3         5 $level++;
146 3         5 $type = ref($$w[0]);
147 3         7 for(@$w) {
148 8         16 $ret .= _rcharpack2($_,$maxlen,$level,$dims);
149             }
150             }elsif (ref(\$w) eq "SCALAR") {
151 6         9 my $len = length($w);
152 6         9 $dims->[$level] = $maxlen;
153 6         22 $ret = $w.("\00" x ($maxlen - $len));
154             }
155 9         16 return $ret;
156             }
157              
158             =head2 string
159              
160             =for ref
161              
162             Function to print a character PDL (created by 'char') in a pretty format.
163              
164             =for usage
165              
166             $char = PDL::Char->new( [['abc', 'def', 'ghi'], ['jkl', 'mno', 'pqr']] );
167             print $char; # 'string' bound to "", perl stringify function
168             # Prints:
169             # [
170             # ['abc' 'def' 'ghi']
171             # ['jkl' 'mno' 'pqr']
172             # ]
173              
174             # 'string' is overloaded to the "" operator, so:
175             # print $char;
176             # should have the same effect.
177              
178             =cut
179              
180             sub string {
181 38     38 1 98 my $self = shift;
182 38   100     59 my $level = shift || 0;
183 38 50       51 my $sep = $PDL::use_commas ? "," : " ";
184 38 100       116 if ($self->dims == 1) {
185 25         23 my $str = ${$self->get_dataref}; # get copy of string
  25         166  
186 25         47 $str =~ s/\00+$//g; # get rid of any null padding
187 25         158 return "\'". $str. "\'". $sep;
188             } else {
189 13         25 my @dims = reverse $self->dims;
190 13         16 my $ret = '';
191 13 100       29 $ret .= (" " x $level) . '[' . ((@dims == 2) ? ' ' : "\n");
192 13         26 for (my $i=0;$i<$dims[0];$i++) {
193 33         47 my $slicestr = ":," x (scalar(@dims)-1) . "($i)";
194 33         111 my $substr = $self->slice($slicestr);
195 33         56 $ret .= $substr->string($level+1);
196             }
197 13         19 $ret .= (" " x $level) . ']' . $sep . "\n";
198 13         59 return $ret;
199             }
200             }
201              
202             =head2 setstr
203              
204             =for ref
205              
206             Function to set one string value in a character PDL. The input position is
207             the position of the string, not a character in the string. The first dimension
208             is assumed to be the length of the string.
209              
210             The input string will be null-padded if the string is shorter than the first
211             dimension of the PDL. It will be truncated if it is longer.
212              
213             =for usage
214              
215             $char = PDL::Char->new( [['abc', 'def', 'ghi'], ['jkl', 'mno', 'pqr']] );
216             $char->setstr(0,1, 'foobar');
217             print $char; # 'string' bound to "", perl stringify function
218             # Prints:
219             # [
220             # ['abc' 'def' 'ghi']
221             # ['foo' 'mno' 'pqr']
222             # ]
223             $char->setstr(2,1, 'f');
224             print $char; # 'string' bound to "", perl stringify function
225             # Prints:
226             # [
227             # ['abc' 'def' 'ghi']
228             # ['foo' 'mno' 'f'] -> note that this 'f' is stored "f\0\0"
229             # ]
230              
231             =cut
232              
233             sub setstr { # Sets a particular single value to a string.
234 3 50   3 1 990 PDL::Core::barf('Usage: setstr($pdl, $x, $y,.., $value)') if $#_<2;
235 3         5 my $self = shift;
236 3         6 my $val = pop;
237 3         15 my @dims = $self->dims;
238 3         4 my $n = $dims[0];
239 3         8 for (my $i=0;$i<$n;$i++) {
240 9 100       35 my $chr = ($i >= length($val)) ? 0 : unpack ("C", substr ($val, $i, 1));
241 9         122 PDL::Core::set_c ($self, [$i, @_], $chr);
242             }
243             }
244              
245             =head2 atstr
246              
247             =for ref
248              
249             Function to fetch one string value from a PDL::Char type PDL, given a position within the PDL.
250             The input position of the string, not a character in the string. The length of the input
251             string is the implied first dimension.
252              
253             =for usage
254              
255             $char = PDL::Char->new( [['abc', 'def', 'ghi'], ['jkl', 'mno', 'pqr']] );
256             print $char->atstr(0,1);
257             # Prints:
258             # jkl
259              
260             =cut
261              
262             sub atstr { # Fetchs a string value from a PDL::Char
263 3 50   3 1 13 PDL::Core::barf('Usage: atstr($pdl, $x, $y,..,)') if (@_ < 2);
264 3         5 my $self = shift;
265 3         7 my $str = ':,' . join (',', map {"($_)"} @_);
  9         27  
266 3         14 my $w = $self->slice($str);
267 3         6 my $val = ${$w->get_dataref}; # get the data
  3         26  
268 3         12 $val =~ s/\00+$//g; # get rid of any null padding
269 3         29 return $val;
270             }
271              
272             # yuck ;) this is a cool little accessor method
273             # rebless a slice into PDL; originally
274             # Marc's idea used in PDL::Complex
275             sub numeric {
276 0     0 0   my ($seq) = @_;
277 0           return bless $seq->slice(''), 'PDL';
278             }
279              
280             1;