bes Updated for version 3.20.13
AttributeElement.cc
1
2// This file is part of the "NcML Module" project, a BES module designed
3// to allow NcML files to be used to be used as a wrapper to add
4// AIS to existing datasets of any format.
5//
6// Copyright (c) 2009 OPeNDAP, Inc.
7// Author: Michael Johnson <m.johnson@opendap.org>
8//
9// For more information, please also see the main website: http://opendap.org/
10//
11// This library is free software; you can redistribute it and/or
12// modify it under the terms of the GNU Lesser General Public
13// License as published by the Free Software Foundation; either
14// version 2.1 of the License, or (at your option) any later version.
15//
16// This library is distributed in the hope that it will be useful,
17// but WITHOUT ANY WARRANTY; without even the implied warranty of
18// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
19// Lesser General Public License for more details.
20//
21// You should have received a copy of the GNU Lesser General Public
22// License along with this library; if not, write to the Free Software
23// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
24//
25// Please see the files COPYING and COPYRIGHT for more information on the GLPL.
26//
27// You can contact OPeNDAP, Inc. at PO Box 112, Saunderstown, RI. 02874-0112.
29
30#include <libdap/DDS.h> // Needed for a test of the dds version
31
32#include "AttributeElement.h"
33#include "NCMLDebug.h"
34#include "NCMLParser.h"
35#include "NCMLUtil.h"
36#include "OtherXMLParser.h"
37
38// This controls whether global attributes are added to a special container.
39// See below...
40#define USE_NC_GLOBAL_CONTAINER 0
41
42namespace ncml_module {
43const string AttributeElement::_sTypeName = "attribute";
44const vector<string> AttributeElement::_sValidAttributes = getValidAttributes();
45#if 0
46const string AttributeElement::_default_global_container = "NC_GLOBAL";
47#endif
48
49AttributeElement::AttributeElement()
50 : NCMLElement(0), _name(""), _type(""), _value(""), _separator(NCMLUtil::WHITESPACE), _orgName(""), _tokens(),
51 _pOtherXMLParser(0)
52{
53 _tokens.reserve(256); // not sure what a good number is, but better than resizing all the time.
54}
55
56AttributeElement::AttributeElement(const AttributeElement &proto)
57 : RCObjectInterface(), NCMLElement(proto)
58{
59 _name = proto._name;
60 _type = proto._type;
61 _value = proto._value;
62 _separator = proto._separator;
63 _orgName = proto._orgName;
64 _tokens = proto._tokens; // jhrg 3/16/11
65 _pOtherXMLParser = 0;
66}
67
68AttributeElement::~AttributeElement()
69{
70 delete _pOtherXMLParser;
71}
72
73const string &
74AttributeElement::getTypeName() const
75{
76 return _sTypeName;
77}
78
80AttributeElement::clone() const
81{
82 return new AttributeElement(*this);
83}
84
85void
86AttributeElement::setAttributes(const XMLAttributeMap &attrs)
87{
88 _name = attrs.getValueForLocalNameOrDefault("name");
89 _type = attrs.getValueForLocalNameOrDefault("type");
90 _value = attrs.getValueForLocalNameOrDefault("value");
91 _separator = attrs.getValueForLocalNameOrDefault("separator");
92 _orgName = attrs.getValueForLocalNameOrDefault("orgName");
93
94 validateAttributes(attrs, _sValidAttributes);
95}
96
97void
98AttributeElement::handleBegin()
99{
100 processAttribute(*_parser);
101}
102
103void
104AttributeElement::handleContent(const string &content)
105{
106 // We should know if it's valid here, but double check with parser.
107 if (_parser->isScopeAtomicAttribute()) {
108 BESDEBUG("ncml2", "Adding attribute values as characters content for atomic attribute=" << _name <<
109 " value=\"" << content
110 << "\"" << endl);
111 _value = content; // save the content unless we end the element, then we'll set it.
112 }
113 // Otherwise, it better be whitespace
114 else if (!NCMLUtil::isAllWhitespace(content)) {
115 THROW_NCML_PARSE_ERROR(_parser->getParseLineNumber(),
116 "Got characters content for a non-atomic attribute!"
117 " attribute@value is not allowed for attribute@type=Structure!");
118 }
119}
120
121void
122AttributeElement::handleEnd()
123{
124 processEndAttribute(*_parser);
125}
126
127string
128AttributeElement::toString() const
129{
130 string ret = "<" + _sTypeName + " ";
131
132 ret += "name=\"" + _name + "\"";
133
134 if (!_type.empty()) {
135 ret += " type=\"" + _type + "\" ";
136 }
137
138 if (_separator != NCMLUtil::WHITESPACE) {
139 ret += " separator=\"" + _separator + "\" ";
140 }
141
142 if (!_orgName.empty()) {
143 ret += " orgName=\"" + _orgName + "\" ";
144 }
145
146 if (!_value.empty()) {
147 ret += " value=\"" + _value + "\" ";
148 }
149
150 ret += ">";
151 return ret;
152}
153
154
157
158void
159AttributeElement::processAttribute(NCMLParser &p)
160{
161 BESDEBUG("ncml2", "handleBeginAttribute called for attribute name=" << _name << endl);
162
163 // Make sure we're in a netcdf and then process the attribute at the current table scope,
164 // which could be anywhere including glboal attributes, nested attributes, or some level down a variable tree.
165 if (!p.withinNetcdf()) {
166 THROW_NCML_PARSE_ERROR(_parser->getParseLineNumber(),
167 "Got <attribute> element while not within a <netcdf> node!");
168 }
169
170 if (p.isScopeAtomicAttribute()) {
171 THROW_NCML_PARSE_ERROR(
172 _parser->getParseLineNumber(),
173 "Got new <attribute> while in a leaf <attribute> at scope=" + p.getScopeString() +
174 " Hierarchies of attributes are only allowed for attribute containers with type=Structure");
175 }
176
177 // Convert the NCML type to a canonical type here.
178 // "Structure" will remain as "Structure" for specialized processing.
179 string internalType = p.convertNcmlTypeToCanonicalType(_type);
180 if (internalType.empty()) {
181 THROW_NCML_PARSE_ERROR(
182 _parser->getParseLineNumber(),
183 "Unknown NCML type=" + _type + " for attribute name=" + _name + " at scope=" + p.getScopeString());
184 }
185
186 p.printScope();
187
188 // First, if the type is a Structure, we are dealing with nested attributes and need to handle it separately.
189 if (_type == NCMLParser::STRUCTURE_TYPE) {
190 BESDEBUG("ncml2", "Processing an attribute element with type Structure." << endl);
191 processAttributeContainerAtCurrentScope(p);
192 }
193 else // It's atomic, so look it up in the current attr table and add a new one or mutate an existing one.
194 {
195 processAtomicAttributeAtCurrentScope(p);
196 }
197}
198
199void
200AttributeElement::processAtomicAttributeAtCurrentScope(NCMLParser &p)
201{
202
203 // If no orgName, just process with name.
204 if (_orgName.empty()) {
205 if (p.attributeExistsAtCurrentScope(_name)) {
206 BESDEBUG("ncml", "Found existing attribute named: " << _name << " with type=" << _type << " at scope=" <<
207 p.getScopeString() << endl);
208 // We set this when the element closes now!
209 // mutateAttributeAtCurrentScope(p, _name, _type, _value);
210 }
211 else {
212 BESDEBUG("ncml", "Didn't find attribute: " << _name << " so adding it with type=" << _type << " and value="
213 << _value << endl);
214 addNewAttribute(p);
215 }
216 }
217
218 else // if orgName then we want to rename an existing attribute, handle that separately
219 {
220 renameAtomicAttribute(p);
221 }
222
223 // If it's of type OtherXML, we need to set a proxy parser.
224 if (_type == "OtherXML") {
225 startOtherXMLParse(p);
226 }
227
228 // In all cases, also push the scope on the stack in case we get values as content.
229 p.enterScope(_name, ScopeStack::ATTRIBUTE_ATOMIC);
230}
231
232void
233AttributeElement::processAttributeContainerAtCurrentScope(NCMLParser &p)
234{
235 NCML_ASSERT_MSG(_type == NCMLParser::STRUCTURE_TYPE,
236 "Logic error: processAttributeContainerAtCurrentScope called with non Structure type.");
237 BESDEBUG("ncml", "Processing attribute container with name:" << _name << endl);
238
239 // Technically it's an error to have a value for a container, so just check and warn.
240 if (!_value.empty()) {
241 THROW_NCML_PARSE_ERROR(_parser->getParseLineNumber(),
242 "Found non empty() value attribute for attribute container at scope=" +
243 p.getTypedScopeString());
244 }
245
246 // Make sure we're in a valid context.
247 VALID_PTR(p.getCurrentAttrTable());
248
249 AttrTable *pAT = 0;
250 // If we're supposed to rename.
251 if (!_orgName.empty()) {
252 pAT = renameAttributeContainer(p);
253 VALID_PTR(pAT); // this should never be null. We throw exceptions for parse errors.
254 }
255 else // Not renaming, either new one or just a scope specification.
256 {
257 AttrTable *pCurrentTable = p.getCurrentAttrTable();
258
259 // See if the attribute container already exists in current scope.
260 pAT = pCurrentTable->simple_find_container(_name);
261 if (!pAT) // doesn't already exist
262 {
263 // So create a new one if the name is free (ie no variable...)
264 if (p.getVariableInCurrentVariableContainer(_name)) {
265 THROW_NCML_PARSE_ERROR(line(),
266 "Cannot create a new attribute container with name=" + _name +
267 " at current scope since a variable with that name already exists. Scope=" +
268 p.getScopeString());
269 }
270
271 // If it is free, go ahead and add it.
272 pAT = pCurrentTable->append_container(_name);
273 BESDEBUG("ncml", "Attribute container was not found, creating new one name=" << _name << " at scope="
274 << p.getScopeString() << endl);
275 }
276 else {
277 BESDEBUG("ncml",
278 "Found an attribute container name=" << _name << " at scope=" << p.getScopeString() << endl);
279 }
280 }
281
282 // No matter how we get here, pAT is now the new scope, so push it under it's name
283 VALID_PTR(pAT);
284 p.setCurrentAttrTable(pAT);
285 p.enterScope(pAT->get_name(), ScopeStack::ATTRIBUTE_CONTAINER);
286}
287
288string
289AttributeElement::getInternalType() const
290{
291 return NCMLParser::convertNcmlTypeToCanonicalType(_type);
292}
293
294void
295AttributeElement::addNewAttribute(NCMLParser &p)
296{
297 VALID_PTR(p.getCurrentAttrTable());
298
299 string internalType = getInternalType();
300
301 // OtherXML cannot be vector, only scalar, so enforce that.
302 if (internalType != "OtherXML") {
303 // Split the value string properly if the type is one that can be a vector.
304 p.tokenizeAttrValues(_tokens, _value, internalType, _separator);
305 BESDEBUG("ncml2", "Adding the attribute '" << _name << "' to the current table" << endl);
306 BESDEBUG("ncml2", "The Current attribute table is at: '" << p.getCurrentAttrTable() << "'" << endl);
307 p.getCurrentAttrTable()->append_attr(_name, internalType, &(_tokens));
308 }
309 else // if we are OtherXML
310 {
311 // At this point, we expect the value to be null. It will show up in content...
312 BESDEBUG("ncml", "Addinng new attribute of type OtherXML data." << endl);
313 if (!_value.empty()) {
314 THROW_NCML_PARSE_ERROR(_parser->getParseLineNumber(),
315 "Adding new Attribute of type=OtherXML: Cannot specify"
316 " an attribute@value for OtherXML --- it must be set in the content! Scope was: "
317 + p.getScopeString());
318 }
319
320 p.getCurrentAttrTable()->append_attr(_name, internalType, _value);
321 }
322}
323
324void
325AttributeElement::mutateAttributeAtCurrentScope(NCMLParser &p, const string &name, const string &type,
326 const string &value)
327{
328 AttrTable *pTable = p.getCurrentAttrTable();
329 VALID_PTR(pTable);
330 NCML_ASSERT_MSG(p.attributeExistsAtCurrentScope(name),
331 "Logic error. mutateAttributeAtCurrentScope called when attribute name=" + name +
332 " didn't exist at scope=" + p.getTypedScopeString());
333
334 // First, pull out the existing attribute's type if unspecified.
335 string actualType = type;
336 if (type.empty()) {
337 actualType = pTable->get_type(name);
338 }
339
340 // Make sure to turn it into internal DAP type for tokenize and storage
341 actualType = p.convertNcmlTypeToCanonicalType(actualType);
342
343 // Can't mutate, so just delete and reenter it. This move change the ordering... Do we care?
344 pTable->del_attr(name);
345
346 // Split the values if needed, again avoiding OtherXML being tokenized since it's a scalar by definition.
347 if (actualType == "OtherXML") {
348 BESDEBUG("ncml_attr", "Setting OtherXML data to: " << endl << _value << endl);
349 pTable->append_attr(name, actualType, _value);
350 }
351 else {
352 p.tokenizeAttrValues(_tokens, value, actualType, _separator);
353#if USE_NC_GLOBAL_CONTAINER
354 // If the NCML handler is adding an
355 // attribute to the top level AttrTable, that violates a rule of the
356 // DAP2 spec which says that the top level attribute object has only
357 // containers. In the case that this code tries to add an attribute
358 // to a top level container, we add it instead to a container named
359 // NC_GLOBAL. If that container does not exist, we create it. I used
360 // NC_GLOBAL (and not NCML_GLOBAL) because the TDS uses that name.
361 // 2/9/11 jhrg
362
363 // NOTE: It seems like this should be above in addNewAttribute, but that
364 // will break the parse later on because of some kind of mismatch
365 // between the contents of the AttrTable and the scope stack. I could
366 // push a new thing on the scope stack, but that might break things
367 // elsewhere. If we _did_ do that, then we could use isScopeGlobal()
368 // to test for global attributes.
369
370 BESDEBUG("ncml_attr", "mutateAttributeAtCurrentScope: Looking at table: " << pTable->get_name() << endl);
371 BESDEBUG("ncml_attr", "Looking at attribute named: " << _name << endl);
372 BESDEBUG("ncml_attr", "isScopeGlobal(): " << p.isScopeGlobal() << endl);
373 BESDEBUG("ncml_attr", "isScopeNetcdf(): " << p.isScopeNetcdf() << endl);
374 BESDEBUG("ncml_attr", "isScopeAtomicAttribute(): " << p.isScopeAtomicAttribute() << endl);
375 BESDEBUG("ncml_attr", "isScopeAttributeContainer(): " << p.isScopeAttributeContainer() << endl);
376 BESDEBUG("ncml_attr", "isScopeVariable(): " << p.isScopeVariable() << endl);
377 BESDEBUG("ncml_attr", "getTypedScopeString(): " << p.getTypedScopeString() << endl);
378 BESDEBUG("ncml_attr", "getScopeDepth(): " << p.getScopeDepth() << endl);
379 BESDEBUG("ncml_attr", "DAP version: " << p.getDDSForCurrentDataset()->get_dap_major() << "." << p.getDDSForCurrentDataset()->get_dap_minor() << endl);
380
381 // Note that in DAP4 we are allowed to have top level attributes. This
382 // change was made so that Structure and Dataset are closer to one
383 // another. jhrg
384 if (p.getScopeDepth() < 2 && p.getDDSForCurrentDataset()->get_dap_major() < 4)
385 {
386 BESDEBUG("ncml_attr", "There's no parent container, looking for " << _default_global_container << "..." << endl);
387 // Using the getDDSForCurrentDataset's attr table is no different
388 // than using pTable. 2/22/11
389 //AttrTable &gat = p.getDDSForCurrentDataset()->get_attr_table();
390 //AttrTable *at = gat.find_container(_default_global_container);
391 AttrTable *at = pTable->find_container(_default_global_container);
392 if (!at)
393 {
394 BESDEBUG("ncml_attr", " not found; adding." << endl);
395 at = pTable->append_container(_default_global_container);
396 }
397 else
398 {
399 BESDEBUG("ncml_attr", " found; using" << endl);
400 }
401
402 at->append_attr(_name, actualType, &(_tokens));
403 }
404 else
405 {
406 BESDEBUG("ncml_attr", "Found parent container..." << endl);
407 pTable->append_attr(_name, actualType, &(_tokens));
408 }
409#else
410 pTable->append_attr(name, actualType, &(_tokens));
411#endif
412 }
413}
414
415void
416AttributeElement::renameAtomicAttribute(NCMLParser &p)
417{
418 AttrTable *pTable = p.getCurrentAttrTable();
419 VALID_PTR(pTable);
420
421 // Check for user errors
422 if (!p.attributeExistsAtCurrentScope(_orgName)) {
423 THROW_NCML_PARSE_ERROR(_parser->getParseLineNumber(),
424 "Failed to change name of non-existent attribute with orgName=" + _orgName +
425 " and new name=" + _name + " at the current scope=" + p.getScopeString());
426 }
427
428 // If the name we're renaming to already exists, we'll assume that's an error as well, since the user probably
429 // wants to know this
430 if (p.isNameAlreadyUsedAtCurrentScope(_name)) {
431 THROW_NCML_PARSE_ERROR(_parser->getParseLineNumber(),
432 "Failed to change name of existing attribute orgName=" + _orgName +
433 " because an attribute or variable with the new name=" + _name +
434 " already exists at the current scope=" + p.getScopeString());
435 }
436
437 AttrTable::Attr_iter it;
438 bool gotIt = p.findAttribute(_orgName, it);
439 NCML_ASSERT(gotIt); // logic bug check, we check above
440
441 // Just to be safe... we shouldn't get down here if it is, but we can't proceed otherwise.
442 NCML_ASSERT_MSG(!pTable->is_container(it),
443 "LOGIC ERROR: renameAtomicAttribute() got an attribute container where it expected an atomic attribute!");
444
445 // Copy the entire vector explicitly here!
446 vector<string> *pAttrVec = pTable->get_attr_vector(it);
447 NCML_ASSERT_MSG(pAttrVec, "Unexpected NULL from get_attr_vector()");
448 // Copy it!
449 vector<string> orgData = *pAttrVec;
450 AttrType orgType = pTable->get_attr_type(it);
451
452 // Delete the old one
453 pTable->del_attr(_orgName);
454
455 // Hmm, what to do if the types are different? I'd say use the new one....
456 string typeToUse = AttrType_to_String(orgType);
457 if (!_type.empty() && _type != typeToUse) {
458 BESDEBUG("ncml", "Warning: renameAtomicAttribute(). New type did not match old type, using new type." << endl);
459 typeToUse = _type;
460 }
461
462 // We'll record the type for the rename as well, for setting the data in the end element.
463 _type = typeToUse;
464
465 pTable->append_attr(_name, typeToUse, &orgData);
466
467 // If value was specified, let's go call mutate on the thing we just made to change the data. Seems
468 // odd a user would do this, but it's allowable I think.
469 if (!_value.empty()) {
470 mutateAttributeAtCurrentScope(p, _name, typeToUse, _value);
471 }
472}
473
474AttrTable *
475AttributeElement::renameAttributeContainer(NCMLParser &p)
476{
477 AttrTable *pTable = p.getCurrentAttrTable();
478 VALID_PTR(pTable);
479 AttrTable *pAT = pTable->simple_find_container(_orgName);
480 if (!pAT) {
481 THROW_NCML_PARSE_ERROR(line(),
482 "renameAttributeContainer: Failed to find attribute container with orgName=" + _orgName +
483 " at scope=" + p.getScopeString());
484 }
485
486 if (p.isNameAlreadyUsedAtCurrentScope(_name)) {
487 THROW_NCML_PARSE_ERROR(_parser->getParseLineNumber(),
488 "Renaming attribute container with orgName=" + _orgName +
489 " to new name=" + _name +
490 " failed since an attribute or variable already exists with that name at scope=" +
491 p.getScopeString());
492 }
493
494 BESDEBUG("ncml", "Renaming attribute container orgName=" << _orgName << " to name=" << _name << " at scope="
495 << p.getTypedScopeString() << endl);
496
497 // Just changing the name doesn't work because of how AttrTable stores names, so we need to remove and readd it under new name.
498 AttrTable::Attr_iter it;
499 bool gotIt = p.findAttribute(_orgName, it);
500 NCML_ASSERT_MSG(gotIt, "Logic error. renameAttributeContainer expected to find attribute but didn't.");
501
502 // We now own pAT.
503 pTable->del_attr_table(it);
504
505 // Shove it back in with the new name.
506 pAT->set_name(_name);
507 pTable->append_container(pAT, _name);
508
509 // Return it as the new current scope.
510 return pAT;
511}
512
513void
514AttributeElement::processEndAttribute(NCMLParser &p)
515{
516
517 BESDEBUG("ncml", "AttributeElement::handleEnd called at scope:" << p.getScopeString() << endl);
518
519 if (p.isScopeAtomicAttribute()) {
520 // If it was an OtherXML, then set the _value from the proxy parser.
521 if (_type == "OtherXML") {
522 VALID_PTR(_pOtherXMLParser);
523 _value = _pOtherXMLParser->getString();
524 SAFE_DELETE(_pOtherXMLParser);
525 }
526
527 // Set the values that we have gotten if we're not a rename, or if we ARE a rename but have a new _value
528 if (_orgName.empty() ||
529 (!_orgName.empty() && !_value.empty())) {
530 mutateAttributeAtCurrentScope(*_parser, _name, _type, _value);
531 }
532 // And pop the attr table
533 p.exitScope();
534 }
535 else if (p.isScopeAttributeContainer()) {
536 p.exitScope();
537 VALID_PTR(p.getCurrentAttrTable());
538 p.setCurrentAttrTable(p.getCurrentAttrTable()->get_parent());
539 // This better be valid or something is really broken!
540 NCML_ASSERT_MSG(p.getCurrentAttrTable(),
541 "ERROR: Null p.getCurrentAttrTable() unexpected while leaving scope of attribute container!");
542 }
543 else // Can't close an attribute if we're not in one!
544 {
545 THROW_NCML_PARSE_ERROR(_parser->getParseLineNumber(),
546 "Got end of attribute element while not parsing an attribute!");
547 }
548}
549
550void
551AttributeElement::startOtherXMLParse(NCMLParser &p)
552{
553 // this owns the memory.
554 _pOtherXMLParser = new OtherXMLParser(p);
555 p.enterOtherXMLParsingState(_pOtherXMLParser);
556}
557
558vector<string>
559AttributeElement::getValidAttributes()
560{
561 vector<string> attrs;
562 attrs.reserve(10);
563 attrs.push_back("name");
564 attrs.push_back("type");
565 attrs.push_back("value");
566 attrs.push_back("orgName");
567 attrs.push_back("separator");
568 return attrs;
569}
570
571
572}
573
574
575
Concrete class for NcML <attribute> element.
static string convertNcmlTypeToCanonicalType(const string &ncmlType)
Definition: NCMLParser.cc:944
const std::string getValueForLocalNameOrDefault(const std::string &localname, const std::string &defVal="") const
Definition: XMLHelpers.cc:181
NcML Parser for adding/modifying/removing metadata (attributes) to existing local datasets using NcML...