02aca4dc6786730a5ccf28169d733acc54097d11
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
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 this->filename = filename;
85 fp = Inkscape::IO::fopen_utf8name(filename, "r");
86 first = true;
87 }
90 int XmlSource::readCb( void * context, char * buffer, int len )
91 {
92 int retVal = -1;
93 if ( context ) {
94 XmlSource* self = static_cast<XmlSource*>(context);
95 retVal = self->read( buffer, len );
96 }
97 return retVal;
98 }
100 int XmlSource::closeCb(void * context)
101 {
102 if ( context ) {
103 XmlSource* self = static_cast<XmlSource*>(context);
104 self->close();
105 }
106 return 0;
107 }
109 int XmlSource::read( char *buffer, int len )
110 {
111 int retVal = 0;
112 size_t got = 0;
114 if ( first ) {
115 first = false;
116 char tmp[] = {0,0};
117 size_t some = fread( tmp, 1, 2, fp );
119 if ( (some >= 2) && (tmp[0] == 0x1f) && ((unsigned char)(tmp[1]) == 0x8b) ) {
120 //g_message(" the file being read is gzip'd. extract it");
121 fclose(fp);
122 fp = 0;
123 fp = Inkscape::IO::fopen_utf8name(filename, "r");
124 instr = new Inkscape::IO::UriInputStream(fp, dummy);
125 gzin = new Inkscape::IO::GzipInputStream(*instr);
126 int single = 0;
127 while ( (int)got < len && single >= 0 )
128 {
129 single = gzin->get();
130 if ( single >= 0 ) {
131 buffer[got++] = 0x0ff & single;
132 } else {
133 break;
134 }
135 }
136 //g_message(" extracted %d bytes this pass", got );
137 } else {
138 memcpy( buffer, tmp, some );
139 got = some;
140 }
141 } else if ( gzin ) {
142 int single = 0;
143 while ( (int)got < len && single >= 0 )
144 {
145 single = gzin->get();
146 if ( single >= 0 ) {
147 buffer[got++] = 0x0ff & single;
148 } else {
149 break;
150 }
151 }
152 //g_message(" extracted %d bytes this pass b", got );
153 } else {
154 got = fread( buffer, 1, len, fp );
155 }
157 if ( feof(fp) ) {
158 retVal = got;
159 }
160 else if ( ferror(fp) ) {
161 retVal = -1;
162 }
163 else {
164 retVal = got;
165 }
167 return retVal;
168 }
170 int XmlSource::close()
171 {
172 if ( gzin ) {
173 gzin->close();
174 delete gzin;
175 gzin = 0;
176 }
177 if ( instr ) {
178 instr->close();
179 fp = 0;
180 delete instr;
181 instr = 0;
182 }
183 if ( fp ) {
184 fclose(fp);
185 fp = 0;
186 }
187 return 0;
188 }
190 /**
191 * Reads XML from a file, including WMF files, and returns the Document.
192 * The default namespace can also be specified, if desired.
193 */
194 Document *
195 sp_repr_read_file (const gchar * filename, const gchar *default_ns)
196 {
197 xmlDocPtr doc = 0;
198 Document * rdoc = 0;
200 xmlSubstituteEntitiesDefault(1);
202 g_return_val_if_fail (filename != NULL, NULL);
203 g_return_val_if_fail (Inkscape::IO::file_test( filename, G_FILE_TEST_EXISTS ), NULL);
205 // TODO: bulia, please look over
206 gsize bytesRead = 0;
207 gsize bytesWritten = 0;
208 GError* error = NULL;
209 // TODO: need to replace with our own fopen and reading
210 gchar* localFilename = g_filename_from_utf8 ( filename,
211 -1, &bytesRead, &bytesWritten, &error);
212 g_return_val_if_fail( localFilename != NULL, NULL );
214 Inkscape::IO::dump_fopen_call( filename, "N" );
216 XmlSource src;
217 src.setFile(filename);
219 xmlDocPtr doubleDoc = xmlReadIO( XmlSource::readCb,
220 XmlSource::closeCb,
221 &src,
222 localFilename,
223 NULL, //"UTF-8",
224 XML_PARSE_NOENT );
227 #ifdef HAVE_LIBWMF
228 if (strlen (localFilename) > 4) {
229 if ( (strcmp (localFilename + strlen (localFilename) - 4,".wmf") == 0)
230 || (strcmp (localFilename + strlen (localFilename) - 4,".WMF") == 0))
231 doc = sp_wmf_convert (localFilename);
232 else
233 doc = xmlParseFile (localFilename);
234 }
235 else {
236 doc = xmlParseFile (localFilename);
237 }
238 #else /* !HAVE_LIBWMF */
239 //doc = xmlParseFile (localFilename);
240 #endif /* !HAVE_LIBWMF */
242 //rdoc = sp_repr_do_read (doc, default_ns);
243 rdoc = sp_repr_do_read (doubleDoc, default_ns);
244 if (doc)
245 xmlFreeDoc (doc);
247 if ( localFilename != NULL )
248 g_free (localFilename);
250 if ( doubleDoc != NULL )
251 {
252 xmlFreeDoc( doubleDoc );
253 }
255 return rdoc;
256 }
258 /**
259 * Reads and parses XML from a buffer, returning it as an Document
260 */
261 Document *
262 sp_repr_read_mem (const gchar * buffer, gint length, const gchar *default_ns)
263 {
264 xmlDocPtr doc;
265 Document * rdoc;
267 xmlSubstituteEntitiesDefault(1);
269 g_return_val_if_fail (buffer != NULL, NULL);
271 doc = xmlParseMemory ((gchar *) buffer, length);
273 rdoc = sp_repr_do_read (doc, default_ns);
274 if (doc)
275 xmlFreeDoc (doc);
276 return rdoc;
277 }
279 namespace Inkscape {
281 struct compare_quark_ids {
282 bool operator()(Glib::QueryQuark const &a, Glib::QueryQuark const &b) const {
283 return a.id() < b.id();
284 }
285 };
287 }
289 namespace {
291 typedef std::map<Glib::QueryQuark, Glib::QueryQuark, Inkscape::compare_quark_ids> PrefixMap;
293 Glib::QueryQuark qname_prefix(Glib::QueryQuark qname) {
294 static PrefixMap prefix_map;
295 PrefixMap::iterator iter = prefix_map.find(qname);
296 if ( iter != prefix_map.end() ) {
297 return (*iter).second;
298 } else {
299 gchar const *name_string=g_quark_to_string(qname);
300 gchar const *prefix_end=strchr(name_string, ':');
301 if (prefix_end) {
302 Glib::Quark prefix=Glib::ustring(name_string, prefix_end);
303 prefix_map.insert(PrefixMap::value_type(qname, prefix));
304 return prefix;
305 } else {
306 return GQuark(0);
307 }
308 }
309 }
311 }
313 namespace {
315 void promote_to_svg_namespace(Node *repr) {
316 if ( repr->type() == Inkscape::XML::ELEMENT_NODE ) {
317 GQuark code = repr->code();
318 if (!qname_prefix(code).id()) {
319 gchar *svg_name = g_strconcat("svg:", g_quark_to_string(code), NULL);
320 repr->setCodeUnsafe(g_quark_from_string(svg_name));
321 g_free(svg_name);
322 }
323 for ( Node *child = sp_repr_children(repr) ; child ; child = sp_repr_next(child) ) {
324 promote_to_svg_namespace(child);
325 }
326 }
327 }
329 }
331 /**
332 * Reads in a XML file to create a Document
333 */
334 Document *
335 sp_repr_do_read (xmlDocPtr doc, const gchar *default_ns)
336 {
337 if (doc == NULL) return NULL;
338 xmlNodePtr node=xmlDocGetRootElement (doc);
339 if (node == NULL) return NULL;
341 GHashTable * prefix_map;
342 prefix_map = g_hash_table_new (g_str_hash, g_str_equal);
344 GSList *reprs=NULL;
345 Node *root=NULL;
347 for ( node = doc->children ; node != NULL ; node = node->next ) {
348 if (node->type == XML_ELEMENT_NODE) {
349 Node *repr=sp_repr_svg_read_node (node, default_ns, prefix_map);
350 reprs = g_slist_append(reprs, repr);
352 if (!root) {
353 root = repr;
354 } else {
355 root = NULL;
356 break;
357 }
358 } else if ( node->type == XML_COMMENT_NODE ) {
359 Node *comment=sp_repr_svg_read_node(node, default_ns, prefix_map);
360 reprs = g_slist_append(reprs, comment);
361 }
362 }
364 Document *rdoc=NULL;
366 if (root != NULL) {
367 /* promote elements of SVG documents that don't use namespaces
368 * into the SVG namespace */
369 if ( default_ns && !strcmp(default_ns, SP_SVG_NS_URI)
370 && !strcmp(root->name(), "svg") )
371 {
372 promote_to_svg_namespace(root);
373 }
375 rdoc = sp_repr_document_new_list(reprs);
376 }
378 for ( GSList *iter = reprs ; iter ; iter = iter->next ) {
379 Node *repr=(Node *)iter->data;
380 Inkscape::GC::release(repr);
381 }
382 g_slist_free(reprs);
384 g_hash_table_destroy (prefix_map);
386 return rdoc;
387 }
389 gint
390 sp_repr_qualified_name (gchar *p, gint len, xmlNsPtr ns, const xmlChar *name, const gchar *default_ns, GHashTable *prefix_map)
391 {
392 const xmlChar *prefix;
393 if ( ns && ns->href ) {
394 prefix = (xmlChar*)sp_xml_ns_uri_prefix ((gchar*)ns->href, (char*)ns->prefix);
395 g_hash_table_insert (prefix_map, (gpointer)prefix, (gpointer)ns->href);
396 } else {
397 prefix = NULL;
398 }
400 if (prefix)
401 return g_snprintf (p, len, "%s:%s", (gchar*)prefix, name);
402 else
403 return g_snprintf (p, len, "%s", name);
404 }
406 static Node *
407 sp_repr_svg_read_node (xmlNodePtr node, const gchar *default_ns, GHashTable *prefix_map)
408 {
409 Node *repr, *crepr;
410 xmlAttrPtr prop;
411 xmlNodePtr child;
412 gchar c[256];
414 if (node->type == XML_TEXT_NODE || node->type == XML_CDATA_SECTION_NODE) {
416 if (node->content == NULL || *(node->content) == '\0')
417 return NULL; // empty text node
419 bool preserve = (xmlNodeGetSpacePreserve (node) == 1);
421 xmlChar *p;
422 for (p = node->content; *p && g_ascii_isspace (*p) && !preserve; p++)
423 ; // skip all whitespace
425 if (!(*p)) { // this is an all-whitespace node, and preserve == default
426 return NULL; // we do not preserve all-whitespace nodes unless we are asked to
427 }
429 Node *rdoc = sp_repr_new_text((const gchar *)node->content);
430 return rdoc;
431 }
433 if (node->type == XML_COMMENT_NODE)
434 return sp_repr_new_comment((const gchar *)node->content);
436 if (node->type == XML_ENTITY_DECL) return NULL;
438 sp_repr_qualified_name (c, 256, node->ns, node->name, default_ns, prefix_map);
439 repr = sp_repr_new (c);
440 /* TODO remember node->ns->prefix if node->ns != NULL */
442 for (prop = node->properties; prop != NULL; prop = prop->next) {
443 if (prop->children) {
444 sp_repr_qualified_name (c, 256, prop->ns, prop->name, default_ns, prefix_map);
445 repr->setAttribute(c, (gchar*)prop->children->content);
446 /* TODO remember prop->ns->prefix if prop->ns != NULL */
447 }
448 }
450 if (node->content)
451 repr->setContent((gchar*)node->content);
453 child = node->xmlChildrenNode;
454 for (child = node->xmlChildrenNode; child != NULL; child = child->next) {
455 crepr = sp_repr_svg_read_node (child, default_ns, prefix_map);
456 if (crepr) {
457 repr->appendChild(crepr);
458 Inkscape::GC::release(crepr);
459 }
460 }
462 return repr;
463 }
465 void
466 sp_repr_save_stream (Document *doc, FILE *fp, gchar const *default_ns, bool compress)
467 {
468 Node *repr;
469 const gchar *str;
471 Inkscape::URI dummy("x");
472 Inkscape::IO::UriOutputStream bout(fp, dummy);
473 Inkscape::IO::GzipOutputStream *gout = compress ? new Inkscape::IO::GzipOutputStream(bout) : NULL;
474 Inkscape::IO::OutputStreamWriter *out = compress ? new Inkscape::IO::OutputStreamWriter( *gout ) : new Inkscape::IO::OutputStreamWriter( bout );
476 /* fixme: do this The Right Way */
478 out->writeString( "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?>\n" );
480 str = ((Node *)doc)->attribute("doctype");
481 if (str) {
482 out->writeString( str );
483 }
485 repr = sp_repr_document_first_child(doc);
486 for ( repr = sp_repr_document_first_child(doc) ;
487 repr ; repr = sp_repr_next(repr) )
488 {
489 if ( repr->type() == Inkscape::XML::ELEMENT_NODE ) {
490 sp_repr_write_stream_root_element(repr, *out, TRUE, default_ns);
491 } else if ( repr->type() == Inkscape::XML::COMMENT_NODE ) {
492 sp_repr_write_stream(repr, *out, 0, TRUE, GQuark(0));
493 out->writeChar( '\n' );
494 } else {
495 sp_repr_write_stream(repr, *out, 0, TRUE, GQuark(0));
496 }
497 }
498 if ( out ) {
499 delete out;
500 out = NULL;
501 }
502 if ( gout ) {
503 delete gout;
504 gout = NULL;
505 }
506 }
508 /* Returns TRUE if file successfully saved; FALSE if not
509 */
510 gboolean
511 sp_repr_save_file (Document *doc, const gchar *filename,
512 gchar const *default_ns)
513 {
514 if (filename == NULL) {
515 return FALSE;
516 }
517 bool compress = false;
518 {
519 if (strlen (filename) > 5) {
520 gchar tmp[] = {0,0,0,0,0,0};
521 strncpy( tmp, filename + strlen (filename) - 5, 6 );
522 tmp[5] = 0;
523 if ( strcasecmp(".svgz", tmp ) == 0 )
524 {
525 //g_message("TIME TO COMPRESS THE OUTPUT FOR SVGZ");
526 compress = true;
527 }
528 }
529 }
531 Inkscape::IO::dump_fopen_call( filename, "B" );
532 FILE *file = Inkscape::IO::fopen_utf8name(filename, "w");
533 if (file == NULL) {
534 return FALSE;
535 }
537 sp_repr_save_stream (doc, file, default_ns, compress);
539 if (fclose (file) != 0) {
540 return FALSE;
541 }
543 return TRUE;
544 }
546 void
547 sp_repr_print (Node * repr)
548 {
549 Inkscape::IO::StdOutputStream bout;
550 Inkscape::IO::OutputStreamWriter out(bout);
552 sp_repr_write_stream (repr, out, 0, TRUE, GQuark(0));
554 return;
555 }
557 /* (No doubt this function already exists elsewhere.) */
558 static void
559 repr_quote_write (Writer &out, const gchar * val)
560 {
561 if (!val) return;
563 for (; *val != '\0'; val++) {
564 switch (*val) {
565 case '"': out.writeString( """ ); break;
566 case '&': out.writeString( "&" ); break;
567 case '<': out.writeString( "<" ); break;
568 case '>': out.writeString( ">" ); break;
569 default: out.writeChar( *val ); break;
570 }
571 }
572 }
574 namespace {
576 typedef std::map<Glib::QueryQuark, gchar const *, Inkscape::compare_quark_ids> LocalNameMap;
577 typedef std::map<Glib::QueryQuark, Inkscape::Util::SharedCStringPtr, Inkscape::compare_quark_ids> NSMap;
579 gchar const *qname_local_name(Glib::QueryQuark qname) {
580 static LocalNameMap local_name_map;
581 LocalNameMap::iterator iter = local_name_map.find(qname);
582 if ( iter != local_name_map.end() ) {
583 return (*iter).second;
584 } else {
585 gchar const *name_string=g_quark_to_string(qname);
586 gchar const *prefix_end=strchr(name_string, ':');
587 if (prefix_end) {
588 return prefix_end + 1;
589 } else {
590 return name_string;
591 }
592 }
593 }
595 void add_ns_map_entry(NSMap &ns_map, Glib::QueryQuark prefix) {
596 using Inkscape::Util::SharedCStringPtr;
598 static const Glib::QueryQuark xml_prefix("xml");
600 NSMap::iterator iter=ns_map.find(prefix);
601 if ( iter == ns_map.end() ) {
602 if (prefix.id()) {
603 gchar const *uri=sp_xml_ns_prefix_uri(g_quark_to_string(prefix));
604 if (uri) {
605 ns_map.insert(NSMap::value_type(prefix, SharedCStringPtr::coerce(uri)));
606 } else if ( prefix != xml_prefix ) {
607 g_warning("No namespace known for normalized prefix %s", g_quark_to_string(prefix));
608 }
609 } else {
610 ns_map.insert(NSMap::value_type(prefix, SharedCStringPtr()));
611 }
612 }
613 }
615 void populate_ns_map(NSMap &ns_map, Node &repr) {
616 if ( repr.type() == Inkscape::XML::ELEMENT_NODE ) {
617 add_ns_map_entry(ns_map, qname_prefix(repr.code()));
618 for ( List<AttributeRecord const> iter=repr.attributeList() ;
619 iter ; ++iter )
620 {
621 Glib::QueryQuark prefix=qname_prefix(iter->key);
622 if (prefix.id()) {
623 add_ns_map_entry(ns_map, prefix);
624 }
625 }
626 for ( Node *child=sp_repr_children(&repr) ;
627 child ; child = sp_repr_next(child) )
628 {
629 populate_ns_map(ns_map, *child);
630 }
631 }
632 }
634 }
636 void
637 sp_repr_write_stream_root_element (Node *repr, Writer &out, gboolean add_whitespace, gchar const *default_ns)
638 {
639 using Inkscape::Util::SharedCStringPtr;
640 g_assert(repr != NULL);
641 Glib::QueryQuark xml_prefix=g_quark_from_static_string("xml");
643 NSMap ns_map;
644 populate_ns_map(ns_map, *repr);
646 Glib::QueryQuark elide_prefix=GQuark(0);
647 if ( default_ns && ns_map.find(GQuark(0)) == ns_map.end() ) {
648 elide_prefix = g_quark_from_string(sp_xml_ns_uri_prefix(default_ns, NULL));
649 }
651 List<AttributeRecord const> attributes=repr->attributeList();
652 for ( NSMap::iterator iter=ns_map.begin() ; iter != ns_map.end() ; ++iter )
653 {
654 Glib::QueryQuark prefix=(*iter).first;
655 SharedCStringPtr ns_uri=(*iter).second;
657 if (prefix.id()) {
658 if ( prefix != xml_prefix ) {
659 if ( elide_prefix == prefix ) {
660 attributes = cons(AttributeRecord(g_quark_from_static_string("xmlns"), ns_uri), attributes);
661 }
663 Glib::ustring attr_name="xmlns:";
664 attr_name.append(g_quark_to_string(prefix));
665 GQuark key = g_quark_from_string(attr_name.c_str());
666 attributes = cons(AttributeRecord(key, ns_uri), attributes);
667 }
668 } else {
669 // if there are non-namespaced elements, we can't globally
670 // use a default namespace
671 elide_prefix = GQuark(0);
672 }
673 }
675 return sp_repr_write_stream_element(repr, out, 0, add_whitespace, elide_prefix, attributes);
676 }
678 void
679 sp_repr_write_stream (Node *repr, Writer &out, gint indent_level,
680 gboolean add_whitespace, Glib::QueryQuark elide_prefix)
681 {
682 if (repr->type() == Inkscape::XML::TEXT_NODE) {
683 repr_quote_write (out, repr->content());
684 } else if (repr->type() == Inkscape::XML::COMMENT_NODE) {
685 out.printf( "<!--%s-->", repr->content() );
686 } else if (repr->type() == Inkscape::XML::ELEMENT_NODE) {
687 sp_repr_write_stream_element(repr, out, indent_level, add_whitespace, elide_prefix, repr->attributeList());
688 } else {
689 g_assert_not_reached();
690 }
691 }
693 void
694 sp_repr_write_stream_element (Node * repr, Writer & out, gint indent_level,
695 gboolean add_whitespace,
696 Glib::QueryQuark elide_prefix,
697 List<AttributeRecord const> attributes)
698 {
699 Node *child;
700 gboolean loose;
701 gint i;
703 g_return_if_fail (repr != NULL);
705 if ( indent_level > 16 )
706 indent_level = 16;
708 if (add_whitespace) {
709 for ( i = 0 ; i < indent_level ; i++ ) {
710 out.writeString( " " );
711 }
712 }
714 GQuark code = repr->code();
715 gchar const *element_name;
716 if ( elide_prefix == qname_prefix(code) ) {
717 element_name = qname_local_name(code);
718 } else {
719 element_name = g_quark_to_string(code);
720 }
721 out.printf( "<%s", element_name );
723 // if this is a <text> element, suppress formatting whitespace
724 // for its content and children:
725 gchar const *xml_space_attr = repr->attribute("xml:space");
726 if (xml_space_attr != NULL && !strcmp(xml_space_attr, "preserve")) {
727 add_whitespace = FALSE;
728 }
730 for ( List<AttributeRecord const> iter = attributes ;
731 iter ; ++iter )
732 {
733 out.writeString("\n");
734 for ( i = 0 ; i < indent_level + 1 ; i++ ) {
735 out.writeString(" ");
736 }
737 out.printf(" %s=\"", g_quark_to_string(iter->key));
738 repr_quote_write(out, iter->value);
739 out.writeChar('"');
740 }
742 loose = TRUE;
743 for (child = repr->firstChild() ; child != NULL; child = child->next()) {
744 if (child->type() == Inkscape::XML::TEXT_NODE) {
745 loose = FALSE;
746 break;
747 }
748 }
749 if (repr->firstChild()) {
750 out.writeString( ">" );
751 if (loose && add_whitespace) {
752 out.writeString( "\n" );
753 }
754 for (child = repr->firstChild(); child != NULL; child = child->next()) {
755 sp_repr_write_stream (child, out, (loose) ? (indent_level + 1) : 0, add_whitespace, elide_prefix);
756 }
758 if (loose && add_whitespace) {
759 for (i = 0; i < indent_level; i++) {
760 out.writeString( " " );
761 }
762 }
763 out.printf( "</%s>", element_name );
764 } else {
765 out.writeString( " />" );
766 }
768 // text elements cannot nest, so we can output newline
769 // after closing text
771 if (add_whitespace || !strcmp (repr->name(), "svg:text")) {
772 out.writeString( "\n" );
773 }
774 }
777 /*
778 Local Variables:
779 mode:c++
780 c-file-style:"stroustrup"
781 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
782 indent-tabs-mode:nil
783 fill-column:99
784 End:
785 */
786 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :