Code

Super duper mega (fun!) commit: replaced encoding=utf-8 with fileencoding=utf-8 in...
[inkscape.git] / src / xml / repr-io.cpp
index bb62393fc541981002e120d4e03b1db8d14e89e4..b1320a4a399d9eb54fc06671da6cb5b779d6e431 100644 (file)
 # include <config.h>
 #endif
 
+#include <cstring>
+#include <string>
 #include <stdexcept>
 
+#include <libxml/parser.h>
+
 #include "xml/repr.h"
 #include "xml/attribute-record.h"
+#include "xml/rebase-hrefs.h"
+#include "xml/simple-document.h"
 
 #include "io/sys.h"
 #include "io/uristream.h"
+#include "io/stringstream.h"
 #include "io/gzipstream.h"
 
+#include "extension/extension.h"
+
+#include "preferences.h"
 
 using Inkscape::IO::Writer;
 using Inkscape::Util::List;
 using Inkscape::Util::cons;
 using Inkscape::XML::Document;
+using Inkscape::XML::SimpleDocument;
 using Inkscape::XML::Node;
 using Inkscape::XML::AttributeRecord;
+using Inkscape::XML::calc_abs_doc_base;
+using Inkscape::XML::rebase_href_attrs;
 
-static Document *sp_repr_do_read (xmlDocPtr doc, const gchar *default_ns);
-static Node *sp_repr_svg_read_node (xmlNodePtr node, const gchar *default_ns, GHashTable *prefix_map);
+Document *sp_repr_do_read (xmlDocPtr doc, const gchar *default_ns);
+static Node *sp_repr_svg_read_node (Document *xml_doc, xmlNodePtr node, const gchar *default_ns, GHashTable *prefix_map);
 static gint sp_repr_qualified_name (gchar *p, gint len, xmlNsPtr ns, const xmlChar *name, const gchar *default_ns, GHashTable *prefix_map);
-static void sp_repr_write_stream_root_element (Node *repr, Writer &out, gboolean add_whitespace, gchar const *default_ns);
-static void sp_repr_write_stream (Node *repr, Writer &out, gint indent_level, gboolean add_whitespace, Glib::QueryQuark elide_prefix);
-static void sp_repr_write_stream_element (Node *repr, Writer &out, gint indent_level, gboolean add_whitespace, Glib::QueryQuark elide_prefix, List<AttributeRecord const> attributes);
+static void sp_repr_write_stream_root_element(Node *repr, Writer &out,
+                                              bool add_whitespace, gchar const *default_ns,
+                                              int inlineattrs, int indent,
+                                              gchar const *old_href_abs_base,
+                                              gchar const *new_href_abs_base);
+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,
+                                         gchar const *old_href_abs_base,
+                                         gchar const *new_href_abs_base);
 
 #ifdef HAVE_LIBWMF
 static xmlDocPtr sp_wmf_convert (const char * file_name);
@@ -51,8 +73,9 @@ class XmlSource
 public:
     XmlSource()
         : filename(0),
+          encoding(0),
           fp(0),
-          first(false),
+          firstFewLen(0),
           dummy("x"),
           instr(0),
           gzin(0)
@@ -61,33 +84,90 @@ public:
     virtual ~XmlSource()
     {
         close();
+        if ( encoding ) {
+            g_free(encoding);
+            encoding = 0;
+        }
     }
 
-    void setFile( char const * filename );
-
-    static int readCb( void * context, char * buffer, int len);
-    static int closeCb(void * context);
+    int setFile( char const * filename );
 
+    static int readCb( void * context, char * buffer, int len );
+    static int closeCb( void * context );
 
+    char const* getEncoding() const { return encoding; }
     int read( char * buffer, int len );
     int close();
 private:
     const char* filename;
+    char* encoding;
     FILE* fp;
-    bool first;
+    unsigned char firstFew[4];
+    int firstFewLen;
     Inkscape::URI dummy;
     Inkscape::IO::UriInputStream* instr;
     Inkscape::IO::GzipInputStream* gzin;
 };
 
-void XmlSource::setFile(char const *filename)
+int XmlSource::setFile(char const *filename)
 {
+    int retVal = -1;
+
     this->filename = filename;
+
     fp = Inkscape::IO::fopen_utf8name(filename, "r");
-    if (fp == NULL) {
-        throw std::runtime_error("Could not open file for reading");
+    if ( fp ) {
+        // First peek in the file to see what it is
+        memset( firstFew, 0, sizeof(firstFew) );
+
+        size_t some = fread( firstFew, 1, 4, fp );
+        if ( fp ) {
+            // first check for compression
+            if ( (some >= 2) && (firstFew[0] == 0x1f) && (firstFew[1] == 0x8b) ) {
+                //g_message(" the file being read is gzip'd. extract it");
+                fclose(fp);
+                fp = 0;
+                fp = Inkscape::IO::fopen_utf8name(filename, "r");
+                instr = new Inkscape::IO::UriInputStream(fp, dummy);
+                gzin = new Inkscape::IO::GzipInputStream(*instr);
+
+                memset( firstFew, 0, sizeof(firstFew) );
+                some = 0;
+                int single = 0;
+                while ( some < 4 && single >= 0 )
+                {
+                    single = gzin->get();
+                    if ( single >= 0 ) {
+                        firstFew[some++] = 0x0ff & single;
+                    } else {
+                        break;
+                    }
+                }
+            }
+
+            int encSkip = 0;
+            if ( (some >= 2) &&(firstFew[0] == 0xfe) && (firstFew[1] == 0xff) ) {
+                encoding = g_strdup("UTF-16BE");
+                encSkip = 2;
+            } else if ( (some >= 2) && (firstFew[0] == 0xff) && (firstFew[1] == 0xfe) ) {
+                encoding = g_strdup("UTF-16LE");
+                encSkip = 2;
+            } else if ( (some >= 3) && (firstFew[0] == 0xef) && (firstFew[1] == 0xbb) && (firstFew[2] == 0xbf) ) {
+                encoding = g_strdup("UTF-8");
+                encSkip = 3;
+            }
+
+            if ( encSkip ) {
+                memmove( firstFew, firstFew + encSkip, (some - encSkip) );
+                some -= encSkip;
+            }
+
+            firstFewLen = some;
+            retVal = 0; // no error
+        }
     }
-    first = true;
+
+    return retVal;
 }
 
 
@@ -115,36 +195,17 @@ int XmlSource::read( char *buffer, int len )
     int retVal = 0;
     size_t got = 0;
 
-    if ( first ) {
-        first = false;
-        char tmp[] = {0,0};
-        size_t some = fread( tmp, 1, 2, fp );
-
-        if ( (some >= 2) && (tmp[0] == 0x1f) && ((unsigned char)(tmp[1]) == 0x8b) ) {
-            //g_message(" the file being read is gzip'd. extract it");
-            fclose(fp);
-            fp = 0;
-            fp = Inkscape::IO::fopen_utf8name(filename, "r");
-            instr = new Inkscape::IO::UriInputStream(fp, dummy);
-            gzin = new Inkscape::IO::GzipInputStream(*instr);
-            int single = 0;
-            while ( (int)got < len && single >= 0 )
-            {
-                single = gzin->get();
-                if ( single >= 0 ) {
-                    buffer[got++] = 0x0ff & single;
-                } else {
-                    break;
-                }
-            }
-            //g_message(" extracted %d bytes this pass", got );
-        } else {
-            memcpy( buffer, tmp, some );
-            got = some;
+    if ( firstFewLen > 0 ) {
+        int some = (len < firstFewLen) ? len : firstFewLen;
+        memcpy( buffer, firstFew, some );
+        if ( len < firstFewLen ) {
+            memmove( firstFew, firstFew + some, (firstFewLen - some) );
         }
+        firstFewLen -= some;
+        got = some;
     } else if ( gzin ) {
         int single = 0;
-        while ( (int)got < len && single >= 0 )
+        while ( (static_cast<int>(got) < len) && (single >= 0) )
         {
             single = gzin->get();
             if ( single >= 0 ) {
@@ -153,18 +214,15 @@ int XmlSource::read( char *buffer, int len )
                 break;
             }
         }
-        //g_message(" extracted %d bytes this pass  b", got );
     } else {
         got = fread( buffer, 1, len, fp );
     }
 
     if ( feof(fp) ) {
         retVal = got;
-    }
-    else if ( ferror(fp) ) {
+    } else if ( ferror(fp) ) {
         retVal = -1;
-    }
-    else {
+    } else {
         retVal = got;
     }
 
@@ -205,6 +263,9 @@ sp_repr_read_file (const gchar * filename, const gchar *default_ns)
 
     g_return_val_if_fail (filename != NULL, NULL);
     g_return_val_if_fail (Inkscape::IO::file_test( filename, G_FILE_TEST_EXISTS ), NULL);
+    /* fixme: A file can disappear at any time, including between now and when we actually try to
+     * open it.  Get rid of the above test once we're sure that we correctly handle
+     * non-existence. */
 
     // TODO: bulia, please look over
     gsize bytesRead = 0;
@@ -217,50 +278,35 @@ sp_repr_read_file (const gchar * filename, const gchar *default_ns)
 
     Inkscape::IO::dump_fopen_call( filename, "N" );
 
-    XmlSource src;
-    try
-    {
-        src.setFile(filename);
-    }
-    catch (...)
-    {
-        return NULL;
-    }
-
-    xmlDocPtr doubleDoc = xmlReadIO( XmlSource::readCb,
-                                     XmlSource::closeCb,
-                                     &src,
-                                     localFilename,
-                                     NULL, //"UTF-8",
-                                     XML_PARSE_NOENT );
-
-
 #ifdef HAVE_LIBWMF
     if (strlen (localFilename) > 4) {
         if ( (strcmp (localFilename + strlen (localFilename) - 4,".wmf") == 0)
-          || (strcmp (localFilename + strlen (localFilename) - 4,".WMF") == 0))
+             || (strcmp (localFilename + strlen (localFilename) - 4,".WMF") == 0)) {
             doc = sp_wmf_convert (localFilename);
-        else
-            doc = xmlParseFile (localFilename);
-    }
-    else {
-        doc = xmlParseFile (localFilename);
+        }
     }
-#else /* !HAVE_LIBWMF */
-    //doc = xmlParseFile (localFilename);
-#endif /* !HAVE_LIBWMF */
+#endif // !HAVE_LIBWMF
 
-    //rdoc = sp_repr_do_read (doc, default_ns);
-    rdoc = sp_repr_do_read (doubleDoc, default_ns);
-    if (doc)
-        xmlFreeDoc (doc);
+    if ( !doc ) {
+        XmlSource src;
+
+        if ( (src.setFile(filename) == 0) ) {
+            doc = xmlReadIO( XmlSource::readCb,
+                             XmlSource::closeCb,
+                             &src,
+                             localFilename,
+                             src.getEncoding(),
+                             XML_PARSE_NOENT | XML_PARSE_HUGE);
+        }
+    }
 
-    if ( localFilename != NULL )
-        g_free (localFilename);
+    rdoc = sp_repr_do_read( doc, default_ns );
+    if ( doc ) {
+        xmlFreeDoc( doc );
+    }
 
-    if ( doubleDoc != NULL )
-    {
-        xmlFreeDoc( doubleDoc );
+    if ( localFilename ) {
+        g_free( localFilename );
     }
 
     return rdoc;
@@ -279,14 +325,25 @@ sp_repr_read_mem (const gchar * buffer, gint length, const gchar *default_ns)
 
     g_return_val_if_fail (buffer != NULL, NULL);
 
-    doc = xmlParseMemory ((gchar *) buffer, length);
+    doc = xmlParseMemory (const_cast<gchar *>(buffer), length);
 
     rdoc = sp_repr_do_read (doc, default_ns);
-    if (doc)
+    if (doc) {
         xmlFreeDoc (doc);
+    }
     return rdoc;
 }
 
+/**
+ * Reads and parses XML from a buffer, returning it as an Document
+ */
+Document *
+sp_repr_read_buf (const Glib::ustring &buf, const gchar *default_ns)
+{
+    return sp_repr_read_mem(buf.c_str(), buf.size(), default_ns);
+}
+
+
 namespace Inkscape {
 
 struct compare_quark_ids {
@@ -323,16 +380,16 @@ Glib::QueryQuark qname_prefix(Glib::QueryQuark qname) {
 
 namespace {
 
-void promote_to_svg_namespace(Node *repr) {
+void promote_to_namespace(Node *repr, const gchar *prefix) {
     if ( repr->type() == Inkscape::XML::ELEMENT_NODE ) {
         GQuark code = repr->code();
         if (!qname_prefix(code).id()) {
-            gchar *svg_name = g_strconcat("svg:", g_quark_to_string(code), NULL);
+            gchar *svg_name = g_strconcat(prefix, ":", g_quark_to_string(code), NULL);
             repr->setCodeUnsafe(g_quark_from_string(svg_name));
             g_free(svg_name);
         }
         for ( Node *child = sp_repr_children(repr) ; child ; child = sp_repr_next(child) ) {
-            promote_to_svg_namespace(child);
+            promote_to_namespace(child, prefix);
         }
     }
 }
@@ -345,20 +402,25 @@ void promote_to_svg_namespace(Node *repr) {
 Document *
 sp_repr_do_read (xmlDocPtr doc, const gchar *default_ns)
 {
-    if (doc == NULL) return NULL;
+    if (doc == NULL) {
+        return NULL;
+    }
     xmlNodePtr node=xmlDocGetRootElement (doc);
-    if (node == NULL) return NULL;
+    if (node == NULL) {
+        return NULL;
+    }
 
     GHashTable * prefix_map;
     prefix_map = g_hash_table_new (g_str_hash, g_str_equal);
 
-    GSList *reprs=NULL;
-    Node *root=NULL;
+    Document *rdoc = new Inkscape::XML::SimpleDocument();
 
+    Node *root=NULL;
     for ( node = doc->children ; node != NULL ; node = node->next ) {
         if (node->type == XML_ELEMENT_NODE) {
-            Node *repr=sp_repr_svg_read_node (node, default_ns, prefix_map);
-            reprs = g_slist_append(reprs, repr);
+            Node *repr=sp_repr_svg_read_node(rdoc, node, default_ns, prefix_map);
+            rdoc->appendChild(repr);
+            Inkscape::GC::release(repr);
 
             if (!root) {
                 root = repr;
@@ -366,31 +428,25 @@ sp_repr_do_read (xmlDocPtr doc, const gchar *default_ns)
                 root = NULL;
                 break;
             }
-        } else if ( node->type == XML_COMMENT_NODE ) {
-            Node *comment=sp_repr_svg_read_node(node, default_ns, prefix_map);
-            reprs = g_slist_append(reprs, comment);
+        } else if ( node->type == XML_COMMENT_NODE || node->type == XML_PI_NODE ) {
+            Node *repr=sp_repr_svg_read_node(rdoc, node, default_ns, prefix_map);
+            rdoc->appendChild(repr);
+            Inkscape::GC::release(repr);
         }
     }
 
-    Document *rdoc=NULL;
-
     if (root != NULL) {
-        /* promote elements of SVG documents that don't use namespaces
-         * into the SVG namespace */
-        if ( default_ns && !strcmp(default_ns, SP_SVG_NS_URI)
-             && !strcmp(root->name(), "svg") )
-        {
-            promote_to_svg_namespace(root);
+        /* promote elements of some XML documents that don't use namespaces
+         * into their default namespace */
+        if ( default_ns && !strchr(root->name(), ':') ) {
+            if ( !strcmp(default_ns, SP_SVG_NS_URI) ) {
+                promote_to_namespace(root, "svg");
+            }
+            if ( !strcmp(default_ns, INKSCAPE_EXTENSION_URI) ) {
+                promote_to_namespace(root, INKSCAPE_EXTENSION_NS_NC);
+            }
         }
-
-        rdoc = sp_repr_document_new_list(reprs);
-    }
-
-    for ( GSList *iter = reprs ; iter ; iter = iter->next ) {
-        Node *repr=(Node *)iter->data;
-        Inkscape::GC::release(repr);
     }
-    g_slist_free(reprs);
 
     g_hash_table_destroy (prefix_map);
 
@@ -398,24 +454,28 @@ sp_repr_do_read (xmlDocPtr doc, const gchar *default_ns)
 }
 
 gint
-sp_repr_qualified_name (gchar *p, gint len, xmlNsPtr ns, const xmlChar *name, const gchar *default_ns, GHashTable *prefix_map)
+sp_repr_qualified_name (gchar *p, gint len, xmlNsPtr ns, const xmlChar *name, const gchar */*default_ns*/, GHashTable *prefix_map)
 {
     const xmlChar *prefix;
     if ( ns && ns->href ) {
-        prefix = (xmlChar*)sp_xml_ns_uri_prefix ((gchar*)ns->href, (char*)ns->prefix);
-        g_hash_table_insert (prefix_map, (gpointer)prefix, (gpointer)ns->href);
+        prefix = reinterpret_cast<const xmlChar*>( sp_xml_ns_uri_prefix(reinterpret_cast<const gchar*>(ns->href),
+                                                                        reinterpret_cast<const char*>(ns->prefix)) );
+        void* p0 = reinterpret_cast<gpointer>(const_cast<xmlChar *>(prefix));
+        void* p1 = reinterpret_cast<gpointer>(const_cast<xmlChar *>(ns->href));
+        g_hash_table_insert( prefix_map, p0, p1 );
     } else {
         prefix = NULL;
     }
 
-    if (prefix)
-        return g_snprintf (p, len, "%s:%s", (gchar*)prefix, name);
-    else
+    if (prefix) {
+        return g_snprintf (p, len, "%s:%s", reinterpret_cast<const gchar*>(prefix), name);
+    } else {
         return g_snprintf (p, len, "%s", name);
+    }
 }
 
 static Node *
-sp_repr_svg_read_node (xmlNodePtr node, const gchar *default_ns, GHashTable *prefix_map)
+sp_repr_svg_read_node (Document *xml_doc, xmlNodePtr node, const gchar *default_ns, GHashTable *prefix_map)
 {
     Node *repr, *crepr;
     xmlAttrPtr prop;
@@ -424,8 +484,9 @@ sp_repr_svg_read_node (xmlNodePtr node, const gchar *default_ns, GHashTable *pre
 
     if (node->type == XML_TEXT_NODE || node->type == XML_CDATA_SECTION_NODE) {
 
-        if (node->content == NULL || *(node->content) == '\0')
+        if (node->content == NULL || *(node->content) == '\0') {
             return NULL; // empty text node
+        }
 
         bool preserve = (xmlNodeGetSpacePreserve (node) == 1);
 
@@ -437,33 +498,41 @@ sp_repr_svg_read_node (xmlNodePtr node, const gchar *default_ns, GHashTable *pre
             return NULL; // we do not preserve all-whitespace nodes unless we are asked to
         }
 
-        Node *rdoc = sp_repr_new_text((const gchar *)node->content);
-        return rdoc;
+        return xml_doc->createTextNode(reinterpret_cast<gchar *>(node->content));
+    }
+
+    if (node->type == XML_COMMENT_NODE) {
+        return xml_doc->createComment(reinterpret_cast<gchar *>(node->content));
     }
 
-    if (node->type == XML_COMMENT_NODE)
-        return sp_repr_new_comment((const gchar *)node->content);
+    if (node->type == XML_PI_NODE) {
+        return xml_doc->createPI(reinterpret_cast<const gchar *>(node->name),
+                                 reinterpret_cast<const gchar *>(node->content));
+    }
 
-    if (node->type == XML_ENTITY_DECL) return NULL;
+    if (node->type == XML_ENTITY_DECL) {
+        return NULL;
+    }
 
     sp_repr_qualified_name (c, 256, node->ns, node->name, default_ns, prefix_map);
-    repr = sp_repr_new (c);
+    repr = xml_doc->createElement(c);
     /* TODO remember node->ns->prefix if node->ns != NULL */
 
     for (prop = node->properties; prop != NULL; prop = prop->next) {
         if (prop->children) {
             sp_repr_qualified_name (c, 256, prop->ns, prop->name, default_ns, prefix_map);
-            repr->setAttribute(c, (gchar*)prop->children->content);
+            repr->setAttribute(c, reinterpret_cast<gchar*>(prop->children->content));
             /* TODO remember prop->ns->prefix if prop->ns != NULL */
         }
     }
 
-    if (node->content)
-        repr->setContent((gchar*)node->content);
+    if (node->content) {
+        repr->setContent(reinterpret_cast<gchar*>(node->content));
+    }
 
     child = node->xmlChildrenNode;
     for (child = node->xmlChildrenNode; child != NULL; child = child->next) {
-        crepr = sp_repr_svg_read_node (child, default_ns, prefix_map);
+        crepr = sp_repr_svg_read_node (xml_doc, child, default_ns, prefix_map);
         if (crepr) {
             repr->appendChild(crepr);
             Inkscape::GC::release(crepr);
@@ -473,119 +542,200 @@ sp_repr_svg_read_node (xmlNodePtr node, const gchar *default_ns, GHashTable *pre
     return repr;
 }
 
-void
-sp_repr_save_stream (Document *doc, FILE *fp, gchar const *default_ns, bool compress)
-{
-    Node *repr;
-    const gchar *str;
 
-    Inkscape::URI dummy("x");
-    Inkscape::IO::UriOutputStream bout(fp, dummy);
-    Inkscape::IO::GzipOutputStream *gout = compress ? new Inkscape::IO::GzipOutputStream(bout) : NULL;
-    Inkscape::IO::OutputStreamWriter *out  = compress ? new Inkscape::IO::OutputStreamWriter( *gout ) : new Inkscape::IO::OutputStreamWriter( bout );
+static void
+sp_repr_save_writer(Document *doc, Inkscape::IO::Writer *out,
+                    gchar const *default_ns,
+                    gchar const *old_href_abs_base,
+                    gchar const *new_href_abs_base)
+{
+    Inkscape::Preferences *prefs = Inkscape::Preferences::get();
+    bool inlineattrs = prefs->getBool("/options/svgoutput/inlineattrs");
+    int indent = prefs->getInt("/options/svgoutput/indent", 2);
 
     /* fixme: do this The Right Way */
-
     out->writeString( "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?>\n" );
 
-    str = ((Node *)doc)->attribute("doctype");
+    const gchar *str = static_cast<Node *>(doc)->attribute("doctype");
     if (str) {
         out->writeString( str );
     }
 
-    repr = sp_repr_document_first_child(doc);
-    for ( repr = sp_repr_document_first_child(doc) ;
-          repr ; repr = sp_repr_next(repr) )
+    for (Node *repr = sp_repr_document_first_child(doc);
+         repr; repr = sp_repr_next(repr))
     {
-        if ( repr->type() == Inkscape::XML::ELEMENT_NODE ) {
-            sp_repr_write_stream_root_element(repr, *out, TRUE, default_ns);
-        } else if ( repr->type() == Inkscape::XML::COMMENT_NODE ) {
-            sp_repr_write_stream(repr, *out, 0, TRUE, GQuark(0));
-            out->writeChar( '\n' );
+        Inkscape::XML::NodeType const node_type = repr->type();
+        if ( node_type == Inkscape::XML::ELEMENT_NODE ) {
+            sp_repr_write_stream_root_element(repr, *out, TRUE, default_ns, inlineattrs, indent,
+                                              old_href_abs_base, new_href_abs_base);
         } else {
-            sp_repr_write_stream(repr, *out, 0, TRUE, GQuark(0));
+            sp_repr_write_stream(repr, *out, 0, TRUE, GQuark(0), inlineattrs, indent,
+                                 old_href_abs_base, new_href_abs_base);
+            if ( node_type == Inkscape::XML::COMMENT_NODE ) {
+                out->writeChar('\n');
+            }
         }
     }
-    if ( out ) {
-        delete out;
-        out = NULL;
-    }
-    if ( gout ) {
-        delete gout;
-        gout = NULL;
-    }
 }
 
-/* Returns TRUE if file successfully saved; FALSE if not
+
+
+
+Glib::ustring
+sp_repr_save_buf(Document *doc)
+{   
+    Inkscape::IO::StringOutputStream souts;
+    Inkscape::IO::OutputStreamWriter outs(souts);
+
+    sp_repr_save_writer(doc, &outs, SP_INKSCAPE_NS_URI, 0, 0);
+
+       outs.close();
+       Glib::ustring buf = souts.getString();
+
+       return buf;
+}
+
+
+
+
+
+void
+sp_repr_save_stream(Document *doc, FILE *fp, gchar const *default_ns, bool compress,
+                    gchar const *const old_href_abs_base,
+                    gchar const *const new_href_abs_base)
+{
+    Inkscape::URI dummy("x");
+    Inkscape::IO::UriOutputStream bout(fp, dummy);
+    Inkscape::IO::GzipOutputStream *gout = compress ? new Inkscape::IO::GzipOutputStream(bout) : NULL;
+    Inkscape::IO::OutputStreamWriter *out  = compress ? new Inkscape::IO::OutputStreamWriter( *gout ) : new Inkscape::IO::OutputStreamWriter( bout );
+
+    sp_repr_save_writer(doc, out, default_ns, old_href_abs_base, new_href_abs_base);
+
+    delete out;
+    delete gout;
+}
+
+
+
+/**
+ * Returns true iff file successfully saved.
+ *
+ * \param filename The actual file to do I/O to, which might be a temp file.
+ *
+ * \param for_filename The base URI [actually filename] to assume for purposes of rewriting
+ *              xlink:href attributes.
  */
-gboolean
-sp_repr_save_file (Document *doc, const gchar *filename,
-                   gchar const *default_ns)
+bool
+sp_repr_save_rebased_file(Document *doc, gchar const *const filename, gchar const *default_ns,
+                          gchar const *old_base, gchar const *for_filename)
 {
-    if (filename == NULL) {
-        return FALSE;
+    if (!filename) {
+        return false;
     }
-    bool compress = false;
+
+    bool compress;
     {
-        if (strlen (filename) > 5) {
-            gchar tmp[] = {0,0,0,0,0,0};
-            strncpy( tmp, filename + strlen (filename) - 5, 6 );
-            tmp[5] = 0;
-            if ( strcasecmp(".svgz", tmp ) == 0 )
-            {
-                //g_message("TIME TO COMPRESS THE OUTPUT FOR SVGZ");
-                compress = true;
-            }
-        }
+        size_t const filename_len = strlen(filename);
+        compress = ( filename_len > 5
+                     && strcasecmp(".svgz", filename + filename_len - 5) == 0 );
     }
 
     Inkscape::IO::dump_fopen_call( filename, "B" );
     FILE *file = Inkscape::IO::fopen_utf8name(filename, "w");
     if (file == NULL) {
-        return FALSE;
+        return false;
     }
 
-    sp_repr_save_stream (doc, file, default_ns, compress);
+    gchar *old_href_abs_base = NULL;
+    gchar *new_href_abs_base = NULL;
+    if (for_filename) {
+        old_href_abs_base = calc_abs_doc_base(old_base);
+        if (g_path_is_absolute(for_filename)) {
+            new_href_abs_base = g_path_get_dirname(for_filename);
+        } else {
+            gchar *const cwd = g_get_current_dir();
+            gchar *const for_abs_filename = g_build_filename(cwd, for_filename, NULL);
+            g_free(cwd);
+            new_href_abs_base = g_path_get_dirname(for_abs_filename);
+            g_free(for_abs_filename);
+        }
+
+        /* effic: Once we're confident that we never need (or never want) to resort
+         * to using sodipodi:absref instead of the xlink:href value,
+         * then we should do `if streq() { free them and set both to NULL; }'. */
+    }
+    sp_repr_save_stream(doc, file, default_ns, compress, old_href_abs_base, new_href_abs_base);
+
+    g_free(old_href_abs_base);
+    g_free(new_href_abs_base);
 
     if (fclose (file) != 0) {
-        return FALSE;
+        return false;
     }
 
-    return TRUE;
+    return true;
 }
 
-void
-sp_repr_print (Node * repr)
+/**
+ * Returns true iff file successfully saved.
+ */
+bool
+sp_repr_save_file(Document *doc, gchar const *const filename, gchar const *default_ns)
 {
-    Inkscape::IO::StdOutputStream bout;
-    Inkscape::IO::OutputStreamWriter out(bout);
-
-    sp_repr_write_stream (repr, out, 0, TRUE, GQuark(0));
-
-    return;
+    return sp_repr_save_rebased_file(doc, filename, default_ns, NULL, NULL);
 }
 
+
 /* (No doubt this function already exists elsewhere.) */
 static void
 repr_quote_write (Writer &out, const gchar * val)
 {
-    if (!val) return;
+    if (val) {
+        for (; *val != '\0'; val++) {
+            switch (*val) {
+                case '"': out.writeString( "&quot;" ); break;
+                case '&': out.writeString( "&amp;" ); break;
+                case '<': out.writeString( "&lt;" ); break;
+                case '>': out.writeString( "&gt;" ); break;
+                default: out.writeChar( *val ); break;
+            }
+        }
+    }
+}
 
-    for (; *val != '\0'; val++) {
-        switch (*val) {
-        case '"': out.writeString( "&quot;" ); break;
-        case '&': out.writeString( "&amp;" ); break;
-        case '<': out.writeString( "&lt;" ); break;
-        case '>': out.writeString( "&gt;" ); break;
-        default: out.writeChar( *val ); break;
+static void repr_write_comment( Writer &out, const gchar * val, bool addWhitespace, gint indentLevel, int indent )
+{
+    if ( indentLevel > 16 ) {
+        indentLevel = 16;
+    }
+    if (addWhitespace && indent) {
+        for (gint i = 0; i < indentLevel; i++) {
+            for (gint j = 0; j < indent; j++) {
+                out.writeString(" ");
+            }
         }
     }
+
+    out.writeString("<!--");
+    // WARNING out.printf() and out.writeString() are *NOT* non-ASCII friendly.
+    if (val) {
+        for (const gchar* cur = val; *cur; cur++ ) {
+            out.writeChar(*cur);
+        }
+    } else {
+        out.writeString(" ");
+    }
+    out.writeString("-->");
+
+    if (addWhitespace) {
+        out.writeString("\n");
+    }
 }
 
 namespace {
 
 typedef std::map<Glib::QueryQuark, gchar const *, Inkscape::compare_quark_ids> LocalNameMap;
-typedef std::map<Glib::QueryQuark, Inkscape::Util::SharedCStringPtr, Inkscape::compare_quark_ids> NSMap;
+typedef std::map<Glib::QueryQuark, Inkscape::Util::ptr_shared<char>, Inkscape::compare_quark_ids> NSMap;
 
 gchar const *qname_local_name(Glib::QueryQuark qname) {
     static LocalNameMap local_name_map;
@@ -604,7 +754,8 @@ gchar const *qname_local_name(Glib::QueryQuark qname) {
 }
 
 void add_ns_map_entry(NSMap &ns_map, Glib::QueryQuark prefix) {
-    using Inkscape::Util::SharedCStringPtr;
+    using Inkscape::Util::ptr_shared;
+    using Inkscape::Util::share_unsafe;
 
     static const Glib::QueryQuark xml_prefix("xml");
 
@@ -613,12 +764,12 @@ void add_ns_map_entry(NSMap &ns_map, Glib::QueryQuark prefix) {
         if (prefix.id()) {
             gchar const *uri=sp_xml_ns_prefix_uri(g_quark_to_string(prefix));
             if (uri) {
-                ns_map.insert(NSMap::value_type(prefix, SharedCStringPtr::coerce(uri)));
+                ns_map.insert(NSMap::value_type(prefix, share_unsafe(uri)));
             } else if ( prefix != xml_prefix ) {
                 g_warning("No namespace known for normalized prefix %s", g_quark_to_string(prefix));
             }
         } else {
-            ns_map.insert(NSMap::value_type(prefix, SharedCStringPtr()));
+            ns_map.insert(NSMap::value_type(prefix, ptr_shared<char>()));
         }
     }
 }
@@ -644,10 +795,15 @@ void populate_ns_map(NSMap &ns_map, Node &repr) {
 
 }
 
-void
-sp_repr_write_stream_root_element (Node *repr, Writer &out, gboolean add_whitespace, gchar const *default_ns)
+static void
+sp_repr_write_stream_root_element(Node *repr, Writer &out,
+                                  bool add_whitespace, gchar const *default_ns,
+                                  int inlineattrs, int indent,
+                                  gchar const *const old_href_base,
+                                  gchar const *const new_href_base)
 {
-    using Inkscape::Util::SharedCStringPtr;
+    using Inkscape::Util::ptr_shared;
+
     g_assert(repr != NULL);
     Glib::QueryQuark xml_prefix=g_quark_from_static_string("xml");
 
@@ -663,7 +819,7 @@ sp_repr_write_stream_root_element (Node *repr, Writer &out, gboolean add_whitesp
     for ( NSMap::iterator iter=ns_map.begin() ; iter != ns_map.end() ; ++iter ) 
     {
         Glib::QueryQuark prefix=(*iter).first;
-        SharedCStringPtr ns_uri=(*iter).second;
+        ptr_shared<char> ns_uri=(*iter).second;
 
         if (prefix.id()) {
             if ( prefix != xml_prefix ) {
@@ -683,42 +839,71 @@ sp_repr_write_stream_root_element (Node *repr, Writer &out, gboolean add_whitesp
         }
     }
 
-    return sp_repr_write_stream_element(repr, out, 0, add_whitespace, elide_prefix, attributes);
+    return sp_repr_write_stream_element(repr, out, 0, add_whitespace, elide_prefix, attributes,
+                                        inlineattrs, indent, old_href_base, new_href_base);
 }
 
-void
-sp_repr_write_stream (Node *repr, Writer &out, gint indent_level,
-                      gboolean add_whitespace, Glib::QueryQuark elide_prefix)
+void sp_repr_write_stream( Node *repr, Writer &out, gint indent_level,
+                           bool add_whitespace, Glib::QueryQuark elide_prefix,
+                           int inlineattrs, int indent,
+                           gchar const *const old_href_base,
+                           gchar const *const new_href_base)
 {
-    if (repr->type() == Inkscape::XML::TEXT_NODE) {
-        repr_quote_write (out, repr->content());
-    } else if (repr->type() == Inkscape::XML::COMMENT_NODE) {
-        out.printf( "<!--%s-->", repr->content() );
-    } else if (repr->type() == Inkscape::XML::ELEMENT_NODE) {
-        sp_repr_write_stream_element(repr, out, indent_level, add_whitespace, elide_prefix, repr->attributeList());
-    } else {
-        g_assert_not_reached();
+    switch (repr->type()) {
+        case Inkscape::XML::TEXT_NODE: {
+            repr_quote_write( out, repr->content() );
+            break;
+        }
+        case Inkscape::XML::COMMENT_NODE: {
+            repr_write_comment( out, repr->content(), add_whitespace, indent_level, indent );
+            break;
+        }
+        case Inkscape::XML::PI_NODE: {
+            out.printf( "<?%s %s?>", repr->name(), repr->content() );
+            break;
+        }
+        case Inkscape::XML::ELEMENT_NODE: {
+            sp_repr_write_stream_element( repr, out, indent_level,
+                                          add_whitespace, elide_prefix,
+                                          repr->attributeList(),
+                                          inlineattrs, indent,
+                                          old_href_base, new_href_base);
+            break;
+        }
+        case Inkscape::XML::DOCUMENT_NODE: {
+            g_assert_not_reached();
+            break;
+        }
+        default: {
+            g_assert_not_reached();
+        }
     }
 }
 
-void
+
+static void
 sp_repr_write_stream_element (Node * repr, Writer & out, gint indent_level,
-                              gboolean add_whitespace,
+                              bool add_whitespace,
                               Glib::QueryQuark elide_prefix,
-                              List<AttributeRecord const> attributes)
+                              List<AttributeRecord const> attributes, 
+                              int inlineattrs, int indent,
+                              gchar const *const old_href_base,
+                              gchar const *const new_href_base)
 {
     Node *child;
-    gboolean loose;
-    gint i;
+    bool loose;
 
     g_return_if_fail (repr != NULL);
 
-    if ( indent_level > 16 )
+    if ( indent_level > 16 ) {
         indent_level = 16;
+    }
 
-    if (add_whitespace) {
-        for ( i = 0 ; i < indent_level ; i++ ) {
-            out.writeString( "  " );
+    if (add_whitespace && indent) {
+        for (gint i = 0; i < indent_level; i++) {
+            for (gint j = 0; j < indent; j++) {
+                out.writeString(" ");
+            }
         }
     }
 
@@ -735,15 +920,22 @@ sp_repr_write_stream_element (Node * repr, Writer & out, gint indent_level,
     // for its content and children:
     gchar const *xml_space_attr = repr->attribute("xml:space");
     if (xml_space_attr != NULL && !strcmp(xml_space_attr, "preserve")) {
-        add_whitespace = FALSE;
+        add_whitespace = false;
     }
 
-    for ( List<AttributeRecord const> iter = attributes ;
+    for ( List<AttributeRecord const> iter = rebase_href_attrs(old_href_base, new_href_base,
+                                                               attributes);
           iter ; ++iter )
     {
-        out.writeString("\n");
-        for ( i = 0 ; i < indent_level + 1 ; i++ ) {
-            out.writeString("  ");
+        if (!inlineattrs) {
+            out.writeString("\n");
+            if (indent) {
+                for ( gint i = 0 ; i < indent_level + 1 ; i++ ) {
+                    for ( gint j = 0 ; j < indent ; j++ ) {
+                        out.writeString(" ");
+                    }
+                }
+            }
         }
         out.printf(" %s=\"", g_quark_to_string(iter->key));
         repr_quote_write(out, iter->value);
@@ -763,12 +955,16 @@ sp_repr_write_stream_element (Node * repr, Writer & out, gint indent_level,
             out.writeString( "\n" );
         }
         for (child = repr->firstChild(); child != NULL; child = child->next()) {
-            sp_repr_write_stream (child, out, (loose) ? (indent_level + 1) : 0, add_whitespace, elide_prefix);
+            sp_repr_write_stream(child, out, ( loose ? indent_level + 1 : 0 ),
+                                 add_whitespace, elide_prefix, inlineattrs, indent,
+                                 old_href_base, new_href_base);
         }
 
-        if (loose && add_whitespace) {
-            for (i = 0; i < indent_level; i++) {
-                out.writeString( "  " );
+        if (loose && add_whitespace && indent) {
+            for (gint i = 0; i < indent_level; i++) {
+                for ( gint j = 0 ; j < indent ; j++ ) {
+                    out.writeString(" ");
+                }
             }
         }
         out.printf( "</%s>", element_name );
@@ -794,4 +990,4 @@ sp_repr_write_stream_element (Node * repr, Writer & out, gint indent_level,
   fill-column:99
   End:
 */
-// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :
+// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 :