Code

2a5125375a462dc0180dfda2ac3392c407648220
[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/stringstream.h"
30 #include "io/gzipstream.h"
32 #include "prefs-utils.h"
34 using Inkscape::IO::Writer;
35 using Inkscape::Util::List;
36 using Inkscape::Util::cons;
37 using Inkscape::XML::Document;
38 using Inkscape::XML::SimpleDocument;
39 using Inkscape::XML::Node;
40 using Inkscape::XML::AttributeRecord;
42 Document *sp_repr_do_read (xmlDocPtr doc, const gchar *default_ns);
43 static Node *sp_repr_svg_read_node (Document *xml_doc, xmlNodePtr node, const gchar *default_ns, GHashTable *prefix_map);
44 static gint sp_repr_qualified_name (gchar *p, gint len, xmlNsPtr ns, const xmlChar *name, const gchar *default_ns, GHashTable *prefix_map);
45 static void sp_repr_write_stream_root_element (Node *repr, Writer &out, bool add_whitespace, gchar const *default_ns, int inlineattrs, int indent);
46 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);
48 #ifdef HAVE_LIBWMF
49 static xmlDocPtr sp_wmf_convert (const char * file_name);
50 static char * sp_wmf_image_name (void * context);
51 #endif /* HAVE_LIBWMF */
54 class XmlSource
55 {
56 public:
57     XmlSource()
58         : filename(0),
59           encoding(0),
60           fp(0),
61           firstFewLen(0),
62           dummy("x"),
63           instr(0),
64           gzin(0)
65     {
66     }
67     virtual ~XmlSource()
68     {
69         close();
70         if ( encoding ) {
71             g_free(encoding);
72             encoding = 0;
73         }
74     }
76     int setFile( char const * filename );
78     static int readCb( void * context, char * buffer, int len );
79     static int closeCb( void * context );
81     char const* getEncoding() const { return encoding; }
82     int read( char * buffer, int len );
83     int close();
84 private:
85     const char* filename;
86     char* encoding;
87     FILE* fp;
88     unsigned char firstFew[4];
89     int firstFewLen;
90     Inkscape::URI dummy;
91     Inkscape::IO::UriInputStream* instr;
92     Inkscape::IO::GzipInputStream* gzin;
93 };
95 int XmlSource::setFile(char const *filename)
96 {
97     int retVal = -1;
99     this->filename = filename;
101     fp = Inkscape::IO::fopen_utf8name(filename, "r");
102     if ( fp ) {
103         // First peek in the file to see what it is
104         memset( firstFew, 0, sizeof(firstFew) );
106         size_t some = fread( firstFew, 1, 4, fp );
107         if ( fp ) {
108             // first check for compression
109             if ( (some >= 2) && (firstFew[0] == 0x1f) && (firstFew[1] == 0x8b) ) {
110                 //g_message(" the file being read is gzip'd. extract it");
111                 fclose(fp);
112                 fp = 0;
113                 fp = Inkscape::IO::fopen_utf8name(filename, "r");
114                 instr = new Inkscape::IO::UriInputStream(fp, dummy);
115                 gzin = new Inkscape::IO::GzipInputStream(*instr);
117                 memset( firstFew, 0, sizeof(firstFew) );
118                 some = 0;
119                 int single = 0;
120                 while ( some < 4 && single >= 0 )
121                 {
122                     single = gzin->get();
123                     if ( single >= 0 ) {
124                         firstFew[some++] = 0x0ff & single;
125                     } else {
126                         break;
127                     }
128                 }
129             }
131             int encSkip = 0;
132             if ( (some >= 2) &&(firstFew[0] == 0xfe) && (firstFew[1] == 0xff) ) {
133                 encoding = g_strdup("UTF-16BE");
134                 encSkip = 2;
135             } else if ( (some >= 2) && (firstFew[0] == 0xff) && (firstFew[1] == 0xfe) ) {
136                 encoding = g_strdup("UTF-16LE");
137                 encSkip = 2;
138             } else if ( (some >= 3) && (firstFew[0] == 0xef) && (firstFew[1] == 0xbb) && (firstFew[2] == 0xbf) ) {
139                 encoding = g_strdup("UTF-8");
140                 encSkip = 3;
141             }
143             if ( encSkip ) {
144                 memmove( firstFew, firstFew + encSkip, (some - encSkip) );
145                 some -= encSkip;
146             }
148             firstFewLen = some;
149             retVal = 0; // no error
150         }
151     }
153     return retVal;
157 int XmlSource::readCb( void * context, char * buffer, int len )
159     int retVal = -1;
160     if ( context ) {
161         XmlSource* self = static_cast<XmlSource*>(context);
162         retVal = self->read( buffer, len );
163     }
164     return retVal;
167 int XmlSource::closeCb(void * context)
169     if ( context ) {
170         XmlSource* self = static_cast<XmlSource*>(context);
171         self->close();
172     }
173     return 0;
176 int XmlSource::read( char *buffer, int len )
178     int retVal = 0;
179     size_t got = 0;
181     if ( firstFewLen > 0 ) {
182         int some = (len < firstFewLen) ? len : firstFewLen;
183         memcpy( buffer, firstFew, some );
184         if ( len < firstFewLen ) {
185             memmove( firstFew, firstFew + some, (firstFewLen - some) );
186         }
187         firstFewLen -= some;
188         got = some;
189     } else if ( gzin ) {
190         int single = 0;
191         while ( (int)got < len && single >= 0 )
192         {
193             single = gzin->get();
194             if ( single >= 0 ) {
195                 buffer[got++] = 0x0ff & single;
196             } else {
197                 break;
198             }
199         }
200     } else {
201         got = fread( buffer, 1, len, fp );
202     }
204     if ( feof(fp) ) {
205         retVal = got;
206     } else if ( ferror(fp) ) {
207         retVal = -1;
208     } else {
209         retVal = got;
210     }
212     return retVal;
215 int XmlSource::close()
217     if ( gzin ) {
218         gzin->close();
219         delete gzin;
220         gzin = 0;
221     }
222     if ( instr ) {
223         instr->close();
224         fp = 0;
225         delete instr;
226         instr = 0;
227     }
228     if ( fp ) {
229         fclose(fp);
230         fp = 0;
231     }
232     return 0;
235 /**
236  * Reads XML from a file, including WMF files, and returns the Document.
237  * The default namespace can also be specified, if desired.
238  */
239 Document *
240 sp_repr_read_file (const gchar * filename, const gchar *default_ns)
242     xmlDocPtr doc = 0;
243     Document * rdoc = 0;
245     xmlSubstituteEntitiesDefault(1);
247     g_return_val_if_fail (filename != NULL, NULL);
248     g_return_val_if_fail (Inkscape::IO::file_test( filename, G_FILE_TEST_EXISTS ), NULL);
249     /* fixme: A file can disappear at any time, including between now and when we actually try to
250      * open it.  Get rid of the above test once we're sure that we correctly handle
251      * non-existence. */
253     // TODO: bulia, please look over
254     gsize bytesRead = 0;
255     gsize bytesWritten = 0;
256     GError* error = NULL;
257     // TODO: need to replace with our own fopen and reading
258     gchar* localFilename = g_filename_from_utf8 ( filename,
259                                  -1,  &bytesRead,  &bytesWritten, &error);
260     g_return_val_if_fail( localFilename != NULL, NULL );
262     Inkscape::IO::dump_fopen_call( filename, "N" );
264 #ifdef HAVE_LIBWMF
265     if (strlen (localFilename) > 4) {
266         if ( (strcmp (localFilename + strlen (localFilename) - 4,".wmf") == 0)
267              || (strcmp (localFilename + strlen (localFilename) - 4,".WMF") == 0)) {
268             doc = sp_wmf_convert (localFilename);
269         }
270     }
271 #endif // !HAVE_LIBWMF
273     if ( !doc ) {
274         XmlSource src;
276         if ( (src.setFile(filename) == 0) ) {
277             doc = xmlReadIO( XmlSource::readCb,
278                              XmlSource::closeCb,
279                              &src,
280                              localFilename,
281                              src.getEncoding(),
282                              XML_PARSE_NOENT );
283         }
284     }
286     rdoc = sp_repr_do_read( doc, default_ns );
287     if ( doc ) {
288         xmlFreeDoc( doc );
289     }
291     if ( localFilename ) {
292         g_free( localFilename );
293     }
295     return rdoc;
298 /**
299  * Reads and parses XML from a buffer, returning it as an Document
300  */
301 Document *
302 sp_repr_read_mem (const gchar * buffer, gint length, const gchar *default_ns)
304     xmlDocPtr doc;
305     Document * rdoc;
307     xmlSubstituteEntitiesDefault(1);
309     g_return_val_if_fail (buffer != NULL, NULL);
311     doc = xmlParseMemory ((gchar *) buffer, length);
313     rdoc = sp_repr_do_read (doc, default_ns);
314     if (doc)
315         xmlFreeDoc (doc);
316     return rdoc;
319 /**
320  * Reads and parses XML from a buffer, returning it as an Document
321  */
322 Document *
323 sp_repr_read_buf (const Glib::ustring &buf, const gchar *default_ns)
325     return sp_repr_read_mem(buf.c_str(), buf.size(), default_ns);
329 namespace Inkscape {
331 struct compare_quark_ids {
332     bool operator()(Glib::QueryQuark const &a, Glib::QueryQuark const &b) const {
333         return a.id() < b.id();
334     }
335 };
339 namespace {
341 typedef std::map<Glib::QueryQuark, Glib::QueryQuark, Inkscape::compare_quark_ids> PrefixMap;
343 Glib::QueryQuark qname_prefix(Glib::QueryQuark qname) {
344     static PrefixMap prefix_map;
345     PrefixMap::iterator iter = prefix_map.find(qname);
346     if ( iter != prefix_map.end() ) {
347         return (*iter).second;
348     } else {
349         gchar const *name_string=g_quark_to_string(qname);
350         gchar const *prefix_end=strchr(name_string, ':');
351         if (prefix_end) {
352             Glib::Quark prefix=Glib::ustring(name_string, prefix_end);
353             prefix_map.insert(PrefixMap::value_type(qname, prefix));
354             return prefix;
355         } else {
356             return GQuark(0);
357         }
358     }
363 namespace {
365 void promote_to_svg_namespace(Node *repr) {
366     if ( repr->type() == Inkscape::XML::ELEMENT_NODE ) {
367         GQuark code = repr->code();
368         if (!qname_prefix(code).id()) {
369             gchar *svg_name = g_strconcat("svg:", g_quark_to_string(code), NULL);
370             repr->setCodeUnsafe(g_quark_from_string(svg_name));
371             g_free(svg_name);
372         }
373         for ( Node *child = sp_repr_children(repr) ; child ; child = sp_repr_next(child) ) {
374             promote_to_svg_namespace(child);
375         }
376     }
381 /**
382  * Reads in a XML file to create a Document
383  */
384 Document *
385 sp_repr_do_read (xmlDocPtr doc, const gchar *default_ns)
387     if (doc == NULL) return NULL;
388     xmlNodePtr node=xmlDocGetRootElement (doc);
389     if (node == NULL) return NULL;
391     GHashTable * prefix_map;
392     prefix_map = g_hash_table_new (g_str_hash, g_str_equal);
394     Document *rdoc = new Inkscape::XML::SimpleDocument();
396     Node *root=NULL;
397     for ( node = doc->children ; node != NULL ; node = node->next ) {
398         if (node->type == XML_ELEMENT_NODE) {
399             Node *repr=sp_repr_svg_read_node(rdoc, node, default_ns, prefix_map);
400             rdoc->appendChild(repr);
401             Inkscape::GC::release(repr);
403             if (!root) {
404                 root = repr;
405             } else {
406                 root = NULL;
407                 break;
408             }
409         } else if ( node->type == XML_COMMENT_NODE || node->type == XML_PI_NODE ) {
410             Node *repr=sp_repr_svg_read_node(rdoc, node, default_ns, prefix_map);
411             rdoc->appendChild(repr);
412             Inkscape::GC::release(repr);
413         }
414     }
416     if (root != NULL) {
417         /* promote elements of SVG documents that don't use namespaces
418          * into the SVG namespace */
419         if ( default_ns && !strcmp(default_ns, SP_SVG_NS_URI)
420              && !strcmp(root->name(), "svg") )
421         {
422             promote_to_svg_namespace(root);
423         }
424     }
426     g_hash_table_destroy (prefix_map);
428     return rdoc;
431 gint
432 sp_repr_qualified_name (gchar *p, gint len, xmlNsPtr ns, const xmlChar *name, const gchar */*default_ns*/, GHashTable *prefix_map)
434     const xmlChar *prefix;
435     if ( ns && ns->href ) {
436         prefix = (xmlChar*)sp_xml_ns_uri_prefix ((gchar*)ns->href, (char*)ns->prefix);
437         g_hash_table_insert (prefix_map, (gpointer)prefix, (gpointer)ns->href);
438     } else {
439         prefix = NULL;
440     }
442     if (prefix)
443         return g_snprintf (p, len, "%s:%s", (gchar*)prefix, name);
444     else
445         return g_snprintf (p, len, "%s", name);
448 static Node *
449 sp_repr_svg_read_node (Document *xml_doc, xmlNodePtr node, const gchar *default_ns, GHashTable *prefix_map)
451     Node *repr, *crepr;
452     xmlAttrPtr prop;
453     xmlNodePtr child;
454     gchar c[256];
456     if (node->type == XML_TEXT_NODE || node->type == XML_CDATA_SECTION_NODE) {
458         if (node->content == NULL || *(node->content) == '\0')
459             return NULL; // empty text node
461         bool preserve = (xmlNodeGetSpacePreserve (node) == 1);
463         xmlChar *p;
464         for (p = node->content; *p && g_ascii_isspace (*p) && !preserve; p++)
465             ; // skip all whitespace
467         if (!(*p)) { // this is an all-whitespace node, and preserve == default
468             return NULL; // we do not preserve all-whitespace nodes unless we are asked to
469         }
471         return xml_doc->createTextNode((const gchar *)node->content);
472     }
474     if (node->type == XML_COMMENT_NODE)
475         return xml_doc->createComment((const gchar *)node->content);
477     if (node->type == XML_PI_NODE)
478         return xml_doc->createPI((const gchar *)node->name, (const gchar *)node->content);
480     if (node->type == XML_ENTITY_DECL) return NULL;
482     sp_repr_qualified_name (c, 256, node->ns, node->name, default_ns, prefix_map);
483     repr = xml_doc->createElement(c);
484     /* TODO remember node->ns->prefix if node->ns != NULL */
486     for (prop = node->properties; prop != NULL; prop = prop->next) {
487         if (prop->children) {
488             sp_repr_qualified_name (c, 256, prop->ns, prop->name, default_ns, prefix_map);
489             repr->setAttribute(c, (gchar*)prop->children->content);
490             /* TODO remember prop->ns->prefix if prop->ns != NULL */
491         }
492     }
494     if (node->content)
495         repr->setContent((gchar*)node->content);
497     child = node->xmlChildrenNode;
498     for (child = node->xmlChildrenNode; child != NULL; child = child->next) {
499         crepr = sp_repr_svg_read_node (xml_doc, child, default_ns, prefix_map);
500         if (crepr) {
501             repr->appendChild(crepr);
502             Inkscape::GC::release(crepr);
503         }
504     }
506     return repr;
510 void
511 sp_repr_save_writer(Document *doc, Inkscape::IO::Writer *out,
512               gchar const *default_ns)
514     int inlineattrs = prefs_get_int_attribute("options.svgoutput", "inlineattrs", 0);
515     int indent = prefs_get_int_attribute("options.svgoutput", "indent", 2);
517     /* fixme: do this The Right Way */
518     out->writeString( "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?>\n" );
520     const gchar *str = ((Node *)doc)->attribute("doctype");
521     if (str)
522         out->writeString( str );
524     Node *repr = sp_repr_document_first_child(doc);
525     for ( repr = sp_repr_document_first_child(doc) ;
526           repr ; repr = sp_repr_next(repr) )
527     {
528         if ( repr->type() == Inkscape::XML::ELEMENT_NODE ) {
529             sp_repr_write_stream_root_element(repr, *out, TRUE, default_ns, inlineattrs, indent);
530         } else if ( repr->type() == Inkscape::XML::COMMENT_NODE ) {
531             sp_repr_write_stream(repr, *out, 0, TRUE, GQuark(0), inlineattrs, indent);
532             out->writeChar( '\n' );
533         } else {
534             sp_repr_write_stream(repr, *out, 0, TRUE, GQuark(0), inlineattrs, indent);
535         }
536     }
542 Glib::ustring
543 sp_repr_save_buf(Document *doc)
544 {   
545     Inkscape::IO::StringOutputStream souts;
546     Inkscape::IO::OutputStreamWriter outs(souts);
548     sp_repr_save_writer(doc, &outs, SP_INKSCAPE_NS_URI);
550         outs.close();
551         Glib::ustring buf = souts.getString();
553         return buf;
560 void
561 sp_repr_save_stream (Document *doc, FILE *fp, gchar const *default_ns, bool compress)
563     Inkscape::URI dummy("x");
564     Inkscape::IO::UriOutputStream bout(fp, dummy);
565     Inkscape::IO::GzipOutputStream *gout = compress ? new Inkscape::IO::GzipOutputStream(bout) : NULL;
566     Inkscape::IO::OutputStreamWriter *out  = compress ? new Inkscape::IO::OutputStreamWriter( *gout ) : new Inkscape::IO::OutputStreamWriter( bout );
568     sp_repr_save_writer(doc, out, default_ns);
569     
570     delete out;
571     delete gout;
576 /* Returns TRUE if file successfully saved; FALSE if not
577  */
578 bool
579 sp_repr_save_file (Document *doc, const gchar *filename,
580                    gchar const *default_ns)
582     if (filename == NULL) {
583         return FALSE;
584     }
585     bool compress = false;
586     {
587         if (strlen (filename) > 5) {
588             gchar tmp[] = {0,0,0,0,0,0};
589             strncpy( tmp, filename + strlen (filename) - 5, 6 );
590             tmp[5] = 0;
591             if ( strcasecmp(".svgz", tmp ) == 0 )
592             {
593                 //g_message("TIME TO COMPRESS THE OUTPUT FOR SVGZ");
594                 compress = true;
595             }
596         }
597     }
599     Inkscape::IO::dump_fopen_call( filename, "B" );
600     FILE *file = Inkscape::IO::fopen_utf8name(filename, "w");
601     if (file == NULL) {
602         return FALSE;
603     }
605     sp_repr_save_stream (doc, file, default_ns, compress);
607     if (fclose (file) != 0) {
608         return FALSE;
609     }
611     return TRUE;
614 void
615 sp_repr_print (Node * repr)
617     Inkscape::IO::StdOutputStream bout;
618     Inkscape::IO::OutputStreamWriter out(bout);
620     sp_repr_write_stream (repr, out, 0, TRUE, GQuark(0), 0, 2);
622     return;
625 /* (No doubt this function already exists elsewhere.) */
626 static void
627 repr_quote_write (Writer &out, const gchar * val)
629     if (!val) return;
631     for (; *val != '\0'; val++) {
632         switch (*val) {
633         case '"': out.writeString( "&quot;" ); break;
634         case '&': out.writeString( "&amp;" ); break;
635         case '<': out.writeString( "&lt;" ); break;
636         case '>': out.writeString( "&gt;" ); break;
637         default: out.writeChar( *val ); break;
638         }
639     }
642 namespace {
644 typedef std::map<Glib::QueryQuark, gchar const *, Inkscape::compare_quark_ids> LocalNameMap;
645 typedef std::map<Glib::QueryQuark, Inkscape::Util::ptr_shared<char>, Inkscape::compare_quark_ids> NSMap;
647 gchar const *qname_local_name(Glib::QueryQuark qname) {
648     static LocalNameMap local_name_map;
649     LocalNameMap::iterator iter = local_name_map.find(qname);
650     if ( iter != local_name_map.end() ) {
651         return (*iter).second;
652     } else {
653         gchar const *name_string=g_quark_to_string(qname);
654         gchar const *prefix_end=strchr(name_string, ':');
655         if (prefix_end) {
656             return prefix_end + 1;
657         } else {
658             return name_string;
659         }
660     }
663 void add_ns_map_entry(NSMap &ns_map, Glib::QueryQuark prefix) {
664     using Inkscape::Util::ptr_shared;
665     using Inkscape::Util::share_unsafe;
667     static const Glib::QueryQuark xml_prefix("xml");
669     NSMap::iterator iter=ns_map.find(prefix);
670     if ( iter == ns_map.end() ) {
671         if (prefix.id()) {
672             gchar const *uri=sp_xml_ns_prefix_uri(g_quark_to_string(prefix));
673             if (uri) {
674                 ns_map.insert(NSMap::value_type(prefix, share_unsafe(uri)));
675             } else if ( prefix != xml_prefix ) {
676                 g_warning("No namespace known for normalized prefix %s", g_quark_to_string(prefix));
677             }
678         } else {
679             ns_map.insert(NSMap::value_type(prefix, ptr_shared<char>()));
680         }
681     }
684 void populate_ns_map(NSMap &ns_map, Node &repr) {
685     if ( repr.type() == Inkscape::XML::ELEMENT_NODE ) {
686         add_ns_map_entry(ns_map, qname_prefix(repr.code()));
687         for ( List<AttributeRecord const> iter=repr.attributeList() ;
688               iter ; ++iter )
689         {
690             Glib::QueryQuark prefix=qname_prefix(iter->key);
691             if (prefix.id()) {
692                 add_ns_map_entry(ns_map, prefix);
693             }
694         }
695         for ( Node *child=sp_repr_children(&repr) ;
696               child ; child = sp_repr_next(child) )
697         {
698             populate_ns_map(ns_map, *child);
699         }
700     }
705 void
706 sp_repr_write_stream_root_element (Node *repr, Writer &out, bool add_whitespace, gchar const *default_ns, 
707                                    int inlineattrs, int indent)
709     using Inkscape::Util::ptr_shared;
710     g_assert(repr != NULL);
711     Glib::QueryQuark xml_prefix=g_quark_from_static_string("xml");
713     NSMap ns_map;
714     populate_ns_map(ns_map, *repr);
716     Glib::QueryQuark elide_prefix=GQuark(0);
717     if ( default_ns && ns_map.find(GQuark(0)) == ns_map.end() ) {
718         elide_prefix = g_quark_from_string(sp_xml_ns_uri_prefix(default_ns, NULL));
719     }
721     List<AttributeRecord const> attributes=repr->attributeList();
722     for ( NSMap::iterator iter=ns_map.begin() ; iter != ns_map.end() ; ++iter ) 
723     {
724         Glib::QueryQuark prefix=(*iter).first;
725         ptr_shared<char> ns_uri=(*iter).second;
727         if (prefix.id()) {
728             if ( prefix != xml_prefix ) {
729                 if ( elide_prefix == prefix ) {
730                     attributes = cons(AttributeRecord(g_quark_from_static_string("xmlns"), ns_uri), attributes);
731                 }
733                 Glib::ustring attr_name="xmlns:";
734                 attr_name.append(g_quark_to_string(prefix));
735                 GQuark key = g_quark_from_string(attr_name.c_str());
736                 attributes = cons(AttributeRecord(key, ns_uri), attributes);
737             }
738         } else {
739             // if there are non-namespaced elements, we can't globally
740             // use a default namespace
741             elide_prefix = GQuark(0);
742         }
743     }
745     return sp_repr_write_stream_element(repr, out, 0, add_whitespace, elide_prefix, attributes, inlineattrs, indent);
748 void
749 sp_repr_write_stream (Node *repr, Writer &out, gint indent_level,
750                       bool add_whitespace, Glib::QueryQuark elide_prefix, int inlineattrs, int indent)
752     if (repr->type() == Inkscape::XML::TEXT_NODE) {
753         repr_quote_write (out, repr->content());
754     } else if (repr->type() == Inkscape::XML::COMMENT_NODE) {
755         out.printf( "<!--%s-->", repr->content() );
756     } else if (repr->type() == Inkscape::XML::PI_NODE) {
757         out.printf( "<?%s %s?>", repr->name(), repr->content() );
758     } else if (repr->type() == Inkscape::XML::ELEMENT_NODE) {
759         sp_repr_write_stream_element(repr, out, indent_level, add_whitespace, elide_prefix, repr->attributeList(), inlineattrs, indent);
760     } else {
761         g_assert_not_reached();
762     }
766 void
767 sp_repr_write_stream_element (Node * repr, Writer & out, gint indent_level,
768                               bool add_whitespace,
769                               Glib::QueryQuark elide_prefix,
770                               List<AttributeRecord const> attributes, 
771                               int inlineattrs, int indent)
773     Node *child;
774     bool loose;
776     g_return_if_fail (repr != NULL);
778     if ( indent_level > 16 )
779         indent_level = 16;
781     if (add_whitespace && indent) {
782         for (gint i = 0; i < indent_level; i++) {
783             for (gint j = 0; j < indent; j++) {
784                 out.writeString(" ");
785             }
786         }
787     }
789     GQuark code = repr->code();
790     gchar const *element_name;
791     if ( elide_prefix == qname_prefix(code) ) {
792         element_name = qname_local_name(code);
793     } else {
794         element_name = g_quark_to_string(code);
795     }
796     out.printf( "<%s", element_name );
798     // if this is a <text> element, suppress formatting whitespace
799     // for its content and children:
800     gchar const *xml_space_attr = repr->attribute("xml:space");
801     if (xml_space_attr != NULL && !strcmp(xml_space_attr, "preserve")) {
802         add_whitespace = FALSE;
803     }
805     for ( List<AttributeRecord const> iter = attributes ;
806           iter ; ++iter )
807     {
808         if (!inlineattrs) {
809             out.writeString("\n");
810             if (indent) {
811                 for ( gint i = 0 ; i < indent_level + 1 ; i++ ) {
812                     for ( gint j = 0 ; j < indent ; j++ ) {
813                         out.writeString(" ");
814                     }
815                 }
816             }
817         }
818         out.printf(" %s=\"", g_quark_to_string(iter->key));
819         repr_quote_write(out, iter->value);
820         out.writeChar('"');
821     }
823     loose = TRUE;
824     for (child = repr->firstChild() ; child != NULL; child = child->next()) {
825         if (child->type() == Inkscape::XML::TEXT_NODE) {
826             loose = FALSE;
827             break;
828         }
829     }
830     if (repr->firstChild()) {
831         out.writeString( ">" );
832         if (loose && add_whitespace) {
833             out.writeString( "\n" );
834         }
835         for (child = repr->firstChild(); child != NULL; child = child->next()) {
836             sp_repr_write_stream (child, out, (loose) ? (indent_level + 1) : 0, add_whitespace, elide_prefix, inlineattrs, indent);
837         }
839         if (loose && add_whitespace && indent) {
840             for (gint i = 0; i < indent_level; i++) {
841                 for ( gint j = 0 ; j < indent ; j++ ) {
842                     out.writeString(" ");
843                 }
844             }
845         }
846         out.printf( "</%s>", element_name );
847     } else {
848         out.writeString( " />" );
849     }
851     // text elements cannot nest, so we can output newline
852     // after closing text
854     if (add_whitespace || !strcmp (repr->name(), "svg:text")) {
855         out.writeString( "\n" );
856     }
860 /*
861   Local Variables:
862   mode:c++
863   c-file-style:"stroustrup"
864   c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
865   indent-tabs-mode:nil
866   fill-column:99
867   End:
868 */
869 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :