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)
113 {
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;
171 }
174 int XmlSource::readCb( void * context, char * buffer, int len )
175 {
176 int retVal = -1;
177 if ( context ) {
178 XmlSource* self = static_cast<XmlSource*>(context);
179 retVal = self->read( buffer, len );
180 }
181 return retVal;
182 }
184 int XmlSource::closeCb(void * context)
185 {
186 if ( context ) {
187 XmlSource* self = static_cast<XmlSource*>(context);
188 self->close();
189 }
190 return 0;
191 }
193 int XmlSource::read( char *buffer, int len )
194 {
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;
230 }
232 int XmlSource::close()
233 {
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;
250 }
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)
258 {
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;
313 }
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)
320 {
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;
335 }
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)
342 {
343 return sp_repr_read_mem(buf.c_str(), buf.size(), default_ns);
344 }
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 };
355 }
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 }
377 }
379 }
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 }
395 }
397 }
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)
404 {
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;
454 }
456 gint
457 sp_repr_qualified_name (gchar *p, gint len, xmlNsPtr ns, const xmlChar *name, const gchar */*default_ns*/, GHashTable *prefix_map)
458 {
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 }
475 }
477 static Node *
478 sp_repr_svg_read_node (Document *xml_doc, xmlNodePtr node, const gchar *default_ns, GHashTable *prefix_map)
479 {
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;
543 }
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)
551 {
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 }
579 }
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;
596 }
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)
606 {
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;
616 }
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)
631 {
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;
677 }
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)
684 {
685 return sp_repr_save_rebased_file(doc, filename, default_ns, NULL, NULL);
686 }
689 /* (No doubt this function already exists elsewhere.) */
690 static void
691 repr_quote_write (Writer &out, const gchar * val)
692 {
693 if (val) {
694 for (; *val != '\0'; val++) {
695 switch (*val) {
696 case '"': out.writeString( """ ); break;
697 case '&': out.writeString( "&" ); break;
698 case '<': out.writeString( "<" ); break;
699 case '>': out.writeString( ">" ); break;
700 default: out.writeChar( *val ); break;
701 }
702 }
703 }
704 }
706 static void repr_write_comment( Writer &out, const gchar * val, bool addWhitespace, gint indentLevel, int indent )
707 {
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 }
733 }
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 }
754 }
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 }
775 }
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 }
794 }
796 }
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)
804 {
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);
844 }
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)
851 {
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 }
881 }
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)
892 {
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 }
981 }
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:encoding=utf-8:textwidth=99 :