1 /*
2 * Copyright (C) 2001-2003 Peter J Jones (pjones@pmade.org)
3 * All Rights Reserved
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions
7 * are met:
8 *
9 * 1. Redistributions of source code must retain the above copyright
10 * notice, this list of conditions and the following disclaimer.
11 * 2. Redistributions in binary form must reproduce the above copyright
12 * notice, this list of conditions and the following disclaimer in
13 * the documentation and/or other materials provided with the
14 * distribution.
15 * 3. Neither the name of the Author nor the names of its contributors
16 * may be used to endorse or promote products derived from this software
17 * without specific prior written permission.
18 *
19 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS''
20 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
21 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
22 * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR
23 * OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
24 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
25 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
26 * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
27 * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
28 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
29 * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
30 * SUCH DAMAGE.
31 */
32
33 /*
34 * $Id: attributes.cpp 543412 2017-08-09 18:22:55Z satskyse $
35 * NOTE: This file was modified from its original version 0.6.0
36 * to fit the NCBI C++ Toolkit build framework and
37 * API and functionality requirements.
38 * Most importantly, it adds support for XML namespaces (see "namespace.hpp").
39 */
40
41 /** @file
42 * This file contains the implementation of the xml::attributes class.
43 **/
44
45 // xmlwrapp includes
46 #include <misc/xmlwrapp/attributes.hpp>
47 #include <misc/xmlwrapp/exception.hpp>
48 #include "ait_impl.hpp"
49 #include "pimpl_base.hpp"
50
51 // standard includes
52 #include <string.h>
53 #include <new>
54 #include <algorithm>
55 #include <list>
56 #include <string.h>
57
58 // libxml2 includes
59 #include <libxml/tree.h>
60
61 using namespace xml;
62 using namespace xml::impl;
63
64 const char * kInsertError = "inserting attribute error";
65
66
67 //####################################################################
68 struct xml::attributes::pimpl : public pimpl_base<xml::attributes::pimpl> {
69 //####################################################################
pimplxml::attributes::pimpl70 pimpl (void) : owner_(true) {
71 xmlnode_ = xmlNewNode(0, reinterpret_cast<const xmlChar*>("blank"));
72 if (!xmlnode_) throw std::bad_alloc();
73 }
74 //####################################################################
pimplxml::attributes::pimpl75 pimpl (xmlNodePtr node) : xmlnode_(node), owner_(false)
76 {}
77 //####################################################################
pimplxml::attributes::pimpl78 pimpl (const pimpl &other) : owner_(true) {
79 xmlnode_ = xmlCopyNode(other.xmlnode_, 2);
80 if (!xmlnode_) throw std::bad_alloc();
81 }
82 //####################################################################
~pimplxml::attributes::pimpl83 ~pimpl (void)
84 { release(); }
85 //####################################################################
releasexml::attributes::pimpl86 void release (void)
87 { if (owner_ && xmlnode_) xmlFreeNode(xmlnode_); }
88 //####################################################################
89
90 xmlNodePtr xmlnode_;
91 bool owner_;
92
93 private:
94 pimpl & operator=(const pimpl &);
95 };
96 //####################################################################
attributes(void)97 xml::attributes::attributes (void) {
98 pimpl_ = new pimpl;
99 }
100 //####################################################################
attributes(int)101 xml::attributes::attributes (int) {
102 pimpl_ = new pimpl(0);
103 }
104 //####################################################################
attributes(const attributes & other)105 xml::attributes::attributes (const attributes &other) {
106 pimpl_ = new pimpl(*other.pimpl_);
107 }
108 //####################################################################
operator =(const attributes & other)109 xml::attributes& xml::attributes::operator= (const attributes &other) {
110 attributes tmp(other);
111 swap(tmp);
112 return *this;
113 }
114 //####################################################################
swap(attributes & other)115 void xml::attributes::swap (attributes &other) {
116 std::swap(pimpl_, other.pimpl_);
117 }
118 //####################################################################
~attributes(void)119 xml::attributes::~attributes (void) {
120 if (pimpl_ != NULL)
121 delete pimpl_;
122 }
123 //####################################################################
attributes(attributes && other)124 xml::attributes::attributes (attributes &&other) :
125 pimpl_(other.pimpl_)
126 {
127 other.pimpl_ = NULL;
128 }
129 //####################################################################
operator =(attributes && other)130 xml::attributes & xml::attributes::operator= (attributes &&other) {
131 if (this != &other) {
132 if (pimpl_ != NULL)
133 delete pimpl_;
134 pimpl_ = other.pimpl_;
135 other.pimpl_ = NULL;
136 }
137 return *this;
138 }
139 //####################################################################
get_data(void)140 void* xml::attributes::get_data (void) {
141 return pimpl_->xmlnode_;
142 }
143 //####################################################################
createUnsafeNamespace(void * libxml2RawNamespace)144 xml::ns xml::attributes::createUnsafeNamespace (void * libxml2RawNamespace) {
145 return xml::ns(libxml2RawNamespace);
146 }
147 //####################################################################
getUnsafeNamespacePointer(const xml::ns & name_space)148 void * xml::attributes::getUnsafeNamespacePointer (const xml::ns &name_space) {
149 return name_space.unsafe_ns_;
150 }
151 //####################################################################
set_data(void * node)152 void xml::attributes::set_data (void *node) {
153 xmlNodePtr x = static_cast<xmlNodePtr>(node);
154
155 pimpl_->release();
156 pimpl_->owner_ = false;
157 pimpl_->xmlnode_ = x;
158 }
159 //####################################################################
begin(void)160 xml::attributes::iterator xml::attributes::begin (void) {
161 return iterator(pimpl_->xmlnode_,
162 pimpl_->xmlnode_->properties,
163 false, // not default
164 false); // not from find
165 }
166 //####################################################################
begin(void) const167 xml::attributes::const_iterator xml::attributes::begin (void) const {
168 return const_iterator(pimpl_->xmlnode_,
169 pimpl_->xmlnode_->properties,
170 false, // not default
171 false); // not from find
172 }
173 //####################################################################
end(void)174 xml::attributes::iterator xml::attributes::end (void) {
175 return iterator(pimpl_->xmlnode_,
176 NULL,
177 false,
178 false);
179 }
180 //####################################################################
end(void) const181 xml::attributes::const_iterator xml::attributes::end (void) const {
182 return const_iterator(pimpl_->xmlnode_,
183 NULL,
184 false,
185 false);
186 }
187 //####################################################################
insert(const char * name,const char * value,const ns * nspace)188 void xml::attributes::insert (const char *name, const char *value,
189 const ns *nspace ) {
190 if ( (!name) || (!value))
191 throw xml::exception("name and value of an attribute to "
192 "insert must not be NULL");
193 if (name[0] == '\0')
194 throw xml::exception("name cannot be empty");
195
196 bool only_whitespaces = true;
197 const char * current = name;
198 while (*current != '\0') {
199 if (memchr(" \t\n\r", *current, 4) == NULL) {
200 only_whitespaces = false;
201 break;
202 }
203 ++current;
204 }
205 if (only_whitespaces)
206 throw xml::exception("name may not consist "
207 "of only whitespace characters");
208
209
210 const char * column = strchr(name, ':');
211
212 if (!nspace) {
213 // It means ithat the user does not care about namespaces.
214 // So we should bother about them.
215 if (column) {
216 // The given name is qualified. Search for the namespace basing on
217 // the prefix
218 if (*(column + 1) == '\0')
219 throw xml::exception("invalid attribute name");
220 if (column == name)
221 throw xml::exception("an attribute may not have a default namespace");
222
223 std::string prefix(name, column - name);
224 xmlNsPtr resolved_ns = xmlSearchNs(pimpl_->xmlnode_->doc,
225 pimpl_->xmlnode_,
226 reinterpret_cast<const xmlChar*>(prefix.c_str()));
227 if (!resolved_ns)
228 throw xml::exception("cannot resolve namespace");
229
230 if (xmlSetNsProp(pimpl_->xmlnode_,
231 resolved_ns,
232 reinterpret_cast<const xmlChar*>(column + 1),
233 reinterpret_cast<const xmlChar*>(value)) == NULL)
234 throw xml::exception(kInsertError);
235 return;
236 }
237
238 // The given name is not qualified.
239 if (xmlSetProp(pimpl_->xmlnode_,
240 reinterpret_cast<const xmlChar*>(name),
241 reinterpret_cast<const xmlChar*>(value)) == NULL)
242 throw xml::exception(kInsertError);
243 return;
244 }
245
246 // Some namespace is provided. Check that the name is not qualified.
247 if (column)
248 throw xml::exception("cannot specify both a qualified name and a namespace");
249
250 if (nspace->is_void()) {
251 // The user wanted to insert an attribute without a namespace at all.
252 if (xmlSetProp(pimpl_->xmlnode_,
253 reinterpret_cast<const xmlChar*>(name),
254 reinterpret_cast<const xmlChar*>(value)) == NULL)
255 throw xml::exception(kInsertError);
256 return;
257 }
258
259 if (nspace->get_prefix() == std::string(""))
260 throw xml::exception("an attribute may not have a default namespace");
261
262 if (!nspace->is_safe()) {
263 if (xmlSetNsProp(pimpl_->xmlnode_,
264 reinterpret_cast<xmlNsPtr>(nspace->unsafe_ns_),
265 reinterpret_cast<const xmlChar*>(name),
266 reinterpret_cast<const xmlChar*>(value)) == NULL)
267 throw xml::exception(kInsertError);
268 return;
269 }
270
271
272 // Resolve namespace basing on uri
273 xmlNsPtr resolved_ns = xmlSearchNsByHref(pimpl_->xmlnode_->doc,
274 pimpl_->xmlnode_,
275 reinterpret_cast<const xmlChar*>(nspace->get_uri()));
276 if (!resolved_ns)
277 throw xml::exception("inserting attribute error: "
278 "cannot resolve namespace");
279
280 if (xmlSetNsProp(pimpl_->xmlnode_,
281 resolved_ns,
282 reinterpret_cast<const xmlChar*>(name),
283 reinterpret_cast<const xmlChar*>(value)) == NULL)
284 throw xml::exception(kInsertError);
285 return;
286 }
287 //####################################################################
find(const char * name,const ns * nspace)288 xml::attributes::iterator xml::attributes::find(const char *name,
289 const ns *nspace) {
290 xmlAttrPtr prop = find_prop(pimpl_->xmlnode_, name, nspace);
291 if (prop != 0)
292 return iterator(pimpl_->xmlnode_, prop, false, true);
293
294 phantom_attr* dtd_prop = find_default_prop(pimpl_->xmlnode_, name, nspace);
295 if (dtd_prop != 0)
296 return iterator(pimpl_->xmlnode_, dtd_prop, true, true);
297
298 return iterator(pimpl_->xmlnode_, NULL, false, true);
299 }
300 //####################################################################
find(const char * name,const ns * nspace) const301 xml::attributes::const_iterator xml::attributes::find (const char *name,
302 const ns *nspace) const {
303 xmlAttrPtr prop = find_prop(pimpl_->xmlnode_, name, nspace);
304 if (prop != 0)
305 return const_iterator(pimpl_->xmlnode_, prop, false, true);
306
307 phantom_attr * dtd_prop = find_default_prop(pimpl_->xmlnode_, name, nspace);
308 if (dtd_prop != 0)
309 return const_iterator(pimpl_->xmlnode_, dtd_prop, true, true);
310
311 return const_iterator(pimpl_->xmlnode_, NULL, false, true);
312 }
313 //####################################################################
erase(iterator to_erase)314 xml::attributes::iterator xml::attributes::erase (iterator to_erase) {
315 // Check that the iterator is initialized and
316 // that it is of the same node
317 if (to_erase == iterator() ||
318 to_erase.pimpl_->get()->xmlnode_ != pimpl_->xmlnode_)
319 throw xml::exception("cannot erase attribute, the iterator is "
320 "not initialized or belongs to another node attributes");
321
322 // There is nothing to erase for these two cases
323 if (to_erase == end()) // It must be first because otherwise end() is referenced
324 return end();
325 if (to_erase->is_default())
326 return end();
327
328 // It's not default so we should delete it
329 xmlAttrPtr prop_to_erase = static_cast<xmlAttrPtr>(to_erase.pimpl_->get()->prop_);
330 to_erase.pimpl_->get()->prop_ = static_cast<xmlAttrPtr>(to_erase.pimpl_->get()->prop_)->next;
331
332 xmlUnsetNsProp(pimpl_->xmlnode_, prop_to_erase->ns, prop_to_erase->name);
333 return to_erase;
334 }
335 //####################################################################
erase(const char * name,const ns * nspace)336 xml::attributes::size_type xml::attributes::erase (const char *name, const ns *nspace) {
337
338 if (!name)
339 return 0;
340
341 const char * column = strchr(name, ':');
342 if (!nspace) {
343 if (column) {
344 // name is qualified
345 if (column == name)
346 return 0; // Default NS is provided
347 if (*(column + 1) == '\0')
348 return 0; // No name is provided
349
350 std::string prefix(name, column - name);
351 xmlNsPtr resolved_ns = xmlSearchNs(pimpl_->xmlnode_->doc,
352 pimpl_->xmlnode_,
353 reinterpret_cast<const xmlChar*>(prefix.c_str()));
354 if (!resolved_ns)
355 return 0;
356
357 if (xmlUnsetNsProp(pimpl_->xmlnode_,
358 resolved_ns,
359 reinterpret_cast<const xmlChar*>(column + 1)) == 0)
360 return 1;
361 return 0;
362 }
363
364 // It is a non-qualified name. Search basing on names only.
365 size_type count = 0;
366 xmlAttrPtr att = find_prop(pimpl_->xmlnode_, name, NULL);
367 while (att != NULL) {
368 ++count;
369 xmlUnlinkNode(reinterpret_cast<xmlNodePtr>(att));
370 xmlFreeProp(att);
371 att = find_prop(pimpl_->xmlnode_, name, NULL);
372 }
373 return count;
374 }
375
376 if (column)
377 return 0; // Both a namespace and a prefix in the name are provided
378
379 if (nspace->is_void()) {
380 // The attribute must not have a namespace at all
381 if (xmlUnsetProp(pimpl_->xmlnode_,
382 reinterpret_cast<const xmlChar*>(name)) == 0)
383 return 1;
384 return 0;
385 }
386
387 if (!nspace->is_safe()) {
388 if (xmlUnsetNsProp(pimpl_->xmlnode_,
389 reinterpret_cast<xmlNsPtr>(nspace->unsafe_ns_),
390 reinterpret_cast<const xmlChar*>(name)) == 0)
391 return 1;
392 return 0;
393 }
394
395 // Safe namespace. Resolve it basing on the uri.
396 xmlNsPtr resolved_ns = xmlSearchNsByHref(pimpl_->xmlnode_->doc,
397 pimpl_->xmlnode_,
398 reinterpret_cast<const xmlChar*>(nspace->get_uri()));
399
400 if (!resolved_ns)
401 return 0; // Namespace is not resolved. Nothing will be deleted anyway.
402
403 // There could be many attributes with different prefixes but with the same
404 // URIs and names
405 size_type count = 0;
406 while (xmlUnsetNsProp(pimpl_->xmlnode_,
407 resolved_ns,
408 reinterpret_cast<const xmlChar*>(name)) == 0)
409 ++count;
410 return count;
411 }
412 //####################################################################
empty(void) const413 bool xml::attributes::empty (void) const {
414 return pimpl_->xmlnode_->properties == 0;
415 }
416 //####################################################################
size(void) const417 xml::attributes::size_type xml::attributes::size (void) const {
418 size_type count = 0;
419
420 xmlAttrPtr prop = pimpl_->xmlnode_->properties;
421 while (prop != 0) { ++count; prop = prop->next; }
422
423 return count;
424 }
425 //####################################################################
426 struct attr_cmp {
operator ()attr_cmp427 bool operator() (const xmlAttrPtr & lhs, const xmlAttrPtr & rhs) const {
428 return strcmp(reinterpret_cast<const char *>(lhs->name),
429 reinterpret_cast<const char *>(rhs->name)) < 0;
430 }
431 };
sort(void)432 void xml::attributes::sort (void) {
433 std::list<xmlAttrPtr> attrs;
434 xmlAttrPtr prop = pimpl_->xmlnode_->properties;
435
436 // Collect pointers to the attributes
437 while (prop != 0) {
438 attrs.push_back(prop);
439 prop = prop->next;
440 }
441
442 // Sort pointers by the attribute names
443 attrs.sort(attr_cmp());
444
445 // Modify the list of attributes to have them sorted
446 xmlAttrPtr cur = NULL;
447 xmlAttrPtr prev = NULL;
448
449 for (std::list<xmlAttrPtr>::const_iterator k = attrs.begin();
450 k != attrs.end(); ++k) {
451 cur = *k;
452
453 if (prev == NULL)
454 pimpl_->xmlnode_->properties = cur;
455
456 cur->prev = prev;
457 cur->next = NULL;
458
459 if (prev != NULL)
460 prev->next = cur;
461
462 prev = cur;
463 }
464 }
465 //####################################################################
466
467