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"
23 #include "xml/simple-document.h"
25 #include "io/sys.h"
26 #include "io/uristream.h"
27 #include "io/gzipstream.h"
29 #include "prefs-utils.h"
31 using Inkscape::IO::Writer;
32 using Inkscape::Util::List;
33 using Inkscape::Util::cons;
34 using Inkscape::XML::Document;
35 using Inkscape::XML::SimpleDocument;
36 using Inkscape::XML::Node;
37 using Inkscape::XML::AttributeRecord;
39 static Document *sp_repr_do_read (xmlDocPtr doc, const gchar *default_ns);
40 static Node *sp_repr_svg_read_node (Document *xml_doc, xmlNodePtr node, const gchar *default_ns, GHashTable *prefix_map);
41 static gint sp_repr_qualified_name (gchar *p, gint len, xmlNsPtr ns, const xmlChar *name, const gchar *default_ns, GHashTable *prefix_map);
42 static void sp_repr_write_stream_root_element (Node *repr, Writer &out, bool add_whitespace, gchar const *default_ns, int inlineattrs, int indent);
43 static void sp_repr_write_stream (Node *repr, Writer &out, gint indent_level, bool add_whitespace, Glib::QueryQuark elide_prefix, int inlineattrs, int indent);
44 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);
46 #ifdef HAVE_LIBWMF
47 static xmlDocPtr sp_wmf_convert (const char * file_name);
48 static char * sp_wmf_image_name (void * context);
49 #endif /* HAVE_LIBWMF */
52 class XmlSource
53 {
54 public:
55 XmlSource()
56 : filename(0),
57 encoding(0),
58 fp(0),
59 firstFewLen(0),
60 dummy("x"),
61 instr(0),
62 gzin(0)
63 {
64 }
65 virtual ~XmlSource()
66 {
67 close();
68 if ( encoding ) {
69 g_free(encoding);
70 encoding = 0;
71 }
72 }
74 int setFile( char const * filename );
76 static int readCb( void * context, char * buffer, int len );
77 static int closeCb( void * context );
79 char const* getEncoding() const { return encoding; }
80 int read( char * buffer, int len );
81 int close();
82 private:
83 const char* filename;
84 char* encoding;
85 FILE* fp;
86 unsigned char firstFew[4];
87 int firstFewLen;
88 Inkscape::URI dummy;
89 Inkscape::IO::UriInputStream* instr;
90 Inkscape::IO::GzipInputStream* gzin;
91 };
93 int XmlSource::setFile(char const *filename)
94 {
95 int retVal = -1;
97 this->filename = filename;
99 fp = Inkscape::IO::fopen_utf8name(filename, "r");
100 if ( fp ) {
101 // First peek in the file to see what it is
102 memset( firstFew, 0, sizeof(firstFew) );
104 size_t some = fread( firstFew, 1, 4, fp );
105 if ( fp ) {
106 // first check for compression
107 if ( (some >= 2) && (firstFew[0] == 0x1f) && (firstFew[1] == 0x8b) ) {
108 //g_message(" the file being read is gzip'd. extract it");
109 fclose(fp);
110 fp = 0;
111 fp = Inkscape::IO::fopen_utf8name(filename, "r");
112 instr = new Inkscape::IO::UriInputStream(fp, dummy);
113 gzin = new Inkscape::IO::GzipInputStream(*instr);
115 memset( firstFew, 0, sizeof(firstFew) );
116 some = 0;
117 int single = 0;
118 while ( some < 4 && single >= 0 )
119 {
120 single = gzin->get();
121 if ( single >= 0 ) {
122 firstFew[some++] = 0x0ff & single;
123 } else {
124 break;
125 }
126 }
127 }
129 int encSkip = 0;
130 if ( (some >= 2) &&(firstFew[0] == 0xfe) && (firstFew[1] == 0xff) ) {
131 encoding = g_strdup("UTF-16BE");
132 encSkip = 2;
133 } else if ( (some >= 2) && (firstFew[0] == 0xff) && (firstFew[1] == 0xfe) ) {
134 encoding = g_strdup("UTF-16LE");
135 encSkip = 2;
136 } else if ( (some >= 3) && (firstFew[0] == 0xef) && (firstFew[1] == 0xbb) && (firstFew[2] == 0xbf) ) {
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);
247 /* fixme: A file can disappear at any time, including between now and when we actually try to
248 * open it. Get rid of the above test once we're sure that we correctly handle
249 * non-existence. */
251 // TODO: bulia, please look over
252 gsize bytesRead = 0;
253 gsize bytesWritten = 0;
254 GError* error = NULL;
255 // TODO: need to replace with our own fopen and reading
256 gchar* localFilename = g_filename_from_utf8 ( filename,
257 -1, &bytesRead, &bytesWritten, &error);
258 g_return_val_if_fail( localFilename != NULL, NULL );
260 Inkscape::IO::dump_fopen_call( filename, "N" );
262 #ifdef HAVE_LIBWMF
263 if (strlen (localFilename) > 4) {
264 if ( (strcmp (localFilename + strlen (localFilename) - 4,".wmf") == 0)
265 || (strcmp (localFilename + strlen (localFilename) - 4,".WMF") == 0)) {
266 doc = sp_wmf_convert (localFilename);
267 }
268 }
269 #endif // !HAVE_LIBWMF
271 if ( !doc ) {
272 XmlSource src;
274 if ( (src.setFile(filename) == 0) ) {
275 doc = xmlReadIO( XmlSource::readCb,
276 XmlSource::closeCb,
277 &src,
278 localFilename,
279 src.getEncoding(),
280 XML_PARSE_NOENT );
281 }
282 }
284 rdoc = sp_repr_do_read( doc, default_ns );
285 if ( doc ) {
286 xmlFreeDoc( doc );
287 }
289 if ( localFilename ) {
290 g_free( localFilename );
291 }
293 return rdoc;
294 }
296 /**
297 * Reads and parses XML from a buffer, returning it as an Document
298 */
299 Document *
300 sp_repr_read_mem (const gchar * buffer, gint length, const gchar *default_ns)
301 {
302 xmlDocPtr doc;
303 Document * rdoc;
305 xmlSubstituteEntitiesDefault(1);
307 g_return_val_if_fail (buffer != NULL, NULL);
309 doc = xmlParseMemory ((gchar *) buffer, length);
311 rdoc = sp_repr_do_read (doc, default_ns);
312 if (doc)
313 xmlFreeDoc (doc);
314 return rdoc;
315 }
317 namespace Inkscape {
319 struct compare_quark_ids {
320 bool operator()(Glib::QueryQuark const &a, Glib::QueryQuark const &b) const {
321 return a.id() < b.id();
322 }
323 };
325 }
327 namespace {
329 typedef std::map<Glib::QueryQuark, Glib::QueryQuark, Inkscape::compare_quark_ids> PrefixMap;
331 Glib::QueryQuark qname_prefix(Glib::QueryQuark qname) {
332 static PrefixMap prefix_map;
333 PrefixMap::iterator iter = prefix_map.find(qname);
334 if ( iter != prefix_map.end() ) {
335 return (*iter).second;
336 } else {
337 gchar const *name_string=g_quark_to_string(qname);
338 gchar const *prefix_end=strchr(name_string, ':');
339 if (prefix_end) {
340 Glib::Quark prefix=Glib::ustring(name_string, prefix_end);
341 prefix_map.insert(PrefixMap::value_type(qname, prefix));
342 return prefix;
343 } else {
344 return GQuark(0);
345 }
346 }
347 }
349 }
351 namespace {
353 void promote_to_svg_namespace(Node *repr) {
354 if ( repr->type() == Inkscape::XML::ELEMENT_NODE ) {
355 GQuark code = repr->code();
356 if (!qname_prefix(code).id()) {
357 gchar *svg_name = g_strconcat("svg:", g_quark_to_string(code), NULL);
358 repr->setCodeUnsafe(g_quark_from_string(svg_name));
359 g_free(svg_name);
360 }
361 for ( Node *child = sp_repr_children(repr) ; child ; child = sp_repr_next(child) ) {
362 promote_to_svg_namespace(child);
363 }
364 }
365 }
367 }
369 /**
370 * Reads in a XML file to create a Document
371 */
372 Document *
373 sp_repr_do_read (xmlDocPtr doc, const gchar *default_ns)
374 {
375 if (doc == NULL) return NULL;
376 xmlNodePtr node=xmlDocGetRootElement (doc);
377 if (node == NULL) return NULL;
379 GHashTable * prefix_map;
380 prefix_map = g_hash_table_new (g_str_hash, g_str_equal);
382 Document *rdoc = new Inkscape::XML::SimpleDocument();
384 Node *root=NULL;
385 for ( node = doc->children ; node != NULL ; node = node->next ) {
386 if (node->type == XML_ELEMENT_NODE) {
387 Node *repr=sp_repr_svg_read_node(rdoc, node, default_ns, prefix_map);
388 rdoc->appendChild(repr);
389 Inkscape::GC::release(repr);
391 if (!root) {
392 root = repr;
393 } else {
394 root = NULL;
395 break;
396 }
397 } else if ( node->type == XML_COMMENT_NODE ) {
398 Node *comment=sp_repr_svg_read_node(rdoc, node, default_ns, prefix_map);
399 rdoc->appendChild(comment);
400 Inkscape::GC::release(comment);
401 }
402 }
404 if (root != NULL) {
405 /* promote elements of SVG documents that don't use namespaces
406 * into the SVG namespace */
407 if ( default_ns && !strcmp(default_ns, SP_SVG_NS_URI)
408 && !strcmp(root->name(), "svg") )
409 {
410 promote_to_svg_namespace(root);
411 }
412 }
414 g_hash_table_destroy (prefix_map);
416 return rdoc;
417 }
419 gint
420 sp_repr_qualified_name (gchar *p, gint len, xmlNsPtr ns, const xmlChar *name, const gchar *default_ns, GHashTable *prefix_map)
421 {
422 const xmlChar *prefix;
423 if ( ns && ns->href ) {
424 prefix = (xmlChar*)sp_xml_ns_uri_prefix ((gchar*)ns->href, (char*)ns->prefix);
425 g_hash_table_insert (prefix_map, (gpointer)prefix, (gpointer)ns->href);
426 } else {
427 prefix = NULL;
428 }
430 if (prefix)
431 return g_snprintf (p, len, "%s:%s", (gchar*)prefix, name);
432 else
433 return g_snprintf (p, len, "%s", name);
434 }
436 static Node *
437 sp_repr_svg_read_node (Document *xml_doc, xmlNodePtr node, const gchar *default_ns, GHashTable *prefix_map)
438 {
439 Node *repr, *crepr;
440 xmlAttrPtr prop;
441 xmlNodePtr child;
442 gchar c[256];
444 if (node->type == XML_TEXT_NODE || node->type == XML_CDATA_SECTION_NODE) {
446 if (node->content == NULL || *(node->content) == '\0')
447 return NULL; // empty text node
449 bool preserve = (xmlNodeGetSpacePreserve (node) == 1);
451 xmlChar *p;
452 for (p = node->content; *p && g_ascii_isspace (*p) && !preserve; p++)
453 ; // skip all whitespace
455 if (!(*p)) { // this is an all-whitespace node, and preserve == default
456 return NULL; // we do not preserve all-whitespace nodes unless we are asked to
457 }
459 return xml_doc->createTextNode((const gchar *)node->content);
460 }
462 if (node->type == XML_COMMENT_NODE)
463 return xml_doc->createComment((const gchar *)node->content);
465 if (node->type == XML_ENTITY_DECL) return NULL;
467 sp_repr_qualified_name (c, 256, node->ns, node->name, default_ns, prefix_map);
468 repr = xml_doc->createElement(c);
469 /* TODO remember node->ns->prefix if node->ns != NULL */
471 for (prop = node->properties; prop != NULL; prop = prop->next) {
472 if (prop->children) {
473 sp_repr_qualified_name (c, 256, prop->ns, prop->name, default_ns, prefix_map);
474 repr->setAttribute(c, (gchar*)prop->children->content);
475 /* TODO remember prop->ns->prefix if prop->ns != NULL */
476 }
477 }
479 if (node->content)
480 repr->setContent((gchar*)node->content);
482 child = node->xmlChildrenNode;
483 for (child = node->xmlChildrenNode; child != NULL; child = child->next) {
484 crepr = sp_repr_svg_read_node (xml_doc, child, default_ns, prefix_map);
485 if (crepr) {
486 repr->appendChild(crepr);
487 Inkscape::GC::release(crepr);
488 }
489 }
491 return repr;
492 }
494 void
495 sp_repr_save_stream (Document *doc, FILE *fp, gchar const *default_ns, bool compress)
496 {
497 Node *repr;
498 const gchar *str;
500 Inkscape::URI dummy("x");
501 Inkscape::IO::UriOutputStream bout(fp, dummy);
502 Inkscape::IO::GzipOutputStream *gout = compress ? new Inkscape::IO::GzipOutputStream(bout) : NULL;
503 Inkscape::IO::OutputStreamWriter *out = compress ? new Inkscape::IO::OutputStreamWriter( *gout ) : new Inkscape::IO::OutputStreamWriter( bout );
505 int inlineattrs = prefs_get_int_attribute("options.svgoutput", "inlineattrs", 0);
506 int indent = prefs_get_int_attribute("options.svgoutput", "indent", 2);
508 /* fixme: do this The Right Way */
509 out->writeString( "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?>\n" );
511 str = ((Node *)doc)->attribute("doctype");
512 if (str) {
513 out->writeString( str );
514 }
516 repr = sp_repr_document_first_child(doc);
517 for ( repr = sp_repr_document_first_child(doc) ;
518 repr ; repr = sp_repr_next(repr) )
519 {
520 if ( repr->type() == Inkscape::XML::ELEMENT_NODE ) {
521 sp_repr_write_stream_root_element(repr, *out, TRUE, default_ns, inlineattrs, indent);
522 } else if ( repr->type() == Inkscape::XML::COMMENT_NODE ) {
523 sp_repr_write_stream(repr, *out, 0, TRUE, GQuark(0), inlineattrs, indent);
524 out->writeChar( '\n' );
525 } else {
526 sp_repr_write_stream(repr, *out, 0, TRUE, GQuark(0), inlineattrs, indent);
527 }
528 }
529 if ( out ) {
530 delete out;
531 out = NULL;
532 }
533 if ( gout ) {
534 delete gout;
535 gout = NULL;
536 }
537 }
539 /* Returns TRUE if file successfully saved; FALSE if not
540 */
541 bool
542 sp_repr_save_file (Document *doc, const gchar *filename,
543 gchar const *default_ns)
544 {
545 if (filename == NULL) {
546 return FALSE;
547 }
548 bool compress = false;
549 {
550 if (strlen (filename) > 5) {
551 gchar tmp[] = {0,0,0,0,0,0};
552 strncpy( tmp, filename + strlen (filename) - 5, 6 );
553 tmp[5] = 0;
554 if ( strcasecmp(".svgz", tmp ) == 0 )
555 {
556 //g_message("TIME TO COMPRESS THE OUTPUT FOR SVGZ");
557 compress = true;
558 }
559 }
560 }
562 Inkscape::IO::dump_fopen_call( filename, "B" );
563 FILE *file = Inkscape::IO::fopen_utf8name(filename, "w");
564 if (file == NULL) {
565 return FALSE;
566 }
568 sp_repr_save_stream (doc, file, default_ns, compress);
570 if (fclose (file) != 0) {
571 return FALSE;
572 }
574 return TRUE;
575 }
577 void
578 sp_repr_print (Node * repr)
579 {
580 Inkscape::IO::StdOutputStream bout;
581 Inkscape::IO::OutputStreamWriter out(bout);
583 sp_repr_write_stream (repr, out, 0, TRUE, GQuark(0), 0, 2);
585 return;
586 }
588 /* (No doubt this function already exists elsewhere.) */
589 static void
590 repr_quote_write (Writer &out, const gchar * val)
591 {
592 if (!val) return;
594 for (; *val != '\0'; val++) {
595 switch (*val) {
596 case '"': out.writeString( """ ); break;
597 case '&': out.writeString( "&" ); break;
598 case '<': out.writeString( "<" ); break;
599 case '>': out.writeString( ">" ); break;
600 default: out.writeChar( *val ); break;
601 }
602 }
603 }
605 namespace {
607 typedef std::map<Glib::QueryQuark, gchar const *, Inkscape::compare_quark_ids> LocalNameMap;
608 typedef std::map<Glib::QueryQuark, Inkscape::Util::ptr_shared<char>, Inkscape::compare_quark_ids> NSMap;
610 gchar const *qname_local_name(Glib::QueryQuark qname) {
611 static LocalNameMap local_name_map;
612 LocalNameMap::iterator iter = local_name_map.find(qname);
613 if ( iter != local_name_map.end() ) {
614 return (*iter).second;
615 } else {
616 gchar const *name_string=g_quark_to_string(qname);
617 gchar const *prefix_end=strchr(name_string, ':');
618 if (prefix_end) {
619 return prefix_end + 1;
620 } else {
621 return name_string;
622 }
623 }
624 }
626 void add_ns_map_entry(NSMap &ns_map, Glib::QueryQuark prefix) {
627 using Inkscape::Util::ptr_shared;
628 using Inkscape::Util::share_unsafe;
630 static const Glib::QueryQuark xml_prefix("xml");
632 NSMap::iterator iter=ns_map.find(prefix);
633 if ( iter == ns_map.end() ) {
634 if (prefix.id()) {
635 gchar const *uri=sp_xml_ns_prefix_uri(g_quark_to_string(prefix));
636 if (uri) {
637 ns_map.insert(NSMap::value_type(prefix, share_unsafe(uri)));
638 } else if ( prefix != xml_prefix ) {
639 g_warning("No namespace known for normalized prefix %s", g_quark_to_string(prefix));
640 }
641 } else {
642 ns_map.insert(NSMap::value_type(prefix, ptr_shared<char>()));
643 }
644 }
645 }
647 void populate_ns_map(NSMap &ns_map, Node &repr) {
648 if ( repr.type() == Inkscape::XML::ELEMENT_NODE ) {
649 add_ns_map_entry(ns_map, qname_prefix(repr.code()));
650 for ( List<AttributeRecord const> iter=repr.attributeList() ;
651 iter ; ++iter )
652 {
653 Glib::QueryQuark prefix=qname_prefix(iter->key);
654 if (prefix.id()) {
655 add_ns_map_entry(ns_map, prefix);
656 }
657 }
658 for ( Node *child=sp_repr_children(&repr) ;
659 child ; child = sp_repr_next(child) )
660 {
661 populate_ns_map(ns_map, *child);
662 }
663 }
664 }
666 }
668 void
669 sp_repr_write_stream_root_element (Node *repr, Writer &out, bool add_whitespace, gchar const *default_ns,
670 int inlineattrs, int indent)
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, inlineattrs, indent);
709 }
711 void
712 sp_repr_write_stream (Node *repr, Writer &out, gint indent_level,
713 bool add_whitespace, Glib::QueryQuark elide_prefix, int inlineattrs, int indent)
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(), inlineattrs, indent);
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 bool add_whitespace,
729 Glib::QueryQuark elide_prefix,
730 List<AttributeRecord const> attributes,
731 int inlineattrs, int indent)
732 {
733 Node *child;
734 bool loose;
736 g_return_if_fail (repr != NULL);
738 if ( indent_level > 16 )
739 indent_level = 16;
741 if (add_whitespace && indent) {
742 for (gint i = 0; i < indent_level; i++) {
743 for (gint j = 0; j < indent; j++) {
744 out.writeString(" ");
745 }
746 }
747 }
749 GQuark code = repr->code();
750 gchar const *element_name;
751 if ( elide_prefix == qname_prefix(code) ) {
752 element_name = qname_local_name(code);
753 } else {
754 element_name = g_quark_to_string(code);
755 }
756 out.printf( "<%s", element_name );
758 // if this is a <text> element, suppress formatting whitespace
759 // for its content and children:
760 gchar const *xml_space_attr = repr->attribute("xml:space");
761 if (xml_space_attr != NULL && !strcmp(xml_space_attr, "preserve")) {
762 add_whitespace = FALSE;
763 }
765 for ( List<AttributeRecord const> iter = attributes ;
766 iter ; ++iter )
767 {
768 if (!inlineattrs) {
769 out.writeString("\n");
770 if (indent) {
771 for ( gint i = 0 ; i < indent_level + 1 ; i++ ) {
772 for ( gint j = 0 ; j < indent ; j++ ) {
773 out.writeString(" ");
774 }
775 }
776 }
777 }
778 out.printf(" %s=\"", g_quark_to_string(iter->key));
779 repr_quote_write(out, iter->value);
780 out.writeChar('"');
781 }
783 loose = TRUE;
784 for (child = repr->firstChild() ; child != NULL; child = child->next()) {
785 if (child->type() == Inkscape::XML::TEXT_NODE) {
786 loose = FALSE;
787 break;
788 }
789 }
790 if (repr->firstChild()) {
791 out.writeString( ">" );
792 if (loose && add_whitespace) {
793 out.writeString( "\n" );
794 }
795 for (child = repr->firstChild(); child != NULL; child = child->next()) {
796 sp_repr_write_stream (child, out, (loose) ? (indent_level + 1) : 0, add_whitespace, elide_prefix, inlineattrs, indent);
797 }
799 if (loose && add_whitespace && indent) {
800 for (gint i = 0; i < indent_level; i++) {
801 for ( gint j = 0 ; j < indent ; j++ ) {
802 out.writeString(" ");
803 }
804 }
805 }
806 out.printf( "</%s>", element_name );
807 } else {
808 out.writeString( " />" );
809 }
811 // text elements cannot nest, so we can output newline
812 // after closing text
814 if (add_whitespace || !strcmp (repr->name(), "svg:text")) {
815 out.writeString( "\n" );
816 }
817 }
820 /*
821 Local Variables:
822 mode:c++
823 c-file-style:"stroustrup"
824 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
825 indent-tabs-mode:nil
826 fill-column:99
827 End:
828 */
829 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :