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