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 "xml/repr.h"
24 #include "xml/attribute-record.h"
25 #include "xml/simple-document.h"
27 #include "io/sys.h"
28 #include "io/uristream.h"
29 #include "io/stringstream.h"
30 #include "io/gzipstream.h"
32 #include "prefs-utils.h"
34 using Inkscape::IO::Writer;
35 using Inkscape::Util::List;
36 using Inkscape::Util::cons;
37 using Inkscape::XML::Document;
38 using Inkscape::XML::SimpleDocument;
39 using Inkscape::XML::Node;
40 using Inkscape::XML::AttributeRecord;
42 Document *sp_repr_do_read (xmlDocPtr doc, const gchar *default_ns);
43 static Node *sp_repr_svg_read_node (Document *xml_doc, xmlNodePtr node, const gchar *default_ns, GHashTable *prefix_map);
44 static gint sp_repr_qualified_name (gchar *p, gint len, xmlNsPtr ns, const xmlChar *name, const gchar *default_ns, GHashTable *prefix_map);
45 static void sp_repr_write_stream_root_element (Node *repr, Writer &out, bool add_whitespace, gchar const *default_ns, int inlineattrs, int indent);
46 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);
48 #ifdef HAVE_LIBWMF
49 static xmlDocPtr sp_wmf_convert (const char * file_name);
50 static char * sp_wmf_image_name (void * context);
51 #endif /* HAVE_LIBWMF */
54 class XmlSource
55 {
56 public:
57 XmlSource()
58 : filename(0),
59 encoding(0),
60 fp(0),
61 firstFewLen(0),
62 dummy("x"),
63 instr(0),
64 gzin(0)
65 {
66 }
67 virtual ~XmlSource()
68 {
69 close();
70 if ( encoding ) {
71 g_free(encoding);
72 encoding = 0;
73 }
74 }
76 int setFile( char const * filename );
78 static int readCb( void * context, char * buffer, int len );
79 static int closeCb( void * context );
81 char const* getEncoding() const { return encoding; }
82 int read( char * buffer, int len );
83 int close();
84 private:
85 const char* filename;
86 char* encoding;
87 FILE* fp;
88 unsigned char firstFew[4];
89 int firstFewLen;
90 Inkscape::URI dummy;
91 Inkscape::IO::UriInputStream* instr;
92 Inkscape::IO::GzipInputStream* gzin;
93 };
95 int XmlSource::setFile(char const *filename)
96 {
97 int retVal = -1;
99 this->filename = filename;
101 fp = Inkscape::IO::fopen_utf8name(filename, "r");
102 if ( fp ) {
103 // First peek in the file to see what it is
104 memset( firstFew, 0, sizeof(firstFew) );
106 size_t some = fread( firstFew, 1, 4, fp );
107 if ( fp ) {
108 // first check for compression
109 if ( (some >= 2) && (firstFew[0] == 0x1f) && (firstFew[1] == 0x8b) ) {
110 //g_message(" the file being read is gzip'd. extract it");
111 fclose(fp);
112 fp = 0;
113 fp = Inkscape::IO::fopen_utf8name(filename, "r");
114 instr = new Inkscape::IO::UriInputStream(fp, dummy);
115 gzin = new Inkscape::IO::GzipInputStream(*instr);
117 memset( firstFew, 0, sizeof(firstFew) );
118 some = 0;
119 int single = 0;
120 while ( some < 4 && single >= 0 )
121 {
122 single = gzin->get();
123 if ( single >= 0 ) {
124 firstFew[some++] = 0x0ff & single;
125 } else {
126 break;
127 }
128 }
129 }
131 int encSkip = 0;
132 if ( (some >= 2) &&(firstFew[0] == 0xfe) && (firstFew[1] == 0xff) ) {
133 encoding = g_strdup("UTF-16BE");
134 encSkip = 2;
135 } else if ( (some >= 2) && (firstFew[0] == 0xff) && (firstFew[1] == 0xfe) ) {
136 encoding = g_strdup("UTF-16LE");
137 encSkip = 2;
138 } else if ( (some >= 3) && (firstFew[0] == 0xef) && (firstFew[1] == 0xbb) && (firstFew[2] == 0xbf) ) {
139 encoding = g_strdup("UTF-8");
140 encSkip = 3;
141 }
143 if ( encSkip ) {
144 memmove( firstFew, firstFew + encSkip, (some - encSkip) );
145 some -= encSkip;
146 }
148 firstFewLen = some;
149 retVal = 0; // no error
150 }
151 }
153 return retVal;
154 }
157 int XmlSource::readCb( void * context, char * buffer, int len )
158 {
159 int retVal = -1;
160 if ( context ) {
161 XmlSource* self = static_cast<XmlSource*>(context);
162 retVal = self->read( buffer, len );
163 }
164 return retVal;
165 }
167 int XmlSource::closeCb(void * context)
168 {
169 if ( context ) {
170 XmlSource* self = static_cast<XmlSource*>(context);
171 self->close();
172 }
173 return 0;
174 }
176 int XmlSource::read( char *buffer, int len )
177 {
178 int retVal = 0;
179 size_t got = 0;
181 if ( firstFewLen > 0 ) {
182 int some = (len < firstFewLen) ? len : firstFewLen;
183 memcpy( buffer, firstFew, some );
184 if ( len < firstFewLen ) {
185 memmove( firstFew, firstFew + some, (firstFewLen - some) );
186 }
187 firstFewLen -= some;
188 got = some;
189 } else if ( gzin ) {
190 int single = 0;
191 while ( (int)got < len && single >= 0 )
192 {
193 single = gzin->get();
194 if ( single >= 0 ) {
195 buffer[got++] = 0x0ff & single;
196 } else {
197 break;
198 }
199 }
200 } else {
201 got = fread( buffer, 1, len, fp );
202 }
204 if ( feof(fp) ) {
205 retVal = got;
206 } else if ( ferror(fp) ) {
207 retVal = -1;
208 } else {
209 retVal = got;
210 }
212 return retVal;
213 }
215 int XmlSource::close()
216 {
217 if ( gzin ) {
218 gzin->close();
219 delete gzin;
220 gzin = 0;
221 }
222 if ( instr ) {
223 instr->close();
224 fp = 0;
225 delete instr;
226 instr = 0;
227 }
228 if ( fp ) {
229 fclose(fp);
230 fp = 0;
231 }
232 return 0;
233 }
235 /**
236 * Reads XML from a file, including WMF files, and returns the Document.
237 * The default namespace can also be specified, if desired.
238 */
239 Document *
240 sp_repr_read_file (const gchar * filename, const gchar *default_ns)
241 {
242 xmlDocPtr doc = 0;
243 Document * rdoc = 0;
245 xmlSubstituteEntitiesDefault(1);
247 g_return_val_if_fail (filename != NULL, NULL);
248 g_return_val_if_fail (Inkscape::IO::file_test( filename, G_FILE_TEST_EXISTS ), NULL);
249 /* fixme: A file can disappear at any time, including between now and when we actually try to
250 * open it. Get rid of the above test once we're sure that we correctly handle
251 * non-existence. */
253 // TODO: bulia, please look over
254 gsize bytesRead = 0;
255 gsize bytesWritten = 0;
256 GError* error = NULL;
257 // TODO: need to replace with our own fopen and reading
258 gchar* localFilename = g_filename_from_utf8 ( filename,
259 -1, &bytesRead, &bytesWritten, &error);
260 g_return_val_if_fail( localFilename != NULL, NULL );
262 Inkscape::IO::dump_fopen_call( filename, "N" );
264 #ifdef HAVE_LIBWMF
265 if (strlen (localFilename) > 4) {
266 if ( (strcmp (localFilename + strlen (localFilename) - 4,".wmf") == 0)
267 || (strcmp (localFilename + strlen (localFilename) - 4,".WMF") == 0)) {
268 doc = sp_wmf_convert (localFilename);
269 }
270 }
271 #endif // !HAVE_LIBWMF
273 if ( !doc ) {
274 XmlSource src;
276 if ( (src.setFile(filename) == 0) ) {
277 doc = xmlReadIO( XmlSource::readCb,
278 XmlSource::closeCb,
279 &src,
280 localFilename,
281 src.getEncoding(),
282 XML_PARSE_NOENT );
283 }
284 }
286 rdoc = sp_repr_do_read( doc, default_ns );
287 if ( doc ) {
288 xmlFreeDoc( doc );
289 }
291 if ( localFilename ) {
292 g_free( localFilename );
293 }
295 return rdoc;
296 }
298 /**
299 * Reads and parses XML from a buffer, returning it as an Document
300 */
301 Document *
302 sp_repr_read_mem (const gchar * buffer, gint length, const gchar *default_ns)
303 {
304 xmlDocPtr doc;
305 Document * rdoc;
307 xmlSubstituteEntitiesDefault(1);
309 g_return_val_if_fail (buffer != NULL, NULL);
311 doc = xmlParseMemory ((gchar *) buffer, length);
313 rdoc = sp_repr_do_read (doc, default_ns);
314 if (doc)
315 xmlFreeDoc (doc);
316 return rdoc;
317 }
319 /**
320 * Reads and parses XML from a buffer, returning it as an Document
321 */
322 Document *
323 sp_repr_read_buf (const Glib::ustring &buf, const gchar *default_ns)
324 {
325 return sp_repr_read_mem(buf.c_str(), buf.size(), default_ns);
326 }
329 namespace Inkscape {
331 struct compare_quark_ids {
332 bool operator()(Glib::QueryQuark const &a, Glib::QueryQuark const &b) const {
333 return a.id() < b.id();
334 }
335 };
337 }
339 namespace {
341 typedef std::map<Glib::QueryQuark, Glib::QueryQuark, Inkscape::compare_quark_ids> PrefixMap;
343 Glib::QueryQuark qname_prefix(Glib::QueryQuark qname) {
344 static PrefixMap prefix_map;
345 PrefixMap::iterator iter = prefix_map.find(qname);
346 if ( iter != prefix_map.end() ) {
347 return (*iter).second;
348 } else {
349 gchar const *name_string=g_quark_to_string(qname);
350 gchar const *prefix_end=strchr(name_string, ':');
351 if (prefix_end) {
352 Glib::Quark prefix=Glib::ustring(name_string, prefix_end);
353 prefix_map.insert(PrefixMap::value_type(qname, prefix));
354 return prefix;
355 } else {
356 return GQuark(0);
357 }
358 }
359 }
361 }
363 namespace {
365 void promote_to_svg_namespace(Node *repr) {
366 if ( repr->type() == Inkscape::XML::ELEMENT_NODE ) {
367 GQuark code = repr->code();
368 if (!qname_prefix(code).id()) {
369 gchar *svg_name = g_strconcat("svg:", g_quark_to_string(code), NULL);
370 repr->setCodeUnsafe(g_quark_from_string(svg_name));
371 g_free(svg_name);
372 }
373 for ( Node *child = sp_repr_children(repr) ; child ; child = sp_repr_next(child) ) {
374 promote_to_svg_namespace(child);
375 }
376 }
377 }
379 }
381 /**
382 * Reads in a XML file to create a Document
383 */
384 Document *
385 sp_repr_do_read (xmlDocPtr doc, const gchar *default_ns)
386 {
387 if (doc == NULL) return NULL;
388 xmlNodePtr node=xmlDocGetRootElement (doc);
389 if (node == NULL) return NULL;
391 GHashTable * prefix_map;
392 prefix_map = g_hash_table_new (g_str_hash, g_str_equal);
394 Document *rdoc = new Inkscape::XML::SimpleDocument();
396 Node *root=NULL;
397 for ( node = doc->children ; node != NULL ; node = node->next ) {
398 if (node->type == XML_ELEMENT_NODE) {
399 Node *repr=sp_repr_svg_read_node(rdoc, node, default_ns, prefix_map);
400 rdoc->appendChild(repr);
401 Inkscape::GC::release(repr);
403 if (!root) {
404 root = repr;
405 } else {
406 root = NULL;
407 break;
408 }
409 } else if ( node->type == XML_COMMENT_NODE || node->type == XML_PI_NODE ) {
410 Node *repr=sp_repr_svg_read_node(rdoc, node, default_ns, prefix_map);
411 rdoc->appendChild(repr);
412 Inkscape::GC::release(repr);
413 }
414 }
416 if (root != NULL) {
417 /* promote elements of SVG documents that don't use namespaces
418 * into the SVG namespace */
419 if ( default_ns && !strcmp(default_ns, SP_SVG_NS_URI)
420 && !strcmp(root->name(), "svg") )
421 {
422 promote_to_svg_namespace(root);
423 }
424 }
426 g_hash_table_destroy (prefix_map);
428 return rdoc;
429 }
431 gint
432 sp_repr_qualified_name (gchar *p, gint len, xmlNsPtr ns, const xmlChar *name, const gchar *default_ns, GHashTable *prefix_map)
433 {
434 const xmlChar *prefix;
435 if ( ns && ns->href ) {
436 prefix = (xmlChar*)sp_xml_ns_uri_prefix ((gchar*)ns->href, (char*)ns->prefix);
437 g_hash_table_insert (prefix_map, (gpointer)prefix, (gpointer)ns->href);
438 } else {
439 prefix = NULL;
440 }
442 if (prefix)
443 return g_snprintf (p, len, "%s:%s", (gchar*)prefix, name);
444 else
445 return g_snprintf (p, len, "%s", name);
446 }
448 static Node *
449 sp_repr_svg_read_node (Document *xml_doc, xmlNodePtr node, const gchar *default_ns, GHashTable *prefix_map)
450 {
451 Node *repr, *crepr;
452 xmlAttrPtr prop;
453 xmlNodePtr child;
454 gchar c[256];
456 if (node->type == XML_TEXT_NODE || node->type == XML_CDATA_SECTION_NODE) {
458 if (node->content == NULL || *(node->content) == '\0')
459 return NULL; // empty text node
461 bool preserve = (xmlNodeGetSpacePreserve (node) == 1);
463 xmlChar *p;
464 for (p = node->content; *p && g_ascii_isspace (*p) && !preserve; p++)
465 ; // skip all whitespace
467 if (!(*p)) { // this is an all-whitespace node, and preserve == default
468 return NULL; // we do not preserve all-whitespace nodes unless we are asked to
469 }
471 return xml_doc->createTextNode((const gchar *)node->content);
472 }
474 if (node->type == XML_COMMENT_NODE)
475 return xml_doc->createComment((const gchar *)node->content);
477 if (node->type == XML_PI_NODE)
478 return xml_doc->createPI((const gchar *)node->name, (const gchar *)node->content);
480 if (node->type == XML_ENTITY_DECL) return NULL;
482 sp_repr_qualified_name (c, 256, node->ns, node->name, default_ns, prefix_map);
483 repr = xml_doc->createElement(c);
484 /* TODO remember node->ns->prefix if node->ns != NULL */
486 for (prop = node->properties; prop != NULL; prop = prop->next) {
487 if (prop->children) {
488 sp_repr_qualified_name (c, 256, prop->ns, prop->name, default_ns, prefix_map);
489 repr->setAttribute(c, (gchar*)prop->children->content);
490 /* TODO remember prop->ns->prefix if prop->ns != NULL */
491 }
492 }
494 if (node->content)
495 repr->setContent((gchar*)node->content);
497 child = node->xmlChildrenNode;
498 for (child = node->xmlChildrenNode; child != NULL; child = child->next) {
499 crepr = sp_repr_svg_read_node (xml_doc, child, default_ns, prefix_map);
500 if (crepr) {
501 repr->appendChild(crepr);
502 Inkscape::GC::release(crepr);
503 }
504 }
506 return repr;
507 }
509 void
510 sp_repr_save_stream (Document *doc, FILE *fp, gchar const *default_ns, bool compress)
511 {
512 Node *repr;
513 const gchar *str;
515 Inkscape::URI dummy("x");
516 Inkscape::IO::UriOutputStream bout(fp, dummy);
517 Inkscape::IO::GzipOutputStream *gout = compress ? new Inkscape::IO::GzipOutputStream(bout) : NULL;
518 Inkscape::IO::OutputStreamWriter *out = compress ? new Inkscape::IO::OutputStreamWriter( *gout ) : new Inkscape::IO::OutputStreamWriter( bout );
520 int inlineattrs = prefs_get_int_attribute("options.svgoutput", "inlineattrs", 0);
521 int indent = prefs_get_int_attribute("options.svgoutput", "indent", 2);
523 /* fixme: do this The Right Way */
524 out->writeString( "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?>\n" );
526 str = ((Node *)doc)->attribute("doctype");
527 if (str) {
528 out->writeString( str );
529 }
531 repr = sp_repr_document_first_child(doc);
532 for ( repr = sp_repr_document_first_child(doc) ;
533 repr ; repr = sp_repr_next(repr) )
534 {
535 if ( repr->type() == Inkscape::XML::ELEMENT_NODE ) {
536 sp_repr_write_stream_root_element(repr, *out, TRUE, default_ns, inlineattrs, indent);
537 } else if ( repr->type() == Inkscape::XML::COMMENT_NODE ) {
538 sp_repr_write_stream(repr, *out, 0, TRUE, GQuark(0), inlineattrs, indent);
539 out->writeChar( '\n' );
540 } else {
541 sp_repr_write_stream(repr, *out, 0, TRUE, GQuark(0), inlineattrs, indent);
542 }
543 }
544 if ( out ) {
545 delete out;
546 out = NULL;
547 }
548 if ( gout ) {
549 delete gout;
550 gout = NULL;
551 }
552 }
554 /* Returns TRUE if file successfully saved; FALSE if not
555 */
556 bool
557 sp_repr_save_file (Document *doc, const gchar *filename,
558 gchar const *default_ns)
559 {
560 if (filename == NULL) {
561 return FALSE;
562 }
563 bool compress = false;
564 {
565 if (strlen (filename) > 5) {
566 gchar tmp[] = {0,0,0,0,0,0};
567 strncpy( tmp, filename + strlen (filename) - 5, 6 );
568 tmp[5] = 0;
569 if ( strcasecmp(".svgz", tmp ) == 0 )
570 {
571 //g_message("TIME TO COMPRESS THE OUTPUT FOR SVGZ");
572 compress = true;
573 }
574 }
575 }
577 Inkscape::IO::dump_fopen_call( filename, "B" );
578 FILE *file = Inkscape::IO::fopen_utf8name(filename, "w");
579 if (file == NULL) {
580 return FALSE;
581 }
583 sp_repr_save_stream (doc, file, default_ns, compress);
585 if (fclose (file) != 0) {
586 return FALSE;
587 }
589 return TRUE;
590 }
592 void
593 sp_repr_print (Node * repr)
594 {
595 Inkscape::IO::StdOutputStream bout;
596 Inkscape::IO::OutputStreamWriter out(bout);
598 sp_repr_write_stream (repr, out, 0, TRUE, GQuark(0), 0, 2);
600 return;
601 }
603 /* (No doubt this function already exists elsewhere.) */
604 static void
605 repr_quote_write (Writer &out, const gchar * val)
606 {
607 if (!val) return;
609 for (; *val != '\0'; val++) {
610 switch (*val) {
611 case '"': out.writeString( """ ); break;
612 case '&': out.writeString( "&" ); break;
613 case '<': out.writeString( "<" ); break;
614 case '>': out.writeString( ">" ); break;
615 default: out.writeChar( *val ); break;
616 }
617 }
618 }
620 namespace {
622 typedef std::map<Glib::QueryQuark, gchar const *, Inkscape::compare_quark_ids> LocalNameMap;
623 typedef std::map<Glib::QueryQuark, Inkscape::Util::ptr_shared<char>, Inkscape::compare_quark_ids> NSMap;
625 gchar const *qname_local_name(Glib::QueryQuark qname) {
626 static LocalNameMap local_name_map;
627 LocalNameMap::iterator iter = local_name_map.find(qname);
628 if ( iter != local_name_map.end() ) {
629 return (*iter).second;
630 } else {
631 gchar const *name_string=g_quark_to_string(qname);
632 gchar const *prefix_end=strchr(name_string, ':');
633 if (prefix_end) {
634 return prefix_end + 1;
635 } else {
636 return name_string;
637 }
638 }
639 }
641 void add_ns_map_entry(NSMap &ns_map, Glib::QueryQuark prefix) {
642 using Inkscape::Util::ptr_shared;
643 using Inkscape::Util::share_unsafe;
645 static const Glib::QueryQuark xml_prefix("xml");
647 NSMap::iterator iter=ns_map.find(prefix);
648 if ( iter == ns_map.end() ) {
649 if (prefix.id()) {
650 gchar const *uri=sp_xml_ns_prefix_uri(g_quark_to_string(prefix));
651 if (uri) {
652 ns_map.insert(NSMap::value_type(prefix, share_unsafe(uri)));
653 } else if ( prefix != xml_prefix ) {
654 g_warning("No namespace known for normalized prefix %s", g_quark_to_string(prefix));
655 }
656 } else {
657 ns_map.insert(NSMap::value_type(prefix, ptr_shared<char>()));
658 }
659 }
660 }
662 void populate_ns_map(NSMap &ns_map, Node &repr) {
663 if ( repr.type() == Inkscape::XML::ELEMENT_NODE ) {
664 add_ns_map_entry(ns_map, qname_prefix(repr.code()));
665 for ( List<AttributeRecord const> iter=repr.attributeList() ;
666 iter ; ++iter )
667 {
668 Glib::QueryQuark prefix=qname_prefix(iter->key);
669 if (prefix.id()) {
670 add_ns_map_entry(ns_map, prefix);
671 }
672 }
673 for ( Node *child=sp_repr_children(&repr) ;
674 child ; child = sp_repr_next(child) )
675 {
676 populate_ns_map(ns_map, *child);
677 }
678 }
679 }
681 }
683 void
684 sp_repr_write_stream_root_element (Node *repr, Writer &out, bool add_whitespace, gchar const *default_ns,
685 int inlineattrs, int indent)
686 {
687 using Inkscape::Util::ptr_shared;
688 g_assert(repr != NULL);
689 Glib::QueryQuark xml_prefix=g_quark_from_static_string("xml");
691 NSMap ns_map;
692 populate_ns_map(ns_map, *repr);
694 Glib::QueryQuark elide_prefix=GQuark(0);
695 if ( default_ns && ns_map.find(GQuark(0)) == ns_map.end() ) {
696 elide_prefix = g_quark_from_string(sp_xml_ns_uri_prefix(default_ns, NULL));
697 }
699 List<AttributeRecord const> attributes=repr->attributeList();
700 for ( NSMap::iterator iter=ns_map.begin() ; iter != ns_map.end() ; ++iter )
701 {
702 Glib::QueryQuark prefix=(*iter).first;
703 ptr_shared<char> ns_uri=(*iter).second;
705 if (prefix.id()) {
706 if ( prefix != xml_prefix ) {
707 if ( elide_prefix == prefix ) {
708 attributes = cons(AttributeRecord(g_quark_from_static_string("xmlns"), ns_uri), attributes);
709 }
711 Glib::ustring attr_name="xmlns:";
712 attr_name.append(g_quark_to_string(prefix));
713 GQuark key = g_quark_from_string(attr_name.c_str());
714 attributes = cons(AttributeRecord(key, ns_uri), attributes);
715 }
716 } else {
717 // if there are non-namespaced elements, we can't globally
718 // use a default namespace
719 elide_prefix = GQuark(0);
720 }
721 }
723 return sp_repr_write_stream_element(repr, out, 0, add_whitespace, elide_prefix, attributes, inlineattrs, indent);
724 }
726 void
727 sp_repr_write_stream (Node *repr, Writer &out, gint indent_level,
728 bool add_whitespace, Glib::QueryQuark elide_prefix, int inlineattrs, int indent)
729 {
730 if (repr->type() == Inkscape::XML::TEXT_NODE) {
731 repr_quote_write (out, repr->content());
732 } else if (repr->type() == Inkscape::XML::COMMENT_NODE) {
733 out.printf( "<!--%s-->", repr->content() );
734 } else if (repr->type() == Inkscape::XML::PI_NODE) {
735 out.printf( "<?%s %s?>", repr->name(), repr->content() );
736 } else if (repr->type() == Inkscape::XML::ELEMENT_NODE) {
737 sp_repr_write_stream_element(repr, out, indent_level, add_whitespace, elide_prefix, repr->attributeList(), inlineattrs, indent);
738 } else {
739 g_assert_not_reached();
740 }
741 }
744 Glib::ustring
745 sp_repr_write_buf(Node *repr, gint indent_level,
746 bool add_whitespace, Glib::QueryQuark elide_prefix,
747 int inlineattrs, int indent)
748 {
749 Glib::ustring buf;
750 Inkscape::IO::StringOutputStream souts;
751 Inkscape::IO::OutputStreamWriter outs(souts);
752 sp_repr_write_stream(repr, outs, indent_level, add_whitespace,
753 elide_prefix, inlineattrs, indent);
754 outs.close();
755 buf = souts.getString();
756 return buf;
757 }
760 void
761 sp_repr_write_stream_element (Node * repr, Writer & out, gint indent_level,
762 bool add_whitespace,
763 Glib::QueryQuark elide_prefix,
764 List<AttributeRecord const> attributes,
765 int inlineattrs, int indent)
766 {
767 Node *child;
768 bool loose;
770 g_return_if_fail (repr != NULL);
772 if ( indent_level > 16 )
773 indent_level = 16;
775 if (add_whitespace && indent) {
776 for (gint i = 0; i < indent_level; i++) {
777 for (gint j = 0; j < indent; j++) {
778 out.writeString(" ");
779 }
780 }
781 }
783 GQuark code = repr->code();
784 gchar const *element_name;
785 if ( elide_prefix == qname_prefix(code) ) {
786 element_name = qname_local_name(code);
787 } else {
788 element_name = g_quark_to_string(code);
789 }
790 out.printf( "<%s", element_name );
792 // if this is a <text> element, suppress formatting whitespace
793 // for its content and children:
794 gchar const *xml_space_attr = repr->attribute("xml:space");
795 if (xml_space_attr != NULL && !strcmp(xml_space_attr, "preserve")) {
796 add_whitespace = FALSE;
797 }
799 for ( List<AttributeRecord const> iter = attributes ;
800 iter ; ++iter )
801 {
802 if (!inlineattrs) {
803 out.writeString("\n");
804 if (indent) {
805 for ( gint i = 0 ; i < indent_level + 1 ; i++ ) {
806 for ( gint j = 0 ; j < indent ; j++ ) {
807 out.writeString(" ");
808 }
809 }
810 }
811 }
812 out.printf(" %s=\"", g_quark_to_string(iter->key));
813 repr_quote_write(out, iter->value);
814 out.writeChar('"');
815 }
817 loose = TRUE;
818 for (child = repr->firstChild() ; child != NULL; child = child->next()) {
819 if (child->type() == Inkscape::XML::TEXT_NODE) {
820 loose = FALSE;
821 break;
822 }
823 }
824 if (repr->firstChild()) {
825 out.writeString( ">" );
826 if (loose && add_whitespace) {
827 out.writeString( "\n" );
828 }
829 for (child = repr->firstChild(); child != NULL; child = child->next()) {
830 sp_repr_write_stream (child, out, (loose) ? (indent_level + 1) : 0, add_whitespace, elide_prefix, inlineattrs, indent);
831 }
833 if (loose && add_whitespace && indent) {
834 for (gint i = 0; i < indent_level; i++) {
835 for ( gint j = 0 ; j < indent ; j++ ) {
836 out.writeString(" ");
837 }
838 }
839 }
840 out.printf( "</%s>", element_name );
841 } else {
842 out.writeString( " />" );
843 }
845 // text elements cannot nest, so we can output newline
846 // after closing text
848 if (add_whitespace || !strcmp (repr->name(), "svg:text")) {
849 out.writeString( "\n" );
850 }
851 }
854 /*
855 Local Variables:
856 mode:c++
857 c-file-style:"stroustrup"
858 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
859 indent-tabs-mode:nil
860 fill-column:99
861 End:
862 */
863 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :