Inkscape
Vector Graphics Editor
Loading...
Searching...
No Matches
repr-io.cpp
Go to the documentation of this file.
1// SPDX-License-Identifier: GPL-2.0-or-later
2/*
3 * Dirty DOM-like tree
4 *
5 * Authors:
6 * Lauris Kaplinski <lauris@kaplinski.com>
7 * bulia byak <buliabyak@users.sf.net>
8 *
9 * Copyright (C) 1999-2002 Lauris Kaplinski
10 *
11 * Released under GNU GPL v2+, read the file 'COPYING' for more information.
12 */
13
14#include <cstring>
15#include <string>
16#include <stdexcept>
17
18#include <libxml/parser.h>
19#include <libxml/xinclude.h>
20
21#include "xml/repr.h"
23#include "xml/rebase-hrefs.h"
24#include "xml/simple-document.h"
25#include "xml/text-node.h"
26#include "xml/node.h"
27
28#include "io/sys.h"
31#include "io/stream/uristream.h"
32
33#include "extension/extension.h"
34
35#include "attribute-rel-util.h"
36#include "attribute-sort-util.h"
37
38#include "preferences.h"
39
40#include <glibmm/miscutils.h>
41
49
50Document *sp_repr_do_read (xmlDocPtr doc, const gchar *default_ns);
51static Node *sp_repr_svg_read_node (Document *xml_doc, xmlNodePtr node, const gchar *default_ns, std::map<std::string, std::string> &prefix_map);
52static gint sp_repr_qualified_name (gchar *p, gint len, xmlNsPtr ns, const xmlChar *name, const gchar *default_ns, std::map<std::string, std::string> &prefix_map);
53static void sp_repr_write_stream_root_element(Node *repr, Writer &out,
54 bool add_whitespace, gchar const *default_ns,
55 int inlineattrs, int indent,
56 gchar const *old_href_abs_base,
57 gchar const *new_href_abs_base);
58
59static void sp_repr_write_stream_element(Node *repr, Writer &out,
60 gint indent_level, bool add_whitespace,
61 Glib::QueryQuark elide_prefix,
62 const AttributeVector & attributes,
63 int inlineattrs, int indent,
64 gchar const *old_href_abs_base,
65 gchar const *new_href_abs_base);
66
67
68class XmlSource
69{
70public:
71 XmlSource()
72 : filename(nullptr),
73 encoding(nullptr),
74 fp(nullptr),
75 firstFewLen(0),
76 instr(nullptr),
77 gzin(nullptr)
78 {
79 for (unsigned char & k : firstFew)
80 {
81 k=0;
82 }
83 }
84 virtual ~XmlSource()
85 {
86 close();
87 if ( encoding ) {
88 g_free(encoding);
89 encoding = nullptr;
90 }
91 }
92
93 int setFile( char const * filename );
94
95 xmlDocPtr readXml();
96
97 static int readCb( void * context, char * buffer, int len );
98 static int closeCb( void * context );
99
100 char const* getEncoding() const { return encoding; }
101 int read( char * buffer, int len );
102 int close();
103private:
104 const char* filename;
105 char* encoding;
106 FILE* fp;
107 unsigned char firstFew[4];
108 int firstFewLen;
111};
112
113int XmlSource::setFile(char const *filename)
114{
115 int retVal = -1;
116
117 this->filename = filename;
118
119 fp = Inkscape::IO::fopen_utf8name(filename, "r");
120 if ( fp ) {
121 // First peek in the file to see what it is
122 memset( firstFew, 0, sizeof(firstFew) );
123
124 size_t some = fread( firstFew, 1, 4, fp );
125 if ( fp ) {
126 // first check for compression
127 if ( (some >= 2) && (firstFew[0] == 0x1f) && (firstFew[1] == 0x8b) ) {
128 //g_message(" the file being read is gzip'd. extract it");
129 fclose(fp);
130 fp = nullptr;
131 fp = Inkscape::IO::fopen_utf8name(filename, "r");
132 instr = new Inkscape::IO::FileInputStream(fp);
133 gzin = new Inkscape::IO::GzipInputStream(*instr);
134
135 memset( firstFew, 0, sizeof(firstFew) );
136 some = 0;
137 int single = 0;
138 while ( some < 4 && single >= 0 )
139 {
140 single = gzin->get();
141 if ( single >= 0 ) {
142 firstFew[some++] = 0x0ff & single;
143 } else {
144 break;
145 }
146 }
147 }
148
149 int encSkip = 0;
150 if ( (some >= 2) &&(firstFew[0] == 0xfe) && (firstFew[1] == 0xff) ) {
151 encoding = g_strdup("UTF-16BE");
152 encSkip = 2;
153 } else if ( (some >= 2) && (firstFew[0] == 0xff) && (firstFew[1] == 0xfe) ) {
154 encoding = g_strdup("UTF-16LE");
155 encSkip = 2;
156 } else if ( (some >= 3) && (firstFew[0] == 0xef) && (firstFew[1] == 0xbb) && (firstFew[2] == 0xbf) ) {
157 encoding = g_strdup("UTF-8");
158 encSkip = 3;
159 }
160
161 if ( encSkip ) {
162 memmove( firstFew, firstFew + encSkip, (some - encSkip) );
163 some -= encSkip;
164 }
165
166 firstFewLen = some;
167 retVal = 0; // no error
168 }
169 }
170 return retVal;
171}
172
173xmlDocPtr XmlSource::readXml()
174{
175 int parse_options = XML_PARSE_HUGE | XML_PARSE_RECOVER;
176
178 bool allowNetAccess = prefs->getBool("/options/externalresources/xml/allow_net_access", false);
179 if (!allowNetAccess) parse_options |= XML_PARSE_NONET;
180
181 return xmlReadIO(readCb, closeCb, this, filename, getEncoding(), parse_options);
182}
183
184int XmlSource::readCb( void * context, char * buffer, int len )
185{
186 int retVal = -1;
187
188 if ( context ) {
189 XmlSource* self = static_cast<XmlSource*>(context);
190 retVal = self->read( buffer, len );
191 }
192 return retVal;
193}
194
195int XmlSource::closeCb(void * context)
196{
197 if ( context ) {
198 XmlSource* self = static_cast<XmlSource*>(context);
199 self->close();
200 }
201 return 0;
202}
203
204int XmlSource::read( char *buffer, int len )
205{
206 int retVal = 0;
207 size_t got = 0;
208
209 if ( firstFewLen > 0 ) {
210 int some = (len < firstFewLen) ? len : firstFewLen;
211 memcpy( buffer, firstFew, some );
212 if ( len < firstFewLen ) {
213 memmove( firstFew, firstFew + some, (firstFewLen - some) );
214 }
215 firstFewLen -= some;
216 got = some;
217 } else if ( gzin ) {
218 int single = 0;
219 while ( (static_cast<int>(got) < len) && (single >= 0) )
220 {
221 single = gzin->get();
222 if ( single >= 0 ) {
223 buffer[got++] = 0x0ff & single;
224 } else {
225 break;
226 }
227 }
228 } else {
229 got = fread( buffer, 1, len, fp );
230 }
231
232 if ( feof(fp) ) {
233 retVal = got;
234 } else if ( ferror(fp) ) {
235 retVal = -1;
236 } else {
237 retVal = got;
238 }
239
240 return retVal;
241}
242
243int XmlSource::close()
244{
245 if ( gzin ) {
246 gzin->close();
247 delete gzin;
248 gzin = nullptr;
249 }
250 if ( instr ) {
251 instr->close();
252 fp = nullptr;
253 delete instr;
254 instr = nullptr;
255 }
256 if ( fp ) {
257 fclose(fp);
258 fp = nullptr;
259 }
260 return 0;
261}
262
274Document *sp_repr_read_file (const gchar * filename, const gchar *default_ns, bool xinclude)
275{
276 xmlDocPtr doc = nullptr;
277 Document * rdoc = nullptr;
278
279 xmlSubstituteEntitiesDefault(1);
280
281 g_return_val_if_fail(filename != nullptr, NULL);
282 if (!Inkscape::IO::file_test(filename, G_FILE_TEST_EXISTS)) {
283 g_warning("Can't open file: %s (doesn't exist)", filename);
284 return nullptr;
285 }
286 /* fixme: A file can disappear at any time, including between now and when we actually try to
287 * open it. Get rid of the above test once we're sure that we correctly handle
288 * non-existence. */
289
290 // TODO: bulia, please look over
291 gsize bytesRead = 0;
292 gsize bytesWritten = 0;
293 GError* error = nullptr;
294 // TODO: need to replace with our own fopen and reading
295 gchar* localFilename = g_filename_from_utf8(filename, -1, &bytesRead, &bytesWritten, &error);
296 g_return_val_if_fail(localFilename != nullptr, NULL);
297
298 Inkscape::IO::dump_fopen_call(filename, "N");
299
300 XmlSource src;
301
302 if (src.setFile(filename) == 0) {
303 doc = src.readXml();
304 if (xinclude && doc && doc->properties && xmlXIncludeProcessFlags(doc, XML_PARSE_NOXINCNODE) < 0) {
305 g_warning("XInclude processing failed for %s", filename);
306 }
307 rdoc = sp_repr_do_read(doc, default_ns);
308 }
309
310 if (doc) {
311 xmlFreeDoc(doc);
312 }
313
314 if (localFilename) {
315 g_free(localFilename);
316 }
317
318 return rdoc;
319}
320
324Document *sp_repr_read_mem (const gchar * buffer, gint length, const gchar *default_ns)
325{
326 xmlDocPtr doc;
327 Document * rdoc;
328
329 xmlSubstituteEntitiesDefault(1);
330
331 g_return_val_if_fail (buffer != nullptr, NULL);
332
333 int parser_options = XML_PARSE_HUGE | XML_PARSE_RECOVER;
334 parser_options |= XML_PARSE_NONET; // TODO: should we allow network access?
335 // proper solution would be to check the preference "/options/externalresources/xml/allow_net_access"
336 // as done in XmlSource::readXml which gets called by the analogous sp_repr_read_file()
337 // but sp_repr_read_mem() seems to be called in locations where Inkscape::Preferences::get() fails badly
338 doc = xmlReadMemory (const_cast<gchar *>(buffer), length, nullptr, nullptr, parser_options);
339
340 rdoc = sp_repr_do_read (doc, default_ns);
341 if (doc) {
342 xmlFreeDoc (doc);
343 }
344 return rdoc;
345}
346
350Document *sp_repr_read_buf (const Glib::ustring &buf, const gchar *default_ns)
351{
352 return sp_repr_read_mem(buf.c_str(), buf.size(), default_ns);
353}
354
355
356namespace Inkscape {
357
358struct compare_quark_ids {
359 bool operator()(Glib::QueryQuark const &a, Glib::QueryQuark const &b) const {
360 return a.id() < b.id();
361 }
362};
363
364}
365
366namespace {
367
368typedef std::map<Glib::QueryQuark, Glib::QueryQuark, Inkscape::compare_quark_ids> PrefixMap;
369
370Glib::QueryQuark qname_prefix(Glib::QueryQuark qname) {
371 static PrefixMap prefix_map;
372 PrefixMap::iterator iter = prefix_map.find(qname);
373 if ( iter != prefix_map.end() ) {
374 return (*iter).second;
375 } else {
376 gchar const *name_string=g_quark_to_string(qname);
377 gchar const *prefix_end=strchr(name_string, ':');
378 if (prefix_end) {
379 Glib::Quark prefix=Glib::ustring(name_string, prefix_end);
380 prefix_map.insert(PrefixMap::value_type(qname, prefix));
381 return prefix;
382 } else {
383 return GQuark(0);
384 }
385 }
386}
387
388}
389
390namespace {
391
392void promote_to_namespace(Node *repr, const gchar *prefix) {
394 GQuark code = repr->code();
395 if (!qname_prefix(code).id()) {
396 gchar *svg_name = g_strconcat(prefix, ":", g_quark_to_string(code), nullptr);
397 repr->setCodeUnsafe(g_quark_from_string(svg_name));
398 g_free(svg_name);
399 }
400 for ( Node *child = repr->firstChild() ; child ; child = child->next() ) {
401 promote_to_namespace(child, prefix);
402 }
403 }
404}
405
413void repair_namespace(Node *repr, const gchar *prefix) {
415 gchar *svg_name = nullptr;
416 if (strncmp(repr->name(), "ns:", 3) == 0) {
417 svg_name = g_strconcat(prefix, ":", repr->name() + 3, nullptr);
418 } else if (strncmp(repr->name(), "svg0:", 5) == 0) {
419 svg_name = g_strconcat(prefix, ":", repr->name() + 5, nullptr);
420 }
421 if (svg_name) {
422 repr->setCodeUnsafe(g_quark_from_string(svg_name));
423 g_free(svg_name);
424 }
425 for ( Node *child = repr->firstChild() ; child ; child = child->next() ) {
426 repair_namespace(child, prefix);
427 }
428 }
429}
430
431}
432
436Document *sp_repr_do_read (xmlDocPtr doc, const gchar *default_ns)
437{
438 if (doc == nullptr) {
439 return nullptr;
440 }
441 xmlNodePtr node=xmlDocGetRootElement (doc);
442 if (node == nullptr) {
443 return nullptr;
444 }
445
446 std::map<std::string, std::string> prefix_map;
447
449
450 Node *root=nullptr;
451 for ( node = doc->children ; node != nullptr ; node = node->next ) {
452 if (node->type == XML_ELEMENT_NODE) {
453 Node *repr=sp_repr_svg_read_node(rdoc, node, default_ns, prefix_map);
454 rdoc->appendChild(repr);
456
457 if (!root) {
458 root = repr;
459 } else {
460 root = nullptr;
461 break;
462 }
463 } else if ( node->type == XML_COMMENT_NODE || node->type == XML_PI_NODE ) {
464 Node *repr=sp_repr_svg_read_node(rdoc, node, default_ns, prefix_map);
465 rdoc->appendChild(repr);
467 }
468 }
469
470 if (root != nullptr) {
471 /* promote elements of some XML documents that don't use namespaces
472 * into their default namespace */
473 if (!strcmp(root->name(), "ns:svg") || !strcmp(root->name(), "svg0:svg")) {
474 g_warning("Detected broken namespace \"%s\" in the SVG file, attempting to work around it", root->name());
475 repair_namespace(root, "svg");
476 } else if ( default_ns && !strchr(root->name(), ':') ) {
477 if ( !strcmp(default_ns, SP_SVG_NS_URI) ) {
478 promote_to_namespace(root, "svg");
479 }
480 if ( !strcmp(default_ns, INKSCAPE_EXTENSION_URI) ) {
481 promote_to_namespace(root, INKSCAPE_EXTENSION_NS_NC);
482 }
483 }
484
485
486 // Clean unnecessary attributes and style properties from SVG documents. (Controlled by
487 // preferences.) Note: internal Inkscape svg files will also be cleaned (filters.svg,
488 // icons.svg). How can one tell if a file is internal?
489 if ( !strcmp(root->name(), "svg:svg" ) ) {
491 bool clean = prefs->getBool("/options/svgoutput/check_on_reading");
492 if( clean ) {
494 }
495 }
496 }
497
498 return rdoc;
499}
500
501gint sp_repr_qualified_name (gchar *p, gint len, xmlNsPtr ns, const xmlChar *name, const gchar */*default_ns*/, std::map<std::string, std::string> &prefix_map)
502{
503 const xmlChar *prefix;
504 if (ns){
505 if (ns->href ) {
506 prefix = reinterpret_cast<const xmlChar*>( sp_xml_ns_uri_prefix(reinterpret_cast<const gchar*>(ns->href),
507 reinterpret_cast<const char*>(ns->prefix)) );
508 prefix_map[reinterpret_cast<const char*>(prefix)] = reinterpret_cast<const char*>(ns->href);
509 }
510 else {
511 prefix = nullptr;
512 }
513 }
514 else {
515 prefix = nullptr;
516 }
517
518 if (prefix) {
519 return g_snprintf (p, len, "%s:%s", reinterpret_cast<const gchar*>(prefix), name);
520 } else {
521 return g_snprintf (p, len, "%s", name);
522 }
523}
524
525static Node *sp_repr_svg_read_node (Document *xml_doc, xmlNodePtr node, const gchar *default_ns, std::map<std::string, std::string> &prefix_map)
526{
527 xmlAttrPtr prop;
528 xmlNodePtr child;
529 gchar c[256];
530
531 if (node->type == XML_TEXT_NODE || node->type == XML_CDATA_SECTION_NODE) {
532
533 if (node->content == nullptr || *(node->content) == '\0') {
534 return nullptr; // empty text node
535 }
536
537 // Since libxml2 2.9.0, only element nodes are checked, thus check parent.
538 // Note: this only handles XML's rules for white space. SVG's specific rules
539 // are handled in sp-string.cpp.
540 bool preserve = (xmlNodeGetSpacePreserve (node->parent) == 1);
541
542 xmlChar *p;
543 for (p = node->content; *p && g_ascii_isspace (*p) && !preserve; p++)
544 ; // skip all whitespace
545
546 if (!(*p)) { // this is an all-whitespace node, and preserve == default
547 return nullptr; // we do not preserve all-whitespace nodes unless we are asked to
548 }
549
550 // We keep track of original node type so that CDATA sections are preserved on output.
551 return xml_doc->createTextNode(reinterpret_cast<gchar *>(node->content),
552 node->type == XML_CDATA_SECTION_NODE );
553 }
554
555 if (node->type == XML_COMMENT_NODE) {
556 return xml_doc->createComment(reinterpret_cast<gchar *>(node->content));
557 }
558
559 if (node->type == XML_PI_NODE) {
560 return xml_doc->createPI(reinterpret_cast<const gchar *>(node->name),
561 reinterpret_cast<const gchar *>(node->content));
562 }
563
564 if (node->type == XML_ENTITY_DECL) {
565 return nullptr;
566 }
567
568 sp_repr_qualified_name (c, 256, node->ns, node->name, default_ns, prefix_map);
569 Node *repr = xml_doc->createElement(c);
570 /* TODO remember node->ns->prefix if node->ns != NULL */
571
572 for (prop = node->properties; prop != nullptr; prop = prop->next) {
573 if (prop->children) {
574 sp_repr_qualified_name (c, 256, prop->ns, prop->name, default_ns, prefix_map);
575 repr->setAttribute(c, reinterpret_cast<gchar*>(prop->children->content));
576 /* TODO remember prop->ns->prefix if prop->ns != NULL */
577 }
578 }
579
580 if (node->content) {
581 repr->setContent(reinterpret_cast<gchar*>(node->content));
582 }
583
584 for (child = node->xmlChildrenNode; child != nullptr; child = child->next) {
585 Node *crepr = sp_repr_svg_read_node (xml_doc, child, default_ns, prefix_map);
586 if (crepr) {
587 repr->appendChild(crepr);
589 }
590 }
591
592 return repr;
593}
594
595
597 gchar const *default_ns,
598 gchar const *old_href_abs_base,
599 gchar const *new_href_abs_base)
600{
602 bool inlineattrs = prefs->getBool("/options/svgoutput/inlineattrs");
603 int indent = prefs->getInt("/options/svgoutput/indent", 2);
604
605 /* fixme: do this The Right Way */
606 out->writeString( "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?>\n" );
607
608 const gchar *str = static_cast<Node *>(doc)->attribute("doctype");
609 if (str) {
610 out->writeString( str );
611 }
612
613 for (Node *repr = sp_repr_document_first_child(doc);
614 repr; repr = repr->next())
615 {
616 Inkscape::XML::NodeType const node_type = repr->type();
617 if ( node_type == Inkscape::XML::NodeType::ELEMENT_NODE ) {
618 sp_repr_write_stream_root_element(repr, *out, TRUE, default_ns, inlineattrs, indent,
619 old_href_abs_base, new_href_abs_base);
620 } else {
621 sp_repr_write_stream(repr, *out, 0, TRUE, GQuark(0), inlineattrs, indent,
622 old_href_abs_base, new_href_abs_base);
623 if ( node_type == Inkscape::XML::NodeType::COMMENT_NODE ) {
624 out->writeChar('\n');
625 }
626 }
627 }
628}
629
630
631Glib::ustring sp_repr_save_buf(Document *doc)
632{
635
636 sp_repr_save_writer(doc, &outs, SP_INKSCAPE_NS_URI, nullptr, nullptr);
637
638 outs.close();
639 Glib::ustring buf = souts.getString();
640
641 return buf;
642}
643
644
645void sp_repr_save_stream(Document *doc, FILE *fp, gchar const *default_ns, bool compress,
646 gchar const *const old_href_abs_base,
647 gchar const *const new_href_abs_base)
648{
650 Inkscape::IO::GzipOutputStream *gout = compress ? new Inkscape::IO::GzipOutputStream(bout) : nullptr;
652
653 sp_repr_save_writer(doc, out, default_ns, old_href_abs_base, new_href_abs_base);
654
655 delete out;
656 delete gout;
657}
658
659
660
669bool sp_repr_save_rebased_file(Document *doc, gchar const *const filename, gchar const *default_ns,
670 gchar const *old_base, gchar const *for_filename)
671{
672 if (!filename) {
673 return false;
674 }
675
676 bool compress;
677 {
678 size_t const filename_len = strlen(filename);
679 compress = ( filename_len > 5
680 && strcasecmp(".svgz", filename + filename_len - 5) == 0 );
681 }
682
683 Inkscape::IO::dump_fopen_call( filename, "B" );
684 FILE *file = Inkscape::IO::fopen_utf8name(filename, "w");
685 if (file == nullptr) {
686 return false;
687 }
688
689 std::string old_href_abs_base;
690 std::string new_href_abs_base;
691
692 if (old_base) {
693 old_href_abs_base = old_base;
694 if (!Glib::path_is_absolute(old_href_abs_base)) {
695 old_href_abs_base = Glib::build_filename(Glib::get_current_dir(), old_href_abs_base);
696 }
697 }
698
699 if (for_filename) {
700 if (Glib::path_is_absolute(for_filename)) {
701 new_href_abs_base = Glib::path_get_dirname(for_filename);
702 } else {
703 std::string const cwd = Glib::get_current_dir();
704 std::string const for_abs_filename = Glib::build_filename(cwd, for_filename);
705 new_href_abs_base = Glib::path_get_dirname(for_abs_filename);
706 }
707
708 /* effic: Once we're confident that we never need (or never want) to resort
709 * to using sodipodi:absref instead of the xlink:href value,
710 * then we should do `if streq() { free them and set both to NULL; }'. */
711 }
712 sp_repr_save_stream(doc, file, default_ns, compress, old_href_abs_base.c_str(), new_href_abs_base.c_str());
713
714 if (fclose (file) != 0) {
715 return false;
716 }
717
718 return true;
719}
720
724bool sp_repr_save_file(Document *doc, gchar const *const filename, gchar const *default_ns)
725{
726 return sp_repr_save_rebased_file(doc, filename, default_ns, nullptr, nullptr);
727}
728
729
730/* (No doubt this function already exists elsewhere.) */
731static void repr_quote_write (Writer &out, const gchar * val, bool attr)
732{
733 if (val) {
734 for (; *val != '\0'; val++) {
735 switch (*val) {
736 case '"': out.writeString( "&quot;" ); break;
737 case '&': out.writeString( "&amp;" ); break;
738 case '<': out.writeString( "&lt;" ); break;
739 case '>': out.writeString( "&gt;" ); break;
740 case '\n': out.writeString( attr ? "&#10;" : "\n" ); break;
741 default: out.writeChar( *val ); break;
742 }
743 }
744 }
745}
746
747static void repr_write_comment( Writer &out, const gchar * val, bool addWhitespace, gint indentLevel, int indent )
748{
749 if ( indentLevel > 16 ) {
750 indentLevel = 16;
751 }
752 if (addWhitespace && indent) {
753 for (gint i = 0; i < indentLevel; i++) {
754 for (gint j = 0; j < indent; j++) {
755 out.writeChar(' ');
756 }
757 }
758 }
759
760 out.printf("<!--%s-->", val);
761
762 if (addWhitespace) {
763 out.writeChar('\n');
764 }
765}
766
767namespace {
768
769typedef std::map<Glib::QueryQuark, gchar const *, Inkscape::compare_quark_ids> LocalNameMap;
770typedef std::map<Glib::QueryQuark, Inkscape::Util::ptr_shared, Inkscape::compare_quark_ids> NSMap;
771
772gchar const *qname_local_name(Glib::QueryQuark qname) {
773 static LocalNameMap local_name_map;
774 LocalNameMap::iterator iter = local_name_map.find(qname);
775 if ( iter != local_name_map.end() ) {
776 return (*iter).second;
777 } else {
778 gchar const *name_string=g_quark_to_string(qname);
779 gchar const *prefix_end=strchr(name_string, ':');
780 if (prefix_end) {
781 return prefix_end + 1;
782 } else {
783 return name_string;
784 }
785 }
786}
787
788void add_ns_map_entry(NSMap &ns_map, Glib::QueryQuark prefix) {
791
792 static const Glib::QueryQuark xml_prefix("xml");
793
794 NSMap::iterator iter=ns_map.find(prefix);
795 if ( iter == ns_map.end() ) {
796 if (prefix.id()) {
797 gchar const *uri=sp_xml_ns_prefix_uri(g_quark_to_string(prefix));
798 if (uri) {
799 ns_map.insert(NSMap::value_type(prefix, share_unsafe(uri)));
800 } else if ( prefix != xml_prefix ) {
801 g_warning("No namespace known for normalized prefix %s", g_quark_to_string(prefix));
802 }
803 } else {
804 ns_map.insert(NSMap::value_type(prefix, ptr_shared()));
805 }
806 }
807}
808
809void populate_ns_map(NSMap &ns_map, Node &repr) {
811 add_ns_map_entry(ns_map, qname_prefix(repr.code()));
812 for ( const auto & iter : repr.attributeList() )
813 {
814 Glib::QueryQuark prefix=qname_prefix(iter.key);
815 if (prefix.id()) {
816 add_ns_map_entry(ns_map, prefix);
817 }
818 }
819 for ( Node *child=repr.firstChild() ;
820 child ; child = child->next() )
821 {
822 populate_ns_map(ns_map, *child);
823 }
824 }
825}
826
827}
828
830 bool add_whitespace, gchar const *default_ns,
831 int inlineattrs, int indent,
832 gchar const *const old_href_base,
833 gchar const *const new_href_base)
834{
836
837 g_assert(repr != nullptr);
838
839 // Clean unnecessary attributes and stype properties. (Controlled by preferences.)
841 bool clean = prefs->getBool("/options/svgoutput/check_on_writing");
842 if (clean) sp_attribute_clean_tree( repr );
843
844 // Sort attributes in a canonical order (helps with "diffing" SVG files).only if not set disable optimizations
845 bool sort = !prefs->getBool("/options/svgoutput/disable_optimizations") && prefs->getBool("/options/svgoutput/sort_attributes");
846 if (sort) sp_attribute_sort_tree( *repr );
847
848 Glib::QueryQuark xml_prefix=g_quark_from_static_string("xml");
849
850 NSMap ns_map;
851 populate_ns_map(ns_map, *repr);
852
853 Glib::QueryQuark elide_prefix=GQuark(0);
854 if ( default_ns && ns_map.find(GQuark(0)) == ns_map.end() ) {
855 elide_prefix = g_quark_from_string(sp_xml_ns_uri_prefix(default_ns, nullptr));
856 }
857
858 auto attributes = repr->attributeList(); // copy
859
861 for (auto iter : ns_map)
862 {
863 Glib::QueryQuark prefix=iter.first;
864 ptr_shared ns_uri=iter.second;
865
866 if (prefix.id()) {
867 if ( prefix != xml_prefix ) {
868 if ( elide_prefix == prefix ) {
869 //repr->setAttribute(share_string("xmlns"), share_string(ns_uri));
870 attributes.emplace_back(g_quark_from_static_string("xmlns"), ns_uri);
871 }
872
873 Glib::ustring attr_name="xmlns:";
874 attr_name.append(g_quark_to_string(prefix));
875 GQuark key = g_quark_from_string(attr_name.c_str());
876 //repr->setAttribute(share_string(attr_name.c_str()), share_string(ns_uri));
877 attributes.emplace_back(key, ns_uri);
878 }
879 } else {
880 // if there are non-namespaced elements, we can't globally
881 // use a default namespace
882 elide_prefix = GQuark(0);
883 }
884 }
885
886 return sp_repr_write_stream_element(repr, out, 0, add_whitespace, elide_prefix, attributes,
887 inlineattrs, indent, old_href_base, new_href_base);
888}
889
891 bool add_whitespace, Glib::QueryQuark elide_prefix,
892 int inlineattrs, int indent,
893 gchar const *const old_href_base,
894 gchar const *const new_href_base)
895{
896 switch (repr->type()) {
898 auto textnode = dynamic_cast<const Inkscape::XML::TextNode *>(repr);
899 assert(textnode);
900 if (textnode->is_CData()) {
901 // Preserve CDATA sections, not converting '&' to &amp;, etc.
902 out.printf( "<![CDATA[%s]]>", repr->content() );
903 } else {
904 repr_quote_write( out, repr->content(), false );
905 }
906 break;
907 }
909 repr_write_comment( out, repr->content(), add_whitespace, indent_level, indent );
910 break;
911 }
913 out.printf( "<?%s %s?>", repr->name(), repr->content() );
914 break;
915 }
918 add_whitespace, elide_prefix,
919 repr->attributeList(),
920 inlineattrs, indent,
921 old_href_base, new_href_base);
922 break;
923 }
925 g_assert_not_reached();
926 break;
927 }
928 default: {
929 g_assert_not_reached();
930 }
931 }
932}
933
934Glib::ustring sp_repr_write_buf(Node *repr, int indent_level, bool add_whitespace, Glib::QueryQuark elide_prefix,
935 int inlineattrs, int indent, char const *old_href_base,
936 char const *new_href_base)
937{
940
941 sp_repr_write_stream(repr, outs, indent_level, add_whitespace, elide_prefix, inlineattrs, indent, old_href_base,
942 new_href_base);
943
944 outs.close();
945 return souts.getString();
946}
947
949 gint indent_level, bool add_whitespace,
950 Glib::QueryQuark elide_prefix,
951 const AttributeVector & attributes,
952 int inlineattrs, int indent,
953 gchar const *old_href_base,
954 gchar const *new_href_base )
955{
956 Node *child = nullptr;
957 bool loose = false;
958 bool const add_whitespace_parent = add_whitespace;
959
960 g_return_if_fail (repr != nullptr);
961
962 if ( indent_level > 16 ) {
963 indent_level = 16;
964 }
965
966 if (add_whitespace && indent) {
967 for (gint i = 0; i < indent_level; i++) {
968 for (gint j = 0; j < indent; j++) {
969 out.writeChar(' ');
970 }
971 }
972 }
973
974 GQuark code = repr->code();
975 gchar const *element_name;
976 if ( elide_prefix == qname_prefix(code) ) {
977 element_name = qname_local_name(code);
978 } else {
979 element_name = g_quark_to_string(code);
980 }
981 out.printf( "<%s", element_name );
982
983 // If this is a <text> element, suppress formatting whitespace
984 // for its content and children:
985 if (strcmp(repr->name(), "svg:text") == 0 ||
986 strcmp(repr->name(), "svg:flowRoot") == 0) {
987 add_whitespace = false;
988 } else {
989 // Suppress formatting whitespace for xml:space="preserve"
990 gchar const *xml_space_attr = repr->attribute("xml:space");
991 if (g_strcmp0(xml_space_attr, "preserve") == 0) {
992 add_whitespace = false;
993 } else if (g_strcmp0(xml_space_attr, "default") == 0) {
994 add_whitespace = true;
995 }
996 }
997
998 const auto rbd = rebase_href_attrs(old_href_base, new_href_base, attributes);
999 for (const auto &iter : rbd) {
1000 if (!inlineattrs) {
1001 out.writeChar('\n');
1002 if (indent) {
1003 for ( gint i = 0 ; i < indent_level + 1 ; i++ ) {
1004 for ( gint j = 0 ; j < indent ; j++ ) {
1005 out.writeChar(' ');
1006 }
1007 }
1008 }
1009 }
1010 out.printf(" %s=\"", g_quark_to_string(iter.key));
1011 repr_quote_write(out, iter.value, true);
1012 out.writeChar('"');
1013 }
1014
1015 loose = TRUE;
1016 for (child = repr->firstChild() ; child != nullptr; child = child->next()) {
1018 loose = FALSE;
1019 break;
1020 }
1021 }
1022
1023 if (repr->firstChild()) {
1024 out.writeChar('>');
1025 if (loose && add_whitespace) {
1026 out.writeChar('\n');
1027 }
1028 for (child = repr->firstChild(); child != nullptr; child = child->next()) {
1029 sp_repr_write_stream(child, out, ( loose ? indent_level + 1 : 0 ),
1030 add_whitespace, elide_prefix, inlineattrs, indent,
1031 old_href_base, new_href_base);
1032 }
1033
1034 if (loose && add_whitespace && indent) {
1035 for (gint i = 0; i < indent_level; i++) {
1036 for ( gint j = 0 ; j < indent ; j++ ) {
1037 out.writeChar(' ');
1038 }
1039 }
1040 }
1041 out.printf( "</%s>", element_name );
1042 } else {
1043 out.writeString( " />" );
1044 }
1045
1046 if (add_whitespace_parent) {
1047 out.writeChar('\n');
1048 }
1049}
1050
1051
1052/*
1053 Local Variables:
1054 mode:c++
1055 c-file-style:"stroustrup"
1056 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
1057 indent-tabs-mode:nil
1058 fill-column:99
1059 End:
1060*/
1061// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 :
TODO: insert short description here.
void sp_attribute_clean_tree(Node *repr)
Remove or warn about inappropriate attributes and useless stype properties.
Utility functions related to parsing and validation of XML attributes.
void sp_attribute_sort_tree(Node &repr)
Sort attributes by name.
TODO: insert short description here.
Cairo::RefPtr< Cairo::Region > clean
Definition canvas.cpp:183
This class is for receiving a stream of data from a file.
Definition uristream.h:40
This class is for sending a stream to a destination file.
Definition uristream.h:69
This class is for deflating a gzip-compressed InputStream source.
Definition gzipstream.h:39
This class is for gzip-compressing data going to the destination OutputStream.
Definition gzipstream.h:86
Class for placing a Writer on an open OutputStream.
void close() override
Close the underlying OutputStream.
This class is for sending a stream to a Glib::ustring.
virtual Glib::ustring & getString()
This interface and its descendants are for unicode character-oriented output.
virtual Writer & printf(char const *fmt,...) G_GNUC_PRINTF(2
virtual Writer & writeString(const char *str)=0
virtual Writer virtual Writer & writeChar(char val)=0
Preference storage class.
Definition preferences.h:61
bool getBool(Glib::ustring const &pref_path, bool def=false)
Retrieve a Boolean value.
static Preferences * get()
Access the singleton Preferences object.
int getInt(Glib::ustring const &pref_path, int def=0)
Retrieve an integer.
Key-value pair representing an attribute.
Interface for refcounted XML nodes.
Definition node.h:80
virtual Node * parent()=0
Get the parent of this node.
virtual Node * next()=0
Get the next sibling of this node.
virtual void setCodeUnsafe(int code)=0
Set the integer GQuark code for the name of the node.
virtual int code() const =0
Get the integer code corresponding to the node's name.
virtual void appendChild(Node *child)=0
Append a node as the last child of this node.
virtual char const * name() const =0
Get the name of the element node.
virtual const AttributeVector & attributeList() const =0
Get a list of the node's attributes.
void setAttribute(Util::const_char_ptr key, Util::const_char_ptr value)
Change an attribute of this node.
Definition node.cpp:25
virtual Node * firstChild()=0
Get the first child of this node.
virtual void setContent(char const *value)=0
Set the content of a text or comment node.
virtual char const * attribute(char const *key) const =0
Get the string representation of a node's attribute.
virtual char const * content() const =0
Get the content of a text or comment node.
virtual NodeType type() const =0
Get the type of the node.
RootCluster root
double c[8][4]
Inkscape::Extension::Extension: Frontend to certain, possibly pluggable, actions.
Zlib-enabled input and output streams.
Inkscape::XML::Node * node
TODO: insert short description here.
static R & release(R &r)
Decrements the reference count of a anchored object.
bool file_test(char const *utf8name, GFileTest test)
Definition sys.cpp:116
void dump_fopen_call(char const *utf8name, char const *id)
Definition sys.cpp:37
FILE * fopen_utf8name(char const *utf8name, char const *mode)
Open a file with g_fopen().
Definition sys.cpp:72
ptr_shared share_unsafe(char const *string)
Definition share.h:90
ptr_shared share_string(char const *string)
Definition share.cpp:20
NodeType
Enumeration containing all supported node types.
Definition node.h:40
@ DOCUMENT_NODE
Top-level document node. Do not confuse with the root node.
@ PI_NODE
Processing instruction node, e.g. <?xml version="1.0" encoding="utf-8" standalone="no"?...
@ COMMENT_NODE
Comment node, e.g. <!– some comment –>.
@ ELEMENT_NODE
Regular element node, e.g. <group />.
@ TEXT_NODE
Text node, e.g. "Some text" in <group>Some text</group> is represented by a text node.
AttributeVector rebase_href_attrs(char const *old_abs_base, char const *new_abs_base, const AttributeVector &attributes)
Change relative xlink:href attributes to be relative to new_abs_base instead of old_abs_base.
std::vector< AttributeRecord, Inkscape::GC::Alloc< AttributeRecord > > AttributeVector
Definition node.h:34
Helper class to stream background task notifications as a series of messages.
static cairo_user_data_key_t key
int buf
Singleton class to access the preferences file in a convenient way.
Ocnode * child[8]
Definition quantize.cpp:33
TODO: insert short description here.
Document * sp_repr_read_file(const gchar *filename, const gchar *default_ns, bool xinclude)
Reads XML from a file, and returns the Document.
Definition repr-io.cpp:274
static void sp_repr_save_writer(Document *doc, Inkscape::IO::Writer *out, gchar const *default_ns, gchar const *old_href_abs_base, gchar const *new_href_abs_base)
Definition repr-io.cpp:596
bool sp_repr_save_rebased_file(Document *doc, gchar const *const filename, gchar const *default_ns, gchar const *old_base, gchar const *for_filename)
Returns true if file successfully saved.
Definition repr-io.cpp:669
static void repr_write_comment(Writer &out, const gchar *val, bool addWhitespace, gint indentLevel, int indent)
Definition repr-io.cpp:747
Document * sp_repr_read_mem(const gchar *buffer, gint length, const gchar *default_ns)
Reads and parses XML from a buffer, returning it as an Document.
Definition repr-io.cpp:324
Document * sp_repr_do_read(xmlDocPtr doc, const gchar *default_ns)
Reads in a XML file to create a Document.
Definition repr-io.cpp:436
static void sp_repr_write_stream_root_element(Node *repr, Writer &out, bool add_whitespace, gchar const *default_ns, int inlineattrs, int indent, gchar const *old_href_abs_base, gchar const *new_href_abs_base)
Definition repr-io.cpp:829
static gint sp_repr_qualified_name(gchar *p, gint len, xmlNsPtr ns, const xmlChar *name, const gchar *default_ns, std::map< std::string, std::string > &prefix_map)
Definition repr-io.cpp:501
Document * sp_repr_read_buf(const Glib::ustring &buf, const gchar *default_ns)
Reads and parses XML from a buffer, returning it as an Document.
Definition repr-io.cpp:350
void sp_repr_write_stream(Node *repr, Writer &out, gint indent_level, bool add_whitespace, Glib::QueryQuark elide_prefix, int inlineattrs, int indent, gchar const *const old_href_base, gchar const *const new_href_base)
Definition repr-io.cpp:890
Glib::ustring sp_repr_save_buf(Document *doc)
Definition repr-io.cpp:631
Glib::ustring sp_repr_write_buf(Node *repr, int indent_level, bool add_whitespace, Glib::QueryQuark elide_prefix, int inlineattrs, int indent, char const *old_href_base, char const *new_href_base)
Definition repr-io.cpp:934
static void repr_quote_write(Writer &out, const gchar *val, bool attr)
Definition repr-io.cpp:731
bool sp_repr_save_file(Document *doc, gchar const *const filename, gchar const *default_ns)
Returns true iff file successfully saved.
Definition repr-io.cpp:724
static Node * sp_repr_svg_read_node(Document *xml_doc, xmlNodePtr node, const gchar *default_ns, std::map< std::string, std::string > &prefix_map)
Definition repr-io.cpp:525
void sp_repr_save_stream(Document *doc, FILE *fp, gchar const *default_ns, bool compress, gchar const *const old_href_abs_base, gchar const *const new_href_abs_base)
Definition repr-io.cpp:645
static void sp_repr_write_stream_element(Node *repr, Writer &out, gint indent_level, bool add_whitespace, Glib::QueryQuark elide_prefix, const AttributeVector &attributes, int inlineattrs, int indent, gchar const *old_href_abs_base, gchar const *new_href_abs_base)
Definition repr-io.cpp:948
gchar const * sp_xml_ns_uri_prefix(gchar const *uri, gchar const *suggested)
gchar const * sp_xml_ns_prefix_uri(gchar const *prefix)
C facade to Inkscape::XML::Node.
Inkscape::XML::Node * sp_repr_document_first_child(Inkscape::XML::Document const *doc)
Definition repr.h:169
auto len
Definition safe-printf.h:21
Inkscape::XML::SimpleDocument - generic XML document implementation.
guint32 GQuark
static unsigned indent_level
Definition sp-object.cpp:70
Interface for XML documents.
Definition document.h:43
virtual Node * createTextNode(char const *content)=0
virtual Node * createElement(char const *name)=0
virtual Node * createComment(char const *content)=0
virtual Node * createPI(char const *target, char const *content)=0
Text node, e.g.
Definition text-node.h:24
Text node implementation.
Glib::ustring name
Definition toolbars.cpp:55
This should be the only way that we provide sources/sinks to any input/output stream.
Interface for XML nodes.