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