bes Updated for version 3.20.10
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
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
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...