Code

Add fixme comment re testing for file existence inside g_return_if_fail.
[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 <stdexcept>
21 #include "xml/repr.h"
22 #include "xml/attribute-record.h"
23 #include "xml/simple-document.h"
25 #include "io/sys.h"
26 #include "io/uristream.h"
27 #include "io/gzipstream.h"
29 #include "prefs-utils.h"
31 using Inkscape::IO::Writer;
32 using Inkscape::Util::List;
33 using Inkscape::Util::cons;
34 using Inkscape::XML::Document;
35 using Inkscape::XML::SimpleDocument;
36 using Inkscape::XML::Node;
37 using Inkscape::XML::AttributeRecord;
39 static Document *sp_repr_do_read (xmlDocPtr doc, const gchar *default_ns);
40 static Node *sp_repr_svg_read_node (Document *xml_doc, xmlNodePtr node, const gchar *default_ns, GHashTable *prefix_map);
41 static gint sp_repr_qualified_name (gchar *p, gint len, xmlNsPtr ns, const xmlChar *name, const gchar *default_ns, GHashTable *prefix_map);
42 static void sp_repr_write_stream_root_element (Node *repr, Writer &out, bool add_whitespace, gchar const *default_ns, int inlineattrs, int indent);
43 static void sp_repr_write_stream (Node *repr, Writer &out, gint indent_level, bool add_whitespace, Glib::QueryQuark elide_prefix, int inlineattrs, int indent);
44 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);
46 #ifdef HAVE_LIBWMF
47 static xmlDocPtr sp_wmf_convert (const char * file_name);
48 static char * sp_wmf_image_name (void * context);
49 #endif /* HAVE_LIBWMF */
52 class XmlSource
53 {
54 public:
55     XmlSource()
56         : filename(0),
57           encoding(0),
58           fp(0),
59           firstFewLen(0),
60           dummy("x"),
61           instr(0),
62           gzin(0)
63     {
64     }
65     virtual ~XmlSource()
66     {
67         close();
68         if ( encoding ) {
69             g_free(encoding);
70             encoding = 0;
71         }
72     }
74     int setFile( char const * filename );
76     static int readCb( void * context, char * buffer, int len );
77     static int closeCb( void * context );
79     char const* getEncoding() const { return encoding; }
80     int read( char * buffer, int len );
81     int close();
82 private:
83     const char* filename;
84     char* encoding;
85     FILE* fp;
86     unsigned char firstFew[4];
87     int firstFewLen;
88     Inkscape::URI dummy;
89     Inkscape::IO::UriInputStream* instr;
90     Inkscape::IO::GzipInputStream* gzin;
91 };
93 int XmlSource::setFile(char const *filename)
94 {
95     int retVal = -1;
97     this->filename = filename;
99     fp = Inkscape::IO::fopen_utf8name(filename, "r");
100     if ( fp ) {
101         // First peek in the file to see what it is
102         memset( firstFew, 0, sizeof(firstFew) );
104         size_t some = fread( firstFew, 1, 4, fp );
105         if ( fp ) {
106             // first check for compression
107             if ( (some >= 2) && (firstFew[0] == 0x1f) && (firstFew[1] == 0x8b) ) {
108                 //g_message(" the file being read is gzip'd. extract it");
109                 fclose(fp);
110                 fp = 0;
111                 fp = Inkscape::IO::fopen_utf8name(filename, "r");
112                 instr = new Inkscape::IO::UriInputStream(fp, dummy);
113                 gzin = new Inkscape::IO::GzipInputStream(*instr);
115                 memset( firstFew, 0, sizeof(firstFew) );
116                 some = 0;
117                 int single = 0;
118                 while ( some < 4 && single >= 0 )
119                 {
120                     single = gzin->get();
121                     if ( single >= 0 ) {
122                         firstFew[some++] = 0x0ff & single;
123                     } else {
124                         break;
125                     }
126                 }
127             }
129             int encSkip = 0;
130             if ( (some >= 2) &&(firstFew[0] == 0xfe) && (firstFew[1] == 0xff) ) {
131                 encoding = g_strdup("UTF-16BE");
132                 encSkip = 2;
133             } else if ( (some >= 2) && (firstFew[0] == 0xff) && (firstFew[1] == 0xfe) ) {
134                 encoding = g_strdup("UTF-16LE");
135                 encSkip = 2;
136             } else if ( (some >= 3) && (firstFew[0] == 0xef) && (firstFew[1] == 0xbb) && (firstFew[2] == 0xbf) ) {
137                 encoding = g_strdup("UTF-8");
138                 encSkip = 3;
139             }
141             if ( encSkip ) {
142                 memmove( firstFew, firstFew + encSkip, (some - encSkip) );
143                 some -= encSkip;
144             }
146             firstFewLen = some;
147             retVal = 0; // no error
148         }
149     }
151     return retVal;
155 int XmlSource::readCb( void * context, char * buffer, int len )
157     int retVal = -1;
158     if ( context ) {
159         XmlSource* self = static_cast<XmlSource*>(context);
160         retVal = self->read( buffer, len );
161     }
162     return retVal;
165 int XmlSource::closeCb(void * context)
167     if ( context ) {
168         XmlSource* self = static_cast<XmlSource*>(context);
169         self->close();
170     }
171     return 0;
174 int XmlSource::read( char *buffer, int len )
176     int retVal = 0;
177     size_t got = 0;
179     if ( firstFewLen > 0 ) {
180         int some = (len < firstFewLen) ? len : firstFewLen;
181         memcpy( buffer, firstFew, some );
182         if ( len < firstFewLen ) {
183             memmove( firstFew, firstFew + some, (firstFewLen - some) );
184         }
185         firstFewLen -= some;
186         got = some;
187     } else if ( gzin ) {
188         int single = 0;
189         while ( (int)got < len && single >= 0 )
190         {
191             single = gzin->get();
192             if ( single >= 0 ) {
193                 buffer[got++] = 0x0ff & single;
194             } else {
195                 break;
196             }
197         }
198     } else {
199         got = fread( buffer, 1, len, fp );
200     }
202     if ( feof(fp) ) {
203         retVal = got;
204     } else if ( ferror(fp) ) {
205         retVal = -1;
206     } else {
207         retVal = got;
208     }
210     return retVal;
213 int XmlSource::close()
215     if ( gzin ) {
216         gzin->close();
217         delete gzin;
218         gzin = 0;
219     }
220     if ( instr ) {
221         instr->close();
222         fp = 0;
223         delete instr;
224         instr = 0;
225     }
226     if ( fp ) {
227         fclose(fp);
228         fp = 0;
229     }
230     return 0;
233 /**
234  * Reads XML from a file, including WMF files, and returns the Document.
235  * The default namespace can also be specified, if desired.
236  */
237 Document *
238 sp_repr_read_file (const gchar * filename, const gchar *default_ns)
240     xmlDocPtr doc = 0;
241     Document * rdoc = 0;
243     xmlSubstituteEntitiesDefault(1);
245     g_return_val_if_fail (filename != NULL, NULL);
246     g_return_val_if_fail (Inkscape::IO::file_test( filename, G_FILE_TEST_EXISTS ), NULL);
247     /* fixme: A file can disappear at any time, including between now and when we actually try to
248      * open it.  Get rid of the above test once we're sure that we correctly handle
249      * non-existence. */
251     // TODO: bulia, please look over
252     gsize bytesRead = 0;
253     gsize bytesWritten = 0;
254     GError* error = NULL;
255     // TODO: need to replace with our own fopen and reading
256     gchar* localFilename = g_filename_from_utf8 ( filename,
257                                  -1,  &bytesRead,  &bytesWritten, &error);
258     g_return_val_if_fail( localFilename != NULL, NULL );
260     Inkscape::IO::dump_fopen_call( filename, "N" );
262 #ifdef HAVE_LIBWMF
263     if (strlen (localFilename) > 4) {
264         if ( (strcmp (localFilename + strlen (localFilename) - 4,".wmf") == 0)
265              || (strcmp (localFilename + strlen (localFilename) - 4,".WMF") == 0)) {
266             doc = sp_wmf_convert (localFilename);
267         }
268     }
269 #endif // !HAVE_LIBWMF
271     if ( !doc ) {
272         XmlSource src;
274         if ( (src.setFile(filename) == 0) ) {
275             doc = xmlReadIO( XmlSource::readCb,
276                              XmlSource::closeCb,
277                              &src,
278                              localFilename,
279                              src.getEncoding(),
280                              XML_PARSE_NOENT );
281         }
282     }
284     rdoc = sp_repr_do_read( doc, default_ns );
285     if ( doc ) {
286         xmlFreeDoc( doc );
287     }
289     if ( localFilename ) {
290         g_free( localFilename );
291     }
293     return rdoc;
296 /**
297  * Reads and parses XML from a buffer, returning it as an Document
298  */
299 Document *
300 sp_repr_read_mem (const gchar * buffer, gint length, const gchar *default_ns)
302     xmlDocPtr doc;
303     Document * rdoc;
305     xmlSubstituteEntitiesDefault(1);
307     g_return_val_if_fail (buffer != NULL, NULL);
309     doc = xmlParseMemory ((gchar *) buffer, length);
311     rdoc = sp_repr_do_read (doc, default_ns);
312     if (doc)
313         xmlFreeDoc (doc);
314     return rdoc;
317 namespace Inkscape {
319 struct compare_quark_ids {
320     bool operator()(Glib::QueryQuark const &a, Glib::QueryQuark const &b) const {
321         return a.id() < b.id();
322     }
323 };
327 namespace {
329 typedef std::map<Glib::QueryQuark, Glib::QueryQuark, Inkscape::compare_quark_ids> PrefixMap;
331 Glib::QueryQuark qname_prefix(Glib::QueryQuark qname) {
332     static PrefixMap prefix_map;
333     PrefixMap::iterator iter = prefix_map.find(qname);
334     if ( iter != prefix_map.end() ) {
335         return (*iter).second;
336     } else {
337         gchar const *name_string=g_quark_to_string(qname);
338         gchar const *prefix_end=strchr(name_string, ':');
339         if (prefix_end) {
340             Glib::Quark prefix=Glib::ustring(name_string, prefix_end);
341             prefix_map.insert(PrefixMap::value_type(qname, prefix));
342             return prefix;
343         } else {
344             return GQuark(0);
345         }
346     }
351 namespace {
353 void promote_to_svg_namespace(Node *repr) {
354     if ( repr->type() == Inkscape::XML::ELEMENT_NODE ) {
355         GQuark code = repr->code();
356         if (!qname_prefix(code).id()) {
357             gchar *svg_name = g_strconcat("svg:", g_quark_to_string(code), NULL);
358             repr->setCodeUnsafe(g_quark_from_string(svg_name));
359             g_free(svg_name);
360         }
361         for ( Node *child = sp_repr_children(repr) ; child ; child = sp_repr_next(child) ) {
362             promote_to_svg_namespace(child);
363         }
364     }
369 /**
370  * Reads in a XML file to create a Document
371  */
372 Document *
373 sp_repr_do_read (xmlDocPtr doc, const gchar *default_ns)
375     if (doc == NULL) return NULL;
376     xmlNodePtr node=xmlDocGetRootElement (doc);
377     if (node == NULL) return NULL;
379     GHashTable * prefix_map;
380     prefix_map = g_hash_table_new (g_str_hash, g_str_equal);
382     Document *rdoc = new Inkscape::XML::SimpleDocument();
384     Node *root=NULL;
385     for ( node = doc->children ; node != NULL ; node = node->next ) {
386         if (node->type == XML_ELEMENT_NODE) {
387             Node *repr=sp_repr_svg_read_node(rdoc, node, default_ns, prefix_map);
388             rdoc->appendChild(repr);
389             Inkscape::GC::release(repr);
391             if (!root) {
392                 root = repr;
393             } else {
394                 root = NULL;
395                 break;
396             }
397         } else if ( node->type == XML_COMMENT_NODE ) {
398             Node *comment=sp_repr_svg_read_node(rdoc, node, default_ns, prefix_map);
399             rdoc->appendChild(comment);
400             Inkscape::GC::release(comment);
401         }
402     }
404     if (root != NULL) {
405         /* promote elements of SVG documents that don't use namespaces
406          * into the SVG namespace */
407         if ( default_ns && !strcmp(default_ns, SP_SVG_NS_URI)
408              && !strcmp(root->name(), "svg") )
409         {
410             promote_to_svg_namespace(root);
411         }
412     }
414     g_hash_table_destroy (prefix_map);
416     return rdoc;
419 gint
420 sp_repr_qualified_name (gchar *p, gint len, xmlNsPtr ns, const xmlChar *name, const gchar *default_ns, GHashTable *prefix_map)
422     const xmlChar *prefix;
423     if ( ns && ns->href ) {
424         prefix = (xmlChar*)sp_xml_ns_uri_prefix ((gchar*)ns->href, (char*)ns->prefix);
425         g_hash_table_insert (prefix_map, (gpointer)prefix, (gpointer)ns->href);
426     } else {
427         prefix = NULL;
428     }
430     if (prefix)
431         return g_snprintf (p, len, "%s:%s", (gchar*)prefix, name);
432     else
433         return g_snprintf (p, len, "%s", name);
436 static Node *
437 sp_repr_svg_read_node (Document *xml_doc, xmlNodePtr node, const gchar *default_ns, GHashTable *prefix_map)
439     Node *repr, *crepr;
440     xmlAttrPtr prop;
441     xmlNodePtr child;
442     gchar c[256];
444     if (node->type == XML_TEXT_NODE || node->type == XML_CDATA_SECTION_NODE) {
446         if (node->content == NULL || *(node->content) == '\0')
447             return NULL; // empty text node
449         bool preserve = (xmlNodeGetSpacePreserve (node) == 1);
451         xmlChar *p;
452         for (p = node->content; *p && g_ascii_isspace (*p) && !preserve; p++)
453             ; // skip all whitespace
455         if (!(*p)) { // this is an all-whitespace node, and preserve == default
456             return NULL; // we do not preserve all-whitespace nodes unless we are asked to
457         }
459         return xml_doc->createTextNode((const gchar *)node->content);
460     }
462     if (node->type == XML_COMMENT_NODE)
463         return xml_doc->createComment((const gchar *)node->content);
465     if (node->type == XML_ENTITY_DECL) return NULL;
467     sp_repr_qualified_name (c, 256, node->ns, node->name, default_ns, prefix_map);
468     repr = xml_doc->createElement(c);
469     /* TODO remember node->ns->prefix if node->ns != NULL */
471     for (prop = node->properties; prop != NULL; prop = prop->next) {
472         if (prop->children) {
473             sp_repr_qualified_name (c, 256, prop->ns, prop->name, default_ns, prefix_map);
474             repr->setAttribute(c, (gchar*)prop->children->content);
475             /* TODO remember prop->ns->prefix if prop->ns != NULL */
476         }
477     }
479     if (node->content)
480         repr->setContent((gchar*)node->content);
482     child = node->xmlChildrenNode;
483     for (child = node->xmlChildrenNode; child != NULL; child = child->next) {
484         crepr = sp_repr_svg_read_node (xml_doc, child, default_ns, prefix_map);
485         if (crepr) {
486             repr->appendChild(crepr);
487             Inkscape::GC::release(crepr);
488         }
489     }
491     return repr;
494 void
495 sp_repr_save_stream (Document *doc, FILE *fp, gchar const *default_ns, bool compress)
497     Node *repr;
498     const gchar *str;
500     Inkscape::URI dummy("x");
501     Inkscape::IO::UriOutputStream bout(fp, dummy);
502     Inkscape::IO::GzipOutputStream *gout = compress ? new Inkscape::IO::GzipOutputStream(bout) : NULL;
503     Inkscape::IO::OutputStreamWriter *out  = compress ? new Inkscape::IO::OutputStreamWriter( *gout ) : new Inkscape::IO::OutputStreamWriter( bout );
505     int inlineattrs = prefs_get_int_attribute("options.svgoutput", "inlineattrs", 0);
506     int indent = prefs_get_int_attribute("options.svgoutput", "indent", 2);
508     /* fixme: do this The Right Way */
509     out->writeString( "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?>\n" );
511     str = ((Node *)doc)->attribute("doctype");
512     if (str) {
513         out->writeString( str );
514     }
516     repr = sp_repr_document_first_child(doc);
517     for ( repr = sp_repr_document_first_child(doc) ;
518           repr ; repr = sp_repr_next(repr) )
519     {
520         if ( repr->type() == Inkscape::XML::ELEMENT_NODE ) {
521             sp_repr_write_stream_root_element(repr, *out, TRUE, default_ns, inlineattrs, indent);
522         } else if ( repr->type() == Inkscape::XML::COMMENT_NODE ) {
523             sp_repr_write_stream(repr, *out, 0, TRUE, GQuark(0), inlineattrs, indent);
524             out->writeChar( '\n' );
525         } else {
526             sp_repr_write_stream(repr, *out, 0, TRUE, GQuark(0), inlineattrs, indent);
527         }
528     }
529     if ( out ) {
530         delete out;
531         out = NULL;
532     }
533     if ( gout ) {
534         delete gout;
535         gout = NULL;
536     }
539 /* Returns TRUE if file successfully saved; FALSE if not
540  */
541 bool
542 sp_repr_save_file (Document *doc, const gchar *filename,
543                    gchar const *default_ns)
545     if (filename == NULL) {
546         return FALSE;
547     }
548     bool compress = false;
549     {
550         if (strlen (filename) > 5) {
551             gchar tmp[] = {0,0,0,0,0,0};
552             strncpy( tmp, filename + strlen (filename) - 5, 6 );
553             tmp[5] = 0;
554             if ( strcasecmp(".svgz", tmp ) == 0 )
555             {
556                 //g_message("TIME TO COMPRESS THE OUTPUT FOR SVGZ");
557                 compress = true;
558             }
559         }
560     }
562     Inkscape::IO::dump_fopen_call( filename, "B" );
563     FILE *file = Inkscape::IO::fopen_utf8name(filename, "w");
564     if (file == NULL) {
565         return FALSE;
566     }
568     sp_repr_save_stream (doc, file, default_ns, compress);
570     if (fclose (file) != 0) {
571         return FALSE;
572     }
574     return TRUE;
577 void
578 sp_repr_print (Node * repr)
580     Inkscape::IO::StdOutputStream bout;
581     Inkscape::IO::OutputStreamWriter out(bout);
583     sp_repr_write_stream (repr, out, 0, TRUE, GQuark(0), 0, 2);
585     return;
588 /* (No doubt this function already exists elsewhere.) */
589 static void
590 repr_quote_write (Writer &out, const gchar * val)
592     if (!val) return;
594     for (; *val != '\0'; val++) {
595         switch (*val) {
596         case '"': out.writeString( "&quot;" ); break;
597         case '&': out.writeString( "&amp;" ); break;
598         case '<': out.writeString( "&lt;" ); break;
599         case '>': out.writeString( "&gt;" ); break;
600         default: out.writeChar( *val ); break;
601         }
602     }
605 namespace {
607 typedef std::map<Glib::QueryQuark, gchar const *, Inkscape::compare_quark_ids> LocalNameMap;
608 typedef std::map<Glib::QueryQuark, Inkscape::Util::ptr_shared<char>, Inkscape::compare_quark_ids> NSMap;
610 gchar const *qname_local_name(Glib::QueryQuark qname) {
611     static LocalNameMap local_name_map;
612     LocalNameMap::iterator iter = local_name_map.find(qname);
613     if ( iter != local_name_map.end() ) {
614         return (*iter).second;
615     } else {
616         gchar const *name_string=g_quark_to_string(qname);
617         gchar const *prefix_end=strchr(name_string, ':');
618         if (prefix_end) {
619             return prefix_end + 1;
620         } else {
621             return name_string;
622         }
623     }
626 void add_ns_map_entry(NSMap &ns_map, Glib::QueryQuark prefix) {
627     using Inkscape::Util::ptr_shared;
628     using Inkscape::Util::share_unsafe;
630     static const Glib::QueryQuark xml_prefix("xml");
632     NSMap::iterator iter=ns_map.find(prefix);
633     if ( iter == ns_map.end() ) {
634         if (prefix.id()) {
635             gchar const *uri=sp_xml_ns_prefix_uri(g_quark_to_string(prefix));
636             if (uri) {
637                 ns_map.insert(NSMap::value_type(prefix, share_unsafe(uri)));
638             } else if ( prefix != xml_prefix ) {
639                 g_warning("No namespace known for normalized prefix %s", g_quark_to_string(prefix));
640             }
641         } else {
642             ns_map.insert(NSMap::value_type(prefix, ptr_shared<char>()));
643         }
644     }
647 void populate_ns_map(NSMap &ns_map, Node &repr) {
648     if ( repr.type() == Inkscape::XML::ELEMENT_NODE ) {
649         add_ns_map_entry(ns_map, qname_prefix(repr.code()));
650         for ( List<AttributeRecord const> iter=repr.attributeList() ;
651               iter ; ++iter )
652         {
653             Glib::QueryQuark prefix=qname_prefix(iter->key);
654             if (prefix.id()) {
655                 add_ns_map_entry(ns_map, prefix);
656             }
657         }
658         for ( Node *child=sp_repr_children(&repr) ;
659               child ; child = sp_repr_next(child) )
660         {
661             populate_ns_map(ns_map, *child);
662         }
663     }
668 void
669 sp_repr_write_stream_root_element (Node *repr, Writer &out, bool add_whitespace, gchar const *default_ns, 
670                                    int inlineattrs, int indent)
672     using Inkscape::Util::ptr_shared;
673     g_assert(repr != NULL);
674     Glib::QueryQuark xml_prefix=g_quark_from_static_string("xml");
676     NSMap ns_map;
677     populate_ns_map(ns_map, *repr);
679     Glib::QueryQuark elide_prefix=GQuark(0);
680     if ( default_ns && ns_map.find(GQuark(0)) == ns_map.end() ) {
681         elide_prefix = g_quark_from_string(sp_xml_ns_uri_prefix(default_ns, NULL));
682     }
684     List<AttributeRecord const> attributes=repr->attributeList();
685     for ( NSMap::iterator iter=ns_map.begin() ; iter != ns_map.end() ; ++iter ) 
686     {
687         Glib::QueryQuark prefix=(*iter).first;
688         ptr_shared<char> ns_uri=(*iter).second;
690         if (prefix.id()) {
691             if ( prefix != xml_prefix ) {
692                 if ( elide_prefix == prefix ) {
693                     attributes = cons(AttributeRecord(g_quark_from_static_string("xmlns"), ns_uri), attributes);
694                 }
696                 Glib::ustring attr_name="xmlns:";
697                 attr_name.append(g_quark_to_string(prefix));
698                 GQuark key = g_quark_from_string(attr_name.c_str());
699                 attributes = cons(AttributeRecord(key, ns_uri), attributes);
700             }
701         } else {
702             // if there are non-namespaced elements, we can't globally
703             // use a default namespace
704             elide_prefix = GQuark(0);
705         }
706     }
708     return sp_repr_write_stream_element(repr, out, 0, add_whitespace, elide_prefix, attributes, inlineattrs, indent);
711 void
712 sp_repr_write_stream (Node *repr, Writer &out, gint indent_level,
713                       bool add_whitespace, Glib::QueryQuark elide_prefix, int inlineattrs, int indent)
715     if (repr->type() == Inkscape::XML::TEXT_NODE) {
716         repr_quote_write (out, repr->content());
717     } else if (repr->type() == Inkscape::XML::COMMENT_NODE) {
718         out.printf( "<!--%s-->", repr->content() );
719     } else if (repr->type() == Inkscape::XML::ELEMENT_NODE) {
720         sp_repr_write_stream_element(repr, out, indent_level, add_whitespace, elide_prefix, repr->attributeList(), inlineattrs, indent);
721     } else {
722         g_assert_not_reached();
723     }
726 void
727 sp_repr_write_stream_element (Node * repr, Writer & out, gint indent_level,
728                               bool add_whitespace,
729                               Glib::QueryQuark elide_prefix,
730                               List<AttributeRecord const> attributes, 
731                               int inlineattrs, int indent)
733     Node *child;
734     bool loose;
736     g_return_if_fail (repr != NULL);
738     if ( indent_level > 16 )
739         indent_level = 16;
741     if (add_whitespace && indent) {
742         for (gint i = 0; i < indent_level; i++) {
743             for (gint j = 0; j < indent; j++) {
744                 out.writeString(" ");
745             }
746         }
747     }
749     GQuark code = repr->code();
750     gchar const *element_name;
751     if ( elide_prefix == qname_prefix(code) ) {
752         element_name = qname_local_name(code);
753     } else {
754         element_name = g_quark_to_string(code);
755     }
756     out.printf( "<%s", element_name );
758     // if this is a <text> element, suppress formatting whitespace
759     // for its content and children:
760     gchar const *xml_space_attr = repr->attribute("xml:space");
761     if (xml_space_attr != NULL && !strcmp(xml_space_attr, "preserve")) {
762         add_whitespace = FALSE;
763     }
765     for ( List<AttributeRecord const> iter = attributes ;
766           iter ; ++iter )
767     {
768         if (!inlineattrs) {
769             out.writeString("\n");
770             if (indent) {
771                 for ( gint i = 0 ; i < indent_level + 1 ; i++ ) {
772                     for ( gint j = 0 ; j < indent ; j++ ) {
773                         out.writeString(" ");
774                     }
775                 }
776             }
777         }
778         out.printf(" %s=\"", g_quark_to_string(iter->key));
779         repr_quote_write(out, iter->value);
780         out.writeChar('"');
781     }
783     loose = TRUE;
784     for (child = repr->firstChild() ; child != NULL; child = child->next()) {
785         if (child->type() == Inkscape::XML::TEXT_NODE) {
786             loose = FALSE;
787             break;
788         }
789     }
790     if (repr->firstChild()) {
791         out.writeString( ">" );
792         if (loose && add_whitespace) {
793             out.writeString( "\n" );
794         }
795         for (child = repr->firstChild(); child != NULL; child = child->next()) {
796             sp_repr_write_stream (child, out, (loose) ? (indent_level + 1) : 0, add_whitespace, elide_prefix, inlineattrs, indent);
797         }
799         if (loose && add_whitespace && indent) {
800             for (gint i = 0; i < indent_level; i++) {
801                 for ( gint j = 0 ; j < indent ; j++ ) {
802                     out.writeString(" ");
803                 }
804             }
805         }
806         out.printf( "</%s>", element_name );
807     } else {
808         out.writeString( " />" );
809     }
811     // text elements cannot nest, so we can output newline
812     // after closing text
814     if (add_whitespace || !strcmp (repr->name(), "svg:text")) {
815         out.writeString( "\n" );
816     }
820 /*
821   Local Variables:
822   mode:c++
823   c-file-style:"stroustrup"
824   c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
825   indent-tabs-mode:nil
826   fill-column:99
827   End:
828 */
829 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :