libdap Updated for version 3.18.1
DDS.cc
1// -*- mode: c++; c-basic-offset:4 -*-
2
3// This file is part of libdap, A C++ implementation of the OPeNDAP Data
4// Access Protocol.
5
6// Copyright (c) 2002,2003 OPeNDAP, Inc.
7// Author: James Gallagher <jgallagher@opendap.org>
8//
9// This library is free software; you can redistribute it and/or
10// modify it under the terms of the GNU Lesser General Public
11// License as published by the Free Software Foundation; either
12// version 2.1 of the License, or (at your option) any later version.
13//
14// This library is distributed in the hope that it will be useful,
15// but WITHOUT ANY WARRANTY; without even the implied warranty of
16// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
17// Lesser General Public License for more details.
18//
19// You should have received a copy of the GNU Lesser General Public
20// License along with this library; if not, write to the Free Software
21// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
22//
23// You can contact OPeNDAP, Inc. at PO Box 112, Saunderstown, RI. 02874-0112.
24
25// (c) COPYRIGHT URI/MIT 1994-1999
26// Please read the full copyright statement in the file COPYRIGHT_URI.
27//
28// Authors:
29// jhrg,jimg James Gallagher <jgallagher@gso.uri.edu>
30
31//
32// jhrg 9/7/94
33
34#include "config.h"
35
36#include <cstdio>
37#include <cmath>
38#include <sys/types.h>
39
40#ifdef WIN32
41#include <io.h>
42#include <process.h>
43#include <fstream>
44#else
45#include <unistd.h> // for alarm and dup
46#include <sys/wait.h>
47#endif
48
49#include <iostream>
50#include <sstream>
51#include <algorithm>
52#include <functional>
53
54//#define DODS_DEBUG
55//#define DODS_DEBUG2
56
57#include "GNURegex.h"
58
59#include "DAS.h"
60#include "Clause.h"
61#include "Error.h"
62#include "InternalErr.h"
63#include "Keywords2.h"
64
65#include "parser.h"
66#include "debug.h"
67#include "util.h"
68
69#include "Byte.h"
70#include "Int16.h"
71#include "UInt16.h"
72#include "Int32.h"
73#include "UInt32.h"
74#include "Float32.h"
75#include "Float64.h"
76#include "Str.h"
77#include "Url.h"
78#include "Array.h"
79#include "Structure.h"
80#include "Sequence.h"
81#include "Grid.h"
82
83#include "escaping.h"
84
95const string c_xml_xsi = "http://www.w3.org/2001/XMLSchema-instance";
96const string c_xml_namespace = "http://www.w3.org/XML/1998/namespace";
97
98const string grddl_transformation_dap32 = "http://xml.opendap.org/transforms/ddxToRdfTriples.xsl";
99
100const string c_default_dap20_schema_location = "http://xml.opendap.org/dap/dap2.xsd";
101const string c_default_dap32_schema_location = "http://xml.opendap.org/dap/dap3.2.xsd";
102const string c_default_dap40_schema_location = "http://xml.opendap.org/dap/dap4.0.xsd";
103
104const string c_dap20_namespace = "http://xml.opendap.org/ns/DAP2";
105const string c_dap32_namespace = "http://xml.opendap.org/ns/DAP/3.2#";
106const string c_dap40_namespace = "http://xml.opendap.org/ns/DAP/4.0#";
107
108const string c_dap_20_n_sl = c_dap20_namespace + " " + c_default_dap20_schema_location;
109const string c_dap_32_n_sl = c_dap32_namespace + " " + c_default_dap32_schema_location;
110const string c_dap_40_n_sl = c_dap40_namespace + " " + c_default_dap40_schema_location;
121using namespace std;
122
123int ddsparse(libdap::parser_arg *arg);
124
125// Glue for the DDS parser defined in dds.lex
126void dds_switch_to_buffer(void *new_buffer);
127void dds_delete_buffer(void * buffer);
128void *dds_buffer(FILE *fp);
129
130namespace libdap {
131
132void
133DDS::duplicate(const DDS &dds)
134{
135 DBG(cerr << "Entering DDS::duplicate... " <<endl);
136#if 0
137 BaseTypeFactory *d_factory;
138
139 string d_name; // The dataset d_name
140 string d_filename; // File d_name (or other OS identifier) for
141 string d_container_name; // d_name of container structure
142 Structure *d_container; // current container for container d_name
143 // dataset or part of dataset.
144
145 int d_dap_major; // The protocol major version number
146 int d_dap_minor; // ... and minor version number
147 string d_dap_version; // String version of the protocol
148 string d_request_xml_base;
149 string d_namespace;
150
151 AttrTable d_attr; // Global attributes.
152
153 vector<BaseType *> vars; // Variables at the top level
154
155 int d_timeout; // alarm time in seconds. If greater than
156 // zero, raise the alarm signal if more than
157 // d_timeout seconds are spent reading data.
158 Keywords d_keywords; // Holds keywords parsed from the CE
159
160 long d_max_response_size; // In bytes
161#endif
162
163 d_factory = dds.d_factory;
164
165 d_name = dds.d_name;
166 d_filename = dds.d_filename;
167 d_container_name = dds.d_container_name;
168 d_container = dds.d_container;
169
170 d_dap_major = dds.d_dap_major;
171 d_dap_minor = dds.d_dap_minor;
172
173 d_dap_version = dds.d_dap_version; // String version of the protocol
174 d_request_xml_base = dds.d_request_xml_base;
175 d_namespace = dds.d_namespace;
176
177 d_attr = dds.d_attr;
178
179 DDS &dds_tmp = const_cast<DDS &>(dds);
180
181 // copy the things pointed to by the list, not just the pointers
182 for (Vars_iter i = dds_tmp.var_begin(); i != dds_tmp.var_end(); i++) {
183 add_var(*i); // add_var() dups the BaseType.
184 }
185
186 d_timeout = dds.d_timeout;
187
188 d_keywords = dds.d_keywords; // value copy; Keywords contains no pointers
189
190 d_max_response_size = dds.d_max_response_size;
191}
192
205DDS::DDS(BaseTypeFactory *factory, const string &name)
206 : d_factory(factory), d_name(name), d_container_name(""), d_container(0),
207 d_request_xml_base(""),
208 d_timeout(0), d_keywords(), d_max_response_size(0)
209{
210 DBG(cerr << "Building a DDS for the default version (2.0)" << endl);
211
212 // This method sets a number of values, including those returned by
213 // get_protocol_major(), ..., get_namespace().
214 set_dap_version("2.0");
215}
216
232DDS::DDS(BaseTypeFactory *factory, const string &name, const string &version)
233 : d_factory(factory), d_name(name), d_container_name(""), d_container(0),
234 d_request_xml_base(""),
235 d_timeout(0), d_keywords(), d_max_response_size(0)
236{
237 DBG(cerr << "Building a DDS for version: " << version << endl);
238
239 // This method sets a number of values, including those returned by
240 // get_protocol_major(), ..., get_namespace().
241 set_dap_version(version);
242}
243
245DDS::DDS(const DDS &rhs) : DapObj()
246{
247 DBG(cerr << "Entering DDS(const DDS &rhs) ..." << endl);
248 duplicate(rhs);
249 DBG(cerr << " bye." << endl);
250}
251
252DDS::~DDS()
253{
254 // delete all the variables in this DDS
255 for (Vars_iter i = vars.begin(); i != vars.end(); i++) {
256 BaseType *btp = *i ;
257 delete btp ; btp = 0;
258 }
259}
260
261DDS &
262DDS::operator=(const DDS &rhs)
263{
264 DBG(cerr << "Entering DDS::operator= ..." << endl);
265 if (this == &rhs)
266 return *this;
267
268 duplicate(rhs);
269
270 DBG(cerr << " bye." << endl);
271 return *this;
272}
273
288{
289 // If there is a container set in the DDS then check the container from
290 // the DAS. If they are not the same container, then throw an exception
291 // (should be working on the same container). If the container does not
292 // exist in the DAS, then throw an exception
293 if (d_container && das->container_name() != d_container_name)
294 throw InternalErr(__FILE__, __LINE__,
295 "Error transferring attributes: working on a container in dds, but not das");
296
297 // Give each variable a chance to claim its attributes.
299
300 for (DDS::Vars_iter i = var_begin(), e = var_end(); i != e; i++) {
301 (*i)->transfer_attributes(top);
302 }
303#if 0
304 Vars_iter var = var_begin();
305 while (var != var_end()) {
306 try {
307 DBG(cerr << "Processing the attributes for: " << (*var)->d_name() << " a " << (*var)->type_name() << endl);
308 (*var)->transfer_attributes(top);
309 var++;
310 }
311 catch (Error &e) {
312 DBG(cerr << "Got this exception: " << e.get_error_message() << endl);
313 var++;
314 throw e;
315 }
316 }
317#endif
318 // Now we transfer all of the attributes still marked as global to the
319 // global container in the DDS.
320 for (AttrTable::Attr_iter i = top->attr_begin(), e = top->attr_end(); i != e; ++i) {
321 if ((*i)->type == Attr_container && (*i)->attributes->is_global_attribute()) {
322 // copy the source container so that the DAS passed in can be
323 // deleted after calling this method.
324 AttrTable *at = new AttrTable(*(*i)->attributes);
325 d_attr.append_container(at, at->get_name());
326 }
327 }
328#if 0
329 AttrTable::Attr_iter at_cont_p = top_level->attr_begin();
330 while (at_cont_p != top_level->attr_end()) {
331 // In truth, all of the top level attributes should be containers, but
332 // this test handles the abnormal case where somehow someone makes a
333 // top level attribute that is not a container by silently dropping it.
334 if ((*at_cont_p)->type == Attr_container && (*at_cont_p)->attributes->is_global_attribute()) {
335 DBG(cerr << (*at_cont_p)->d_name << " is a global attribute." << endl);
336 // copy the source container so that the DAS passed in can be
337 // deleted after calling this method.
338 AttrTable *at = new AttrTable(*(*at_cont_p)->attributes);
339 d_attr.append_container(at, at->get_name());
340 }
341
342 at_cont_p++;
343 }
344#endif
345}
346
354
356string
358{
359 return d_name;
360}
361
363void
364DDS::set_dataset_name(const string &n)
365{
366 d_name = n;
367}
368
370
372AttrTable &
374{
375 return d_attr;
376}
377
387string
389{
390 return d_filename;
391}
392
394void
395DDS::filename(const string &fn)
396{
397 d_filename = fn;
398}
400
404void
406{
407 d_dap_major = p;
408
409 // This works because regardless of the order set_dap_major and set_dap_minor
410 // are called, once they both are called, the value in the string is
411 // correct. I protect against negative numbers because that would be
412 // nonsensical.
413 if (d_dap_minor >= 0) {
414 ostringstream oss;
415 oss << d_dap_major << "." << d_dap_minor;
416 d_dap_version = oss.str();
417 }
418}
419
423void
425{
426 d_dap_minor = p;
427
428 if (d_dap_major >= 0) {
429 ostringstream oss;
430 oss << d_dap_major << "." << d_dap_minor;
431 d_dap_version = oss.str();
432 }
433}
434
440void
441DDS::set_dap_version(const string &v /* = "2.0" */)
442{
443 istringstream iss(v);
444
445 int major = -1, minor = -1;
446 char dot;
447 if (!iss.eof() && !iss.fail())
448 iss >> major;
449 if (!iss.eof() && !iss.fail())
450 iss >> dot;
451 if (!iss.eof() && !iss.fail())
452 iss >> minor;
453
454 if (major == -1 || minor == -1 or dot != '.')
455 throw InternalErr(__FILE__, __LINE__, "Could not parse dap version. Value given: " + v);
456
457 d_dap_version = v;
458
459 d_dap_major = major;
460 d_dap_minor = minor;
461
462 // Now set the related XML constants. These might be overwritten if
463 // the DDS instance is being built from a document parse, but if it's
464 // being constructed by a server the code to generate the XML document
465 // needs these values to match the DAP version information.
466 switch (d_dap_major) {
467 case 2:
468 d_namespace = c_dap20_namespace;
469 break;
470 case 3:
471 d_namespace = c_dap32_namespace;
472 break;
473 case 4:
474 d_namespace = c_dap40_namespace;
475 break;
476 default:
477 throw InternalErr(__FILE__, __LINE__, "Unknown DAP version.");
478 }
479}
480
488void
490{
491 int major = floor(d);
492 int minor = (d-major)*10;
493
494 DBG(cerr << "Major: " << major << ", Minor: " << minor << endl);
495
496 ostringstream oss;
497 oss << major << "." << minor;
498
499 set_dap_version(oss.str());
500}
501
511string
513{
514 return d_container_name;
515}
516
519void
520DDS::container_name(const string &cn)
521{
522 // we want to search the DDS for the top level structure with the given
523 // d_name. Set the container to null so that we don't search some previous
524 // container.
525 d_container = 0 ;
526 if( !cn.empty() )
527 {
528 d_container = dynamic_cast<Structure *>( var( cn ) ) ;
529 if( !d_container )
530 {
531 // create a structure for this container. Calling add_var
532 // while_container is null will add the new structure to DDS and
533 // not some sub structure. Adding the new structure makes a copy
534 // of it. So after adding it, go get it and set d_container.
535 Structure *s = new Structure( cn ) ;
536 add_var( s ) ;
537 delete s ;
538 s = 0 ;
539 d_container = dynamic_cast<Structure *>( var( cn ) ) ;
540 }
541 }
542 d_container_name = cn;
543
544}
545
547Structure *
549{
550 return d_container ;
551}
552
554
565int
566DDS::get_request_size(bool constrained)
567{
568 int w = 0;
569 for (Vars_iter i = vars.begin(); i != vars.end(); i++) {
570 if (constrained) {
571 if ((*i)->send_p())
572 w += (*i)->width(constrained);
573 }
574 else {
575 w += (*i)->width(constrained);
576 }
577 }
578
579 return w;
580}
581
588 if (!bt)
589 throw InternalErr(__FILE__, __LINE__, "Trying to add a BaseType object with a NULL pointer.");
590#if 0
591 if (bt->is_dap4_only_type())
592 throw InternalErr(__FILE__, __LINE__, "Attempt to add a DAP4 type to a DAP2 DDS.");
593#endif
594 DBG2(cerr << "In DDS::add_var(), bt's address is: " << bt << endl);
595
596 BaseType *btp = bt->ptr_duplicate();
597 DBG2(cerr << "In DDS::add_var(), btp's address is: " << btp << endl);
598 if (d_container) {
599 // Mem leak fix [mjohnson nov 2009]
600 // Structure::add_var() creates ANOTHER copy.
601 d_container->add_var(bt);
602 // So we need to delete btp or else it leaks
603 delete btp;
604 btp = 0;
605 }
606 else {
607 vars.push_back(btp);
608 }
609}
610
613void
615{
616 if (!bt)
617 throw InternalErr(__FILE__, __LINE__, "Trying to add a BaseType object with a NULL pointer.");
618#if 0
619 //FIXME There's no longer a DAP2 and DAP4 DDS
620 if (bt->is_dap4_only_type())
621 throw InternalErr(__FILE__, __LINE__, "Attempt to add a DAP4 type to a DAP2 DDS.");
622#endif
623
624 DBG2(cerr << "In DDS::add_var(), bt's address is: " << bt << endl);
625
626 if (d_container) {
627 d_container->add_var_nocopy(bt);
628 }
629 else {
630 vars.push_back(bt);
631 }
632}
633
634
641void
642DDS::del_var(const string &n)
643{
644 if( d_container )
645 {
646 d_container->del_var( n ) ;
647 return ;
648 }
649
650 for (Vars_iter i = vars.begin(); i != vars.end(); i++) {
651 if ((*i)->name() == n) {
652 BaseType *bt = *i ;
653 vars.erase(i) ;
654 delete bt ; bt = 0;
655 return;
656 }
657 }
658}
659
664void
665DDS::del_var(Vars_iter i)
666{
667 if (i != vars.end()) {
668 BaseType *bt = *i ;
669 vars.erase(i) ;
670 delete bt ; bt = 0;
671 }
672}
673
680void
681DDS::del_var(Vars_iter i1, Vars_iter i2)
682{
683 for (Vars_iter i_tmp = i1; i_tmp != i2; i_tmp++) {
684 BaseType *bt = *i_tmp ;
685 delete bt ; bt = 0;
686 }
687 vars.erase(i1, i2) ;
688}
689
697BaseType *
698DDS::var(const string &n, BaseType::btp_stack &s)
699{
700 return var(n, &s);
701}
721BaseType *
722DDS::var(const string &n, BaseType::btp_stack *s)
723{
724 string name = www2id(n);
725 if( d_container )
726 return d_container->var( name, false, s ) ;
727
728 BaseType *v = exact_match(name, s);
729 if (v)
730 return v;
731
732 return leaf_match(name, s);
733}
734
735BaseType *
736DDS::leaf_match(const string &n, BaseType::btp_stack *s)
737{
738 DBG(cerr << "DDS::leaf_match: Looking for " << n << endl);
739
740 for (Vars_iter i = vars.begin(); i != vars.end(); i++) {
741 BaseType *btp = *i;
742 DBG(cerr << "DDS::leaf_match: Looking for " << n << " in: " << btp->d_name() << endl);
743 // Look for the d_name in the dataset's top-level
744 if (btp->name() == n) {
745 DBG(cerr << "Found " << n << " in: " << btp->d_name() << endl);
746 return btp;
747 }
748
749 if (btp->is_constructor_type()) {
750 BaseType *found = btp->var(n, false, s);
751 if (found) {
752 DBG(cerr << "Found " << n << " in: " << btp->d_name() << endl);
753 return found;
754 }
755 }
756#if STRUCTURE_ARRAY_SYNTAX_OLD
757 if (btp->is_vector_type() && btp->var()->is_constructor_type()) {
758 s->push(btp);
759 BaseType *found = btp->var()->var(n, false, s);
760 if (found) {
761 DBG(cerr << "Found " << n << " in: " << btp->var()->d_name() << endl);
762 return found;
763 }
764 }
765#endif
766 }
767
768 return 0; // It is not here.
769}
770
771BaseType *
772DDS::exact_match(const string &name, BaseType::btp_stack *s)
773{
774 for (Vars_iter i = vars.begin(); i != vars.end(); i++) {
775 BaseType *btp = *i;
776 DBG2(cerr << "Looking for " << d_name << " in: " << btp << endl);
777 // Look for the d_name in the current ctor type or the top level
778 if (btp->name() == name) {
779 DBG2(cerr << "Found " << d_name << " in: " << btp << endl);
780 return btp;
781 }
782 }
783
784 string::size_type dot_pos = name.find(".");
785 if (dot_pos != string::npos) {
786 string aggregate = name.substr(0, dot_pos);
787 string field = name.substr(dot_pos + 1);
788
789 BaseType *agg_ptr = var(aggregate, s);
790 if (agg_ptr) {
791 DBG2(cerr << "Descending into " << agg_ptr->name() << endl);
792 return agg_ptr->var(field, true, s);
793 }
794 else
795 return 0; // qualified names must be *fully* qualified
796 }
797
798 return 0; // It is not here.
799}
800
801
804DDS::Vars_iter
806{
807 return vars.begin();
808}
809
810DDS::Vars_riter
812{
813 return vars.rbegin();
814}
815
816DDS::Vars_iter
818{
819 return vars.end() ;
820}
821
822DDS::Vars_riter
824{
825 return vars.rend() ;
826}
827
831DDS::Vars_iter
833{
834 return vars.begin() + i;
835}
836
840BaseType *
842{
843 return *(vars.begin() + i);
844}
845
850void
851DDS::insert_var(Vars_iter i, BaseType *ptr)
852{
853#if 0
854 if (ptr->is_dap4_only_type())
855 throw InternalErr(__FILE__, __LINE__, "Attempt to add a DAP4 type to a DAP2 DDS.");
856#endif
857 vars.insert(i, ptr->ptr_duplicate());
858}
859
867void
869{
870#if 0
871 if (ptr->is_dap4_only_type())
872 throw InternalErr(__FILE__, __LINE__, "Attempt to add a DAP4 type to a DAP2 DDS.");
873#endif
874 vars.insert(i, ptr);
875}
876
878int
880{
881 return vars.size();
882}
883
884void
885DDS::timeout_on()
886{
887#if USE_LOCAL_TIMEOUT_SCHEME
888#ifndef WIN32
889 alarm(d_timeout);
890#endif
891#endif
892}
893
894void
895DDS::timeout_off()
896{
897#if USE_LOCAL_TIMEOUT_SCHEME
898#ifndef WIN32
899 // Old behavior commented out. I think it is an error to change the value
900 // of d_timeout. The way this will likely be used is to set the timeout
901 // value once and then 'turn on' or turn off' that timeout as the situation
902 // dictates. The initeded use for the DDS timeout is so that timeouts for
903 // data responses will include the CPU resources needed to build the response
904 // but not the time spent transmitting the response. This may change when
905 // more parallelism is added to the server... These methods are called from
906 // BESDapResponseBuilder in bes/dap. jhrg 12/22/15
907
908 // d_timeout = alarm(0);
909
910 alarm(0);
911#endif
912#endif
913}
914
915void
916DDS::set_timeout(int)
917{
918#if USE_LOCAL_TIMEOUT_SCHEME
919 // Has no effect under win32
920 d_timeout = t;
921#endif
922}
923
924int
925DDS::get_timeout()
926{
927#if USE_LOCAL_TIMEOUT_SCHEME
928 // Has to effect under win32
929 return d_timeout;
930#endif
931 return 0;
932}
933
935void
937{
938 for (Vars_iter i = vars.begin(); i != vars.end(); i++) {
939 if ((*i)->type() == dods_sequence_c)
940 dynamic_cast<Sequence&>(**i).set_leaf_sequence();
941 else if ((*i)->type() == dods_structure_c)
942 dynamic_cast<Structure&>(**i).set_leaf_sequence();
943 }
944}
945
947void
948DDS::parse(string fname)
949{
950 FILE *in = fopen(fname.c_str(), "r");
951
952 if (!in) {
953 throw Error(cannot_read_file, "Could not open: " + fname);
954 }
955
956 try {
957 parse(in);
958 fclose(in);
959 }
960 catch (Error &e) {
961 fclose(in);
962 throw ;
963 }
964}
965
966
968void
970{
971#ifdef WIN32
972 int new_fd = _dup(fd);
973#else
974 int new_fd = dup(fd);
975#endif
976
977 if (new_fd < 0)
978 throw InternalErr(__FILE__, __LINE__, "Could not access file.");
979 FILE *in = fdopen(new_fd, "r");
980
981 if (!in) {
982 throw InternalErr(__FILE__, __LINE__, "Could not access file.");
983 }
984
985 try {
986 parse(in);
987 fclose(in);
988 }
989 catch (Error &e) {
990 fclose(in);
991 throw ;
992 }
993}
994
1001void
1002DDS::parse(FILE *in)
1003{
1004 if (!in) {
1005 throw InternalErr(__FILE__, __LINE__, "Null input stream.");
1006 }
1007
1008 void *buffer = dds_buffer(in);
1009 dds_switch_to_buffer(buffer);
1010
1011 parser_arg arg(this);
1012
1013 bool status = ddsparse(&arg) == 0;
1014
1015 dds_delete_buffer(buffer);
1016
1017 DBG2(cout << "Status from parser: " << status << endl);
1018
1019 // STATUS is the result of the parser function; if a recoverable error
1020 // was found it will be true but arg.status() will be false.
1021 if (!status || !arg.status()) {// Check parse result
1022 if (arg.error())
1023 throw *arg.error();
1024 }
1025}
1026
1028void
1029DDS::print(FILE *out)
1030{
1031 ostringstream oss;
1032 print(oss);
1033 fwrite(oss.str().data(), sizeof(char), oss.str().length(), out);
1034}
1035
1037void
1038DDS::print(ostream &out)
1039{
1040 out << "Dataset {\n" ;
1041
1042 for (Vars_citer i = vars.begin(); i != vars.end(); i++) {
1043 (*i)->print_decl(out) ;
1044 }
1045
1046 out << "} " << id2www(d_name) << ";\n" ;
1047
1048 return ;
1049}
1050
1060void
1061DDS::print_das(ostream &out)
1062{
1063 out << "Attributes {\n" ;
1064
1065 d_attr.print(out, " ");
1066 for (Vars_citer i = vars.begin(); i != vars.end(); i++) {
1067 (*i)->get_attr_table().print(out, " ");
1068 }
1069
1070 out << "}\n" ;
1071}
1072
1083void
1085{
1086 ostringstream oss;
1087 print_constrained(oss);
1088 fwrite(oss.str().data(), sizeof(char), oss.str().length(), out);
1089}
1090
1101void
1103{
1104 out << "Dataset {\n" ;
1105
1106 for (Vars_citer i = vars.begin(); i != vars.end(); i++) {
1107 // for each variable, indent with four spaces, print a trailing
1108 // semicolon, do not print debugging information, print only
1109 // variables in the current projection.
1110 (*i)->print_decl(out, " ", true, false, true) ;
1111 }
1112
1113 out << "} " << id2www(d_name) << ";\n" ;
1114
1115 return;
1116}
1117
1129void
1130DDS::print_xml(FILE *out, bool constrained, const string &blob)
1131{
1132 ostringstream oss;
1133 print_xml_writer(oss, constrained, blob);
1134 fwrite(oss.str().data(), 1, oss.str().length(), out);
1135}
1136
1148void
1149DDS::print_xml(ostream &out, bool constrained, const string &blob)
1150{
1151 print_xml_writer(out, constrained, blob);
1152}
1153
1154class VariablePrintXMLWriter : public unary_function<BaseType *, void>
1155{
1156 XMLWriter &d_xml;
1157 bool d_constrained;
1158public:
1159 VariablePrintXMLWriter(XMLWriter &xml, bool constrained)
1160 : d_xml(xml), d_constrained(constrained)
1161 {}
1162 void operator()(BaseType *bt)
1163 {
1164 bt->print_xml_writer(d_xml, d_constrained);
1165 }
1166};
1167
1184void
1185DDS::print_xml_writer(ostream &out, bool constrained, const string &blob)
1186{
1187 XMLWriter xml(" ");
1188
1189 // Stamp and repeat for these sections; trying to economize is makes it
1190 // even more confusing
1191 if (get_dap_major() >= 4) {
1192 if (xmlTextWriterStartElement(xml.get_writer(), (const xmlChar*) "Group") < 0)
1193 throw InternalErr(__FILE__, __LINE__, "Could not write Group element");
1194 if (xmlTextWriterWriteAttribute(xml.get_writer(), (const xmlChar*) "name", (const xmlChar*)d_name.c_str()) < 0)
1195 throw InternalErr(__FILE__, __LINE__, "Could not write attribute for name");
1196
1197 if (xmlTextWriterWriteAttribute(xml.get_writer(), (const xmlChar*) "dapVersion", (const xmlChar*)get_dap_version().c_str()) < 0)
1198 throw InternalErr(__FILE__, __LINE__, "Could not write attribute for dapVersion");
1199
1200 if (!get_request_xml_base().empty()) {
1201 if (xmlTextWriterWriteAttribute(xml.get_writer(), (const xmlChar*) "xmlns:xml", (const xmlChar*)c_xml_namespace.c_str()) < 0)
1202 throw InternalErr(__FILE__, __LINE__, "Could not write attribute for xmlns:xml");
1203
1204 if (xmlTextWriterWriteAttribute(xml.get_writer(), (const xmlChar*) "xml:base", (const xmlChar*)get_request_xml_base().c_str()) < 0)
1205 throw InternalErr(__FILE__, __LINE__, "Could not write attribute for xml:base");
1206 }
1207 if (!get_namespace().empty()) {
1208 if (xmlTextWriterWriteAttribute(xml.get_writer(), (const xmlChar*) "xmlns", (const xmlChar*)get_namespace().c_str()) < 0)
1209 throw InternalErr(__FILE__, __LINE__, "Could not write attribute for xmlns");
1210 }
1211 }
1212 else if (get_dap_major() == 3 && get_dap_minor() >= 2) {
1213 if (xmlTextWriterStartElement(xml.get_writer(), (const xmlChar*) "Dataset") < 0)
1214 throw InternalErr(__FILE__, __LINE__, "Could not write Dataset element");
1215 if (xmlTextWriterWriteAttribute(xml.get_writer(), (const xmlChar*) "name", (const xmlChar*)d_name.c_str()) < 0)
1216 throw InternalErr(__FILE__, __LINE__, "Could not write attribute for name");
1217 if (xmlTextWriterWriteAttribute(xml.get_writer(), (const xmlChar*) "xmlns:xsi", (const xmlChar*)"http://www.w3.org/2001/XMLSchema-instance") < 0)
1218 throw InternalErr(__FILE__, __LINE__, "Could not write attribute for xmlns:xsi");
1219
1220 if (xmlTextWriterWriteAttribute(xml.get_writer(), (const xmlChar*) "xsi:schemaLocation", (const xmlChar*)c_dap_32_n_sl.c_str()) < 0)
1221 throw InternalErr(__FILE__, __LINE__, "Could not write attribute for xmlns:schemaLocation");
1222
1223 if (xmlTextWriterWriteAttribute(xml.get_writer(), (const xmlChar*) "xmlns:grddl", (const xmlChar*)"http://www.w3.org/2003/g/data-view#") < 0)
1224 throw InternalErr(__FILE__, __LINE__, "Could not write attribute for xmlns:grddl");
1225
1226 if (xmlTextWriterWriteAttribute(xml.get_writer(), (const xmlChar*) "grddl:transformation", (const xmlChar*)grddl_transformation_dap32.c_str()) < 0)
1227 throw InternalErr(__FILE__, __LINE__, "Could not write attribute for xmlns:transformation");
1228
1229 if (xmlTextWriterWriteAttribute(xml.get_writer(), (const xmlChar*) "xmlns", (const xmlChar*)c_dap32_namespace.c_str()) < 0)
1230 throw InternalErr(__FILE__, __LINE__, "Could not write attribute for xmlns");
1231 if (xmlTextWriterWriteAttribute(xml.get_writer(), (const xmlChar*) "xmlns:dap", (const xmlChar*)c_dap32_namespace.c_str()) < 0)
1232 throw InternalErr(__FILE__, __LINE__, "Could not write attribute for xmlns:dap");
1233
1234 if (xmlTextWriterWriteAttribute(xml.get_writer(), (const xmlChar*) "dapVersion", (const xmlChar*)"3.2") < 0)
1235 throw InternalErr(__FILE__, __LINE__, "Could not write attribute for dapVersion");
1236
1237 if (!get_request_xml_base().empty()) {
1238 if (xmlTextWriterWriteAttribute(xml.get_writer(), (const xmlChar*) "xmlns:xml", (const xmlChar*)c_xml_namespace.c_str()) < 0)
1239 throw InternalErr(__FILE__, __LINE__, "Could not write attribute for xmlns:xml");
1240
1241 if (xmlTextWriterWriteAttribute(xml.get_writer(), (const xmlChar*) "xml:base", (const xmlChar*)get_request_xml_base().c_str()) < 0)
1242 throw InternalErr(__FILE__, __LINE__, "Could not write attribute for xml:base");
1243 }
1244 }
1245 else { // dap2
1246 if (xmlTextWriterStartElement(xml.get_writer(), (const xmlChar*) "Dataset") < 0)
1247 throw InternalErr(__FILE__, __LINE__, "Could not write Dataset element");
1248 if (xmlTextWriterWriteAttribute(xml.get_writer(), (const xmlChar*) "name", (const xmlChar*)d_name.c_str()) < 0)
1249 throw InternalErr(__FILE__, __LINE__, "Could not write attribute for d_name");
1250 if (xmlTextWriterWriteAttribute(xml.get_writer(), (const xmlChar*) "xmlns:xsi", (const xmlChar*)"http://www.w3.org/2001/XMLSchema-instance") < 0)
1251 throw InternalErr(__FILE__, __LINE__, "Could not write attribute for xmlns:xsi");
1252
1253 if (xmlTextWriterWriteAttribute(xml.get_writer(), (const xmlChar*) "xmlns", (const xmlChar*)c_dap20_namespace.c_str()) < 0)
1254 throw InternalErr(__FILE__, __LINE__, "Could not write attribute for xmlns");
1255
1256 if (xmlTextWriterWriteAttribute(xml.get_writer(), (const xmlChar*) "xsi:schemaLocation", (const xmlChar*)c_dap_20_n_sl.c_str()) < 0)
1257 throw InternalErr(__FILE__, __LINE__, "Could not write attribute for xmlns:schemaLocation");
1258 }
1259
1260 // Print the global attributes
1261 d_attr.print_xml_writer(xml);
1262
1263 // Print each variable
1264 for_each(var_begin(), var_end(), VariablePrintXMLWriter(xml, constrained));
1265
1266 // For DAP 3.2 and greater, use the new syntax and value. The 'blob' is
1267 // the CID of the MIME part that holds the data. For DAP2 (which includes
1268 // 3.0 and 3.1), the blob is an href. For DAP4, only write the CID if it's
1269 // given.
1270 if (get_dap_major() >= 4) {
1271 if (!blob.empty()) {
1272 if (xmlTextWriterStartElement(xml.get_writer(), (const xmlChar*) "blob") < 0)
1273 throw InternalErr(__FILE__, __LINE__, "Could not write blob element");
1274 string cid = "cid:" + blob;
1275 if (xmlTextWriterWriteAttribute(xml.get_writer(), (const xmlChar*) "href", (const xmlChar*) cid.c_str()) < 0)
1276 throw InternalErr(__FILE__, __LINE__, "Could not write attribute for d_name");
1277 if (xmlTextWriterEndElement(xml.get_writer()) < 0)
1278 throw InternalErr(__FILE__, __LINE__, "Could not end blob element");
1279 }
1280 }
1281 else if (get_dap_major() == 3 && get_dap_minor() >= 2) {
1282 if (xmlTextWriterStartElement(xml.get_writer(), (const xmlChar*) "blob") < 0)
1283 throw InternalErr(__FILE__, __LINE__, "Could not write blob element");
1284 string cid = "cid:" + blob;
1285 if (xmlTextWriterWriteAttribute(xml.get_writer(), (const xmlChar*) "href", (const xmlChar*) cid.c_str()) < 0)
1286 throw InternalErr(__FILE__, __LINE__, "Could not write attribute for d_name");
1287 if (xmlTextWriterEndElement(xml.get_writer()) < 0)
1288 throw InternalErr(__FILE__, __LINE__, "Could not end blob element");
1289 }
1290 else { // dap2
1291 if (xmlTextWriterStartElement(xml.get_writer(), (const xmlChar*) "dataBLOB") < 0)
1292 throw InternalErr(__FILE__, __LINE__, "Could not write dataBLOB element");
1293 if (xmlTextWriterWriteAttribute(xml.get_writer(), (const xmlChar*) "href", (const xmlChar*) "") < 0)
1294 throw InternalErr(__FILE__, __LINE__, "Could not write attribute for d_name");
1295 if (xmlTextWriterEndElement(xml.get_writer()) < 0)
1296 throw InternalErr(__FILE__, __LINE__, "Could not end dataBLOB element");
1297 }
1298
1299 if (xmlTextWriterEndElement(xml.get_writer()) < 0)
1300 throw InternalErr(__FILE__, __LINE__, "Could not end Dataset element");
1301
1302 out << xml.get_doc();// << ends;// << endl;
1303}
1304
1317void
1318DDS::print_dmr(ostream &out, bool constrained)
1319{
1320 if (get_dap_major() < 4)
1321 throw InternalErr(__FILE__, __LINE__, "Tried to print a DMR with DAP major version less than 4");
1322
1323 XMLWriter xml(" ");
1324
1325 // DAP4 wraps a dataset in a top-level Group element.
1326 if (xmlTextWriterStartElement(xml.get_writer(), (const xmlChar*) "Group") < 0)
1327 throw InternalErr(__FILE__, __LINE__, "Could not write Group element");
1328
1329 if (xmlTextWriterWriteAttribute(xml.get_writer(), (const xmlChar*) "xmlns:xml",
1330 (const xmlChar*) c_xml_namespace.c_str()) < 0)
1331 throw InternalErr(__FILE__, __LINE__, "Could not write attribute for xmlns:xml");
1332
1333 if (xmlTextWriterWriteAttribute(xml.get_writer(), (const xmlChar*) "xmlns:xsi", (const xmlChar*) c_xml_xsi.c_str())
1334 < 0)
1335 throw InternalErr(__FILE__, __LINE__, "Could not write attribute for xmlns:xsi");
1336
1337 if (xmlTextWriterWriteAttribute(xml.get_writer(), (const xmlChar*) "xsi:schemaLocation",
1338 (const xmlChar*) c_dap_40_n_sl.c_str()) < 0)
1339 throw InternalErr(__FILE__, __LINE__, "Could not write attribute for xmlns:schemaLocation");
1340
1341 if (xmlTextWriterWriteAttribute(xml.get_writer(), (const xmlChar*) "xmlns",
1342 (const xmlChar*) get_namespace().c_str()) < 0)
1343 throw InternalErr(__FILE__, __LINE__, "Could not write attribute for xmlns");
1344
1345 if (xmlTextWriterWriteAttribute(xml.get_writer(), (const xmlChar*) "dapVersion",
1346 (const xmlChar*) get_dap_version().c_str()) < 0)
1347 throw InternalErr(__FILE__, __LINE__, "Could not write attribute for dapVersion");
1348
1349 if (xmlTextWriterWriteAttribute(xml.get_writer(), (const xmlChar*) "dmrVersion", (const xmlChar*) get_dmr_version().c_str()) < 0)
1350 throw InternalErr(__FILE__, __LINE__, "Could not write attribute for dapVersion");
1351
1352 if (!get_request_xml_base().empty()) {
1353 if (xmlTextWriterWriteAttribute(xml.get_writer(), (const xmlChar*) "xml:base",
1354 (const xmlChar*) get_request_xml_base().c_str()) < 0)
1355 throw InternalErr(__FILE__, __LINE__, "Could not write attribute for xml:base");
1356 }
1357
1358 if (xmlTextWriterWriteAttribute(xml.get_writer(), (const xmlChar*) "name", (const xmlChar*) d_name.c_str()) < 0)
1359 throw InternalErr(__FILE__, __LINE__, "Could not write attribute for name");
1360
1361 // Print the global attributes
1362 d_attr.print_xml_writer(xml);
1363
1364 // Print each variable
1365 for_each(var_begin(), var_end(), VariablePrintXMLWriter(xml, constrained));
1366
1367#if 0
1368 // For DAP 3.2 and greater, use the new syntax and value. The 'blob' is
1369 // the CID of the MIME part that holds the data. For DAP2 (which includes
1370 // 3.0 and 3.1), the blob is an href. For DAP4, only write the CID if it's
1371 // given.
1372 if (get_dap_major() >= 4) {
1373 if (!blob.empty()) {
1374 if (xmlTextWriterStartElement(xml.get_writer(), (const xmlChar*) "blob") < 0)
1375 throw InternalErr(__FILE__, __LINE__, "Could not write blob element");
1376 string cid = "cid:" + blob;
1377 if (xmlTextWriterWriteAttribute(xml.get_writer(), (const xmlChar*) "href", (const xmlChar*) cid.c_str()) < 0)
1378 throw InternalErr(__FILE__, __LINE__, "Could not write attribute for d_name");
1379 if (xmlTextWriterEndElement(xml.get_writer()) < 0)
1380 throw InternalErr(__FILE__, __LINE__, "Could not end blob element");
1381 }
1382 }
1383#endif
1384
1385 if (xmlTextWriterEndElement(xml.get_writer()) < 0)
1386 throw InternalErr(__FILE__, __LINE__, "Could not end the top-level Group element");
1387
1388 out << xml.get_doc();
1389}
1390
1391// Used by DDS::send() when returning data from a function call.
1406bool
1408{
1409 // The dataset must have a d_name
1410 if (d_name == "") {
1411 cerr << "A dataset must have a d_name" << endl;
1412 return false;
1413 }
1414
1415 string msg;
1416 if (!unique_names(vars, d_name, "Dataset", msg))
1417 return false;
1418
1419 if (all)
1420 for (Vars_iter i = vars.begin(); i != vars.end(); i++)
1421 if (!(*i)->check_semantics(msg, true))
1422 return false;
1423
1424 return true;
1425}
1426
1452bool
1453DDS::mark(const string &n, bool state)
1454{
1455 // TODO use auto_ptr
1456 BaseType::btp_stack *s = new BaseType::btp_stack;
1457
1458 DBG2(cerr << "DDS::mark: Looking for " << n << endl);
1459
1460 BaseType *variable = var(n, s);
1461 if (!variable) {
1462 DBG2(cerr << "Could not find variable " << n << endl);
1463 delete s; s = 0;
1464 return false;
1465 }
1466 variable->set_send_p(state);
1467
1468 DBG2(cerr << "DDS::mark: Set variable " << variable->d_name()
1469 << " (a " << variable->type_name() << ")" << endl);
1470
1471 // Now check the btp_stack and run BaseType::set_send_p for every
1472 // BaseType pointer on the stack. Using BaseType::set_send_p() will
1473 // set the property for a Constructor but not its contained variables
1474 // which preserves the semantics of projecting just one field.
1475 while (!s->empty()) {
1476 s->top()->BaseType::set_send_p(state);
1477
1478 DBG2(cerr << "DDS::mark: Set variable " << s->top()->d_name()
1479 << " (a " << s->top()->type_name() << ")" << endl);
1480 // FIXME get_parent() hosed?
1481#if 1
1482 string parent_name = (s->top()->get_parent()) ? s->top()->get_parent()->name(): "none";
1483 string parent_type = (s->top()->get_parent()) ? s->top()->get_parent()->type_name(): "none";
1484 DBG2(cerr << "DDS::mark: Parent variable " << parent_name << " (a " << parent_type << ")" << endl);
1485#endif
1486 s->pop();
1487 }
1488
1489 delete s ; s = 0;
1490
1491 return true;
1492}
1493
1499void
1500DDS::mark_all(bool state)
1501{
1502 for (Vars_iter i = vars.begin(); i != vars.end(); i++)
1503 (*i)->set_send_p(state);
1504}
1505
1513void
1514DDS::dump(ostream &strm) const
1515{
1516 strm << DapIndent::LMarg << "DDS::dump - ("
1517 << (void *)this << ")" << endl ;
1518 DapIndent::Indent() ;
1519 strm << DapIndent::LMarg << "d_name: " << d_name << endl ;
1520 strm << DapIndent::LMarg << "filename: " << d_filename << endl ;
1521 strm << DapIndent::LMarg << "protocol major: " << d_dap_major << endl;
1522 strm << DapIndent::LMarg << "protocol minor: " << d_dap_minor << endl;
1523 strm << DapIndent::LMarg << "factory: " << (void *)d_factory << endl ;
1524
1525 strm << DapIndent::LMarg << "global attributes:" << endl ;
1526 DapIndent::Indent() ;
1527 d_attr.dump(strm) ;
1528 DapIndent::UnIndent() ;
1529
1530 if (vars.size()) {
1531 strm << DapIndent::LMarg << "vars:" << endl ;
1532 DapIndent::Indent() ;
1533 Vars_citer i = vars.begin() ;
1534 Vars_citer ie = vars.end() ;
1535 for (; i != ie; i++) {
1536 (*i)->dump(strm) ;
1537 }
1538 DapIndent::UnIndent() ;
1539 }
1540 else {
1541 strm << DapIndent::LMarg << "vars: none" << endl ;
1542 }
1543
1544 DapIndent::UnIndent() ;
1545}
1546
1547} // namespace libdap
Contains the attributes for a dataset.
Definition: AttrTable.h:143
virtual Attr_iter attr_end()
Definition: AttrTable.cc:718
virtual Attr_iter attr_begin()
Definition: AttrTable.cc:710
virtual string get_name() const
Get the name of this attribute table.
Definition: AttrTable.cc:237
The basic data type for the DODS DAP types.
Definition: BaseType.h:118
virtual string type_name() const
Returns the type of the class instance as a string.
Definition: BaseType.cc:324
virtual string name() const
Returns the name of the class instance.
Definition: BaseType.cc:265
virtual BaseType * var(const string &name="", bool exact_match=true, btp_stack *s=0)
Returns a pointer to a member of a constructor class.
Definition: BaseType.cc:679
virtual bool is_vector_type() const
Returns true if the instance is a vector (i.e., array) type variable.
Definition: BaseType.cc:347
virtual bool is_constructor_type() const
Returns true if the instance is a constructor (i.e., Structure, Sequence or Grid) type variable.
Definition: BaseType.cc:357
virtual void set_send_p(bool state)
Definition: BaseType.cc:513
virtual BaseType * ptr_duplicate()=0
Hold attribute data for a DAP2 dataset.
Definition: DAS.h:122
virtual AttrTable * get_top_level_attributes()
Returns the top most set of attributes.
Definition: DAS.h:166
virtual string container_name() const
Returns the name of the current attribute container when multiple files used to build this DAS.
Definition: DAS.h:149
void set_dataset_name(const string &n)
Definition: DDS.cc:364
void set_dap_major(int p)
Definition: DDS.cc:405
void mark_all(bool state)
Definition: DDS.cc:1500
void print_dmr(ostream &out, bool constrained)
Definition: DDS.cc:1318
Vars_riter var_rend()
Return a reverse iterator.
Definition: DDS.cc:823
void add_var_nocopy(BaseType *bt)
Adds the variable to the DDS.
Definition: DDS.cc:614
bool check_semantics(bool all=false)
Check the semantics of each of the variables represented in the DDS.
Definition: DDS.cc:1407
string filename() const
Definition: DDS.cc:388
virtual AttrTable & get_attr_table()
Definition: DDS.cc:373
virtual void transfer_attributes(DAS *das)
Definition: DDS.cc:287
void set_dap_minor(int p)
Definition: DDS.cc:424
Vars_riter var_rbegin()
Return a reverse iterator.
Definition: DDS.cc:811
string get_namespace() const
Get the namespace associated with the DDS - likely set only by DDX responses.
Definition: DDS.h:285
int num_var()
Returns the number of variables in the DDS.
Definition: DDS.cc:879
Vars_iter get_vars_iter(int i)
Get an iterator.
Definition: DDS.cc:832
void print(FILE *out)
Print the entire DDS to the specified file.
Definition: DDS.cc:1029
BaseType * get_var_index(int i)
Get a variable.
Definition: DDS.cc:841
int get_request_size(bool constrained)
Get the estimated response size.
Definition: DDS.cc:566
string get_dataset_name() const
Definition: DDS.cc:357
void del_var(const string &n)
Removes a variable from the DDS.
Definition: DDS.cc:642
void parse(string fname)
Parse a DDS from a file with the given d_name.
Definition: DDS.cc:948
BaseType * var(const string &n, BaseType::btp_stack &s)
Definition: DDS.cc:698
void print_xml(FILE *out, bool constrained, const string &blob="")
Definition: DDS.cc:1130
void insert_var(Vars_iter i, BaseType *ptr)
Insert a variable before the referenced element.
Definition: DDS.cc:851
bool mark(const string &name, bool state)
Mark the send_p flag of the named variable to state.
Definition: DDS.cc:1453
int get_dap_minor() const
Get the DAP minor version as sent by the client.
Definition: DDS.h:263
DDS(BaseTypeFactory *factory, const string &name="")
Definition: DDS.cc:205
void tag_nested_sequences()
Traverse DDS, set Sequence leaf nodes.
Definition: DDS.cc:936
void print_constrained(FILE *out)
Print a constrained DDS to the specified file.
Definition: DDS.cc:1084
Vars_iter var_begin()
Return an iterator to the first variable.
Definition: DDS.cc:805
string container_name()
Definition: DDS.cc:512
void insert_var_nocopy(Vars_iter i, BaseType *ptr)
Definition: DDS.cc:868
string get_request_xml_base() const
Get the URL that will return this DDS/DDX/DataThing.
Definition: DDS.h:279
int get_dap_major() const
Get the DAP major version as sent by the client.
Definition: DDS.h:261
Vars_iter var_end()
Return an iterator.
Definition: DDS.cc:817
void set_dap_version(const string &version_string="2.0")
Definition: DDS.cc:441
Structure * container()
Definition: DDS.cc:548
void add_var(BaseType *bt)
Adds a copy of the variable to the DDS. Using the ptr_duplicate() method, perform a deep copy on the ...
Definition: DDS.cc:587
void print_xml_writer(ostream &out, bool constrained, const string &blob="")
Definition: DDS.cc:1185
void print_das(ostream &out)
Definition: DDS.cc:1061
virtual void dump(ostream &strm) const
dumps information about this object
Definition: DDS.cc:1514
libdap base object for common functionality of libdap objects
Definition: DapObj.h:56
A class for error processing.
Definition: Error.h:91
string get_error_message() const
Definition: Error.cc:276
A class for software fault reporting.
Definition: InternalErr.h:65
Holds a sequence.
Definition: Sequence.h:163
virtual void set_leaf_sequence(int lvl=1)
Mark the Sequence which holds the leaf elements.
Definition: Sequence.cc:1231
Holds a structure (aggregate) type.
Definition: Structure.h:84
virtual void set_leaf_sequence(int level=1)
Traverse Structure, set Sequence leaf nodes.
Definition: Structure.cc:252
string www2id(const string &in, const string &escape, const string &except)
Definition: escaping.cc:220
string id2www(string in, const string &allowable)
Definition: escaping.cc:153
Pass parameters by reference to a parser.
Definition: parser.h:69