1 ////////////////////////////////////////////////////////////////////////
2 //
3 // Copyright (C) 2008-2021 The Octave Project Developers
4 //
5 // See the file COPYRIGHT.md in the top-level directory of this
6 // distribution or <https://octave.org/copyright/>.
7 //
8 // This file is part of Octave.
9 //
10 // Octave is free software: you can redistribute it and/or modify it
11 // under the terms of the GNU General Public License as published by
12 // the Free Software Foundation, either version 3 of the License, or
13 // (at your option) any later version.
14 //
15 // Octave is distributed in the hope that it will be useful, but
16 // WITHOUT ANY WARRANTY; without even the implied warranty of
17 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
18 // GNU General Public License for more details.
19 //
20 // You should have received a copy of the GNU General Public License
21 // along with Octave; see the file COPYING.  If not, see
22 // <https://www.gnu.org/licenses/>.
23 //
24 ////////////////////////////////////////////////////////////////////////
25 
26 // Adapted from previous version of dlmread.occ as authored by Kai
27 // Habel, but core code has been completely re-written.
28 
29 #if defined (HAVE_CONFIG_H)
30 #  include "config.h"
31 #endif
32 
33 #include <cmath>
34 #include <cctype>
35 #include <fstream>
36 #include <limits>
37 
38 #include "file-ops.h"
39 #include "lo-ieee.h"
40 #include "lo-sysdep.h"
41 
42 #include "defun.h"
43 #include "interpreter.h"
44 #include "oct-stream.h"
45 #include "error.h"
46 #include "ovl.h"
47 #include "utils.h"
48 
49 static const octave_idx_type idx_max
50   = std::numeric_limits<octave_idx_type>::max () - 1;
51 
52 static const double idx_max_dbl = double (idx_max);
53 
54 static bool
read_cell_spec(std::istream & is,octave_idx_type & row,octave_idx_type & col)55 read_cell_spec (std::istream& is, octave_idx_type& row, octave_idx_type& col)
56 {
57   bool stat = false;
58 
59   if (is.peek () == std::istream::traits_type::eof ())
60     stat = true;
61   else
62     {
63       if (::isalpha (is.peek ()))
64         {
65           col = 0;
66           while (is && ::isalpha (is.peek ()))
67             {
68               char ch = is.get ();
69               col *= 26;
70               if (ch >= 'a')
71                 col += ch - 'a' + 1;
72               else
73                 col += ch - 'A' + 1;
74             }
75           col--;
76 
77           if (is)
78             {
79               is >> row;
80               row--;
81               if (is)
82                 stat = true;
83             }
84         }
85     }
86 
87   return stat;
88 }
89 
90 static bool
parse_range_spec(const octave_value & range_spec,octave_idx_type & rlo,octave_idx_type & clo,octave_idx_type & rup,octave_idx_type & cup)91 parse_range_spec (const octave_value& range_spec,
92                   octave_idx_type& rlo, octave_idx_type& clo,
93                   octave_idx_type& rup, octave_idx_type& cup)
94 {
95   bool stat = true;
96 
97   if (range_spec.is_string ())
98     {
99       std::istringstream is (range_spec.string_value ());
100       char ch = is.peek ();
101 
102       if (ch == '.' || ch == ':')
103         {
104           rlo = 0;
105           clo = 0;
106           ch = is.get ();
107           if (ch == '.')
108             {
109               ch = is.get ();
110               if (ch != '.')
111                 stat = false;
112             }
113         }
114       else
115         {
116           stat = read_cell_spec (is, rlo, clo);
117 
118           if (stat)
119             {
120               ch = is.peek ();
121 
122               if (ch == '.' || ch == ':')
123                 {
124                   ch = is.get ();
125                   if (ch == '.')
126                     {
127                       ch = is.get ();
128                       if (! is || ch != '.')
129                         stat = false;
130                     }
131 
132                   rup = idx_max;
133                   cup = idx_max;
134                 }
135               else
136                 {
137                   rup = rlo;
138                   cup = clo;
139                   if (! is || ! is.eof ())
140                     stat = false;
141                 }
142             }
143         }
144 
145       if (stat && is && ! is.eof ())
146         stat = read_cell_spec (is, rup, cup);
147 
148       if (! is || ! is.eof ())
149         stat = false;
150     }
151   else if (range_spec.is_real_matrix () && range_spec.numel () == 4)
152     {
153       NDArray range (range_spec.array_value ());
154       if (range.any_element_is_nan ())
155         error ("dlmread: NaN is not a valid row or column specifier");
156 
157       // double --> unsigned int avoiding any overflow
158       rlo = static_cast<octave_idx_type> (std::min (range(0), idx_max_dbl));
159       clo = static_cast<octave_idx_type> (std::min (range(1), idx_max_dbl));
160       rup = static_cast<octave_idx_type> (std::min (range(2), idx_max_dbl));
161       cup = static_cast<octave_idx_type> (std::min (range(3), idx_max_dbl));
162     }
163   else
164     stat = false;
165 
166   return stat;
167 }
168 
169 DEFMETHOD (dlmread, interp, args, ,
170            doc: /* -*- texinfo -*-
171 @deftypefn  {} {@var{data} =} dlmread (@var{file})
172 @deftypefnx {} {@var{data} =} dlmread (@var{file}, @var{sep})
173 @deftypefnx {} {@var{data} =} dlmread (@var{file}, @var{sep}, @var{r0}, @var{c0})
174 @deftypefnx {} {@var{data} =} dlmread (@var{file}, @var{sep}, @var{range})
175 @deftypefnx {} {@var{data} =} dlmread (@dots{}, "emptyvalue", @var{EMPTYVAL})
176 Read numeric data from the text file @var{file} which uses the delimiter
177 @var{sep} between data values.
178 
179 If @var{sep} is not defined the separator between fields is determined from
180 the file itself.
181 
182 The optional scalar arguments @var{r0} and @var{c0} define the starting row
183 and column of the data to be read.  These values are indexed from zero,
184 i.e., the first data row corresponds to an index of zero.
185 
186 The @var{range} parameter specifies exactly which data elements are read.
187 The first form of the parameter is a 4-element vector containing the upper
188 left and lower right corners @code{[@var{R0},@var{C0},@var{R1},@var{C1}]}
189 where the indices are zero-based.  To specify the last column---the equivalent
190 of @code{end} when indexing---use the specifier @code{Inf}.  Alternatively, a
191 spreadsheet style form such as @qcode{"A2..Q15"} or @qcode{"T1:AA5"} can be
192 used.  The lowest alphabetical index @qcode{'A'} refers to the first column.
193 The lowest row index is 1.
194 
195 @var{file} should be a filename or a file id given by @code{fopen}.  In the
196 latter case, the file is read until end of file is reached.
197 
198 The @qcode{"emptyvalue"} option may be used to specify the value used to
199 fill empty fields.  The default is zero.  Note that any non-numeric values,
200 such as text, are also replaced by the @qcode{"emptyvalue"}.
201 @seealso{csvread, textscan, dlmwrite}
202 @end deftypefn */)
203 {
204   int nargin = args.length ();
205 
206   double empty_value = 0.0;
207 
208   if (nargin > 2 && args(nargin-2).is_string ()
209       && args(nargin-2).string_value () == "emptyvalue")
210     {
211       empty_value = args(nargin-1).double_value ();
212 
213       nargin -= 2;
214     }
215 
216   if (nargin < 1 || nargin > 4)
217     print_usage ();
218 
219   std::istream *input = nullptr;
220   std::ifstream input_file;
221 
222   if (args(0).is_string ())
223     {
224       // Filename.
225       std::string fname (args(0).string_value ());
226 
227       std::string tname = octave::sys::file_ops::tilde_expand (fname);
228 
229       tname = octave::find_data_file_in_load_path ("dlmread", tname);
230 
231 #if defined (OCTAVE_USE_WINDOWS_API)
232       std::wstring wname = octave::sys::u8_to_wstring (tname);
233       input_file.open (wname.c_str (), std::ios::in);
234 #else
235       input_file.open (tname.c_str (), std::ios::in);
236 #endif
237 
238       if (! input_file)
239         error ("dlmread: unable to open file '%s'", fname.c_str ());
240 
241       input = &input_file;
242     }
243   else if (args(0).is_scalar_type ())
244     {
245       octave::stream_list& streams = interp.get_stream_list ();
246 
247       octave::stream is = streams.lookup (args(0), "dlmread");
248 
249       input = is.input_stream ();
250 
251       if (! input)
252         error ("dlmread: stream FILE not open for input");
253     }
254   else
255     error ("dlmread: FILE argument must be a string or file id");
256 
257   // Set default separator.
258   std::string sep;
259   if (nargin > 1)
260     {
261       if (args(1).is_sq_string ())
262         sep = octave::do_string_escapes (args(1).string_value ());
263       else
264         sep = args(1).string_value ();
265     }
266 
267   // Take a subset if a range was given.
268   octave_idx_type r0 = 0;
269   octave_idx_type c0 = 0;
270   octave_idx_type r1 = idx_max;
271   octave_idx_type c1 = idx_max;
272   if (nargin > 2)
273     {
274       if (nargin == 3)
275         {
276           if (! parse_range_spec (args(2), r0, c0, r1, c1))
277             error ("dlmread: error parsing RANGE");
278         }
279       else if (nargin == 4)
280         {
281           r0 = args(2).idx_type_value ();
282           c0 = args(3).idx_type_value ();
283         }
284 
285       if (r0 < 0 || c0 < 0)
286         error ("dlmread: left (R0) and top (C0) must be positive");
287 
288       // Short-circuit and return if range is empty
289       if (r1 < r0 || c1 < c0)
290         return ovl (Matrix (0,0));
291     }
292 
293   octave_idx_type i = 0;
294   octave_idx_type j = 0;
295   octave_idx_type r = 1;
296   octave_idx_type c = 1;
297   // Start with a reasonable size to avoid constant resizing of matrix.
298   octave_idx_type rmax = 32;
299   octave_idx_type cmax = 0;
300 
301   Matrix rdata (rmax, cmax, empty_value);
302   ComplexMatrix cdata;
303 
304   bool iscmplx = false;
305   bool sep_is_wspace = (sep.find_first_of (" \t") != std::string::npos);
306   bool auto_sep_is_wspace = false;
307 
308   std::string line;
309 
310   // Skip the r0 leading lines
311   octave_idx_type rcnt = r0;
312   while (rcnt > 0 && getline (*input, line))
313     rcnt--;
314 
315   if (rcnt > 0)
316     return ovl (Matrix (0,0));  // Not enough lines in file to satisfy RANGE
317   else
318     r1 -= r0;
319 
320   std::istringstream tmp_stream;
321 
322   // Read the data one field at a time, growing the data matrix as needed.
323   while (getline (*input, line))
324     {
325       // Skip blank lines for compatibility.
326       if ((! sep_is_wspace || auto_sep_is_wspace)
327           && line.find_first_not_of (" \t") == std::string::npos)
328         continue;
329 
330       // Infer separator from file if delimiter is blank.
331       if (sep.empty ())
332         {
333           // Skip leading whitespace.
334           std::size_t pos1 = line.find_first_not_of (" \t");
335 
336           // For Matlab compatibility, blank delimiter should
337           // correspond to whitespace (space and tab).
338           std::size_t n = line.find_first_of (",:; \t", pos1);
339           if (n == std::string::npos)
340             {
341               sep = " \t";
342               auto_sep_is_wspace = true;
343             }
344           else
345             {
346               char ch = line.at (n);
347 
348               switch (line.at (n))
349                 {
350                 case ' ':
351                 case '\t':
352                   sep = " \t";
353                   auto_sep_is_wspace = true;
354                   break;
355 
356                 default:
357                   sep = ch;
358                   break;
359                 }
360             }
361         }
362 
363       // Estimate the number of columns from first line of data.
364       if (cmax == 0)
365         {
366           std::size_t pos1, pos2;
367           if (auto_sep_is_wspace)
368             pos1 = line.find_first_not_of (" \t");
369           else
370             pos1 = 0;
371 
372           do
373             {
374               pos2 = line.find_first_of (sep, pos1);
375 
376               if (auto_sep_is_wspace && pos2 != std::string::npos)
377                 {
378                   // Treat consecutive separators as one.
379                   pos2 = line.find_first_not_of (sep, pos2);
380                   if (pos2 != std::string::npos)
381                     pos2 -= 1;
382                 }
383 
384               // Separator followed by EOL doesn't generate extra column
385               if (pos2 != std::string::npos)
386                 cmax++;
387 
388               pos1 = pos2 + 1;
389             }
390           while (pos2 != std::string::npos);
391 
392           // FIXME: Should always be the case that iscmplx == false.
393           //        Flag is initialized that way and no data has been read.
394           if (iscmplx)
395             cdata.resize (rmax, cmax, empty_value);
396           else
397             rdata.resize (rmax, cmax, empty_value);
398         }
399 
400       r = (r > i + 1 ? r : i + 1);
401       j = 0;
402 
403       std::size_t pos1, pos2;
404       if (auto_sep_is_wspace)
405         pos1 = line.find_first_not_of (" \t");  // Skip leading whitespace.
406       else
407         pos1 = 0;
408 
409       do
410         {
411           octave_quit ();
412 
413           pos2 = line.find_first_of (sep, pos1);
414           std::string str = line.substr (pos1, pos2 - pos1);
415 
416           if (auto_sep_is_wspace && pos2 != std::string::npos)
417             {
418               // Treat consecutive separators as one.
419               pos2 = line.find_first_not_of (sep, pos2);
420               if (pos2 != std::string::npos)
421                 pos2 -= 1;
422               else
423                 pos2 = line.length () - 1;
424             }
425 
426           // Separator followed by EOL doesn't generate extra column
427           if (pos2 == std::string::npos && str.empty ())
428             break;
429 
430           c = (c > j + 1 ? c : j + 1);
431           if (r > rmax || c > cmax)
432             {
433               // Use resize_and_fill for the case of unequal length rows.
434               // Keep rmax a power of 2.
435               rmax = std::max (2*(r-1), rmax);
436               cmax = std::max (c, cmax);
437               if (iscmplx)
438                 cdata.resize (rmax, cmax, empty_value);
439               else
440                 rdata.resize (rmax, cmax, empty_value);
441             }
442 
443           tmp_stream.str (str);
444           tmp_stream.clear ();
445 
446           double x = octave_read_double (tmp_stream);
447           if (tmp_stream)
448             {
449               if (tmp_stream.eof ())
450                 {
451                   if (iscmplx)
452                     cdata(i,j++) = x;
453                   else
454                     rdata(i,j++) = x;
455                 }
456               else
457                 {
458                   int next_char = tmp_stream.peek ();
459                   if (next_char == 'i' || next_char == 'j'
460                       || next_char == 'I' || next_char == 'J')
461                     {
462                       // Process pure imaginary numbers.
463                       tmp_stream.get ();
464                       next_char = tmp_stream.peek ();
465                       if (next_char == std::istringstream::traits_type::eof ())
466                         {
467                           if (! iscmplx)
468                             {
469                               iscmplx = true;
470                               cdata = ComplexMatrix (rdata);
471                             }
472 
473                           cdata(i,j++) = Complex (0, x);
474                         }
475                       else
476                         {
477                           // Parsing failed, <number>i|j<extra text>
478                           j++;  // Leave data initialized to empty_value
479                         }
480                     }
481                   else if (std::isalpha (next_char) && ! std::isfinite (x))
482                     {
483                       // Parsing failed, <Inf|NA|NaN><extra text>
484                       j++;  // Leave data initialized to empty_value
485                     }
486                   else
487                     {
488                       double y = octave_read_double (tmp_stream);
489 
490                       if (! iscmplx && y != 0.0)
491                         {
492                           iscmplx = true;
493                           cdata = ComplexMatrix (rdata);
494                         }
495 
496                       if (iscmplx)
497                         cdata(i,j++) = Complex (x, y);
498                       else
499                         rdata(i,j++) = x;
500                     }
501                 }
502             }
503           else
504             {
505               // octave_read_double() parsing failed
506               j++;  // Leave data initialized to empty_value
507             }
508 
509           pos1 = pos2 + 1;
510         }
511       while (pos2 != std::string::npos);
512 
513       if (i == r1)
514         break;  // Stop early if the desired range has been read.
515 
516       i++;
517     }
518 
519   // Clip selection indices to actual size of data
520   if (r1 >= r)
521     r1 = r - 1;
522   if (c1 >= c)
523     c1 = c - 1;
524 
525   if (iscmplx)
526     {
527       if ((i == 0 && j == 0) || (c0 > c1))
528         return ovl (ComplexMatrix (0,0));
529 
530       cdata = cdata.extract (0, c0, r1, c1);
531       return ovl (cdata);
532     }
533   else
534     {
535       if ((i == 0 && j == 0) || (c0 > c1))
536         return ovl (Matrix (0,0));
537 
538       rdata = rdata.extract (0, c0, r1, c1);
539       return ovl (rdata);
540     }
541 }
542 
543 /*
544 %!test
545 %! file = tempname ();
546 %! unwind_protect
547 %!   fid = fopen (file, "wt");
548 %!   fwrite (fid, "1, 2, 3\n4, 5, 6\n7, 8, 9\n10, 11, 12");
549 %!   fclose (fid);
550 %!
551 %!   assert (dlmread (file), [1, 2, 3; 4, 5, 6; 7, 8, 9;10, 11, 12]);
552 %!   assert (dlmread (file, ","), [1, 2, 3; 4, 5, 6; 7, 8, 9; 10, 11, 12]);
553 %!   assert (dlmread (file, ",", [1, 0, 2, 1]), [4, 5; 7, 8]);
554 %!   assert (dlmread (file, ",", "B1..C2"), [2, 3; 5, 6]);
555 %!   assert (dlmread (file, ",", "B1:C2"), [2, 3; 5, 6]);
556 %!   assert (dlmread (file, ",", "..C2"), [1, 2, 3; 4, 5, 6]);
557 %!   assert (dlmread (file, ",", 0, 1), [2, 3; 5, 6; 8, 9; 11, 12]);
558 %!   assert (dlmread (file, ",", "B1.."), [2, 3; 5, 6; 8, 9; 11, 12]);
559 %!   assert (dlmread (file, ",", 10, 0), []);
560 %!   assert (dlmread (file, ",", 0, 10), []);
561 %!   fail ('dlmread (file, ",", [0 1])', "error parsing RANGE");
562 %! unwind_protect_cleanup
563 %!   unlink (file);
564 %! end_unwind_protect
565 
566 %!testif ; ! ismac ()
567 %! file = tempname ();
568 %! unwind_protect
569 %!   fid = fopen (file, "wt");
570 %!   fwrite (fid, "1, 2, 3\n4+4i, 5, 6\n7, 8, 9\n10, 11, 12");
571 %!   fclose (fid);
572 %!
573 %!   assert (dlmread (file), [1, 2, 3; 4 + 4i, 5, 6; 7, 8, 9; 10, 11, 12]);
574 %!   assert (dlmread (file, ","), [1,2,3; 4 + 4i, 5, 6; 7, 8, 9; 10, 11, 12]);
575 %!   assert (dlmread (file, ",", [1, 0, 2, 1]), [4 + 4i, 5; 7, 8]);
576 %!   assert (dlmread (file, ",", "A2..B3"), [4 + 4i, 5; 7, 8]);
577 %!   assert (dlmread (file, ",", "A2:B3"), [4 + 4i, 5; 7, 8]);
578 %!   assert (dlmread (file, ",", "..B3"), [1, 2; 4 + 4i, 5; 7, 8]);
579 %!   assert (dlmread (file, ",", 1, 0), [4 + 4i, 5, 6; 7, 8, 9; 10, 11, 12]);
580 %!   assert (dlmread (file, ",", "A2.."), [4 + 4i, 5, 6; 7, 8, 9; 10, 11, 12]);
581 %!   assert (dlmread (file, ",", 10, 0), []);
582 %!   assert (dlmread (file, ",", 0, 10), []);
583 %! unwind_protect_cleanup
584 %!   unlink (file);
585 %! end_unwind_protect
586 
587 %!xtest <47413>
588 %! ## Same test code as above, but intended only for test statistics on Mac.
589 %! if (! ismac ()), return; endif
590 %! file = tempname ();
591 %! unwind_protect
592 %!   fid = fopen (file, "wt");
593 %!   fwrite (fid, "1, 2, 3\n4+4i, 5, 6\n7, 8, 9\n10, 11, 12");
594 %!   fclose (fid);
595 %!
596 %!   assert (dlmread (file), [1, 2, 3; 4 + 4i, 5, 6; 7, 8, 9; 10, 11, 12]);
597 %!   assert (dlmread (file, ","), [1,2,3; 4 + 4i, 5, 6; 7, 8, 9; 10, 11, 12]);
598 %!   assert (dlmread (file, ",", [1, 0, 2, 1]), [4 + 4i, 5; 7, 8]);
599 %!   assert (dlmread (file, ",", "A2..B3"), [4 + 4i, 5; 7, 8]);
600 %!   assert (dlmread (file, ",", "A2:B3"), [4 + 4i, 5; 7, 8]);
601 %!   assert (dlmread (file, ",", "..B3"), [1, 2; 4 + 4i, 5; 7, 8]);
602 %!   assert (dlmread (file, ",", 1, 0), [4 + 4i, 5, 6; 7, 8, 9; 10, 11, 12]);
603 %!   assert (dlmread (file, ",", "A2.."), [4 + 4i, 5, 6; 7, 8, 9; 10, 11, 12]);
604 %!   assert (dlmread (file, ",", 10, 0), []);
605 %!   assert (dlmread (file, ",", 0, 10), []);
606 %! unwind_protect_cleanup
607 %!   unlink (file);
608 %! end_unwind_protect
609 
610 %!test <*42025>
611 %! file = tempname ();
612 %! unwind_protect
613 %!   fid = fopen (file, "wt");
614 %!   fwrite (fid, "    \n 1 2\n11 22\n ");
615 %!   fclose (fid);
616 %!
617 %!   assert (dlmread (file), [1, 2; 11, 22]);
618 %!   assert (dlmread (file, " "), [ 0,  0, 0, 0
619 %!                                  0,  1, 2, 0
620 %!                                 11, 22, 0, 0
621 %!                                  0,  0, 0, 0]);
622 %! unwind_protect_cleanup
623 %!   unlink (file);
624 %! end_unwind_protect
625 
626 %!testif ; ! ismac ()   <*50589>
627 %! file = tempname ();
628 %! unwind_protect
629 %!   fid = fopen (file, "wt");
630 %!   fwrite (fid, "1;2;3\n");
631 %!   fwrite (fid, "1i;2I;3j;4J\n");
632 %!   fwrite (fid, "4;5;6\n");
633 %!   fwrite (fid, "-4i;+5I;-6j;+7J\n");
634 %!   fclose (fid);
635 %!
636 %!   assert (dlmread (file), [1, 2, 3, 0; 1i, 2i, 3i, 4i;
637 %!                            4, 5, 6, 0; -4i, 5i, -6i, 7i]);
638 %!   assert (dlmread (file, "", [0 0 0 3]), [1, 2, 3]);
639 %!   assert (dlmread (file, "", [1 0 1 3]), [1i, 2i, 3i, 4i]);
640 %! unwind_protect_cleanup
641 %!   unlink (file);
642 %! end_unwind_protect
643 
644 %!xtest <47413>
645 %! ## Same test code as above, but intended only for test statistics on Mac.
646 %! if (! ismac ()), return; endif
647 %! file = tempname ();
648 %! unwind_protect
649 %!   fid = fopen (file, "wt");
650 %!   fwrite (fid, "1;2;3\n");
651 %!   fwrite (fid, "1i;2I;3j;4J\n");
652 %!   fwrite (fid, "4;5;6\n");
653 %!   fwrite (fid, "-4i;+5I;-6j;+7J\n");
654 %!   fclose (fid);
655 %!
656 %!   assert (dlmread (file), [1, 2, 3, 0; 1i, 2i, 3i, 4i;
657 %!                            4, 5, 6, 0; -4i, 5i, -6i, 7i]);
658 %!   assert (dlmread (file, "", [0 0 0 3]), [1, 2, 3]);
659 %!   assert (dlmread (file, "", [1 0 1 3]), [1i, 2i, 3i, 4i]);
660 %! unwind_protect_cleanup
661 %!   unlink (file);
662 %! end_unwind_protect
663 
664 ## NA was not properly read from a file
665 %!test
666 %! file = tempname ();
667 %! unwind_protect
668 %!   fid = fopen (file, "wt");
669 %!   fwrite (fid, "1,NA,3");
670 %!   fclose (fid);
671 %!
672 %!   assert (dlmread (file), [1, NA, 3]);
673 %! unwind_protect_cleanup
674 %!   unlink (file);
675 %! end_unwind_protect
676 
677 ## "Name" was read as NA rather than parse error
678 %!test <*54029>
679 %! file = tempname ();
680 %! unwind_protect
681 %!   fid = fopen (file, "wt");
682 %!   fwrite (fid, "NaNe,bNa,Name,c\n1,NaN,3,Inftest\n-Inf,6,NA,8");
683 %!   fclose (fid);
684 %!
685 %!   assert (dlmread (file), [0, 0, 0, 0; 1, NaN, 3, 0; -Inf, 6, NA, 8]);
686 %! unwind_protect_cleanup
687 %!   unlink (file);
688 %! end_unwind_protect
689 
690 ## Infinity incorrectly changed matrix to complex, rather than parse error
691 %!test
692 %! file = tempname ();
693 %! unwind_protect
694 %!   fid = fopen (file, "wt");
695 %!   fwrite (fid, "1,Infinity,3");
696 %!   fclose (fid);
697 %!
698 %!   assert (dlmread (file), [1, 0, 3]);
699 %! unwind_protect_cleanup
700 %!   unlink (file);
701 %! end_unwind_protect
702 
703 ## Purely complex numbers with trailing garbage produced complex matrix
704 %!test
705 %! file = tempname ();
706 %! unwind_protect
707 %!   fid = fopen (file, "wt");
708 %!   fwrite (fid, "1,2jack,3");
709 %!   fclose (fid);
710 %!
711 %!   assert (dlmread (file), [1, 0, 3]);
712 %! unwind_protect_cleanup
713 %!   unlink (file);
714 %! end_unwind_protect
715 
716 */
717