1 /**************************************************************************\
2  * Copyright (c) Kongsberg Oil & Gas Technologies AS
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 are
7  * met:
8  *
9  * Redistributions of source code must retain the above copyright notice,
10  * this list of conditions and the following disclaimer.
11  *
12  * Redistributions in binary form must reproduce the above copyright
13  * notice, this list of conditions and the following disclaimer in the
14  * documentation and/or other materials provided with the distribution.
15  *
16  * Neither the name of the copyright holder nor the names of its
17  * contributors may be used to endorse or promote products derived from
18  * this software without specific prior written permission.
19  *
20  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
21  * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
22  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
23  * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
24  * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
25  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
26  * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
27  * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
28  * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
29  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
30  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
31 \**************************************************************************/
32 
33 #include <Inventor/C/XML/document.h>
34 #include "documentp.h"
35 
36 #ifdef HAVE_CONFIG_H
37 #include <config.h>
38 #endif // HAVE_CONFIG_H
39 
40 #include <cstdio>
41 #include <cstdlib>
42 #include <cstring>
43 #include <cassert>
44 
45 #include <boost/scoped_array.hpp>
46 
47 #include <coindefs.h>
48 #include <Inventor/C/XML/element.h>
49 #include <Inventor/C/XML/attribute.h>
50 #include <Inventor/C/XML/path.h>
51 #include <Inventor/C/XML/parser.h>
52 #include <Inventor/lists/SbList.h>
53 #include <Inventor/SbString.h>
54 
55 #include "expat/expat.h"
56 #include "utils.h"
57 #include "elementp.h"
58 
59 // #define DEV_DEBUG 1
60 
61 /*!
62   \page xmlparsing XML Parsing with Coin
63 
64   For Coin 3.0, we added an XML parser to Coin.  This document describes
65   how it can be used for generic purposes.
66 
67   Why another XML parser, you might ask?  First of all, the XML parser
68   is actually a third-party parser, expat.  Coin needed one, and many
69   Coin-dependent projects needed one as well.  We therefore needed to
70   expose an API for it.  However, integrating a 3rd-party parser into
71   Coin, we can not expose its API directly, or other projects also
72   using Expat would get conflicts.  We therefore needed to expose the
73   XML API with a unique API, hence the API you see here.  It is based
74   on a XML DOM API we use(d) in a couple of other projects, but it has
75   been tweaked to fit into Coin and to be wrapped over Expat (the
76   original implementation just used flex).
77 
78   The XML parser is both a streaming parser and a DOM parser.  Being a
79   streaming parser means that documents can be read in without having
80   to be fully contained in memory.  When used as a DOM parser, the
81   whole document is fully parsed in first, and then inspected by
82   client code by traversing the DOM.  The two modes can actually be
83   mixed arbitrarily if ending up with a partial DOM sounds useful.
84 
85   The XML parser has both a C API and a C++ API.  The C++ API is just
86   a wrapper around the C API, and only serves as convenience if you
87   prefer to read/write C++ code (which is tighter) over more verbose
88   C code.
89 
90   The C API naming convention may look a bit strange, unless you have
91   written libraries to be wrapped for scheme/lisp-like languages
92   before.  Then you might be familiar with the convention of suffixing
93   your functions based on their behaviour/usage meaning.  Mutating
94   functions are suffixed with "!", or "_x" for (eXclamation point),
95   and predicates are suffixed with "?", or "_p" in C.
96 
97   The simplest way to use the XML parser is to just call
98   cc_xml_read_file(filename) and then traverse the DOM model through
99   using cc_xml_doc_get_root(), cc_xml_elt_get_child(), and
100   cc_xml_elt_get_attr().
101 
102   \sa XML, cc_xml_doc, cc_xml_elt, cc_xml_attr
103 */
104 
105 // *************************************************************************
106 
107 /*!
108   \var typedef struct cc_xml_doc cc_xml_doc
109   \brief opaque container object type for XML documents
110 
111   This type is an opaque container object type for an XML document structure,
112   and also the interface for configuring the parsing and writing code.
113 
114   \ingroup XML
115 */
116 
117 struct cc_xml_doc {
118   XML_Parser parser;
119 
120   cc_xml_filter_cb * filtercb;
121   void * filtercbdata;
122 
123   // document type
124 
125   char * xmlversion;
126   char * xmlencoding;
127 
128   char * filename;
129   cc_xml_elt * root;
130   cc_xml_elt * current;
131 
132   SbList<cc_xml_elt *> parsestack;
133 };
134 
135 // *************************************************************************
136 // internal functions
137 
138 namespace {
139 
140 void
cc_xml_doc_expat_element_start_handler_cb(void * userdata,const XML_Char * elementtype,const XML_Char ** attributes)141 cc_xml_doc_expat_element_start_handler_cb(void * userdata, const XML_Char * elementtype, const XML_Char ** attributes)
142 {
143   XML_Parser parser = static_cast<XML_Parser>(userdata);
144   cc_xml_doc * doc = static_cast<cc_xml_doc *>(XML_GetUserData(parser));
145 
146   cc_xml_elt * elt = cc_xml_elt_new_from_data(elementtype, NULL);
147   assert(elt);
148 
149   // FIXME: check if attribute values are automatically dequoted or not...
150   // (dequote if not)
151   if (attributes) {
152     for (int c = 0; attributes[c] != NULL; c += 2) {
153       cc_xml_attr * attr = cc_xml_attr_new_from_data(attributes[c], attributes[c+1]);
154       cc_xml_elt_set_attribute_x(elt, attr);
155     }
156   }
157 
158   if (doc->parsestack.getLength() > 0) {
159     cc_xml_elt * parent = doc->parsestack[doc->parsestack.getLength()-1];
160     cc_xml_elt_add_child_x(parent, elt);
161   }
162 
163   if ((doc->parsestack.getLength() == 0) && (doc->root == NULL)) {
164     cc_xml_doc_set_root_x(doc, elt);
165   }
166 
167   doc->parsestack.push(elt);
168 
169   if (doc->filtercb) {
170     doc->filtercb(doc->filtercbdata, doc, elt, TRUE);
171   }
172 }
173 
174 void
cc_xml_doc_expat_element_end_handler_cb(void * userdata,const XML_Char * element)175 cc_xml_doc_expat_element_end_handler_cb(void * userdata, const XML_Char * element)
176 {
177   XML_Parser parser = static_cast<XML_Parser>(userdata);
178   cc_xml_doc * doc = static_cast<cc_xml_doc *>(XML_GetUserData(parser));
179 
180   const int stackdepth = doc->parsestack.getLength();
181   if (stackdepth == 0) {
182     // flag error
183     return;
184   }
185 
186   cc_xml_elt * topelt = doc->parsestack.pop();
187   if (strcmp(cc_xml_elt_get_type(topelt), element) != 0) {
188     // this means XML input is closing a tag that was not the one opened at
189     // this level. flag error
190   }
191 
192   if (doc->filtercb) {
193     switch (doc->filtercb(doc->filtercbdata, doc, topelt, FALSE)) {
194     case DISCARD:
195       {
196         cc_xml_elt * parent = cc_xml_elt_get_parent(topelt);
197         if (parent) {
198           cc_xml_elt_remove_child_x(parent, topelt);
199           cc_xml_elt_delete_x(topelt);
200         } else {
201           if (topelt == doc->root) {
202             cc_xml_doc_set_root_x(doc, NULL);
203             cc_xml_elt_delete_x(topelt);
204           } else {
205             assert(!"invalid case - investigate");
206           }
207         }
208       }
209       break;
210     case KEEP:
211       break;
212     default:
213       assert(!"invalid filter choice returned from client code");
214       break;
215     }
216   }
217 }
218 
219 SbBool
cc_xml_is_all_whitespace_p(const char * strptr)220 cc_xml_is_all_whitespace_p(const char * strptr)
221 {
222   while (*strptr) {
223     switch (*strptr) {
224     case ' ':
225     case '\t':
226     case '\n':
227     case '\r':
228       break;
229     default:
230       return FALSE;
231     }
232     ++strptr;
233   }
234   return TRUE;
235 }
236 
237 void
cc_xml_doc_expat_character_data_handler_cb(void * userdata,const XML_Char * cdata,int len)238 cc_xml_doc_expat_character_data_handler_cb(void * userdata, const XML_Char * cdata, int len)
239 {
240 #ifdef DEV_DEBUG
241   fprintf(stdout, "cc_xml_doc_expat_character_data_handler_cb()\n");
242 #endif // DEV_DEBUG
243 
244   XML_Parser parser = static_cast<XML_Parser>(userdata);
245   cc_xml_doc * doc = static_cast<cc_xml_doc *>(XML_GetUserData(parser));
246 
247   cc_xml_elt * elt = cc_xml_elt_new();
248   assert(elt);
249 
250   // need a temporary buffer for the cdata to make a nullterminated string.
251   boost::scoped_array<char> buffer;
252   buffer.reset(new char [len + 1]);
253   memcpy(buffer.get(), cdata, len);
254   buffer[len] = '\0';
255   cc_xml_elt_set_type_x(elt, COIN_XML_CDATA_TYPE);
256   cc_xml_elt_set_cdata_x(elt, buffer.get());
257 
258   if (cc_xml_is_all_whitespace_p(buffer.get())) {
259     cc_xml_elt_delete_x(elt);
260     return;
261   }
262 
263   if (doc->parsestack.getLength() > 0) {
264     cc_xml_elt * parent = doc->parsestack[doc->parsestack.getLength()-1];
265     cc_xml_elt_add_child_x(parent, elt);
266   }
267 
268   //FIXME 2008-06-11 BFG: Possible leak of elt.
269 
270   if (doc->filtercb) {
271     doc->filtercb(doc->filtercbdata, doc, elt, TRUE);
272     cc_xml_filter_choice choice = doc->filtercb(doc->filtercbdata, doc, elt, FALSE);
273     switch (choice) {
274     case KEEP:
275       break;
276     case DISCARD:
277       {
278         if (doc->parsestack.getLength() > 0) {
279           cc_xml_elt * parent = doc->parsestack[doc->parsestack.getLength()-1];
280           cc_xml_elt_remove_child_x(parent, elt);
281           cc_xml_elt_delete_x(elt);
282           elt = NULL;
283         }
284       }
285       break;
286     default:
287       assert(!"invalid filter choice returned from client code");
288       break;
289     }
290   }
291 
292 #ifdef DEV_DEBUG
293   fprintf(stdout, "\nCDATA: '%s'\n", buffer.get());
294 #endif // DEV_DEBUG
295 }
296 
297 void
cc_xml_doc_expat_processing_instruction_handler_cb(void * COIN_UNUSED_ARG (userdata),const XML_Char * COIN_UNUSED_ARG (target),const XML_Char * COIN_UNUSED_ARG (pidata))298 cc_xml_doc_expat_processing_instruction_handler_cb(void * COIN_UNUSED_ARG(userdata), const XML_Char * COIN_UNUSED_ARG(target), const XML_Char * COIN_UNUSED_ARG(pidata))
299 {
300 #ifdef DEV_DEBUG
301   fprintf(stdout, "received processing Instruction...\n");
302 #endif // DEV_DEBUG
303 }
304 
305 
306 void
cc_xml_doc_create_parser_x(cc_xml_doc * doc)307 cc_xml_doc_create_parser_x(cc_xml_doc * doc)
308 {
309   assert(doc && !doc->parser);
310   doc->parser = XML_ParserCreate(NULL);
311   assert(doc->parser);
312   XML_UseParserAsHandlerArg(doc->parser);
313   XML_SetUserData(doc->parser, doc);
314   XML_SetElementHandler(doc->parser,
315                         cc_xml_doc_expat_element_start_handler_cb,
316                         cc_xml_doc_expat_element_end_handler_cb);
317   XML_SetCharacterDataHandler(doc->parser,
318                               cc_xml_doc_expat_character_data_handler_cb);
319   XML_SetProcessingInstructionHandler(doc->parser, cc_xml_doc_expat_processing_instruction_handler_cb);
320 }
321 
322 void
cc_xml_doc_delete_parser_x(cc_xml_doc * doc)323 cc_xml_doc_delete_parser_x(cc_xml_doc * doc)
324 {
325   assert(doc && doc->parser);
326   XML_ParserFree(doc->parser);
327   doc->parser = NULL;
328 }
329 
330 } // anonymous namespace
331 
332 // *************************************************************************
333 
334 /*!
335   \fn cc_xml_doc * cc_xml_doc_new(void)
336 
337   Creates a new cc_xml_doc object that is totally blank.
338 
339   \ingroup XML
340   \relates cc_xml_doc
341 */
342 
343 cc_xml_doc *
cc_xml_doc_new(void)344 cc_xml_doc_new(void)
345 {
346   cc_xml_doc * doc = new cc_xml_doc;
347   assert(doc);
348   doc->parser = NULL;
349   doc->xmlversion = NULL;
350   doc->xmlencoding = NULL;
351   doc->filtercb = NULL;
352   doc->filtercbdata = NULL;
353   doc->filename = NULL;
354   doc->root = NULL;
355   doc->current = NULL;
356   return doc;
357 }
358 
359 /*!
360   \fn void cc_xml_doc_delete_x(cc_xml_doc * doc)
361 
362   Frees up a cc_xml_doc object and all its resources.
363 
364   \ingroup XML
365   \relates cc_xml_doc
366 */
367 
368 void
cc_xml_doc_delete_x(cc_xml_doc * doc)369 cc_xml_doc_delete_x(cc_xml_doc * doc)
370 {
371   assert(doc);
372   if (doc->parser) { cc_xml_doc_delete_parser_x(doc); }
373   delete [] doc->xmlversion;
374   delete [] doc->xmlencoding;
375   delete [] doc->filename;
376   if (doc->root) cc_xml_elt_delete_x(doc->root);
377   delete doc;
378 }
379 
380 // *************************************************************************
381 
382 /*!
383   \fn void cc_xml_doc_set_filter_cb_x(cc_xml_doc * doc, cc_xml_filter_cb * cb, void * userdata)
384 
385   Sets the filter callback for document parsing.  This makes it
386   possible to use the parser as a streaming parser, by making the
387   parser discard all elements it has read in.
388 
389   Elements can only be discarded as they are popped - on push they will be
390   kept regardless of what the filter callback returns.
391 
392   \ingroup XML
393   \relates cc_xml_doc
394 */
395 
396 void
cc_xml_doc_set_filter_cb_x(cc_xml_doc * doc,cc_xml_filter_cb * cb,void * userdata)397 cc_xml_doc_set_filter_cb_x(cc_xml_doc * doc, cc_xml_filter_cb * cb, void * userdata)
398 {
399   doc->filtercb = cb;
400   doc->filtercbdata = userdata;
401 }
402 
403 /*!
404   \fn void cc_xml_doc_get_filter_cb(const cc_xml_doc * doc, cc_xml_filter_cb *& cb, void *& userdata)
405 
406   Returns the set filter callback in the \a cb arg and \a userdata arg.
407 
408   \ingroup XML
409   \relates cc_xml_doc
410 */
411 
412 void
cc_xml_doc_get_filter_cb(const cc_xml_doc * doc,cc_xml_filter_cb * & cb,void * & userdata)413 cc_xml_doc_get_filter_cb(const cc_xml_doc * doc, cc_xml_filter_cb *& cb, void *& userdata)
414 {
415   cb = doc->filtercb;
416   userdata = doc->filtercbdata;
417 }
418 
419 // *************************************************************************
420 
421 /*!
422   Creates an cc_xml_doc and reads a file into it.
423   This is just a convenience function.
424 */
425 cc_xml_doc *
cc_xml_read_file(const char * path)426 cc_xml_read_file(const char * path) // parser.h convenience function
427 {
428   cc_xml_doc * doc = cc_xml_doc_new();
429   assert(doc);
430   if (!cc_xml_doc_read_file_x(doc, path)) {
431     cc_xml_doc_delete_x(doc);
432     return NULL;
433   }
434   return doc;
435 }
436 
437 /*!
438   Reads a file into the cc_xml_doc object.
439 
440   Deletes any old XML DOM the doc contains.
441 */
442 
443 SbBool
cc_xml_doc_read_file_x(cc_xml_doc * doc,const char * path)444 cc_xml_doc_read_file_x(cc_xml_doc * doc, const char * path)
445 {
446   assert(doc);
447   if (doc->root) {
448     cc_xml_elt_delete_x(doc->root);
449     doc->root = NULL;
450   }
451 
452   if (!doc->parser) {
453     cc_xml_doc_create_parser_x(doc);
454   }
455 
456   FILE * fp = fopen(path, "rb");
457   if (!fp) {
458     // FIXME: error condition
459     cc_xml_doc_delete_parser_x(doc);
460     return FALSE;
461   }
462 
463   // read in file in 8K chunks, buffers kept by expat
464 
465   SbBool error = FALSE;
466   SbBool final = FALSE;
467 
468   while (!final && !error) {
469     void * buf = XML_GetBuffer(doc->parser, 8192);
470     assert(buf);
471     int bytes = static_cast<int>(fread(buf, 1, 8192, fp));
472     final = feof(fp);
473     XML_Status status = XML_ParseBuffer(doc->parser, bytes, final);
474     if (status != XML_STATUS_OK) { cc_xml_doc_handle_parse_error(doc); }
475   }
476 
477   fclose(fp);
478 
479   cc_xml_doc_set_filename_x(doc, path);
480 
481   return !error;
482 }
483 
484 cc_xml_doc *
cc_xml_read_buffer(const char * buffer)485 cc_xml_read_buffer(const char * buffer) // parser.h convenience function
486 {
487   cc_xml_doc * doc = cc_xml_doc_new();
488   assert(doc);
489   assert(buffer);
490   size_t buflen = strlen(buffer);
491   if (!cc_xml_doc_read_buffer_x(doc, buffer, buflen)) {
492     cc_xml_doc_delete_x(doc);
493     return NULL;
494   }
495   cc_xml_doc_set_filename_x(doc, "<memory buffer>");
496   return doc;
497 }
498 
499 namespace {
500 void cc_xml_doc_parse_buffer_partial_init_x(cc_xml_doc * doc);
501 }
502 
503 SbBool
cc_xml_doc_read_buffer_x(cc_xml_doc * doc,const char * buffer,size_t buflen)504 cc_xml_doc_read_buffer_x(cc_xml_doc * doc, const char * buffer, size_t buflen)
505 {
506 #ifdef DEV_DEBUG
507   fprintf(stdout, "cc_xml_doc_read_buffer_x(%p, %d, %p)\n", doc, (int) buflen, buffer);
508 #endif // DEV_DEBUG
509   cc_xml_doc_parse_buffer_partial_init_x(doc);
510   return cc_xml_doc_parse_buffer_partial_done_x(doc, buffer, buflen);
511 }
512 
513 // xml_doc_parse_ vs xml_doc_read_
514 
515 namespace {
516 void
cc_xml_doc_parse_buffer_partial_init_x(cc_xml_doc * doc)517 cc_xml_doc_parse_buffer_partial_init_x(cc_xml_doc * doc) // maybe expose and require explicit?
518 {
519 #ifdef DEV_DEBUG
520   fprintf(stdout, "cc_xml_doc_parse_buffer_partial_init_x()\n");
521 #endif // DEV_DEBUG
522   assert(doc->parser == NULL);
523   cc_xml_doc_create_parser_x(doc);
524 }
525 }
526 
527 SbBool
cc_xml_doc_parse_buffer_partial_x(cc_xml_doc * doc,const char * buffer,size_t buflen)528 cc_xml_doc_parse_buffer_partial_x(cc_xml_doc * doc, const char * buffer, size_t buflen)
529 {
530   assert(doc);
531 #ifdef DEV_DEBUG
532   fprintf(stdout, "cc_xml_doc_parse_buffer_partial_x()\n");
533 #endif // DEV_DEBUG
534   if (!doc->parser) {
535     cc_xml_doc_parse_buffer_partial_init_x(doc);
536   }
537 
538   XML_Status status = XML_Parse(doc->parser, buffer, static_cast<int>(buflen), FALSE);
539   if (status != XML_STATUS_OK) {
540     cc_xml_doc_handle_parse_error(doc);
541     // FIXME: should delete_parser() be invoked here?
542   }
543 
544   return (status == XML_STATUS_OK);
545 }
546 
547 SbBool
cc_xml_doc_parse_buffer_partial_done_x(cc_xml_doc * doc,const char * buffer,size_t buflen)548 cc_xml_doc_parse_buffer_partial_done_x(cc_xml_doc * doc, const char * buffer, size_t buflen)
549 {
550 #ifdef DEV_DEBUG
551   fprintf(stdout, "cc_xml_doc_parse_buffer_partial_done_x()\n");
552 #endif // DEV_DEBUG
553   assert(doc);
554   if (!doc->parser) {
555     cc_xml_doc_parse_buffer_partial_init_x(doc);
556   }
557   XML_Status status = XML_Parse(doc->parser, buffer, static_cast<int>(buflen), TRUE);
558 
559   if (status != XML_STATUS_OK) {
560     cc_xml_doc_handle_parse_error(doc);
561   }
562 
563   cc_xml_doc_delete_parser_x(doc);
564   return (status == XML_STATUS_OK);
565 }
566 
567 // *************************************************************************
568 
569 /*!
570   Sets the filename attribute.  Frees old filename data if any.
571 */
572 
573 void
cc_xml_doc_set_filename_x(cc_xml_doc * doc,const char * path)574 cc_xml_doc_set_filename_x(cc_xml_doc * doc, const char * path)
575 {
576   assert(doc);
577   delete [] doc->filename;
578   doc->filename = cc_xml_strdup(path);
579 }
580 
581 /*!
582   Returns the filename attribute.  If nothing has been set, NULL is returned.
583   If document was read from memory, "<memory buffer>" was returned.
584 */
585 
586 const char *
cc_xml_doc_get_filename(const cc_xml_doc * doc)587 cc_xml_doc_get_filename(const cc_xml_doc * doc)
588 {
589   assert(doc && doc->filename);
590   return doc->filename;
591 }
592 
593 /*!
594   Sets the current pointer.  Not in use for anything at the moment, and might
595   get deprecated.
596 */
597 void
cc_xml_doc_set_current_x(cc_xml_doc * doc,cc_xml_elt * elt)598 cc_xml_doc_set_current_x(cc_xml_doc * doc, cc_xml_elt * elt)
599 {
600   assert(doc);
601   doc->current = elt;
602 }
603 
604 /*!
605   Returns the current pointer.  Might get deprecated.
606 */
607 
608 cc_xml_elt *
cc_xml_doc_get_current(const cc_xml_doc * doc)609 cc_xml_doc_get_current(const cc_xml_doc * doc)
610 {
611   assert(doc);
612   return doc->current;
613 }
614 
615 /*!
616   Sets the root element for the document.  Only useful when
617   constructing documents to be written.
618 */
619 
620 void
cc_xml_doc_set_root_x(cc_xml_doc * doc,cc_xml_elt * root)621 cc_xml_doc_set_root_x(cc_xml_doc * doc, cc_xml_elt * root)
622 {
623   assert(doc);
624   doc->root = root;
625 }
626 
627 /*!
628   Returns the root element of the document.
629 */
630 
631 cc_xml_elt *
cc_xml_doc_get_root(const cc_xml_doc * doc)632 cc_xml_doc_get_root(const cc_xml_doc * doc)
633 {
634   assert(doc);
635   return doc->root;
636 }
637 
638 // *************************************************************************
639 
640 void
cc_xml_doc_strip_whitespace_x(cc_xml_doc * doc)641 cc_xml_doc_strip_whitespace_x(cc_xml_doc * doc)
642 {
643   assert(doc);
644   return;
645 #if 0 // FIXME
646   cc_xml_path * path = cc_xml_path_new();
647   cc_xml_path_set_x(path, CC_XML_CDATA_TYPE, NULL);
648   cc_xml_elt * elt = cc_xml_doc_find_element(doc, path);
649   while ( elt != NULL ) {
650     cc_xml_elt * next = cc_xml_doc_find_next_element(doc, elt, path);
651     if ( sc_whitespace_p(cc_xml_elt_get_data(elt)) )  {
652       cc_xml_elt_remove_child_x(cc_xml_elt_get_parent(elt), elt);
653       cc_xml_elt_delete_x(elt);
654     } else {
655       cc_xml_elt_strip_whitespace_x(elt);
656     }
657     elt = next;
658   }
659   cc_xml_path_delete_x(path);
660 #endif
661 }
662 
663 // *************************************************************************
664 
665 const cc_xml_elt *
cc_xml_doc_find_element(const cc_xml_doc * doc,cc_xml_path * path)666 cc_xml_doc_find_element(const cc_xml_doc * doc, cc_xml_path * path)
667 {
668   assert(doc && path);
669   return cc_xml_elt_find(doc->root, path);
670 } // cc_xml_doc_find_element()
671 
672 const cc_xml_elt *
cc_xml_doc_find_next_element(const cc_xml_doc * doc,cc_xml_elt * prev,cc_xml_path * path)673 cc_xml_doc_find_next_element(const cc_xml_doc * doc, cc_xml_elt * prev, cc_xml_path * path)
674 {
675   assert(doc && prev && path);
676   return cc_xml_elt_find_next(doc->root, prev, path);
677 } // cc_xml_doc_find_next_element()
678 
679 cc_xml_elt *
cc_xml_doc_create_element_x(cc_xml_doc * doc,cc_xml_path * path)680 cc_xml_doc_create_element_x(cc_xml_doc * doc, cc_xml_path * path)
681 {
682   assert(doc && path);
683   cc_xml_elt * root = cc_xml_doc_get_root(doc);
684   assert(root);
685   return cc_xml_elt_create_x(root, path);
686 } // cc_xml_doc_create_element_x()
687 
688 // *************************************************************************
689 
690 SbBool
cc_xml_doc_write_to_buffer(const cc_xml_doc * doc,char * & buffer,size_t & bytes)691 cc_xml_doc_write_to_buffer(const cc_xml_doc * doc, char *& buffer, size_t & bytes)
692 {
693   assert(doc);
694   bytes = static_cast<int>(cc_xml_doc_calculate_size(doc));
695   buffer = new char [ bytes + 1 ];
696 
697   size_t bytesleft = bytes;
698   char * hereptr = buffer;
699 
700 // macro to advance buffer pointer and decrement bytesleft count
701 #define ADVANCE_NUM_BYTES(len)          \
702   do { const int length = (len);        \
703        hereptr += length;               \
704        bytesleft -= length; } while (0)
705 
706 // macro to copy in a string literal and advance pointers
707 #define ADVANCE_STRING_LITERAL(str)                \
708   do { static const char strobj[] = str;           \
709        const int strlength = (sizeof(strobj) - 1); \
710        strncpy(hereptr, strobj, strlength);        \
711        ADVANCE_NUM_BYTES(strlength); } while (0)
712 
713 // macro to copy in a run-time string and advance pointers
714 #define ADVANCE_STRING(str)                      \
715   do { const int strlength = strlen(str);        \
716        strncpy(hereptr, str, strlength);         \
717        ADVANCE_NUM_BYTES(strlength); } while (0)
718 
719   // duplicate block, see cc_xml_doc_calculate_size()
720   ADVANCE_STRING_LITERAL("<?xml version=\"");
721   if (doc->xmlversion) {
722     ADVANCE_STRING(doc->xmlversion);
723   } else {
724     ADVANCE_STRING_LITERAL("1.0");
725   }
726   ADVANCE_STRING_LITERAL("\" encoding=\"");
727   if (doc->xmlencoding) {
728     ADVANCE_STRING(doc->xmlencoding);
729   } else {
730     ADVANCE_STRING_LITERAL("UTF-8");
731   }
732   ADVANCE_STRING_LITERAL("\"?>\n");
733 
734   if (doc->root) {
735     ADVANCE_NUM_BYTES(cc_xml_elt_write_to_buffer(doc->root, hereptr, bytesleft, 0, 2));
736   }
737 
738 #undef ADVANCE_STRING
739 #undef ADVANCE_STRING_LITERAL
740 #undef ADVANCE_NUM_BYTES
741 
742   buffer[bytes] = '\0';
743 
744   return TRUE;
745 }
746 
747 SbBool
cc_xml_doc_write_to_file(const cc_xml_doc * doc,const char * path)748 cc_xml_doc_write_to_file(const cc_xml_doc * doc, const char * path)
749 {
750   assert(doc);
751   assert(path);;
752 
753   size_t bufsize = 0;
754   boost::scoped_array<char> buffer;
755   {
756     char * bufptr = NULL;
757     if (!cc_xml_doc_write_to_buffer(doc, bufptr, bufsize)) {
758       return FALSE;
759     }
760     assert(bufptr);
761     buffer.reset(bufptr);
762   }
763 
764   const size_t bytes = strlen(buffer.get());
765   assert(bufsize == bytes);
766   FILE * fp = NULL;
767   if (strcmp(path, "-") == 0)
768     fp = stdout;
769   else
770     fp = fopen(path, "wb");
771   assert(fp != NULL);
772   fwrite(buffer.get(), 1, bufsize, fp);
773   if (strcmp(path, "-") != 0)
774     fclose(fp);
775 
776   return TRUE;
777 }
778 
779 // *************************************************************************
780 
781 /*!
782   Compare document DOM against other document DOM, and return path to first
783   difference.  Returns NULL if documents are equal.  To be used mostly for
784   testing the XML I/O code.
785 */
786 
787 cc_xml_path *
cc_xml_doc_diff(const cc_xml_doc * COIN_UNUSED_ARG (doc),const cc_xml_doc * COIN_UNUSED_ARG (other))788 cc_xml_doc_diff(const cc_xml_doc * COIN_UNUSED_ARG(doc), const cc_xml_doc * COIN_UNUSED_ARG(other))
789 {
790 #ifdef DEV_DEBUG
791   COIN_STUB();
792 #endif // DEV_DEBUG
793   // FIXME: implement
794   return NULL;
795 }
796 
797 // In documentp.h
798 size_t
cc_xml_doc_calculate_size(const cc_xml_doc * doc)799 cc_xml_doc_calculate_size(const cc_xml_doc * doc)
800 {
801   size_t bytes = 0;
802 
803 // macro to advance a given number of bytes
804 #define ADVANCE_NUM_BYTES(num) \
805   do { bytes += (num); } while (0)
806 
807 // macro to increment bytecount for string literal
808 #define ADVANCE_STRING_LITERAL(str) \
809   do { static const char strobj[] = str; bytes += (sizeof(strobj) - 1); } while (0)
810 
811 // macro to increment bytecount for run-time string
812 #define ADVANCE_STRING(str) \
813   do { bytes += strlen(str); } while (0)
814 
815   // duplicate block, see cc_xml_doc_write_to_buffer()
816   ADVANCE_STRING_LITERAL("<?xml version=\"");
817   if (doc->xmlversion) {
818     ADVANCE_STRING(doc->xmlversion);
819   } else {
820     ADVANCE_STRING_LITERAL("1.0");
821   }
822   ADVANCE_STRING_LITERAL("\" encoding=\"");
823   if (doc->xmlencoding) {
824     ADVANCE_STRING(doc->xmlencoding);
825   } else {
826     ADVANCE_STRING_LITERAL("UTF-8");
827   }
828   ADVANCE_STRING_LITERAL("\"?>\n");
829 
830   if (doc->root) {
831     ADVANCE_NUM_BYTES(cc_xml_elt_calculate_size(doc->root, 0, 2));
832   }
833 
834 #undef ADVANCE_STRING
835 #undef ADVANCE_STRING_LITERAL
836 #undef ADVANCE_NUM_BYTES
837 
838   return bytes;
839 }
840 
841 /*
842   Internal function for centralizing the error message generation.
843 */
844 
845 void
cc_xml_doc_handle_parse_error(const cc_xml_doc * doc)846 cc_xml_doc_handle_parse_error(const cc_xml_doc * doc)
847 {
848   assert(doc);
849   assert(doc->parser);
850 
851   const int line = XML_GetCurrentLineNumber(doc->parser);
852   const int column = XML_GetCurrentColumnNumber(doc->parser);
853 
854   const char * errormsg = XML_ErrorString(XML_GetErrorCode(doc->parser));
855 
856   SbString errorstr;
857   errorstr.sprintf("XML parse error, line %d, column %d: %s\n", line, column, errormsg);
858   fprintf(stderr, "%s", errorstr.getString());
859 }
860 
861 /*
862 */
863 
864 void
cc_xml_doc_handle_parse_warning(const cc_xml_doc * doc,const char * message)865 cc_xml_doc_handle_parse_warning(const cc_xml_doc * doc, const char * message)
866 {
867   assert(doc);
868   assert(doc->parser);
869   assert(message);
870 
871   const int line = XML_GetCurrentLineNumber(doc->parser);
872   const int column = XML_GetCurrentColumnNumber(doc->parser);
873 
874   SbString errorstr;
875   errorstr.sprintf("XML parse warning, line %d, column %d: %s\n", line, column, message);
876   fprintf(stderr, "%s", errorstr.getString());
877 }
878 
879 // *************************************************************************
880 
881 #ifdef COIN_TEST_SUITE
882 
883 #include <boost/scoped_array.hpp>
884 #include <Inventor/C/XML/parser.h>
885 #include <Inventor/C/XML/path.h>
886 
BOOST_AUTO_TEST_CASE(bufread)887 BOOST_AUTO_TEST_CASE(bufread)
888 {
889   const char * buffer =
890 "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n\n"
891 "<test value=\"one\" compact=\"\">\n"
892 "  <b>hei</b>\n"
893 "</test>\n";
894   cc_xml_doc * doc1 = cc_xml_read_buffer(buffer);
895   BOOST_CHECK_MESSAGE(doc1 != NULL, "cc_xml_doc_read_buffer() failed");
896 
897   boost::scoped_array<char> buffer2;
898   size_t bytecount = 0;
899   {
900     char * bufptr = NULL;
901     cc_xml_doc_write_to_buffer(doc1, bufptr, bytecount);
902     buffer2.reset(bufptr);
903   }
904 
905   cc_xml_doc * doc2 = cc_xml_read_buffer(buffer2.get());
906 
907   cc_xml_path * diffpath = cc_xml_doc_diff(doc1, doc2);
908   BOOST_CHECK_MESSAGE(diffpath == NULL, "document read->write->read DOM differences");
909 
910   cc_xml_doc_delete_x(doc1);
911   cc_xml_doc_delete_x(doc2);
912 }
913 
914 #endif // !COIN_TEST_SUITE
915