bes  Updated for version 3.20.13
AttributeElement.cc
1 // 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 
42 namespace ncml_module {
43 const string AttributeElement::_sTypeName = "attribute";
44 const vector<string> AttributeElement::_sValidAttributes = getValidAttributes();
45 #if 0
46 const string AttributeElement::_default_global_container = "NC_GLOBAL";
47 #endif
48 
49 AttributeElement::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 
56 AttributeElement::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 
68 AttributeElement::~AttributeElement()
69 {
70  delete _pOtherXMLParser;
71 }
72 
73 const string &
74 AttributeElement::getTypeName() const
75 {
76  return _sTypeName;
77 }
78 
80 AttributeElement::clone() const
81 {
82  return new AttributeElement(*this);
83 }
84 
85 void
86 AttributeElement::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 
97 void
98 AttributeElement::handleBegin()
99 {
100  processAttribute(*_parser);
101 }
102 
103 void
104 AttributeElement::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 
121 void
122 AttributeElement::handleEnd()
123 {
124  processEndAttribute(*_parser);
125 }
126 
127 string
128 AttributeElement::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 
158 void
159 AttributeElement::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 
199 void
200 AttributeElement::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 
232 void
233 AttributeElement::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 
288 string
289 AttributeElement::getInternalType() const
290 {
291  return NCMLParser::convertNcmlTypeToCanonicalType(_type);
292 }
293 
294 void
295 AttributeElement::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 
324 void
325 AttributeElement::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 
415 void
416 AttributeElement::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 
474 AttrTable *
475 AttributeElement::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 
513 void
514 AttributeElement::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 
550 void
551 AttributeElement::startOtherXMLParse(NCMLParser &p)
552 {
553  // this owns the memory.
554  _pOtherXMLParser = new OtherXMLParser(p);
555  p.enterOtherXMLParsingState(_pOtherXMLParser);
556 }
557 
558 vector<string>
559 AttributeElement::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...