GNU Octave  9.1.0
A high-level interpreted language, primarily intended for numerical computations, mostly compatible with Matlab
ls-oct-text.cc
Go to the documentation of this file.
1 ////////////////////////////////////////////////////////////////////////
2 //
3 // Copyright (C) 1996-2024 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 #if defined (HAVE_CONFIG_H)
27 # include "config.h"
28 #endif
29 
30 #include <cstring>
31 #include <cctype>
32 
33 #include <fstream>
34 #include <iomanip>
35 #include <istream>
36 #include <ostream>
37 #include <sstream>
38 #include <string>
39 
40 #include "byte-swap.h"
41 #include "data-conv.h"
42 #include "file-ops.h"
43 #include "glob-match.h"
44 #include "lo-mappers.h"
45 #include "mach-info.h"
46 #include "oct-env.h"
47 #include "oct-time.h"
48 #include "quit.h"
49 #include "str-vec.h"
50 
51 #include "Cell.h"
52 #include "defun.h"
53 #include "error.h"
54 #include "errwarn.h"
55 #include "interpreter.h"
56 #include "interpreter-private.h"
57 #include "load-save.h"
58 #include "ls-ascii-helper.h"
59 #include "ls-oct-text.h"
60 #include "ovl.h"
61 #include "oct-map.h"
62 #include "ov-cell.h"
63 #include "pager.h"
64 #include "unwind-prot.h"
65 #include "utils.h"
66 #include "variables.h"
67 #include "version.h"
68 #include "dMatrix.h"
69 
70 // The number of decimal digits to use when writing ASCII data.
71 // 17 is the minimum necessary for lossless save/restore of IEEE-754 doubles.
72 static int Vsave_precision = 17;
73 
74 // Functions for reading octave format text data.
75 
76 // Extract a KEYWORD and its value from stream IS, returning the
77 // associated value in a new string.
78 //
79 // Input should look something like:
80 //
81 // [%#][ \t]*keyword[ \t]*:[ \t]*string-value[ \t]*\n
82 
83 std::string
84 extract_keyword (std::istream& is, const char *keyword, const bool next_only)
85 {
86  std::string retval;
87 
88  int ch = is.peek ();
89  if (next_only && ch != '%' && ch != '#')
90  return retval;
91 
92  char c;
93  while (is.get (c))
94  {
95  if (c == '%' || c == '#')
96  {
97  std::ostringstream buf;
98 
99  while (is.get (c) && (c == ' ' || c == '\t' || c == '%' || c == '#'))
100  ; // Skip whitespace and comment characters.
101 
102  if (isalpha (c))
103  buf << c;
104 
105  while (is.get (c) && isalpha (c))
106  buf << c;
107 
108  std::string tmp = buf.str ();
109  bool match = (tmp.substr (0, strlen (keyword)) == keyword);
110 
111  if (match)
112  {
113  std::ostringstream value;
114  while (is.get (c) && (c == ' ' || c == '\t' || c == ':'))
115  ; // Skip whitespace and the colon.
116 
117  is.putback (c);
118  retval = octave::read_until_newline (is, false);
119  break;
120  }
121  else if (next_only)
122  break;
123  else
124  octave::skip_until_newline (is, false);
125  }
126  }
127 
128  int len = retval.length ();
129 
130  if (len > 0)
131  {
132  while (len)
133  {
134  c = retval[len-1];
135 
136  if (c == ' ' || c == '\t')
137  len--;
138  else
139  {
140  retval.resize (len);
141  break;
142  }
143  }
144  }
145 
146  return retval;
147 }
148 
149 // Extract one value (scalar, matrix, string, etc.) from stream IS and
150 // place it in TC, returning the name of the variable. If the value
151 // is tagged as global in the file, return TRUE in GLOBAL.
152 //
153 // Each type supplies its own function to load the data, and so this
154 // function is extensible.
155 //
156 // FILENAME is used for error messages.
157 //
158 // The data is expected to be in the following format:
159 //
160 // The input file must have a header followed by some data.
161 //
162 // All lines in the header must begin with a '#' character.
163 //
164 // The header must contain a list of keyword and value pairs with the
165 // keyword and value separated by a colon.
166 //
167 // Keywords must appear in the following order:
168 //
169 // # name: <name>
170 // # type: <type>
171 // # <info>
172 //
173 // Where, for the built in types are:
174 //
175 // <name> : a valid identifier
176 //
177 // <type> : <typename>
178 // | global <typename>
179 //
180 // <typename> : scalar
181 // | complex scalar
182 // | matrix
183 // | complex matrix
184 // | bool
185 // | bool matrix
186 // | string
187 // | range
188 //
189 // <info> : <matrix info>
190 // | <string info>
191 //
192 // <matrix info> : # rows: <integer>
193 // : # columns: <integer>
194 //
195 // <string info> : # elements: <integer>
196 // : # length: <integer> (once before each string)
197 //
198 // For backward compatibility the type "string array" is treated as a
199 // "string" type. Also "string" can have a single element with no elements
200 // line such that
201 //
202 // <string info> : # length: <integer>
203 //
204 // Formatted ASCII data follows the header.
205 //
206 // Example:
207 //
208 // # name: foo
209 // # type: matrix
210 // # rows: 2
211 // # columns: 2
212 // 2 4
213 // 1 3
214 //
215 // Example:
216 //
217 // # name: foo
218 // # type: string
219 // # elements: 5
220 // # length: 4
221 // this
222 // # length: 2
223 // is
224 // # length: 1
225 // a
226 // # length: 6
227 // string
228 // # length: 5
229 // array
230 //
231 // FIXME: This format is fairly rigid, and doesn't allow for arbitrary comments.
232 // Someone should fix that. It does allow arbitrary types however.
233 
234 // Ugh. The signature of the compare method is not standard in older
235 // versions of the GNU libstdc++. Do this instead:
236 
237 #define SUBSTRING_COMPARE_EQ(s, pos, n, t) (s.substr (pos, n) == (t))
238 
239 static octave_value
240 load_inline_fcn (std::istream& is, const std::string& filename)
241 {
242  int nargs;
243  if (extract_keyword (is, "nargs", nargs, true))
244  {
245  std::string name;
246  octave_value_list args (nargs+1);
247  for (int i = 0; i < nargs; i++)
248  {
249  std::string tmp;
250  is >> tmp;
251  args(i+1) = tmp;
252  }
253  is >> name;
254  if (name == "0")
255  name = "";
256 
258 
259  std::string buf;
260 
261  if (is)
262  {
263 
264  // Get a line of text whitespace characters included,
265  // leaving newline in the stream.
266  buf = octave::read_until_newline (is, true);
267  }
268 
269  if (is)
270  {
271  args(0) = std::string (buf);
272 
273  octave::interpreter& interp = octave::__get_interpreter__ ();
274 
275  octave_value_list tmp = interp.feval ("inline", args, 1);
276 
277  if (tmp.length () > 0)
278  return tmp(0);
279  }
280  }
281 
282  error ("load: trouble reading ascii file '%s'", filename.c_str ());
283 }
284 
285 std::string
286 read_text_data (std::istream& is, const std::string& filename, bool& global,
287  octave_value& tc, octave_idx_type count,
288  const bool do_name_validation)
289 {
290  // Read name for this entry or break on EOF.
291 
292  std::string name = extract_keyword (is, "name");
293 
294  if (name.empty ())
295  {
296  if (count == 0)
297  error ("load: empty name keyword or no data found in file '%s'",
298  filename.c_str ());
299 
300  return "";
301  }
302 
303  if (name != CELL_ELT_TAG
304  && do_name_validation && ! octave::valid_identifier (name))
305  error ("load: invalid identifier '%s' found in file '%s'",
306  name.c_str (), filename.c_str ());
307 
308  // Look for type keyword.
309 
310  std::string tag = extract_keyword (is, "type");
311 
312  if (tag.empty ())
313  error ("load: failed to extract keyword specifying value type");
314 
315  std::string typ;
316  std::size_t pos = tag.rfind (' ');
317 
318  if (pos != std::string::npos)
319  {
320  global = SUBSTRING_COMPARE_EQ (tag, 0, 6, "global");
321 
322  typ = (global ? tag.substr (7) : tag);
323  }
324  else
325  typ = tag;
326 
327  // Special case for backward compatibility. A small bit of cruft
328  if (SUBSTRING_COMPARE_EQ (typ, 0, 12, "string array"))
329  tc = charMatrix ();
330  else if (SUBSTRING_COMPARE_EQ (typ, 0, 15, "inline function"))
331  {
332  // Special case for loading old octave_inline_fcn objects.
333  tc = load_inline_fcn (is, filename);
334  return name;
335  }
336  else
337  {
338  octave::type_info& type_info = octave::__get_type_info__ ();
339 
340  tc = type_info.lookup_type (typ);
341  }
342 
343  if (! tc.load_ascii (is))
344  error ("load: trouble reading ascii file '%s'", filename.c_str ());
345 
346  return name;
347 }
348 
349 // Save the data from TC along with the corresponding NAME, and global
350 // flag MARK_AS_GLOBAL on stream OS in the plain text format described
351 // above for load_text_data. If NAME is empty, the name: line is not
352 // generated. PRECISION specifies the number of decimal digits to print.
353 //
354 // Assumes ranges and strings cannot contain Inf or NaN values.
355 //
356 // Returns 1 for success and 0 for failure.
357 
358 // FIXME: should probably write the help string here too.
359 
360 bool
361 save_text_data (std::ostream& os, const octave_value& val_arg,
362  const std::string& name, bool mark_global,
363  int precision)
364 {
365  if (! name.empty ())
366  os << "# name: " << name << "\n";
367 
368  octave_value val = val_arg;
369 
370  if (mark_global)
371  os << "# type: global " << val.type_name () << "\n";
372  else
373  os << "# type: " << val.type_name () << "\n";
374 
375  if (! precision)
376  precision = Vsave_precision;
377 
378  long old_precision = os.precision ();
379  os.precision (precision);
380 
381  bool success = val.save_ascii (os);
382 
383  // Insert an extra pair of newline characters after the data so that
384  // multiple data elements may be handled separately by gnuplot (see
385  // the description of the index qualifier for the plot command in the
386  // gnuplot documentation).
387  os << "\n\n";
388 
389  os.precision (old_precision);
390 
391  return (os && success);
392 }
393 
394 bool
395 save_text_data_for_plotting (std::ostream& os, const octave_value& t,
396  const std::string& name)
397 {
398  return save_text_data (os, t, name, false, 6);
399 }
400 
401 // Maybe this should be a static function in tree-plot.cc?
402 
403 // If TC is matrix, save it on stream OS in a format useful for
404 // making a 3-D plot with gnuplot. If PARAMETRIC is TRUE,
405 // assume a parametric 3-D plot will be generated.
406 
407 bool
408 save_three_d (std::ostream& os, const octave_value& tc, bool parametric)
409 {
410  octave_idx_type nr = tc.rows ();
411  octave_idx_type nc = tc.columns ();
412 
413  if (! tc.is_real_matrix ())
414  error ("for now, I can only save real matrices in 3-D format");
415 
416  os << "# 3-D data...\n"
417  << "# type: matrix\n"
418  << "# total rows: " << nr << "\n"
419  << "# total columns: " << nc << "\n";
420 
421  long old_precision = os.precision ();
422  os.precision (6);
423 
424  if (parametric)
425  {
426  octave_idx_type extras = nc % 3;
427  if (extras)
428  warning ("ignoring last %" OCTAVE_IDX_TYPE_FORMAT " columns", extras);
429 
430  Matrix tmp = tc.matrix_value ();
431  nr = tmp.rows ();
432 
433  for (octave_idx_type i = 0; i < nc-extras; i += 3)
434  {
435  os << tmp.extract (0, i, nr-1, i+2);
436  if (i+3 < nc-extras)
437  os << "\n";
438  }
439  }
440  else
441  {
442  Matrix tmp = tc.matrix_value ();
443  nr = tmp.rows ();
444 
445  for (octave_idx_type i = 0; i < nc; i++)
446  {
447  os << tmp.extract (0, i, nr-1, i);
448  if (i+1 < nc)
449  os << "\n";
450  }
451  }
452 
453  os.precision (old_precision);
454 
455  return (static_cast<bool> (os));
456 }
457 
459 
460 DEFUN (save_precision, args, nargout,
461  doc: /* -*- texinfo -*-
462 @deftypefn {} {@var{val} =} save_precision ()
463 @deftypefnx {} {@var{old_val} =} save_precision (@var{new_val})
464 @deftypefnx {} {@var{old_val} =} save_precision (@var{new_val}, "local")
465 Query or set the internal variable that specifies the number of digits to
466 keep when saving data in text format.
467 
468 The default value is 17 which is the minimum necessary for the lossless saving
469 and restoring of IEEE-754 double values; For IEEE-754 single values the minimum
470 value is 9. If file size is a concern, it is probably better to choose a
471 binary format for saving data rather than to reduce the precision of the saved
472 values.
473 
474 When called from inside a function with the @qcode{"local"} option, the
475 variable is changed locally for the function and any subroutines it calls.
476 The original variable value is restored when exiting the function.
477 
478 @seealso{save_default_options}
479 @end deftypefn */)
480 {
481  return set_internal_variable (Vsave_precision, args, nargout,
482  "save_precision", -1,
484 }
485 
486 OCTAVE_END_NAMESPACE(octave)
charNDArray max(char d, const charNDArray &m)
Definition: chNDArray.cc:230
octave_idx_type rows() const
Definition: Array.h:459
Definition: dMatrix.h:42
Matrix extract(octave_idx_type r1, octave_idx_type c1, octave_idx_type r2, octave_idx_type c2) const
Definition: dMatrix.cc:397
octave_idx_type length() const
Definition: ovl.h:113
octave_idx_type rows() const
Definition: ov.h:545
bool save_ascii(std::ostream &os)
Definition: ov.h:1366
bool load_ascii(std::istream &is)
bool is_real_matrix() const
Definition: ov.h:613
std::string type_name() const
Definition: ov.h:1345
octave_idx_type columns() const
Definition: ov.h:547
Matrix matrix_value(bool frc_str_conv=false) const
Definition: ov.h:853
octave_value lookup_type(const std::string &nm)
Definition: ov-typeinfo.cc:494
OCTAVE_BEGIN_NAMESPACE(octave) static octave_value daspk_fcn
#define DEFUN(name, args_name, nargout_name, doc)
Macro to define a builtin function.
Definition: defun.h:56
void warning(const char *fmt,...)
Definition: error.cc:1063
void() error(const char *fmt,...)
Definition: error.cc:988
interpreter & __get_interpreter__()
type_info & __get_type_info__()
void skip_until_newline(std::istream &is, bool keep_newline)
std::string read_until_newline(std::istream &is, bool keep_newline)
void skip_preceeding_newline(std::istream &is)
std::string read_text_data(std::istream &is, const std::string &filename, bool &global, octave_value &tc, octave_idx_type count, const bool do_name_validation)
Definition: ls-oct-text.cc:286
bool save_text_data_for_plotting(std::ostream &os, const octave_value &t, const std::string &name)
Definition: ls-oct-text.cc:395
std::string extract_keyword(std::istream &is, const char *keyword, const bool next_only)
Definition: ls-oct-text.cc:84
bool save_text_data(std::ostream &os, const octave_value &val_arg, const std::string &name, bool mark_global, int precision)
Definition: ls-oct-text.cc:361
bool save_three_d(std::ostream &os, const octave_value &tc, bool parametric)
Definition: ls-oct-text.cc:408
#define SUBSTRING_COMPARE_EQ(s, pos, n, t)
Definition: ls-oct-text.cc:237
#define CELL_ELT_TAG
Definition: ls-oct-text.h:42
bool valid_identifier(const char *s)
Definition: utils.cc:79
octave_value set_internal_variable(bool &var, const octave_value_list &args, int nargout, const char *nm)
Definition: variables.cc:583
T::size_type strlen(const typename T::value_type *str)
Definition: oct-string.cc:88
F77_RET_T len
Definition: xerbla.cc:61