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 <stdexcept>
21 #include "xml/repr.h"
22 #include "xml/attribute-record.h"
24 #include "io/sys.h"
25 #include "io/uristream.h"
26 #include "io/gzipstream.h"
29 using Inkscape::IO::Writer;
30 using Inkscape::Util::List;
31 using Inkscape::Util::cons;
32 using Inkscape::XML::Document;
33 using Inkscape::XML::Node;
34 using Inkscape::XML::AttributeRecord;
36 static Document *sp_repr_do_read (xmlDocPtr doc, const gchar *default_ns);
37 static Node *sp_repr_svg_read_node (xmlNodePtr node, const gchar *default_ns, GHashTable *prefix_map);
38 static gint sp_repr_qualified_name (gchar *p, gint len, xmlNsPtr ns, const xmlChar *name, const gchar *default_ns, GHashTable *prefix_map);
39 static void sp_repr_write_stream_root_element (Node *repr, Writer &out, gboolean add_whitespace, gchar const *default_ns);
40 static void sp_repr_write_stream (Node *repr, Writer &out, gint indent_level, gboolean add_whitespace, Glib::QueryQuark elide_prefix);
41 static void sp_repr_write_stream_element (Node *repr, Writer &out, gint indent_level, gboolean add_whitespace, Glib::QueryQuark elide_prefix, List<AttributeRecord const> attributes);
43 #ifdef HAVE_LIBWMF
44 static xmlDocPtr sp_wmf_convert (const char * file_name);
45 static char * sp_wmf_image_name (void * context);
46 #endif /* HAVE_LIBWMF */
49 class XmlSource
50 {
51 public:
52 XmlSource()
53 : filename(0),
54 encoding(0),
55 fp(0),
56 firstFewLen(0),
57 dummy("x"),
58 instr(0),
59 gzin(0)
60 {
61 }
62 virtual ~XmlSource()
63 {
64 close();
65 if ( encoding ) {
66 g_free(encoding);
67 encoding = 0;
68 }
69 }
71 int setFile( char const * filename );
73 static int readCb( void * context, char * buffer, int len );
74 static int closeCb( void * context );
76 char const* getEncoding() const { return encoding; }
77 int read( char * buffer, int len );
78 int close();
79 private:
80 const char* filename;
81 char* encoding;
82 FILE* fp;
83 unsigned char firstFew[4];
84 int firstFewLen;
85 Inkscape::URI dummy;
86 Inkscape::IO::UriInputStream* instr;
87 Inkscape::IO::GzipInputStream* gzin;
88 };
90 int XmlSource::setFile(char const *filename)
91 {
92 int retVal = -1;
94 this->filename = filename;
96 fp = Inkscape::IO::fopen_utf8name(filename, "r");
97 if ( fp ) {
98 // First peek in the file to see what it is
99 memset( firstFew, 0, sizeof(firstFew) );
101 size_t some = fread( firstFew, 1, 4, fp );
102 if ( fp ) {
103 // first check for compression
104 if ( (some >= 2) && (firstFew[0] == 0x1f) && (firstFew[1] == 0x8b) ) {
105 //g_message(" the file being read is gzip'd. extract it");
106 fclose(fp);
107 fp = 0;
108 fp = Inkscape::IO::fopen_utf8name(filename, "r");
109 instr = new Inkscape::IO::UriInputStream(fp, dummy);
110 gzin = new Inkscape::IO::GzipInputStream(*instr);
112 memset( firstFew, 0, sizeof(firstFew) );
113 some = 0;
114 int single = 0;
115 while ( some < 4 && single >= 0 )
116 {
117 single = gzin->get();
118 if ( single >= 0 ) {
119 firstFew[some++] = 0x0ff & single;
120 } else {
121 break;
122 }
123 }
124 }
126 int encSkip = 0;
127 if ( (some >= 2) &&(firstFew[0] == 0xfe) && (firstFew[1] == 0xff) ) {
128 g_warning("File is UTF-16BE");
129 encoding = g_strdup("UTF-16BE");
130 encSkip = 2;
131 } else if ( (some >= 2) && (firstFew[0] == 0xff) && (firstFew[1] == 0xfe) ) {
132 g_warning("File is UTF-16LE");
133 encoding = g_strdup("UTF-16LE");
134 encSkip = 2;
135 } else if ( (some >= 3) && (firstFew[0] == 0xef) && (firstFew[1] == 0xbb) && (firstFew[2] == 0xbf) ) {
136 g_warning("File is UTF-8");
137 encoding = g_strdup("UTF-8");
138 encSkip = 3;
139 }
141 if ( encSkip ) {
142 memmove( firstFew, firstFew + encSkip, (some - encSkip) );
143 some -= encSkip;
144 }
146 firstFewLen = some;
147 retVal = 0; // no error
148 }
149 }
151 return retVal;
152 }
155 int XmlSource::readCb( void * context, char * buffer, int len )
156 {
157 int retVal = -1;
158 if ( context ) {
159 XmlSource* self = static_cast<XmlSource*>(context);
160 retVal = self->read( buffer, len );
161 }
162 return retVal;
163 }
165 int XmlSource::closeCb(void * context)
166 {
167 if ( context ) {
168 XmlSource* self = static_cast<XmlSource*>(context);
169 self->close();
170 }
171 return 0;
172 }
174 int XmlSource::read( char *buffer, int len )
175 {
176 int retVal = 0;
177 size_t got = 0;
179 if ( firstFewLen > 0 ) {
180 int some = (len < firstFewLen) ? len : firstFewLen;
181 memcpy( buffer, firstFew, some );
182 if ( len < firstFewLen ) {
183 memmove( firstFew, firstFew + some, (firstFewLen - some) );
184 }
185 firstFewLen -= some;
186 got = some;
187 } else if ( gzin ) {
188 int single = 0;
189 while ( (int)got < len && single >= 0 )
190 {
191 single = gzin->get();
192 if ( single >= 0 ) {
193 buffer[got++] = 0x0ff & single;
194 } else {
195 break;
196 }
197 }
198 } else {
199 got = fread( buffer, 1, len, fp );
200 }
202 if ( feof(fp) ) {
203 retVal = got;
204 } else if ( ferror(fp) ) {
205 retVal = -1;
206 } else {
207 retVal = got;
208 }
210 return retVal;
211 }
213 int XmlSource::close()
214 {
215 if ( gzin ) {
216 gzin->close();
217 delete gzin;
218 gzin = 0;
219 }
220 if ( instr ) {
221 instr->close();
222 fp = 0;
223 delete instr;
224 instr = 0;
225 }
226 if ( fp ) {
227 fclose(fp);
228 fp = 0;
229 }
230 return 0;
231 }
233 /**
234 * Reads XML from a file, including WMF files, and returns the Document.
235 * The default namespace can also be specified, if desired.
236 */
237 Document *
238 sp_repr_read_file (const gchar * filename, const gchar *default_ns)
239 {
240 xmlDocPtr doc = 0;
241 Document * rdoc = 0;
243 xmlSubstituteEntitiesDefault(1);
245 g_return_val_if_fail (filename != NULL, NULL);
246 g_return_val_if_fail (Inkscape::IO::file_test( filename, G_FILE_TEST_EXISTS ), NULL);
248 // TODO: bulia, please look over
249 gsize bytesRead = 0;
250 gsize bytesWritten = 0;
251 GError* error = NULL;
252 // TODO: need to replace with our own fopen and reading
253 gchar* localFilename = g_filename_from_utf8 ( filename,
254 -1, &bytesRead, &bytesWritten, &error);
255 g_return_val_if_fail( localFilename != NULL, NULL );
257 Inkscape::IO::dump_fopen_call( filename, "N" );
259 #ifdef HAVE_LIBWMF
260 if (strlen (localFilename) > 4) {
261 if ( (strcmp (localFilename + strlen (localFilename) - 4,".wmf") == 0)
262 || (strcmp (localFilename + strlen (localFilename) - 4,".WMF") == 0)) {
263 doc = sp_wmf_convert (localFilename);
264 }
265 }
266 #endif // !HAVE_LIBWMF
268 if ( !doc ) {
269 XmlSource src;
271 if ( (src.setFile(filename) == 0) ) {
272 doc = xmlReadIO( XmlSource::readCb,
273 XmlSource::closeCb,
274 &src,
275 localFilename,
276 src.getEncoding(),
277 XML_PARSE_NOENT );
278 }
279 }
281 rdoc = sp_repr_do_read( doc, default_ns );
282 if ( doc ) {
283 xmlFreeDoc( doc );
284 }
286 if ( localFilename ) {
287 g_free( localFilename );
288 }
290 return rdoc;
291 }
293 /**
294 * Reads and parses XML from a buffer, returning it as an Document
295 */
296 Document *
297 sp_repr_read_mem (const gchar * buffer, gint length, const gchar *default_ns)
298 {
299 xmlDocPtr doc;
300 Document * rdoc;
302 xmlSubstituteEntitiesDefault(1);
304 g_return_val_if_fail (buffer != NULL, NULL);
306 doc = xmlParseMemory ((gchar *) buffer, length);
308 rdoc = sp_repr_do_read (doc, default_ns);
309 if (doc)
310 xmlFreeDoc (doc);
311 return rdoc;
312 }
314 namespace Inkscape {
316 struct compare_quark_ids {
317 bool operator()(Glib::QueryQuark const &a, Glib::QueryQuark const &b) const {
318 return a.id() < b.id();
319 }
320 };
322 }
324 namespace {
326 typedef std::map<Glib::QueryQuark, Glib::QueryQuark, Inkscape::compare_quark_ids> PrefixMap;
328 Glib::QueryQuark qname_prefix(Glib::QueryQuark qname) {
329 static PrefixMap prefix_map;
330 PrefixMap::iterator iter = prefix_map.find(qname);
331 if ( iter != prefix_map.end() ) {
332 return (*iter).second;
333 } else {
334 gchar const *name_string=g_quark_to_string(qname);
335 gchar const *prefix_end=strchr(name_string, ':');
336 if (prefix_end) {
337 Glib::Quark prefix=Glib::ustring(name_string, prefix_end);
338 prefix_map.insert(PrefixMap::value_type(qname, prefix));
339 return prefix;
340 } else {
341 return GQuark(0);
342 }
343 }
344 }
346 }
348 namespace {
350 void promote_to_svg_namespace(Node *repr) {
351 if ( repr->type() == Inkscape::XML::ELEMENT_NODE ) {
352 GQuark code = repr->code();
353 if (!qname_prefix(code).id()) {
354 gchar *svg_name = g_strconcat("svg:", g_quark_to_string(code), NULL);
355 repr->setCodeUnsafe(g_quark_from_string(svg_name));
356 g_free(svg_name);
357 }
358 for ( Node *child = sp_repr_children(repr) ; child ; child = sp_repr_next(child) ) {
359 promote_to_svg_namespace(child);
360 }
361 }
362 }
364 }
366 /**
367 * Reads in a XML file to create a Document
368 */
369 Document *
370 sp_repr_do_read (xmlDocPtr doc, const gchar *default_ns)
371 {
372 if (doc == NULL) return NULL;
373 xmlNodePtr node=xmlDocGetRootElement (doc);
374 if (node == NULL) return NULL;
376 GHashTable * prefix_map;
377 prefix_map = g_hash_table_new (g_str_hash, g_str_equal);
379 GSList *reprs=NULL;
380 Node *root=NULL;
382 for ( node = doc->children ; node != NULL ; node = node->next ) {
383 if (node->type == XML_ELEMENT_NODE) {
384 Node *repr=sp_repr_svg_read_node (node, default_ns, prefix_map);
385 reprs = g_slist_append(reprs, repr);
387 if (!root) {
388 root = repr;
389 } else {
390 root = NULL;
391 break;
392 }
393 } else if ( node->type == XML_COMMENT_NODE ) {
394 Node *comment=sp_repr_svg_read_node(node, default_ns, prefix_map);
395 reprs = g_slist_append(reprs, comment);
396 }
397 }
399 Document *rdoc=NULL;
401 if (root != NULL) {
402 /* promote elements of SVG documents that don't use namespaces
403 * into the SVG namespace */
404 if ( default_ns && !strcmp(default_ns, SP_SVG_NS_URI)
405 && !strcmp(root->name(), "svg") )
406 {
407 promote_to_svg_namespace(root);
408 }
410 rdoc = sp_repr_document_new_list(reprs);
411 }
413 for ( GSList *iter = reprs ; iter ; iter = iter->next ) {
414 Node *repr=(Node *)iter->data;
415 Inkscape::GC::release(repr);
416 }
417 g_slist_free(reprs);
419 g_hash_table_destroy (prefix_map);
421 return rdoc;
422 }
424 gint
425 sp_repr_qualified_name (gchar *p, gint len, xmlNsPtr ns, const xmlChar *name, const gchar *default_ns, GHashTable *prefix_map)
426 {
427 const xmlChar *prefix;
428 if ( ns && ns->href ) {
429 prefix = (xmlChar*)sp_xml_ns_uri_prefix ((gchar*)ns->href, (char*)ns->prefix);
430 g_hash_table_insert (prefix_map, (gpointer)prefix, (gpointer)ns->href);
431 } else {
432 prefix = NULL;
433 }
435 if (prefix)
436 return g_snprintf (p, len, "%s:%s", (gchar*)prefix, name);
437 else
438 return g_snprintf (p, len, "%s", name);
439 }
441 static Node *
442 sp_repr_svg_read_node (xmlNodePtr node, const gchar *default_ns, GHashTable *prefix_map)
443 {
444 Node *repr, *crepr;
445 xmlAttrPtr prop;
446 xmlNodePtr child;
447 gchar c[256];
449 if (node->type == XML_TEXT_NODE || node->type == XML_CDATA_SECTION_NODE) {
451 if (node->content == NULL || *(node->content) == '\0')
452 return NULL; // empty text node
454 bool preserve = (xmlNodeGetSpacePreserve (node) == 1);
456 xmlChar *p;
457 for (p = node->content; *p && g_ascii_isspace (*p) && !preserve; p++)
458 ; // skip all whitespace
460 if (!(*p)) { // this is an all-whitespace node, and preserve == default
461 return NULL; // we do not preserve all-whitespace nodes unless we are asked to
462 }
464 Node *rdoc = sp_repr_new_text((const gchar *)node->content);
465 return rdoc;
466 }
468 if (node->type == XML_COMMENT_NODE)
469 return sp_repr_new_comment((const gchar *)node->content);
471 if (node->type == XML_ENTITY_DECL) return NULL;
473 sp_repr_qualified_name (c, 256, node->ns, node->name, default_ns, prefix_map);
474 repr = sp_repr_new (c);
475 /* TODO remember node->ns->prefix if node->ns != NULL */
477 for (prop = node->properties; prop != NULL; prop = prop->next) {
478 if (prop->children) {
479 sp_repr_qualified_name (c, 256, prop->ns, prop->name, default_ns, prefix_map);
480 repr->setAttribute(c, (gchar*)prop->children->content);
481 /* TODO remember prop->ns->prefix if prop->ns != NULL */
482 }
483 }
485 if (node->content)
486 repr->setContent((gchar*)node->content);
488 child = node->xmlChildrenNode;
489 for (child = node->xmlChildrenNode; child != NULL; child = child->next) {
490 crepr = sp_repr_svg_read_node (child, default_ns, prefix_map);
491 if (crepr) {
492 repr->appendChild(crepr);
493 Inkscape::GC::release(crepr);
494 }
495 }
497 return repr;
498 }
500 void
501 sp_repr_save_stream (Document *doc, FILE *fp, gchar const *default_ns, bool compress)
502 {
503 Node *repr;
504 const gchar *str;
506 Inkscape::URI dummy("x");
507 Inkscape::IO::UriOutputStream bout(fp, dummy);
508 Inkscape::IO::GzipOutputStream *gout = compress ? new Inkscape::IO::GzipOutputStream(bout) : NULL;
509 Inkscape::IO::OutputStreamWriter *out = compress ? new Inkscape::IO::OutputStreamWriter( *gout ) : new Inkscape::IO::OutputStreamWriter( bout );
511 /* fixme: do this The Right Way */
513 out->writeString( "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?>\n" );
515 str = ((Node *)doc)->attribute("doctype");
516 if (str) {
517 out->writeString( str );
518 }
520 repr = sp_repr_document_first_child(doc);
521 for ( repr = sp_repr_document_first_child(doc) ;
522 repr ; repr = sp_repr_next(repr) )
523 {
524 if ( repr->type() == Inkscape::XML::ELEMENT_NODE ) {
525 sp_repr_write_stream_root_element(repr, *out, TRUE, default_ns);
526 } else if ( repr->type() == Inkscape::XML::COMMENT_NODE ) {
527 sp_repr_write_stream(repr, *out, 0, TRUE, GQuark(0));
528 out->writeChar( '\n' );
529 } else {
530 sp_repr_write_stream(repr, *out, 0, TRUE, GQuark(0));
531 }
532 }
533 if ( out ) {
534 delete out;
535 out = NULL;
536 }
537 if ( gout ) {
538 delete gout;
539 gout = NULL;
540 }
541 }
543 /* Returns TRUE if file successfully saved; FALSE if not
544 */
545 gboolean
546 sp_repr_save_file (Document *doc, const gchar *filename,
547 gchar const *default_ns)
548 {
549 if (filename == NULL) {
550 return FALSE;
551 }
552 bool compress = false;
553 {
554 if (strlen (filename) > 5) {
555 gchar tmp[] = {0,0,0,0,0,0};
556 strncpy( tmp, filename + strlen (filename) - 5, 6 );
557 tmp[5] = 0;
558 if ( strcasecmp(".svgz", tmp ) == 0 )
559 {
560 //g_message("TIME TO COMPRESS THE OUTPUT FOR SVGZ");
561 compress = true;
562 }
563 }
564 }
566 Inkscape::IO::dump_fopen_call( filename, "B" );
567 FILE *file = Inkscape::IO::fopen_utf8name(filename, "w");
568 if (file == NULL) {
569 return FALSE;
570 }
572 sp_repr_save_stream (doc, file, default_ns, compress);
574 if (fclose (file) != 0) {
575 return FALSE;
576 }
578 return TRUE;
579 }
581 void
582 sp_repr_print (Node * repr)
583 {
584 Inkscape::IO::StdOutputStream bout;
585 Inkscape::IO::OutputStreamWriter out(bout);
587 sp_repr_write_stream (repr, out, 0, TRUE, GQuark(0));
589 return;
590 }
592 /* (No doubt this function already exists elsewhere.) */
593 static void
594 repr_quote_write (Writer &out, const gchar * val)
595 {
596 if (!val) return;
598 for (; *val != '\0'; val++) {
599 switch (*val) {
600 case '"': out.writeString( """ ); break;
601 case '&': out.writeString( "&" ); break;
602 case '<': out.writeString( "<" ); break;
603 case '>': out.writeString( ">" ); break;
604 default: out.writeChar( *val ); break;
605 }
606 }
607 }
609 namespace {
611 typedef std::map<Glib::QueryQuark, gchar const *, Inkscape::compare_quark_ids> LocalNameMap;
612 typedef std::map<Glib::QueryQuark, Inkscape::Util::ptr_shared<char>, Inkscape::compare_quark_ids> NSMap;
614 gchar const *qname_local_name(Glib::QueryQuark qname) {
615 static LocalNameMap local_name_map;
616 LocalNameMap::iterator iter = local_name_map.find(qname);
617 if ( iter != local_name_map.end() ) {
618 return (*iter).second;
619 } else {
620 gchar const *name_string=g_quark_to_string(qname);
621 gchar const *prefix_end=strchr(name_string, ':');
622 if (prefix_end) {
623 return prefix_end + 1;
624 } else {
625 return name_string;
626 }
627 }
628 }
630 void add_ns_map_entry(NSMap &ns_map, Glib::QueryQuark prefix) {
631 using Inkscape::Util::ptr_shared;
632 using Inkscape::Util::share_unsafe;
634 static const Glib::QueryQuark xml_prefix("xml");
636 NSMap::iterator iter=ns_map.find(prefix);
637 if ( iter == ns_map.end() ) {
638 if (prefix.id()) {
639 gchar const *uri=sp_xml_ns_prefix_uri(g_quark_to_string(prefix));
640 if (uri) {
641 ns_map.insert(NSMap::value_type(prefix, share_unsafe(uri)));
642 } else if ( prefix != xml_prefix ) {
643 g_warning("No namespace known for normalized prefix %s", g_quark_to_string(prefix));
644 }
645 } else {
646 ns_map.insert(NSMap::value_type(prefix, ptr_shared<char>()));
647 }
648 }
649 }
651 void populate_ns_map(NSMap &ns_map, Node &repr) {
652 if ( repr.type() == Inkscape::XML::ELEMENT_NODE ) {
653 add_ns_map_entry(ns_map, qname_prefix(repr.code()));
654 for ( List<AttributeRecord const> iter=repr.attributeList() ;
655 iter ; ++iter )
656 {
657 Glib::QueryQuark prefix=qname_prefix(iter->key);
658 if (prefix.id()) {
659 add_ns_map_entry(ns_map, prefix);
660 }
661 }
662 for ( Node *child=sp_repr_children(&repr) ;
663 child ; child = sp_repr_next(child) )
664 {
665 populate_ns_map(ns_map, *child);
666 }
667 }
668 }
670 }
672 void
673 sp_repr_write_stream_root_element (Node *repr, Writer &out, gboolean add_whitespace, gchar const *default_ns)
674 {
675 using Inkscape::Util::ptr_shared;
676 g_assert(repr != NULL);
677 Glib::QueryQuark xml_prefix=g_quark_from_static_string("xml");
679 NSMap ns_map;
680 populate_ns_map(ns_map, *repr);
682 Glib::QueryQuark elide_prefix=GQuark(0);
683 if ( default_ns && ns_map.find(GQuark(0)) == ns_map.end() ) {
684 elide_prefix = g_quark_from_string(sp_xml_ns_uri_prefix(default_ns, NULL));
685 }
687 List<AttributeRecord const> attributes=repr->attributeList();
688 for ( NSMap::iterator iter=ns_map.begin() ; iter != ns_map.end() ; ++iter )
689 {
690 Glib::QueryQuark prefix=(*iter).first;
691 ptr_shared<char> ns_uri=(*iter).second;
693 if (prefix.id()) {
694 if ( prefix != xml_prefix ) {
695 if ( elide_prefix == prefix ) {
696 attributes = cons(AttributeRecord(g_quark_from_static_string("xmlns"), ns_uri), attributes);
697 }
699 Glib::ustring attr_name="xmlns:";
700 attr_name.append(g_quark_to_string(prefix));
701 GQuark key = g_quark_from_string(attr_name.c_str());
702 attributes = cons(AttributeRecord(key, ns_uri), attributes);
703 }
704 } else {
705 // if there are non-namespaced elements, we can't globally
706 // use a default namespace
707 elide_prefix = GQuark(0);
708 }
709 }
711 return sp_repr_write_stream_element(repr, out, 0, add_whitespace, elide_prefix, attributes);
712 }
714 void
715 sp_repr_write_stream (Node *repr, Writer &out, gint indent_level,
716 gboolean add_whitespace, Glib::QueryQuark elide_prefix)
717 {
718 if (repr->type() == Inkscape::XML::TEXT_NODE) {
719 repr_quote_write (out, repr->content());
720 } else if (repr->type() == Inkscape::XML::COMMENT_NODE) {
721 out.printf( "<!--%s-->", repr->content() );
722 } else if (repr->type() == Inkscape::XML::ELEMENT_NODE) {
723 sp_repr_write_stream_element(repr, out, indent_level, add_whitespace, elide_prefix, repr->attributeList());
724 } else {
725 g_assert_not_reached();
726 }
727 }
729 void
730 sp_repr_write_stream_element (Node * repr, Writer & out, gint indent_level,
731 gboolean add_whitespace,
732 Glib::QueryQuark elide_prefix,
733 List<AttributeRecord const> attributes)
734 {
735 Node *child;
736 gboolean loose;
737 gint i;
739 g_return_if_fail (repr != NULL);
741 if ( indent_level > 16 )
742 indent_level = 16;
744 if (add_whitespace) {
745 for ( i = 0 ; i < indent_level ; i++ ) {
746 out.writeString( " " );
747 }
748 }
750 GQuark code = repr->code();
751 gchar const *element_name;
752 if ( elide_prefix == qname_prefix(code) ) {
753 element_name = qname_local_name(code);
754 } else {
755 element_name = g_quark_to_string(code);
756 }
757 out.printf( "<%s", element_name );
759 // if this is a <text> element, suppress formatting whitespace
760 // for its content and children:
761 gchar const *xml_space_attr = repr->attribute("xml:space");
762 if (xml_space_attr != NULL && !strcmp(xml_space_attr, "preserve")) {
763 add_whitespace = FALSE;
764 }
766 for ( List<AttributeRecord const> iter = attributes ;
767 iter ; ++iter )
768 {
769 out.writeString("\n");
770 for ( i = 0 ; i < indent_level + 1 ; i++ ) {
771 out.writeString(" ");
772 }
773 out.printf(" %s=\"", g_quark_to_string(iter->key));
774 repr_quote_write(out, iter->value);
775 out.writeChar('"');
776 }
778 loose = TRUE;
779 for (child = repr->firstChild() ; child != NULL; child = child->next()) {
780 if (child->type() == Inkscape::XML::TEXT_NODE) {
781 loose = FALSE;
782 break;
783 }
784 }
785 if (repr->firstChild()) {
786 out.writeString( ">" );
787 if (loose && add_whitespace) {
788 out.writeString( "\n" );
789 }
790 for (child = repr->firstChild(); child != NULL; child = child->next()) {
791 sp_repr_write_stream (child, out, (loose) ? (indent_level + 1) : 0, add_whitespace, elide_prefix);
792 }
794 if (loose && add_whitespace) {
795 for (i = 0; i < indent_level; i++) {
796 out.writeString( " " );
797 }
798 }
799 out.printf( "</%s>", element_name );
800 } else {
801 out.writeString( " />" );
802 }
804 // text elements cannot nest, so we can output newline
805 // after closing text
807 if (add_whitespace || !strcmp (repr->name(), "svg:text")) {
808 out.writeString( "\n" );
809 }
810 }
813 /*
814 Local Variables:
815 mode:c++
816 c-file-style:"stroustrup"
817 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
818 indent-tabs-mode:nil
819 fill-column:99
820 End:
821 */
822 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :