Code

Pot and Dutch translation 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 <libxml/parser.h>
25 #include "xml/repr.h"
26 #include "xml/attribute-record.h"
27 #include "xml/rebase-hrefs.h"
28 #include "xml/simple-document.h"
30 #include "io/sys.h"
31 #include "io/uristream.h"
32 #include "io/stringstream.h"
33 #include "io/gzipstream.h"
35 #include "extension/extension.h"
37 #include "preferences.h"
39 using Inkscape::IO::Writer;
40 using Inkscape::Util::List;
41 using Inkscape::Util::cons;
42 using Inkscape::XML::Document;
43 using Inkscape::XML::SimpleDocument;
44 using Inkscape::XML::Node;
45 using Inkscape::XML::AttributeRecord;
46 using Inkscape::XML::calc_abs_doc_base;
47 using Inkscape::XML::rebase_href_attrs;
49 Document *sp_repr_do_read (xmlDocPtr doc, const gchar *default_ns);
50 static Node *sp_repr_svg_read_node (Document *xml_doc, xmlNodePtr node, const gchar *default_ns, GHashTable *prefix_map);
51 static gint sp_repr_qualified_name (gchar *p, gint len, xmlNsPtr ns, const xmlChar *name, const gchar *default_ns, GHashTable *prefix_map);
52 static void sp_repr_write_stream_root_element(Node *repr, Writer &out,
53                                               bool add_whitespace, gchar const *default_ns,
54                                               int inlineattrs, int indent,
55                                               gchar const *old_href_abs_base,
56                                               gchar const *new_href_abs_base);
57 static void sp_repr_write_stream_element(Node *repr, Writer &out,
58                                          gint indent_level, bool add_whitespace,
59                                          Glib::QueryQuark elide_prefix,
60                                          List<AttributeRecord const> attributes,
61                                          int inlineattrs, int indent,
62                                          gchar const *old_href_abs_base,
63                                          gchar const *new_href_abs_base);
65 #ifdef HAVE_LIBWMF
66 static xmlDocPtr sp_wmf_convert (const char * file_name);
67 static char * sp_wmf_image_name (void * context);
68 #endif /* HAVE_LIBWMF */
71 class XmlSource
72 {
73 public:
74     XmlSource()
75         : filename(0),
76           encoding(0),
77           fp(0),
78           firstFewLen(0),
79           dummy("x"),
80           instr(0),
81           gzin(0)
82     {
83     }
84     virtual ~XmlSource()
85     {
86         close();
87         if ( encoding ) {
88             g_free(encoding);
89             encoding = 0;
90         }
91     }
93     int setFile( char const * filename );
95     static int readCb( void * context, char * buffer, int len );
96     static int closeCb( void * context );
98     char const* getEncoding() const { return encoding; }
99     int read( char * buffer, int len );
100     int close();
101 private:
102     const char* filename;
103     char* encoding;
104     FILE* fp;
105     unsigned char firstFew[4];
106     int firstFewLen;
107     Inkscape::URI dummy;
108     Inkscape::IO::UriInputStream* instr;
109     Inkscape::IO::GzipInputStream* gzin;
110 };
112 int XmlSource::setFile(char const *filename)
114     int retVal = -1;
116     this->filename = filename;
118     fp = Inkscape::IO::fopen_utf8name(filename, "r");
119     if ( fp ) {
120         // First peek in the file to see what it is
121         memset( firstFew, 0, sizeof(firstFew) );
123         size_t some = fread( firstFew, 1, 4, fp );
124         if ( fp ) {
125             // first check for compression
126             if ( (some >= 2) && (firstFew[0] == 0x1f) && (firstFew[1] == 0x8b) ) {
127                 //g_message(" the file being read is gzip'd. extract it");
128                 fclose(fp);
129                 fp = 0;
130                 fp = Inkscape::IO::fopen_utf8name(filename, "r");
131                 instr = new Inkscape::IO::UriInputStream(fp, dummy);
132                 gzin = new Inkscape::IO::GzipInputStream(*instr);
134                 memset( firstFew, 0, sizeof(firstFew) );
135                 some = 0;
136                 int single = 0;
137                 while ( some < 4 && single >= 0 )
138                 {
139                     single = gzin->get();
140                     if ( single >= 0 ) {
141                         firstFew[some++] = 0x0ff & single;
142                     } else {
143                         break;
144                     }
145                 }
146             }
148             int encSkip = 0;
149             if ( (some >= 2) &&(firstFew[0] == 0xfe) && (firstFew[1] == 0xff) ) {
150                 encoding = g_strdup("UTF-16BE");
151                 encSkip = 2;
152             } else if ( (some >= 2) && (firstFew[0] == 0xff) && (firstFew[1] == 0xfe) ) {
153                 encoding = g_strdup("UTF-16LE");
154                 encSkip = 2;
155             } else if ( (some >= 3) && (firstFew[0] == 0xef) && (firstFew[1] == 0xbb) && (firstFew[2] == 0xbf) ) {
156                 encoding = g_strdup("UTF-8");
157                 encSkip = 3;
158             }
160             if ( encSkip ) {
161                 memmove( firstFew, firstFew + encSkip, (some - encSkip) );
162                 some -= encSkip;
163             }
165             firstFewLen = some;
166             retVal = 0; // no error
167         }
168     }
170     return retVal;
174 int XmlSource::readCb( void * context, char * buffer, int len )
176     int retVal = -1;
177     if ( context ) {
178         XmlSource* self = static_cast<XmlSource*>(context);
179         retVal = self->read( buffer, len );
180     }
181     return retVal;
184 int XmlSource::closeCb(void * context)
186     if ( context ) {
187         XmlSource* self = static_cast<XmlSource*>(context);
188         self->close();
189     }
190     return 0;
193 int XmlSource::read( char *buffer, int len )
195     int retVal = 0;
196     size_t got = 0;
198     if ( firstFewLen > 0 ) {
199         int some = (len < firstFewLen) ? len : firstFewLen;
200         memcpy( buffer, firstFew, some );
201         if ( len < firstFewLen ) {
202             memmove( firstFew, firstFew + some, (firstFewLen - some) );
203         }
204         firstFewLen -= some;
205         got = some;
206     } else if ( gzin ) {
207         int single = 0;
208         while ( (static_cast<int>(got) < len) && (single >= 0) )
209         {
210             single = gzin->get();
211             if ( single >= 0 ) {
212                 buffer[got++] = 0x0ff & single;
213             } else {
214                 break;
215             }
216         }
217     } else {
218         got = fread( buffer, 1, len, fp );
219     }
221     if ( feof(fp) ) {
222         retVal = got;
223     } else if ( ferror(fp) ) {
224         retVal = -1;
225     } else {
226         retVal = got;
227     }
229     return retVal;
232 int XmlSource::close()
234     if ( gzin ) {
235         gzin->close();
236         delete gzin;
237         gzin = 0;
238     }
239     if ( instr ) {
240         instr->close();
241         fp = 0;
242         delete instr;
243         instr = 0;
244     }
245     if ( fp ) {
246         fclose(fp);
247         fp = 0;
248     }
249     return 0;
252 /**
253  * Reads XML from a file, including WMF files, and returns the Document.
254  * The default namespace can also be specified, if desired.
255  */
256 Document *
257 sp_repr_read_file (const gchar * filename, const gchar *default_ns)
259     xmlDocPtr doc = 0;
260     Document * rdoc = 0;
262     xmlSubstituteEntitiesDefault(1);
264     g_return_val_if_fail (filename != NULL, NULL);
265     g_return_val_if_fail (Inkscape::IO::file_test( filename, G_FILE_TEST_EXISTS ), NULL);
266     /* fixme: A file can disappear at any time, including between now and when we actually try to
267      * open it.  Get rid of the above test once we're sure that we correctly handle
268      * non-existence. */
270     // TODO: bulia, please look over
271     gsize bytesRead = 0;
272     gsize bytesWritten = 0;
273     GError* error = NULL;
274     // TODO: need to replace with our own fopen and reading
275     gchar* localFilename = g_filename_from_utf8 ( filename,
276                                  -1,  &bytesRead,  &bytesWritten, &error);
277     g_return_val_if_fail( localFilename != NULL, NULL );
279     Inkscape::IO::dump_fopen_call( filename, "N" );
281 #ifdef HAVE_LIBWMF
282     if (strlen (localFilename) > 4) {
283         if ( (strcmp (localFilename + strlen (localFilename) - 4,".wmf") == 0)
284              || (strcmp (localFilename + strlen (localFilename) - 4,".WMF") == 0)) {
285             doc = sp_wmf_convert (localFilename);
286         }
287     }
288 #endif // !HAVE_LIBWMF
290     if ( !doc ) {
291         XmlSource src;
293         if ( (src.setFile(filename) == 0) ) {
294             doc = xmlReadIO( XmlSource::readCb,
295                              XmlSource::closeCb,
296                              &src,
297                              localFilename,
298                              src.getEncoding(),
299                              XML_PARSE_NOENT | XML_PARSE_HUGE);
300         }
301     }
303     rdoc = sp_repr_do_read( doc, default_ns );
304     if ( doc ) {
305         xmlFreeDoc( doc );
306     }
308     if ( localFilename ) {
309         g_free( localFilename );
310     }
312     return rdoc;
315 /**
316  * Reads and parses XML from a buffer, returning it as an Document
317  */
318 Document *
319 sp_repr_read_mem (const gchar * buffer, gint length, const gchar *default_ns)
321     xmlDocPtr doc;
322     Document * rdoc;
324     xmlSubstituteEntitiesDefault(1);
326     g_return_val_if_fail (buffer != NULL, NULL);
328     doc = xmlParseMemory (const_cast<gchar *>(buffer), length);
330     rdoc = sp_repr_do_read (doc, default_ns);
331     if (doc) {
332         xmlFreeDoc (doc);
333     }
334     return rdoc;
337 /**
338  * Reads and parses XML from a buffer, returning it as an Document
339  */
340 Document *
341 sp_repr_read_buf (const Glib::ustring &buf, const gchar *default_ns)
343     return sp_repr_read_mem(buf.c_str(), buf.size(), default_ns);
347 namespace Inkscape {
349 struct compare_quark_ids {
350     bool operator()(Glib::QueryQuark const &a, Glib::QueryQuark const &b) const {
351         return a.id() < b.id();
352     }
353 };
357 namespace {
359 typedef std::map<Glib::QueryQuark, Glib::QueryQuark, Inkscape::compare_quark_ids> PrefixMap;
361 Glib::QueryQuark qname_prefix(Glib::QueryQuark qname) {
362     static PrefixMap prefix_map;
363     PrefixMap::iterator iter = prefix_map.find(qname);
364     if ( iter != prefix_map.end() ) {
365         return (*iter).second;
366     } else {
367         gchar const *name_string=g_quark_to_string(qname);
368         gchar const *prefix_end=strchr(name_string, ':');
369         if (prefix_end) {
370             Glib::Quark prefix=Glib::ustring(name_string, prefix_end);
371             prefix_map.insert(PrefixMap::value_type(qname, prefix));
372             return prefix;
373         } else {
374             return GQuark(0);
375         }
376     }
381 namespace {
383 void promote_to_namespace(Node *repr, const gchar *prefix) {
384     if ( repr->type() == Inkscape::XML::ELEMENT_NODE ) {
385         GQuark code = repr->code();
386         if (!qname_prefix(code).id()) {
387             gchar *svg_name = g_strconcat(prefix, ":", g_quark_to_string(code), NULL);
388             repr->setCodeUnsafe(g_quark_from_string(svg_name));
389             g_free(svg_name);
390         }
391         for ( Node *child = sp_repr_children(repr) ; child ; child = sp_repr_next(child) ) {
392             promote_to_namespace(child, prefix);
393         }
394     }
399 /**
400  * Reads in a XML file to create a Document
401  */
402 Document *
403 sp_repr_do_read (xmlDocPtr doc, const gchar *default_ns)
405     if (doc == NULL) {
406         return NULL;
407     }
408     xmlNodePtr node=xmlDocGetRootElement (doc);
409     if (node == NULL) {
410         return NULL;
411     }
413     GHashTable * prefix_map;
414     prefix_map = g_hash_table_new (g_str_hash, g_str_equal);
416     Document *rdoc = new Inkscape::XML::SimpleDocument();
418     Node *root=NULL;
419     for ( node = doc->children ; node != NULL ; node = node->next ) {
420         if (node->type == XML_ELEMENT_NODE) {
421             Node *repr=sp_repr_svg_read_node(rdoc, node, default_ns, prefix_map);
422             rdoc->appendChild(repr);
423             Inkscape::GC::release(repr);
425             if (!root) {
426                 root = repr;
427             } else {
428                 root = NULL;
429                 break;
430             }
431         } else if ( node->type == XML_COMMENT_NODE || node->type == XML_PI_NODE ) {
432             Node *repr=sp_repr_svg_read_node(rdoc, node, default_ns, prefix_map);
433             rdoc->appendChild(repr);
434             Inkscape::GC::release(repr);
435         }
436     }
438     if (root != NULL) {
439         /* promote elements of some XML documents that don't use namespaces
440          * into their default namespace */
441         if ( default_ns && !strchr(root->name(), ':') ) {
442             if ( !strcmp(default_ns, SP_SVG_NS_URI) ) {
443                 promote_to_namespace(root, "svg");
444             }
445             if ( !strcmp(default_ns, INKSCAPE_EXTENSION_URI) ) {
446                 promote_to_namespace(root, INKSCAPE_EXTENSION_NS_NC);
447             }
448         }
449     }
451     g_hash_table_destroy (prefix_map);
453     return rdoc;
456 gint
457 sp_repr_qualified_name (gchar *p, gint len, xmlNsPtr ns, const xmlChar *name, const gchar */*default_ns*/, GHashTable *prefix_map)
459     const xmlChar *prefix;
460     if ( ns && ns->href ) {
461         prefix = reinterpret_cast<const xmlChar*>( sp_xml_ns_uri_prefix(reinterpret_cast<const gchar*>(ns->href),
462                                                                         reinterpret_cast<const char*>(ns->prefix)) );
463         void* p0 = reinterpret_cast<gpointer>(const_cast<xmlChar *>(prefix));
464         void* p1 = reinterpret_cast<gpointer>(const_cast<xmlChar *>(ns->href));
465         g_hash_table_insert( prefix_map, p0, p1 );
466     } else {
467         prefix = NULL;
468     }
470     if (prefix) {
471         return g_snprintf (p, len, "%s:%s", reinterpret_cast<const gchar*>(prefix), name);
472     } else {
473         return g_snprintf (p, len, "%s", name);
474     }
477 static Node *
478 sp_repr_svg_read_node (Document *xml_doc, xmlNodePtr node, const gchar *default_ns, GHashTable *prefix_map)
480     Node *repr, *crepr;
481     xmlAttrPtr prop;
482     xmlNodePtr child;
483     gchar c[256];
485     if (node->type == XML_TEXT_NODE || node->type == XML_CDATA_SECTION_NODE) {
487         if (node->content == NULL || *(node->content) == '\0') {
488             return NULL; // empty text node
489         }
491         bool preserve = (xmlNodeGetSpacePreserve (node) == 1);
493         xmlChar *p;
494         for (p = node->content; *p && g_ascii_isspace (*p) && !preserve; p++)
495             ; // skip all whitespace
497         if (!(*p)) { // this is an all-whitespace node, and preserve == default
498             return NULL; // we do not preserve all-whitespace nodes unless we are asked to
499         }
501         return xml_doc->createTextNode(reinterpret_cast<gchar *>(node->content));
502     }
504     if (node->type == XML_COMMENT_NODE) {
505         return xml_doc->createComment(reinterpret_cast<gchar *>(node->content));
506     }
508     if (node->type == XML_PI_NODE) {
509         return xml_doc->createPI(reinterpret_cast<const gchar *>(node->name),
510                                  reinterpret_cast<const gchar *>(node->content));
511     }
513     if (node->type == XML_ENTITY_DECL) {
514         return NULL;
515     }
517     sp_repr_qualified_name (c, 256, node->ns, node->name, default_ns, prefix_map);
518     repr = xml_doc->createElement(c);
519     /* TODO remember node->ns->prefix if node->ns != NULL */
521     for (prop = node->properties; prop != NULL; prop = prop->next) {
522         if (prop->children) {
523             sp_repr_qualified_name (c, 256, prop->ns, prop->name, default_ns, prefix_map);
524             repr->setAttribute(c, reinterpret_cast<gchar*>(prop->children->content));
525             /* TODO remember prop->ns->prefix if prop->ns != NULL */
526         }
527     }
529     if (node->content) {
530         repr->setContent(reinterpret_cast<gchar*>(node->content));
531     }
533     child = node->xmlChildrenNode;
534     for (child = node->xmlChildrenNode; child != NULL; child = child->next) {
535         crepr = sp_repr_svg_read_node (xml_doc, child, default_ns, prefix_map);
536         if (crepr) {
537             repr->appendChild(crepr);
538             Inkscape::GC::release(crepr);
539         }
540     }
542     return repr;
546 static void
547 sp_repr_save_writer(Document *doc, Inkscape::IO::Writer *out,
548                     gchar const *default_ns,
549                     gchar const *old_href_abs_base,
550                     gchar const *new_href_abs_base)
552     Inkscape::Preferences *prefs = Inkscape::Preferences::get();
553     bool inlineattrs = prefs->getBool("/options/svgoutput/inlineattrs");
554     int indent = prefs->getInt("/options/svgoutput/indent", 2);
556     /* fixme: do this The Right Way */
557     out->writeString( "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?>\n" );
559     const gchar *str = static_cast<Node *>(doc)->attribute("doctype");
560     if (str) {
561         out->writeString( str );
562     }
564     for (Node *repr = sp_repr_document_first_child(doc);
565          repr; repr = sp_repr_next(repr))
566     {
567         Inkscape::XML::NodeType const node_type = repr->type();
568         if ( node_type == Inkscape::XML::ELEMENT_NODE ) {
569             sp_repr_write_stream_root_element(repr, *out, TRUE, default_ns, inlineattrs, indent,
570                                               old_href_abs_base, new_href_abs_base);
571         } else {
572             sp_repr_write_stream(repr, *out, 0, TRUE, GQuark(0), inlineattrs, indent,
573                                  old_href_abs_base, new_href_abs_base);
574             if ( node_type == Inkscape::XML::COMMENT_NODE ) {
575                 out->writeChar('\n');
576             }
577         }
578     }
584 Glib::ustring
585 sp_repr_save_buf(Document *doc)
586 {   
587     Inkscape::IO::StringOutputStream souts;
588     Inkscape::IO::OutputStreamWriter outs(souts);
590     sp_repr_save_writer(doc, &outs, SP_INKSCAPE_NS_URI, 0, 0);
592         outs.close();
593         Glib::ustring buf = souts.getString();
595         return buf;
602 void
603 sp_repr_save_stream(Document *doc, FILE *fp, gchar const *default_ns, bool compress,
604                     gchar const *const old_href_abs_base,
605                     gchar const *const new_href_abs_base)
607     Inkscape::URI dummy("x");
608     Inkscape::IO::UriOutputStream bout(fp, dummy);
609     Inkscape::IO::GzipOutputStream *gout = compress ? new Inkscape::IO::GzipOutputStream(bout) : NULL;
610     Inkscape::IO::OutputStreamWriter *out  = compress ? new Inkscape::IO::OutputStreamWriter( *gout ) : new Inkscape::IO::OutputStreamWriter( bout );
612     sp_repr_save_writer(doc, out, default_ns, old_href_abs_base, new_href_abs_base);
614     delete out;
615     delete gout;
620 /**
621  * Returns true iff file successfully saved.
622  *
623  * \param filename The actual file to do I/O to, which might be a temp file.
624  *
625  * \param for_filename The base URI [actually filename] to assume for purposes of rewriting
626  *              xlink:href attributes.
627  */
628 bool
629 sp_repr_save_rebased_file(Document *doc, gchar const *const filename, gchar const *default_ns,
630                           gchar const *old_base, gchar const *for_filename)
632     if (!filename) {
633         return false;
634     }
636     bool compress;
637     {
638         size_t const filename_len = strlen(filename);
639         compress = ( filename_len > 5
640                      && strcasecmp(".svgz", filename + filename_len - 5) == 0 );
641     }
643     Inkscape::IO::dump_fopen_call( filename, "B" );
644     FILE *file = Inkscape::IO::fopen_utf8name(filename, "w");
645     if (file == NULL) {
646         return false;
647     }
649     gchar *old_href_abs_base = NULL;
650     gchar *new_href_abs_base = NULL;
651     if (for_filename) {
652         old_href_abs_base = calc_abs_doc_base(old_base);
653         if (g_path_is_absolute(for_filename)) {
654             new_href_abs_base = g_path_get_dirname(for_filename);
655         } else {
656             gchar *const cwd = g_get_current_dir();
657             gchar *const for_abs_filename = g_build_filename(cwd, for_filename, NULL);
658             g_free(cwd);
659             new_href_abs_base = g_path_get_dirname(for_abs_filename);
660             g_free(for_abs_filename);
661         }
663         /* effic: Once we're confident that we never need (or never want) to resort
664          * to using sodipodi:absref instead of the xlink:href value,
665          * then we should do `if streq() { free them and set both to NULL; }'. */
666     }
667     sp_repr_save_stream(doc, file, default_ns, compress, old_href_abs_base, new_href_abs_base);
669     g_free(old_href_abs_base);
670     g_free(new_href_abs_base);
672     if (fclose (file) != 0) {
673         return false;
674     }
676     return true;
679 /**
680  * Returns true iff file successfully saved.
681  */
682 bool
683 sp_repr_save_file(Document *doc, gchar const *const filename, gchar const *default_ns)
685     return sp_repr_save_rebased_file(doc, filename, default_ns, NULL, NULL);
689 /* (No doubt this function already exists elsewhere.) */
690 static void
691 repr_quote_write (Writer &out, const gchar * val)
693     if (val) {
694         for (; *val != '\0'; val++) {
695             switch (*val) {
696                 case '"': out.writeString( "&quot;" ); break;
697                 case '&': out.writeString( "&amp;" ); break;
698                 case '<': out.writeString( "&lt;" ); break;
699                 case '>': out.writeString( "&gt;" ); break;
700                 default: out.writeChar( *val ); break;
701             }
702         }
703     }
706 static void repr_write_comment( Writer &out, const gchar * val, bool addWhitespace, gint indentLevel, int indent )
708     if ( indentLevel > 16 ) {
709         indentLevel = 16;
710     }
711     if (addWhitespace && indent) {
712         for (gint i = 0; i < indentLevel; i++) {
713             for (gint j = 0; j < indent; j++) {
714                 out.writeString(" ");
715             }
716         }
717     }
719     out.writeString("<!--");
720     // WARNING out.printf() and out.writeString() are *NOT* non-ASCII friendly.
721     if (val) {
722         for (const gchar* cur = val; *cur; cur++ ) {
723             out.writeChar(*cur);
724         }
725     } else {
726         out.writeString(" ");
727     }
728     out.writeString("-->");
730     if (addWhitespace) {
731         out.writeString("\n");
732     }
735 namespace {
737 typedef std::map<Glib::QueryQuark, gchar const *, Inkscape::compare_quark_ids> LocalNameMap;
738 typedef std::map<Glib::QueryQuark, Inkscape::Util::ptr_shared<char>, Inkscape::compare_quark_ids> NSMap;
740 gchar const *qname_local_name(Glib::QueryQuark qname) {
741     static LocalNameMap local_name_map;
742     LocalNameMap::iterator iter = local_name_map.find(qname);
743     if ( iter != local_name_map.end() ) {
744         return (*iter).second;
745     } else {
746         gchar const *name_string=g_quark_to_string(qname);
747         gchar const *prefix_end=strchr(name_string, ':');
748         if (prefix_end) {
749             return prefix_end + 1;
750         } else {
751             return name_string;
752         }
753     }
756 void add_ns_map_entry(NSMap &ns_map, Glib::QueryQuark prefix) {
757     using Inkscape::Util::ptr_shared;
758     using Inkscape::Util::share_unsafe;
760     static const Glib::QueryQuark xml_prefix("xml");
762     NSMap::iterator iter=ns_map.find(prefix);
763     if ( iter == ns_map.end() ) {
764         if (prefix.id()) {
765             gchar const *uri=sp_xml_ns_prefix_uri(g_quark_to_string(prefix));
766             if (uri) {
767                 ns_map.insert(NSMap::value_type(prefix, share_unsafe(uri)));
768             } else if ( prefix != xml_prefix ) {
769                 g_warning("No namespace known for normalized prefix %s", g_quark_to_string(prefix));
770             }
771         } else {
772             ns_map.insert(NSMap::value_type(prefix, ptr_shared<char>()));
773         }
774     }
777 void populate_ns_map(NSMap &ns_map, Node &repr) {
778     if ( repr.type() == Inkscape::XML::ELEMENT_NODE ) {
779         add_ns_map_entry(ns_map, qname_prefix(repr.code()));
780         for ( List<AttributeRecord const> iter=repr.attributeList() ;
781               iter ; ++iter )
782         {
783             Glib::QueryQuark prefix=qname_prefix(iter->key);
784             if (prefix.id()) {
785                 add_ns_map_entry(ns_map, prefix);
786             }
787         }
788         for ( Node *child=sp_repr_children(&repr) ;
789               child ; child = sp_repr_next(child) )
790         {
791             populate_ns_map(ns_map, *child);
792         }
793     }
798 static void
799 sp_repr_write_stream_root_element(Node *repr, Writer &out,
800                                   bool add_whitespace, gchar const *default_ns,
801                                   int inlineattrs, int indent,
802                                   gchar const *const old_href_base,
803                                   gchar const *const new_href_base)
805     using Inkscape::Util::ptr_shared;
807     g_assert(repr != NULL);
808     Glib::QueryQuark xml_prefix=g_quark_from_static_string("xml");
810     NSMap ns_map;
811     populate_ns_map(ns_map, *repr);
813     Glib::QueryQuark elide_prefix=GQuark(0);
814     if ( default_ns && ns_map.find(GQuark(0)) == ns_map.end() ) {
815         elide_prefix = g_quark_from_string(sp_xml_ns_uri_prefix(default_ns, NULL));
816     }
818     List<AttributeRecord const> attributes=repr->attributeList();
819     for ( NSMap::iterator iter=ns_map.begin() ; iter != ns_map.end() ; ++iter ) 
820     {
821         Glib::QueryQuark prefix=(*iter).first;
822         ptr_shared<char> ns_uri=(*iter).second;
824         if (prefix.id()) {
825             if ( prefix != xml_prefix ) {
826                 if ( elide_prefix == prefix ) {
827                     attributes = cons(AttributeRecord(g_quark_from_static_string("xmlns"), ns_uri), attributes);
828                 }
830                 Glib::ustring attr_name="xmlns:";
831                 attr_name.append(g_quark_to_string(prefix));
832                 GQuark key = g_quark_from_string(attr_name.c_str());
833                 attributes = cons(AttributeRecord(key, ns_uri), attributes);
834             }
835         } else {
836             // if there are non-namespaced elements, we can't globally
837             // use a default namespace
838             elide_prefix = GQuark(0);
839         }
840     }
842     return sp_repr_write_stream_element(repr, out, 0, add_whitespace, elide_prefix, attributes,
843                                         inlineattrs, indent, old_href_base, new_href_base);
846 void sp_repr_write_stream( Node *repr, Writer &out, gint indent_level,
847                            bool add_whitespace, Glib::QueryQuark elide_prefix,
848                            int inlineattrs, int indent,
849                            gchar const *const old_href_base,
850                            gchar const *const new_href_base)
852     switch (repr->type()) {
853         case Inkscape::XML::TEXT_NODE: {
854             repr_quote_write( out, repr->content() );
855             break;
856         }
857         case Inkscape::XML::COMMENT_NODE: {
858             repr_write_comment( out, repr->content(), add_whitespace, indent_level, indent );
859             break;
860         }
861         case Inkscape::XML::PI_NODE: {
862             out.printf( "<?%s %s?>", repr->name(), repr->content() );
863             break;
864         }
865         case Inkscape::XML::ELEMENT_NODE: {
866             sp_repr_write_stream_element( repr, out, indent_level,
867                                           add_whitespace, elide_prefix,
868                                           repr->attributeList(),
869                                           inlineattrs, indent,
870                                           old_href_base, new_href_base);
871             break;
872         }
873         case Inkscape::XML::DOCUMENT_NODE: {
874             g_assert_not_reached();
875             break;
876         }
877         default: {
878             g_assert_not_reached();
879         }
880     }
884 static void
885 sp_repr_write_stream_element (Node * repr, Writer & out, gint indent_level,
886                               bool add_whitespace,
887                               Glib::QueryQuark elide_prefix,
888                               List<AttributeRecord const> attributes, 
889                               int inlineattrs, int indent,
890                               gchar const *const old_href_base,
891                               gchar const *const new_href_base)
893     Node *child;
894     bool loose;
896     g_return_if_fail (repr != NULL);
898     if ( indent_level > 16 ) {
899         indent_level = 16;
900     }
902     if (add_whitespace && indent) {
903         for (gint i = 0; i < indent_level; i++) {
904             for (gint j = 0; j < indent; j++) {
905                 out.writeString(" ");
906             }
907         }
908     }
910     GQuark code = repr->code();
911     gchar const *element_name;
912     if ( elide_prefix == qname_prefix(code) ) {
913         element_name = qname_local_name(code);
914     } else {
915         element_name = g_quark_to_string(code);
916     }
917     out.printf( "<%s", element_name );
919     // if this is a <text> element, suppress formatting whitespace
920     // for its content and children:
921     gchar const *xml_space_attr = repr->attribute("xml:space");
922     if (xml_space_attr != NULL && !strcmp(xml_space_attr, "preserve")) {
923         add_whitespace = false;
924     }
926     for ( List<AttributeRecord const> iter = rebase_href_attrs(old_href_base, new_href_base,
927                                                                attributes);
928           iter ; ++iter )
929     {
930         if (!inlineattrs) {
931             out.writeString("\n");
932             if (indent) {
933                 for ( gint i = 0 ; i < indent_level + 1 ; i++ ) {
934                     for ( gint j = 0 ; j < indent ; j++ ) {
935                         out.writeString(" ");
936                     }
937                 }
938             }
939         }
940         out.printf(" %s=\"", g_quark_to_string(iter->key));
941         repr_quote_write(out, iter->value);
942         out.writeChar('"');
943     }
945     loose = TRUE;
946     for (child = repr->firstChild() ; child != NULL; child = child->next()) {
947         if (child->type() == Inkscape::XML::TEXT_NODE) {
948             loose = FALSE;
949             break;
950         }
951     }
952     if (repr->firstChild()) {
953         out.writeString( ">" );
954         if (loose && add_whitespace) {
955             out.writeString( "\n" );
956         }
957         for (child = repr->firstChild(); child != NULL; child = child->next()) {
958             sp_repr_write_stream(child, out, ( loose ? indent_level + 1 : 0 ),
959                                  add_whitespace, elide_prefix, inlineattrs, indent,
960                                  old_href_base, new_href_base);
961         }
963         if (loose && add_whitespace && indent) {
964             for (gint i = 0; i < indent_level; i++) {
965                 for ( gint j = 0 ; j < indent ; j++ ) {
966                     out.writeString(" ");
967                 }
968             }
969         }
970         out.printf( "</%s>", element_name );
971     } else {
972         out.writeString( " />" );
973     }
975     // text elements cannot nest, so we can output newline
976     // after closing text
978     if (add_whitespace || !strcmp (repr->name(), "svg:text")) {
979         out.writeString( "\n" );
980     }
984 /*
985   Local Variables:
986   mode:c++
987   c-file-style:"stroustrup"
988   c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
989   indent-tabs-mode:nil
990   fill-column:99
991   End:
992 */
993 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 :