## Copyright (C) 2009-2013 Eric Chassande-Mottin, CNRS (France) ## ## This file is part of Octave. ## ## Octave is free software; you can redistribute it and/or modify it ## under the terms of the GNU General Public License as published by ## the Free Software Foundation; either version 3 of the License, or (at ## your option) any later version. ## ## Octave is distributed in the hope that it will be useful, but ## WITHOUT ANY WARRANTY; without even the implied warranty of ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ## General Public License for more details. ## ## You should have received a copy of the GNU General Public License ## along with Octave; see the file COPYING. If not, see ## . ## -*- texinfo -*- ## @deftypefn {Function File} {[@var{a}, @dots{}] =} textread (@var{filename}) ## @deftypefnx {Function File} {[@var{a}, @dots{}] =} textread (@var{filename}, @var{format}) ## @deftypefnx {Function File} {[@var{a}, @dots{}] =} textread (@var{filename}, @var{format}, @var{n}) ## @deftypefnx {Function File} {[@var{a}, @dots{}] =} textread (@var{filename}, @var{format}, @var{prop1}, @var{value1}, @dots{}) ## @deftypefnx {Function File} {[@var{a}, @dots{}] =} textread (@var{filename}, @var{format}, @var{n}, @var{prop1}, @var{value1}, @dots{}) ## Read data from a text file. ## ## The file @var{filename} is read and parsed according to @var{format}. The ## function behaves like @code{strread} except it works by parsing a file ## instead of a string. See the documentation of @code{strread} for details. ## ## In addition to the options supported by @code{strread}, this function ## supports two more: ## ## @itemize ## @item "headerlines": ## The first @var{value} number of lines of @var{filename} are skipped. ## ## @item "endofline": ## Specify a single character or "\r\n". If no value is given, it will be ## inferred from the file. If set to "" (empty string) EOLs are ignored as ## delimiters. ## @end itemize ## ## The optional input @var{n} specifies the number of data lines to read; in ## this sense it differs slightly from the format repeat count in strread. ## ## @seealso{strread, load, dlmread, fscanf, textscan} ## @end deftypefn function varargout = textread (filename, format = "%f", varargin) BUFLENGTH = 4096; # Read buffer to speed up processing @var{n} ## Check input if (nargin < 1) print_usage (); endif if (! ischar (filename) || ! ischar (format)) error ("textread: FILENAME and FORMAT arguments must be strings"); endif if (! isempty (varargin) && isnumeric (varargin{1})) nlines = varargin{1}; else nlines = Inf; endif if (nlines < 1) printf ("textread: N = 0, no data read\n"); varargout = cell (1, nargout); return endif ## Read file fid = fopen (filename, "r"); if (fid == -1) error ("textread: could not open '%s' for reading", filename); endif ## Skip header lines if requested headerlines = find (strcmpi (varargin, "headerlines"), 1); if (! isempty (headerlines)) ## Beware of missing or wrong headerline value if (headerlines == numel (varargin) || ! isnumeric (varargin{headerlines + 1})) error ("missing or illegal value for 'headerlines'" ); endif ## Avoid conveying floats to fskipl varargin{headerlines + 1} = round (varargin{headerlines + 1}); ## Beware of zero valued headerline, fskipl would skip to EOF if (varargin{headerlines + 1} > 0) fskipl (fid, varargin{headerlines + 1}); varargin(headerlines:headerlines+1) = []; nargin = nargin - 2; elseif (varargin{headerlines + 1} < 0) warning ("textread: negative headerline value ignored"); endif endif st_pos = ftell (fid); ## Read a first file chunk. Rest follows after endofline processing [str, count] = fscanf (fid, "%c", BUFLENGTH); if (isempty (str) || count < 1) warning ("textread: empty file"); varargout = cell (1, nargout); return; endif endofline = find (strcmpi (varargin, "endofline"), 1); if (! isempty (endofline)) ## 'endofline' option set by user. if (! ischar (varargin{endofline + 1})); error ("character value required for EndOfLine"); endif else ## Determine EOL from file. Search for EOL candidates in first BUFLENGTH chars eol_srch_len = min (length (str), BUFLENGTH); ## First try DOS (CRLF) if (! isempty (strfind ("\r\n", str(1 : eol_srch_len)))) eol_char = "\r\n"; ## Perhaps old Macintosh? (CR) elseif (! isempty (strfind ("\r", str(1 : eol_srch_len)))) eol_char = "\r"; ## Otherwise, use plain UNIX (LF) else eol_char = "\n"; endif ## Set up default endofline param value varargin(end+1:end+2) = {"endofline", eol_char}; endif ## Now that we know what EOL looks like, we can process format_repeat_count. ## FIXME The below isn't ML-compatible: counts lines, not format string uses if (isfinite (nlines) && (nlines > 0)) l_eol_char = length (eol_char); eoi = findstr (str, eol_char); n_eoi = length (eoi); nblks = 0; ## Avoid slow repeated str concatenation, first seek requested end of data while (n_eoi < nlines && count == BUFLENGTH) [nstr, count] = fscanf (fid, "%c", BUFLENGTH); if (count > 0) ## Watch out for multichar EOL being missed across buffer boundaries if (l_eol_char > 1) str = [str(end - length (eol_char) + 2 : end) nstr]; else str = nstr; endif eoi = findstr (str, eol_char); n_eoi += numel (eoi); ++nblks; endif endwhile ## Found EOL delimiting last requested line. Compute ptr (incl. EOL) if (isempty (eoi)) printf ("textread: format repeat count specified but no endofline found\n"); eoi_pos = nblks * BUFLENGTH + count; else eoi_pos = (nblks * BUFLENGTH) + eoi(end + min (nlines, n_eoi) - n_eoi); endif fseek (fid, st_pos, "bof"); str = fscanf (fid, "%c", eoi_pos); else fseek (fid, st_pos, "bof"); str = fread (fid, "char=>char").'; endif fclose (fid); ## Set up default whitespace param value if needed if (isempty (find (strcmpi ("whitespace", varargin)))) varargin(end+1:end+2) = {"whitespace", " \b\t"}; endif ## Call strread to make it do the real work [varargout{1:max (nargout, 1)}] = strread (str, format, varargin {:}); ## Hack to concatenate/reshape numeric output into 2D array (undocumented ML) ## In ML this only works in case of an empty format string and nargout <= 1 if (isempty (format) && nargout < 2) ## Get number of fields per line. ## 1. Get eol_char position iwhsp = find (strcmpi ("whitespace", varargin)); whsp = varargin{iwhsp + 1}; idx = regexp (str, eol_char, "once"); ## 2. Get first data line til EOL. Avoid corner case of just one line if (! isempty (idx)) str = str(1:idx-1); endif ## 3. whitespace incl. consecutive whitespace => single space str = regexprep (str, sprintf ("[%s]+", whsp), ' '); ## 4. Remove possible leading & trailing spaces str = strtrim (str); ## 5. Count spaces, add one to get nr of data fields per line ncols = numel (strfind (str, " ")) + 1; ## 6. Reshape, watch out we need a transpose nrows = numel (varargout{1}) / ncols; varargout{1} = reshape (varargout{1}, ncols, nrows)'; endif endfunction %!test %! f = tmpnam (); %! d = rand (5, 3); %! dlmwrite (f, d, "precision", "%5.2f"); %! [a, b, c] = textread (f, "%f %f %f", "delimiter", ",", "headerlines", 3); %! unlink (f); %! assert (a, d(4:5, 1), 1e-2); %! assert (b, d(4:5, 2), 1e-2); %! assert (c, d(4:5, 3), 1e-2); %% Test reading 2D matrix with empty format %!test %! f = tmpnam (); %! d = rand (5, 2); %! dlmwrite (f, d, "precision", "%5.2f"); %! A = textread (f, "", "headerlines", 3); %! unlink (f); %! assert (A, d(4:5, :), 1e-2); %% Test reading 2D matrix with empty format, corner case = one line w/o EOL %!test %! f = tmpnam (); %! unlink (f); %! fid = fopen (f, "w"); %! d = rand (1, 4); %! fprintf (fid, " %f %f %f %f ", d); %! fclose (fid); %! A = textread (f, ""); %! unlink (f); %! assert (A, d, 1e-6); %!test %! f = tmpnam (); %! unlink (f); %! fid = fopen (f, "w"); %! d = rand (1, 4); %! fprintf (fid, " %f %f %f %f ", d); %! fclose (fid); %! A = textread (f, ""); %! unlink (f); %! assert (A, d, 1e-6); %% Test input validation %!error textread () %!error textread (1) %!error textread (1, "%f") %!error textread ("fname", 1) %!error textread (file_in_loadpath ("textread.m"), "", "headerlines") %!error textread (file_in_loadpath ("textread.m"), "", "headerlines", 'hh') %!error textread (file_in_loadpath ("textread.m"), "%s", "endofline", true)