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