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