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