1 /*
2 * Dirty DOM-like tree
3 *
4 * Authors:
5 * Lauris Kaplinski <lauris@kaplinski.com>
6 * bulia byak <buliabyak@users.sf.net>
7 *
8 * Copyright (C) 1999-2002 Lauris Kaplinski
9 *
10 * Released under GNU GPL, read the file 'COPYING' for more information
11 */
13 #ifdef HAVE_CONFIG_H
14 # include <config.h>
15 #endif
17 #include <cstring>
18 #include <string>
19 #include <stdexcept>
21 #include <libxml/parser.h>
23 #include "xml/repr.h"
24 #include "xml/attribute-record.h"
25 #include "xml/rebase-hrefs.h"
26 #include "xml/simple-document.h"
28 #include "io/sys.h"
29 #include "io/uristream.h"
30 #include "io/stringstream.h"
31 #include "io/gzipstream.h"
33 #include "extension/extension.h"
35 #include "preferences.h"
37 using Inkscape::IO::Writer;
38 using Inkscape::Util::List;
39 using Inkscape::Util::cons;
40 using Inkscape::XML::Document;
41 using Inkscape::XML::SimpleDocument;
42 using Inkscape::XML::Node;
43 using Inkscape::XML::AttributeRecord;
44 using Inkscape::XML::calc_abs_doc_base;
45 using Inkscape::XML::rebase_href_attrs;
47 Document *sp_repr_do_read (xmlDocPtr doc, const gchar *default_ns);
48 static Node *sp_repr_svg_read_node (Document *xml_doc, xmlNodePtr node, const gchar *default_ns, GHashTable *prefix_map);
49 static gint sp_repr_qualified_name (gchar *p, gint len, xmlNsPtr ns, const xmlChar *name, const gchar *default_ns, GHashTable *prefix_map);
50 static void sp_repr_write_stream_root_element(Node *repr, Writer &out,
51 bool add_whitespace, gchar const *default_ns,
52 int inlineattrs, int indent,
53 gchar const *old_href_abs_base,
54 gchar const *new_href_abs_base);
55 static void sp_repr_write_stream_element(Node *repr, Writer &out,
56 gint indent_level, bool add_whitespace,
57 Glib::QueryQuark elide_prefix,
58 List<AttributeRecord const> attributes,
59 int inlineattrs, int indent,
60 gchar const *old_href_abs_base,
61 gchar const *new_href_abs_base);
63 #ifdef HAVE_LIBWMF
64 static xmlDocPtr sp_wmf_convert (const char * file_name);
65 static char * sp_wmf_image_name (void * context);
66 #endif /* HAVE_LIBWMF */
69 class XmlSource
70 {
71 public:
72 XmlSource()
73 : filename(0),
74 encoding(0),
75 fp(0),
76 firstFewLen(0),
77 dummy("x"),
78 instr(0),
79 gzin(0)
80 {
81 }
82 virtual ~XmlSource()
83 {
84 close();
85 if ( encoding ) {
86 g_free(encoding);
87 encoding = 0;
88 }
89 }
91 int setFile( char const * filename );
93 static int readCb( void * context, char * buffer, int len );
94 static int closeCb( void * context );
96 char const* getEncoding() const { return encoding; }
97 int read( char * buffer, int len );
98 int close();
99 private:
100 const char* filename;
101 char* encoding;
102 FILE* fp;
103 unsigned char firstFew[4];
104 int firstFewLen;
105 Inkscape::URI dummy;
106 Inkscape::IO::UriInputStream* instr;
107 Inkscape::IO::GzipInputStream* gzin;
108 };
110 int XmlSource::setFile(char const *filename)
111 {
112 int retVal = -1;
114 this->filename = filename;
116 fp = Inkscape::IO::fopen_utf8name(filename, "r");
117 if ( fp ) {
118 // First peek in the file to see what it is
119 memset( firstFew, 0, sizeof(firstFew) );
121 size_t some = fread( firstFew, 1, 4, fp );
122 if ( fp ) {
123 // first check for compression
124 if ( (some >= 2) && (firstFew[0] == 0x1f) && (firstFew[1] == 0x8b) ) {
125 //g_message(" the file being read is gzip'd. extract it");
126 fclose(fp);
127 fp = 0;
128 fp = Inkscape::IO::fopen_utf8name(filename, "r");
129 instr = new Inkscape::IO::UriInputStream(fp, dummy);
130 gzin = new Inkscape::IO::GzipInputStream(*instr);
132 memset( firstFew, 0, sizeof(firstFew) );
133 some = 0;
134 int single = 0;
135 while ( some < 4 && single >= 0 )
136 {
137 single = gzin->get();
138 if ( single >= 0 ) {
139 firstFew[some++] = 0x0ff & single;
140 } else {
141 break;
142 }
143 }
144 }
146 int encSkip = 0;
147 if ( (some >= 2) &&(firstFew[0] == 0xfe) && (firstFew[1] == 0xff) ) {
148 encoding = g_strdup("UTF-16BE");
149 encSkip = 2;
150 } else if ( (some >= 2) && (firstFew[0] == 0xff) && (firstFew[1] == 0xfe) ) {
151 encoding = g_strdup("UTF-16LE");
152 encSkip = 2;
153 } else if ( (some >= 3) && (firstFew[0] == 0xef) && (firstFew[1] == 0xbb) && (firstFew[2] == 0xbf) ) {
154 encoding = g_strdup("UTF-8");
155 encSkip = 3;
156 }
158 if ( encSkip ) {
159 memmove( firstFew, firstFew + encSkip, (some - encSkip) );
160 some -= encSkip;
161 }
163 firstFewLen = some;
164 retVal = 0; // no error
165 }
166 }
168 return retVal;
169 }
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 }
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 }
191 int XmlSource::read( char *buffer, int len )
192 {
193 int retVal = 0;
194 size_t got = 0;
196 if ( firstFewLen > 0 ) {
197 int some = (len < firstFewLen) ? len : firstFewLen;
198 memcpy( buffer, firstFew, some );
199 if ( len < firstFewLen ) {
200 memmove( firstFew, firstFew + some, (firstFewLen - some) );
201 }
202 firstFewLen -= some;
203 got = some;
204 } else if ( gzin ) {
205 int single = 0;
206 while ( (static_cast<int>(got) < len) && (single >= 0) )
207 {
208 single = gzin->get();
209 if ( single >= 0 ) {
210 buffer[got++] = 0x0ff & single;
211 } else {
212 break;
213 }
214 }
215 } else {
216 got = fread( buffer, 1, len, fp );
217 }
219 if ( feof(fp) ) {
220 retVal = got;
221 } else if ( ferror(fp) ) {
222 retVal = -1;
223 } else {
224 retVal = got;
225 }
227 return retVal;
228 }
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 }
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;
260 xmlSubstituteEntitiesDefault(1);
262 g_return_val_if_fail (filename != NULL, NULL);
263 g_return_val_if_fail (Inkscape::IO::file_test( filename, G_FILE_TEST_EXISTS ), NULL);
264 /* fixme: A file can disappear at any time, including between now and when we actually try to
265 * open it. Get rid of the above test once we're sure that we correctly handle
266 * non-existence. */
268 // TODO: bulia, please look over
269 gsize bytesRead = 0;
270 gsize bytesWritten = 0;
271 GError* error = NULL;
272 // TODO: need to replace with our own fopen and reading
273 gchar* localFilename = g_filename_from_utf8 ( filename,
274 -1, &bytesRead, &bytesWritten, &error);
275 g_return_val_if_fail( localFilename != NULL, NULL );
277 Inkscape::IO::dump_fopen_call( filename, "N" );
279 #ifdef HAVE_LIBWMF
280 if (strlen (localFilename) > 4) {
281 if ( (strcmp (localFilename + strlen (localFilename) - 4,".wmf") == 0)
282 || (strcmp (localFilename + strlen (localFilename) - 4,".WMF") == 0)) {
283 doc = sp_wmf_convert (localFilename);
284 }
285 }
286 #endif // !HAVE_LIBWMF
288 if ( !doc ) {
289 XmlSource src;
291 if ( (src.setFile(filename) == 0) ) {
292 doc = xmlReadIO( XmlSource::readCb,
293 XmlSource::closeCb,
294 &src,
295 localFilename,
296 src.getEncoding(),
297 XML_PARSE_NOENT | XML_PARSE_HUGE);
298 }
299 }
301 rdoc = sp_repr_do_read( doc, default_ns );
302 if ( doc ) {
303 xmlFreeDoc( doc );
304 }
306 if ( localFilename ) {
307 g_free( localFilename );
308 }
310 return rdoc;
311 }
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;
322 xmlSubstituteEntitiesDefault(1);
324 g_return_val_if_fail (buffer != NULL, NULL);
326 doc = xmlParseMemory (const_cast<gchar *>(buffer), length);
328 rdoc = sp_repr_do_read (doc, default_ns);
329 if (doc) {
330 xmlFreeDoc (doc);
331 }
332 return rdoc;
333 }
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 }
345 namespace Inkscape {
347 struct compare_quark_ids {
348 bool operator()(Glib::QueryQuark const &a, Glib::QueryQuark const &b) const {
349 return a.id() < b.id();
350 }
351 };
353 }
355 namespace {
357 typedef std::map<Glib::QueryQuark, Glib::QueryQuark, Inkscape::compare_quark_ids> PrefixMap;
359 Glib::QueryQuark qname_prefix(Glib::QueryQuark qname) {
360 static PrefixMap prefix_map;
361 PrefixMap::iterator iter = prefix_map.find(qname);
362 if ( iter != prefix_map.end() ) {
363 return (*iter).second;
364 } else {
365 gchar const *name_string=g_quark_to_string(qname);
366 gchar const *prefix_end=strchr(name_string, ':');
367 if (prefix_end) {
368 Glib::Quark prefix=Glib::ustring(name_string, prefix_end);
369 prefix_map.insert(PrefixMap::value_type(qname, prefix));
370 return prefix;
371 } else {
372 return GQuark(0);
373 }
374 }
375 }
377 }
379 namespace {
381 void promote_to_namespace(Node *repr, const gchar *prefix) {
382 if ( repr->type() == Inkscape::XML::ELEMENT_NODE ) {
383 GQuark code = repr->code();
384 if (!qname_prefix(code).id()) {
385 gchar *svg_name = g_strconcat(prefix, ":", g_quark_to_string(code), NULL);
386 repr->setCodeUnsafe(g_quark_from_string(svg_name));
387 g_free(svg_name);
388 }
389 for ( Node *child = sp_repr_children(repr) ; child ; child = sp_repr_next(child) ) {
390 promote_to_namespace(child, prefix);
391 }
392 }
393 }
395 }
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 }
411 GHashTable * prefix_map;
412 prefix_map = g_hash_table_new (g_str_hash, g_str_equal);
414 Document *rdoc = new Inkscape::XML::SimpleDocument();
416 Node *root=NULL;
417 for ( node = doc->children ; node != NULL ; node = node->next ) {
418 if (node->type == XML_ELEMENT_NODE) {
419 Node *repr=sp_repr_svg_read_node(rdoc, node, default_ns, prefix_map);
420 rdoc->appendChild(repr);
421 Inkscape::GC::release(repr);
423 if (!root) {
424 root = repr;
425 } else {
426 root = NULL;
427 break;
428 }
429 } else if ( node->type == XML_COMMENT_NODE || node->type == XML_PI_NODE ) {
430 Node *repr=sp_repr_svg_read_node(rdoc, node, default_ns, prefix_map);
431 rdoc->appendChild(repr);
432 Inkscape::GC::release(repr);
433 }
434 }
436 if (root != NULL) {
437 /* promote elements of some XML documents that don't use namespaces
438 * into their default namespace */
439 if ( default_ns && !strchr(root->name(), ':') ) {
440 if ( !strcmp(default_ns, SP_SVG_NS_URI) ) {
441 promote_to_namespace(root, "svg");
442 }
443 if ( !strcmp(default_ns, INKSCAPE_EXTENSION_URI) ) {
444 promote_to_namespace(root, INKSCAPE_EXTENSION_NS_NC);
445 }
446 }
447 }
449 g_hash_table_destroy (prefix_map);
451 return rdoc;
452 }
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 }
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 }
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];
483 if (node->type == XML_TEXT_NODE || node->type == XML_CDATA_SECTION_NODE) {
485 if (node->content == NULL || *(node->content) == '\0') {
486 return NULL; // empty text node
487 }
489 bool preserve = (xmlNodeGetSpacePreserve (node) == 1);
491 xmlChar *p;
492 for (p = node->content; *p && g_ascii_isspace (*p) && !preserve; p++)
493 ; // skip all whitespace
495 if (!(*p)) { // this is an all-whitespace node, and preserve == default
496 return NULL; // we do not preserve all-whitespace nodes unless we are asked to
497 }
499 return xml_doc->createTextNode(reinterpret_cast<gchar *>(node->content));
500 }
502 if (node->type == XML_COMMENT_NODE) {
503 return xml_doc->createComment(reinterpret_cast<gchar *>(node->content));
504 }
506 if (node->type == XML_PI_NODE) {
507 return xml_doc->createPI(reinterpret_cast<const gchar *>(node->name),
508 reinterpret_cast<const gchar *>(node->content));
509 }
511 if (node->type == XML_ENTITY_DECL) {
512 return NULL;
513 }
515 sp_repr_qualified_name (c, 256, node->ns, node->name, default_ns, prefix_map);
516 repr = xml_doc->createElement(c);
517 /* TODO remember node->ns->prefix if node->ns != NULL */
519 for (prop = node->properties; prop != NULL; prop = prop->next) {
520 if (prop->children) {
521 sp_repr_qualified_name (c, 256, prop->ns, prop->name, default_ns, prefix_map);
522 repr->setAttribute(c, reinterpret_cast<gchar*>(prop->children->content));
523 /* TODO remember prop->ns->prefix if prop->ns != NULL */
524 }
525 }
527 if (node->content) {
528 repr->setContent(reinterpret_cast<gchar*>(node->content));
529 }
531 child = node->xmlChildrenNode;
532 for (child = node->xmlChildrenNode; child != NULL; child = child->next) {
533 crepr = sp_repr_svg_read_node (xml_doc, child, default_ns, prefix_map);
534 if (crepr) {
535 repr->appendChild(crepr);
536 Inkscape::GC::release(crepr);
537 }
538 }
540 return repr;
541 }
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);
554 /* fixme: do this The Right Way */
555 out->writeString( "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?>\n" );
557 const gchar *str = static_cast<Node *>(doc)->attribute("doctype");
558 if (str) {
559 out->writeString( str );
560 }
562 for (Node *repr = sp_repr_document_first_child(doc);
563 repr; repr = sp_repr_next(repr))
564 {
565 Inkscape::XML::NodeType const node_type = repr->type();
566 if ( node_type == Inkscape::XML::ELEMENT_NODE ) {
567 sp_repr_write_stream_root_element(repr, *out, TRUE, default_ns, inlineattrs, indent,
568 old_href_abs_base, new_href_abs_base);
569 } else {
570 sp_repr_write_stream(repr, *out, 0, TRUE, GQuark(0), inlineattrs, indent,
571 old_href_abs_base, new_href_abs_base);
572 if ( node_type == Inkscape::XML::COMMENT_NODE ) {
573 out->writeChar('\n');
574 }
575 }
576 }
577 }
582 Glib::ustring
583 sp_repr_save_buf(Document *doc)
584 {
585 Inkscape::IO::StringOutputStream souts;
586 Inkscape::IO::OutputStreamWriter outs(souts);
588 sp_repr_save_writer(doc, &outs, SP_INKSCAPE_NS_URI, 0, 0);
590 outs.close();
591 Glib::ustring buf = souts.getString();
593 return buf;
594 }
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 );
610 sp_repr_save_writer(doc, out, default_ns, old_href_abs_base, new_href_abs_base);
612 delete out;
613 delete gout;
614 }
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 }
634 bool compress;
635 {
636 size_t const filename_len = strlen(filename);
637 compress = ( filename_len > 5
638 && strcasecmp(".svgz", filename + filename_len - 5) == 0 );
639 }
641 Inkscape::IO::dump_fopen_call( filename, "B" );
642 FILE *file = Inkscape::IO::fopen_utf8name(filename, "w");
643 if (file == NULL) {
644 return false;
645 }
647 gchar *old_href_abs_base = NULL;
648 gchar *new_href_abs_base = NULL;
649 if (for_filename) {
650 old_href_abs_base = calc_abs_doc_base(old_base);
651 if (g_path_is_absolute(for_filename)) {
652 new_href_abs_base = g_path_get_dirname(for_filename);
653 } else {
654 gchar *const cwd = g_get_current_dir();
655 gchar *const for_abs_filename = g_build_filename(cwd, for_filename, NULL);
656 g_free(cwd);
657 new_href_abs_base = g_path_get_dirname(for_abs_filename);
658 g_free(for_abs_filename);
659 }
661 /* effic: Once we're confident that we never need (or never want) to resort
662 * to using sodipodi:absref instead of the xlink:href value,
663 * then we should do `if streq() { free them and set both to NULL; }'. */
664 }
665 sp_repr_save_stream(doc, file, default_ns, compress, old_href_abs_base, new_href_abs_base);
667 g_free(old_href_abs_base);
668 g_free(new_href_abs_base);
670 if (fclose (file) != 0) {
671 return false;
672 }
674 return true;
675 }
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 }
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( """ ); break;
695 case '&': out.writeString( "&" ); break;
696 case '<': out.writeString( "<" ); break;
697 case '>': out.writeString( ">" ); break;
698 default: out.writeChar( *val ); break;
699 }
700 }
701 }
702 }
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 }
717 out.writeString("<!--");
718 // WARNING out.printf() and out.writeString() are *NOT* non-ASCII friendly.
719 if (val) {
720 for (const gchar* cur = val; *cur; cur++ ) {
721 out.writeChar(*cur);
722 }
723 } else {
724 out.writeString(" ");
725 }
726 out.writeString("-->");
728 if (addWhitespace) {
729 out.writeString("\n");
730 }
731 }
733 namespace {
735 typedef std::map<Glib::QueryQuark, gchar const *, Inkscape::compare_quark_ids> LocalNameMap;
736 typedef std::map<Glib::QueryQuark, Inkscape::Util::ptr_shared<char>, Inkscape::compare_quark_ids> NSMap;
738 gchar const *qname_local_name(Glib::QueryQuark qname) {
739 static LocalNameMap local_name_map;
740 LocalNameMap::iterator iter = local_name_map.find(qname);
741 if ( iter != local_name_map.end() ) {
742 return (*iter).second;
743 } else {
744 gchar const *name_string=g_quark_to_string(qname);
745 gchar const *prefix_end=strchr(name_string, ':');
746 if (prefix_end) {
747 return prefix_end + 1;
748 } else {
749 return name_string;
750 }
751 }
752 }
754 void add_ns_map_entry(NSMap &ns_map, Glib::QueryQuark prefix) {
755 using Inkscape::Util::ptr_shared;
756 using Inkscape::Util::share_unsafe;
758 static const Glib::QueryQuark xml_prefix("xml");
760 NSMap::iterator iter=ns_map.find(prefix);
761 if ( iter == ns_map.end() ) {
762 if (prefix.id()) {
763 gchar const *uri=sp_xml_ns_prefix_uri(g_quark_to_string(prefix));
764 if (uri) {
765 ns_map.insert(NSMap::value_type(prefix, share_unsafe(uri)));
766 } else if ( prefix != xml_prefix ) {
767 g_warning("No namespace known for normalized prefix %s", g_quark_to_string(prefix));
768 }
769 } else {
770 ns_map.insert(NSMap::value_type(prefix, ptr_shared<char>()));
771 }
772 }
773 }
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 }
794 }
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;
805 g_assert(repr != NULL);
806 Glib::QueryQuark xml_prefix=g_quark_from_static_string("xml");
808 NSMap ns_map;
809 populate_ns_map(ns_map, *repr);
811 Glib::QueryQuark elide_prefix=GQuark(0);
812 if ( default_ns && ns_map.find(GQuark(0)) == ns_map.end() ) {
813 elide_prefix = g_quark_from_string(sp_xml_ns_uri_prefix(default_ns, NULL));
814 }
816 List<AttributeRecord const> attributes=repr->attributeList();
817 for ( NSMap::iterator iter=ns_map.begin() ; iter != ns_map.end() ; ++iter )
818 {
819 Glib::QueryQuark prefix=(*iter).first;
820 ptr_shared<char> ns_uri=(*iter).second;
822 if (prefix.id()) {
823 if ( prefix != xml_prefix ) {
824 if ( elide_prefix == prefix ) {
825 attributes = cons(AttributeRecord(g_quark_from_static_string("xmlns"), ns_uri), attributes);
826 }
828 Glib::ustring attr_name="xmlns:";
829 attr_name.append(g_quark_to_string(prefix));
830 GQuark key = g_quark_from_string(attr_name.c_str());
831 attributes = cons(AttributeRecord(key, ns_uri), attributes);
832 }
833 } else {
834 // if there are non-namespaced elements, we can't globally
835 // use a default namespace
836 elide_prefix = GQuark(0);
837 }
838 }
840 return sp_repr_write_stream_element(repr, out, 0, add_whitespace, elide_prefix, attributes,
841 inlineattrs, indent, old_href_base, new_href_base);
842 }
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 }
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;
894 g_return_if_fail (repr != NULL);
896 if ( indent_level > 16 ) {
897 indent_level = 16;
898 }
900 if (add_whitespace && indent) {
901 for (gint i = 0; i < indent_level; i++) {
902 for (gint j = 0; j < indent; j++) {
903 out.writeString(" ");
904 }
905 }
906 }
908 GQuark code = repr->code();
909 gchar const *element_name;
910 if ( elide_prefix == qname_prefix(code) ) {
911 element_name = qname_local_name(code);
912 } else {
913 element_name = g_quark_to_string(code);
914 }
915 out.printf( "<%s", element_name );
917 // if this is a <text> element, suppress formatting whitespace
918 // for its content and children:
919 gchar const *xml_space_attr = repr->attribute("xml:space");
920 if (xml_space_attr != NULL && !strcmp(xml_space_attr, "preserve")) {
921 add_whitespace = false;
922 }
924 for ( List<AttributeRecord const> iter = rebase_href_attrs(old_href_base, new_href_base,
925 attributes);
926 iter ; ++iter )
927 {
928 if (!inlineattrs) {
929 out.writeString("\n");
930 if (indent) {
931 for ( gint i = 0 ; i < indent_level + 1 ; i++ ) {
932 for ( gint j = 0 ; j < indent ; j++ ) {
933 out.writeString(" ");
934 }
935 }
936 }
937 }
938 out.printf(" %s=\"", g_quark_to_string(iter->key));
939 repr_quote_write(out, iter->value);
940 out.writeChar('"');
941 }
943 loose = TRUE;
944 for (child = repr->firstChild() ; child != NULL; child = child->next()) {
945 if (child->type() == Inkscape::XML::TEXT_NODE) {
946 loose = FALSE;
947 break;
948 }
949 }
950 if (repr->firstChild()) {
951 out.writeString( ">" );
952 if (loose && add_whitespace) {
953 out.writeString( "\n" );
954 }
955 for (child = repr->firstChild(); child != NULL; child = child->next()) {
956 sp_repr_write_stream(child, out, ( loose ? indent_level + 1 : 0 ),
957 add_whitespace, elide_prefix, inlineattrs, indent,
958 old_href_base, new_href_base);
959 }
961 if (loose && add_whitespace && indent) {
962 for (gint i = 0; i < indent_level; i++) {
963 for ( gint j = 0 ; j < indent ; j++ ) {
964 out.writeString(" ");
965 }
966 }
967 }
968 out.printf( "</%s>", element_name );
969 } else {
970 out.writeString( " />" );
971 }
973 // text elements cannot nest, so we can output newline
974 // after closing text
976 if (add_whitespace || !strcmp (repr->name(), "svg:text")) {
977 out.writeString( "\n" );
978 }
979 }
982 /*
983 Local Variables:
984 mode:c++
985 c-file-style:"stroustrup"
986 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
987 indent-tabs-mode:nil
988 fill-column:99
989 End:
990 */
991 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 :