D4ParserSax2.cc
// -*- mode: c++; c-basic-offset:4 -*-
// This file is part of libdap, A C++ implementation of the OPeNDAP Data
// Access Protocol.
// Copyright (c) 2012 OPeNDAP, Inc.
// Author: James Gallagher <jgallagher@opendap.org>
//
// This library is free software; you can redistribute it and/or
// modify it under the terms of the GNU Lesser General Public
// License as published by the Free Software Foundation; either
// version 2.1 of the License, or (at your option) any later version.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
//
// You can contact OPeNDAP, Inc. at PO Box 112, Saunderstown, RI. 02874-0112.
#include "config.h"
// #define DODS_DEBUG 1
#include <iostream>
#include <sstream>
#include <cassert>
#include <cstdarg>
#include <cstring>
#include <libxml2/libxml/parserInternals.h>
#include "DMR.h"
#include "Array.h"
#include "BaseType.h"
#include "D4Attributes.h"
#include "D4BaseTypeFactory.h"
#include "D4Enum.h"
#include "D4Group.h"
#include "D4Maps.h"
#include "D4ParserSax2.h"
#include "DapXmlNamespaces.h"
#include "debug.h"
#include "util.h"
namespace libdap {
static const char *states[] = {"parser_start",
"inside_dataset",
// inside_group is the state just after parsing the start of a Group
// element.
"inside_group",
"inside_attribute_container", "inside_attribute", "inside_attribute_value",
"inside_other_xml_attribute",
"inside_enum_def", "inside_enum_const",
"inside_dim_def",
// This covers Byte, ..., Url, Opaque
"inside_simple_type",
// "inside_array",
"inside_dim", "inside_map",
"inside_constructor",
"not_dap4_element",
"parser_unknown", "parser_error", "parser_fatal_error",
"parser_end"};
static bool is_not(const char *name, const char *tag) { return strcmp(name, tag) != 0; }
D4EnumDef *D4ParserSax2::enum_def() {
if (!d_enum_def)
d_enum_def = new D4EnumDef;
return d_enum_def;
}
D4Dimension *D4ParserSax2::dim_def() {
if (!d_dim_def)
d_dim_def = new D4Dimension;
return d_dim_def;
}
void D4ParserSax2::transfer_xml_attrs(const xmlChar **attributes, int nb_attributes) {
if (!xml_attrs.empty())
xml_attrs.clear(); // erase old attributes
// Make a value using the attribute name and the prefix, namespace URI
// and the value. The prefix might be null.
unsigned int index = 0;
for (int i = 0; i < nb_attributes; ++i, index += 5) {
xml_attrs.insert(map<string, XMLAttribute>::value_type(string((const char *)attributes[index]),
XMLAttribute(attributes + index + 1)));
DBG(cerr << "XML Attribute '" << (const char *)attributes[index]
<< "': " << xml_attrs[(const char *)attributes[index]].value << endl);
}
}
void D4ParserSax2::transfer_xml_ns(const xmlChar **namespaces, int nb_namespaces) {
// make a value with the prefix and namespace URI. The prefix might be null.
for (int i = 0; i < nb_namespaces; ++i) {
namespace_table.insert(map<string, string>::value_type(
namespaces[i * 2] != 0 ? (const char *)namespaces[i * 2] : "", (const char *)namespaces[i * 2 + 1]));
}
}
bool D4ParserSax2::check_required_attribute(const string &attr) {
if (xml_attrs.find(attr) == xml_attrs.end()) {
dmr_error(this, "Required attribute '%s' not found.", attr.c_str());
return false;
} else
return true;
}
bool D4ParserSax2::check_attribute(const string &attr) { return (xml_attrs.find(attr) != xml_attrs.end()); }
bool D4ParserSax2::process_dimension_def(const char *name, const xmlChar **attrs, int nb_attributes) {
if (is_not(name, "Dimension"))
return false;
transfer_xml_attrs(attrs, nb_attributes);
if (!(check_required_attribute("name") && check_required_attribute("size"))) {
dmr_error(this, "The required attribute 'name' or 'size' was missing from a Dimension element.");
return false;
}
// This getter (dim_def) allocates a new object if needed.
dim_def()->set_name(xml_attrs["name"].value);
try {
dim_def()->set_size(xml_attrs["size"].value);
} catch (Error &e) {
dmr_error(this, "%s", e.get_error_message().c_str());
return false;
}
return true;
}
bool D4ParserSax2::process_dimension(const char *name, const xmlChar **attrs, int nb_attributes) {
if (is_not(name, "Dim"))
return false;
transfer_xml_attrs(attrs, nb_attributes);
if (check_attribute("size") && check_attribute("name")) {
dmr_error(this, "Only one of 'size' and 'name' are allowed in a Dim element, but both were used.");
return false;
}
if (!(check_attribute("size") || check_attribute("name"))) {
dmr_error(this, "Either 'size' or 'name' must be used in a Dim element.");
return false;
}
if (!top_basetype()->is_vector_type()) {
// Make the top BaseType* an array
BaseType *b = top_basetype();
pop_basetype();
Array *a = static_cast<Array *>(dmr()->factory()->NewVariable(dods_array_c, b->name()));
a->set_is_dap4(true);
a->add_var_nocopy(b);
a->set_attributes_nocopy(b->attributes());
// trick: instead of popping b's attributes, copying them and then pushing
// a's copy, just move the pointer (but make sure there's only one object that
// references that pointer).
b->set_attributes_nocopy(0);
push_basetype(a);
}
assert(top_basetype()->is_vector_type());
Array *a = static_cast<Array *>(top_basetype());
if (check_attribute("size")) {
a->append_dim_ll(strtoll(xml_attrs["size"].value.c_str(), nullptr, 10));
#if 0
a->append_dim(atoi(xml_attrs["size"].value.c_str())); // low budget code for now. jhrg 8/20/13
#endif
return true;
} else if (check_attribute("name")) {
string name = xml_attrs["name"].value;
D4Dimension *dim = 0;
if (name[0] == '/') // lookup the Dimension in the root group
dim = dmr()->root()->find_dim(name);
else // get enclosing Group and lookup Dimension there
dim = top_group()->find_dim(name);
if (!dim)
throw Error("The dimension '" + name + "' was not found while parsing the variable '" + a->name() + "'.");
a->append_dim(dim);
return true;
}
return false;
}
bool D4ParserSax2::process_map(const char *name, const xmlChar **attrs, int nb_attributes) {
if (is_not(name, "Map"))
return false;
transfer_xml_attrs(attrs, nb_attributes);
if (!check_attribute("name")) {
dmr_error(this, "The 'name' attribute must be used in a Map element.");
return false;
}
if (!top_basetype()->is_vector_type()) {
// Make the top BaseType* an array
BaseType *b = top_basetype();
pop_basetype();
Array *a = static_cast<Array *>(dmr()->factory()->NewVariable(dods_array_c, b->name()));
a->set_is_dap4(true);
a->add_var_nocopy(b);
a->set_attributes_nocopy(b->attributes());
// trick: instead of popping b's attributes, copying them and then pushing
// a's copy, just move the pointer (but make sure there's only one object that
// references that pointer).
b->set_attributes_nocopy(0);
push_basetype(a);
}
assert(top_basetype()->is_vector_type());
Array *a = static_cast<Array *>(top_basetype());
string map_name = xml_attrs["name"].value;
if (xml_attrs["name"].value[0] != '/')
map_name = top_group()->FQN() + map_name;
Array *map_source = 0; // The array variable that holds the data for the Map
if (map_name[0] == '/') // lookup the Map in the root group
map_source = dmr()->root()->find_map_source(map_name);
else // get enclosing Group and lookup Map there
map_source = top_group()->find_map_source(map_name);
// Change: If the parser is in 'strict' mode (the default) and the Array named by
// the Map cannot be fond, it is an error. If 'strict' mode is false (permissive
// mode), then this is not an error. However, the Array referenced by the Map will
// be null. This is a change in the parser's behavior to accommodate requests for
// Arrays that include Maps that do not also include the Map(s) in the request.
// See https://opendap.atlassian.net/browse/HYRAX-98. jhrg 4/13/16
if (!map_source && d_strict)
throw Error("The Map '" + map_name + "' was not found while parsing the variable '" + a->name() + "'.");
a->maps()->add_map(new D4Map(map_name, map_source));
return true;
}
bool D4ParserSax2::process_group(const char *name, const xmlChar **attrs, int nb_attributes) {
if (is_not(name, "Group"))
return false;
transfer_xml_attrs(attrs, nb_attributes);
if (!check_required_attribute("name")) {
dmr_error(this, "The required attribute 'name' was missing from a Group element.");
return false;
}
BaseType *btp = dmr()->factory()->NewVariable(dods_group_c, xml_attrs["name"].value);
if (!btp) {
dmr_fatal_error(this, "Could not instantiate the Group '%s'.", xml_attrs["name"].value.c_str());
return false;
}
D4Group *grp = static_cast<D4Group *>(btp);
// Need to set this to get the D4Attribute behavior in the type classes
// shared between DAP2 and DAP4. jhrg 4/18/13
grp->set_is_dap4(true);
// link it up and change the current group
D4Group *parent = top_group();
if (!parent) {
dmr_fatal_error(this, "No Group on the Group stack.");
return false;
}
grp->set_parent(parent);
parent->add_group_nocopy(grp);
push_group(grp);
push_attributes(grp->attributes());
return true;
}
inline bool D4ParserSax2::process_attribute(const char *name, const xmlChar **attrs, int nb_attributes) {
if (is_not(name, "Attribute"))
return false;
// These methods set the state to parser_error if a problem is found.
transfer_xml_attrs(attrs, nb_attributes);
// add error
if (!(check_required_attribute(string("name")) && check_required_attribute(string("type")))) {
dmr_error(this, "The required attribute 'name' or 'type' was missing from an Attribute element.");
return false;
}
if (xml_attrs["type"].value == "Container") {
push_state(inside_attribute_container);
DBG(cerr << "Pushing attribute container " << xml_attrs["name"].value << endl);
D4Attribute *child = new D4Attribute(xml_attrs["name"].value, attr_container_c);
D4Attributes *tos = top_attributes();
// add return
if (!tos) {
delete child;
dmr_fatal_error(this, "Expected an Attribute container on the top of the attribute stack.");
return false;
}
tos->add_attribute_nocopy(child);
push_attributes(child->attributes());
} else if (xml_attrs["type"].value == "OtherXML") {
push_state(inside_other_xml_attribute);
dods_attr_name = xml_attrs["name"].value;
dods_attr_type = xml_attrs["type"].value;
} else {
push_state(inside_attribute);
dods_attr_name = xml_attrs["name"].value;
dods_attr_type = xml_attrs["type"].value;
}
return true;
}
inline bool D4ParserSax2::process_enum_def(const char *name, const xmlChar **attrs, int nb_attributes) {
if (is_not(name, "Enumeration"))
return false;
transfer_xml_attrs(attrs, nb_attributes);
if (!(check_required_attribute("name") && check_required_attribute("basetype"))) {
dmr_error(this, "The required attribute 'name' or 'basetype' was missing from an Enumeration element.");
return false;
}
Type t = get_type(xml_attrs["basetype"].value.c_str());
if (!is_integer_type(t)) {
dmr_error(this, "The Enumeration '%s' must have an integer type, instead the type '%s' was used.",
xml_attrs["name"].value.c_str(), xml_attrs["basetype"].value.c_str());
return false;
}
// This getter allocates a new object if needed.
string enum_def_path = xml_attrs["name"].value;
#if 0
// Use FQNs when things are referenced, not when they are defined
if (xml_attrs["name"].value[0] != '/')
enum_def_path = top_group()->FQN() + enum_def_path;
#endif
enum_def()->set_name(enum_def_path);
enum_def()->set_type(t);
return true;
}
inline bool D4ParserSax2::process_enum_const(const char *name, const xmlChar **attrs, int nb_attributes) {
if (is_not(name, "EnumConst"))
return false;
// These methods set the state to parser_error if a problem is found.
transfer_xml_attrs(attrs, nb_attributes);
if (!(check_required_attribute("name") && check_required_attribute("value"))) {
dmr_error(this, "The required attribute 'name' or 'value' was missing from an EnumConst element.");
return false;
}
istringstream iss(xml_attrs["value"].value);
long long value = 0;
iss >> skipws >> value;
if (iss.fail() || iss.bad()) {
dmr_error(this, "Expected an integer value for an Enumeration constant, got '%s' instead.",
xml_attrs["value"].value.c_str());
} else if (!enum_def()->is_valid_enum_value(value)) {
dmr_error(this, "In an Enumeration constant, the value '%s' cannot fit in a variable of type '%s'.",
xml_attrs["value"].value.c_str(), D4type_name(d_enum_def->type()).c_str());
} else {
// unfortunate choice of names... args are 'label' and 'value'
enum_def()->add_value(xml_attrs["name"].value, value);
}
return true;
}
inline bool D4ParserSax2::process_variable(const char *name, const xmlChar **attrs, int nb_attributes) {
Type t = get_type(name);
if (is_simple_type(t)) {
process_variable_helper(t, inside_simple_type, attrs, nb_attributes);
return true;
} else {
switch (t) {
case dods_structure_c:
process_variable_helper(t, inside_constructor, attrs, nb_attributes);
return true;
case dods_sequence_c:
process_variable_helper(t, inside_constructor, attrs, nb_attributes);
return true;
default:
return false;
}
}
}
void D4ParserSax2::process_variable_helper(Type t, ParseState s, const xmlChar **attrs, int nb_attributes) {
transfer_xml_attrs(attrs, nb_attributes);
if (check_required_attribute("name")) {
BaseType *btp = dmr()->factory()->NewVariable(t, xml_attrs["name"].value);
if (!btp) {
dmr_fatal_error(this, "Could not instantiate the variable '%s'.", xml_attrs["name"].value.c_str());
return;
}
if ((t == dods_enum_c) && check_required_attribute("enum")) {
D4EnumDef *enum_def = 0;
string enum_path = xml_attrs["enum"].value;
if (enum_path[0] == '/')
enum_def = dmr()->root()->find_enum_def(enum_path);
else
enum_def = top_group()->find_enum_def(enum_path);
if (!enum_def)
dmr_fatal_error(this, "Could not find the Enumeration definition '%s'.", enum_path.c_str());
static_cast<D4Enum *>(btp)->set_enumeration(enum_def);
}
btp->set_is_dap4(true); // see comment above
push_basetype(btp);
push_attributes(btp->attributes());
push_state(s);
}
}
void D4ParserSax2::dmr_start_document(void *p) {
D4ParserSax2 *parser = static_cast<D4ParserSax2 *>(p);
parser->d_error_msg = "";
parser->char_data = "";
// Set this in intern_helper so that the loop test for the parser_end
// state works for the first iteration. It seems like XMLParseChunk calls this
// function on it's first run. jhrg 9/16/13
// parser->push_state(parser_start);
parser->push_attributes(parser->dmr()->root()->attributes());
if (parser->debug())
cerr << "Parser start state: " << states[parser->get_state()] << endl;
}
void D4ParserSax2::dmr_end_document(void *p) {
D4ParserSax2 *parser = static_cast<D4ParserSax2 *>(p);
if (parser->debug())
cerr << "Parser end state: " << states[parser->get_state()] << endl;
if (parser->get_state() != parser_end)
D4ParserSax2::dmr_error(parser, "The document contained unbalanced tags.");
// If we've found any sort of error, don't make the DMR; intern() will
// take care of the error.
if (parser->get_state() == parser_error || parser->get_state() == parser_fatal_error)
return;
if (!parser->empty_basetype() || parser->empty_group())
D4ParserSax2::dmr_error(parser,
"The document did not contain a valid root Group or contained unbalanced tags.");
parser->pop_group(); // leave the stack 'clean'
parser->pop_attributes();
}
void D4ParserSax2::dmr_start_element(void *p, const xmlChar *l, const xmlChar *prefix, const xmlChar *URI,
int nb_namespaces, const xmlChar **namespaces, int nb_attributes,
int /*nb_defaulted*/, const xmlChar **attributes) {
D4ParserSax2 *parser = static_cast<D4ParserSax2 *>(p);
const char *localname = (const char *)l;
if (parser->debug())
cerr << "Start element " << localname << " prefix: " << (prefix ? (char *)prefix : "null")
<< " ns: " << (URI ? (char *)URI : "null") << " (state: " << states[parser->get_state()] << ")" << endl;
if (parser->get_state() != parser_error) {
string dap4_ns_name = DapXmlNamspaces::getDapNamespaceString(DAP_4_0);
if (parser->debug())
cerr << "dap4_ns_name: " << dap4_ns_name << endl;
string this_element_ns_name = (URI != 0) ? ((char *)URI) : "";
if (parser->debug())
cerr << "this_element_ns_name: " << this_element_ns_name << endl;
if (this_element_ns_name.compare(dap4_ns_name)) {
if (parser->debug())
cerr << "Start of non DAP4 element: " << localname << " detected." << endl;
parser->push_state(not_dap4_element);
// return;
}
}
switch (parser->get_state()) {
case parser_start:
if (is_not(localname, "Dataset"))
D4ParserSax2::dmr_error(parser, "Expected DMR to start with a Dataset element; found '%s' instead.",
localname);
parser->root_ns = URI ? (const char *)URI : "";
parser->transfer_xml_attrs(attributes, nb_attributes);
if (parser->check_required_attribute(string("name")))
parser->dmr()->set_name(parser->xml_attrs["name"].value);
if (parser->check_attribute("dapVersion"))
parser->dmr()->set_dap_version(parser->xml_attrs["dapVersion"].value);
if (parser->check_attribute("dmrVersion"))
parser->dmr()->set_dmr_version(parser->xml_attrs["dmrVersion"].value);
if (parser->check_attribute("base"))
parser->dmr()->set_request_xml_base(parser->xml_attrs["base"].value);
if (!parser->root_ns.empty())
parser->dmr()->set_namespace(parser->root_ns);
// Push the root Group on the stack
parser->push_group(parser->dmr()->root());
parser->push_state(inside_dataset);
break;
// Both inside dataset and inside group can have the same stuff.
// The difference is that the Dataset holds the root group, which
// must be present; other groups are optional
case inside_dataset:
case inside_group:
if (parser->process_enum_def(localname, attributes, nb_attributes))
parser->push_state(inside_enum_def);
else if (parser->process_dimension_def(localname, attributes, nb_attributes))
parser->push_state(inside_dim_def);
else if (parser->process_group(localname, attributes, nb_attributes))
parser->push_state(inside_group);
else if (parser->process_variable(localname, attributes, nb_attributes))
// This will push either inside_simple_type or inside_structure
// onto the parser state stack.
break;
else if (parser->process_attribute(localname, attributes, nb_attributes))
// This will push either inside_attribute, inside_attribute_container
// or inside_otherxml_attribute onto the parser state stack
break;
else
D4ParserSax2::dmr_error(
parser, "Expected an Attribute, Enumeration, Dimension, Group or variable element; found '%s' instead.",
localname);
break;
case inside_attribute_container:
if (parser->process_attribute(localname, attributes, nb_attributes))
break;
else
D4ParserSax2::dmr_error(parser, "Expected an Attribute element; found '%s' instead.", localname);
break;
case inside_attribute:
if (parser->process_attribute(localname, attributes, nb_attributes))
break;
else if (strcmp(localname, "Value") == 0)
parser->push_state(inside_attribute_value);
else
dmr_error(parser, "Expected an 'Attribute' or 'Value' element; found '%s' instead.", localname);
break;
case inside_attribute_value:
// Attribute values are processed by the end element code.
break;
case inside_other_xml_attribute:
parser->other_xml_depth++;
// Accumulate the elements here
parser->other_xml.append("<");
if (prefix) {
parser->other_xml.append((const char *)prefix);
parser->other_xml.append(":");
}
parser->other_xml.append(localname);
if (nb_namespaces != 0) {
parser->transfer_xml_ns(namespaces, nb_namespaces);
for (map<string, string>::iterator i = parser->namespace_table.begin(); i != parser->namespace_table.end();
++i) {
parser->other_xml.append(" xmlns");
if (!i->first.empty()) {
parser->other_xml.append(":");
parser->other_xml.append(i->first);
}
parser->other_xml.append("=\"");
parser->other_xml.append(i->second);
parser->other_xml.append("\"");
}
}
if (nb_attributes != 0) {
parser->transfer_xml_attrs(attributes, nb_attributes);
for (XMLAttrMap::iterator i = parser->xml_attr_begin(); i != parser->xml_attr_end(); ++i) {
parser->other_xml.append(" ");
if (!i->second.prefix.empty()) {
parser->other_xml.append(i->second.prefix);
parser->other_xml.append(":");
}
parser->other_xml.append(i->first);
parser->other_xml.append("=\"");
parser->other_xml.append(i->second.value);
parser->other_xml.append("\"");
}
}
parser->other_xml.append(">");
break;
case inside_enum_def:
// process an EnumConst element
if (parser->process_enum_const(localname, attributes, nb_attributes))
parser->push_state(inside_enum_const);
else
dmr_error(parser, "Expected an 'EnumConst' element; found '%s' instead.", localname);
break;
case inside_enum_const:
// No content; nothing to do
break;
case inside_dim_def:
// No content; nothing to do
break;
#if 0
case inside_dimension:
// No content.
break;
#endif
case inside_dim:
// No content.
break;
case inside_map:
// No content.
break;
case inside_simple_type:
if (parser->process_attribute(localname, attributes, nb_attributes))
break;
else if (parser->process_dimension(localname, attributes, nb_attributes))
parser->push_state(inside_dim);
else if (parser->process_map(localname, attributes, nb_attributes))
parser->push_state(inside_map);
else
dmr_error(parser, "Expected an 'Attribute', 'Dim' or 'Map' element; found '%s' instead.", localname);
break;
case inside_constructor:
if (parser->process_variable(localname, attributes, nb_attributes))
// This will push either inside_simple_type or inside_structure
// onto the parser state stack.
break;
else if (parser->process_attribute(localname, attributes, nb_attributes))
break;
else if (parser->process_dimension(localname, attributes, nb_attributes))
parser->push_state(inside_dim);
else if (parser->process_map(localname, attributes, nb_attributes))
parser->push_state(inside_map);
else
D4ParserSax2::dmr_error(parser, "Expected an Attribute, Dim, Map or variable element; found '%s' instead.",
localname);
break;
case not_dap4_element:
if (parser->debug())
cerr << "Inside non DAP4 element. localname: " << localname << endl;
break;
case parser_unknown:
// FIXME?
// *** Never used? If so remove/error
parser->push_state(parser_unknown);
break;
case parser_error:
case parser_fatal_error:
break;
case parser_end:
// FIXME Error?
break;
}
if (parser->debug())
cerr << "Start element exit state: " << states[parser->get_state()] << endl;
}
void D4ParserSax2::dmr_end_element(void *p, const xmlChar *l, const xmlChar *prefix, const xmlChar *URI) {
D4ParserSax2 *parser = static_cast<D4ParserSax2 *>(p);
const char *localname = (const char *)l;
if (parser->debug())
cerr << "End element " << localname << " (state " << states[parser->get_state()] << ")" << endl;
switch (parser->get_state()) {
case parser_start:
dmr_fatal_error(parser, "Unexpected state, inside start state while processing element '%s'.", localname);
break;
case inside_dataset:
if (is_not(localname, "Dataset"))
D4ParserSax2::dmr_error(parser, "Expected an end Dataset tag; found '%s' instead.", localname);
parser->pop_state();
if (parser->get_state() != parser_start)
dmr_fatal_error(parser, "Unexpected state, expected start state.");
else {
parser->pop_state();
parser->push_state(parser_end);
}
break;
case inside_group: {
if (is_not(localname, "Group"))
D4ParserSax2::dmr_error(parser, "Expected an end tag for a Group; found '%s' instead.", localname);
if (!parser->empty_basetype() || parser->empty_group())
D4ParserSax2::dmr_error(parser,
"The document did not contain a valid root Group or contained unbalanced tags.");
parser->pop_group();
parser->pop_state();
break;
}
case inside_attribute_container:
if (is_not(localname, "Attribute"))
D4ParserSax2::dmr_error(parser, "Expected an end Attribute tag; found '%s' instead.", localname);
parser->pop_state();
parser->pop_attributes();
break;
case inside_attribute:
if (is_not(localname, "Attribute"))
D4ParserSax2::dmr_error(parser, "Expected an end Attribute tag; found '%s' instead.", localname);
parser->pop_state();
break;
case inside_attribute_value: {
if (is_not(localname, "Value"))
D4ParserSax2::dmr_error(parser, "Expected an end value tag; found '%s' instead.", localname);
parser->pop_state();
// The old code added more values using the name and type as
// indexes to find the correct attribute. Use get() for that
// now. Or fix this code to keep a pointer to the to attribute...
D4Attributes *attrs = parser->top_attributes();
D4Attribute *attr = attrs->get(parser->dods_attr_name);
if (!attr) {
attr = new D4Attribute(parser->dods_attr_name, StringToD4AttributeType(parser->dods_attr_type));
attrs->add_attribute_nocopy(attr);
}
attr->add_value(parser->char_data);
parser->char_data = ""; // Null this after use.
break;
}
case inside_other_xml_attribute: {
if (strcmp(localname, "Attribute") == 0 && parser->root_ns == (const char *)URI) {
parser->pop_state();
// The old code added more values using the name and type as
// indexes to find the correct attribute. Use get() for that
// now. Or fix this code to keep a pointer to the to attribute...
D4Attributes *attrs = parser->top_attributes();
D4Attribute *attr = attrs->get(parser->dods_attr_name);
if (!attr) {
attr = new D4Attribute(parser->dods_attr_name, StringToD4AttributeType(parser->dods_attr_type));
attrs->add_attribute_nocopy(attr);
}
attr->add_value(parser->other_xml);
parser->other_xml = ""; // Null this after use.
} else {
if (parser->other_xml_depth == 0) {
D4ParserSax2::dmr_error(parser, "Expected an OtherXML attribute to end! Instead I found '%s'",
localname);
break;
}
parser->other_xml_depth--;
parser->other_xml.append("</");
if (prefix) {
parser->other_xml.append((const char *)prefix);
parser->other_xml.append(":");
}
parser->other_xml.append(localname);
parser->other_xml.append(">");
}
break;
}
case inside_enum_def:
if (is_not(localname, "Enumeration"))
D4ParserSax2::dmr_error(parser, "Expected an end Enumeration tag; found '%s' instead.", localname);
if (!parser->top_group())
D4ParserSax2::dmr_fatal_error(
parser, "Expected a Group to be the current item, while finishing up an Enumeration.");
else {
// copy the pointer; not a deep copy
parser->top_group()->enum_defs()->add_enum_nocopy(parser->enum_def());
// Set the enum_def to null; next call to enum_def() will
// allocate a new object
parser->clear_enum_def();
parser->pop_state();
}
break;
case inside_enum_const:
if (is_not(localname, "EnumConst"))
D4ParserSax2::dmr_error(parser, "Expected an end EnumConst tag; found '%s' instead.", localname);
parser->pop_state();
break;
case inside_dim_def: {
if (is_not(localname, "Dimension"))
D4ParserSax2::dmr_error(parser, "Expected an end Dimension tag; found '%s' instead.", localname);
if (!parser->top_group())
D4ParserSax2::dmr_error(parser,
"Expected a Group to be the current item, while finishing up an Dimension.");
// FIXME Use the Group on the top of the group stack
// copy the pointer; not a deep copy
parser->top_group()->dims()->add_dim_nocopy(parser->dim_def());
// parser->dmr()->root()->dims()->add_dim_nocopy(parser->dim_def());
// Set the dim_def to null; next call to dim_def() will
// allocate a new object. Calling 'clear' is important because
// the cleanup method will free dim_def if it's not null and
// we just copied the pointer in the add_dim_nocopy() call
// above.
parser->clear_dim_def();
parser->pop_state();
break;
}
case inside_simple_type:
if (is_simple_type(get_type(localname))) {
BaseType *btp = parser->top_basetype();
parser->pop_basetype();
parser->pop_attributes();
BaseType *parent = 0;
if (!parser->empty_basetype())
parent = parser->top_basetype();
else if (!parser->empty_group())
parent = parser->top_group();
else {
dmr_fatal_error(parser, "Both the Variable and Groups stacks are empty while closing a %s element.",
localname);
delete btp;
parser->pop_state();
break;
}
if (parent->type() == dods_array_c)
static_cast<Array *>(parent)->prototype()->add_var_nocopy(btp);
else
parent->add_var_nocopy(btp);
} else
D4ParserSax2::dmr_error(parser, "Expected an end tag for a simple type; found '%s' instead.", localname);
parser->pop_state();
break;
case inside_dim:
if (is_not(localname, "Dim"))
D4ParserSax2::dmr_fatal_error(parser, "Expected an end Dim tag; found '%s' instead.", localname);
parser->pop_state();
break;
case inside_map:
if (is_not(localname, "Map"))
D4ParserSax2::dmr_fatal_error(parser, "Expected an end Map tag; found '%s' instead.", localname);
parser->pop_state();
break;
case inside_constructor: {
if (strcmp(localname, "Structure") != 0 && strcmp(localname, "Sequence") != 0) {
D4ParserSax2::dmr_error(parser, "Expected an end tag for a constructor; found '%s' instead.", localname);
return;
}
BaseType *btp = parser->top_basetype();
parser->pop_basetype();
parser->pop_attributes();
BaseType *parent = 0;
if (!parser->empty_basetype())
parent = parser->top_basetype();
else if (!parser->empty_group())
parent = parser->top_group();
else {
dmr_fatal_error(parser, "Both the Variable and Groups stacks are empty while closing a %s element.",
localname);
delete btp;
parser->pop_state();
break;
}
// TODO Why doesn't this code mirror the simple_var case and test
// for the parent being an array? jhrg 10/13/13
parent->add_var_nocopy(btp);
parser->pop_state();
break;
}
case not_dap4_element:
if (parser->debug())
cerr << "End of non DAP4 element: " << localname << endl;
parser->pop_state();
break;
case parser_unknown:
parser->pop_state();
break;
case parser_error:
case parser_fatal_error:
break;
case parser_end:
// FIXME Error?
break;
}
if (parser->debug())
cerr << "End element exit state: " << states[parser->get_state()] << endl;
}
void D4ParserSax2::dmr_get_characters(void *p, const xmlChar *ch, int len) {
D4ParserSax2 *parser = static_cast<D4ParserSax2 *>(p);
switch (parser->get_state()) {
case inside_attribute_value:
parser->char_data.append((const char *)(ch), len);
DBG(cerr << "Characters: '" << parser->char_data << "'" << endl);
break;
case inside_other_xml_attribute:
parser->other_xml.append((const char *)(ch), len);
DBG(cerr << "Other XML Characters: '" << parser->other_xml << "'" << endl);
break;
default:
break;
}
}
void D4ParserSax2::dmr_ignoreable_whitespace(void *p, const xmlChar *ch, int len) {
D4ParserSax2 *parser = static_cast<D4ParserSax2 *>(p);
switch (parser->get_state()) {
case inside_other_xml_attribute:
parser->other_xml.append((const char *)(ch), len);
break;
default:
break;
}
}
void D4ParserSax2::dmr_get_cdata(void *p, const xmlChar *value, int len) {
D4ParserSax2 *parser = static_cast<D4ParserSax2 *>(p);
switch (parser->get_state()) {
case inside_other_xml_attribute:
parser->other_xml.append((const char *)(value), len);
break;
case parser_unknown:
break;
default:
D4ParserSax2::dmr_error(parser, "Found a CData block but none are allowed by DAP4.");
break;
}
}
xmlEntityPtr D4ParserSax2::dmr_get_entity(void *, const xmlChar *name) { return xmlGetPredefinedEntity(name); }
void D4ParserSax2::dmr_fatal_error(void *p, const char *msg, ...) {
va_list args;
D4ParserSax2 *parser = static_cast<D4ParserSax2 *>(p);
parser->push_state(parser_fatal_error);
va_start(args, msg);
char str[1024];
vsnprintf(str, 1024, msg, args);
va_end(args);
int line = xmlSAX2GetLineNumber(parser->d_context);
if (!parser->d_error_msg.empty())
parser->d_error_msg += "\n";
parser->d_error_msg += "At line " + long_to_string(line) + ": " + string(str);
}
void D4ParserSax2::dmr_error(void *p, const char *msg, ...) {
va_list args;
D4ParserSax2 *parser = static_cast<D4ParserSax2 *>(p);
parser->push_state(parser_error);
va_start(args, msg);
char str[1024];
vsnprintf(str, 1024, msg, args);
va_end(args);
int line = xmlSAX2GetLineNumber(parser->d_context);
if (!parser->d_error_msg.empty())
parser->d_error_msg += "\n";
parser->d_error_msg += "At line " + long_to_string(line) + ": " + string(str);
}
void D4ParserSax2::cleanup_parse() {
bool wellFormed = d_context->wellFormed;
bool valid = d_context->valid;
// d_context->sax = NULL;
xmlFreeParserCtxt(d_context);
delete d_enum_def;
d_enum_def = 0;
delete d_dim_def;
d_dim_def = 0;
// If there's an error, there may still be items on the stack at the
// end of the parse.
while (!btp_stack.empty()) {
delete top_basetype();
pop_basetype();
}
if (!wellFormed)
throw Error("The DMR was not well formed. " + d_error_msg);
else if (!valid)
throw Error("The DMR was not valid." + d_error_msg);
else if (get_state() == parser_error)
throw Error(d_error_msg);
else if (get_state() == parser_fatal_error)
throw InternalErr(d_error_msg);
}
void D4ParserSax2::intern(istream &f, DMR *dest_dmr, bool debug) {
d_debug = debug;
// Code example from libxml2 docs re: read from a stream.
if (!f.good())
throw Error("Input stream not open or read error");
if (!dest_dmr)
throw InternalErr(__FILE__, __LINE__, "DMR object is null");
d_dmr = dest_dmr; // dump values here
#if 0
int line_num = 1;
string line;
// Get the <xml ... ?> line
getline(f, line);
if (line.length() == 0) throw Error("No input found while parsing the DMR.");
if (debug) cerr << "line: (" << line_num << "): " << endl << line << endl << endl;
d_context = xmlCreatePushParserCtxt(&d_dmr_sax_parser, this, line.c_str(), line.length(), "stream");
d_context->validate = true;
push_state(parser_start);
// Get the first line of stuff
getline(f, line);
++line_num;
if (debug) cerr << "line: (" << line_num << "): " << endl << line << endl << endl;
while (!f.eof() && (get_state() != parser_end)) {
xmlParseChunk(d_context, line.c_str(), line.length(), 0);
// Get the next line
getline(f, line);
++line_num;
if (debug) cerr << "line: (" << line_num << "): " << endl << line << endl << endl;
}
// This call ends the parse.
xmlParseChunk(d_context, line.c_str(), 0, 1/*terminate*/);
#else
int line_num = 1;
string line;
// Get the XML prolog line (looks like: <?xml ... ?> )
getline(f, line);
if (line.length() == 0)
throw Error("No input found while parsing the DMR.");
if (debug)
cerr << "line: (" << line_num << "): " << endl << line << endl << endl;
d_context = xmlCreatePushParserCtxt(&d_dmr_sax_parser, this, line.c_str(), line.length(), "stream");
d_context->validate = true;
push_state(parser_start);
// Get the first chunk of the stuff
long chunk_count = 0;
long chunk_size = 0;
f.read(d_parse_buffer, D4_PARSE_BUFF_SIZE);
chunk_size = f.gcount();
d_parse_buffer[chunk_size] =
0; // null terminate the string. We can do it this way because the buffer is +1 bigger than D4_PARSE_BUFF_SIZE
if (debug)
cerr << "chunk: (" << chunk_count++ << "): " << endl << d_parse_buffer << endl << endl;
while (!f.eof() && (get_state() != parser_end)) {
xmlParseChunk(d_context, d_parse_buffer, chunk_size, 0);
// There is more to read. Get the next chunk
f.read(d_parse_buffer, D4_PARSE_BUFF_SIZE);
chunk_size = f.gcount();
d_parse_buffer[chunk_size] = 0; // null terminate the string. We can do it this way because the buffer is +1
// bigger than D4_PARSE_BUFF_SIZE
if (debug)
cerr << "chunk: (" << chunk_count++ << "): " << endl << d_parse_buffer << endl << endl;
}
// This call ends the parse.
xmlParseChunk(d_context, d_parse_buffer, chunk_size, 1 /*terminate*/);
#endif
// This checks that the state on the parser stack is parser_end and throws
// an exception if it's not (i.e., the loop exited with gcount() == 0).
cleanup_parse();
}
void D4ParserSax2::intern(const string &document, DMR *dest_dmr, bool debug) {
intern(document.c_str(), document.length(), dest_dmr, debug);
}
void D4ParserSax2::intern(const char *buffer, int size, DMR *dest_dmr, bool debug) {
if (!(size > 0))
return;
d_debug = debug;
// Code example from libxml2 docs re: read from a stream.
if (!dest_dmr)
throw InternalErr(__FILE__, __LINE__, "DMR object is null");
d_dmr = dest_dmr; // dump values in dest_dmr
push_state(parser_start);
d_context = xmlCreatePushParserCtxt(&d_dmr_sax_parser, this, buffer, size, "stream");
d_context->validate = true;
// This call ends the parse.
xmlParseChunk(d_context, buffer, 0, 1 /*terminate*/);
// This checks that the state on the parser stack is parser_end and throws
// an exception if it's not (i.e., the loop exited with gcount() == 0).
cleanup_parse();
}
} // namespace libdap
libdap::D4Attributes::get
D4Attribute * get(const string &fqn)
Definition D4Attributes.cc:444
libdap::D4Dimension
Definition D4Dimensions.h:41
libdap::D4Dimensions::add_dim_nocopy
void add_dim_nocopy(D4Dimension *dim)
Definition D4Dimensions.h:163
libdap::D4EnumDef
Definition D4EnumDefs.h:42
libdap::D4Group::dims
D4Dimensions * dims()
Get the dimensions defined for this Group.
Definition D4Group.h:84
libdap::D4Group::enum_defs
D4EnumDefs * enum_defs()
Get the enumerations defined for this Group.
Definition D4Group.h:100
libdap::D4ParserSax2
Definition D4ParserSax2.h:76
libdap::D4ParserSax2::intern
void intern(istream &f, DMR *dest_dmr, bool debug=false)
Definition D4ParserSax2.cc:1251
libdap::DMR
Definition DMR.h:56
libdap::DMR::set_dap_version
void set_dap_version(const std::string &version_string)
Definition DMR.cc:239
libdap::DMR::root
D4Group * root()
Definition DMR.cc:228
libdap::DMR::set_namespace
void set_namespace(const std::string &ns)
Set the namespace for this DMR.
Definition DMR.h:164
libdap::DMR::set_request_xml_base
void set_request_xml_base(const std::string &xb)
Definition DMR.h:158
libdap::DapXmlNamspaces::getDapNamespaceString
static std::string getDapNamespaceString(DAPVersion version)
Definition DapXmlNamespaces.h:81
libdap::Error
A class for error processing.
Definition Error.h:92
libdap::InternalErr
A class for software fault reporting.
Definition InternalErr.h:61
libdap::D4ParserSax2::dmr_start_document
static void dmr_start_document(void *parser)
Definition D4ParserSax2.cc:559
libdap::D4ParserSax2::dmr_start_element
static void dmr_start_element(void *parser, const xmlChar *localname, const xmlChar *prefix, const xmlChar *URI, int nb_namespaces, const xmlChar **namespaces, int nb_attributes, int nb_defaulted, const xmlChar **attributes)
Definition D4ParserSax2.cc:612
libdap::D4ParserSax2::dmr_ignoreable_whitespace
static void dmr_ignoreable_whitespace(void *parser, const xmlChar *ch, int len)
Definition D4ParserSax2.cc:1115
libdap::D4ParserSax2::dmr_get_characters
static void dmr_get_characters(void *parser, const xmlChar *ch, int len)
Definition D4ParserSax2.cc:1092
libdap::D4ParserSax2::dmr_end_document
static void dmr_end_document(void *parser)
Definition D4ParserSax2.cc:577
libdap::D4ParserSax2::dmr_get_cdata
static void dmr_get_cdata(void *parser, const xmlChar *value, int len)
Definition D4ParserSax2.cc:1133
libdap::D4ParserSax2::dmr_get_entity
static xmlEntityPtr dmr_get_entity(void *parser, const xmlChar *name)
Definition D4ParserSax2.cc:1155
libdap::D4ParserSax2::dmr_fatal_error
static void dmr_fatal_error(void *parser, const char *msg,...)
Definition D4ParserSax2.cc:1167
libdap
top level DAP object to house generic methods
Definition AlarmHandler.h:35
libdap::Type
Type
Identifies the data type.
Definition Type.h:94
libdap::is_simple_type
virtual bool is_simple_type() const
Returns true if the instance is a numeric, string or URL type variable.
Definition BaseType.cc:347
libdap::D4type_name
string D4type_name(Type t)
Returns the type of the class instance as a string. Supports all DAP4 types and not the DAP2-only typ...
Definition util.cc:688
libdap::is_integer_type
bool is_integer_type(Type t)
Definition util.cc:888
libdap::is_vector_type
virtual bool is_vector_type() const
Returns true if the instance is a vector (i.e., array) type variable.
Definition BaseType.cc:352
libdap::value
virtual D4SeqValues value() const
Get the values for this D4Sequence This method returns a reference to the values held by the instance...
Definition D4Sequence.h:281
libdap::get_type
ObjectType get_type(const string &value)
Definition mime_util.cc:300