Code

export sp_repr_write_stream()
[inkscape.git] / src / xml / repr-io.cpp
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 <cstring>
20 #include <string>
21 #include <stdexcept>
23 #include "xml/repr.h"
24 #include "xml/attribute-record.h"
25 #include "xml/simple-document.h"
27 #include "io/sys.h"
28 #include "io/uristream.h"
29 #include "io/gzipstream.h"
31 #include "prefs-utils.h"
33 using Inkscape::IO::Writer;
34 using Inkscape::Util::List;
35 using Inkscape::Util::cons;
36 using Inkscape::XML::Document;
37 using Inkscape::XML::SimpleDocument;
38 using Inkscape::XML::Node;
39 using Inkscape::XML::AttributeRecord;
41 Document *sp_repr_do_read (xmlDocPtr doc, const gchar *default_ns);
42 static Node *sp_repr_svg_read_node (Document *xml_doc, xmlNodePtr node, const gchar *default_ns, GHashTable *prefix_map);
43 static gint sp_repr_qualified_name (gchar *p, gint len, xmlNsPtr ns, const xmlChar *name, const gchar *default_ns, GHashTable *prefix_map);
44 static void sp_repr_write_stream_root_element (Node *repr, Writer &out, bool add_whitespace, gchar const *default_ns, int inlineattrs, int indent);
45 static void sp_repr_write_stream_element (Node *repr, Writer &out, gint indent_level, bool add_whitespace, Glib::QueryQuark elide_prefix, List<AttributeRecord const> attributes, int inlineattrs, int indent);
47 #ifdef HAVE_LIBWMF
48 static xmlDocPtr sp_wmf_convert (const char * file_name);
49 static char * sp_wmf_image_name (void * context);
50 #endif /* HAVE_LIBWMF */
53 class XmlSource
54 {
55 public:
56     XmlSource()
57         : filename(0),
58           encoding(0),
59           fp(0),
60           firstFewLen(0),
61           dummy("x"),
62           instr(0),
63           gzin(0)
64     {
65     }
66     virtual ~XmlSource()
67     {
68         close();
69         if ( encoding ) {
70             g_free(encoding);
71             encoding = 0;
72         }
73     }
75     int setFile( char const * filename );
77     static int readCb( void * context, char * buffer, int len );
78     static int closeCb( void * context );
80     char const* getEncoding() const { return encoding; }
81     int read( char * buffer, int len );
82     int close();
83 private:
84     const char* filename;
85     char* encoding;
86     FILE* fp;
87     unsigned char firstFew[4];
88     int firstFewLen;
89     Inkscape::URI dummy;
90     Inkscape::IO::UriInputStream* instr;
91     Inkscape::IO::GzipInputStream* gzin;
92 };
94 int XmlSource::setFile(char const *filename)
95 {
96     int retVal = -1;
98     this->filename = filename;
100     fp = Inkscape::IO::fopen_utf8name(filename, "r");
101     if ( fp ) {
102         // First peek in the file to see what it is
103         memset( firstFew, 0, sizeof(firstFew) );
105         size_t some = fread( firstFew, 1, 4, fp );
106         if ( fp ) {
107             // first check for compression
108             if ( (some >= 2) && (firstFew[0] == 0x1f) && (firstFew[1] == 0x8b) ) {
109                 //g_message(" the file being read is gzip'd. extract it");
110                 fclose(fp);
111                 fp = 0;
112                 fp = Inkscape::IO::fopen_utf8name(filename, "r");
113                 instr = new Inkscape::IO::UriInputStream(fp, dummy);
114                 gzin = new Inkscape::IO::GzipInputStream(*instr);
116                 memset( firstFew, 0, sizeof(firstFew) );
117                 some = 0;
118                 int single = 0;
119                 while ( some < 4 && single >= 0 )
120                 {
121                     single = gzin->get();
122                     if ( single >= 0 ) {
123                         firstFew[some++] = 0x0ff & single;
124                     } else {
125                         break;
126                     }
127                 }
128             }
130             int encSkip = 0;
131             if ( (some >= 2) &&(firstFew[0] == 0xfe) && (firstFew[1] == 0xff) ) {
132                 encoding = g_strdup("UTF-16BE");
133                 encSkip = 2;
134             } else if ( (some >= 2) && (firstFew[0] == 0xff) && (firstFew[1] == 0xfe) ) {
135                 encoding = g_strdup("UTF-16LE");
136                 encSkip = 2;
137             } else if ( (some >= 3) && (firstFew[0] == 0xef) && (firstFew[1] == 0xbb) && (firstFew[2] == 0xbf) ) {
138                 encoding = g_strdup("UTF-8");
139                 encSkip = 3;
140             }
142             if ( encSkip ) {
143                 memmove( firstFew, firstFew + encSkip, (some - encSkip) );
144                 some -= encSkip;
145             }
147             firstFewLen = some;
148             retVal = 0; // no error
149         }
150     }
152     return retVal;
156 int XmlSource::readCb( void * context, char * buffer, int len )
158     int retVal = -1;
159     if ( context ) {
160         XmlSource* self = static_cast<XmlSource*>(context);
161         retVal = self->read( buffer, len );
162     }
163     return retVal;
166 int XmlSource::closeCb(void * context)
168     if ( context ) {
169         XmlSource* self = static_cast<XmlSource*>(context);
170         self->close();
171     }
172     return 0;
175 int XmlSource::read( char *buffer, int len )
177     int retVal = 0;
178     size_t got = 0;
180     if ( firstFewLen > 0 ) {
181         int some = (len < firstFewLen) ? len : firstFewLen;
182         memcpy( buffer, firstFew, some );
183         if ( len < firstFewLen ) {
184             memmove( firstFew, firstFew + some, (firstFewLen - some) );
185         }
186         firstFewLen -= some;
187         got = some;
188     } else if ( gzin ) {
189         int single = 0;
190         while ( (int)got < len && single >= 0 )
191         {
192             single = gzin->get();
193             if ( single >= 0 ) {
194                 buffer[got++] = 0x0ff & single;
195             } else {
196                 break;
197             }
198         }
199     } else {
200         got = fread( buffer, 1, len, fp );
201     }
203     if ( feof(fp) ) {
204         retVal = got;
205     } else if ( ferror(fp) ) {
206         retVal = -1;
207     } else {
208         retVal = got;
209     }
211     return retVal;
214 int XmlSource::close()
216     if ( gzin ) {
217         gzin->close();
218         delete gzin;
219         gzin = 0;
220     }
221     if ( instr ) {
222         instr->close();
223         fp = 0;
224         delete instr;
225         instr = 0;
226     }
227     if ( fp ) {
228         fclose(fp);
229         fp = 0;
230     }
231     return 0;
234 /**
235  * Reads XML from a file, including WMF files, and returns the Document.
236  * The default namespace can also be specified, if desired.
237  */
238 Document *
239 sp_repr_read_file (const gchar * filename, const gchar *default_ns)
241     xmlDocPtr doc = 0;
242     Document * rdoc = 0;
244     xmlSubstituteEntitiesDefault(1);
246     g_return_val_if_fail (filename != NULL, NULL);
247     g_return_val_if_fail (Inkscape::IO::file_test( filename, G_FILE_TEST_EXISTS ), NULL);
248     /* fixme: A file can disappear at any time, including between now and when we actually try to
249      * open it.  Get rid of the above test once we're sure that we correctly handle
250      * non-existence. */
252     // TODO: bulia, please look over
253     gsize bytesRead = 0;
254     gsize bytesWritten = 0;
255     GError* error = NULL;
256     // TODO: need to replace with our own fopen and reading
257     gchar* localFilename = g_filename_from_utf8 ( filename,
258                                  -1,  &bytesRead,  &bytesWritten, &error);
259     g_return_val_if_fail( localFilename != NULL, NULL );
261     Inkscape::IO::dump_fopen_call( filename, "N" );
263 #ifdef HAVE_LIBWMF
264     if (strlen (localFilename) > 4) {
265         if ( (strcmp (localFilename + strlen (localFilename) - 4,".wmf") == 0)
266              || (strcmp (localFilename + strlen (localFilename) - 4,".WMF") == 0)) {
267             doc = sp_wmf_convert (localFilename);
268         }
269     }
270 #endif // !HAVE_LIBWMF
272     if ( !doc ) {
273         XmlSource src;
275         if ( (src.setFile(filename) == 0) ) {
276             doc = xmlReadIO( XmlSource::readCb,
277                              XmlSource::closeCb,
278                              &src,
279                              localFilename,
280                              src.getEncoding(),
281                              XML_PARSE_NOENT );
282         }
283     }
285     rdoc = sp_repr_do_read( doc, default_ns );
286     if ( doc ) {
287         xmlFreeDoc( doc );
288     }
290     if ( localFilename ) {
291         g_free( localFilename );
292     }
294     return rdoc;
297 /**
298  * Reads and parses XML from a buffer, returning it as an Document
299  */
300 Document *
301 sp_repr_read_mem (const gchar * buffer, gint length, const gchar *default_ns)
303     xmlDocPtr doc;
304     Document * rdoc;
306     xmlSubstituteEntitiesDefault(1);
308     g_return_val_if_fail (buffer != NULL, NULL);
310     doc = xmlParseMemory ((gchar *) buffer, length);
312     rdoc = sp_repr_do_read (doc, default_ns);
313     if (doc)
314         xmlFreeDoc (doc);
315     return rdoc;
318 namespace Inkscape {
320 struct compare_quark_ids {
321     bool operator()(Glib::QueryQuark const &a, Glib::QueryQuark const &b) const {
322         return a.id() < b.id();
323     }
324 };
328 namespace {
330 typedef std::map<Glib::QueryQuark, Glib::QueryQuark, Inkscape::compare_quark_ids> PrefixMap;
332 Glib::QueryQuark qname_prefix(Glib::QueryQuark qname) {
333     static PrefixMap prefix_map;
334     PrefixMap::iterator iter = prefix_map.find(qname);
335     if ( iter != prefix_map.end() ) {
336         return (*iter).second;
337     } else {
338         gchar const *name_string=g_quark_to_string(qname);
339         gchar const *prefix_end=strchr(name_string, ':');
340         if (prefix_end) {
341             Glib::Quark prefix=Glib::ustring(name_string, prefix_end);
342             prefix_map.insert(PrefixMap::value_type(qname, prefix));
343             return prefix;
344         } else {
345             return GQuark(0);
346         }
347     }
352 namespace {
354 void promote_to_svg_namespace(Node *repr) {
355     if ( repr->type() == Inkscape::XML::ELEMENT_NODE ) {
356         GQuark code = repr->code();
357         if (!qname_prefix(code).id()) {
358             gchar *svg_name = g_strconcat("svg:", g_quark_to_string(code), NULL);
359             repr->setCodeUnsafe(g_quark_from_string(svg_name));
360             g_free(svg_name);
361         }
362         for ( Node *child = sp_repr_children(repr) ; child ; child = sp_repr_next(child) ) {
363             promote_to_svg_namespace(child);
364         }
365     }
370 /**
371  * Reads in a XML file to create a Document
372  */
373 Document *
374 sp_repr_do_read (xmlDocPtr doc, const gchar *default_ns)
376     if (doc == NULL) return NULL;
377     xmlNodePtr node=xmlDocGetRootElement (doc);
378     if (node == NULL) return NULL;
380     GHashTable * prefix_map;
381     prefix_map = g_hash_table_new (g_str_hash, g_str_equal);
383     Document *rdoc = new Inkscape::XML::SimpleDocument();
385     Node *root=NULL;
386     for ( node = doc->children ; node != NULL ; node = node->next ) {
387         if (node->type == XML_ELEMENT_NODE) {
388             Node *repr=sp_repr_svg_read_node(rdoc, node, default_ns, prefix_map);
389             rdoc->appendChild(repr);
390             Inkscape::GC::release(repr);
392             if (!root) {
393                 root = repr;
394             } else {
395                 root = NULL;
396                 break;
397             }
398         } else if ( node->type == XML_COMMENT_NODE ) {
399             Node *comment=sp_repr_svg_read_node(rdoc, node, default_ns, prefix_map);
400             rdoc->appendChild(comment);
401             Inkscape::GC::release(comment);
402         }
403     }
405     if (root != NULL) {
406         /* promote elements of SVG documents that don't use namespaces
407          * into the SVG namespace */
408         if ( default_ns && !strcmp(default_ns, SP_SVG_NS_URI)
409              && !strcmp(root->name(), "svg") )
410         {
411             promote_to_svg_namespace(root);
412         }
413     }
415     g_hash_table_destroy (prefix_map);
417     return rdoc;
420 gint
421 sp_repr_qualified_name (gchar *p, gint len, xmlNsPtr ns, const xmlChar *name, const gchar *default_ns, GHashTable *prefix_map)
423     const xmlChar *prefix;
424     if ( ns && ns->href ) {
425         prefix = (xmlChar*)sp_xml_ns_uri_prefix ((gchar*)ns->href, (char*)ns->prefix);
426         g_hash_table_insert (prefix_map, (gpointer)prefix, (gpointer)ns->href);
427     } else {
428         prefix = NULL;
429     }
431     if (prefix)
432         return g_snprintf (p, len, "%s:%s", (gchar*)prefix, name);
433     else
434         return g_snprintf (p, len, "%s", name);
437 static Node *
438 sp_repr_svg_read_node (Document *xml_doc, xmlNodePtr node, const gchar *default_ns, GHashTable *prefix_map)
440     Node *repr, *crepr;
441     xmlAttrPtr prop;
442     xmlNodePtr child;
443     gchar c[256];
445     if (node->type == XML_TEXT_NODE || node->type == XML_CDATA_SECTION_NODE) {
447         if (node->content == NULL || *(node->content) == '\0')
448             return NULL; // empty text node
450         bool preserve = (xmlNodeGetSpacePreserve (node) == 1);
452         xmlChar *p;
453         for (p = node->content; *p && g_ascii_isspace (*p) && !preserve; p++)
454             ; // skip all whitespace
456         if (!(*p)) { // this is an all-whitespace node, and preserve == default
457             return NULL; // we do not preserve all-whitespace nodes unless we are asked to
458         }
460         return xml_doc->createTextNode((const gchar *)node->content);
461     }
463     if (node->type == XML_COMMENT_NODE)
464         return xml_doc->createComment((const gchar *)node->content);
466     if (node->type == XML_ENTITY_DECL) return NULL;
468     sp_repr_qualified_name (c, 256, node->ns, node->name, default_ns, prefix_map);
469     repr = xml_doc->createElement(c);
470     /* TODO remember node->ns->prefix if node->ns != NULL */
472     for (prop = node->properties; prop != NULL; prop = prop->next) {
473         if (prop->children) {
474             sp_repr_qualified_name (c, 256, prop->ns, prop->name, default_ns, prefix_map);
475             repr->setAttribute(c, (gchar*)prop->children->content);
476             /* TODO remember prop->ns->prefix if prop->ns != NULL */
477         }
478     }
480     if (node->content)
481         repr->setContent((gchar*)node->content);
483     child = node->xmlChildrenNode;
484     for (child = node->xmlChildrenNode; child != NULL; child = child->next) {
485         crepr = sp_repr_svg_read_node (xml_doc, child, default_ns, prefix_map);
486         if (crepr) {
487             repr->appendChild(crepr);
488             Inkscape::GC::release(crepr);
489         }
490     }
492     return repr;
495 void
496 sp_repr_save_stream (Document *doc, FILE *fp, gchar const *default_ns, bool compress)
498     Node *repr;
499     const gchar *str;
501     Inkscape::URI dummy("x");
502     Inkscape::IO::UriOutputStream bout(fp, dummy);
503     Inkscape::IO::GzipOutputStream *gout = compress ? new Inkscape::IO::GzipOutputStream(bout) : NULL;
504     Inkscape::IO::OutputStreamWriter *out  = compress ? new Inkscape::IO::OutputStreamWriter( *gout ) : new Inkscape::IO::OutputStreamWriter( bout );
506     int inlineattrs = prefs_get_int_attribute("options.svgoutput", "inlineattrs", 0);
507     int indent = prefs_get_int_attribute("options.svgoutput", "indent", 2);
509     /* fixme: do this The Right Way */
510     out->writeString( "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?>\n" );
512     str = ((Node *)doc)->attribute("doctype");
513     if (str) {
514         out->writeString( str );
515     }
517     repr = sp_repr_document_first_child(doc);
518     for ( repr = sp_repr_document_first_child(doc) ;
519           repr ; repr = sp_repr_next(repr) )
520     {
521         if ( repr->type() == Inkscape::XML::ELEMENT_NODE ) {
522             sp_repr_write_stream_root_element(repr, *out, TRUE, default_ns, inlineattrs, indent);
523         } else if ( repr->type() == Inkscape::XML::COMMENT_NODE ) {
524             sp_repr_write_stream(repr, *out, 0, TRUE, GQuark(0), inlineattrs, indent);
525             out->writeChar( '\n' );
526         } else {
527             sp_repr_write_stream(repr, *out, 0, TRUE, GQuark(0), inlineattrs, indent);
528         }
529     }
530     if ( out ) {
531         delete out;
532         out = NULL;
533     }
534     if ( gout ) {
535         delete gout;
536         gout = NULL;
537     }
540 /* Returns TRUE if file successfully saved; FALSE if not
541  */
542 bool
543 sp_repr_save_file (Document *doc, const gchar *filename,
544                    gchar const *default_ns)
546     if (filename == NULL) {
547         return FALSE;
548     }
549     bool compress = false;
550     {
551         if (strlen (filename) > 5) {
552             gchar tmp[] = {0,0,0,0,0,0};
553             strncpy( tmp, filename + strlen (filename) - 5, 6 );
554             tmp[5] = 0;
555             if ( strcasecmp(".svgz", tmp ) == 0 )
556             {
557                 //g_message("TIME TO COMPRESS THE OUTPUT FOR SVGZ");
558                 compress = true;
559             }
560         }
561     }
563     Inkscape::IO::dump_fopen_call( filename, "B" );
564     FILE *file = Inkscape::IO::fopen_utf8name(filename, "w");
565     if (file == NULL) {
566         return FALSE;
567     }
569     sp_repr_save_stream (doc, file, default_ns, compress);
571     if (fclose (file) != 0) {
572         return FALSE;
573     }
575     return TRUE;
578 void
579 sp_repr_print (Node * repr)
581     Inkscape::IO::StdOutputStream bout;
582     Inkscape::IO::OutputStreamWriter out(bout);
584     sp_repr_write_stream (repr, out, 0, TRUE, GQuark(0), 0, 2);
586     return;
589 /* (No doubt this function already exists elsewhere.) */
590 static void
591 repr_quote_write (Writer &out, const gchar * val)
593     if (!val) return;
595     for (; *val != '\0'; val++) {
596         switch (*val) {
597         case '"': out.writeString( "&quot;" ); break;
598         case '&': out.writeString( "&amp;" ); break;
599         case '<': out.writeString( "&lt;" ); break;
600         case '>': out.writeString( "&gt;" ); break;
601         default: out.writeChar( *val ); break;
602         }
603     }
606 namespace {
608 typedef std::map<Glib::QueryQuark, gchar const *, Inkscape::compare_quark_ids> LocalNameMap;
609 typedef std::map<Glib::QueryQuark, Inkscape::Util::ptr_shared<char>, Inkscape::compare_quark_ids> NSMap;
611 gchar const *qname_local_name(Glib::QueryQuark qname) {
612     static LocalNameMap local_name_map;
613     LocalNameMap::iterator iter = local_name_map.find(qname);
614     if ( iter != local_name_map.end() ) {
615         return (*iter).second;
616     } else {
617         gchar const *name_string=g_quark_to_string(qname);
618         gchar const *prefix_end=strchr(name_string, ':');
619         if (prefix_end) {
620             return prefix_end + 1;
621         } else {
622             return name_string;
623         }
624     }
627 void add_ns_map_entry(NSMap &ns_map, Glib::QueryQuark prefix) {
628     using Inkscape::Util::ptr_shared;
629     using Inkscape::Util::share_unsafe;
631     static const Glib::QueryQuark xml_prefix("xml");
633     NSMap::iterator iter=ns_map.find(prefix);
634     if ( iter == ns_map.end() ) {
635         if (prefix.id()) {
636             gchar const *uri=sp_xml_ns_prefix_uri(g_quark_to_string(prefix));
637             if (uri) {
638                 ns_map.insert(NSMap::value_type(prefix, share_unsafe(uri)));
639             } else if ( prefix != xml_prefix ) {
640                 g_warning("No namespace known for normalized prefix %s", g_quark_to_string(prefix));
641             }
642         } else {
643             ns_map.insert(NSMap::value_type(prefix, ptr_shared<char>()));
644         }
645     }
648 void populate_ns_map(NSMap &ns_map, Node &repr) {
649     if ( repr.type() == Inkscape::XML::ELEMENT_NODE ) {
650         add_ns_map_entry(ns_map, qname_prefix(repr.code()));
651         for ( List<AttributeRecord const> iter=repr.attributeList() ;
652               iter ; ++iter )
653         {
654             Glib::QueryQuark prefix=qname_prefix(iter->key);
655             if (prefix.id()) {
656                 add_ns_map_entry(ns_map, prefix);
657             }
658         }
659         for ( Node *child=sp_repr_children(&repr) ;
660               child ; child = sp_repr_next(child) )
661         {
662             populate_ns_map(ns_map, *child);
663         }
664     }
669 void
670 sp_repr_write_stream_root_element (Node *repr, Writer &out, bool add_whitespace, gchar const *default_ns, 
671                                    int inlineattrs, int indent)
673     using Inkscape::Util::ptr_shared;
674     g_assert(repr != NULL);
675     Glib::QueryQuark xml_prefix=g_quark_from_static_string("xml");
677     NSMap ns_map;
678     populate_ns_map(ns_map, *repr);
680     Glib::QueryQuark elide_prefix=GQuark(0);
681     if ( default_ns && ns_map.find(GQuark(0)) == ns_map.end() ) {
682         elide_prefix = g_quark_from_string(sp_xml_ns_uri_prefix(default_ns, NULL));
683     }
685     List<AttributeRecord const> attributes=repr->attributeList();
686     for ( NSMap::iterator iter=ns_map.begin() ; iter != ns_map.end() ; ++iter ) 
687     {
688         Glib::QueryQuark prefix=(*iter).first;
689         ptr_shared<char> ns_uri=(*iter).second;
691         if (prefix.id()) {
692             if ( prefix != xml_prefix ) {
693                 if ( elide_prefix == prefix ) {
694                     attributes = cons(AttributeRecord(g_quark_from_static_string("xmlns"), ns_uri), attributes);
695                 }
697                 Glib::ustring attr_name="xmlns:";
698                 attr_name.append(g_quark_to_string(prefix));
699                 GQuark key = g_quark_from_string(attr_name.c_str());
700                 attributes = cons(AttributeRecord(key, ns_uri), attributes);
701             }
702         } else {
703             // if there are non-namespaced elements, we can't globally
704             // use a default namespace
705             elide_prefix = GQuark(0);
706         }
707     }
709     return sp_repr_write_stream_element(repr, out, 0, add_whitespace, elide_prefix, attributes, inlineattrs, indent);
712 void
713 sp_repr_write_stream (Node *repr, Writer &out, gint indent_level,
714                       bool add_whitespace, Glib::QueryQuark elide_prefix, int inlineattrs, int indent)
716     if (repr->type() == Inkscape::XML::TEXT_NODE) {
717         repr_quote_write (out, repr->content());
718     } else if (repr->type() == Inkscape::XML::COMMENT_NODE) {
719         out.printf( "<!--%s-->", repr->content() );
720     } else if (repr->type() == Inkscape::XML::ELEMENT_NODE) {
721         sp_repr_write_stream_element(repr, out, indent_level, add_whitespace, elide_prefix, repr->attributeList(), inlineattrs, indent);
722     } else {
723         g_assert_not_reached();
724     }
727 void
728 sp_repr_write_stream_element (Node * repr, Writer & out, gint indent_level,
729                               bool add_whitespace,
730                               Glib::QueryQuark elide_prefix,
731                               List<AttributeRecord const> attributes, 
732                               int inlineattrs, int indent)
734     Node *child;
735     bool loose;
737     g_return_if_fail (repr != NULL);
739     if ( indent_level > 16 )
740         indent_level = 16;
742     if (add_whitespace && indent) {
743         for (gint i = 0; i < indent_level; i++) {
744             for (gint j = 0; j < indent; j++) {
745                 out.writeString(" ");
746             }
747         }
748     }
750     GQuark code = repr->code();
751     gchar const *element_name;
752     if ( elide_prefix == qname_prefix(code) ) {
753         element_name = qname_local_name(code);
754     } else {
755         element_name = g_quark_to_string(code);
756     }
757     out.printf( "<%s", element_name );
759     // if this is a <text> element, suppress formatting whitespace
760     // for its content and children:
761     gchar const *xml_space_attr = repr->attribute("xml:space");
762     if (xml_space_attr != NULL && !strcmp(xml_space_attr, "preserve")) {
763         add_whitespace = FALSE;
764     }
766     for ( List<AttributeRecord const> iter = attributes ;
767           iter ; ++iter )
768     {
769         if (!inlineattrs) {
770             out.writeString("\n");
771             if (indent) {
772                 for ( gint i = 0 ; i < indent_level + 1 ; i++ ) {
773                     for ( gint j = 0 ; j < indent ; j++ ) {
774                         out.writeString(" ");
775                     }
776                 }
777             }
778         }
779         out.printf(" %s=\"", g_quark_to_string(iter->key));
780         repr_quote_write(out, iter->value);
781         out.writeChar('"');
782     }
784     loose = TRUE;
785     for (child = repr->firstChild() ; child != NULL; child = child->next()) {
786         if (child->type() == Inkscape::XML::TEXT_NODE) {
787             loose = FALSE;
788             break;
789         }
790     }
791     if (repr->firstChild()) {
792         out.writeString( ">" );
793         if (loose && add_whitespace) {
794             out.writeString( "\n" );
795         }
796         for (child = repr->firstChild(); child != NULL; child = child->next()) {
797             sp_repr_write_stream (child, out, (loose) ? (indent_level + 1) : 0, add_whitespace, elide_prefix, inlineattrs, indent);
798         }
800         if (loose && add_whitespace && indent) {
801             for (gint i = 0; i < indent_level; i++) {
802                 for ( gint j = 0 ; j < indent ; j++ ) {
803                     out.writeString(" ");
804                 }
805             }
806         }
807         out.printf( "</%s>", element_name );
808     } else {
809         out.writeString( " />" );
810     }
812     // text elements cannot nest, so we can output newline
813     // after closing text
815     if (add_whitespace || !strcmp (repr->name(), "svg:text")) {
816         out.writeString( "\n" );
817     }
821 /*
822   Local Variables:
823   mode:c++
824   c-file-style:"stroustrup"
825   c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
826   indent-tabs-mode:nil
827   fill-column:99
828   End:
829 */
830 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :