Code

Merge and cleanup of GSoC C++-ification project.
[inkscape.git] / src / xml / repr-io.cpp
1 /*
2  * Dirty DOM-like  tree
3  *
4  * Authors:
5  *   Lauris Kaplinski <lauris@kaplinski.com>
6  *   bulia byak <buliabyak@users.sf.net>
7  *
8  * Copyright (C) 1999-2002 Lauris Kaplinski
9  *
10  * Released under GNU GPL, read the file 'COPYING' for more information
11  */
13 #ifdef HAVE_CONFIG_H
14 # include <config.h>
15 #endif
17 #include <cstring>
18 #include <string>
19 #include <stdexcept>
21 #include <libxml/parser.h>
23 #include "xml/repr.h"
24 #include "xml/attribute-record.h"
25 #include "xml/rebase-hrefs.h"
26 #include "xml/simple-document.h"
28 #include "io/sys.h"
29 #include "io/uristream.h"
30 #include "io/stringstream.h"
31 #include "io/gzipstream.h"
33 #include "extension/extension.h"
35 #include "preferences.h"
37 using Inkscape::IO::Writer;
38 using Inkscape::Util::List;
39 using Inkscape::Util::cons;
40 using Inkscape::XML::Document;
41 using Inkscape::XML::SimpleDocument;
42 using Inkscape::XML::Node;
43 using Inkscape::XML::AttributeRecord;
44 using Inkscape::XML::calc_abs_doc_base;
45 using Inkscape::XML::rebase_href_attrs;
47 Document *sp_repr_do_read (xmlDocPtr doc, const gchar *default_ns);
48 static Node *sp_repr_svg_read_node (Document *xml_doc, xmlNodePtr node, const gchar *default_ns, GHashTable *prefix_map);
49 static gint sp_repr_qualified_name (gchar *p, gint len, xmlNsPtr ns, const xmlChar *name, const gchar *default_ns, GHashTable *prefix_map);
50 static void sp_repr_write_stream_root_element(Node *repr, Writer &out,
51                                               bool add_whitespace, gchar const *default_ns,
52                                               int inlineattrs, int indent,
53                                               gchar const *old_href_abs_base,
54                                               gchar const *new_href_abs_base);
55 static void sp_repr_write_stream_element(Node *repr, Writer &out,
56                                          gint indent_level, bool add_whitespace,
57                                          Glib::QueryQuark elide_prefix,
58                                          List<AttributeRecord const> attributes,
59                                          int inlineattrs, int indent,
60                                          gchar const *old_href_abs_base,
61                                          gchar const *new_href_abs_base);
63 #ifdef HAVE_LIBWMF
64 static xmlDocPtr sp_wmf_convert (const char * file_name);
65 static char * sp_wmf_image_name (void * context);
66 #endif /* HAVE_LIBWMF */
69 class XmlSource
70 {
71 public:
72     XmlSource()
73         : filename(0),
74           encoding(0),
75           fp(0),
76           firstFewLen(0),
77           dummy("x"),
78           instr(0),
79           gzin(0)
80     {
81     }
82     virtual ~XmlSource()
83     {
84         close();
85         if ( encoding ) {
86             g_free(encoding);
87             encoding = 0;
88         }
89     }
91     int setFile( char const * filename );
93     static int readCb( void * context, char * buffer, int len );
94     static int closeCb( void * context );
96     char const* getEncoding() const { return encoding; }
97     int read( char * buffer, int len );
98     int close();
99 private:
100     const char* filename;
101     char* encoding;
102     FILE* fp;
103     unsigned char firstFew[4];
104     int firstFewLen;
105     Inkscape::URI dummy;
106     Inkscape::IO::UriInputStream* instr;
107     Inkscape::IO::GzipInputStream* gzin;
108 };
110 int XmlSource::setFile(char const *filename)
112     int retVal = -1;
114     this->filename = filename;
116     fp = Inkscape::IO::fopen_utf8name(filename, "r");
117     if ( fp ) {
118         // First peek in the file to see what it is
119         memset( firstFew, 0, sizeof(firstFew) );
121         size_t some = fread( firstFew, 1, 4, fp );
122         if ( fp ) {
123             // first check for compression
124             if ( (some >= 2) && (firstFew[0] == 0x1f) && (firstFew[1] == 0x8b) ) {
125                 //g_message(" the file being read is gzip'd. extract it");
126                 fclose(fp);
127                 fp = 0;
128                 fp = Inkscape::IO::fopen_utf8name(filename, "r");
129                 instr = new Inkscape::IO::UriInputStream(fp, dummy);
130                 gzin = new Inkscape::IO::GzipInputStream(*instr);
132                 memset( firstFew, 0, sizeof(firstFew) );
133                 some = 0;
134                 int single = 0;
135                 while ( some < 4 && single >= 0 )
136                 {
137                     single = gzin->get();
138                     if ( single >= 0 ) {
139                         firstFew[some++] = 0x0ff & single;
140                     } else {
141                         break;
142                     }
143                 }
144             }
146             int encSkip = 0;
147             if ( (some >= 2) &&(firstFew[0] == 0xfe) && (firstFew[1] == 0xff) ) {
148                 encoding = g_strdup("UTF-16BE");
149                 encSkip = 2;
150             } else if ( (some >= 2) && (firstFew[0] == 0xff) && (firstFew[1] == 0xfe) ) {
151                 encoding = g_strdup("UTF-16LE");
152                 encSkip = 2;
153             } else if ( (some >= 3) && (firstFew[0] == 0xef) && (firstFew[1] == 0xbb) && (firstFew[2] == 0xbf) ) {
154                 encoding = g_strdup("UTF-8");
155                 encSkip = 3;
156             }
158             if ( encSkip ) {
159                 memmove( firstFew, firstFew + encSkip, (some - encSkip) );
160                 some -= encSkip;
161             }
163             firstFewLen = some;
164             retVal = 0; // no error
165         }
166     }
168     return retVal;
172 int XmlSource::readCb( void * context, char * buffer, int len )
174     int retVal = -1;
175     if ( context ) {
176         XmlSource* self = static_cast<XmlSource*>(context);
177         retVal = self->read( buffer, len );
178     }
179     return retVal;
182 int XmlSource::closeCb(void * context)
184     if ( context ) {
185         XmlSource* self = static_cast<XmlSource*>(context);
186         self->close();
187     }
188     return 0;
191 int XmlSource::read( char *buffer, int len )
193     int retVal = 0;
194     size_t got = 0;
196     if ( firstFewLen > 0 ) {
197         int some = (len < firstFewLen) ? len : firstFewLen;
198         memcpy( buffer, firstFew, some );
199         if ( len < firstFewLen ) {
200             memmove( firstFew, firstFew + some, (firstFewLen - some) );
201         }
202         firstFewLen -= some;
203         got = some;
204     } else if ( gzin ) {
205         int single = 0;
206         while ( (static_cast<int>(got) < len) && (single >= 0) )
207         {
208             single = gzin->get();
209             if ( single >= 0 ) {
210                 buffer[got++] = 0x0ff & single;
211             } else {
212                 break;
213             }
214         }
215     } else {
216         got = fread( buffer, 1, len, fp );
217     }
219     if ( feof(fp) ) {
220         retVal = got;
221     } else if ( ferror(fp) ) {
222         retVal = -1;
223     } else {
224         retVal = got;
225     }
227     return retVal;
230 int XmlSource::close()
232     if ( gzin ) {
233         gzin->close();
234         delete gzin;
235         gzin = 0;
236     }
237     if ( instr ) {
238         instr->close();
239         fp = 0;
240         delete instr;
241         instr = 0;
242     }
243     if ( fp ) {
244         fclose(fp);
245         fp = 0;
246     }
247     return 0;
250 /**
251  * Reads XML from a file, including WMF files, and returns the Document.
252  * The default namespace can also be specified, if desired.
253  */
254 Document *
255 sp_repr_read_file (const gchar * filename, const gchar *default_ns)
257     xmlDocPtr doc = 0;
258     Document * rdoc = 0;
260     xmlSubstituteEntitiesDefault(1);
262     g_return_val_if_fail (filename != NULL, NULL);
263     g_return_val_if_fail (Inkscape::IO::file_test( filename, G_FILE_TEST_EXISTS ), NULL);
264     /* fixme: A file can disappear at any time, including between now and when we actually try to
265      * open it.  Get rid of the above test once we're sure that we correctly handle
266      * non-existence. */
268     // TODO: bulia, please look over
269     gsize bytesRead = 0;
270     gsize bytesWritten = 0;
271     GError* error = NULL;
272     // TODO: need to replace with our own fopen and reading
273     gchar* localFilename = g_filename_from_utf8 ( filename,
274                                  -1,  &bytesRead,  &bytesWritten, &error);
275     g_return_val_if_fail( localFilename != NULL, NULL );
277     Inkscape::IO::dump_fopen_call( filename, "N" );
279 #ifdef HAVE_LIBWMF
280     if (strlen (localFilename) > 4) {
281         if ( (strcmp (localFilename + strlen (localFilename) - 4,".wmf") == 0)
282              || (strcmp (localFilename + strlen (localFilename) - 4,".WMF") == 0)) {
283             doc = sp_wmf_convert (localFilename);
284         }
285     }
286 #endif // !HAVE_LIBWMF
288     if ( !doc ) {
289         XmlSource src;
291         if ( (src.setFile(filename) == 0) ) {
292             doc = xmlReadIO( XmlSource::readCb,
293                              XmlSource::closeCb,
294                              &src,
295                              localFilename,
296                              src.getEncoding(),
297                              XML_PARSE_NOENT | XML_PARSE_HUGE);
298         }
299     }
301     rdoc = sp_repr_do_read( doc, default_ns );
302     if ( doc ) {
303         xmlFreeDoc( doc );
304     }
306     if ( localFilename ) {
307         g_free( localFilename );
308     }
310     return rdoc;
313 /**
314  * Reads and parses XML from a buffer, returning it as an Document
315  */
316 Document *
317 sp_repr_read_mem (const gchar * buffer, gint length, const gchar *default_ns)
319     xmlDocPtr doc;
320     Document * rdoc;
322     xmlSubstituteEntitiesDefault(1);
324     g_return_val_if_fail (buffer != NULL, NULL);
326     doc = xmlParseMemory (const_cast<gchar *>(buffer), length);
328     rdoc = sp_repr_do_read (doc, default_ns);
329     if (doc) {
330         xmlFreeDoc (doc);
331     }
332     return rdoc;
335 /**
336  * Reads and parses XML from a buffer, returning it as an Document
337  */
338 Document *
339 sp_repr_read_buf (const Glib::ustring &buf, const gchar *default_ns)
341     return sp_repr_read_mem(buf.c_str(), buf.size(), default_ns);
345 namespace Inkscape {
347 struct compare_quark_ids {
348     bool operator()(Glib::QueryQuark const &a, Glib::QueryQuark const &b) const {
349         return a.id() < b.id();
350     }
351 };
355 namespace {
357 typedef std::map<Glib::QueryQuark, Glib::QueryQuark, Inkscape::compare_quark_ids> PrefixMap;
359 Glib::QueryQuark qname_prefix(Glib::QueryQuark qname) {
360     static PrefixMap prefix_map;
361     PrefixMap::iterator iter = prefix_map.find(qname);
362     if ( iter != prefix_map.end() ) {
363         return (*iter).second;
364     } else {
365         gchar const *name_string=g_quark_to_string(qname);
366         gchar const *prefix_end=strchr(name_string, ':');
367         if (prefix_end) {
368             Glib::Quark prefix=Glib::ustring(name_string, prefix_end);
369             prefix_map.insert(PrefixMap::value_type(qname, prefix));
370             return prefix;
371         } else {
372             return GQuark(0);
373         }
374     }
379 namespace {
381 void promote_to_namespace(Node *repr, const gchar *prefix) {
382     if ( repr->type() == Inkscape::XML::ELEMENT_NODE ) {
383         GQuark code = repr->code();
384         if (!qname_prefix(code).id()) {
385             gchar *svg_name = g_strconcat(prefix, ":", g_quark_to_string(code), NULL);
386             repr->setCodeUnsafe(g_quark_from_string(svg_name));
387             g_free(svg_name);
388         }
389         for ( Node *child = sp_repr_children(repr) ; child ; child = sp_repr_next(child) ) {
390             promote_to_namespace(child, prefix);
391         }
392     }
397 /**
398  * Reads in a XML file to create a Document
399  */
400 Document *
401 sp_repr_do_read (xmlDocPtr doc, const gchar *default_ns)
403     if (doc == NULL) {
404         return NULL;
405     }
406     xmlNodePtr node=xmlDocGetRootElement (doc);
407     if (node == NULL) {
408         return NULL;
409     }
411     GHashTable * prefix_map;
412     prefix_map = g_hash_table_new (g_str_hash, g_str_equal);
414     Document *rdoc = new Inkscape::XML::SimpleDocument();
416     Node *root=NULL;
417     for ( node = doc->children ; node != NULL ; node = node->next ) {
418         if (node->type == XML_ELEMENT_NODE) {
419             Node *repr=sp_repr_svg_read_node(rdoc, node, default_ns, prefix_map);
420             rdoc->appendChild(repr);
421             Inkscape::GC::release(repr);
423             if (!root) {
424                 root = repr;
425             } else {
426                 root = NULL;
427                 break;
428             }
429         } else if ( node->type == XML_COMMENT_NODE || node->type == XML_PI_NODE ) {
430             Node *repr=sp_repr_svg_read_node(rdoc, node, default_ns, prefix_map);
431             rdoc->appendChild(repr);
432             Inkscape::GC::release(repr);
433         }
434     }
436     if (root != NULL) {
437         /* promote elements of some XML documents that don't use namespaces
438          * into their default namespace */
439         if ( default_ns && !strchr(root->name(), ':') ) {
440             if ( !strcmp(default_ns, SP_SVG_NS_URI) ) {
441                 promote_to_namespace(root, "svg");
442             }
443             if ( !strcmp(default_ns, INKSCAPE_EXTENSION_URI) ) {
444                 promote_to_namespace(root, INKSCAPE_EXTENSION_NS_NC);
445             }
446         }
447     }
449     g_hash_table_destroy (prefix_map);
451     return rdoc;
454 gint
455 sp_repr_qualified_name (gchar *p, gint len, xmlNsPtr ns, const xmlChar *name, const gchar */*default_ns*/, GHashTable *prefix_map)
457     const xmlChar *prefix;
458     if ( ns && ns->href ) {
459         prefix = reinterpret_cast<const xmlChar*>( sp_xml_ns_uri_prefix(reinterpret_cast<const gchar*>(ns->href),
460                                                                         reinterpret_cast<const char*>(ns->prefix)) );
461         void* p0 = reinterpret_cast<gpointer>(const_cast<xmlChar *>(prefix));
462         void* p1 = reinterpret_cast<gpointer>(const_cast<xmlChar *>(ns->href));
463         g_hash_table_insert( prefix_map, p0, p1 );
464     } else {
465         prefix = NULL;
466     }
468     if (prefix) {
469         return g_snprintf (p, len, "%s:%s", reinterpret_cast<const gchar*>(prefix), name);
470     } else {
471         return g_snprintf (p, len, "%s", name);
472     }
475 static Node *
476 sp_repr_svg_read_node (Document *xml_doc, xmlNodePtr node, const gchar *default_ns, GHashTable *prefix_map)
478     Node *repr, *crepr;
479     xmlAttrPtr prop;
480     xmlNodePtr child;
481     gchar c[256];
483     if (node->type == XML_TEXT_NODE || node->type == XML_CDATA_SECTION_NODE) {
485         if (node->content == NULL || *(node->content) == '\0') {
486             return NULL; // empty text node
487         }
489         bool preserve = (xmlNodeGetSpacePreserve (node) == 1);
491         xmlChar *p;
492         for (p = node->content; *p && g_ascii_isspace (*p) && !preserve; p++)
493             ; // skip all whitespace
495         if (!(*p)) { // this is an all-whitespace node, and preserve == default
496             return NULL; // we do not preserve all-whitespace nodes unless we are asked to
497         }
499         return xml_doc->createTextNode(reinterpret_cast<gchar *>(node->content));
500     }
502     if (node->type == XML_COMMENT_NODE) {
503         return xml_doc->createComment(reinterpret_cast<gchar *>(node->content));
504     }
506     if (node->type == XML_PI_NODE) {
507         return xml_doc->createPI(reinterpret_cast<const gchar *>(node->name),
508                                  reinterpret_cast<const gchar *>(node->content));
509     }
511     if (node->type == XML_ENTITY_DECL) {
512         return NULL;
513     }
515     sp_repr_qualified_name (c, 256, node->ns, node->name, default_ns, prefix_map);
516     repr = xml_doc->createElement(c);
517     /* TODO remember node->ns->prefix if node->ns != NULL */
519     for (prop = node->properties; prop != NULL; prop = prop->next) {
520         if (prop->children) {
521             sp_repr_qualified_name (c, 256, prop->ns, prop->name, default_ns, prefix_map);
522             repr->setAttribute(c, reinterpret_cast<gchar*>(prop->children->content));
523             /* TODO remember prop->ns->prefix if prop->ns != NULL */
524         }
525     }
527     if (node->content) {
528         repr->setContent(reinterpret_cast<gchar*>(node->content));
529     }
531     child = node->xmlChildrenNode;
532     for (child = node->xmlChildrenNode; child != NULL; child = child->next) {
533         crepr = sp_repr_svg_read_node (xml_doc, child, default_ns, prefix_map);
534         if (crepr) {
535             repr->appendChild(crepr);
536             Inkscape::GC::release(crepr);
537         }
538     }
540     return repr;
544 static void
545 sp_repr_save_writer(Document *doc, Inkscape::IO::Writer *out,
546                     gchar const *default_ns,
547                     gchar const *old_href_abs_base,
548                     gchar const *new_href_abs_base)
550     Inkscape::Preferences *prefs = Inkscape::Preferences::get();
551     bool inlineattrs = prefs->getBool("/options/svgoutput/inlineattrs");
552     int indent = prefs->getInt("/options/svgoutput/indent", 2);
554     /* fixme: do this The Right Way */
555     out->writeString( "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?>\n" );
557     const gchar *str = static_cast<Node *>(doc)->attribute("doctype");
558     if (str) {
559         out->writeString( str );
560     }
562     for (Node *repr = sp_repr_document_first_child(doc);
563          repr; repr = sp_repr_next(repr))
564     {
565         Inkscape::XML::NodeType const node_type = repr->type();
566         if ( node_type == Inkscape::XML::ELEMENT_NODE ) {
567             sp_repr_write_stream_root_element(repr, *out, TRUE, default_ns, inlineattrs, indent,
568                                               old_href_abs_base, new_href_abs_base);
569         } else {
570             sp_repr_write_stream(repr, *out, 0, TRUE, GQuark(0), inlineattrs, indent,
571                                  old_href_abs_base, new_href_abs_base);
572             if ( node_type == Inkscape::XML::COMMENT_NODE ) {
573                 out->writeChar('\n');
574             }
575         }
576     }
582 Glib::ustring
583 sp_repr_save_buf(Document *doc)
584 {   
585     Inkscape::IO::StringOutputStream souts;
586     Inkscape::IO::OutputStreamWriter outs(souts);
588     sp_repr_save_writer(doc, &outs, SP_INKSCAPE_NS_URI, 0, 0);
590         outs.close();
591         Glib::ustring buf = souts.getString();
593         return buf;
600 void
601 sp_repr_save_stream(Document *doc, FILE *fp, gchar const *default_ns, bool compress,
602                     gchar const *const old_href_abs_base,
603                     gchar const *const new_href_abs_base)
605     Inkscape::URI dummy("x");
606     Inkscape::IO::UriOutputStream bout(fp, dummy);
607     Inkscape::IO::GzipOutputStream *gout = compress ? new Inkscape::IO::GzipOutputStream(bout) : NULL;
608     Inkscape::IO::OutputStreamWriter *out  = compress ? new Inkscape::IO::OutputStreamWriter( *gout ) : new Inkscape::IO::OutputStreamWriter( bout );
610     sp_repr_save_writer(doc, out, default_ns, old_href_abs_base, new_href_abs_base);
612     delete out;
613     delete gout;
618 /**
619  * Returns true iff file successfully saved.
620  *
621  * \param filename The actual file to do I/O to, which might be a temp file.
622  *
623  * \param for_filename The base URI [actually filename] to assume for purposes of rewriting
624  *              xlink:href attributes.
625  */
626 bool
627 sp_repr_save_rebased_file(Document *doc, gchar const *const filename, gchar const *default_ns,
628                           gchar const *old_base, gchar const *for_filename)
630     if (!filename) {
631         return false;
632     }
634     bool compress;
635     {
636         size_t const filename_len = strlen(filename);
637         compress = ( filename_len > 5
638                      && strcasecmp(".svgz", filename + filename_len - 5) == 0 );
639     }
641     Inkscape::IO::dump_fopen_call( filename, "B" );
642     FILE *file = Inkscape::IO::fopen_utf8name(filename, "w");
643     if (file == NULL) {
644         return false;
645     }
647     gchar *old_href_abs_base = NULL;
648     gchar *new_href_abs_base = NULL;
649     if (for_filename) {
650         old_href_abs_base = calc_abs_doc_base(old_base);
651         if (g_path_is_absolute(for_filename)) {
652             new_href_abs_base = g_path_get_dirname(for_filename);
653         } else {
654             gchar *const cwd = g_get_current_dir();
655             gchar *const for_abs_filename = g_build_filename(cwd, for_filename, NULL);
656             g_free(cwd);
657             new_href_abs_base = g_path_get_dirname(for_abs_filename);
658             g_free(for_abs_filename);
659         }
661         /* effic: Once we're confident that we never need (or never want) to resort
662          * to using sodipodi:absref instead of the xlink:href value,
663          * then we should do `if streq() { free them and set both to NULL; }'. */
664     }
665     sp_repr_save_stream(doc, file, default_ns, compress, old_href_abs_base, new_href_abs_base);
667     g_free(old_href_abs_base);
668     g_free(new_href_abs_base);
670     if (fclose (file) != 0) {
671         return false;
672     }
674     return true;
677 /**
678  * Returns true iff file successfully saved.
679  */
680 bool
681 sp_repr_save_file(Document *doc, gchar const *const filename, gchar const *default_ns)
683     return sp_repr_save_rebased_file(doc, filename, default_ns, NULL, NULL);
687 /* (No doubt this function already exists elsewhere.) */
688 static void
689 repr_quote_write (Writer &out, const gchar * val)
691     if (val) {
692         for (; *val != '\0'; val++) {
693             switch (*val) {
694                 case '"': out.writeString( "&quot;" ); break;
695                 case '&': out.writeString( "&amp;" ); break;
696                 case '<': out.writeString( "&lt;" ); break;
697                 case '>': out.writeString( "&gt;" ); break;
698                 default: out.writeChar( *val ); break;
699             }
700         }
701     }
704 static void repr_write_comment( Writer &out, const gchar * val, bool addWhitespace, gint indentLevel, int indent )
706     if ( indentLevel > 16 ) {
707         indentLevel = 16;
708     }
709     if (addWhitespace && indent) {
710         for (gint i = 0; i < indentLevel; i++) {
711             for (gint j = 0; j < indent; j++) {
712                 out.writeString(" ");
713             }
714         }
715     }
717     out.writeString("<!--");
718     // WARNING out.printf() and out.writeString() are *NOT* non-ASCII friendly.
719     if (val) {
720         for (const gchar* cur = val; *cur; cur++ ) {
721             out.writeChar(*cur);
722         }
723     } else {
724         out.writeString(" ");
725     }
726     out.writeString("-->");
728     if (addWhitespace) {
729         out.writeString("\n");
730     }
733 namespace {
735 typedef std::map<Glib::QueryQuark, gchar const *, Inkscape::compare_quark_ids> LocalNameMap;
736 typedef std::map<Glib::QueryQuark, Inkscape::Util::ptr_shared<char>, Inkscape::compare_quark_ids> NSMap;
738 gchar const *qname_local_name(Glib::QueryQuark qname) {
739     static LocalNameMap local_name_map;
740     LocalNameMap::iterator iter = local_name_map.find(qname);
741     if ( iter != local_name_map.end() ) {
742         return (*iter).second;
743     } else {
744         gchar const *name_string=g_quark_to_string(qname);
745         gchar const *prefix_end=strchr(name_string, ':');
746         if (prefix_end) {
747             return prefix_end + 1;
748         } else {
749             return name_string;
750         }
751     }
754 void add_ns_map_entry(NSMap &ns_map, Glib::QueryQuark prefix) {
755     using Inkscape::Util::ptr_shared;
756     using Inkscape::Util::share_unsafe;
758     static const Glib::QueryQuark xml_prefix("xml");
760     NSMap::iterator iter=ns_map.find(prefix);
761     if ( iter == ns_map.end() ) {
762         if (prefix.id()) {
763             gchar const *uri=sp_xml_ns_prefix_uri(g_quark_to_string(prefix));
764             if (uri) {
765                 ns_map.insert(NSMap::value_type(prefix, share_unsafe(uri)));
766             } else if ( prefix != xml_prefix ) {
767                 g_warning("No namespace known for normalized prefix %s", g_quark_to_string(prefix));
768             }
769         } else {
770             ns_map.insert(NSMap::value_type(prefix, ptr_shared<char>()));
771         }
772     }
775 void populate_ns_map(NSMap &ns_map, Node &repr) {
776     if ( repr.type() == Inkscape::XML::ELEMENT_NODE ) {
777         add_ns_map_entry(ns_map, qname_prefix(repr.code()));
778         for ( List<AttributeRecord const> iter=repr.attributeList() ;
779               iter ; ++iter )
780         {
781             Glib::QueryQuark prefix=qname_prefix(iter->key);
782             if (prefix.id()) {
783                 add_ns_map_entry(ns_map, prefix);
784             }
785         }
786         for ( Node *child=sp_repr_children(&repr) ;
787               child ; child = sp_repr_next(child) )
788         {
789             populate_ns_map(ns_map, *child);
790         }
791     }
796 static void
797 sp_repr_write_stream_root_element(Node *repr, Writer &out,
798                                   bool add_whitespace, gchar const *default_ns,
799                                   int inlineattrs, int indent,
800                                   gchar const *const old_href_base,
801                                   gchar const *const new_href_base)
803     using Inkscape::Util::ptr_shared;
805     g_assert(repr != NULL);
806     Glib::QueryQuark xml_prefix=g_quark_from_static_string("xml");
808     NSMap ns_map;
809     populate_ns_map(ns_map, *repr);
811     Glib::QueryQuark elide_prefix=GQuark(0);
812     if ( default_ns && ns_map.find(GQuark(0)) == ns_map.end() ) {
813         elide_prefix = g_quark_from_string(sp_xml_ns_uri_prefix(default_ns, NULL));
814     }
816     List<AttributeRecord const> attributes=repr->attributeList();
817     for ( NSMap::iterator iter=ns_map.begin() ; iter != ns_map.end() ; ++iter ) 
818     {
819         Glib::QueryQuark prefix=(*iter).first;
820         ptr_shared<char> ns_uri=(*iter).second;
822         if (prefix.id()) {
823             if ( prefix != xml_prefix ) {
824                 if ( elide_prefix == prefix ) {
825                     attributes = cons(AttributeRecord(g_quark_from_static_string("xmlns"), ns_uri), attributes);
826                 }
828                 Glib::ustring attr_name="xmlns:";
829                 attr_name.append(g_quark_to_string(prefix));
830                 GQuark key = g_quark_from_string(attr_name.c_str());
831                 attributes = cons(AttributeRecord(key, ns_uri), attributes);
832             }
833         } else {
834             // if there are non-namespaced elements, we can't globally
835             // use a default namespace
836             elide_prefix = GQuark(0);
837         }
838     }
840     return sp_repr_write_stream_element(repr, out, 0, add_whitespace, elide_prefix, attributes,
841                                         inlineattrs, indent, old_href_base, new_href_base);
844 void sp_repr_write_stream( Node *repr, Writer &out, gint indent_level,
845                            bool add_whitespace, Glib::QueryQuark elide_prefix,
846                            int inlineattrs, int indent,
847                            gchar const *const old_href_base,
848                            gchar const *const new_href_base)
850     switch (repr->type()) {
851         case Inkscape::XML::TEXT_NODE: {
852             repr_quote_write( out, repr->content() );
853             break;
854         }
855         case Inkscape::XML::COMMENT_NODE: {
856             repr_write_comment( out, repr->content(), add_whitespace, indent_level, indent );
857             break;
858         }
859         case Inkscape::XML::PI_NODE: {
860             out.printf( "<?%s %s?>", repr->name(), repr->content() );
861             break;
862         }
863         case Inkscape::XML::ELEMENT_NODE: {
864             sp_repr_write_stream_element( repr, out, indent_level,
865                                           add_whitespace, elide_prefix,
866                                           repr->attributeList(),
867                                           inlineattrs, indent,
868                                           old_href_base, new_href_base);
869             break;
870         }
871         case Inkscape::XML::DOCUMENT_NODE: {
872             g_assert_not_reached();
873             break;
874         }
875         default: {
876             g_assert_not_reached();
877         }
878     }
882 static void
883 sp_repr_write_stream_element (Node * repr, Writer & out, gint indent_level,
884                               bool add_whitespace,
885                               Glib::QueryQuark elide_prefix,
886                               List<AttributeRecord const> attributes, 
887                               int inlineattrs, int indent,
888                               gchar const *const old_href_base,
889                               gchar const *const new_href_base)
891     Node *child;
892     bool loose;
894     g_return_if_fail (repr != NULL);
896     if ( indent_level > 16 ) {
897         indent_level = 16;
898     }
900     if (add_whitespace && indent) {
901         for (gint i = 0; i < indent_level; i++) {
902             for (gint j = 0; j < indent; j++) {
903                 out.writeString(" ");
904             }
905         }
906     }
908     GQuark code = repr->code();
909     gchar const *element_name;
910     if ( elide_prefix == qname_prefix(code) ) {
911         element_name = qname_local_name(code);
912     } else {
913         element_name = g_quark_to_string(code);
914     }
915     out.printf( "<%s", element_name );
917     // if this is a <text> element, suppress formatting whitespace
918     // for its content and children:
919     gchar const *xml_space_attr = repr->attribute("xml:space");
920     if (xml_space_attr != NULL && !strcmp(xml_space_attr, "preserve")) {
921         add_whitespace = false;
922     }
924     for ( List<AttributeRecord const> iter = rebase_href_attrs(old_href_base, new_href_base,
925                                                                attributes);
926           iter ; ++iter )
927     {
928         if (!inlineattrs) {
929             out.writeString("\n");
930             if (indent) {
931                 for ( gint i = 0 ; i < indent_level + 1 ; i++ ) {
932                     for ( gint j = 0 ; j < indent ; j++ ) {
933                         out.writeString(" ");
934                     }
935                 }
936             }
937         }
938         out.printf(" %s=\"", g_quark_to_string(iter->key));
939         repr_quote_write(out, iter->value);
940         out.writeChar('"');
941     }
943     loose = TRUE;
944     for (child = repr->firstChild() ; child != NULL; child = child->next()) {
945         if (child->type() == Inkscape::XML::TEXT_NODE) {
946             loose = FALSE;
947             break;
948         }
949     }
950     if (repr->firstChild()) {
951         out.writeString( ">" );
952         if (loose && add_whitespace) {
953             out.writeString( "\n" );
954         }
955         for (child = repr->firstChild(); child != NULL; child = child->next()) {
956             sp_repr_write_stream(child, out, ( loose ? indent_level + 1 : 0 ),
957                                  add_whitespace, elide_prefix, inlineattrs, indent,
958                                  old_href_base, new_href_base);
959         }
961         if (loose && add_whitespace && indent) {
962             for (gint i = 0; i < indent_level; i++) {
963                 for ( gint j = 0 ; j < indent ; j++ ) {
964                     out.writeString(" ");
965                 }
966             }
967         }
968         out.printf( "</%s>", element_name );
969     } else {
970         out.writeString( " />" );
971     }
973     // text elements cannot nest, so we can output newline
974     // after closing text
976     if (add_whitespace || !strcmp (repr->name(), "svg:text")) {
977         out.writeString( "\n" );
978     }
982 /*
983   Local Variables:
984   mode:c++
985   c-file-style:"stroustrup"
986   c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
987   indent-tabs-mode:nil
988   fill-column:99
989   End:
990 */
991 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 :