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 encoding = g_strdup("UTF-16BE");
129 encSkip = 2;
130 } else if ( (some >= 2) && (firstFew[0] == 0xff) && (firstFew[1] == 0xfe) ) {
131 encoding = g_strdup("UTF-16LE");
132 encSkip = 2;
133 } else if ( (some >= 3) && (firstFew[0] == 0xef) && (firstFew[1] == 0xbb) && (firstFew[2] == 0xbf) ) {
134 encoding = g_strdup("UTF-8");
135 encSkip = 3;
136 }
138 if ( encSkip ) {
139 memmove( firstFew, firstFew + encSkip, (some - encSkip) );
140 some -= encSkip;
141 }
143 firstFewLen = some;
144 retVal = 0; // no error
145 }
146 }
148 return retVal;
149 }
152 int XmlSource::readCb( void * context, char * buffer, int len )
153 {
154 int retVal = -1;
155 if ( context ) {
156 XmlSource* self = static_cast<XmlSource*>(context);
157 retVal = self->read( buffer, len );
158 }
159 return retVal;
160 }
162 int XmlSource::closeCb(void * context)
163 {
164 if ( context ) {
165 XmlSource* self = static_cast<XmlSource*>(context);
166 self->close();
167 }
168 return 0;
169 }
171 int XmlSource::read( char *buffer, int len )
172 {
173 int retVal = 0;
174 size_t got = 0;
176 if ( firstFewLen > 0 ) {
177 int some = (len < firstFewLen) ? len : firstFewLen;
178 memcpy( buffer, firstFew, some );
179 if ( len < firstFewLen ) {
180 memmove( firstFew, firstFew + some, (firstFewLen - some) );
181 }
182 firstFewLen -= some;
183 got = some;
184 } else if ( gzin ) {
185 int single = 0;
186 while ( (int)got < len && single >= 0 )
187 {
188 single = gzin->get();
189 if ( single >= 0 ) {
190 buffer[got++] = 0x0ff & single;
191 } else {
192 break;
193 }
194 }
195 } else {
196 got = fread( buffer, 1, len, fp );
197 }
199 if ( feof(fp) ) {
200 retVal = got;
201 } else if ( ferror(fp) ) {
202 retVal = -1;
203 } else {
204 retVal = got;
205 }
207 return retVal;
208 }
210 int XmlSource::close()
211 {
212 if ( gzin ) {
213 gzin->close();
214 delete gzin;
215 gzin = 0;
216 }
217 if ( instr ) {
218 instr->close();
219 fp = 0;
220 delete instr;
221 instr = 0;
222 }
223 if ( fp ) {
224 fclose(fp);
225 fp = 0;
226 }
227 return 0;
228 }
230 /**
231 * Reads XML from a file, including WMF files, and returns the Document.
232 * The default namespace can also be specified, if desired.
233 */
234 Document *
235 sp_repr_read_file (const gchar * filename, const gchar *default_ns)
236 {
237 xmlDocPtr doc = 0;
238 Document * rdoc = 0;
240 xmlSubstituteEntitiesDefault(1);
242 g_return_val_if_fail (filename != NULL, NULL);
243 g_return_val_if_fail (Inkscape::IO::file_test( filename, G_FILE_TEST_EXISTS ), NULL);
245 // TODO: bulia, please look over
246 gsize bytesRead = 0;
247 gsize bytesWritten = 0;
248 GError* error = NULL;
249 // TODO: need to replace with our own fopen and reading
250 gchar* localFilename = g_filename_from_utf8 ( filename,
251 -1, &bytesRead, &bytesWritten, &error);
252 g_return_val_if_fail( localFilename != NULL, NULL );
254 Inkscape::IO::dump_fopen_call( filename, "N" );
256 #ifdef HAVE_LIBWMF
257 if (strlen (localFilename) > 4) {
258 if ( (strcmp (localFilename + strlen (localFilename) - 4,".wmf") == 0)
259 || (strcmp (localFilename + strlen (localFilename) - 4,".WMF") == 0)) {
260 doc = sp_wmf_convert (localFilename);
261 }
262 }
263 #endif // !HAVE_LIBWMF
265 if ( !doc ) {
266 XmlSource src;
268 if ( (src.setFile(filename) == 0) ) {
269 doc = xmlReadIO( XmlSource::readCb,
270 XmlSource::closeCb,
271 &src,
272 localFilename,
273 src.getEncoding(),
274 XML_PARSE_NOENT );
275 }
276 }
278 rdoc = sp_repr_do_read( doc, default_ns );
279 if ( doc ) {
280 xmlFreeDoc( doc );
281 }
283 if ( localFilename ) {
284 g_free( localFilename );
285 }
287 return rdoc;
288 }
290 /**
291 * Reads and parses XML from a buffer, returning it as an Document
292 */
293 Document *
294 sp_repr_read_mem (const gchar * buffer, gint length, const gchar *default_ns)
295 {
296 xmlDocPtr doc;
297 Document * rdoc;
299 xmlSubstituteEntitiesDefault(1);
301 g_return_val_if_fail (buffer != NULL, NULL);
303 doc = xmlParseMemory ((gchar *) buffer, length);
305 rdoc = sp_repr_do_read (doc, default_ns);
306 if (doc)
307 xmlFreeDoc (doc);
308 return rdoc;
309 }
311 namespace Inkscape {
313 struct compare_quark_ids {
314 bool operator()(Glib::QueryQuark const &a, Glib::QueryQuark const &b) const {
315 return a.id() < b.id();
316 }
317 };
319 }
321 namespace {
323 typedef std::map<Glib::QueryQuark, Glib::QueryQuark, Inkscape::compare_quark_ids> PrefixMap;
325 Glib::QueryQuark qname_prefix(Glib::QueryQuark qname) {
326 static PrefixMap prefix_map;
327 PrefixMap::iterator iter = prefix_map.find(qname);
328 if ( iter != prefix_map.end() ) {
329 return (*iter).second;
330 } else {
331 gchar const *name_string=g_quark_to_string(qname);
332 gchar const *prefix_end=strchr(name_string, ':');
333 if (prefix_end) {
334 Glib::Quark prefix=Glib::ustring(name_string, prefix_end);
335 prefix_map.insert(PrefixMap::value_type(qname, prefix));
336 return prefix;
337 } else {
338 return GQuark(0);
339 }
340 }
341 }
343 }
345 namespace {
347 void promote_to_svg_namespace(Node *repr) {
348 if ( repr->type() == Inkscape::XML::ELEMENT_NODE ) {
349 GQuark code = repr->code();
350 if (!qname_prefix(code).id()) {
351 gchar *svg_name = g_strconcat("svg:", g_quark_to_string(code), NULL);
352 repr->setCodeUnsafe(g_quark_from_string(svg_name));
353 g_free(svg_name);
354 }
355 for ( Node *child = sp_repr_children(repr) ; child ; child = sp_repr_next(child) ) {
356 promote_to_svg_namespace(child);
357 }
358 }
359 }
361 }
363 /**
364 * Reads in a XML file to create a Document
365 */
366 Document *
367 sp_repr_do_read (xmlDocPtr doc, const gchar *default_ns)
368 {
369 if (doc == NULL) return NULL;
370 xmlNodePtr node=xmlDocGetRootElement (doc);
371 if (node == NULL) return NULL;
373 GHashTable * prefix_map;
374 prefix_map = g_hash_table_new (g_str_hash, g_str_equal);
376 GSList *reprs=NULL;
377 Node *root=NULL;
379 for ( node = doc->children ; node != NULL ; node = node->next ) {
380 if (node->type == XML_ELEMENT_NODE) {
381 Node *repr=sp_repr_svg_read_node (node, default_ns, prefix_map);
382 reprs = g_slist_append(reprs, repr);
384 if (!root) {
385 root = repr;
386 } else {
387 root = NULL;
388 break;
389 }
390 } else if ( node->type == XML_COMMENT_NODE ) {
391 Node *comment=sp_repr_svg_read_node(node, default_ns, prefix_map);
392 reprs = g_slist_append(reprs, comment);
393 }
394 }
396 Document *rdoc=NULL;
398 if (root != NULL) {
399 /* promote elements of SVG documents that don't use namespaces
400 * into the SVG namespace */
401 if ( default_ns && !strcmp(default_ns, SP_SVG_NS_URI)
402 && !strcmp(root->name(), "svg") )
403 {
404 promote_to_svg_namespace(root);
405 }
407 rdoc = sp_repr_document_new_list(reprs);
408 }
410 for ( GSList *iter = reprs ; iter ; iter = iter->next ) {
411 Node *repr=(Node *)iter->data;
412 Inkscape::GC::release(repr);
413 }
414 g_slist_free(reprs);
416 g_hash_table_destroy (prefix_map);
418 return rdoc;
419 }
421 gint
422 sp_repr_qualified_name (gchar *p, gint len, xmlNsPtr ns, const xmlChar *name, const gchar *default_ns, GHashTable *prefix_map)
423 {
424 const xmlChar *prefix;
425 if ( ns && ns->href ) {
426 prefix = (xmlChar*)sp_xml_ns_uri_prefix ((gchar*)ns->href, (char*)ns->prefix);
427 g_hash_table_insert (prefix_map, (gpointer)prefix, (gpointer)ns->href);
428 } else {
429 prefix = NULL;
430 }
432 if (prefix)
433 return g_snprintf (p, len, "%s:%s", (gchar*)prefix, name);
434 else
435 return g_snprintf (p, len, "%s", name);
436 }
438 static Node *
439 sp_repr_svg_read_node (xmlNodePtr node, const gchar *default_ns, GHashTable *prefix_map)
440 {
441 Node *repr, *crepr;
442 xmlAttrPtr prop;
443 xmlNodePtr child;
444 gchar c[256];
446 if (node->type == XML_TEXT_NODE || node->type == XML_CDATA_SECTION_NODE) {
448 if (node->content == NULL || *(node->content) == '\0')
449 return NULL; // empty text node
451 bool preserve = (xmlNodeGetSpacePreserve (node) == 1);
453 xmlChar *p;
454 for (p = node->content; *p && g_ascii_isspace (*p) && !preserve; p++)
455 ; // skip all whitespace
457 if (!(*p)) { // this is an all-whitespace node, and preserve == default
458 return NULL; // we do not preserve all-whitespace nodes unless we are asked to
459 }
461 Node *rdoc = sp_repr_new_text((const gchar *)node->content);
462 return rdoc;
463 }
465 if (node->type == XML_COMMENT_NODE)
466 return sp_repr_new_comment((const gchar *)node->content);
468 if (node->type == XML_ENTITY_DECL) return NULL;
470 sp_repr_qualified_name (c, 256, node->ns, node->name, default_ns, prefix_map);
471 repr = sp_repr_new (c);
472 /* TODO remember node->ns->prefix if node->ns != NULL */
474 for (prop = node->properties; prop != NULL; prop = prop->next) {
475 if (prop->children) {
476 sp_repr_qualified_name (c, 256, prop->ns, prop->name, default_ns, prefix_map);
477 repr->setAttribute(c, (gchar*)prop->children->content);
478 /* TODO remember prop->ns->prefix if prop->ns != NULL */
479 }
480 }
482 if (node->content)
483 repr->setContent((gchar*)node->content);
485 child = node->xmlChildrenNode;
486 for (child = node->xmlChildrenNode; child != NULL; child = child->next) {
487 crepr = sp_repr_svg_read_node (child, default_ns, prefix_map);
488 if (crepr) {
489 repr->appendChild(crepr);
490 Inkscape::GC::release(crepr);
491 }
492 }
494 return repr;
495 }
497 void
498 sp_repr_save_stream (Document *doc, FILE *fp, gchar const *default_ns, bool compress)
499 {
500 Node *repr;
501 const gchar *str;
503 Inkscape::URI dummy("x");
504 Inkscape::IO::UriOutputStream bout(fp, dummy);
505 Inkscape::IO::GzipOutputStream *gout = compress ? new Inkscape::IO::GzipOutputStream(bout) : NULL;
506 Inkscape::IO::OutputStreamWriter *out = compress ? new Inkscape::IO::OutputStreamWriter( *gout ) : new Inkscape::IO::OutputStreamWriter( bout );
508 /* fixme: do this The Right Way */
510 out->writeString( "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?>\n" );
512 str = ((Node *)doc)->attribute("doctype");
513 if (str) {
514 out->writeString( str );
515 }
517 repr = sp_repr_document_first_child(doc);
518 for ( repr = sp_repr_document_first_child(doc) ;
519 repr ; repr = sp_repr_next(repr) )
520 {
521 if ( repr->type() == Inkscape::XML::ELEMENT_NODE ) {
522 sp_repr_write_stream_root_element(repr, *out, TRUE, default_ns);
523 } else if ( repr->type() == Inkscape::XML::COMMENT_NODE ) {
524 sp_repr_write_stream(repr, *out, 0, TRUE, GQuark(0));
525 out->writeChar( '\n' );
526 } else {
527 sp_repr_write_stream(repr, *out, 0, TRUE, GQuark(0));
528 }
529 }
530 if ( out ) {
531 delete out;
532 out = NULL;
533 }
534 if ( gout ) {
535 delete gout;
536 gout = NULL;
537 }
538 }
540 /* Returns TRUE if file successfully saved; FALSE if not
541 */
542 gboolean
543 sp_repr_save_file (Document *doc, const gchar *filename,
544 gchar const *default_ns)
545 {
546 if (filename == NULL) {
547 return FALSE;
548 }
549 bool compress = false;
550 {
551 if (strlen (filename) > 5) {
552 gchar tmp[] = {0,0,0,0,0,0};
553 strncpy( tmp, filename + strlen (filename) - 5, 6 );
554 tmp[5] = 0;
555 if ( strcasecmp(".svgz", tmp ) == 0 )
556 {
557 //g_message("TIME TO COMPRESS THE OUTPUT FOR SVGZ");
558 compress = true;
559 }
560 }
561 }
563 Inkscape::IO::dump_fopen_call( filename, "B" );
564 FILE *file = Inkscape::IO::fopen_utf8name(filename, "w");
565 if (file == NULL) {
566 return FALSE;
567 }
569 sp_repr_save_stream (doc, file, default_ns, compress);
571 if (fclose (file) != 0) {
572 return FALSE;
573 }
575 return TRUE;
576 }
578 void
579 sp_repr_print (Node * repr)
580 {
581 Inkscape::IO::StdOutputStream bout;
582 Inkscape::IO::OutputStreamWriter out(bout);
584 sp_repr_write_stream (repr, out, 0, TRUE, GQuark(0));
586 return;
587 }
589 /* (No doubt this function already exists elsewhere.) */
590 static void
591 repr_quote_write (Writer &out, const gchar * val)
592 {
593 if (!val) return;
595 for (; *val != '\0'; val++) {
596 switch (*val) {
597 case '"': out.writeString( """ ); break;
598 case '&': out.writeString( "&" ); break;
599 case '<': out.writeString( "<" ); break;
600 case '>': out.writeString( ">" ); break;
601 default: out.writeChar( *val ); break;
602 }
603 }
604 }
606 namespace {
608 typedef std::map<Glib::QueryQuark, gchar const *, Inkscape::compare_quark_ids> LocalNameMap;
609 typedef std::map<Glib::QueryQuark, Inkscape::Util::ptr_shared<char>, Inkscape::compare_quark_ids> NSMap;
611 gchar const *qname_local_name(Glib::QueryQuark qname) {
612 static LocalNameMap local_name_map;
613 LocalNameMap::iterator iter = local_name_map.find(qname);
614 if ( iter != local_name_map.end() ) {
615 return (*iter).second;
616 } else {
617 gchar const *name_string=g_quark_to_string(qname);
618 gchar const *prefix_end=strchr(name_string, ':');
619 if (prefix_end) {
620 return prefix_end + 1;
621 } else {
622 return name_string;
623 }
624 }
625 }
627 void add_ns_map_entry(NSMap &ns_map, Glib::QueryQuark prefix) {
628 using Inkscape::Util::ptr_shared;
629 using Inkscape::Util::share_unsafe;
631 static const Glib::QueryQuark xml_prefix("xml");
633 NSMap::iterator iter=ns_map.find(prefix);
634 if ( iter == ns_map.end() ) {
635 if (prefix.id()) {
636 gchar const *uri=sp_xml_ns_prefix_uri(g_quark_to_string(prefix));
637 if (uri) {
638 ns_map.insert(NSMap::value_type(prefix, share_unsafe(uri)));
639 } else if ( prefix != xml_prefix ) {
640 g_warning("No namespace known for normalized prefix %s", g_quark_to_string(prefix));
641 }
642 } else {
643 ns_map.insert(NSMap::value_type(prefix, ptr_shared<char>()));
644 }
645 }
646 }
648 void populate_ns_map(NSMap &ns_map, Node &repr) {
649 if ( repr.type() == Inkscape::XML::ELEMENT_NODE ) {
650 add_ns_map_entry(ns_map, qname_prefix(repr.code()));
651 for ( List<AttributeRecord const> iter=repr.attributeList() ;
652 iter ; ++iter )
653 {
654 Glib::QueryQuark prefix=qname_prefix(iter->key);
655 if (prefix.id()) {
656 add_ns_map_entry(ns_map, prefix);
657 }
658 }
659 for ( Node *child=sp_repr_children(&repr) ;
660 child ; child = sp_repr_next(child) )
661 {
662 populate_ns_map(ns_map, *child);
663 }
664 }
665 }
667 }
669 void
670 sp_repr_write_stream_root_element (Node *repr, Writer &out, gboolean add_whitespace, gchar const *default_ns)
671 {
672 using Inkscape::Util::ptr_shared;
673 g_assert(repr != NULL);
674 Glib::QueryQuark xml_prefix=g_quark_from_static_string("xml");
676 NSMap ns_map;
677 populate_ns_map(ns_map, *repr);
679 Glib::QueryQuark elide_prefix=GQuark(0);
680 if ( default_ns && ns_map.find(GQuark(0)) == ns_map.end() ) {
681 elide_prefix = g_quark_from_string(sp_xml_ns_uri_prefix(default_ns, NULL));
682 }
684 List<AttributeRecord const> attributes=repr->attributeList();
685 for ( NSMap::iterator iter=ns_map.begin() ; iter != ns_map.end() ; ++iter )
686 {
687 Glib::QueryQuark prefix=(*iter).first;
688 ptr_shared<char> ns_uri=(*iter).second;
690 if (prefix.id()) {
691 if ( prefix != xml_prefix ) {
692 if ( elide_prefix == prefix ) {
693 attributes = cons(AttributeRecord(g_quark_from_static_string("xmlns"), ns_uri), attributes);
694 }
696 Glib::ustring attr_name="xmlns:";
697 attr_name.append(g_quark_to_string(prefix));
698 GQuark key = g_quark_from_string(attr_name.c_str());
699 attributes = cons(AttributeRecord(key, ns_uri), attributes);
700 }
701 } else {
702 // if there are non-namespaced elements, we can't globally
703 // use a default namespace
704 elide_prefix = GQuark(0);
705 }
706 }
708 return sp_repr_write_stream_element(repr, out, 0, add_whitespace, elide_prefix, attributes);
709 }
711 void
712 sp_repr_write_stream (Node *repr, Writer &out, gint indent_level,
713 gboolean add_whitespace, Glib::QueryQuark elide_prefix)
714 {
715 if (repr->type() == Inkscape::XML::TEXT_NODE) {
716 repr_quote_write (out, repr->content());
717 } else if (repr->type() == Inkscape::XML::COMMENT_NODE) {
718 out.printf( "<!--%s-->", repr->content() );
719 } else if (repr->type() == Inkscape::XML::ELEMENT_NODE) {
720 sp_repr_write_stream_element(repr, out, indent_level, add_whitespace, elide_prefix, repr->attributeList());
721 } else {
722 g_assert_not_reached();
723 }
724 }
726 void
727 sp_repr_write_stream_element (Node * repr, Writer & out, gint indent_level,
728 gboolean add_whitespace,
729 Glib::QueryQuark elide_prefix,
730 List<AttributeRecord const> attributes)
731 {
732 Node *child;
733 gboolean loose;
734 gint i;
736 g_return_if_fail (repr != NULL);
738 if ( indent_level > 16 )
739 indent_level = 16;
741 if (add_whitespace) {
742 for ( i = 0 ; i < indent_level ; i++ ) {
743 out.writeString( " " );
744 }
745 }
747 GQuark code = repr->code();
748 gchar const *element_name;
749 if ( elide_prefix == qname_prefix(code) ) {
750 element_name = qname_local_name(code);
751 } else {
752 element_name = g_quark_to_string(code);
753 }
754 out.printf( "<%s", element_name );
756 // if this is a <text> element, suppress formatting whitespace
757 // for its content and children:
758 gchar const *xml_space_attr = repr->attribute("xml:space");
759 if (xml_space_attr != NULL && !strcmp(xml_space_attr, "preserve")) {
760 add_whitespace = FALSE;
761 }
763 for ( List<AttributeRecord const> iter = attributes ;
764 iter ; ++iter )
765 {
766 out.writeString("\n");
767 for ( i = 0 ; i < indent_level + 1 ; i++ ) {
768 out.writeString(" ");
769 }
770 out.printf(" %s=\"", g_quark_to_string(iter->key));
771 repr_quote_write(out, iter->value);
772 out.writeChar('"');
773 }
775 loose = TRUE;
776 for (child = repr->firstChild() ; child != NULL; child = child->next()) {
777 if (child->type() == Inkscape::XML::TEXT_NODE) {
778 loose = FALSE;
779 break;
780 }
781 }
782 if (repr->firstChild()) {
783 out.writeString( ">" );
784 if (loose && add_whitespace) {
785 out.writeString( "\n" );
786 }
787 for (child = repr->firstChild(); child != NULL; child = child->next()) {
788 sp_repr_write_stream (child, out, (loose) ? (indent_level + 1) : 0, add_whitespace, elide_prefix);
789 }
791 if (loose && add_whitespace) {
792 for (i = 0; i < indent_level; i++) {
793 out.writeString( " " );
794 }
795 }
796 out.printf( "</%s>", element_name );
797 } else {
798 out.writeString( " />" );
799 }
801 // text elements cannot nest, so we can output newline
802 // after closing text
804 if (add_whitespace || !strcmp (repr->name(), "svg:text")) {
805 out.writeString( "\n" );
806 }
807 }
810 /*
811 Local Variables:
812 mode:c++
813 c-file-style:"stroustrup"
814 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
815 indent-tabs-mode:nil
816 fill-column:99
817 End:
818 */
819 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :