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