diff --git a/D4ParserSax2.cc b/D4ParserSax2.cc index c9b7647f3..2951e1ad6 100644 --- a/D4ParserSax2.cc +++ b/D4ParserSax2.cc @@ -43,6 +43,7 @@ #include "D4ParserSax2.h" #include "DapXmlNamespaces.h" +#include "cerealization_patch.h" #include "debug.h" #include "util.h" @@ -651,6 +652,10 @@ void D4ParserSax2::dmr_start_element(void *p, const xmlChar *l, const xmlChar *p if (parser->check_attribute("dmrVersion")) parser->dmr()->set_dmr_version(parser->xml_attrs["dmrVersion"].value); + // TODO - Do we ever need/want to read serialization flag into a DMR state variable? (I don't think so...) + // if (parser->check_attribute(CEREALIZATION_PATCH_ATTR_NAME)) + // parser->dmr()->set_serialization(parser->xml_attrs[CEREALIZATION_PATCH_ATTR_NAME].value); + if (parser->check_attribute("base")) parser->dmr()->set_request_xml_base(parser->xml_attrs["base"].value); diff --git a/DDS.cc b/DDS.cc index 03e58c37b..7beb1c4ee 100644 --- a/DDS.cc +++ b/DDS.cc @@ -71,6 +71,7 @@ #include "DapIndent.h" #include "Error.h" #include "InternalErr.h" +#include "cerealization_patch.h" #include "debug.h" #include "escaping.h" #include "parser.h" @@ -1225,7 +1226,7 @@ void DDS::print_xml_writer(ostream &out, bool constrained, const string &blob) { * @param out Write the XML to this stream * @param constrained Should the DMR be subject to a constraint? */ -void DDS::print_dmr(ostream &out, bool constrained) { +void DDS::print_dmr(ostream &out, bool constrained, bool add_serialization_attr) { if (get_dap_major() < 4) throw InternalErr(__FILE__, __LINE__, "Tried to print a DMR with DAP major version less than 4"); @@ -1257,7 +1258,11 @@ void DDS::print_dmr(ostream &out, bool constrained) { if (xmlTextWriterWriteAttribute(xml.get_writer(), (const xmlChar *)"dmrVersion", (const xmlChar *)get_dmr_version().c_str()) < 0) - throw InternalErr(__FILE__, __LINE__, "Could not write attribute for dapVersion"); + throw InternalErr(__FILE__, __LINE__, "Could not write attribute for dmrVersion"); + + if (add_serialization_attr) { + add_serialization_patch_attribute(xml, get_namespace()); + } if (!get_request_xml_base().empty()) { if (xmlTextWriterWriteAttribute(xml.get_writer(), (const xmlChar *)"xml:base", diff --git a/DDS.h b/DDS.h index 35de784d2..feed168ba 100644 --- a/DDS.h +++ b/DDS.h @@ -286,6 +286,10 @@ class DDS : public DapObj { /** @brief Returns the DAP4 DMR version corresponding to DDS exports. */ string get_dmr_version() const { return "1.0"; } + // TODO - Do we need a state variable for the serialization flag? (I don't think so...) + /** @brief Returns the DAP4 data serialization scheme. */ + // string get_serialization() const { return "4.0"; } + /// @deprecated void set_dap_major(int p); /// @deprecated @@ -415,7 +419,7 @@ class DDS : public DapObj { void print_xml_writer(ostream &out, bool constrained, const string &blob = ""); // Print the DAP4 DMR 'object' - void print_dmr(ostream &out, bool constrained); + void print_dmr(ostream &out, bool constrained, bool add_serialization_attr = false); void print_das(ostream &out); DAS *get_das(); diff --git a/DMR.cc b/DMR.cc index 9337618ac..2b7309a8d 100644 --- a/DMR.cc +++ b/DMR.cc @@ -46,6 +46,7 @@ #include "DDS.h" // Included so DMRs can be built using a DDS for 'legacy' handlers #include "DapIndent.h" +#include "cerealization_patch.h" #include "debug.h" using namespace std; @@ -69,6 +70,9 @@ void DMR::m_duplicate(const DMR &dmr) { d_dmr_version = dmr.d_dmr_version; + // TODO - Do we need a state variable for the serialization flag? (I don't think so...) + // d_serialization = dmr.d_serialization; + d_request_xml_base = dmr.d_request_xml_base; d_namespace = dmr.d_namespace; @@ -311,8 +315,10 @@ uint64_t DMR::request_size_kb(bool constrained) { return d_root->request_size_kb * @param xml use this XMLWriter to build the XML. * @param constrained Should the DMR be subject to a constraint? Defaults to * False + * @param add_serialization_attr Add the servers serialization version + * attribute to the Dataset element */ -void DMR::print_dap4(XMLWriter &xml, bool constrained) { +void DMR::print_dap4(XMLWriter &xml, bool constrained, bool add_serialization_attr) { if (xmlTextWriterStartElement(xml.get_writer(), (const xmlChar *)"Dataset") < 0) throw InternalErr(__FILE__, __LINE__, "Could not write Dataset element"); @@ -332,7 +338,11 @@ void DMR::print_dap4(XMLWriter &xml, bool constrained) { if (xmlTextWriterWriteAttribute(xml.get_writer(), (const xmlChar *)"dmrVersion", (const xmlChar *)dmr_version().c_str()) < 0) - throw InternalErr(__FILE__, __LINE__, "Could not write attribute for dapVersion"); + throw InternalErr(__FILE__, __LINE__, "Could not write attribute for dmrVersion"); + + if (add_serialization_attr) { + add_serialization_patch_attribute(xml, get_namespace()); + } if (xmlTextWriterWriteAttribute(xml.get_writer(), (const xmlChar *)"name", (const xmlChar *)name().c_str()) < 0) throw InternalErr(__FILE__, __LINE__, "Could not write attribute for name"); diff --git a/DMR.h b/DMR.h index 6464b0daf..cf9edf6c6 100644 --- a/DMR.h +++ b/DMR.h @@ -76,6 +76,16 @@ class DMR : public DapObj { /// The 2.0 version indicates the DAP4 Serialization bug fix. std::string d_dmr_version = "2.0"; + /// The version of the DAP4 serialization used to encode the data response values. + /// Prior to adding this indicator variable and its expression in the DMR + /// a non-dap4 compliant serialization scheme, in which Groups were serialized first + /// and then the top-level variables was implemented. + /// We mended this problem with dap4 serialization add added this state + /// variable along with a Dataset xml attribute dap:serialization to express this state. + /// The original patch changed the d_dmr_version to 2.0 - ndp 4/14/26 + // TODO - Do we need a DMR state variable for the serialization flag? (I don't think so...) + // std::string d_serialization = "4.0"; + /// The URL for the request base std::string d_request_xml_base; @@ -179,6 +189,19 @@ class DMR : public DapObj { */ void set_dmr_version(const std::string &v) { d_dmr_version = v; } + /** + * @brief Returns the DAP4 serialization that the source service will deliver. + * TODO - Do we need a DMR state variable for the serialization flag? (I don't think so...) + */ + // std::string serialization() const { return d_serialization; } + + /** + * @brief Sets the DAP4 serialization string. + * TODO - Do we need a DMR state variable for the serialization flag? (I don't think so...) + * @param v DAP4 serialization value. + */ + // void set_serialization(const std::string &v) { d_serialization = v; } + /// Get the URL that will return this DMR std::string request_xml_base() const { return d_request_xml_base; } @@ -255,7 +278,8 @@ class DMR : public DapObj { virtual bool is_dap4_projected(std::vector &inventory); - void print_dap4(XMLWriter &xml, bool constrained = false); + // void print_dap4(XMLWriter &xml, bool constrained = false){print_dap4(xml,constrained,false);}; + void print_dap4(XMLWriter &xml, bool constrained = false, bool add_serialization_attr = false); void dump(std::ostream &strm) const override; diff --git a/Makefile.am b/Makefile.am index 4bbc903ce..cecfd1245 100644 --- a/Makefile.am +++ b/Makefile.am @@ -218,7 +218,7 @@ DAP4_ONLY_HDR = D4StreamMarshaller.h D4StreamUnMarshaller.h Int64.h \ D4Maps.h D4Dimensions.h D4EnumDefs.h D4Group.h DMR.h D4Attributes.h \ D4AttributeType.h D4Enum.h chunked_stream.h chunked_ostream.h \ chunked_istream.h D4Sequence.h crc.h D4Opaque.h D4AsyncUtil.h \ - D4Function.h D4RValue.h D4FilterClause.h + D4Function.h D4RValue.h D4FilterClause.h cerealization_patch.h if USE_C99_TYPES dods-datatypes.h: dods-datatypes-static.h diff --git a/cerealization_patch.h b/cerealization_patch.h new file mode 100644 index 000000000..777a30fce --- /dev/null +++ b/cerealization_patch.h @@ -0,0 +1,65 @@ +// -*- mode: c++; c-basic-offset:4 -*- + +// This file is part of libdap, A C++ implementation of the OPeNDAP Data +// Access Protocol. + +// Copyright (c) 2026 OPeNDAP, Inc. +// Author: NAthan Potter +// +// 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. +// +// cerealization_patch.h +// + +#ifndef CEREALIZATION_PATCH_H +#define CEREALIZATION_PATCH_H + +#define CEREALIZATION_PATCH_ATTR_NAME "hyrax_dap" +#define CEREALIZATION_PATCH_ATTR_VALUE "4.0" + +#include "InternalErr.h" +#include "XMLWriter.h" +namespace libdap { + +/** + * + * Adds the serialization patch attribute to the current element. + * @param xml + * @param dap4_namespace_name + */ +static void add_serialization_patch_attribute(libdap::XMLWriter &xml, const string &) { + + /* + TODO - If we don't need a namespace prefix we can drop this bit. + if (!dap4_namespace_name.empty()) { + // Oddly, the dap namespace prefix is not defined by default. We have to add it to + // namespace prefix our special attribute. + if (xmlTextWriterWriteAttribute(xml.get_writer(), (const xmlChar *)"xmlns:dap", + (const xmlChar *)dap4_namespace_name.c_str()) < 0) + throw libdap::InternalErr(__FILE__, __LINE__, "Could not write attribute for xmlns:dap"); + } + */ + + // TODO - Is this really just a hyrax thing? Rethink this attribute name! + if (xmlTextWriterWriteAttribute(xml.get_writer(), (const xmlChar *)CEREALIZATION_PATCH_ATTR_NAME, + (const xmlChar *)CEREALIZATION_PATCH_ATTR_VALUE) < 0) + throw libdap::InternalErr(__FILE__, __LINE__, "Could not write attribute for " CEREALIZATION_PATCH_ATTR_NAME); +} + +} // namespace libdap + +#endif // CEREALIZATION_PATCH_H diff --git a/getdap4.cc b/getdap4.cc index 2e9b9cfd6..dd35249b9 100644 --- a/getdap4.cc +++ b/getdap4.cc @@ -341,7 +341,7 @@ void read_local_dap4(D4Connect &url, const string &name, const bool get_dmr_flag // Always write the DMR XMLWriter xml; - dmr.print_dap4(xml); + dmr.print_dap4(xml, false, true); cout << xml.get_doc() << endl; if (get_data_flag) diff --git a/tests/D4ResponseBuilder.cc b/tests/D4ResponseBuilder.cc index ac4cae2bb..8113fab68 100644 --- a/tests/D4ResponseBuilder.cc +++ b/tests/D4ResponseBuilder.cc @@ -135,7 +135,7 @@ void D4ResponseBuilder::send_dap(ostream &out, DMR &dmr, bool with_mime_headers, // Write the DMR XMLWriter xml; - dmr.print_dap4(xml, constrained); + dmr.print_dap4(xml, constrained, true); // now make the chunked output stream; set the size to be at least chunk_size // but make sure that the whole of the xml plus the CRLF can fit in the first diff --git a/unit-tests/DMRTest.cc b/unit-tests/DMRTest.cc index 0312882d7..a48232590 100644 --- a/unit-tests/DMRTest.cc +++ b/unit-tests/DMRTest.cc @@ -131,10 +131,15 @@ class DMRTest : public TestFixture { dmr = build_dmr(dds_file, attr); XMLWriter xml; dmr->print_dap4(xml); - DBG(cerr << "DMR: " << endl << xml.get_doc() << endl); + string dmr_doc(xml.get_doc()); + DBG(cerr << "DMR: " << endl << dmr_doc << endl); - string prefix = string(TEST_SRC_DIR) + "/dmr-testsuite/"; - CPPUNIT_ASSERT(string(xml.get_doc()) == read_test_baseline(prefix + dmr_baseline)); + string baseline_file = string(TEST_SRC_DIR) + "/dmr-testsuite/" + dmr_baseline; + DBG(cerr << "baseline_file: " << baseline_file << endl); + string baseline = read_test_baseline(baseline_file); + DBG(cerr << "BASELINE: " << endl << baseline << endl); + + CPPUNIT_ASSERT(dmr_doc == baseline); delete dmr; } catch (Error &e) { delete dmr;