1 #define __SP_REPR_IO_C__
3 /*
4 * Dirty DOM-like tree
5 *
6 * Authors:
7 * Lauris Kaplinski <lauris@kaplinski.com>
8 * bulia byak <buliabyak@users.sf.net>
9 *
10 * Copyright (C) 1999-2002 Lauris Kaplinski
11 *
12 * Released under GNU GPL, read the file 'COPYING' for more information
13 */
15 #ifdef HAVE_CONFIG_H
16 # include <config.h>
17 #endif
19 #include <stdexcept>
21 #include "xml/repr.h"
22 #include "xml/attribute-record.h"
24 #include "io/sys.h"
25 #include "io/uristream.h"
26 #include "io/gzipstream.h"
29 using Inkscape::IO::Writer;
30 using Inkscape::Util::List;
31 using Inkscape::Util::cons;
32 using Inkscape::XML::Document;
33 using Inkscape::XML::Node;
34 using Inkscape::XML::AttributeRecord;
36 static Document *sp_repr_do_read (xmlDocPtr doc, const gchar *default_ns);
37 static Node *sp_repr_svg_read_node (xmlNodePtr node, const gchar *default_ns, GHashTable *prefix_map);
38 static gint sp_repr_qualified_name (gchar *p, gint len, xmlNsPtr ns, const xmlChar *name, const gchar *default_ns, GHashTable *prefix_map);
39 static void sp_repr_write_stream_root_element (Node *repr, Writer &out, gboolean add_whitespace, gchar const *default_ns);
40 static void sp_repr_write_stream (Node *repr, Writer &out, gint indent_level, gboolean add_whitespace, Glib::QueryQuark elide_prefix);
41 static void sp_repr_write_stream_element (Node *repr, Writer &out, gint indent_level, gboolean add_whitespace, Glib::QueryQuark elide_prefix, List<AttributeRecord const> attributes);
43 #ifdef HAVE_LIBWMF
44 static xmlDocPtr sp_wmf_convert (const char * file_name);
45 static char * sp_wmf_image_name (void * context);
46 #endif /* HAVE_LIBWMF */
49 class XmlSource
50 {
51 public:
52 XmlSource()
53 : filename(0),
54 fp(0),
55 first(false),
56 dummy("x"),
57 instr(0),
58 gzin(0)
59 {
60 }
61 virtual ~XmlSource()
62 {
63 close();
64 }
66 void setFile( char const * filename );
68 static int readCb( void * context, char * buffer, int len);
69 static int closeCb(void * context);
72 int read( char * buffer, int len );
73 int close();
74 private:
75 const char* filename;
76 FILE* fp;
77 bool first;
78 Inkscape::URI dummy;
79 Inkscape::IO::UriInputStream* instr;
80 Inkscape::IO::GzipInputStream* gzin;
81 };
83 void XmlSource::setFile(char const *filename)
84 {
85 this->filename = filename;
86 fp = Inkscape::IO::fopen_utf8name(filename, "r");
87 if (fp == NULL) {
88 throw std::runtime_error("Could not open file for reading");
89 }
90 first = true;
91 }
94 int XmlSource::readCb( void * context, char * buffer, int len )
95 {
96 int retVal = -1;
97 if ( context ) {
98 XmlSource* self = static_cast<XmlSource*>(context);
99 retVal = self->read( buffer, len );
100 }
101 return retVal;
102 }
104 int XmlSource::closeCb(void * context)
105 {
106 if ( context ) {
107 XmlSource* self = static_cast<XmlSource*>(context);
108 self->close();
109 }
110 return 0;
111 }
113 int XmlSource::read( char *buffer, int len )
114 {
115 int retVal = 0;
116 size_t got = 0;
118 if ( first ) {
119 first = false;
120 char tmp[] = {0,0};
121 size_t some = fread( tmp, 1, 2, fp );
123 if ( (some >= 2) && (tmp[0] == 0x1f) && ((unsigned char)(tmp[1]) == 0x8b) ) {
124 //g_message(" the file being read is gzip'd. extract it");
125 fclose(fp);
126 fp = 0;
127 fp = Inkscape::IO::fopen_utf8name(filename, "r");
128 instr = new Inkscape::IO::UriInputStream(fp, dummy);
129 gzin = new Inkscape::IO::GzipInputStream(*instr);
130 int single = 0;
131 while ( (int)got < len && single >= 0 )
132 {
133 single = gzin->get();
134 if ( single >= 0 ) {
135 buffer[got++] = 0x0ff & single;
136 } else {
137 break;
138 }
139 }
140 //g_message(" extracted %d bytes this pass", got );
141 } else {
142 memcpy( buffer, tmp, some );
143 got = some;
144 }
145 } else if ( gzin ) {
146 int single = 0;
147 while ( (int)got < len && single >= 0 )
148 {
149 single = gzin->get();
150 if ( single >= 0 ) {
151 buffer[got++] = 0x0ff & single;
152 } else {
153 break;
154 }
155 }
156 //g_message(" extracted %d bytes this pass b", got );
157 } else {
158 got = fread( buffer, 1, len, fp );
159 }
161 if ( feof(fp) ) {
162 retVal = got;
163 }
164 else if ( ferror(fp) ) {
165 retVal = -1;
166 }
167 else {
168 retVal = got;
169 }
171 return retVal;
172 }
174 int XmlSource::close()
175 {
176 if ( gzin ) {
177 gzin->close();
178 delete gzin;
179 gzin = 0;
180 }
181 if ( instr ) {
182 instr->close();
183 fp = 0;
184 delete instr;
185 instr = 0;
186 }
187 if ( fp ) {
188 fclose(fp);
189 fp = 0;
190 }
191 return 0;
192 }
194 /**
195 * Reads XML from a file, including WMF files, and returns the Document.
196 * The default namespace can also be specified, if desired.
197 */
198 Document *
199 sp_repr_read_file (const gchar * filename, const gchar *default_ns)
200 {
201 xmlDocPtr doc = 0;
202 Document * rdoc = 0;
204 xmlSubstituteEntitiesDefault(1);
206 g_return_val_if_fail (filename != NULL, NULL);
207 g_return_val_if_fail (Inkscape::IO::file_test( filename, G_FILE_TEST_EXISTS ), NULL);
209 // TODO: bulia, please look over
210 gsize bytesRead = 0;
211 gsize bytesWritten = 0;
212 GError* error = NULL;
213 // TODO: need to replace with our own fopen and reading
214 gchar* localFilename = g_filename_from_utf8 ( filename,
215 -1, &bytesRead, &bytesWritten, &error);
216 g_return_val_if_fail( localFilename != NULL, NULL );
218 Inkscape::IO::dump_fopen_call( filename, "N" );
220 XmlSource src;
221 try
222 {
223 src.setFile(filename);
224 }
225 catch (...)
226 {
227 return NULL;
228 }
230 xmlDocPtr doubleDoc = xmlReadIO( XmlSource::readCb,
231 XmlSource::closeCb,
232 &src,
233 localFilename,
234 NULL, //"UTF-8",
235 XML_PARSE_NOENT );
238 #ifdef HAVE_LIBWMF
239 if (strlen (localFilename) > 4) {
240 if ( (strcmp (localFilename + strlen (localFilename) - 4,".wmf") == 0)
241 || (strcmp (localFilename + strlen (localFilename) - 4,".WMF") == 0))
242 doc = sp_wmf_convert (localFilename);
243 else
244 doc = xmlParseFile (localFilename);
245 }
246 else {
247 doc = xmlParseFile (localFilename);
248 }
249 #else /* !HAVE_LIBWMF */
250 //doc = xmlParseFile (localFilename);
251 #endif /* !HAVE_LIBWMF */
253 //rdoc = sp_repr_do_read (doc, default_ns);
254 rdoc = sp_repr_do_read (doubleDoc, default_ns);
255 if (doc)
256 xmlFreeDoc (doc);
258 if ( localFilename != NULL )
259 g_free (localFilename);
261 if ( doubleDoc != NULL )
262 {
263 xmlFreeDoc( doubleDoc );
264 }
266 return rdoc;
267 }
269 /**
270 * Reads and parses XML from a buffer, returning it as an Document
271 */
272 Document *
273 sp_repr_read_mem (const gchar * buffer, gint length, const gchar *default_ns)
274 {
275 xmlDocPtr doc;
276 Document * rdoc;
278 xmlSubstituteEntitiesDefault(1);
280 g_return_val_if_fail (buffer != NULL, NULL);
282 doc = xmlParseMemory ((gchar *) buffer, length);
284 rdoc = sp_repr_do_read (doc, default_ns);
285 if (doc)
286 xmlFreeDoc (doc);
287 return rdoc;
288 }
290 namespace Inkscape {
292 struct compare_quark_ids {
293 bool operator()(Glib::QueryQuark const &a, Glib::QueryQuark const &b) const {
294 return a.id() < b.id();
295 }
296 };
298 }
300 namespace {
302 typedef std::map<Glib::QueryQuark, Glib::QueryQuark, Inkscape::compare_quark_ids> PrefixMap;
304 Glib::QueryQuark qname_prefix(Glib::QueryQuark qname) {
305 static PrefixMap prefix_map;
306 PrefixMap::iterator iter = prefix_map.find(qname);
307 if ( iter != prefix_map.end() ) {
308 return (*iter).second;
309 } else {
310 gchar const *name_string=g_quark_to_string(qname);
311 gchar const *prefix_end=strchr(name_string, ':');
312 if (prefix_end) {
313 Glib::Quark prefix=Glib::ustring(name_string, prefix_end);
314 prefix_map.insert(PrefixMap::value_type(qname, prefix));
315 return prefix;
316 } else {
317 return GQuark(0);
318 }
319 }
320 }
322 }
324 namespace {
326 void promote_to_svg_namespace(Node *repr) {
327 if ( repr->type() == Inkscape::XML::ELEMENT_NODE ) {
328 GQuark code = repr->code();
329 if (!qname_prefix(code).id()) {
330 gchar *svg_name = g_strconcat("svg:", g_quark_to_string(code), NULL);
331 repr->setCodeUnsafe(g_quark_from_string(svg_name));
332 g_free(svg_name);
333 }
334 for ( Node *child = sp_repr_children(repr) ; child ; child = sp_repr_next(child) ) {
335 promote_to_svg_namespace(child);
336 }
337 }
338 }
340 }
342 /**
343 * Reads in a XML file to create a Document
344 */
345 Document *
346 sp_repr_do_read (xmlDocPtr doc, const gchar *default_ns)
347 {
348 if (doc == NULL) return NULL;
349 xmlNodePtr node=xmlDocGetRootElement (doc);
350 if (node == NULL) return NULL;
352 GHashTable * prefix_map;
353 prefix_map = g_hash_table_new (g_str_hash, g_str_equal);
355 GSList *reprs=NULL;
356 Node *root=NULL;
358 for ( node = doc->children ; node != NULL ; node = node->next ) {
359 if (node->type == XML_ELEMENT_NODE) {
360 Node *repr=sp_repr_svg_read_node (node, default_ns, prefix_map);
361 reprs = g_slist_append(reprs, repr);
363 if (!root) {
364 root = repr;
365 } else {
366 root = NULL;
367 break;
368 }
369 } else if ( node->type == XML_COMMENT_NODE ) {
370 Node *comment=sp_repr_svg_read_node(node, default_ns, prefix_map);
371 reprs = g_slist_append(reprs, comment);
372 }
373 }
375 Document *rdoc=NULL;
377 if (root != NULL) {
378 /* promote elements of SVG documents that don't use namespaces
379 * into the SVG namespace */
380 if ( default_ns && !strcmp(default_ns, SP_SVG_NS_URI)
381 && !strcmp(root->name(), "svg") )
382 {
383 promote_to_svg_namespace(root);
384 }
386 rdoc = sp_repr_document_new_list(reprs);
387 }
389 for ( GSList *iter = reprs ; iter ; iter = iter->next ) {
390 Node *repr=(Node *)iter->data;
391 Inkscape::GC::release(repr);
392 }
393 g_slist_free(reprs);
395 g_hash_table_destroy (prefix_map);
397 return rdoc;
398 }
400 gint
401 sp_repr_qualified_name (gchar *p, gint len, xmlNsPtr ns, const xmlChar *name, const gchar *default_ns, GHashTable *prefix_map)
402 {
403 const xmlChar *prefix;
404 if ( ns && ns->href ) {
405 prefix = (xmlChar*)sp_xml_ns_uri_prefix ((gchar*)ns->href, (char*)ns->prefix);
406 g_hash_table_insert (prefix_map, (gpointer)prefix, (gpointer)ns->href);
407 } else {
408 prefix = NULL;
409 }
411 if (prefix)
412 return g_snprintf (p, len, "%s:%s", (gchar*)prefix, name);
413 else
414 return g_snprintf (p, len, "%s", name);
415 }
417 static Node *
418 sp_repr_svg_read_node (xmlNodePtr node, const gchar *default_ns, GHashTable *prefix_map)
419 {
420 Node *repr, *crepr;
421 xmlAttrPtr prop;
422 xmlNodePtr child;
423 gchar c[256];
425 if (node->type == XML_TEXT_NODE || node->type == XML_CDATA_SECTION_NODE) {
427 if (node->content == NULL || *(node->content) == '\0')
428 return NULL; // empty text node
430 bool preserve = (xmlNodeGetSpacePreserve (node) == 1);
432 xmlChar *p;
433 for (p = node->content; *p && g_ascii_isspace (*p) && !preserve; p++)
434 ; // skip all whitespace
436 if (!(*p)) { // this is an all-whitespace node, and preserve == default
437 return NULL; // we do not preserve all-whitespace nodes unless we are asked to
438 }
440 Node *rdoc = sp_repr_new_text((const gchar *)node->content);
441 return rdoc;
442 }
444 if (node->type == XML_COMMENT_NODE)
445 return sp_repr_new_comment((const gchar *)node->content);
447 if (node->type == XML_ENTITY_DECL) return NULL;
449 sp_repr_qualified_name (c, 256, node->ns, node->name, default_ns, prefix_map);
450 repr = sp_repr_new (c);
451 /* TODO remember node->ns->prefix if node->ns != NULL */
453 for (prop = node->properties; prop != NULL; prop = prop->next) {
454 if (prop->children) {
455 sp_repr_qualified_name (c, 256, prop->ns, prop->name, default_ns, prefix_map);
456 repr->setAttribute(c, (gchar*)prop->children->content);
457 /* TODO remember prop->ns->prefix if prop->ns != NULL */
458 }
459 }
461 if (node->content)
462 repr->setContent((gchar*)node->content);
464 child = node->xmlChildrenNode;
465 for (child = node->xmlChildrenNode; child != NULL; child = child->next) {
466 crepr = sp_repr_svg_read_node (child, default_ns, prefix_map);
467 if (crepr) {
468 repr->appendChild(crepr);
469 Inkscape::GC::release(crepr);
470 }
471 }
473 return repr;
474 }
476 void
477 sp_repr_save_stream (Document *doc, FILE *fp, gchar const *default_ns, bool compress)
478 {
479 Node *repr;
480 const gchar *str;
482 Inkscape::URI dummy("x");
483 Inkscape::IO::UriOutputStream bout(fp, dummy);
484 Inkscape::IO::GzipOutputStream *gout = compress ? new Inkscape::IO::GzipOutputStream(bout) : NULL;
485 Inkscape::IO::OutputStreamWriter *out = compress ? new Inkscape::IO::OutputStreamWriter( *gout ) : new Inkscape::IO::OutputStreamWriter( bout );
487 /* fixme: do this The Right Way */
489 out->writeString( "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?>\n" );
491 str = ((Node *)doc)->attribute("doctype");
492 if (str) {
493 out->writeString( str );
494 }
496 repr = sp_repr_document_first_child(doc);
497 for ( repr = sp_repr_document_first_child(doc) ;
498 repr ; repr = sp_repr_next(repr) )
499 {
500 if ( repr->type() == Inkscape::XML::ELEMENT_NODE ) {
501 sp_repr_write_stream_root_element(repr, *out, TRUE, default_ns);
502 } else if ( repr->type() == Inkscape::XML::COMMENT_NODE ) {
503 sp_repr_write_stream(repr, *out, 0, TRUE, GQuark(0));
504 out->writeChar( '\n' );
505 } else {
506 sp_repr_write_stream(repr, *out, 0, TRUE, GQuark(0));
507 }
508 }
509 if ( out ) {
510 delete out;
511 out = NULL;
512 }
513 if ( gout ) {
514 delete gout;
515 gout = NULL;
516 }
517 }
519 /* Returns TRUE if file successfully saved; FALSE if not
520 */
521 gboolean
522 sp_repr_save_file (Document *doc, const gchar *filename,
523 gchar const *default_ns)
524 {
525 if (filename == NULL) {
526 return FALSE;
527 }
528 bool compress = false;
529 {
530 if (strlen (filename) > 5) {
531 gchar tmp[] = {0,0,0,0,0,0};
532 strncpy( tmp, filename + strlen (filename) - 5, 6 );
533 tmp[5] = 0;
534 if ( strcasecmp(".svgz", tmp ) == 0 )
535 {
536 //g_message("TIME TO COMPRESS THE OUTPUT FOR SVGZ");
537 compress = true;
538 }
539 }
540 }
542 Inkscape::IO::dump_fopen_call( filename, "B" );
543 FILE *file = Inkscape::IO::fopen_utf8name(filename, "w");
544 if (file == NULL) {
545 return FALSE;
546 }
548 sp_repr_save_stream (doc, file, default_ns, compress);
550 if (fclose (file) != 0) {
551 return FALSE;
552 }
554 return TRUE;
555 }
557 void
558 sp_repr_print (Node * repr)
559 {
560 Inkscape::IO::StdOutputStream bout;
561 Inkscape::IO::OutputStreamWriter out(bout);
563 sp_repr_write_stream (repr, out, 0, TRUE, GQuark(0));
565 return;
566 }
568 /* (No doubt this function already exists elsewhere.) */
569 static void
570 repr_quote_write (Writer &out, const gchar * val)
571 {
572 if (!val) return;
574 for (; *val != '\0'; val++) {
575 switch (*val) {
576 case '"': out.writeString( """ ); break;
577 case '&': out.writeString( "&" ); break;
578 case '<': out.writeString( "<" ); break;
579 case '>': out.writeString( ">" ); break;
580 default: out.writeChar( *val ); break;
581 }
582 }
583 }
585 namespace {
587 typedef std::map<Glib::QueryQuark, gchar const *, Inkscape::compare_quark_ids> LocalNameMap;
588 typedef std::map<Glib::QueryQuark, Inkscape::Util::ptr_shared<char>, Inkscape::compare_quark_ids> NSMap;
590 gchar const *qname_local_name(Glib::QueryQuark qname) {
591 static LocalNameMap local_name_map;
592 LocalNameMap::iterator iter = local_name_map.find(qname);
593 if ( iter != local_name_map.end() ) {
594 return (*iter).second;
595 } else {
596 gchar const *name_string=g_quark_to_string(qname);
597 gchar const *prefix_end=strchr(name_string, ':');
598 if (prefix_end) {
599 return prefix_end + 1;
600 } else {
601 return name_string;
602 }
603 }
604 }
606 void add_ns_map_entry(NSMap &ns_map, Glib::QueryQuark prefix) {
607 using Inkscape::Util::ptr_shared;
608 using Inkscape::Util::share_unsafe;
610 static const Glib::QueryQuark xml_prefix("xml");
612 NSMap::iterator iter=ns_map.find(prefix);
613 if ( iter == ns_map.end() ) {
614 if (prefix.id()) {
615 gchar const *uri=sp_xml_ns_prefix_uri(g_quark_to_string(prefix));
616 if (uri) {
617 ns_map.insert(NSMap::value_type(prefix, share_unsafe(uri)));
618 } else if ( prefix != xml_prefix ) {
619 g_warning("No namespace known for normalized prefix %s", g_quark_to_string(prefix));
620 }
621 } else {
622 ns_map.insert(NSMap::value_type(prefix, ptr_shared<char>()));
623 }
624 }
625 }
627 void populate_ns_map(NSMap &ns_map, Node &repr) {
628 if ( repr.type() == Inkscape::XML::ELEMENT_NODE ) {
629 add_ns_map_entry(ns_map, qname_prefix(repr.code()));
630 for ( List<AttributeRecord const> iter=repr.attributeList() ;
631 iter ; ++iter )
632 {
633 Glib::QueryQuark prefix=qname_prefix(iter->key);
634 if (prefix.id()) {
635 add_ns_map_entry(ns_map, prefix);
636 }
637 }
638 for ( Node *child=sp_repr_children(&repr) ;
639 child ; child = sp_repr_next(child) )
640 {
641 populate_ns_map(ns_map, *child);
642 }
643 }
644 }
646 }
648 void
649 sp_repr_write_stream_root_element (Node *repr, Writer &out, gboolean add_whitespace, gchar const *default_ns)
650 {
651 using Inkscape::Util::ptr_shared;
652 g_assert(repr != NULL);
653 Glib::QueryQuark xml_prefix=g_quark_from_static_string("xml");
655 NSMap ns_map;
656 populate_ns_map(ns_map, *repr);
658 Glib::QueryQuark elide_prefix=GQuark(0);
659 if ( default_ns && ns_map.find(GQuark(0)) == ns_map.end() ) {
660 elide_prefix = g_quark_from_string(sp_xml_ns_uri_prefix(default_ns, NULL));
661 }
663 List<AttributeRecord const> attributes=repr->attributeList();
664 for ( NSMap::iterator iter=ns_map.begin() ; iter != ns_map.end() ; ++iter )
665 {
666 Glib::QueryQuark prefix=(*iter).first;
667 ptr_shared<char> ns_uri=(*iter).second;
669 if (prefix.id()) {
670 if ( prefix != xml_prefix ) {
671 if ( elide_prefix == prefix ) {
672 attributes = cons(AttributeRecord(g_quark_from_static_string("xmlns"), ns_uri), attributes);
673 }
675 Glib::ustring attr_name="xmlns:";
676 attr_name.append(g_quark_to_string(prefix));
677 GQuark key = g_quark_from_string(attr_name.c_str());
678 attributes = cons(AttributeRecord(key, ns_uri), attributes);
679 }
680 } else {
681 // if there are non-namespaced elements, we can't globally
682 // use a default namespace
683 elide_prefix = GQuark(0);
684 }
685 }
687 return sp_repr_write_stream_element(repr, out, 0, add_whitespace, elide_prefix, attributes);
688 }
690 void
691 sp_repr_write_stream (Node *repr, Writer &out, gint indent_level,
692 gboolean add_whitespace, Glib::QueryQuark elide_prefix)
693 {
694 if (repr->type() == Inkscape::XML::TEXT_NODE) {
695 repr_quote_write (out, repr->content());
696 } else if (repr->type() == Inkscape::XML::COMMENT_NODE) {
697 out.printf( "<!--%s-->", repr->content() );
698 } else if (repr->type() == Inkscape::XML::ELEMENT_NODE) {
699 sp_repr_write_stream_element(repr, out, indent_level, add_whitespace, elide_prefix, repr->attributeList());
700 } else {
701 g_assert_not_reached();
702 }
703 }
705 void
706 sp_repr_write_stream_element (Node * repr, Writer & out, gint indent_level,
707 gboolean add_whitespace,
708 Glib::QueryQuark elide_prefix,
709 List<AttributeRecord const> attributes)
710 {
711 Node *child;
712 gboolean loose;
713 gint i;
715 g_return_if_fail (repr != NULL);
717 if ( indent_level > 16 )
718 indent_level = 16;
720 if (add_whitespace) {
721 for ( i = 0 ; i < indent_level ; i++ ) {
722 out.writeString( " " );
723 }
724 }
726 GQuark code = repr->code();
727 gchar const *element_name;
728 if ( elide_prefix == qname_prefix(code) ) {
729 element_name = qname_local_name(code);
730 } else {
731 element_name = g_quark_to_string(code);
732 }
733 out.printf( "<%s", element_name );
735 // if this is a <text> element, suppress formatting whitespace
736 // for its content and children:
737 gchar const *xml_space_attr = repr->attribute("xml:space");
738 if (xml_space_attr != NULL && !strcmp(xml_space_attr, "preserve")) {
739 add_whitespace = FALSE;
740 }
742 for ( List<AttributeRecord const> iter = attributes ;
743 iter ; ++iter )
744 {
745 out.writeString("\n");
746 for ( i = 0 ; i < indent_level + 1 ; i++ ) {
747 out.writeString(" ");
748 }
749 out.printf(" %s=\"", g_quark_to_string(iter->key));
750 repr_quote_write(out, iter->value);
751 out.writeChar('"');
752 }
754 loose = TRUE;
755 for (child = repr->firstChild() ; child != NULL; child = child->next()) {
756 if (child->type() == Inkscape::XML::TEXT_NODE) {
757 loose = FALSE;
758 break;
759 }
760 }
761 if (repr->firstChild()) {
762 out.writeString( ">" );
763 if (loose && add_whitespace) {
764 out.writeString( "\n" );
765 }
766 for (child = repr->firstChild(); child != NULL; child = child->next()) {
767 sp_repr_write_stream (child, out, (loose) ? (indent_level + 1) : 0, add_whitespace, elide_prefix);
768 }
770 if (loose && add_whitespace) {
771 for (i = 0; i < indent_level; i++) {
772 out.writeString( " " );
773 }
774 }
775 out.printf( "</%s>", element_name );
776 } else {
777 out.writeString( " />" );
778 }
780 // text elements cannot nest, so we can output newline
781 // after closing text
783 if (add_whitespace || !strcmp (repr->name(), "svg:text")) {
784 out.writeString( "\n" );
785 }
786 }
789 /*
790 Local Variables:
791 mode:c++
792 c-file-style:"stroustrup"
793 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
794 indent-tabs-mode:nil
795 fill-column:99
796 End:
797 */
798 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :