libdap Updated for version 3.21.0
libdap4 is an implementation of OPeNDAP's DAP protocol.
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 <climits>
39//#include <cstdint>
40
41//#include <sys/types.h>
42
43#ifdef WIN32
44#include <io.h>
45#include <process.h>
46#include <fstream>
47#else
48#include <unistd.h> // for alarm and dup
49#include <sys/wait.h>
50#endif
51
52#include <iostream>
53#include <sstream>
54#include <algorithm>
55#include <functional>
56#include <memory>
57
58// #define DODS_DEBUG
59// #define DODS_DEBUG2
60
61#include "Byte.h"
62#include "Int16.h"
63#include "UInt16.h"
64#include "Int32.h"
65#include "UInt32.h"
66#include "Float32.h"
67#include "Float64.h"
68#include "Str.h"
69#include "Url.h"
70#include "Array.h"
71#include "Structure.h"
72#include "Sequence.h"
73#include "Grid.h"
74
75#include "DAS.h"
76#include "Clause.h"
77#include "Error.h"
78#include "InternalErr.h"
79#include "escaping.h"
80#include "parser.h"
81#include "debug.h"
82#include "util.h"
83#include "DapIndent.h"
84
90const string c_xml_xsi = "http://www.w3.org/2001/XMLSchema-instance";
91const string c_xml_namespace = "http://www.w3.org/XML/1998/namespace";
92
93const string grddl_transformation_dap32 = "http://xml.opendap.org/transforms/ddxToRdfTriples.xsl";
94
95const string c_default_dap20_schema_location = "http://xml.opendap.org/dap/dap2.xsd";
96const string c_default_dap32_schema_location = "http://xml.opendap.org/dap/dap3.2.xsd";
97const string c_default_dap40_schema_location = "http://xml.opendap.org/dap/dap4.0.xsd";
98
99const string c_dap20_namespace = "http://xml.opendap.org/ns/DAP2";
100const string c_dap32_namespace = "http://xml.opendap.org/ns/DAP/3.2#";
101const string c_dap40_namespace = "http://xml.opendap.org/ns/DAP/4.0#";
102
103const string c_dap_20_n_sl = c_dap20_namespace + " " + c_default_dap20_schema_location;
104const string c_dap_32_n_sl = c_dap32_namespace + " " + c_default_dap32_schema_location;
105const string c_dap_40_n_sl = c_dap40_namespace + " " + c_default_dap40_schema_location;
106
110const string TOP_LEVEL_ATTRS_CONTAINER_NAME = "DAP4_GLOBAL";
111
112using namespace std;
113
114int ddsparse(libdap::parser_arg *arg);
115
116// Glue for the DDS parser defined in dds.lex
117void dds_switch_to_buffer(void *new_buffer);
118void dds_delete_buffer(void * buffer);
119void *dds_buffer(FILE *fp);
120
121namespace libdap {
122
123void
124DDS::duplicate(const DDS &dds)
125{
126 DBG(cerr << "Entering DDS::duplicate... " <<endl);
127
128 d_factory = dds.d_factory;
129
130 d_name = dds.d_name;
131 d_filename = dds.d_filename;
132 d_container_name = dds.d_container_name;
133 d_container = dds.d_container;
134
135 d_dap_major = dds.d_dap_major;
136 d_dap_minor = dds.d_dap_minor;
137
138 d_dap_version = dds.d_dap_version; // String version of the protocol
139 d_request_xml_base = dds.d_request_xml_base;
140 d_namespace = dds.d_namespace;
141
142 d_attr = dds.d_attr;
143
144 DDS &dds_tmp = const_cast<DDS &>(dds);
145
146 // copy the things pointed to by the list, not just the pointers
147 for (Vars_iter i = dds_tmp.var_begin(); i != dds_tmp.var_end(); i++) {
148 add_var(*i); // add_var() dups the BaseType.
149 }
150
151 d_timeout = dds.d_timeout;
152
153 d_max_response_size_kb = dds.d_max_response_size_kb;
154}
155
168DDS::DDS(BaseTypeFactory *factory, const string &name)
169 : d_factory(factory), d_name(name), d_container_name(""), d_container(0),
170 d_request_xml_base(""),
171 d_timeout(0), /*d_keywords(),*/ d_max_response_size_kb(0)
172{
173 DBG(cerr << "Building a DDS for the default version (2.0)" << endl);
174
175 // This method sets a number of values, including those returned by
176 // get_protocol_major(), ..., get_namespace().
177 set_dap_version("2.0");
178}
179
195DDS::DDS(BaseTypeFactory *factory, const string &name, const string &version)
196 : d_factory(factory), d_name(name), d_container_name(""), d_container(0),
197 d_request_xml_base(""),
198 d_timeout(0), /*d_keywords(),*/ d_max_response_size_kb(0)
199{
200 DBG(cerr << "Building a DDS for version: " << version << endl);
201
202 // This method sets a number of values, including those returned by
203 // get_protocol_major(), ..., get_namespace().
204 set_dap_version(version);
205}
206
208DDS::DDS(const DDS &rhs) : DapObj()
209{
210 DBG(cerr << "Entering DDS(const DDS &rhs) ..." << endl);
211 duplicate(rhs);
212 DBG(cerr << " bye." << endl);
213}
214
215DDS::~DDS()
216{
217 // delete all the variables in this DDS
218 for (Vars_iter i = vars.begin(); i != vars.end(); i++) {
219 BaseType *btp = *i ;
220 delete btp ; btp = 0;
221 }
222}
223
224DDS &
225DDS::operator=(const DDS &rhs)
226{
227 if (this == &rhs)
228 return *this;
229 duplicate(rhs);
230 return *this;
231}
232
247{
248 // If there is a container set in the DDS then check the container from
249 // the DAS. If they are not the same container, then throw an exception
250 // (should be working on the same container). If the container does not
251 // exist in the DAS, then throw an exception
252 if (d_container && das->container_name() != d_container_name)
253 throw InternalErr(__FILE__, __LINE__,
254 "Error transferring attributes: working on a container in dds, but not das");
255
256 // Give each variable a chance to claim its attributes.
258
259 for (DDS::Vars_iter i = var_begin(), e = var_end(); i != e; i++) {
260 (*i)->transfer_attributes(top);
261 }
262
263 // Now we transfer all the attributes still marked as global to the
264 // global container in the DDS.
265 for (AttrTable::Attr_iter i = top->attr_begin(), e = top->attr_end(); i != e; ++i) {
266 if ((*i)->type == Attr_container && (*i)->attributes->is_global_attribute()) {
267 // copy the source container so that the DAS passed in can be
268 // deleted after calling this method.
269 AttrTable *at = new AttrTable(*(*i)->attributes);
270 d_attr.append_container(at, at->get_name());
271 }
272 }
273}
274
282
284string
286{
287 return d_name;
288}
289
291void
292DDS::set_dataset_name(const string &n)
293{
294 d_name = n;
295}
296
298
300AttrTable &
302{
303 return d_attr;
304}
305
315string
317{
318 return d_filename;
319}
320
322void
323DDS::filename(const string &fn)
324{
325 d_filename = fn;
326}
328
332void
334{
335 d_dap_major = p;
336
337 // This works because regardless of the order set_dap_major and set_dap_minor
338 // are called, once they both are called, the value in the string is
339 // correct. I protect against negative numbers because that would be
340 // nonsensical.
341 if (d_dap_minor >= 0) {
342 ostringstream oss;
343 oss << d_dap_major << "." << d_dap_minor;
344 d_dap_version = oss.str();
345 }
346}
347
351void
353{
354 d_dap_minor = p;
355
356 if (d_dap_major >= 0) {
357 ostringstream oss;
358 oss << d_dap_major << "." << d_dap_minor;
359 d_dap_version = oss.str();
360 }
361}
362
368void
369DDS::set_dap_version(const string &v /* = "2.0" */)
370{
371 istringstream iss(v);
372
373 int major = -1, minor = -1;
374 char dot;
375 if (!iss.eof() && !iss.fail())
376 iss >> major;
377 if (!iss.eof() && !iss.fail())
378 iss >> dot;
379 if (!iss.eof() && !iss.fail())
380 iss >> minor;
381
382 if (major == -1 || minor == -1 or dot != '.')
383 throw InternalErr(__FILE__, __LINE__, "Could not parse dap version. Value given: " + v);
384
385 d_dap_version = v;
386
387 d_dap_major = major;
388 d_dap_minor = minor;
389
390 // Now set the related XML constants. These might be overwritten if
391 // the DDS instance is being built from a document parse, but if it's
392 // being constructed by a server the code to generate the XML document
393 // needs these values to match the DAP version information.
394 switch (d_dap_major) {
395 case 2:
396 d_namespace = c_dap20_namespace;
397 break;
398 case 3:
399 d_namespace = c_dap32_namespace;
400 break;
401 case 4:
402 d_namespace = c_dap40_namespace;
403 break;
404 default:
405 throw InternalErr(__FILE__, __LINE__, "Unknown DAP version.");
406 }
407}
408
416void
418{
419 int major = floor(d);
420 int minor = (d-major)*10;
421
422 DBG(cerr << "Major: " << major << ", Minor: " << minor << endl);
423
424 ostringstream oss;
425 oss << major << "." << minor;
426
427 set_dap_version(oss.str());
428}
429
439string
441{
442 return d_container_name;
443}
444
447void
448DDS::container_name(const string &cn)
449{
450 // we want to search the DDS for the top level structure with the given
451 // d_name. Set the container to null so that we don't search some previous
452 // container.
453 d_container = 0 ;
454 if( !cn.empty() )
455 {
456 d_container = dynamic_cast<Structure *>( var( cn ) ) ;
457 if( !d_container )
458 {
459 // create a structure for this container. Calling add_var
460 // while_container is null will add the new structure to DDS and
461 // not some sub structure. Adding the new structure makes a copy
462 // of it. So after adding it, go get it and set d_container.
463 Structure *s = new Structure( cn ) ;
464 add_var( s ) ;
465 delete s ;
466 s = 0 ;
467 d_container = dynamic_cast<Structure *>( var( cn ) ) ;
468 }
469 }
470 d_container_name = cn;
471
472}
473
475Structure *
477{
478 return d_container ;
479}
480
482
494//[[deprecated("Use DDS::get_request_size_kb()")]]
495int DDS::get_request_size(bool constrained)
496{
497 int w = 0;
498 for (Vars_iter i = vars.begin(); i != vars.end(); i++) {
499 if (constrained) {
500 if ((*i)->send_p())
501 w += (*i)->width(constrained);
502 }
503 else {
504 w += (*i)->width(constrained);
505 }
506 }
507 return w;
508}
509
522uint64_t DDS::get_request_size_kb(bool constrained)
523{
524 uint64_t req_size = 0;
525 for (auto &btp: vars) {
526 if (constrained) {
527 if (btp->send_p())
528 req_size += btp->width_ll(constrained);
529 }
530 else {
531 req_size += btp->width_ll(constrained);
532 }
533 }
534
535 return req_size / 1024;
536}
537
538
545 if (!bt)
546 throw InternalErr(__FILE__, __LINE__, "Trying to add a BaseType object with a NULL pointer.");
547
548 DBG2(cerr << "In DDS::add_var(), bt's address is: " << bt << endl);
549
550 BaseType *btp = bt->ptr_duplicate();
551 DBG2(cerr << "In DDS::add_var(), btp's address is: " << btp << endl);
552 if (d_container) {
553 // Mem leak fix [mjohnson nov 2009]
554 // Structure::add_var() creates ANOTHER copy.
555 d_container->add_var(bt);
556 // So we need to delete btp or else it leaks
557 delete btp;
558 btp = 0;
559 }
560 else {
561 vars.push_back(btp);
562 }
563}
564
567void
569{
570 if (!bt)
571 throw InternalErr(__FILE__, __LINE__, "Trying to add a BaseType object with a NULL pointer.");
572
573 DBG2(cerr << "In DDS::add_var(), bt's address is: " << bt << endl);
574
575 if (d_container) {
576 d_container->add_var_nocopy(bt);
577 }
578 else {
579 vars.push_back(bt);
580 }
581}
582
589void
590DDS::del_var(const string &n)
591{
592 if( d_container )
593 {
594 d_container->del_var( n ) ;
595 return ;
596 }
597
598 for (Vars_iter i = vars.begin(); i != vars.end(); i++) {
599 if ((*i)->name() == n) {
600 BaseType *bt = *i ;
601 vars.erase(i) ;
602 delete bt ; bt = 0;
603 return;
604 }
605 }
606}
607
612void
613DDS::del_var(Vars_iter i)
614{
615 if (i != vars.end()) {
616 BaseType *bt = *i ;
617 vars.erase(i) ;
618 delete bt ; bt = 0;
619 }
620}
621
628void
629DDS::del_var(Vars_iter i1, Vars_iter i2)
630{
631 for (Vars_iter i_tmp = i1; i_tmp != i2; i_tmp++) {
632 BaseType *bt = *i_tmp ;
633 delete bt ; bt = 0;
634 }
635 vars.erase(i1, i2) ;
636}
637
645BaseType *
646DDS::var(const string &n, BaseType::btp_stack &s)
647{
648 return var(n, &s);
649}
650
670BaseType *
671DDS::var(const string &n, BaseType::btp_stack *s)
672{
673 string name = www2id(n);
674 if( d_container )
675 return d_container->var( name, false, s ) ;
676
677 BaseType *v = exact_match(name, s);
678 if (v)
679 return v;
680
681 return leaf_match(name, s);
682}
683
684BaseType *
685DDS::leaf_match(const string &n, BaseType::btp_stack *s)
686{
687 DBG(cerr << "DDS::leaf_match: Looking for " << n << endl);
688
689 for (Vars_iter i = vars.begin(); i != vars.end(); i++) {
690 BaseType *btp = *i;
691 DBG(cerr << "DDS::leaf_match: Looking for " << n << " in: " << btp->name() << endl);
692 // Look for the d_name in the dataset's top-level
693 if (btp->name() == n) {
694 DBG(cerr << "Found " << n << " in: " << btp->name() << endl);
695 return btp;
696 }
697
698 if (btp->is_constructor_type()) {
699 BaseType *found = btp->var(n, false, s);
700 if (found) {
701 DBG(cerr << "Found " << n << " in: " << btp->name() << endl);
702 return found;
703 }
704 }
705#if STRUCTURE_ARRAY_SYNTAX_OLD
706 if (btp->is_vector_type() && btp->var()->is_constructor_type()) {
707 s->push(btp);
708 BaseType *found = btp->var()->var(n, false, s);
709 if (found) {
710 DBG(cerr << "Found " << n << " in: " << btp->var()->d_name() << endl);
711 return found;
712 }
713 }
714#endif
715 }
716
717 return 0; // It is not here.
718}
719
720BaseType *
721DDS::exact_match(const string &name, BaseType::btp_stack *s)
722{
723 for (Vars_iter i = vars.begin(); i != vars.end(); i++) {
724 BaseType *btp = *i;
725 DBG2(cerr << "Looking for " << d_name << " in: " << btp << endl);
726 // Look for the d_name in the current ctor type or the top level
727 if (btp->name() == name) {
728 DBG2(cerr << "Found " << d_name << " in: " << btp << endl);
729 return btp;
730 }
731 }
732
733 string::size_type dot_pos = name.find(".");
734 if (dot_pos != string::npos) {
735 string aggregate = name.substr(0, dot_pos);
736 string field = name.substr(dot_pos + 1);
737
738 BaseType *agg_ptr = var(aggregate, s);
739 if (agg_ptr) {
740 DBG2(cerr << "Descending into " << agg_ptr->name() << endl);
741 return agg_ptr->var(field, true, s);
742 }
743 else
744 return 0; // qualified names must be *fully* qualified
745 }
746
747 return 0; // It is not here.
748}
749
754void
755DDS::insert_var(Vars_iter i, BaseType *ptr)
756{
757 vars.insert(i, ptr->ptr_duplicate());
758}
759
767void
769{
770 vars.insert(i, ptr);
771}
772
774int
776{
777 return vars.size();
778}
779
780void
781DDS::timeout_on()
782{
783#if USE_LOCAL_TIMEOUT_SCHEME
784#ifndef WIN32
785 alarm(d_timeout);
786#endif
787#endif
788}
789
790void
791DDS::timeout_off()
792{
793#if USE_LOCAL_TIMEOUT_SCHEME
794#ifndef WIN32
795 // Old behavior commented out. I think it is an error to change the value
796 // of d_timeout. The way this will likely be used is to set the timeout
797 // value once and then 'turn on' or turn off' that timeout as the situation
798 // dictates. The initeded use for the DDS timeout is so that timeouts for
799 // data responses will include the CPU resources needed to build the response
800 // but not the time spent transmitting the response. This may change when
801 // more parallelism is added to the server... These methods are called from
802 // BESDapResponseBuilder in bes/dap. jhrg 12/22/15
803
804 // d_timeout = alarm(0);
805
806 alarm(0);
807#endif
808#endif
809}
810
811void
812DDS::set_timeout(int)
813{
814#if USE_LOCAL_TIMEOUT_SCHEME
815 // Has no effect under win32
816 d_timeout = t;
817#endif
818}
819
820int
821DDS::get_timeout()
822{
823#if USE_LOCAL_TIMEOUT_SCHEME
824 // Has to effect under win32
825 return d_timeout;
826#endif
827 return 0;
828}
829
831void
833{
834 for (Vars_iter i = vars.begin(); i != vars.end(); i++) {
835 if ((*i)->type() == dods_sequence_c)
836 dynamic_cast<Sequence&>(**i).set_leaf_sequence();
837 else if ((*i)->type() == dods_structure_c)
838 dynamic_cast<Structure&>(**i).set_leaf_sequence();
839 }
840}
841
843void
844DDS::parse(string fname)
845{
846 FILE *in = fopen(fname.c_str(), "r");
847
848 if (!in) {
849 throw Error(cannot_read_file, "Could not open: " + fname);
850 }
851
852 try {
853 parse(in);
854 fclose(in);
855 }
856 catch (Error &e) {
857 fclose(in);
858 throw ;
859 }
860}
861
862
864void
866{
867#ifdef WIN32
868 int new_fd = _dup(fd);
869#else
870 int new_fd = dup(fd);
871#endif
872
873 if (new_fd < 0)
874 throw InternalErr(__FILE__, __LINE__, "Could not access file.");
875 FILE *in = fdopen(new_fd, "r");
876
877 if (!in) {
878 throw InternalErr(__FILE__, __LINE__, "Could not access file.");
879 }
880
881 try {
882 parse(in);
883 fclose(in);
884 }
885 catch (Error &e) {
886 fclose(in);
887 throw ;
888 }
889}
890
897void
898DDS::parse(FILE *in)
899{
900 if (!in) {
901 throw InternalErr(__FILE__, __LINE__, "Null input stream.");
902 }
903
904 void *buffer = dds_buffer(in);
905 dds_switch_to_buffer(buffer);
906
907 parser_arg arg(this);
908
909 bool status = ddsparse(&arg) == 0;
910
911 dds_delete_buffer(buffer);
912
913 DBG2(cout << "Status from parser: " << status << endl);
914
915 // STATUS is the result of the parser function; if a recoverable error
916 // was found it will be true but arg.status() will be false.
917 if (!status || !arg.status()) {// Check parse result
918 if (arg.error())
919 throw *arg.error();
920 }
921}
922
924void
925DDS::print(FILE *out)
926{
927 ostringstream oss;
928 print(oss);
929 fwrite(oss.str().data(), sizeof(char), oss.str().length(), out);
930}
931
933void
934DDS::print(ostream &out)
935{
936 out << "Dataset {\n" ;
937
938 for (Vars_citer i = vars.begin(); i != vars.end(); i++) {
939 (*i)->print_decl(out) ;
940 }
941
942 out << "} " << id2www(d_name) << ";\n" ;
943
944 return ;
945}
946
954bool
956{
957 for (AttrTable::Attr_iter i = a.attr_begin(), e = a.attr_end(); i != e; ++i) {
958 if (a.get_attr_type(i) != Attr_container) {
959 return true;
960 }
961 else if (has_dap2_attributes(*a.get_attr_table(i))) {
962 return true;
963 }
964 }
965
966 return false;
967}
968
976bool
978{
980 return true;
981 }
982
983 Constructor *cons = dynamic_cast<Constructor *>(btp);
984 if (cons) {
985 Grid* grid = dynamic_cast<Grid*>(btp);
986 if(grid){
987 return has_dap2_attributes(grid->get_array());
988 }
989 else {
990 for (Constructor::Vars_iter i = cons->var_begin(), e = cons->var_end(); i != e; i++) {
991 if (has_dap2_attributes(*i)) return true;
992 }
993 }
994 }
995 return false;
996}
997
1007static string four_spaces = " ";
1008void print_var_das(ostream &out, BaseType *bt, string indent = "") {
1009
1010 if (!has_dap2_attributes(bt))
1011 return;
1012
1013 AttrTable attr_table = bt->get_attr_table();
1014 out << indent << add_space_encoding(bt->name()) << " {" << endl;
1015
1016 Constructor *cnstrctr = dynamic_cast<Constructor *>(bt);
1017 if (cnstrctr) {
1018 Grid *grid = dynamic_cast<Grid *>(bt);
1019 if (grid) {
1020 Array *gridArray = grid->get_array();
1021 AttrTable arrayAT = gridArray->get_attr_table();
1022
1023 if (has_dap2_attributes(gridArray))
1024 gridArray->get_attr_table().print(out, indent + four_spaces);
1025#if 0
1026 // I dropped this because we don't want the MAP vectors showing up in the DAS
1027 // as children of a Grid (aka flatten the Grid bro) - ndp 5/25/18
1028 for (Grid::Map_iter mIter = grid->map_begin();
1029 mIter != grid->map_end(); ++mIter) {
1030 BaseType *currentMap = *mIter;
1031 if (has_dap2_attributes(currentMap))
1032 print_var_das(out, currentMap, indent + four_spaces);
1033 }
1034#endif
1035 }
1036 else {
1037 attr_table.print(out, indent + four_spaces);
1038 Constructor::Vars_iter i = cnstrctr->var_begin();
1039 Constructor::Vars_iter e = cnstrctr->var_end();
1040 for (; i != e; i++) {
1041 // Only call print_var_das() if there really are attributes.
1042 // This is made complicated because while there might be none
1043 // for a particular var (*i), that var might be a ctor and its
1044 // descendant might have an attribute. jhrg 3/18/18
1045 if (has_dap2_attributes(*i))
1046 print_var_das(out, *i, indent + four_spaces);
1047 }
1048 }
1049 }
1050 else {
1051 attr_table.print(out, indent + four_spaces);
1052 }
1053
1054 out << indent << "}" << endl;
1055}
1056
1065void
1066DDS::print_das(ostream &out)
1067{
1068 unique_ptr<DAS> das(get_das());
1069
1070 das->print(out);
1071}
1072
1082DAS *
1084{
1085 DAS *das = new DAS();
1086 get_das(das);
1087 return das;
1088}
1089
1095static string
1096get_unique_top_level_global_container_name(DAS *das)
1097{
1098 // It's virtually certain that the TOP_LEVE... name will be unique. If so,
1099 // return the name. The code tests for a table to see if the name _should not_ be used.
1100 AttrTable *table = das->get_table(TOP_LEVEL_ATTRS_CONTAINER_NAME);
1101 if (!table)
1102 return TOP_LEVEL_ATTRS_CONTAINER_NAME;
1103
1104 // ... but the default name might already be used
1105 unsigned int i = 0;
1106 string name;
1107 ostringstream oss;
1108 while (table) {
1109 oss.str(""); // reset to empty for the next suffix
1110 oss << "_" << ++i;
1111 if (!(i < UINT_MAX))
1112 throw InternalErr(__FILE__, __LINE__, "Cannot add top-level attributes to the DAS");
1113 name = TOP_LEVEL_ATTRS_CONTAINER_NAME + oss.str();
1114 table = das->get_table(name);
1115 }
1116
1117 return name;
1118}
1119
1128 Constructor *cons = dynamic_cast<Constructor *>(bt);
1129 if (cons) {
1130 Grid *grid = dynamic_cast<Grid *>(bt);
1131 if(grid){
1132 Array *gridArray = grid->get_array();
1133 AttrTable arrayAT = gridArray->get_attr_table();
1134
1135 for( AttrTable::Attr_iter atIter = arrayAT.attr_begin(); atIter!=arrayAT.attr_end(); ++atIter){
1136 AttrType type = arrayAT.get_attr_type(atIter);
1137 string childName = arrayAT.get_name(atIter);
1138 if (type == Attr_container){
1139 at->append_container( new AttrTable(*arrayAT.get_attr_table(atIter)), childName);
1140 }
1141 else {
1142 vector<string>* pAttrTokens = arrayAT.get_attr_vector(atIter);
1143 // append_attr makes a copy of the vector, so we don't have to do so here.
1144 at->append_attr(childName, AttrType_to_String(type), pAttrTokens,(*atIter)->is_utf8_str);
1145 }
1146 }
1147
1148 }
1149 else {
1150 for (Constructor::Vars_iter i = cons->var_begin(), e = cons->var_end(); i != e; i++) {
1151 if (has_dap2_attributes(*i)) {
1152 AttrTable *childAttrT = new AttrTable((*i)->get_attr_table());
1153 fillConstructorAttrTable(childAttrT, *i);
1154 at->append_container(childAttrT,(*i)->name());
1155 }
1156 }
1157 }
1158 }
1159}
1160
1161void DDS::get_das(DAS *das)
1162{
1163 for (Vars_citer i = vars.begin(); i != vars.end(); i++) {
1164 if (has_dap2_attributes(*i)) {
1165 AttrTable *childAttrT = new AttrTable((*i)->get_attr_table());
1166 fillConstructorAttrTable(childAttrT, *i);
1167 das->add_table((*i)->name(), childAttrT);
1168 }
1169 }
1170
1171 // Used in the rare case we have global attributes not in a table.
1172 unique_ptr<AttrTable> global(new AttrTable);
1173
1174 for (AttrTable::Attr_iter i = d_attr.attr_begin(); i != d_attr.attr_end(); ++i) {
1175 // It's possible, given the API and if the DDS was built from a DMR, that a
1176 // global attribute might not be a container; check for that.
1177 if (d_attr.get_attr_table(i)) {
1178 das->add_table(d_attr.get_name(i), new AttrTable(*(d_attr.get_attr_table(i))));
1179 }
1180 else {
1181 // This must be a top level attribute outside a container. jhrg 4/6/18
1182 global->append_attr(d_attr.get_name(i), d_attr.get_type(i), d_attr.get_attr_vector(i),(*i)->is_utf8_str);
1183 }
1184 }
1185
1186 // if any attributes were added to 'global,' add it to the DAS and take control of the pointer.
1187 if (global->get_size() > 0) {
1188 das->add_table(get_unique_top_level_global_container_name(das), global.get()); // What if this name is not unique?
1189 global.release();
1190 }
1191}
1192
1203void
1205{
1206 ostringstream oss;
1207 print_constrained(oss);
1208 fwrite(oss.str().data(), sizeof(char), oss.str().length(), out);
1209}
1210
1221void
1223{
1224 out << "Dataset {\n" ;
1225
1226 for (Vars_citer i = vars.begin(); i != vars.end(); i++) {
1227 // for each variable, indent with four spaces, print a trailing
1228 // semicolon, do not print debugging information, print only
1229 // variables in the current projection.
1230 (*i)->print_decl(out, " ", true, false, true) ;
1231 }
1232
1233 out << "} " << id2www(d_name) << ";\n" ;
1234
1235 return;
1236}
1237
1249void
1250DDS::print_xml(FILE *out, bool constrained, const string &blob)
1251{
1252 ostringstream oss;
1253 print_xml_writer(oss, constrained, blob);
1254 fwrite(oss.str().data(), 1, oss.str().length(), out);
1255}
1256
1268void
1269DDS::print_xml(ostream &out, bool constrained, const string &blob)
1270{
1271 print_xml_writer(out, constrained, blob);
1272}
1273
1274class VariablePrintXMLWriter : public unary_function<BaseType *, void>
1275{
1276 XMLWriter &d_xml;
1277 bool d_constrained;
1278public:
1279 VariablePrintXMLWriter(XMLWriter &xml, bool constrained)
1280 : d_xml(xml), d_constrained(constrained)
1281 {}
1282 void operator()(BaseType *bt)
1283 {
1284 bt->print_xml_writer(d_xml, d_constrained);
1285 }
1286};
1287
1304void
1305DDS::print_xml_writer(ostream &out, bool constrained, const string &blob)
1306{
1307 XMLWriter xml(" ");
1308
1309 // This is the 'DAP 3.2' DDX response - now the only response libdap will return.
1310 // jhrg 9/10/18
1311 if (xmlTextWriterStartElement(xml.get_writer(), (const xmlChar*) "Dataset") < 0)
1312 throw InternalErr(__FILE__, __LINE__, "Could not write Dataset element");
1313 if (xmlTextWriterWriteAttribute(xml.get_writer(), (const xmlChar*) "name", (const xmlChar*)d_name.c_str()) < 0)
1314 throw InternalErr(__FILE__, __LINE__, "Could not write attribute for name");
1315 if (xmlTextWriterWriteAttribute(xml.get_writer(), (const xmlChar*) "xmlns:xsi", (const xmlChar*)"http://www.w3.org/2001/XMLSchema-instance") < 0)
1316 throw InternalErr(__FILE__, __LINE__, "Could not write attribute for xmlns:xsi");
1317
1318 if (xmlTextWriterWriteAttribute(xml.get_writer(), (const xmlChar*) "xsi:schemaLocation", (const xmlChar*)c_dap_32_n_sl.c_str()) < 0)
1319 throw InternalErr(__FILE__, __LINE__, "Could not write attribute for xmlns:schemaLocation");
1320
1321 if (xmlTextWriterWriteAttribute(xml.get_writer(), (const xmlChar*) "xmlns:grddl", (const xmlChar*)"http://www.w3.org/2003/g/data-view#") < 0)
1322 throw InternalErr(__FILE__, __LINE__, "Could not write attribute for xmlns:grddl");
1323
1324 if (xmlTextWriterWriteAttribute(xml.get_writer(), (const xmlChar*) "grddl:transformation", (const xmlChar*)grddl_transformation_dap32.c_str()) < 0)
1325 throw InternalErr(__FILE__, __LINE__, "Could not write attribute for xmlns:transformation");
1326
1327 if (xmlTextWriterWriteAttribute(xml.get_writer(), (const xmlChar*) "xmlns", (const xmlChar*)c_dap32_namespace.c_str()) < 0)
1328 throw InternalErr(__FILE__, __LINE__, "Could not write attribute for xmlns");
1329 if (xmlTextWriterWriteAttribute(xml.get_writer(), (const xmlChar*) "xmlns:dap", (const xmlChar*)c_dap32_namespace.c_str()) < 0)
1330 throw InternalErr(__FILE__, __LINE__, "Could not write attribute for xmlns:dap");
1331
1332 if (xmlTextWriterWriteAttribute(xml.get_writer(), (const xmlChar*) "dapVersion", (const xmlChar*)"3.2") < 0)
1333 throw InternalErr(__FILE__, __LINE__, "Could not write attribute for dapVersion");
1334
1335 if (!get_request_xml_base().empty()) {
1336 if (xmlTextWriterWriteAttribute(xml.get_writer(), (const xmlChar*) "xmlns:xml", (const xmlChar*)c_xml_namespace.c_str()) < 0)
1337 throw InternalErr(__FILE__, __LINE__, "Could not write attribute for xmlns:xml");
1338
1339 if (xmlTextWriterWriteAttribute(xml.get_writer(), (const xmlChar*) "xml:base", (const xmlChar*)get_request_xml_base().c_str()) < 0)
1340 throw InternalErr(__FILE__, __LINE__, "Could not write attribute for xml:base");
1341 }
1342
1343 // Print the global attributes
1344 d_attr.print_xml_writer(xml);
1345
1346 // Print each variable
1347 for_each(var_begin(), var_end(), VariablePrintXMLWriter(xml, constrained));
1348
1349 if (xmlTextWriterStartElement(xml.get_writer(), (const xmlChar*) "blob") < 0)
1350 throw InternalErr(__FILE__, __LINE__, "Could not write blob element");
1351 string cid = "cid:" + blob;
1352 if (xmlTextWriterWriteAttribute(xml.get_writer(), (const xmlChar*) "href", (const xmlChar*) cid.c_str()) < 0)
1353 throw InternalErr(__FILE__, __LINE__, "Could not write attribute for d_name");
1354 if (xmlTextWriterEndElement(xml.get_writer()) < 0)
1355 throw InternalErr(__FILE__, __LINE__, "Could not end blob element");
1356
1357 if (xmlTextWriterEndElement(xml.get_writer()) < 0)
1358 throw InternalErr(__FILE__, __LINE__, "Could not end Dataset element");
1359
1360 out << xml.get_doc();// << ends;// << endl;
1361}
1362
1376void
1377DDS::print_dmr(ostream &out, bool constrained)
1378{
1379 if (get_dap_major() < 4)
1380 throw InternalErr(__FILE__, __LINE__, "Tried to print a DMR with DAP major version less than 4");
1381
1382 XMLWriter xml(" ");
1383
1384 // DAP4 wraps a dataset in a top-level Group element.
1385 if (xmlTextWriterStartElement(xml.get_writer(), (const xmlChar*) "Group") < 0)
1386 throw InternalErr(__FILE__, __LINE__, "Could not write Group element");
1387
1388 if (xmlTextWriterWriteAttribute(xml.get_writer(), (const xmlChar*) "xmlns:xml",
1389 (const xmlChar*) c_xml_namespace.c_str()) < 0)
1390 throw InternalErr(__FILE__, __LINE__, "Could not write attribute for xmlns:xml");
1391
1392 if (xmlTextWriterWriteAttribute(xml.get_writer(), (const xmlChar*) "xmlns:xsi", (const xmlChar*) c_xml_xsi.c_str())
1393 < 0)
1394 throw InternalErr(__FILE__, __LINE__, "Could not write attribute for xmlns:xsi");
1395
1396 if (xmlTextWriterWriteAttribute(xml.get_writer(), (const xmlChar*) "xsi:schemaLocation",
1397 (const xmlChar*) c_dap_40_n_sl.c_str()) < 0)
1398 throw InternalErr(__FILE__, __LINE__, "Could not write attribute for xmlns:schemaLocation");
1399
1400 if (xmlTextWriterWriteAttribute(xml.get_writer(), (const xmlChar*) "xmlns",
1401 (const xmlChar*) get_namespace().c_str()) < 0)
1402 throw InternalErr(__FILE__, __LINE__, "Could not write attribute for xmlns");
1403
1404 if (xmlTextWriterWriteAttribute(xml.get_writer(), (const xmlChar*) "dapVersion",
1405 (const xmlChar*) get_dap_version().c_str()) < 0)
1406 throw InternalErr(__FILE__, __LINE__, "Could not write attribute for dapVersion");
1407
1408 if (xmlTextWriterWriteAttribute(xml.get_writer(), (const xmlChar*) "dmrVersion", (const xmlChar*) get_dmr_version().c_str()) < 0)
1409 throw InternalErr(__FILE__, __LINE__, "Could not write attribute for dapVersion");
1410
1411 if (!get_request_xml_base().empty()) {
1412 if (xmlTextWriterWriteAttribute(xml.get_writer(), (const xmlChar*) "xml:base",
1413 (const xmlChar*) get_request_xml_base().c_str()) < 0)
1414 throw InternalErr(__FILE__, __LINE__, "Could not write attribute for xml:base");
1415 }
1416
1417 if (xmlTextWriterWriteAttribute(xml.get_writer(), (const xmlChar*) "name", (const xmlChar*) d_name.c_str()) < 0)
1418 throw InternalErr(__FILE__, __LINE__, "Could not write attribute for name");
1419
1420 // Print the global attributes
1421 d_attr.print_xml_writer(xml);
1422
1423 // Print each variable
1424 for_each(var_begin(), var_end(), VariablePrintXMLWriter(xml, constrained));
1425
1426 if (xmlTextWriterEndElement(xml.get_writer()) < 0)
1427 throw InternalErr(__FILE__, __LINE__, "Could not end the top-level Group element");
1428
1429 out << xml.get_doc();
1430}
1431
1432// Used by DDS::send() when returning data from a function call.
1447bool
1449{
1450 // The dataset must have a d_name
1451 if (d_name == "") {
1452 cerr << "A dataset must have a d_name" << endl;
1453 return false;
1454 }
1455
1456 string msg;
1457 if (!unique_names(vars, d_name, "Dataset", msg))
1458 return false;
1459
1460 if (all)
1461 for (Vars_iter i = vars.begin(); i != vars.end(); i++)
1462 if (!(*i)->check_semantics(msg, true))
1463 return false;
1464
1465 return true;
1466}
1467
1491bool
1492DDS::mark(const string &n, bool state)
1493{
1494 unique_ptr<BaseType::btp_stack> s(new BaseType::btp_stack);
1495
1496 DBG2(cerr << "DDS::mark: Looking for " << n << endl);
1497
1498 BaseType *variable = var(n, s.get());
1499 if (!variable) {
1500 throw Error(malformed_expr, "Could not find variable " + n);
1501 }
1502
1503 variable->set_send_p(state);
1504
1505 DBG2(cerr << "DDS::mark: Set variable " << variable->d_name()
1506 << " (a " << variable->type_name() << ")" << endl);
1507
1508 // Now check the btp_stack and run BaseType::set_send_p for every
1509 // BaseType pointer on the stack. Using BaseType::set_send_p() will
1510 // set the property for a Constructor but not its contained variables
1511 // which preserves the semantics of projecting just one field.
1512 while (!s->empty()) {
1513 s->top()->BaseType::set_send_p(state);
1514
1515 DBG2(cerr << "DDS::mark: Set variable " << s->top()->d_name()
1516 << " (a " << s->top()->type_name() << ")" << endl);
1517
1518 string parent_name = (s->top()->get_parent()) ? s->top()->get_parent()->name(): "none";
1519 string parent_type = (s->top()->get_parent()) ? s->top()->get_parent()->type_name(): "none";
1520 DBG2(cerr << "DDS::mark: Parent variable " << parent_name << " (a " << parent_type << ")" << endl);
1521
1522 s->pop();
1523 }
1524
1525 return true;
1526}
1527
1533void
1534DDS::mark_all(bool state)
1535{
1536 for (Vars_iter i = vars.begin(); i != vars.end(); i++)
1537 (*i)->set_send_p(state);
1538}
1539
1545bool DDS::is_dap4_projected(std::vector<string> &inventory)
1546{
1547 bool has_dap4 = d_attr.has_dap4_types("/", inventory);
1548 for(const auto var : variables()){
1549 has_dap4 |= var->is_dap4_projected(inventory);
1550 }
1551 return has_dap4;
1552}
1553
1554
1562void
1563DDS::dump(ostream &strm) const
1564{
1565 strm << DapIndent::LMarg << "DDS::dump - ("
1566 << (void *)this << ")" << endl ;
1567 DapIndent::Indent() ;
1568 strm << DapIndent::LMarg << "d_name: " << d_name << endl ;
1569 strm << DapIndent::LMarg << "filename: " << d_filename << endl ;
1570 strm << DapIndent::LMarg << "protocol major: " << d_dap_major << endl;
1571 strm << DapIndent::LMarg << "protocol minor: " << d_dap_minor << endl;
1572 strm << DapIndent::LMarg << "factory: " << (void *)d_factory << endl ;
1573
1574 strm << DapIndent::LMarg << "global attributes:" << endl ;
1575 DapIndent::Indent() ;
1576 d_attr.dump(strm) ;
1577 DapIndent::UnIndent() ;
1578
1579 if (vars.size()) {
1580 strm << DapIndent::LMarg << "vars:" << endl ;
1581 DapIndent::Indent() ;
1582 Vars_citer i = vars.begin() ;
1583 Vars_citer ie = vars.end() ;
1584 for (; i != ie; i++) {
1585 (*i)->dump(strm) ;
1586 }
1587 DapIndent::UnIndent() ;
1588 }
1589 else {
1590 strm << DapIndent::LMarg << "vars: none" << endl ;
1591 }
1592
1593 DapIndent::UnIndent() ;
1594}
1595
1596} // namespace libdap
A multidimensional array of identical data types.
Definition Array.h:123
Contains the attributes for a dataset.
Definition AttrTable.h:154
virtual AttrTable * append_container(const string &name)
Add a container to the attribute table.
Definition AttrTable.cc:554
bool has_dap4_types(const std::string &path, std::vector< std::string > &inventory) const
virtual AttrTable * get_attr_table(const string &name)
Get an attribute container.
Definition AttrTable.cc:751
virtual Attr_iter attr_end()
Definition AttrTable.cc:863
virtual string get_type(const string &name)
Get the type name of an attribute within this attribute table.
Definition AttrTable.cc:757
virtual vector< string > * get_attr_vector(const string &name)
Get a vector-valued attribute.
Definition AttrTable.cc:797
virtual unsigned int append_attr(const string &name, const string &type, const string &value)
Add an attribute to the table.
Definition AttrTable.cc:335
virtual Attr_iter attr_begin()
Definition AttrTable.cc:855
virtual string get_name() const
Get the name of this attribute table.
Definition AttrTable.cc:266
void print_xml_writer(XMLWriter &xml)
virtual unsigned int get_size() const
Get the number of entries in this attribute table.
Definition AttrTable.cc:259
virtual void dump(ostream &strm) const
dumps information about this object
virtual AttrType get_attr_type(const string &name)
Get the type of an attribute.
Definition AttrTable.cc:765
The basic data type for the DODS DAP types.
Definition BaseType.h:120
virtual bool is_dap4_projected(std::vector< string > &projected_dap4_inventory)
Definition BaseType.cc:1323
virtual string type_name() const
Returns the type of the class instance as a string.
Definition BaseType.cc:376
virtual AttrTable & get_attr_table()
Definition BaseType.cc:579
virtual string name() const
Returns the name of the class instance.
Definition BaseType.cc:317
virtual bool is_vector_type() const
Returns true if the instance is a vector (i.e., array) type variable.
Definition BaseType.cc:399
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:409
virtual void set_send_p(bool state)
Definition BaseType.cc:565
virtual BaseType * ptr_duplicate()=0
virtual BaseType * var(const string &name="", bool exact_match=true, btp_stack *s=nullptr)
Returns a pointer to a member of a constructor class.
Definition BaseType.cc:764
BaseType * var(const string &name, bool exact_match=true, btp_stack *s=nullptr) override
btp_stack no longer needed; use back pointers (BaseType::get_parent())
void add_var(BaseType *bt, Part part=nil) override
void add_var_nocopy(BaseType *bt, Part part=nil) override
unsigned int width(bool constrained=false) const override
Vars_iter var_begin()
virtual void del_var(const string &name)
Remove an element from a Constructor.
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
AttrTable * get_table(AttrTable::Attr_iter &i)
Returns the referenced variable attribute table.
Definition DAS.cc:177
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:292
void set_dap_major(int p)
Definition DDS.cc:333
void mark_all(bool state)
Definition DDS.cc:1534
void print_dmr(ostream &out, bool constrained)
Print the DAP4 DMR object using a DDS.
Definition DDS.cc:1377
void add_var_nocopy(BaseType *bt)
Adds the variable to the DDS.
Definition DDS.cc:568
bool check_semantics(bool all=false)
Check the semantics of each of the variables represented in the DDS.
Definition DDS.cc:1448
string filename() const
Definition DDS.cc:316
virtual AttrTable & get_attr_table()
Definition DDS.cc:301
uint64_t get_request_size_kb(bool constrained)
Get the estimated response size in kilobytes.
Definition DDS.cc:522
virtual void transfer_attributes(DAS *das)
Definition DDS.cc:246
void set_dap_minor(int p)
Definition DDS.cc:352
string get_namespace() const
Get the namespace associated with the DDS - likely set only by DDX responses.
Definition DDS.h:279
int num_var()
Returns the number of variables in the DDS.
Definition DDS.cc:775
void print(FILE *out)
Print the entire DDS to the specified file.
Definition DDS.cc:925
int get_request_size(bool constrained)
Get the estimated response size in bytes.
Definition DDS.cc:495
bool is_dap4_projected(std::vector< string > &inventory)
Definition DDS.cc:1545
string get_dataset_name() const
Definition DDS.cc:285
void del_var(const string &n)
Removes a variable from the DDS.
Definition DDS.cc:590
void parse(string fname)
Parse a DDS from a file with the given d_name.
Definition DDS.cc:844
BaseType * var(const string &n, BaseType::btp_stack &s)
Definition DDS.cc:646
void print_xml(FILE *out, bool constrained, const string &blob="")
Definition DDS.cc:1250
void insert_var(Vars_iter i, BaseType *ptr)
Insert a variable before the referenced element.
Definition DDS.cc:755
bool mark(const string &name, bool state)
Mark the send_p flag of the named variable to state.
Definition DDS.cc:1492
DDS(BaseTypeFactory *factory, const string &name="")
Definition DDS.cc:168
void tag_nested_sequences()
Traverse DDS, set Sequence leaf nodes.
Definition DDS.cc:832
DAS * get_das()
Get a DAS object.
Definition DDS.cc:1083
Vars_iter var_begin()
Definition DDS.h:344
void print_constrained(FILE *out)
Print a constrained DDS to the specified file.
Definition DDS.cc:1204
string container_name()
Definition DDS.cc:440
void insert_var_nocopy(Vars_iter i, BaseType *ptr)
Definition DDS.cc:768
string get_request_xml_base() const
Get the URL that will return this DDS/DDX/DataThing.
Definition DDS.h:273
Vars_iter var_end()
Return an iterator.
Definition DDS.h:349
int get_dap_major() const
Get the DAP major version as sent by the client.
Definition DDS.h:257
const vector< BaseType * > & variables() const
Definition DDS.h:339
void set_dap_version(const string &version_string="2.0")
Definition DDS.cc:369
Structure * container()
Definition DDS.cc:476
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:544
void print_xml_writer(ostream &out, bool constrained, const string &blob="")
Definition DDS.cc:1305
void print_das(ostream &out)
write the DAS response given the attribute information in the DDS
Definition DDS.cc:1066
virtual void dump(ostream &strm) const
dumps information about this object
Definition DDS.cc:1563
libdap base object for common functionality of libdap objects
Definition DapObj.h:51
A class for error processing.
Definition Error.h:94
Holds the Grid data type.
Definition Grid.h:123
Array * get_array()
Returns the Grid Array. This method returns the array using an Array*, so no cast is required.
Definition Grid.cc:498
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:1240
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:309
top level DAP object to house generic methods
bool has_dap2_attributes(AttrTable &a)
Definition DDS.cc:955
string www2id(const string &in, const string &escape, const string &except)
Definition escaping.cc:220
string add_space_encoding(const string &s)
Definition AttrTable.cc:78
string AttrType_to_String(const AttrType at)
Definition AttrTable.cc:97
void fillConstructorAttrTable(AttrTable *at, BaseType *bt)
Recursive helper function for Building DAS entries for Constructor types.
Definition DDS.cc:1127
string id2www(string in, const string &allowable)
Definition escaping.cc:153
Pass parameters by reference to a parser.
Definition parser.h:69