Code

now that selection description includes style (filtered, clipped), we need to update...
[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 "extension/extension.h"
34 #include "preferences.h"
36 using Inkscape::IO::Writer;
37 using Inkscape::Util::List;
38 using Inkscape::Util::cons;
39 using Inkscape::XML::Document;
40 using Inkscape::XML::SimpleDocument;
41 using Inkscape::XML::Node;
42 using Inkscape::XML::AttributeRecord;
44 Document *sp_repr_do_read (xmlDocPtr doc, const gchar *default_ns);
45 static Node *sp_repr_svg_read_node (Document *xml_doc, xmlNodePtr node, const gchar *default_ns, GHashTable *prefix_map);
46 static gint sp_repr_qualified_name (gchar *p, gint len, xmlNsPtr ns, const xmlChar *name, const gchar *default_ns, GHashTable *prefix_map);
47 static void sp_repr_write_stream_root_element (Node *repr, Writer &out, bool add_whitespace, gchar const *default_ns, int inlineattrs, int indent);
48 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);
50 #ifdef HAVE_LIBWMF
51 static xmlDocPtr sp_wmf_convert (const char * file_name);
52 static char * sp_wmf_image_name (void * context);
53 #endif /* HAVE_LIBWMF */
56 class XmlSource
57 {
58 public:
59     XmlSource()
60         : filename(0),
61           encoding(0),
62           fp(0),
63           firstFewLen(0),
64           dummy("x"),
65           instr(0),
66           gzin(0)
67     {
68     }
69     virtual ~XmlSource()
70     {
71         close();
72         if ( encoding ) {
73             g_free(encoding);
74             encoding = 0;
75         }
76     }
78     int setFile( char const * filename );
80     static int readCb( void * context, char * buffer, int len );
81     static int closeCb( void * context );
83     char const* getEncoding() const { return encoding; }
84     int read( char * buffer, int len );
85     int close();
86 private:
87     const char* filename;
88     char* encoding;
89     FILE* fp;
90     unsigned char firstFew[4];
91     int firstFewLen;
92     Inkscape::URI dummy;
93     Inkscape::IO::UriInputStream* instr;
94     Inkscape::IO::GzipInputStream* gzin;
95 };
97 int XmlSource::setFile(char const *filename)
98 {
99     int retVal = -1;
101     this->filename = filename;
103     fp = Inkscape::IO::fopen_utf8name(filename, "r");
104     if ( fp ) {
105         // First peek in the file to see what it is
106         memset( firstFew, 0, sizeof(firstFew) );
108         size_t some = fread( firstFew, 1, 4, fp );
109         if ( fp ) {
110             // first check for compression
111             if ( (some >= 2) && (firstFew[0] == 0x1f) && (firstFew[1] == 0x8b) ) {
112                 //g_message(" the file being read is gzip'd. extract it");
113                 fclose(fp);
114                 fp = 0;
115                 fp = Inkscape::IO::fopen_utf8name(filename, "r");
116                 instr = new Inkscape::IO::UriInputStream(fp, dummy);
117                 gzin = new Inkscape::IO::GzipInputStream(*instr);
119                 memset( firstFew, 0, sizeof(firstFew) );
120                 some = 0;
121                 int single = 0;
122                 while ( some < 4 && single >= 0 )
123                 {
124                     single = gzin->get();
125                     if ( single >= 0 ) {
126                         firstFew[some++] = 0x0ff & single;
127                     } else {
128                         break;
129                     }
130                 }
131             }
133             int encSkip = 0;
134             if ( (some >= 2) &&(firstFew[0] == 0xfe) && (firstFew[1] == 0xff) ) {
135                 encoding = g_strdup("UTF-16BE");
136                 encSkip = 2;
137             } else if ( (some >= 2) && (firstFew[0] == 0xff) && (firstFew[1] == 0xfe) ) {
138                 encoding = g_strdup("UTF-16LE");
139                 encSkip = 2;
140             } else if ( (some >= 3) && (firstFew[0] == 0xef) && (firstFew[1] == 0xbb) && (firstFew[2] == 0xbf) ) {
141                 encoding = g_strdup("UTF-8");
142                 encSkip = 3;
143             }
145             if ( encSkip ) {
146                 memmove( firstFew, firstFew + encSkip, (some - encSkip) );
147                 some -= encSkip;
148             }
150             firstFewLen = some;
151             retVal = 0; // no error
152         }
153     }
155     return retVal;
159 int XmlSource::readCb( void * context, char * buffer, int len )
161     int retVal = -1;
162     if ( context ) {
163         XmlSource* self = static_cast<XmlSource*>(context);
164         retVal = self->read( buffer, len );
165     }
166     return retVal;
169 int XmlSource::closeCb(void * context)
171     if ( context ) {
172         XmlSource* self = static_cast<XmlSource*>(context);
173         self->close();
174     }
175     return 0;
178 int XmlSource::read( char *buffer, int len )
180     int retVal = 0;
181     size_t got = 0;
183     if ( firstFewLen > 0 ) {
184         int some = (len < firstFewLen) ? len : firstFewLen;
185         memcpy( buffer, firstFew, some );
186         if ( len < firstFewLen ) {
187             memmove( firstFew, firstFew + some, (firstFewLen - some) );
188         }
189         firstFewLen -= some;
190         got = some;
191     } else if ( gzin ) {
192         int single = 0;
193         while ( (int)got < len && single >= 0 )
194         {
195             single = gzin->get();
196             if ( single >= 0 ) {
197                 buffer[got++] = 0x0ff & single;
198             } else {
199                 break;
200             }
201         }
202     } else {
203         got = fread( buffer, 1, len, fp );
204     }
206     if ( feof(fp) ) {
207         retVal = got;
208     } else if ( ferror(fp) ) {
209         retVal = -1;
210     } else {
211         retVal = got;
212     }
214     return retVal;
217 int XmlSource::close()
219     if ( gzin ) {
220         gzin->close();
221         delete gzin;
222         gzin = 0;
223     }
224     if ( instr ) {
225         instr->close();
226         fp = 0;
227         delete instr;
228         instr = 0;
229     }
230     if ( fp ) {
231         fclose(fp);
232         fp = 0;
233     }
234     return 0;
237 /**
238  * Reads XML from a file, including WMF files, and returns the Document.
239  * The default namespace can also be specified, if desired.
240  */
241 Document *
242 sp_repr_read_file (const gchar * filename, const gchar *default_ns)
244     xmlDocPtr doc = 0;
245     Document * rdoc = 0;
247     xmlSubstituteEntitiesDefault(1);
249     g_return_val_if_fail (filename != NULL, NULL);
250     g_return_val_if_fail (Inkscape::IO::file_test( filename, G_FILE_TEST_EXISTS ), NULL);
251     /* fixme: A file can disappear at any time, including between now and when we actually try to
252      * open it.  Get rid of the above test once we're sure that we correctly handle
253      * non-existence. */
255     // TODO: bulia, please look over
256     gsize bytesRead = 0;
257     gsize bytesWritten = 0;
258     GError* error = NULL;
259     // TODO: need to replace with our own fopen and reading
260     gchar* localFilename = g_filename_from_utf8 ( filename,
261                                  -1,  &bytesRead,  &bytesWritten, &error);
262     g_return_val_if_fail( localFilename != NULL, NULL );
264     Inkscape::IO::dump_fopen_call( filename, "N" );
266 #ifdef HAVE_LIBWMF
267     if (strlen (localFilename) > 4) {
268         if ( (strcmp (localFilename + strlen (localFilename) - 4,".wmf") == 0)
269              || (strcmp (localFilename + strlen (localFilename) - 4,".WMF") == 0)) {
270             doc = sp_wmf_convert (localFilename);
271         }
272     }
273 #endif // !HAVE_LIBWMF
275     if ( !doc ) {
276         XmlSource src;
278         if ( (src.setFile(filename) == 0) ) {
279             doc = xmlReadIO( XmlSource::readCb,
280                              XmlSource::closeCb,
281                              &src,
282                              localFilename,
283                              src.getEncoding(),
284                              XML_PARSE_NOENT );
285         }
286     }
288     rdoc = sp_repr_do_read( doc, default_ns );
289     if ( doc ) {
290         xmlFreeDoc( doc );
291     }
293     if ( localFilename ) {
294         g_free( localFilename );
295     }
297     return rdoc;
300 /**
301  * Reads and parses XML from a buffer, returning it as an Document
302  */
303 Document *
304 sp_repr_read_mem (const gchar * buffer, gint length, const gchar *default_ns)
306     xmlDocPtr doc;
307     Document * rdoc;
309     xmlSubstituteEntitiesDefault(1);
311     g_return_val_if_fail (buffer != NULL, NULL);
313     doc = xmlParseMemory ((gchar *) buffer, length);
315     rdoc = sp_repr_do_read (doc, default_ns);
316     if (doc)
317         xmlFreeDoc (doc);
318     return rdoc;
321 /**
322  * Reads and parses XML from a buffer, returning it as an Document
323  */
324 Document *
325 sp_repr_read_buf (const Glib::ustring &buf, const gchar *default_ns)
327     return sp_repr_read_mem(buf.c_str(), buf.size(), default_ns);
331 namespace Inkscape {
333 struct compare_quark_ids {
334     bool operator()(Glib::QueryQuark const &a, Glib::QueryQuark const &b) const {
335         return a.id() < b.id();
336     }
337 };
341 namespace {
343 typedef std::map<Glib::QueryQuark, Glib::QueryQuark, Inkscape::compare_quark_ids> PrefixMap;
345 Glib::QueryQuark qname_prefix(Glib::QueryQuark qname) {
346     static PrefixMap prefix_map;
347     PrefixMap::iterator iter = prefix_map.find(qname);
348     if ( iter != prefix_map.end() ) {
349         return (*iter).second;
350     } else {
351         gchar const *name_string=g_quark_to_string(qname);
352         gchar const *prefix_end=strchr(name_string, ':');
353         if (prefix_end) {
354             Glib::Quark prefix=Glib::ustring(name_string, prefix_end);
355             prefix_map.insert(PrefixMap::value_type(qname, prefix));
356             return prefix;
357         } else {
358             return GQuark(0);
359         }
360     }
365 namespace {
367 void promote_to_namespace(Node *repr, const gchar *prefix) {
368     if ( repr->type() == Inkscape::XML::ELEMENT_NODE ) {
369         GQuark code = repr->code();
370         if (!qname_prefix(code).id()) {
371             gchar *svg_name = g_strconcat(prefix, ":", g_quark_to_string(code), NULL);
372             repr->setCodeUnsafe(g_quark_from_string(svg_name));
373             g_free(svg_name);
374         }
375         for ( Node *child = sp_repr_children(repr) ; child ; child = sp_repr_next(child) ) {
376             promote_to_namespace(child, prefix);
377         }
378     }
383 /**
384  * Reads in a XML file to create a Document
385  */
386 Document *
387 sp_repr_do_read (xmlDocPtr doc, const gchar *default_ns)
389     if (doc == NULL) return NULL;
390     xmlNodePtr node=xmlDocGetRootElement (doc);
391     if (node == NULL) return NULL;
393     GHashTable * prefix_map;
394     prefix_map = g_hash_table_new (g_str_hash, g_str_equal);
396     Document *rdoc = new Inkscape::XML::SimpleDocument();
398     Node *root=NULL;
399     for ( node = doc->children ; node != NULL ; node = node->next ) {
400         if (node->type == XML_ELEMENT_NODE) {
401             Node *repr=sp_repr_svg_read_node(rdoc, node, default_ns, prefix_map);
402             rdoc->appendChild(repr);
403             Inkscape::GC::release(repr);
405             if (!root) {
406                 root = repr;
407             } else {
408                 root = NULL;
409                 break;
410             }
411         } else if ( node->type == XML_COMMENT_NODE || node->type == XML_PI_NODE ) {
412             Node *repr=sp_repr_svg_read_node(rdoc, node, default_ns, prefix_map);
413             rdoc->appendChild(repr);
414             Inkscape::GC::release(repr);
415         }
416     }
418     if (root != NULL) {
419         /* promote elements of some XML documents that don't use namespaces
420          * into their default namespace */
421         if ( default_ns && !strchr(root->name(), ':') ) {
422             if ( !strcmp(default_ns, SP_SVG_NS_URI) )
423                 promote_to_namespace(root, "svg");
424             if ( !strcmp(default_ns, INKSCAPE_EXTENSION_URI) )
425                 promote_to_namespace(root, INKSCAPE_EXTENSION_NS_NC);
426         }
427     }
429     g_hash_table_destroy (prefix_map);
431     return rdoc;
434 gint
435 sp_repr_qualified_name (gchar *p, gint len, xmlNsPtr ns, const xmlChar *name, const gchar */*default_ns*/, GHashTable *prefix_map)
437     const xmlChar *prefix;
438     if ( ns && ns->href ) {
439         prefix = (xmlChar*)sp_xml_ns_uri_prefix ((gchar*)ns->href, (char*)ns->prefix);
440         g_hash_table_insert (prefix_map, (gpointer)prefix, (gpointer)ns->href);
441     } else {
442         prefix = NULL;
443     }
445     if (prefix)
446         return g_snprintf (p, len, "%s:%s", (gchar*)prefix, name);
447     else
448         return g_snprintf (p, len, "%s", name);
451 static Node *
452 sp_repr_svg_read_node (Document *xml_doc, xmlNodePtr node, const gchar *default_ns, GHashTable *prefix_map)
454     Node *repr, *crepr;
455     xmlAttrPtr prop;
456     xmlNodePtr child;
457     gchar c[256];
459     if (node->type == XML_TEXT_NODE || node->type == XML_CDATA_SECTION_NODE) {
461         if (node->content == NULL || *(node->content) == '\0')
462             return NULL; // empty text node
464         bool preserve = (xmlNodeGetSpacePreserve (node) == 1);
466         xmlChar *p;
467         for (p = node->content; *p && g_ascii_isspace (*p) && !preserve; p++)
468             ; // skip all whitespace
470         if (!(*p)) { // this is an all-whitespace node, and preserve == default
471             return NULL; // we do not preserve all-whitespace nodes unless we are asked to
472         }
474         return xml_doc->createTextNode((const gchar *)node->content);
475     }
477     if (node->type == XML_COMMENT_NODE)
478         return xml_doc->createComment((const gchar *)node->content);
480     if (node->type == XML_PI_NODE)
481         return xml_doc->createPI((const gchar *)node->name, (const gchar *)node->content);
483     if (node->type == XML_ENTITY_DECL) return NULL;
485     sp_repr_qualified_name (c, 256, node->ns, node->name, default_ns, prefix_map);
486     repr = xml_doc->createElement(c);
487     /* TODO remember node->ns->prefix if node->ns != NULL */
489     for (prop = node->properties; prop != NULL; prop = prop->next) {
490         if (prop->children) {
491             sp_repr_qualified_name (c, 256, prop->ns, prop->name, default_ns, prefix_map);
492             repr->setAttribute(c, (gchar*)prop->children->content);
493             /* TODO remember prop->ns->prefix if prop->ns != NULL */
494         }
495     }
497     if (node->content)
498         repr->setContent((gchar*)node->content);
500     child = node->xmlChildrenNode;
501     for (child = node->xmlChildrenNode; child != NULL; child = child->next) {
502         crepr = sp_repr_svg_read_node (xml_doc, child, default_ns, prefix_map);
503         if (crepr) {
504             repr->appendChild(crepr);
505             Inkscape::GC::release(crepr);
506         }
507     }
509     return repr;
513 void
514 sp_repr_save_writer(Document *doc, Inkscape::IO::Writer *out,
515               gchar const *default_ns)
517     Inkscape::Preferences *prefs = Inkscape::Preferences::get();
518     bool inlineattrs = prefs->getBool("/options/svgoutput/inlineattrs");
519     int indent = prefs->getInt("/options/svgoutput/indent", 2);
521     /* fixme: do this The Right Way */
522     out->writeString( "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?>\n" );
524     const gchar *str = ((Node *)doc)->attribute("doctype");
525     if (str)
526         out->writeString( str );
528     Node *repr = sp_repr_document_first_child(doc);
529     for ( repr = sp_repr_document_first_child(doc) ;
530           repr ; repr = sp_repr_next(repr) )
531     {
532         if ( repr->type() == Inkscape::XML::ELEMENT_NODE ) {
533             sp_repr_write_stream_root_element(repr, *out, TRUE, default_ns, inlineattrs, indent);
534         } else if ( repr->type() == Inkscape::XML::COMMENT_NODE ) {
535             sp_repr_write_stream(repr, *out, 0, TRUE, GQuark(0), inlineattrs, indent);
536             out->writeChar( '\n' );
537         } else {
538             sp_repr_write_stream(repr, *out, 0, TRUE, GQuark(0), inlineattrs, indent);
539         }
540     }
546 Glib::ustring
547 sp_repr_save_buf(Document *doc)
548 {   
549     Inkscape::IO::StringOutputStream souts;
550     Inkscape::IO::OutputStreamWriter outs(souts);
552     sp_repr_save_writer(doc, &outs, SP_INKSCAPE_NS_URI);
554         outs.close();
555         Glib::ustring buf = souts.getString();
557         return buf;
564 void
565 sp_repr_save_stream (Document *doc, FILE *fp, gchar const *default_ns, bool compress)
567     Inkscape::URI dummy("x");
568     Inkscape::IO::UriOutputStream bout(fp, dummy);
569     Inkscape::IO::GzipOutputStream *gout = compress ? new Inkscape::IO::GzipOutputStream(bout) : NULL;
570     Inkscape::IO::OutputStreamWriter *out  = compress ? new Inkscape::IO::OutputStreamWriter( *gout ) : new Inkscape::IO::OutputStreamWriter( bout );
572     sp_repr_save_writer(doc, out, default_ns);
573     
574     delete out;
575     delete gout;
580 /* Returns TRUE if file successfully saved; FALSE if not
581  */
582 bool
583 sp_repr_save_file (Document *doc, const gchar *filename,
584                    gchar const *default_ns)
586     if (filename == NULL) {
587         return FALSE;
588     }
589     bool compress = false;
590     {
591         if (strlen (filename) > 5) {
592             gchar tmp[] = {0,0,0,0,0,0};
593             strncpy( tmp, filename + strlen (filename) - 5, 6 );
594             tmp[5] = 0;
595             if ( strcasecmp(".svgz", tmp ) == 0 )
596             {
597                 //g_message("TIME TO COMPRESS THE OUTPUT FOR SVGZ");
598                 compress = true;
599             }
600         }
601     }
603     Inkscape::IO::dump_fopen_call( filename, "B" );
604     FILE *file = Inkscape::IO::fopen_utf8name(filename, "w");
605     if (file == NULL) {
606         return FALSE;
607     }
609     sp_repr_save_stream (doc, file, default_ns, compress);
611     if (fclose (file) != 0) {
612         return FALSE;
613     }
615     return TRUE;
618 void
619 sp_repr_print (Node * repr)
621     Inkscape::IO::StdOutputStream bout;
622     Inkscape::IO::OutputStreamWriter out(bout);
624     sp_repr_write_stream (repr, out, 0, TRUE, GQuark(0), 0, 2);
626     return;
629 /* (No doubt this function already exists elsewhere.) */
630 static void
631 repr_quote_write (Writer &out, const gchar * val)
633     if (!val) return;
635     for (; *val != '\0'; val++) {
636         switch (*val) {
637         case '"': out.writeString( "&quot;" ); break;
638         case '&': out.writeString( "&amp;" ); break;
639         case '<': out.writeString( "&lt;" ); break;
640         case '>': out.writeString( "&gt;" ); break;
641         default: out.writeChar( *val ); break;
642         }
643     }
646 namespace {
648 typedef std::map<Glib::QueryQuark, gchar const *, Inkscape::compare_quark_ids> LocalNameMap;
649 typedef std::map<Glib::QueryQuark, Inkscape::Util::ptr_shared<char>, Inkscape::compare_quark_ids> NSMap;
651 gchar const *qname_local_name(Glib::QueryQuark qname) {
652     static LocalNameMap local_name_map;
653     LocalNameMap::iterator iter = local_name_map.find(qname);
654     if ( iter != local_name_map.end() ) {
655         return (*iter).second;
656     } else {
657         gchar const *name_string=g_quark_to_string(qname);
658         gchar const *prefix_end=strchr(name_string, ':');
659         if (prefix_end) {
660             return prefix_end + 1;
661         } else {
662             return name_string;
663         }
664     }
667 void add_ns_map_entry(NSMap &ns_map, Glib::QueryQuark prefix) {
668     using Inkscape::Util::ptr_shared;
669     using Inkscape::Util::share_unsafe;
671     static const Glib::QueryQuark xml_prefix("xml");
673     NSMap::iterator iter=ns_map.find(prefix);
674     if ( iter == ns_map.end() ) {
675         if (prefix.id()) {
676             gchar const *uri=sp_xml_ns_prefix_uri(g_quark_to_string(prefix));
677             if (uri) {
678                 ns_map.insert(NSMap::value_type(prefix, share_unsafe(uri)));
679             } else if ( prefix != xml_prefix ) {
680                 g_warning("No namespace known for normalized prefix %s", g_quark_to_string(prefix));
681             }
682         } else {
683             ns_map.insert(NSMap::value_type(prefix, ptr_shared<char>()));
684         }
685     }
688 void populate_ns_map(NSMap &ns_map, Node &repr) {
689     if ( repr.type() == Inkscape::XML::ELEMENT_NODE ) {
690         add_ns_map_entry(ns_map, qname_prefix(repr.code()));
691         for ( List<AttributeRecord const> iter=repr.attributeList() ;
692               iter ; ++iter )
693         {
694             Glib::QueryQuark prefix=qname_prefix(iter->key);
695             if (prefix.id()) {
696                 add_ns_map_entry(ns_map, prefix);
697             }
698         }
699         for ( Node *child=sp_repr_children(&repr) ;
700               child ; child = sp_repr_next(child) )
701         {
702             populate_ns_map(ns_map, *child);
703         }
704     }
709 void
710 sp_repr_write_stream_root_element (Node *repr, Writer &out, bool add_whitespace, gchar const *default_ns, 
711                                    int inlineattrs, int indent)
713     using Inkscape::Util::ptr_shared;
714     g_assert(repr != NULL);
715     Glib::QueryQuark xml_prefix=g_quark_from_static_string("xml");
717     NSMap ns_map;
718     populate_ns_map(ns_map, *repr);
720     Glib::QueryQuark elide_prefix=GQuark(0);
721     if ( default_ns && ns_map.find(GQuark(0)) == ns_map.end() ) {
722         elide_prefix = g_quark_from_string(sp_xml_ns_uri_prefix(default_ns, NULL));
723     }
725     List<AttributeRecord const> attributes=repr->attributeList();
726     for ( NSMap::iterator iter=ns_map.begin() ; iter != ns_map.end() ; ++iter ) 
727     {
728         Glib::QueryQuark prefix=(*iter).first;
729         ptr_shared<char> ns_uri=(*iter).second;
731         if (prefix.id()) {
732             if ( prefix != xml_prefix ) {
733                 if ( elide_prefix == prefix ) {
734                     attributes = cons(AttributeRecord(g_quark_from_static_string("xmlns"), ns_uri), attributes);
735                 }
737                 Glib::ustring attr_name="xmlns:";
738                 attr_name.append(g_quark_to_string(prefix));
739                 GQuark key = g_quark_from_string(attr_name.c_str());
740                 attributes = cons(AttributeRecord(key, ns_uri), attributes);
741             }
742         } else {
743             // if there are non-namespaced elements, we can't globally
744             // use a default namespace
745             elide_prefix = GQuark(0);
746         }
747     }
749     return sp_repr_write_stream_element(repr, out, 0, add_whitespace, elide_prefix, attributes, inlineattrs, indent);
752 void
753 sp_repr_write_stream (Node *repr, Writer &out, gint indent_level,
754                       bool add_whitespace, Glib::QueryQuark elide_prefix, int inlineattrs, int indent)
756     if (repr->type() == Inkscape::XML::TEXT_NODE) {
757         repr_quote_write (out, repr->content());
758     } else if (repr->type() == Inkscape::XML::COMMENT_NODE) {
759         out.printf( "<!--%s-->", repr->content() );
760     } else if (repr->type() == Inkscape::XML::PI_NODE) {
761         out.printf( "<?%s %s?>", repr->name(), repr->content() );
762     } else if (repr->type() == Inkscape::XML::ELEMENT_NODE) {
763         sp_repr_write_stream_element(repr, out, indent_level, add_whitespace, elide_prefix, repr->attributeList(), inlineattrs, indent);
764     } else {
765         g_assert_not_reached();
766     }
770 void
771 sp_repr_write_stream_element (Node * repr, Writer & out, gint indent_level,
772                               bool add_whitespace,
773                               Glib::QueryQuark elide_prefix,
774                               List<AttributeRecord const> attributes, 
775                               int inlineattrs, int indent)
777     Node *child;
778     bool loose;
780     g_return_if_fail (repr != NULL);
782     if ( indent_level > 16 )
783         indent_level = 16;
785     if (add_whitespace && indent) {
786         for (gint i = 0; i < indent_level; i++) {
787             for (gint j = 0; j < indent; j++) {
788                 out.writeString(" ");
789             }
790         }
791     }
793     GQuark code = repr->code();
794     gchar const *element_name;
795     if ( elide_prefix == qname_prefix(code) ) {
796         element_name = qname_local_name(code);
797     } else {
798         element_name = g_quark_to_string(code);
799     }
800     out.printf( "<%s", element_name );
802     // if this is a <text> element, suppress formatting whitespace
803     // for its content and children:
804     gchar const *xml_space_attr = repr->attribute("xml:space");
805     if (xml_space_attr != NULL && !strcmp(xml_space_attr, "preserve")) {
806         add_whitespace = FALSE;
807     }
809     for ( List<AttributeRecord const> iter = attributes ;
810           iter ; ++iter )
811     {
812         if (!inlineattrs) {
813             out.writeString("\n");
814             if (indent) {
815                 for ( gint i = 0 ; i < indent_level + 1 ; i++ ) {
816                     for ( gint j = 0 ; j < indent ; j++ ) {
817                         out.writeString(" ");
818                     }
819                 }
820             }
821         }
822         out.printf(" %s=\"", g_quark_to_string(iter->key));
823         repr_quote_write(out, iter->value);
824         out.writeChar('"');
825     }
827     loose = TRUE;
828     for (child = repr->firstChild() ; child != NULL; child = child->next()) {
829         if (child->type() == Inkscape::XML::TEXT_NODE) {
830             loose = FALSE;
831             break;
832         }
833     }
834     if (repr->firstChild()) {
835         out.writeString( ">" );
836         if (loose && add_whitespace) {
837             out.writeString( "\n" );
838         }
839         for (child = repr->firstChild(); child != NULL; child = child->next()) {
840             sp_repr_write_stream (child, out, (loose) ? (indent_level + 1) : 0, add_whitespace, elide_prefix, inlineattrs, indent);
841         }
843         if (loose && add_whitespace && indent) {
844             for (gint i = 0; i < indent_level; i++) {
845                 for ( gint j = 0 ; j < indent ; j++ ) {
846                     out.writeString(" ");
847                 }
848             }
849         }
850         out.printf( "</%s>", element_name );
851     } else {
852         out.writeString( " />" );
853     }
855     // text elements cannot nest, so we can output newline
856     // after closing text
858     if (add_whitespace || !strcmp (repr->name(), "svg:text")) {
859         out.writeString( "\n" );
860     }
864 /*
865   Local Variables:
866   mode:c++
867   c-file-style:"stroustrup"
868   c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
869   indent-tabs-mode:nil
870   fill-column:99
871   End:
872 */
873 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :