~valavanisalex/ubuntu/precise/inkscape/fix-943984

« back to all changes in this revision

Viewing changes to inkscape-0.47pre1/src/xml/repr-io.cpp

  • Committer: Bazaar Package Importer
  • Author(s): Bryce Harrington
  • Date: 2009-07-02 17:09:45 UTC
  • mfrom: (1.1.9 upstream)
  • Revision ID: james.westby@ubuntu.com-20090702170945-nn6d6zswovbwju1t
Tags: 0.47~pre1-0ubuntu1
* New upstream release.
  - Don't constrain maximization on small resolution devices (pre0)
    (LP: #348842)
  - Fixes segfault on startup (pre0)
    (LP: #391149)

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
#define __SP_REPR_IO_C__
 
2
 
 
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
 */
 
14
 
 
15
#ifdef HAVE_CONFIG_H
 
16
# include <config.h>
 
17
#endif
 
18
 
 
19
#include <cstring>
 
20
#include <string>
 
21
#include <stdexcept>
 
22
 
 
23
#include "xml/repr.h"
 
24
#include "xml/attribute-record.h"
 
25
#include "xml/rebase-hrefs.h"
 
26
#include "xml/simple-document.h"
 
27
 
 
28
#include "io/sys.h"
 
29
#include "io/uristream.h"
 
30
#include "io/stringstream.h"
 
31
#include "io/gzipstream.h"
 
32
 
 
33
#include "extension/extension.h"
 
34
 
 
35
#include "preferences.h"
 
36
 
 
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;
 
46
 
 
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);
 
62
 
 
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 */
 
67
 
 
68
 
 
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
    }
 
90
 
 
91
    int setFile( char const * filename );
 
92
 
 
93
    static int readCb( void * context, char * buffer, int len );
 
94
    static int closeCb( void * context );
 
95
 
 
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
};
 
109
 
 
110
int XmlSource::setFile(char const *filename)
 
111
{
 
112
    int retVal = -1;
 
113
 
 
114
    this->filename = filename;
 
115
 
 
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) );
 
120
 
 
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);
 
131
 
 
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
            }
 
145
 
 
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
            }
 
157
 
 
158
            if ( encSkip ) {
 
159
                memmove( firstFew, firstFew + encSkip, (some - encSkip) );
 
160
                some -= encSkip;
 
161
            }
 
162
 
 
163
            firstFewLen = some;
 
164
            retVal = 0; // no error
 
165
        }
 
166
    }
 
167
 
 
168
    return retVal;
 
169
}
 
170
 
 
171
 
 
172
int XmlSource::readCb( void * context, char * buffer, int len )
 
173
{
 
174
    int retVal = -1;
 
175
    if ( context ) {
 
176
        XmlSource* self = static_cast<XmlSource*>(context);
 
177
        retVal = self->read( buffer, len );
 
178
    }
 
179
    return retVal;
 
180
}
 
181
 
 
182
int XmlSource::closeCb(void * context)
 
183
{
 
184
    if ( context ) {
 
185
        XmlSource* self = static_cast<XmlSource*>(context);
 
186
        self->close();
 
187
    }
 
188
    return 0;
 
189
}
 
190
 
 
191
int XmlSource::read( char *buffer, int len )
 
192
{
 
193
    int retVal = 0;
 
194
    size_t got = 0;
 
195
 
 
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
    }
 
218
 
 
219
    if ( feof(fp) ) {
 
220
        retVal = got;
 
221
    } else if ( ferror(fp) ) {
 
222
        retVal = -1;
 
223
    } else {
 
224
        retVal = got;
 
225
    }
 
226
 
 
227
    return retVal;
 
228
}
 
229
 
 
230
int XmlSource::close()
 
231
{
 
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;
 
248
}
 
249
 
 
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)
 
256
{
 
257
    xmlDocPtr doc = 0;
 
258
    Document * rdoc = 0;
 
259
 
 
260
    xmlSubstituteEntitiesDefault(1);
 
261
 
 
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. */
 
267
 
 
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 );
 
276
 
 
277
    Inkscape::IO::dump_fopen_call( filename, "N" );
 
278
 
 
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
 
287
 
 
288
    if ( !doc ) {
 
289
        XmlSource src;
 
290
 
 
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 );
 
298
        }
 
299
    }
 
300
 
 
301
    rdoc = sp_repr_do_read( doc, default_ns );
 
302
    if ( doc ) {
 
303
        xmlFreeDoc( doc );
 
304
    }
 
305
 
 
306
    if ( localFilename ) {
 
307
        g_free( localFilename );
 
308
    }
 
309
 
 
310
    return rdoc;
 
311
}
 
312
 
 
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)
 
318
{
 
319
    xmlDocPtr doc;
 
320
    Document * rdoc;
 
321
 
 
322
    xmlSubstituteEntitiesDefault(1);
 
323
 
 
324
    g_return_val_if_fail (buffer != NULL, NULL);
 
325
 
 
326
    doc = xmlParseMemory (const_cast<gchar *>(buffer), length);
 
327
 
 
328
    rdoc = sp_repr_do_read (doc, default_ns);
 
329
    if (doc) {
 
330
        xmlFreeDoc (doc);
 
331
    }
 
332
    return rdoc;
 
333
}
 
334
 
 
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)
 
340
{
 
341
    return sp_repr_read_mem(buf.c_str(), buf.size(), default_ns);
 
342
}
 
343
 
 
344
 
 
345
namespace Inkscape {
 
346
 
 
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
};
 
352
 
 
353
}
 
354
 
 
355
namespace {
 
356
 
 
357
typedef std::map<Glib::QueryQuark, Glib::QueryQuark, Inkscape::compare_quark_ids> PrefixMap;
 
358
 
 
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
    }
 
375
}
 
376
 
 
377
}
 
378
 
 
379
namespace {
 
380
 
 
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
    }
 
393
}
 
394
 
 
395
}
 
396
 
 
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)
 
402
{
 
403
    if (doc == NULL) {
 
404
        return NULL;
 
405
    }
 
406
    xmlNodePtr node=xmlDocGetRootElement (doc);
 
407
    if (node == NULL) {
 
408
        return NULL;
 
409
    }
 
410
 
 
411
    GHashTable * prefix_map;
 
412
    prefix_map = g_hash_table_new (g_str_hash, g_str_equal);
 
413
 
 
414
    Document *rdoc = new Inkscape::XML::SimpleDocument();
 
415
 
 
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);
 
422
 
 
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
    }
 
435
 
 
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
    }
 
448
 
 
449
    g_hash_table_destroy (prefix_map);
 
450
 
 
451
    return rdoc;
 
452
}
 
453
 
 
454
gint
 
455
sp_repr_qualified_name (gchar *p, gint len, xmlNsPtr ns, const xmlChar *name, const gchar */*default_ns*/, GHashTable *prefix_map)
 
456
{
 
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
    }
 
467
 
 
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
    }
 
473
}
 
474
 
 
475
static Node *
 
476
sp_repr_svg_read_node (Document *xml_doc, xmlNodePtr node, const gchar *default_ns, GHashTable *prefix_map)
 
477
{
 
478
    Node *repr, *crepr;
 
479
    xmlAttrPtr prop;
 
480
    xmlNodePtr child;
 
481
    gchar c[256];
 
482
 
 
483
    if (node->type == XML_TEXT_NODE || node->type == XML_CDATA_SECTION_NODE) {
 
484
 
 
485
        if (node->content == NULL || *(node->content) == '\0') {
 
486
            return NULL; // empty text node
 
487
        }
 
488
 
 
489
        bool preserve = (xmlNodeGetSpacePreserve (node) == 1);
 
490
 
 
491
        xmlChar *p;
 
492
        for (p = node->content; *p && g_ascii_isspace (*p) && !preserve; p++)
 
493
            ; // skip all whitespace
 
494
 
 
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
        }
 
498
 
 
499
        return xml_doc->createTextNode(reinterpret_cast<gchar *>(node->content));
 
500
    }
 
501
 
 
502
    if (node->type == XML_COMMENT_NODE) {
 
503
        return xml_doc->createComment(reinterpret_cast<gchar *>(node->content));
 
504
    }
 
505
 
 
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
    }
 
510
 
 
511
    if (node->type == XML_ENTITY_DECL) {
 
512
        return NULL;
 
513
    }
 
514
 
 
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 */
 
518
 
 
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
    }
 
526
 
 
527
    if (node->content) {
 
528
        repr->setContent(reinterpret_cast<gchar*>(node->content));
 
529
    }
 
530
 
 
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
    }
 
539
 
 
540
    return repr;
 
541
}
 
542
 
 
543
 
 
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)
 
549
{
 
550
    Inkscape::Preferences *prefs = Inkscape::Preferences::get();
 
551
    bool inlineattrs = prefs->getBool("/options/svgoutput/inlineattrs");
 
552
    int indent = prefs->getInt("/options/svgoutput/indent", 2);
 
553
 
 
554
    /* fixme: do this The Right Way */
 
555
    out->writeString( "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?>\n" );
 
556
 
 
557
    const gchar *str = static_cast<Node *>(doc)->attribute("doctype");
 
558
    if (str) {
 
559
        out->writeString( str );
 
560
    }
 
561
 
 
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
    }
 
577
}
 
578
 
 
579
 
 
580
 
 
581
 
 
582
Glib::ustring
 
583
sp_repr_save_buf(Document *doc)
 
584
{   
 
585
    Inkscape::IO::StringOutputStream souts;
 
586
    Inkscape::IO::OutputStreamWriter outs(souts);
 
587
 
 
588
    sp_repr_save_writer(doc, &outs, SP_INKSCAPE_NS_URI, 0, 0);
 
589
 
 
590
        outs.close();
 
591
        Glib::ustring buf = souts.getString();
 
592
 
 
593
        return buf;
 
594
}
 
595
 
 
596
 
 
597
 
 
598
 
 
599
 
 
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)
 
604
{
 
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 );
 
609
 
 
610
    sp_repr_save_writer(doc, out, default_ns, old_href_abs_base, new_href_abs_base);
 
611
 
 
612
    delete out;
 
613
    delete gout;
 
614
}
 
615
 
 
616
 
 
617
 
 
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)
 
629
{
 
630
    if (!filename) {
 
631
        return false;
 
632
    }
 
633
 
 
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
    }
 
640
 
 
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
    }
 
646
 
 
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
        }
 
660
 
 
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);
 
666
 
 
667
    g_free(old_href_abs_base);
 
668
    g_free(new_href_abs_base);
 
669
 
 
670
    if (fclose (file) != 0) {
 
671
        return false;
 
672
    }
 
673
 
 
674
    return true;
 
675
}
 
676
 
 
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)
 
682
{
 
683
    return sp_repr_save_rebased_file(doc, filename, default_ns, NULL, NULL);
 
684
}
 
685
 
 
686
 
 
687
/* (No doubt this function already exists elsewhere.) */
 
688
static void
 
689
repr_quote_write (Writer &out, const gchar * val)
 
690
{
 
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
    }
 
702
}
 
703
 
 
704
static void repr_write_comment( Writer &out, const gchar * val, bool addWhitespace, gint indentLevel, int indent )
 
705
{
 
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
    }
 
716
 
 
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("-->");
 
727
 
 
728
    if (addWhitespace) {
 
729
        out.writeString("\n");
 
730
    }
 
731
}
 
732
 
 
733
namespace {
 
734
 
 
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;
 
737
 
 
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
    }
 
752
}
 
753
 
 
754
void add_ns_map_entry(NSMap &ns_map, Glib::QueryQuark prefix) {
 
755
    using Inkscape::Util::ptr_shared;
 
756
    using Inkscape::Util::share_unsafe;
 
757
 
 
758
    static const Glib::QueryQuark xml_prefix("xml");
 
759
 
 
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
    }
 
773
}
 
774
 
 
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
    }
 
792
}
 
793
 
 
794
}
 
795
 
 
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)
 
802
{
 
803
    using Inkscape::Util::ptr_shared;
 
804
 
 
805
    g_assert(repr != NULL);
 
806
    Glib::QueryQuark xml_prefix=g_quark_from_static_string("xml");
 
807
 
 
808
    NSMap ns_map;
 
809
    populate_ns_map(ns_map, *repr);
 
810
 
 
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
    }
 
815
 
 
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;
 
821
 
 
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
                }
 
827
 
 
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
    }
 
839
 
 
840
    return sp_repr_write_stream_element(repr, out, 0, add_whitespace, elide_prefix, attributes,
 
841
                                        inlineattrs, indent, old_href_base, new_href_base);
 
842
}
 
843
 
 
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)
 
849
{
 
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
    }
 
879
}
 
880
 
 
881
 
 
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)
 
890
{
 
891
    Node *child;
 
892
    bool loose;
 
893
 
 
894
    g_return_if_fail (repr != NULL);
 
895
 
 
896
    if ( indent_level > 16 ) {
 
897
        indent_level = 16;
 
898
    }
 
899
 
 
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
    }
 
907
 
 
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 );
 
916
 
 
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
    }
 
923
 
 
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
    }
 
942
 
 
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
        }
 
960
 
 
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
    }
 
972
 
 
973
    // text elements cannot nest, so we can output newline
 
974
    // after closing text
 
975
 
 
976
    if (add_whitespace || !strcmp (repr->name(), "svg:text")) {
 
977
        out.writeString( "\n" );
 
978
    }
 
979
}
 
980
 
 
981
 
 
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:encoding=utf-8:textwidth=99 :