summaryrefslogtreecommitdiffstats
path: root/src/xml
diff options
context:
space:
mode:
Diffstat (limited to 'src/xml')
-rw-r--r--src/xml/.cvsignore10
-rw-r--r--src/xml/Makefile_insert86
-rw-r--r--src/xml/attribute-record.h39
-rw-r--r--src/xml/comment-node.h52
-rw-r--r--src/xml/composite-node-observer.cpp308
-rw-r--r--src/xml/composite-node-observer.h86
-rw-r--r--src/xml/croco-node-iface.cpp68
-rw-r--r--src/xml/croco-node-iface.h12
-rw-r--r--src/xml/document.h50
-rw-r--r--src/xml/element-node.h49
-rw-r--r--src/xml/event-fns.h28
-rw-r--r--src/xml/event.cpp489
-rw-r--r--src/xml/event.h150
-rw-r--r--src/xml/invalid-operation-exception.h47
-rw-r--r--src/xml/log-builder.cpp78
-rw-r--r--src/xml/log-builder.h66
-rw-r--r--src/xml/makefile.in17
-rw-r--r--src/xml/node-event-vector.h47
-rw-r--r--src/xml/node-fns.cpp103
-rw-r--r--src/xml/node-fns.h44
-rw-r--r--src/xml/node-iterators.h61
-rw-r--r--src/xml/node-observer.h68
-rw-r--r--src/xml/node.h131
-rw-r--r--src/xml/quote-test.cpp82
-rw-r--r--src/xml/quote-test.h31
-rw-r--r--src/xml/quote.cpp86
-rw-r--r--src/xml/quote.h7
-rw-r--r--src/xml/repr-action-test.cpp77
-rw-r--r--src/xml/repr-action-test.h31
-rw-r--r--src/xml/repr-css.cpp261
-rw-r--r--src/xml/repr-io.cpp786
-rw-r--r--src/xml/repr-sorting.cpp53
-rw-r--r--src/xml/repr-sorting.h11
-rw-r--r--src/xml/repr-util.cpp553
-rw-r--r--src/xml/repr.cpp107
-rw-r--r--src/xml/repr.h261
-rw-r--r--src/xml/session.h56
-rw-r--r--src/xml/simple-document.cpp40
-rw-r--r--src/xml/simple-document.h57
-rw-r--r--src/xml/simple-node.cpp731
-rw-r--r--src/xml/simple-node.h167
-rw-r--r--src/xml/simple-session.cpp122
-rw-r--r--src/xml/simple-session.h84
-rw-r--r--src/xml/sp-css-attr.h33
-rw-r--r--src/xml/text-node.h52
-rw-r--r--src/xml/transaction-logger.h52
46 files changed, 5829 insertions, 0 deletions
diff --git a/src/xml/.cvsignore b/src/xml/.cvsignore
new file mode 100644
index 000000000..ae589847a
--- /dev/null
+++ b/src/xml/.cvsignore
@@ -0,0 +1,10 @@
+.deps
+.dirstamp
+.libs
+Makefile
+Makefile.in
+makefile
+*-test
+test-xml
+test-xml.cpp
+test-xml-main.cpp
diff --git a/src/xml/Makefile_insert b/src/xml/Makefile_insert
new file mode 100644
index 000000000..a678ca25c
--- /dev/null
+++ b/src/xml/Makefile_insert
@@ -0,0 +1,86 @@
+## Makefile.am fragment sourced by src/Makefile.am.
+
+#
+# Sodipodi xml wrapper
+# Author: Lauris Kaplinski <lauris@ariman.ee>
+#
+# Implements xml backbone of inkscape document. In future will be
+# repaced with (wrapper of?) DOM
+#
+
+xml/all: xml/libspxml.a
+
+xml/clean:
+ rm -f xml/libspxml.a xml/libtest-xml.a $(xml_libspxml_a_OBJECTS) $(xml_libtest_xml_a_OBJECTS)
+
+xml_libspxml_a_SOURCES = \
+ algorithms/find-if-before.h \
+ xml/comment-node.h \
+ xml/composite-node-observer.cpp xml/composite-node-observer.h \
+ xml/element-node.h \
+ xml/node-observer.h \
+ xml/quote.cpp \
+ xml/quote.h \
+ xml/repr-css.cpp \
+ xml/log-builder.cpp \
+ xml/log-builder.h \
+ xml/node-fns.cpp \
+ xml/node-fns.h \
+ xml/repr-io.cpp \
+ xml/repr-sorting.cpp \
+ xml/repr-sorting.h \
+ xml/repr-util.cpp \
+ xml/repr.cpp \
+ xml/repr.h \
+ xml/session.h \
+ xml/simple-document.h \
+ xml/simple-document.cpp \
+ xml/simple-node.h \
+ xml/simple-node.cpp \
+ xml/simple-session.cpp \
+ xml/simple-session.h \
+ xml/node.h \
+ xml/croco-node-iface.cpp \
+ xml/croco-node-iface.h \
+ xml/attribute-record.h \
+ xml/sp-css-attr.h \
+ xml/event.cpp xml/event.h xml/event-fns.h \
+ xml/document.h \
+ xml/node-event-vector.h \
+ xml/node-iterators.h \
+ xml/sp-css-attr.h \
+ xml/text-node.h \
+ xml/transaction-logger.h \
+ xml/invalid-operation-exception.h
+
+xml/test-xml-main.cpp: xml/test-xml.cpp $(xml_test_xml_includes)
+ $(top_srcdir)/cxxtest/cxxtestgen.pl --error-printer -root -o xml/test-xml-main.cpp $(xml_test_xml_includes)
+
+xml/test-xml.cpp: $(xml_test_xml_includes)
+ $(top_srcdir)/cxxtest/cxxtestgen.pl -part -o xml/test-xml.cpp $(xml_test_xml_includes)
+
+xml_test_xml_includes = \
+ $(srcdir)/xml/repr-action-test.h \
+ $(srcdir)/xml/quote-test.h
+
+xml_libtest_xml_a_SOURCES = \
+ xml/test-xml.cpp \
+ $(xml_test_xml_includes)
+
+xml_test_xml_SOURCES = \
+ xml/test-xml-main.cpp \
+ $(xml_test_xml_includes)
+
+xml_test_xml_LDADD = \
+ xml/libtest-xml.a \
+ xml/libspxml.a \
+ util/libinkutil.a \
+ libinkpost.a \
+ $(INKSCAPE_LIBS)
+
+
+xml_repr_action_test_SOURCES = xml/repr-action-test.cpp
+xml_repr_action_test_LDADD = xml/libspxml.a svg/libspsvg.a util/libinkutil.a libinkpost.a debug/libinkdebug.a $(INKSCAPE_LIBS)
+
+xml_quote_test_SOURCES = xml/quote-test.cpp
+xml_quote_test_LDADD = $(INKSCAPE_LIBS)
diff --git a/src/xml/attribute-record.h b/src/xml/attribute-record.h
new file mode 100644
index 000000000..dbad0f340
--- /dev/null
+++ b/src/xml/attribute-record.h
@@ -0,0 +1,39 @@
+#ifndef SEEN_XML_SP_REPR_ATTR_H
+#define SEEN_XML_SP_REPR_ATTR_H
+
+#include <glib/gquark.h>
+#include <glib/gtypes.h>
+#include "gc-managed.h"
+#include "util/shared-c-string-ptr.h"
+
+#define SP_REPR_ATTRIBUTE_KEY(a) g_quark_to_string((a)->key)
+#define SP_REPR_ATTRIBUTE_VALUE(a) ((a)->value)
+
+namespace Inkscape {
+namespace XML {
+
+struct AttributeRecord : public Inkscape::GC::Managed<> {
+ AttributeRecord(GQuark k, Inkscape::Util::SharedCStringPtr v)
+ : key(k), value(v) {}
+
+ GQuark key;
+ Inkscape::Util::SharedCStringPtr value;
+
+ // accept default copy constructor and assignment operator
+};
+
+}
+}
+
+#endif /* !SEEN_XML_SP_REPR_ATTR_H */
+
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
+ indent-tabs-mode:nil
+ fill-column:99
+ End:
+*/
+// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 :
diff --git a/src/xml/comment-node.h b/src/xml/comment-node.h
new file mode 100644
index 000000000..dccc5e864
--- /dev/null
+++ b/src/xml/comment-node.h
@@ -0,0 +1,52 @@
+/*
+ * Inkscape::XML::CommentNode - simple XML comment implementation
+ *
+ * Copyright 2004-2005 MenTaLguY <mental@rydia.net>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * See the file COPYING for details.
+ *
+ */
+
+#ifndef SEEN_INKSCAPE_XML_COMMENT_NODE_H
+#define SEEN_INKSCAPE_XML_COMMENT_NODE_H
+
+#include <glib/gquark.h>
+#include "xml/simple-node.h"
+
+namespace Inkscape {
+
+namespace XML {
+
+struct CommentNode : public SimpleNode {
+ explicit CommentNode(Util::SharedCStringPtr content)
+ : SimpleNode(g_quark_from_static_string("comment"))
+ {
+ setContent(content);
+ }
+
+ Inkscape::XML::NodeType type() const { return Inkscape::XML::COMMENT_NODE; }
+
+protected:
+ SimpleNode *_duplicate() const { return new CommentNode(*this); }
+};
+
+}
+
+}
+
+#endif
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
+ indent-tabs-mode:nil
+ fill-column:99
+ End:
+*/
+// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :
diff --git a/src/xml/composite-node-observer.cpp b/src/xml/composite-node-observer.cpp
new file mode 100644
index 000000000..97bce9360
--- /dev/null
+++ b/src/xml/composite-node-observer.cpp
@@ -0,0 +1,308 @@
+/*
+ * Inkscape::XML::CompositeNodeObserver - combine multiple observers
+ *
+ * Copyright 2005 MenTaLguY <mental@rydia.net>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * See the file COPYING for details.
+ *
+ */
+
+#include "algorithms/find-if-before.h"
+#include "xml/composite-node-observer.h"
+#include "xml/node-event-vector.h"
+#include "debug/event-tracker.h"
+#include "debug/simple-event.h"
+
+namespace Inkscape {
+
+namespace XML {
+
+void CompositeNodeObserver::notifyChildAdded(Node &node, Node &child, Node *prev)
+{
+ _startIteration();
+ for ( ObserverRecordList::iterator iter=_active.begin() ;
+ iter != _active.end() ; ++iter )
+ {
+ if (!iter->marked) {
+ iter->observer.notifyChildAdded(node, child, prev);
+ }
+ }
+ _finishIteration();
+}
+
+void CompositeNodeObserver::notifyChildRemoved(Node &node, Node &child,
+ Node *prev)
+{
+ _startIteration();
+ for ( ObserverRecordList::iterator iter=_active.begin() ;
+ iter != _active.end() ; ++iter )
+ {
+ if (!iter->marked) {
+ iter->observer.notifyChildRemoved(node, child, prev);
+ }
+ }
+ _finishIteration();
+}
+
+void CompositeNodeObserver::notifyChildOrderChanged(Node &node, Node &child,
+ Node *old_prev,
+ Node *new_prev)
+{
+ _startIteration();
+ for ( ObserverRecordList::iterator iter=_active.begin() ;
+ iter != _active.end() ; ++iter )
+ {
+ if (!iter->marked) {
+ iter->observer.notifyChildOrderChanged(node, child, old_prev, new_prev);
+ }
+ }
+ _finishIteration();
+}
+
+void CompositeNodeObserver::notifyContentChanged(
+ Node &node,
+ Util::SharedCStringPtr old_content, Util::SharedCStringPtr new_content
+) {
+ _startIteration();
+ for ( ObserverRecordList::iterator iter=_active.begin() ;
+ iter != _active.end() ; ++iter )
+ {
+ if (!iter->marked) {
+ iter->observer.notifyContentChanged(node, old_content, new_content);
+ }
+ }
+ _finishIteration();
+}
+
+void CompositeNodeObserver::notifyAttributeChanged(
+ Node &node, GQuark name,
+ Util::SharedCStringPtr old_value, Util::SharedCStringPtr new_value
+) {
+ _startIteration();
+ for ( ObserverRecordList::iterator iter=_active.begin() ;
+ iter != _active.end() ; ++iter )
+ {
+ if (!iter->marked) {
+ iter->observer.notifyAttributeChanged(node, name, old_value, new_value);
+ }
+ }
+ _finishIteration();
+}
+
+void CompositeNodeObserver::add(NodeObserver &observer) {
+ if (_iterating) {
+ _pending.push_back(ObserverRecord(observer));
+ } else {
+ _active.push_back(ObserverRecord(observer));
+ }
+}
+
+namespace {
+
+class VectorNodeObserver : public NodeObserver, public GC::Managed<> {
+public:
+ VectorNodeObserver(NodeEventVector const &v, void *d)
+ : vector(v), data(d) {}
+
+ NodeEventVector const &vector;
+ void * const data;
+
+ void notifyChildAdded(Node &node, Node &child, Node *prev) {
+ if (vector.child_added) {
+ vector.child_added(&node, &child, prev, data);
+ }
+ }
+
+ void notifyChildRemoved(Node &node, Node &child, Node *prev) {
+ if (vector.child_removed) {
+ vector.child_removed(&node, &child, prev, data);
+ }
+ }
+
+ void notifyChildOrderChanged(Node &node, Node &child, Node *old_prev, Node *new_prev) {
+ if (vector.order_changed) {
+ vector.order_changed(&node, &child, old_prev, new_prev, data);
+ }
+ }
+
+ void notifyContentChanged(Node &node, Util::SharedCStringPtr old_content, Util::SharedCStringPtr new_content) {
+ if (vector.content_changed) {
+ vector.content_changed(&node, old_content, new_content, data);
+ }
+ }
+
+ void notifyAttributeChanged(Node &node, GQuark name, Util::SharedCStringPtr old_value, Util::SharedCStringPtr new_value) {
+ if (vector.attr_changed) {
+ vector.attr_changed(&node, g_quark_to_string(name), old_value, new_value, false, data);
+ }
+ }
+};
+
+}
+
+void CompositeNodeObserver::addListener(NodeEventVector const &vector,
+ void *data)
+{
+ Debug::EventTracker<Debug::SimpleEvent<Debug::Event::XML> > tracker("add-listener");
+ add(*(new VectorNodeObserver(vector, data)));
+}
+
+namespace {
+
+using std::find_if;
+using Algorithms::find_if_before;
+typedef CompositeNodeObserver::ObserverRecord ObserverRecord;
+typedef CompositeNodeObserver::ObserverRecordList ObserverRecordList;
+
+template <typename ObserverPredicate>
+struct unmarked_record_satisfying {
+ ObserverPredicate predicate;
+ unmarked_record_satisfying(ObserverPredicate p) : predicate(p) {}
+ bool operator()(ObserverRecord const &record) {
+ return !record.marked && predicate(record.observer);
+ }
+};
+
+template <typename Predicate>
+bool mark_one(ObserverRecordList &observers, unsigned &marked_count,
+ Predicate p)
+{
+ ObserverRecordList::iterator found=std::find_if(
+ observers.begin(), observers.end(),
+ unmarked_record_satisfying<Predicate>(p)
+ );
+
+ if ( found != observers.end() ) {
+ found->marked = true;
+ return true;
+ } else {
+ return false;
+ }
+}
+
+template <typename Predicate>
+bool remove_one(ObserverRecordList &observers, unsigned &marked_count,
+ Predicate p)
+{
+ if (observers.empty()) {
+ return false;
+ }
+
+ if (unmarked_record_satisfying<Predicate>(p)(observers.front())) {
+ observers.pop_front();
+ return true;
+ }
+
+ ObserverRecordList::iterator found=find_if_before(
+ observers.begin(), observers.end(),
+ unmarked_record_satisfying<Predicate>(p)
+ );
+
+ if ( found != observers.end() ) {
+ observers.erase_after(found);
+ return true;
+ } else {
+ return false;
+ }
+}
+
+bool is_marked(ObserverRecord const &record) { return record.marked; }
+
+void remove_all_marked(ObserverRecordList &observers, unsigned &marked_count)
+{
+ ObserverRecordList::iterator iter;
+
+ g_assert( !observers.empty() || !marked_count );
+
+ while ( marked_count && observers.front().marked ) {
+ observers.pop_front();
+ --marked_count;
+ }
+
+ iter = observers.begin();
+ while (marked_count) {
+ iter = find_if_before(iter, observers.end(), is_marked);
+ observers.erase_after(iter);
+ --marked_count;
+ }
+}
+
+}
+
+void CompositeNodeObserver::_finishIteration() {
+ if (!--_iterating) {
+ remove_all_marked(_active, _active_marked);
+ remove_all_marked(_pending, _pending_marked);
+ _active.insert(_active.end(), _pending.begin(), _pending.end());
+ _pending.clear();
+ }
+}
+
+namespace {
+
+struct eql_observer {
+ NodeObserver const &observer;
+ eql_observer(NodeObserver const &o) : observer(o) {}
+ bool operator()(NodeObserver const &other) {
+ return &observer == &other;
+ }
+};
+
+}
+
+void CompositeNodeObserver::remove(NodeObserver &observer) {
+ eql_observer p(observer);
+ if (_iterating) {
+ mark_one(_active, _active_marked, p) ||
+ mark_one(_pending, _pending_marked, p);
+ } else {
+ remove_one(_active, _active_marked, p) ||
+ remove_one(_pending, _pending_marked, p);
+ }
+}
+
+namespace {
+
+struct vector_data_matches {
+ void * const data;
+ vector_data_matches(void *d) : data(d) {}
+
+ bool operator()(NodeObserver const &observer) {
+ VectorNodeObserver const *vo=dynamic_cast<VectorNodeObserver const *>(&observer);
+ return vo && vo->data == data;
+ }
+};
+
+}
+
+void CompositeNodeObserver::removeListenerByData(void *data) {
+ Debug::EventTracker<Debug::SimpleEvent<Debug::Event::XML> > tracker("remove-listener-by-data");
+ vector_data_matches p(data);
+ if (_iterating) {
+ mark_one(_active, _active_marked, p) ||
+ mark_one(_pending, _pending_marked, p);
+ } else {
+ remove_one(_active, _active_marked, p) ||
+ remove_one(_pending, _pending_marked, p);
+ }
+}
+
+}
+
+}
+
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
+ indent-tabs-mode:nil
+ fill-column:99
+ End:
+*/
+// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :
diff --git a/src/xml/composite-node-observer.h b/src/xml/composite-node-observer.h
new file mode 100644
index 000000000..6568a1099
--- /dev/null
+++ b/src/xml/composite-node-observer.h
@@ -0,0 +1,86 @@
+/*
+ * Inkscape::XML::CompositeNodeObserver - combine multiple observers
+ *
+ * Copyright 2005 MenTaLguY <mental@rydia.net>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * See the file COPYING for details.
+ *
+ */
+
+#ifndef SEEN_INKSCAPE_XML_COMPOSITE_NODE_OBSERVER_H
+#define SEEN_INKSCAPE_XML_COMPOSITE_NODE_OBSERVER_H
+
+#include "gc-managed.h"
+#include "xml/node-observer.h"
+#include "util/list-container.h"
+
+namespace Inkscape {
+
+namespace XML {
+
+class NodeEventVector;
+
+class CompositeNodeObserver : public NodeObserver, public GC::Managed<> {
+public:
+ struct ObserverRecord : public GC::Managed<> {
+ explicit ObserverRecord(NodeObserver &o) : observer(o), marked(false) {}
+
+ NodeObserver &observer;
+ bool marked; //< if marked for removal
+ };
+ typedef Util::ListContainer<ObserverRecord> ObserverRecordList;
+
+ CompositeNodeObserver()
+ : _iterating(0), _active_marked(0), _pending_marked(0) {}
+
+ void add(NodeObserver &observer);
+ void addListener(NodeEventVector const &vector, void *data);
+ void remove(NodeObserver &observer);
+ void removeListenerByData(void *data);
+
+ void notifyChildAdded(Node &node, Node &child, Node *prev);
+
+ void notifyChildRemoved(Node &node, Node &child, Node *prev);
+
+ void notifyChildOrderChanged(Node &node, Node &child,
+ Node *old_prev, Node *new_prev);
+
+ void notifyContentChanged(Node &node,
+ Util::SharedCStringPtr old_content,
+ Util::SharedCStringPtr new_content);
+
+ void notifyAttributeChanged(Node &node, GQuark name,
+ Util::SharedCStringPtr old_value,
+ Util::SharedCStringPtr new_value);
+
+private:
+ unsigned _iterating;
+ ObserverRecordList _active;
+ unsigned _active_marked;
+ ObserverRecordList _pending;
+ unsigned _pending_marked;
+
+ void _startIteration() { ++_iterating; }
+ void _finishIteration();
+};
+
+}
+
+}
+
+#endif
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
+ indent-tabs-mode:nil
+ fill-column:99
+ End:
+*/
+// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :
diff --git a/src/xml/croco-node-iface.cpp b/src/xml/croco-node-iface.cpp
new file mode 100644
index 000000000..0464e4fb7
--- /dev/null
+++ b/src/xml/croco-node-iface.cpp
@@ -0,0 +1,68 @@
+#include "xml/croco-node-iface.h"
+#include "xml/node.h"
+#include <glib/gstrfuncs.h>
+
+static char const *
+local_part(char const *const qname)
+{
+ char const *ret = std::strrchr(qname, ':');
+ if (ret)
+ return ++ret;
+ else
+ return qname;
+}
+
+namespace Inkscape {
+namespace XML {
+
+extern "C" {
+
+static CRXMLNodePtr get_parent(CRXMLNodePtr n) { return static_cast<Node const *>(n)->parent(); }
+static CRXMLNodePtr get_first_child(CRXMLNodePtr n) { return static_cast<Node const *>(n)->firstChild(); }
+static CRXMLNodePtr get_next(CRXMLNodePtr n) { return static_cast<Node const *>(n)->next(); }
+
+static CRXMLNodePtr get_prev(CRXMLNodePtr cn)
+{
+ Node const *n = static_cast<Node const *>(cn);
+ unsigned const n_pos = n->position();
+ if (n_pos) {
+ return n->parent()->nthChild(n_pos - 1);
+ } else {
+ return NULL;
+ }
+}
+
+static char *get_attr(CRXMLNodePtr n, char const *a)
+{
+ return g_strdup(static_cast<Node const *>(n)->attribute(a));
+}
+
+static char const *get_local_name(CRXMLNodePtr n) { return local_part(static_cast<Node const *>(n)->name()); }
+static gboolean is_element_node(CRXMLNodePtr n) { return static_cast<Node const *>(n)->type() == ELEMENT_NODE; }
+}
+
+CRNodeIface const croco_node_iface = {
+ get_parent,
+ get_first_child,
+ get_next,
+ get_prev,
+ get_local_name,
+ get_attr,
+ g_free,
+ is_element_node
+};
+
+}
+}
+
+
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
+ indent-tabs-mode:nil
+ fill-column:99
+ End:
+*/
+// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :
diff --git a/src/xml/croco-node-iface.h b/src/xml/croco-node-iface.h
new file mode 100644
index 000000000..bfac85e04
--- /dev/null
+++ b/src/xml/croco-node-iface.h
@@ -0,0 +1,12 @@
+#ifndef INKSCAPE_SP_REPR_NODE_IFACE_H
+#define INKSCAPE_SP_REPR_NODE_IFACE_H
+
+#include <libcroco/cr-node-iface.h>
+
+namespace Inkscape {
+namespace XML {
+extern CRNodeIface const croco_node_iface;
+}
+}
+
+#endif /* !INKSCAPE_SP_REPR_NODE_IFACE_H */
diff --git a/src/xml/document.h b/src/xml/document.h
new file mode 100644
index 000000000..8dc286a38
--- /dev/null
+++ b/src/xml/document.h
@@ -0,0 +1,50 @@
+/*
+ * Inkscape::XML::Document - interface for XML documents
+ *
+ * Copyright 2005 MenTaLguY <mental@rydia.net>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * See the file COPYING for details.
+ *
+ */
+
+#ifndef SEEN_INKSCAPE_XML_SP_REPR_DOC_H
+#define SEEN_INKSCAPE_XML_SP_REPR_DOC_H
+
+#include "xml/node.h"
+#include "xml/session.h"
+
+namespace Inkscape {
+namespace XML {
+
+struct Document : virtual public Node {
+public:
+ Node *createElementNode(char const *name) {
+ return session()->createElementNode(name);
+ }
+ Node *createTextNode(char const *content) {
+ return session()->createTextNode(content);
+ }
+ Node *createCommentNode(char const *content) {
+ return session()->createCommentNode(content);
+ }
+};
+
+}
+}
+
+#endif
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
+ indent-tabs-mode:nil
+ fill-column:99
+ End:
+*/
+// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :
diff --git a/src/xml/element-node.h b/src/xml/element-node.h
new file mode 100644
index 000000000..b9b7a4034
--- /dev/null
+++ b/src/xml/element-node.h
@@ -0,0 +1,49 @@
+/*
+ * Inkscape::XML::ElementNode - simple XML element implementation
+ *
+ * Copyright 2004-2005 MenTaLguY <mental@rydia.net>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * See the file COPYING for details.
+ *
+ */
+
+#ifndef SEEN_INKSCAPE_XML_ELEMENT_NODE_H
+#define SEEN_INKSCAPE_XML_ELEMENT_NODE_H
+
+#include "xml/simple-node.h"
+
+namespace Inkscape {
+
+namespace XML {
+
+class ElementNode : public SimpleNode {
+public:
+ explicit ElementNode(int code)
+ : SimpleNode(code) {}
+
+ Inkscape::XML::NodeType type() const { return Inkscape::XML::ELEMENT_NODE; }
+
+protected:
+ SimpleNode *_duplicate() const { return new ElementNode(*this); }
+};
+
+}
+
+}
+
+#endif
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
+ indent-tabs-mode:nil
+ fill-column:99
+ End:
+*/
+// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :
diff --git a/src/xml/event-fns.h b/src/xml/event-fns.h
new file mode 100644
index 000000000..65c65c48c
--- /dev/null
+++ b/src/xml/event-fns.h
@@ -0,0 +1,28 @@
+#ifndef SEEN_INKSCAPE_XML_SP_REPR_ACTION_FNS_H
+#define SEEN_INKSCAPE_XML_SP_REPR_ACTION_FNS_H
+
+namespace Inkscape {
+namespace XML {
+
+class Document;
+class Event;
+class NodeObserver;
+
+void replay_log_to_observer(Event const *log, NodeObserver &observer);
+void undo_log_to_observer(Event const *log, NodeObserver &observer);
+
+}
+}
+
+void sp_repr_begin_transaction (Inkscape::XML::Document *doc);
+void sp_repr_rollback (Inkscape::XML::Document *doc);
+void sp_repr_commit (Inkscape::XML::Document *doc);
+Inkscape::XML::Event *sp_repr_commit_undoable (Inkscape::XML::Document *doc);
+
+void sp_repr_undo_log (Inkscape::XML::Event *log);
+void sp_repr_replay_log (Inkscape::XML::Event *log);
+Inkscape::XML::Event *sp_repr_coalesce_log (Inkscape::XML::Event *a, Inkscape::XML::Event *b);
+void sp_repr_free_log (Inkscape::XML::Event *log);
+void sp_repr_debug_print_log(Inkscape::XML::Event const *log);
+
+#endif
diff --git a/src/xml/event.cpp b/src/xml/event.cpp
new file mode 100644
index 000000000..c9f0b50a7
--- /dev/null
+++ b/src/xml/event.cpp
@@ -0,0 +1,489 @@
+/*
+ * Repr transaction logging
+ *
+ * Authors:
+ * Lauris Kaplinski <lauris@kaplinski.com>
+ * MenTaLguY <mental@rydia.net>
+ *
+ * Copyright (C) 2004-2005 MenTaLguY
+ * Copyright (C) 1999-2003 authors
+ * Copyright (C) 2001-2002 Ximian, Inc.
+ * g++ port Copyright (C) 2003 Nathan Hurst
+ *
+ * Released under GNU GPL, read the file 'COPYING' for more information
+ */
+
+#include "event.h"
+#include "event-fns.h"
+#include "util/reverse-list.h"
+#include "xml/document.h"
+#include "xml/node-observer.h"
+#include "debug/event-tracker.h"
+#include "debug/simple-event.h"
+
+using Inkscape::Util::List;
+using Inkscape::Util::reverse_list;
+
+int Inkscape::XML::Event::_next_serial=0;
+
+using Inkscape::XML::Session;
+
+void
+sp_repr_begin_transaction (Inkscape::XML::Document *doc)
+{
+ using Inkscape::Debug::SimpleEvent;
+ using Inkscape::Debug::EventTracker;
+ using Inkscape::Debug::Event;
+
+ EventTracker<SimpleEvent<Event::XML> > tracker("begin-transaction");
+
+ g_assert(doc != NULL);
+ Session *session=doc->session();
+ g_assert(session != NULL);
+ session->beginTransaction();
+}
+
+void
+sp_repr_rollback (Inkscape::XML::Document *doc)
+{
+ using Inkscape::Debug::SimpleEvent;
+ using Inkscape::Debug::EventTracker;
+ using Inkscape::Debug::Event;
+
+ EventTracker<SimpleEvent<Event::XML> > tracker("rollback");
+
+ g_assert(doc != NULL);
+ Session *session=doc->session();
+ g_assert(session != NULL);
+ session->rollback();
+}
+
+void
+sp_repr_commit (Inkscape::XML::Document *doc)
+{
+ using Inkscape::Debug::SimpleEvent;
+ using Inkscape::Debug::EventTracker;
+ using Inkscape::Debug::Event;
+
+ EventTracker<SimpleEvent<Event::XML> > tracker("commit");
+
+ g_assert(doc != NULL);
+ Session *session=doc->session();
+ g_assert(session != NULL);
+ session->commit();
+}
+
+Inkscape::XML::Event *
+sp_repr_commit_undoable (Inkscape::XML::Document *doc)
+{
+ using Inkscape::Debug::SimpleEvent;
+ using Inkscape::Debug::EventTracker;
+ using Inkscape::Debug::Event;
+
+ EventTracker<SimpleEvent<Event::XML> > tracker("commit");
+
+ g_assert(doc != NULL);
+ Session *session=doc->session();
+ g_assert(session != NULL);
+ return session->commitUndoable();
+}
+
+namespace {
+
+class LogPerformer : public Inkscape::XML::NodeObserver {
+public:
+ typedef Inkscape::XML::Node Node;
+
+ static LogPerformer &instance() {
+ static LogPerformer singleton;
+ return singleton;
+ }
+
+ void notifyChildAdded(Node &parent, Node &child, Node *ref) {
+ parent.addChild(&child, ref);
+ }
+
+ void notifyChildRemoved(Node &parent, Node &child, Node *old_ref) {
+ parent.removeChild(&child);
+ }
+
+ void notifyChildOrderChanged(Node &parent, Node &child,
+ Node *old_ref, Node *new_ref)
+ {
+ parent.changeOrder(&child, new_ref);
+ }
+
+ void notifyAttributeChanged(Node &node, GQuark name,
+ Inkscape::Util::SharedCStringPtr old_value,
+ Inkscape::Util::SharedCStringPtr new_value)
+ {
+ node.setAttribute(g_quark_to_string(name), new_value);
+ }
+
+ void notifyContentChanged(Node &node,
+ Inkscape::Util::SharedCStringPtr old_value,
+ Inkscape::Util::SharedCStringPtr new_value)
+ {
+ node.setContent(new_value);
+ }
+};
+
+}
+
+void Inkscape::XML::undo_log_to_observer(
+ Inkscape::XML::Event const *log,
+ Inkscape::XML::NodeObserver &observer
+) {
+ for ( Event const *action = log ; action ; action = action->next ) {
+ action->undoOne(observer);
+ }
+}
+
+void
+sp_repr_undo_log (Inkscape::XML::Event *log)
+{
+ using Inkscape::Debug::SimpleEvent;
+ using Inkscape::Debug::EventTracker;
+ using Inkscape::Debug::Event;
+
+ EventTracker<SimpleEvent<Event::XML> > tracker("undo-log");
+
+ if (log) {
+ g_assert(!log->repr->session()->inTransaction());
+ }
+
+ Inkscape::XML::undo_log_to_observer(log, LogPerformer::instance());
+}
+
+void Inkscape::XML::EventAdd::_undoOne(
+ Inkscape::XML::NodeObserver &observer
+) const {
+ observer.notifyChildRemoved(*this->repr, *this->child, this->ref);
+}
+
+void Inkscape::XML::EventDel::_undoOne(
+ Inkscape::XML::NodeObserver &observer
+) const {
+ observer.notifyChildAdded(*this->repr, *this->child, this->ref);
+}
+
+void Inkscape::XML::EventChgAttr::_undoOne(
+ Inkscape::XML::NodeObserver &observer
+) const {
+ observer.notifyAttributeChanged(*this->repr, this->key, this->newval, this->oldval);
+}
+
+void Inkscape::XML::EventChgContent::_undoOne(
+ Inkscape::XML::NodeObserver &observer
+) const {
+ observer.notifyContentChanged(*this->repr, this->newval, this->oldval);
+}
+
+void Inkscape::XML::EventChgOrder::_undoOne(
+ Inkscape::XML::NodeObserver &observer
+) const {
+ observer.notifyChildOrderChanged(*this->repr, *this->child, this->newref, this->oldref);
+}
+
+void Inkscape::XML::replay_log_to_observer(
+ Inkscape::XML::Event const *log,
+ Inkscape::XML::NodeObserver &observer
+) {
+ List<Inkscape::XML::Event const &> reversed =
+ reverse_list<Inkscape::XML::Event::ConstIterator>(log, NULL);
+ for ( ; reversed ; ++reversed ) {
+ reversed->replayOne(observer);
+ }
+}
+
+void
+sp_repr_replay_log (Inkscape::XML::Event *log)
+{
+ using Inkscape::Debug::SimpleEvent;
+ using Inkscape::Debug::EventTracker;
+ using Inkscape::Debug::Event;
+
+ EventTracker<SimpleEvent<Event::XML> > tracker("replay-log");
+
+ if (log) {
+ // Nodes created by the whiteboard deserializer tend to not
+ // have an associated session. This conditional hacks a way around that,
+ // but what's the best way to fix the whiteboard code so that new nodes
+ // will have an associated session? -- yipdw
+ if (log->repr->session()) {
+ g_assert(!log->repr->session()->inTransaction());
+ }
+ }
+
+ Inkscape::XML::replay_log_to_observer(log, LogPerformer::instance());
+}
+
+void Inkscape::XML::EventAdd::_replayOne(
+ Inkscape::XML::NodeObserver &observer
+) const {
+ observer.notifyChildAdded(*this->repr, *this->child, this->ref);
+}
+
+void Inkscape::XML::EventDel::_replayOne(
+ Inkscape::XML::NodeObserver &observer
+) const {
+ observer.notifyChildRemoved(*this->repr, *this->child, this->ref);
+}
+
+void Inkscape::XML::EventChgAttr::_replayOne(
+ Inkscape::XML::NodeObserver &observer
+) const {
+ observer.notifyAttributeChanged(*this->repr, this->key, this->oldval, this->newval);
+}
+
+void Inkscape::XML::EventChgContent::_replayOne(
+ Inkscape::XML::NodeObserver &observer
+) const {
+ observer.notifyContentChanged(*this->repr, this->oldval, this->newval);
+}
+
+void Inkscape::XML::EventChgOrder::_replayOne(
+ Inkscape::XML::NodeObserver &observer
+) const {
+ observer.notifyChildOrderChanged(*this->repr, *this->child, this->oldref, this->newref);
+}
+
+Inkscape::XML::Event *
+sp_repr_coalesce_log (Inkscape::XML::Event *a, Inkscape::XML::Event *b)
+{
+ Inkscape::XML::Event *action;
+ Inkscape::XML::Event **prev_ptr;
+
+ if (!b) return a;
+ if (!a) return b;
+
+ /* find the earliest action in the second log */
+ /* (also noting the pointer that references it, so we can
+ * replace it later) */
+ prev_ptr = &b;
+ for ( action = b ; action->next ; action = action->next ) {
+ prev_ptr = &action->next;
+ }
+
+ /* add the first log after it */
+ action->next = a;
+
+ /* optimize the result */
+ *prev_ptr = action->optimizeOne();
+
+ return b;
+}
+
+void
+sp_repr_free_log (Inkscape::XML::Event *log)
+{
+ while (log) {
+ Inkscape::XML::Event *action;
+ action = log;
+ log = action->next;
+ delete action;
+ }
+}
+
+namespace {
+
+template <typename T> struct ActionRelations;
+
+template <>
+struct ActionRelations<Inkscape::XML::EventAdd> {
+ typedef Inkscape::XML::EventDel Opposite;
+};
+
+template <>
+struct ActionRelations<Inkscape::XML::EventDel> {
+ typedef Inkscape::XML::EventAdd Opposite;
+};
+
+template <typename A>
+Inkscape::XML::Event *cancel_add_or_remove(A *action) {
+ typedef typename ActionRelations<A>::Opposite Opposite;
+ Opposite *opposite=dynamic_cast<Opposite *>(action->next);
+
+ if ( opposite && opposite->repr == action->repr &&
+ opposite->child == action->child &&
+ opposite->ref == action->ref )
+ {
+ Inkscape::XML::Event *remaining=opposite->next;
+
+ delete opposite;
+ delete action;
+
+ return remaining;
+ } else {
+ return action;
+ }
+}
+
+}
+
+Inkscape::XML::Event *Inkscape::XML::EventAdd::_optimizeOne() {
+ return cancel_add_or_remove(this);
+}
+
+Inkscape::XML::Event *Inkscape::XML::EventDel::_optimizeOne() {
+ return cancel_add_or_remove(this);
+}
+
+Inkscape::XML::Event *Inkscape::XML::EventChgAttr::_optimizeOne() {
+ Inkscape::XML::EventChgAttr *chg_attr=dynamic_cast<Inkscape::XML::EventChgAttr *>(this->next);
+
+ /* consecutive chgattrs on the same key can be combined */
+ if ( chg_attr && chg_attr->repr == this->repr &&
+ chg_attr->key == this->key )
+ {
+ /* replace our oldval with the prior action's */
+ this->oldval = chg_attr->oldval;
+
+ /* discard the prior action */
+ this->next = chg_attr->next;
+ delete chg_attr;
+ }
+
+ return this;
+}
+
+Inkscape::XML::Event *Inkscape::XML::EventChgContent::_optimizeOne() {
+ Inkscape::XML::EventChgContent *chg_content=dynamic_cast<Inkscape::XML::EventChgContent *>(this->next);
+
+ /* consecutive content changes can be combined */
+ if ( chg_content && chg_content->repr == this->repr ) {
+ /* replace our oldval with the prior action's */
+ this->oldval = chg_content->oldval;
+
+ /* get rid of the prior action*/
+ this->next = chg_content->next;
+ delete chg_content;
+ }
+
+ return this;
+}
+
+Inkscape::XML::Event *Inkscape::XML::EventChgOrder::_optimizeOne() {
+ Inkscape::XML::EventChgOrder *chg_order=dynamic_cast<Inkscape::XML::EventChgOrder *>(this->next);
+
+ /* consecutive chgorders for the same child may be combined or
+ * canceled out */
+ if ( chg_order && chg_order->repr == this->repr &&
+ chg_order->child == this->child )
+ {
+ if ( chg_order->oldref == this->newref ) {
+ /* cancel them out */
+ Inkscape::XML::Event *after=chg_order->next;
+
+ delete chg_order;
+ delete this;
+
+ return after;
+ } else {
+ /* combine them */
+ this->oldref = chg_order->oldref;
+
+ /* get rid of the other one */
+ this->next = chg_order->next;
+ delete chg_order;
+
+ return this;
+ }
+ } else {
+ return this;
+ }
+}
+
+namespace {
+
+class LogPrinter : public Inkscape::XML::NodeObserver {
+public:
+ typedef Inkscape::XML::Node Node;
+
+ static LogPrinter &instance() {
+ static LogPrinter singleton;
+ return singleton;
+ }
+
+ static Glib::ustring node_to_string(Node const &node) {
+ Glib::ustring result;
+ char const *type_name=NULL;
+ switch (node.type()) {
+ case Inkscape::XML::DOCUMENT_NODE:
+ type_name = "Document";
+ break;
+ case Inkscape::XML::ELEMENT_NODE:
+ type_name = "Element";
+ break;
+ case Inkscape::XML::TEXT_NODE:
+ type_name = "Text";
+ break;
+ case Inkscape::XML::COMMENT_NODE:
+ type_name = "Comment";
+ break;
+ default:
+ g_assert_not_reached();
+ }
+ char buffer[40];
+ result.append("#<");
+ result.append(type_name);
+ result.append(":");
+ snprintf(buffer, 40, "0x%p", &node);
+ result.append(buffer);
+ result.append(">");
+
+ return result;
+ }
+
+ static Glib::ustring ref_to_string(Node *ref) {
+ if (ref) {
+ return node_to_string(*ref);
+ } else {
+ return "beginning";
+ }
+ }
+
+ void notifyChildAdded(Node &parent, Node &child, Node *ref) {
+ g_warning("Event: Added %s to %s after %s", node_to_string(parent).c_str(), node_to_string(child).c_str(), ref_to_string(ref).c_str());
+ }
+
+ void notifyChildRemoved(Node &parent, Node &child, Node *ref) {
+ g_warning("Event: Removed %s from %s", node_to_string(parent).c_str(), node_to_string(child).c_str());
+ }
+
+ void notifyChildOrderChanged(Node &parent, Node &child,
+ Node *old_ref, Node *new_ref)
+ {
+ g_warning("Event: Moved %s after %s in %s", node_to_string(child).c_str(), ref_to_string(new_ref).c_str(), node_to_string(parent).c_str());
+ }
+
+ void notifyAttributeChanged(Node &node, GQuark name,
+ Inkscape::Util::SharedCStringPtr old_value,
+ Inkscape::Util::SharedCStringPtr new_value)
+ {
+ if (new_value) {
+ g_warning("Event: Set attribute %s to \"%s\" on %s", g_quark_to_string(name), new_value.cString(), node_to_string(node).c_str());
+ } else {
+ g_warning("Event: Unset attribute %s on %s", g_quark_to_string(name), node_to_string(node).c_str());
+ }
+ }
+
+ void notifyContentChanged(Node &node,
+ Inkscape::Util::SharedCStringPtr old_value,
+ Inkscape::Util::SharedCStringPtr new_value)
+ {
+ if (new_value) {
+ g_warning("Event: Set content of %s to \"%s\"", node_to_string(node).c_str(), new_value.cString());
+ } else {
+ g_warning("Event: Unset content of %s", node_to_string(node).c_str());
+ }
+ }
+};
+
+}
+
+void
+sp_repr_debug_print_log(Inkscape::XML::Event const *log) {
+ Inkscape::XML::replay_log_to_observer(log, LogPrinter::instance());
+}
+
diff --git a/src/xml/event.h b/src/xml/event.h
new file mode 100644
index 000000000..e4ce8b26a
--- /dev/null
+++ b/src/xml/event.h
@@ -0,0 +1,150 @@
+#ifndef SEEN_INKSCAPE_XML_SP_REPR_ACTION_H
+#define SEEN_INKSCAPE_XML_SP_REPR_ACTION_H
+
+#include <glib/gtypes.h>
+#include <glib/gquark.h>
+#include <glibmm/ustring.h>
+
+#include <iterator>
+#include "util/shared-c-string-ptr.h"
+#include "util/forward-pointer-iterator.h"
+#include "gc-managed.h"
+#include "xml/node.h"
+
+namespace Inkscape {
+namespace XML {
+
+class Node;
+class NodeObserver;
+
+enum EventType {
+ EVENT_ADD,
+ EVENT_DEL,
+ EVENT_CHG_ATTR,
+ EVENT_CHG_CONTENT,
+ EVENT_CHG_ORDER
+};
+
+class Event
+: public Inkscape::GC::Managed<Inkscape::GC::SCANNED, Inkscape::GC::MANUAL>
+{
+public:
+
+ virtual ~Event() {}
+
+ Event *next;
+ int serial;
+ Node *repr;
+
+ struct IteratorStrategy {
+ static Event const *next(Event const *action) {
+ return action->next;
+ }
+ };
+
+ typedef Inkscape::Util::ForwardPointerIterator<Event, IteratorStrategy> Iterator;
+ typedef Inkscape::Util::ForwardPointerIterator<Event const, IteratorStrategy> ConstIterator;
+
+ Event *optimizeOne() { return _optimizeOne(); }
+ void undoOne(NodeObserver &observer) const {
+ _undoOne(observer);
+ }
+ void replayOne(NodeObserver &observer) const {
+ _replayOne(observer);
+ }
+
+protected:
+ Event(Node *r, Event *n)
+ : next(n), serial(_next_serial++), repr(r) {}
+
+ virtual Event *_optimizeOne()=0;
+ virtual void _undoOne(NodeObserver &) const=0;
+ virtual void _replayOne(NodeObserver &) const=0;
+
+private:
+ static int _next_serial;
+};
+
+class EventAdd : public Event {
+public:
+ EventAdd(Node *repr, Node *c, Node *rr, Event *next)
+ : Event(repr, next), child(c), ref(rr) {}
+
+ Node *child;
+ Node *ref;
+
+private:
+ Event *_optimizeOne();
+ void _undoOne(NodeObserver &observer) const;
+ void _replayOne(NodeObserver &observer) const;
+};
+
+class EventDel : public Event {
+public:
+ EventDel(Node *repr, Node *c, Node *rr, Event *next)
+ : Event(repr, next), child(c), ref(rr) {}
+
+ Node *child;
+ Node *ref;
+
+private:
+ Event *_optimizeOne();
+ void _undoOne(NodeObserver &observer) const;
+ void _replayOne(NodeObserver &observer) const;
+};
+
+class EventChgAttr : public Event {
+public:
+ EventChgAttr(Node *repr, GQuark k,
+ Inkscape::Util::SharedCStringPtr ov,
+ Inkscape::Util::SharedCStringPtr nv,
+ Event *next)
+ : Event(repr, next), key(k),
+ oldval(ov), newval(nv) {}
+
+ GQuark key;
+ Inkscape::Util::SharedCStringPtr oldval;
+ Inkscape::Util::SharedCStringPtr newval;
+
+private:
+ Event *_optimizeOne();
+ void _undoOne(NodeObserver &observer) const;
+ void _replayOne(NodeObserver &observer) const;
+};
+
+class EventChgContent : public Event {
+public:
+ EventChgContent(Node *repr,
+ Inkscape::Util::SharedCStringPtr ov,
+ Inkscape::Util::SharedCStringPtr nv,
+ Event *next)
+ : Event(repr, next), oldval(ov), newval(nv) {}
+
+ Inkscape::Util::SharedCStringPtr oldval;
+ Inkscape::Util::SharedCStringPtr newval;
+
+private:
+ Event *_optimizeOne();
+ void _undoOne(NodeObserver &observer) const;
+ void _replayOne(NodeObserver &observer) const;
+};
+
+class EventChgOrder : public Event {
+public:
+ EventChgOrder(Node *repr, Node *c, Node *orr, Node *nrr, Event *next)
+ : Event(repr, next), child(c),
+ oldref(orr), newref(nrr) {}
+
+ Node *child;
+ Node *oldref, *newref;
+
+private:
+ Event *_optimizeOne();
+ void _undoOne(NodeObserver &observer) const;
+ void _replayOne(NodeObserver &observer) const;
+};
+
+}
+}
+
+#endif
diff --git a/src/xml/invalid-operation-exception.h b/src/xml/invalid-operation-exception.h
new file mode 100644
index 000000000..fa3f740d1
--- /dev/null
+++ b/src/xml/invalid-operation-exception.h
@@ -0,0 +1,47 @@
+/*
+ * Inkscape::XML::InvalidOperationException - invalid operation for node type
+ *
+ * Copyright 2004-2005 MenTaLguY <mental@rydia.net>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * See the file COPYING for details.
+ *
+ */
+
+#ifndef SEEN_INKSCAPE_XML_INVALID_OPERATION_EXCEPTION_H
+#define SEEN_INKSCAPE_XML_INVALID_OPERATION_EXCEPTION_H
+
+#include <exception>
+#include <stdexcept>
+
+namespace Inkscape {
+
+namespace XML {
+
+class InvalidOperationException : public std::logic_error {
+public:
+ InvalidOperationException(std::string const &message) :
+ std::logic_error(message)
+ { }
+};
+
+}
+
+}
+
+#endif
+
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
+ indent-tabs-mode:nil
+ fill-column:99
+ End:
+*/
+// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :
diff --git a/src/xml/log-builder.cpp b/src/xml/log-builder.cpp
new file mode 100644
index 000000000..b3d8db59f
--- /dev/null
+++ b/src/xml/log-builder.cpp
@@ -0,0 +1,78 @@
+/*
+ * Inkscape::XML::LogBuilder - NodeObserver which builds an event log
+ *
+ * Copyright 2005 MenTaLguY <mental@rydia.net>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * See the file COPYING for details.
+ *
+ */
+
+#include "xml/log-builder.h"
+#include "xml/event.h"
+#include "xml/event-fns.h"
+
+namespace Inkscape {
+namespace XML {
+
+void LogBuilder::discard() {
+ sp_repr_free_log(_log);
+ _log = NULL;
+}
+
+Event *LogBuilder::detach() {
+ Event *log=_log;
+ _log = NULL;
+ return log;
+}
+
+void LogBuilder::addChild(Node &node, Node &child, Node *prev) {
+ _log = new Inkscape::XML::EventAdd(&node, &child, prev, _log);
+ _log = _log->optimizeOne();
+}
+
+void LogBuilder::removeChild(Node &node, Node &child, Node *prev) {
+ _log = new Inkscape::XML::EventDel(&node, &child, prev, _log);
+ _log = _log->optimizeOne();
+}
+
+void LogBuilder::setChildOrder(Node &node, Node &child,
+ Node *old_prev, Node *new_prev)
+{
+ _log = new Inkscape::XML::EventChgOrder(&node, &child, old_prev, new_prev, _log);
+ _log = _log->optimizeOne();
+}
+
+void LogBuilder::setContent(Node &node,
+ Util::SharedCStringPtr old_content,
+ Util::SharedCStringPtr new_content)
+{
+ _log = new Inkscape::XML::EventChgContent(&node, old_content, new_content, _log);
+ _log = _log->optimizeOne();
+}
+
+void LogBuilder::setAttribute(Node &node, GQuark name,
+ Util::SharedCStringPtr old_value,
+ Util::SharedCStringPtr new_value)
+{
+ _log = new Inkscape::XML::EventChgAttr(&node, name, old_value, new_value, _log);
+ _log = _log->optimizeOne();
+}
+
+}
+}
+
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
+ indent-tabs-mode:nil
+ fill-column:99
+ End:
+*/
+// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :
diff --git a/src/xml/log-builder.h b/src/xml/log-builder.h
new file mode 100644
index 000000000..1f1ea1959
--- /dev/null
+++ b/src/xml/log-builder.h
@@ -0,0 +1,66 @@
+/*
+ * Inkscape::XML::LogBuilder - NodeObserver which builds an event log
+ *
+ * Copyright 2005 MenTaLguY <mental@rydia.net>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * See the file COPYING for details.
+ *
+ */
+
+#ifndef SEEN_INKSCAPE_XML_LOG_BUILDER_H
+#define SEEN_INKSCAPE_XML_LOG_BUILDER_H
+
+#include "gc-managed.h"
+#include "xml/node-observer.h"
+
+namespace Inkscape {
+namespace XML {
+
+class Event;
+
+class LogBuilder {
+public:
+ LogBuilder() : _log(NULL) {}
+ ~LogBuilder() { discard(); }
+
+ void discard();
+ Event *detach();
+
+ void addChild(Node &node, Node &child, Node *prev);
+
+ void removeChild(Node &node, Node &child, Node *prev);
+
+ void setChildOrder(Node &node, Node &child,
+ Node *old_prev, Node *new_prev);
+
+ void setContent(Node &node,
+ Util::SharedCStringPtr old_content,
+ Util::SharedCStringPtr new_content);
+
+ void setAttribute(Node &node, GQuark name,
+ Util::SharedCStringPtr old_value,
+ Util::SharedCStringPtr new_value);
+
+private:
+ Event *_log;
+};
+
+}
+}
+
+#endif
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
+ indent-tabs-mode:nil
+ fill-column:99
+ End:
+*/
+// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :
diff --git a/src/xml/makefile.in b/src/xml/makefile.in
new file mode 100644
index 000000000..dd966601f
--- /dev/null
+++ b/src/xml/makefile.in
@@ -0,0 +1,17 @@
+# Convenience stub makefile to call the real Makefile.
+
+@SET_MAKE@
+
+# Explicit so that it's the default rule.
+all:
+ cd .. && $(MAKE) xml/all
+
+clean %.a %.o:
+ cd .. && $(MAKE) xml/$@
+
+.PHONY: all clean
+
+OBJEXT = @OBJEXT@
+
+.SUFFIXES:
+.SUFFIXES: .a .$(OBJEXT)
diff --git a/src/xml/node-event-vector.h b/src/xml/node-event-vector.h
new file mode 100644
index 000000000..920e28475
--- /dev/null
+++ b/src/xml/node-event-vector.h
@@ -0,0 +1,47 @@
+#ifndef SEEN_INKSCAPE_XML_SP_REPR_EVENT_VECTOR
+#define SEEN_INKSCAPE_XML_SP_REPR_EVENT_VECTOR
+
+/*
+ * Fuzzy DOM-like tree implementation
+ *
+ * Authors:
+ * Lauris Kaplinski <lauris@kaplinski.com>
+ * Frank Felfe <innerspace@iname.com>
+ *
+ * Copyright (C) 1999-2002 Lauris Kaplinski and Frank Felfe
+ * Copyright (C) 2000-2002 Ximian, Inc.
+ *
+ * Released under GNU GPL, read the file 'COPYING' for more information
+ */
+
+#include <glib/gtypes.h>
+
+#include "xml/node.h"
+
+namespace Inkscape {
+namespace XML {
+
+struct NodeEventVector {
+ /* Immediate signals */
+ void (* child_added) (Node *repr, Node *child, Node *ref, void * data);
+ void (* child_removed) (Node *repr, Node *child, Node *ref, void * data);
+ void (* attr_changed) (Node *repr, const gchar *key, const gchar *oldval, const gchar *newval, bool is_interactive, void * data);
+ void (* content_changed) (Node *repr, const gchar *oldcontent, const gchar *newcontent, void * data);
+ void (* order_changed) (Node *repr, Node *child, Node *oldref, Node *newref, void * data);
+};
+
+}
+}
+
+inline void sp_repr_synthesize_events (Inkscape::XML::Node *repr, const Inkscape::XML::NodeEventVector *vector, void * data) {
+ repr->synthesizeEvents(vector, data);
+}
+
+inline void sp_repr_add_listener (Inkscape::XML::Node *repr, const Inkscape::XML::NodeEventVector *vector, void * data) {
+ repr->addListener(vector, data);
+}
+inline void sp_repr_remove_listener_by_data (Inkscape::XML::Node *repr, void * data) {
+ repr->removeListenerByData(data);
+}
+
+#endif
diff --git a/src/xml/node-fns.cpp b/src/xml/node-fns.cpp
new file mode 100644
index 000000000..16947e66e
--- /dev/null
+++ b/src/xml/node-fns.cpp
@@ -0,0 +1,103 @@
+#ifdef HAVE_CONFIG_H
+# include "config.h"
+#endif
+
+#include <map>
+
+#include "xml/node-iterators.h"
+#include "algorithms/find-if-before.h"
+
+namespace Inkscape {
+namespace XML {
+
+/* the id_permitted stuff is a temporary-ish hack */
+
+namespace {
+
+bool id_permitted_internal(GQuark qname) {
+ char const *qname_s=g_quark_to_string(qname);
+ return !strncmp("svg:", qname_s, 4) || !strncmp("sodipodi:", qname_s, 9) ||
+ !strncmp("inkscape:", qname_s, 9);
+}
+
+
+bool id_permitted_internal_memoized(GQuark qname) {
+ typedef std::map<GQuark, bool> IdPermittedMap;
+ IdPermittedMap id_permitted_names;
+
+ IdPermittedMap::iterator found;
+ found = id_permitted_names.find(qname);
+ if ( found != id_permitted_names.end() ) {
+ return found->second;
+ } else {
+ bool permitted=id_permitted_internal(qname);
+ id_permitted_names[qname] = permitted;
+ return permitted;
+ }
+}
+
+}
+
+bool id_permitted(Node const *node) {
+ g_return_val_if_fail(node != NULL, false);
+
+ if ( node->type() != ELEMENT_NODE ) {
+ return false;
+ }
+
+ return id_permitted_internal_memoized((GQuark)node->code());
+}
+
+struct node_matches {
+ node_matches(Node const &n) : node(n) {}
+ bool operator()(Node const &other) { return &other == &node; }
+ Node const &node;
+};
+
+/** Returns the sibling before \a node in \a node's parent's children,
+ * or NULL if \a node is the first of those children (or if child is
+ * NULL or has no parent).
+ *
+ * Useful in combination with Node::addChild, when you want to insert
+ * a new child _before_ a given existing child.
+ *
+ * Note: Involves a linear search (unlike next_node).
+ *
+ * \pre Links are correct, i.e. \a node isin its parent's children.
+ *
+ * \post (ret == NULL
+ * ? node == NULL || node->parent() == NULL || node->parent()->firstChild() == node
+ * : ret->next() == node).
+ */
+Node *previous_node(Node *node) {
+ using Inkscape::Algorithms::find_if_before;
+
+ if ( !node || !node->parent() ) {
+ return NULL;
+ }
+
+ Node *previous=find_if_before<NodeSiblingIterator>(
+ node->parent()->firstChild(), NULL, node_matches(*node)
+ );
+
+ g_assert(previous == NULL
+ ? node->parent()->firstChild() == node
+ : previous->next() == node);
+
+ return previous;
+}
+
+}
+}
+
+
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
+ indent-tabs-mode:nil
+ fill-column:99
+ End:
+*/
+// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 :
diff --git a/src/xml/node-fns.h b/src/xml/node-fns.h
new file mode 100644
index 000000000..54366e61f
--- /dev/null
+++ b/src/xml/node-fns.h
@@ -0,0 +1,44 @@
+#ifndef SEEN_REPR_GET_CHILDREN_H
+#define SEEN_REPR_GET_CHILDREN_H
+
+#include "xml/node.h"
+
+namespace Inkscape {
+namespace XML {
+
+class Node;
+
+bool id_permitted(Node const *node);
+
+inline Node *next_node(Node *node) {
+ return ( node ? node->next() : NULL );
+}
+inline Node const *next_node(Node const *node) {
+ return ( node ? node->next() : NULL );
+}
+Node *previous_node(Node *node);
+inline Node const *previous_node(Node const *node) {
+ return previous_node(const_cast<Node *>(node));
+}
+inline Node *parent_node(Node *node) {
+ return ( node ? node->parent() : NULL );
+}
+inline Node const *parent_node(Node const *node) {
+ return ( node ? node->parent() : NULL );
+}
+
+}
+}
+
+#endif /* !SEEN_REPR_GET_CHILDREN_H */
+
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
+ indent-tabs-mode:nil
+ fill-column:99
+ End:
+*/
+// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 :
diff --git a/src/xml/node-iterators.h b/src/xml/node-iterators.h
new file mode 100644
index 000000000..0868fb2ab
--- /dev/null
+++ b/src/xml/node-iterators.h
@@ -0,0 +1,61 @@
+/*
+ * Node iterators
+ *
+ * Authors:
+ * MenTaLguY <mental@rydia.net>
+ *
+ * Copyright (C) 2004 MenTaLguY
+ *
+ * Released under GNU GPL, read the file 'COPYING' for more information
+ */
+
+#ifndef SEEN_INKSCAPE_XML_SP_REPR_ITERATORS_H
+#define SEEN_INKSCAPE_XML_SP_REPR_ITERATORS_H
+
+#include "util/forward-pointer-iterator.h"
+#include "xml/node.h"
+
+namespace Inkscape {
+namespace XML {
+
+struct NodeSiblingIteratorStrategy {
+ static Node const *next(Node const *node) {
+ return ( node ? node->next() : NULL );
+ }
+};
+struct NodeParentIteratorStrategy {
+ static Node const *next(Node const *node) {
+ return ( node ? node->parent() : NULL );
+ }
+};
+
+typedef Inkscape::Util::ForwardPointerIterator<Node,
+ NodeSiblingIteratorStrategy>
+ NodeSiblingIterator;
+
+typedef Inkscape::Util::ForwardPointerIterator<Node const,
+ NodeSiblingIteratorStrategy>
+ NodeConstSiblingIterator;
+
+typedef Inkscape::Util::ForwardPointerIterator<Node,
+ NodeParentIteratorStrategy>
+ NodeParentIterator;
+
+typedef Inkscape::Util::ForwardPointerIterator<Node const,
+ NodeParentIteratorStrategy>
+ NodeConstParentIterator;
+
+}
+}
+
+#endif
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
+ indent-tabs-mode:nil
+ fill-column:99
+ End:
+*/
+// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :
diff --git a/src/xml/node-observer.h b/src/xml/node-observer.h
new file mode 100644
index 000000000..4a884b8a9
--- /dev/null
+++ b/src/xml/node-observer.h
@@ -0,0 +1,68 @@
+/*
+ * Inkscape::XML::NodeObserver - interface implemented by observers of XML nodes
+ *
+ * Copyright 2005 MenTaLguY <mental@rydia.net>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * See the file COPYING for details.
+ *
+ */
+
+#ifndef SEEN_INKSCAPE_XML_NODE_OBSERVER_H
+#define SEEN_INKSCAPE_XML_NODE_OBSERVER_H
+
+#include <glib/gquark.h>
+#include "util/shared-c-string-ptr.h"
+
+namespace Inkscape {
+namespace XML {
+class Node;
+}
+}
+
+
+namespace Inkscape {
+
+namespace XML {
+
+class NodeObserver {
+public:
+ NodeObserver() {}
+
+ virtual ~NodeObserver() {}
+
+ virtual void notifyChildAdded(Node &node, Node &child, Node *prev)=0;
+
+ virtual void notifyChildRemoved(Node &node, Node &child, Node *prev)=0;
+
+ virtual void notifyChildOrderChanged(Node &node, Node &child,
+ Node *old_prev, Node *new_prev)=0;
+
+ virtual void notifyContentChanged(Node &node,
+ Util::SharedCStringPtr old_content,
+ Util::SharedCStringPtr new_content)=0;
+
+ virtual void notifyAttributeChanged(Node &node, GQuark name,
+ Util::SharedCStringPtr old_value,
+ Util::SharedCStringPtr new_value)=0;
+};
+
+}
+
+}
+
+#endif
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
+ indent-tabs-mode:nil
+ fill-column:99
+ End:
+*/
+// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :
diff --git a/src/xml/node.h b/src/xml/node.h
new file mode 100644
index 000000000..413d81a11
--- /dev/null
+++ b/src/xml/node.h
@@ -0,0 +1,131 @@
+/*
+ * Node - interface for XML nodes
+ *
+ * Copyright 2005 MenTaLguY <mental@rydia.net>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * See the file COPYING for details.
+ *
+ */
+
+#ifndef SEEN_INKSCAPE_XML_NODE_H
+#define SEEN_INKSCAPE_XML_NODE_H
+
+#include <glib/gtypes.h>
+#include "gc-anchored.h"
+#include "util/list.h"
+
+namespace Inkscape {
+namespace XML {
+
+class Session;
+class TransactionLogger;
+class AttributeRecord;
+class Document;
+class NodeEventVector;
+class NodeObserver;
+
+enum NodeType {
+ DOCUMENT_NODE,
+ ELEMENT_NODE,
+ TEXT_NODE,
+ COMMENT_NODE
+};
+
+// careful; GC::Anchored should only appear once in the inheritance
+// hierarcy; else there will be leaks
+class Node : public Inkscape::GC::Anchored {
+public:
+ Node() {}
+
+ virtual ~Node() {}
+
+ virtual NodeType type() const=0;
+
+ virtual Session *session()=0;
+
+ virtual gchar const *name() const=0;
+ virtual int code() const=0;
+ virtual void setCodeUnsafe(int code)=0;
+
+ virtual Document *document()=0;
+ virtual Document const *document() const=0;
+
+ virtual Node *duplicate() const=0;
+
+ virtual Node *root()=0;
+ virtual Node const *root() const=0;
+
+ virtual Node *parent()=0;
+ virtual Node const *parent() const=0;
+
+ virtual Node *next()=0;
+ virtual Node const *next() const=0;
+
+ virtual Node *firstChild()=0;
+ virtual Node const *firstChild() const=0;
+ virtual Node *lastChild()=0;
+ virtual Node const *lastChild() const=0;
+
+ virtual unsigned childCount() const=0;
+ virtual Node *nthChild(unsigned index)=0;
+ virtual Node const *nthChild(unsigned index) const=0;
+
+ virtual void addChild(Node *child, Node *ref)=0;
+ virtual void appendChild(Node *child)=0;
+ virtual void removeChild(Node *child)=0;
+ virtual void changeOrder(Node *child, Node *ref)=0;
+
+ virtual unsigned position() const=0;
+ virtual void setPosition(int pos)=0;
+
+ virtual gchar const *attribute(gchar const *key) const=0;
+ virtual void setAttribute(gchar const *key, gchar const *value, bool is_interactive=false)=0;
+ virtual bool matchAttributeName(gchar const *partial_name) const=0;
+
+ virtual gchar const *content() const=0;
+ virtual void setContent(gchar const *value)=0;
+
+ virtual void mergeFrom(Node const *src, gchar const *key)=0;
+
+ virtual Inkscape::Util::List<AttributeRecord const> attributeList() const=0;
+
+ virtual void synthesizeEvents(NodeEventVector const *vector, void *data)=0;
+ virtual void synthesizeEvents(NodeObserver &observer)=0;
+ virtual void addObserver(NodeObserver &observer)=0;
+ virtual void addListener(NodeEventVector const *vector, void *data)=0;
+ virtual void removeObserver(NodeObserver &observer)=0;
+ virtual void removeListenerByData(void *data)=0;
+
+protected:
+ Node(Node const &) : Anchored() {}
+
+public: // ideally these should be protected too somehow...
+ virtual void _setParent(Node *parent)=0;
+ virtual void _setNext(Node *next)=0;
+ virtual void _bindDocument(Document &document)=0;
+ virtual void _bindLogger(TransactionLogger &logger)=0;
+
+ virtual unsigned _childPosition(Node const &child) const=0;
+ virtual unsigned _cachedPosition() const=0;
+ virtual void _setCachedPosition(unsigned position) const=0;
+};
+
+}
+}
+
+#endif
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
+ indent-tabs-mode:nil
+ fill-column:99
+ End:
+*/
+// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :
diff --git a/src/xml/quote-test.cpp b/src/xml/quote-test.cpp
new file mode 100644
index 000000000..61291b9de
--- /dev/null
+++ b/src/xml/quote-test.cpp
@@ -0,0 +1,82 @@
+/* Initial author: Peter Moulder.
+ Hereby released into the Public Domain. */
+
+#include <functional>
+#include "utest/test-1ary-cases.h"
+
+/* mental disclaims all responsibility for this evil idea for testing
+ static functions. The main disadvantages are that we retain any
+ #define's and `using' directives of the included file. */
+#include "quote.cpp"
+
+struct streq_free2 {
+ bool operator()(char const *exp, char *got)
+ {
+ bool const ret = (strcmp(exp, got) == 0);
+ g_free(got);
+ return ret;
+ }
+};
+
+static bool
+test_xml_quoted_strlen()
+{
+ utest_start("xml_quoted_strlen");
+ struct Case1<char const *, size_t> cases[] = {
+ {"", 0},
+ {"x", 1},
+ {"Foo", 3},
+ {"\"", 6},
+ {"&", 5},
+ {"<", 4},
+ {">", 4},
+ {"a\"b", 8},
+ {"a\"b<c>d;!@#$%^*(\\)?", 30}
+ };
+ test_1ary_cases<size_t, char const *, size_t, std::equal_to<size_t> >("xml_quoted_strlen",
+ xml_quoted_strlen,
+ G_N_ELEMENTS(cases),
+ cases);
+ return utest_end();
+}
+
+static bool
+test_xml_quote_strdup()
+{
+ utest_start("xml_quote_strdup");
+ struct Case1<char const *, char const *> cases[] = {
+ {"", ""},
+ {"x", "x"},
+ {"Foo", "Foo"},
+ {"\"", "&quot;"},
+ {"&", "&amp;"},
+ {"<", "&lt;"},
+ {">", "&gt;"},
+ {"a\"b<c>d;!@#$%^*(\\)?", "a&quot;b&lt;c&gt;d;!@#$%^*(\\)?"}
+ };
+ test_1ary_cases<char *, char const *, char const *, streq_free2>("xml_quote_strdup",
+ xml_quote_strdup,
+ G_N_ELEMENTS(cases),
+ cases);
+ return utest_end();
+}
+
+int main() {
+ bool const succ = (test_xml_quoted_strlen()
+ && test_xml_quote_strdup());
+ return ( succ
+ ? EXIT_SUCCESS
+ : EXIT_FAILURE );
+}
+
+
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
+ indent-tabs-mode:nil
+ fill-column:99
+ End:
+*/
+// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :
diff --git a/src/xml/quote-test.h b/src/xml/quote-test.h
new file mode 100644
index 000000000..39f7e6167
--- /dev/null
+++ b/src/xml/quote-test.h
@@ -0,0 +1,31 @@
+#include <cxxtest/TestSuite.h>
+
+class XmlQuoteTest : public CxxTest::TestSuite
+{
+public:
+
+ XmlQuoteTest()
+ {
+ }
+ virtual ~XmlQuoteTest() {}
+
+// createSuite and destroySuite get us per-suite setup and teardown
+// without us having to worry about static initialization order, etc.
+ static XmlQuoteTest *createSuite() { return new XmlQuoteTest(); }
+ static void destroySuite( XmlQuoteTest *suite ) { delete suite; }
+
+ void testFoo()
+ {
+ }
+};
+
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
+ indent-tabs-mode:nil
+ fill-column:99
+ End:
+*/
+// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :
diff --git a/src/xml/quote.cpp b/src/xml/quote.cpp
new file mode 100644
index 000000000..b5505c5e1
--- /dev/null
+++ b/src/xml/quote.cpp
@@ -0,0 +1,86 @@
+/** \file
+ * XML quoting routines.
+ */
+
+/* Based on Lauris' repr_quote_write in repr-io.cpp.
+ *
+ * Copyright (C) 1999-2002 Lauris Kaplinski
+ * Copyright (C) 2004 Monash University
+ *
+ * May be modified and/or redistributed under the terms of version 2
+ * of the GNU General Public License: see the file `COPYING'.
+ */
+
+#include <string.h>
+#include <glib/gmem.h>
+
+
+/** \return strlen(xml_quote_strdup(\a val)) (without doing the malloc).
+ * \pre val != NULL
+ */
+static size_t
+xml_quoted_strlen(char const *val)
+{
+ size_t ret = 0;
+ for (; *val != '\0'; val++) {
+ switch (*val) {
+ case '"': ret += sizeof("&quot;") - 1; break;
+ case '&': ret += sizeof("&amp;") - 1; break;
+ case '<': ret += sizeof("&lt;") - 1; break;
+ case '>': ret += sizeof("&gt;") - 1; break;
+ default: ++ret; break;
+ }
+ }
+ return ret;
+}
+
+/** Writes \a src (including the NUL byte) to \a dest, doing XML quoting as necessary.
+ *
+ * \pre \a src != NULL.
+ * \pre \a dest must have enough space for (xml_quoted_strlen(src) + 1) bytes.
+ */
+static void
+xml_quote(char *dest, char const *src)
+{
+#define COPY_LIT(_lit) do { \
+ size_t cpylen = sizeof(_lit "") - 1; \
+ memcpy(dest, _lit, cpylen); \
+ dest += cpylen; \
+ } while(0)
+
+ for (; *src != '\0'; ++src) {
+ switch (*src) {
+ case '"': COPY_LIT("&quot;"); break;
+ case '&': COPY_LIT("&amp;"); break;
+ case '<': COPY_LIT("&lt;"); break;
+ case '>': COPY_LIT("&gt;"); break;
+ default: *dest++ = *src; break;
+ }
+ }
+ *dest = '\0';
+
+#undef COPY_LIT
+}
+
+/** \return A g_malloc'd buffer containing an XML-quoted version of \a src.
+ * \pre src != NULL.
+ */
+char *
+xml_quote_strdup(char const *src)
+{
+ size_t const quoted_size = xml_quoted_strlen(src) + 1;
+ char *ret = (char *) g_malloc(quoted_size);
+ xml_quote(ret, src);
+ return ret;
+}
+
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
+ indent-tabs-mode:nil
+ fill-column:99
+ End:
+*/
+// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :
diff --git a/src/xml/quote.h b/src/xml/quote.h
new file mode 100644
index 000000000..597272cd3
--- /dev/null
+++ b/src/xml/quote.h
@@ -0,0 +1,7 @@
+#ifndef SEEN_XML_QUOTE_H
+#define SEEN_XML_QUOTE_H
+
+char *xml_quote_strdup(char const *src);
+
+
+#endif /* !SEEN_XML_QUOTE_H */
diff --git a/src/xml/repr-action-test.cpp b/src/xml/repr-action-test.cpp
new file mode 100644
index 000000000..7f5f27f2d
--- /dev/null
+++ b/src/xml/repr-action-test.cpp
@@ -0,0 +1,77 @@
+#include <stdlib.h>
+#include <glib.h>
+#include "../utest/utest.h"
+
+#include "repr.h"
+#include "event-fns.h"
+
+int main(int argc, char *argv[]) {
+ Inkscape::XML::Document *document;
+ Inkscape::XML::Node *a, *b, *c, *root;
+
+ Inkscape::GC::init();
+
+ document = sp_repr_document_new("test");
+ root = sp_repr_document_root(document);
+
+ utest_start("XML Transactions");
+
+ a = sp_repr_new("a");
+ b = sp_repr_new("b");
+ c = sp_repr_new("c");
+
+ UTEST_TEST("rollback of node addition") {
+ sp_repr_begin_transaction(document);
+ UTEST_ASSERT(sp_repr_parent(a) == NULL);
+
+ root->appendChild(a);
+ UTEST_ASSERT(sp_repr_parent(a) == root);
+
+ sp_repr_rollback(document);
+ UTEST_ASSERT(sp_repr_parent(a) == NULL);
+ }
+
+ UTEST_TEST("rollback of node removal") {
+ root->appendChild(a);
+
+ sp_repr_begin_transaction(document);
+ UTEST_ASSERT(sp_repr_parent(a) == root);
+
+ sp_repr_unparent(a);
+ UTEST_ASSERT(sp_repr_parent(a) == NULL);
+
+ sp_repr_rollback(document);
+ UTEST_ASSERT(sp_repr_parent(a) == root);
+ }
+
+ sp_repr_unparent(a);
+
+ UTEST_TEST("rollback of node reordering") {
+ root->appendChild(a);
+ root->appendChild(b);
+ root->appendChild(c);
+
+ sp_repr_begin_transaction(document);
+ UTEST_ASSERT(sp_repr_next(a) == b);
+ UTEST_ASSERT(sp_repr_next(b) == c);
+ UTEST_ASSERT(sp_repr_next(c) == NULL);
+
+ root->changeOrder(b, c);
+ UTEST_ASSERT(sp_repr_next(a) == c);
+ UTEST_ASSERT(sp_repr_next(b) == NULL);
+ UTEST_ASSERT(sp_repr_next(c) == b);
+
+ sp_repr_rollback(document);
+ UTEST_ASSERT(sp_repr_next(a) == b);
+ UTEST_ASSERT(sp_repr_next(b) == c);
+ UTEST_ASSERT(sp_repr_next(c) == NULL);
+ }
+
+ sp_repr_unparent(a);
+ sp_repr_unparent(b);
+ sp_repr_unparent(c);
+
+ /* lots more tests needed ... */
+
+ return utest_end() ? 0 : 1;
+}
diff --git a/src/xml/repr-action-test.h b/src/xml/repr-action-test.h
new file mode 100644
index 000000000..10f7d52f1
--- /dev/null
+++ b/src/xml/repr-action-test.h
@@ -0,0 +1,31 @@
+#include <cxxtest/TestSuite.h>
+
+class XmlReprActionTest : public CxxTest::TestSuite
+{
+public:
+
+ XmlReprActionTest()
+ {
+ }
+ virtual ~XmlReprActionTest() {}
+
+// createSuite and destroySuite get us per-suite setup and teardown
+// without us having to worry about static initialization order, etc.
+ static XmlReprActionTest *createSuite() { return new XmlReprActionTest(); }
+ static void destroySuite( XmlReprActionTest *suite ) { delete suite; }
+
+ void testFoo()
+ {
+ }
+};
+
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
+ indent-tabs-mode:nil
+ fill-column:99
+ End:
+*/
+// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :
diff --git a/src/xml/repr-css.cpp b/src/xml/repr-css.cpp
new file mode 100644
index 000000000..4953c2657
--- /dev/null
+++ b/src/xml/repr-css.cpp
@@ -0,0 +1,261 @@
+/*
+ * bulia byak <buliabyak@users.sf.net>
+*/
+
+#define SP_REPR_CSS_C
+
+
+#include <glibmm/ustring.h>
+
+#include "xml/repr.h"
+#include "xml/simple-node.h"
+
+using Inkscape::Util::List;
+using Inkscape::XML::AttributeRecord;
+using Inkscape::XML::SimpleNode;
+using Inkscape::XML::Node;
+using Inkscape::XML::NodeType;
+
+struct SPCSSAttrImpl : public SimpleNode, public SPCSSAttr {
+public:
+ SPCSSAttrImpl() : SimpleNode(g_quark_from_static_string("css")) {}
+
+ NodeType type() const { return Inkscape::XML::ELEMENT_NODE; }
+
+protected:
+ SimpleNode *_duplicate() const { return new SPCSSAttrImpl(*this); }
+};
+
+static void sp_repr_css_add_components(SPCSSAttr *css, Node *repr, gchar const *attr);
+
+SPCSSAttr *
+sp_repr_css_attr_new()
+{
+ return new SPCSSAttrImpl();
+}
+
+void
+sp_repr_css_attr_unref(SPCSSAttr *css)
+{
+ g_assert(css != NULL);
+ Inkscape::GC::release((Node *) css);
+}
+
+SPCSSAttr *sp_repr_css_attr(Node *repr, gchar const *attr)
+{
+ g_assert(repr != NULL);
+ g_assert(attr != NULL);
+
+ SPCSSAttr *css = sp_repr_css_attr_new();
+ sp_repr_css_add_components(css, repr, attr);
+ return css;
+}
+
+SPCSSAttr *sp_repr_css_attr_inherited(Node *repr, gchar const *attr)
+{
+ g_assert(repr != NULL);
+ g_assert(attr != NULL);
+
+ SPCSSAttr *css = sp_repr_css_attr_new();
+
+ sp_repr_css_add_components(css, repr, attr);
+ Node *current = sp_repr_parent(repr);
+
+ while (current) {
+ sp_repr_css_add_components(css, current, attr);
+ current = sp_repr_parent(current);
+ }
+
+ return css;
+}
+
+static void
+sp_repr_css_add_components(SPCSSAttr *css, Node *repr, gchar const *attr)
+{
+ g_assert(css != NULL);
+ g_assert(repr != NULL);
+ g_assert(attr != NULL);
+
+ char const *data = repr->attribute(attr);
+ sp_repr_css_attr_add_from_string(css, data);
+}
+
+char const *
+sp_repr_css_property(SPCSSAttr *css, gchar const *name, gchar const *defval)
+{
+ g_assert(css != NULL);
+ g_assert(name != NULL);
+
+ char const *attr = ((Node *)css)->attribute(name);
+ return ( attr == NULL
+ ? defval
+ : attr );
+}
+
+bool
+sp_repr_css_property_is_unset(SPCSSAttr *css, gchar const *name)
+{
+ g_assert(css != NULL);
+ g_assert(name != NULL);
+
+ char const *attr = ((Node *)css)->attribute(name);
+ return (attr && !strcmp(attr, "inkscape:unset"));
+}
+
+
+void
+sp_repr_css_set_property(SPCSSAttr *css, gchar const *name, gchar const *value)
+{
+ g_assert(css != NULL);
+ g_assert(name != NULL);
+
+ sp_repr_set_attr((Node *) css, name, value);
+}
+
+void
+sp_repr_css_unset_property(SPCSSAttr *css, gchar const *name)
+{
+ g_assert(css != NULL);
+ g_assert(name != NULL);
+
+ sp_repr_set_attr((Node *) css, name, "inkscape:unset");
+}
+
+double
+sp_repr_css_double_property(SPCSSAttr *css, gchar const *name, double defval)
+{
+ g_assert(css != NULL);
+ g_assert(name != NULL);
+
+ return sp_repr_get_double_attribute((Node *) css, name, defval);
+}
+
+gchar *
+sp_repr_css_write_string(SPCSSAttr *css)
+{
+ Glib::ustring buffer;
+
+ for ( List<AttributeRecord const> iter = css->attributeList() ;
+ iter ; ++iter )
+ {
+ if (iter->value && !strcmp(iter->value, "inkscape:unset")) {
+ continue;
+ }
+
+ buffer.append(g_quark_to_string(iter->key));
+ buffer.push_back(':');
+ buffer.append(iter->value);
+ if (rest(iter)) {
+ buffer.push_back(';');
+ }
+ }
+
+ return (buffer.empty() ? NULL : g_strdup (buffer.c_str()));
+}
+
+void
+sp_repr_css_set(Node *repr, SPCSSAttr *css, gchar const *attr)
+{
+ g_assert(repr != NULL);
+ g_assert(css != NULL);
+ g_assert(attr != NULL);
+
+ gchar *value = sp_repr_css_write_string(css);
+
+ repr->setAttribute(attr, value);
+
+ if (value) g_free (value);
+}
+
+void
+sp_repr_css_print(SPCSSAttr *css)
+{
+ for ( List<AttributeRecord const> iter = css->attributeList() ;
+ iter ; ++iter )
+ {
+ g_print(g_quark_to_string(iter->key));
+ g_print(":\t");
+ g_print(iter->value);
+ g_print("\n");
+ }
+}
+
+void
+sp_repr_css_merge(SPCSSAttr *dst, SPCSSAttr *src)
+{
+ g_assert(dst != NULL);
+ g_assert(src != NULL);
+
+ dst->mergeFrom(src, "");
+}
+
+void
+sp_repr_css_attr_add_from_string(SPCSSAttr *css, gchar const *data)
+{
+ if (data != NULL) {
+ char *new_str = g_strdup(data);
+ char **token = g_strsplit(new_str, ";", 0);
+ for (char **ctoken = token; *ctoken != NULL; ctoken++) {
+ char *current = g_strstrip(*ctoken);
+ char *key = current;
+ char *val;
+ for (val = key; *val != '\0'; val++)
+ if (*val == ':') break;
+ if (*val == '\0') break;
+ *val++ = '\0';
+ key = g_strstrip(key);
+ val = g_strstrip(val);
+ if (*val == '\0') break;
+
+ /* fixme: CSS specifies that later declarations override earlier ones with the same
+ key. (Ref: http://www.w3.org/TR/REC-CSS2/cascade.html#cascading-order point 4.)
+ Either add a precondition that there are no key duplicates in the string, or get rid
+ of the below condition (documenting the change that data[] will override existing
+ values in *css), or process the list in reverse order. */
+ if (!css->attribute(key))
+ sp_repr_set_attr((Node *) css, key, val);
+ }
+ g_strfreev(token);
+ g_free(new_str);
+ }
+}
+
+void
+sp_repr_css_change(Node *repr, SPCSSAttr *css, gchar const *attr)
+{
+ g_assert(repr != NULL);
+ g_assert(css != NULL);
+ g_assert(attr != NULL);
+
+ SPCSSAttr *current = sp_repr_css_attr(repr, attr);
+ sp_repr_css_merge(current, css);
+ sp_repr_css_set(repr, current, attr);
+
+ sp_repr_css_attr_unref(current);
+}
+
+void
+sp_repr_css_change_recursive(Node *repr, SPCSSAttr *css, gchar const *attr)
+{
+ g_assert(repr != NULL);
+ g_assert(css != NULL);
+ g_assert(attr != NULL);
+
+ sp_repr_css_change(repr, css, attr);
+
+ for (Node *child = repr->firstChild(); child != NULL; child = child->next()) {
+ sp_repr_css_change_recursive(child, css, attr);
+ }
+}
+
+
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
+ indent-tabs-mode:nil
+ fill-column:99
+ End:
+*/
+// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :
diff --git a/src/xml/repr-io.cpp b/src/xml/repr-io.cpp
new file mode 100644
index 000000000..88fae5319
--- /dev/null
+++ b/src/xml/repr-io.cpp
@@ -0,0 +1,786 @@
+#define __SP_REPR_IO_C__
+
+/*
+ * Dirty DOM-like tree
+ *
+ * Authors:
+ * Lauris Kaplinski <lauris@kaplinski.com>
+ * bulia byak <buliabyak@users.sf.net>
+ *
+ * Copyright (C) 1999-2002 Lauris Kaplinski
+ *
+ * Released under GNU GPL, read the file 'COPYING' for more information
+ */
+
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif
+
+
+
+#include "xml/repr.h"
+#include "xml/attribute-record.h"
+
+#include "io/sys.h"
+#include "io/uristream.h"
+#include "io/gzipstream.h"
+
+
+using Inkscape::IO::Writer;
+using Inkscape::Util::List;
+using Inkscape::Util::cons;
+using Inkscape::XML::Document;
+using Inkscape::XML::Node;
+using Inkscape::XML::AttributeRecord;
+
+static Document *sp_repr_do_read (xmlDocPtr doc, const gchar *default_ns);
+static Node *sp_repr_svg_read_node (xmlNodePtr node, const gchar *default_ns, GHashTable *prefix_map);
+static gint sp_repr_qualified_name (gchar *p, gint len, xmlNsPtr ns, const xmlChar *name, const gchar *default_ns, GHashTable *prefix_map);
+static void sp_repr_write_stream_root_element (Node *repr, Writer &out, gboolean add_whitespace, gchar const *default_ns);
+static void sp_repr_write_stream (Node *repr, Writer &out, gint indent_level, gboolean add_whitespace, Glib::QueryQuark elide_prefix);
+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);
+
+#ifdef HAVE_LIBWMF
+static xmlDocPtr sp_wmf_convert (const char * file_name);
+static char * sp_wmf_image_name (void * context);
+#endif /* HAVE_LIBWMF */
+
+
+class XmlSource
+{
+public:
+ XmlSource()
+ : filename(0),
+ fp(0),
+ first(false),
+ dummy("x"),
+ instr(0),
+ gzin(0)
+ {
+ }
+ virtual ~XmlSource()
+ {
+ close();
+ }
+
+ void setFile( char const * filename );
+
+ static int readCb( void * context, char * buffer, int len);
+ static int closeCb(void * context);
+
+
+ int read( char * buffer, int len );
+ int close();
+private:
+ const char* filename;
+ FILE* fp;
+ bool first;
+ Inkscape::URI dummy;
+ Inkscape::IO::UriInputStream* instr;
+ Inkscape::IO::GzipInputStream* gzin;
+};
+
+void XmlSource::setFile( char const * filename ) {
+ this->filename = filename;
+ fp = Inkscape::IO::fopen_utf8name(filename, "r");
+ first = true;
+}
+
+
+int XmlSource::readCb( void * context, char * buffer, int len )
+{
+ int retVal = -1;
+ if ( context ) {
+ XmlSource* self = static_cast<XmlSource*>(context);
+ retVal = self->read( buffer, len );
+ }
+ return retVal;
+}
+
+int XmlSource::closeCb(void * context)
+{
+ if ( context ) {
+ XmlSource* self = static_cast<XmlSource*>(context);
+ self->close();
+ }
+ return 0;
+}
+
+int XmlSource::read( char *buffer, int len )
+{
+ int retVal = 0;
+ size_t got = 0;
+
+ if ( first ) {
+ first = false;
+ char tmp[] = {0,0};
+ size_t some = fread( tmp, 1, 2, fp );
+
+ if ( (some >= 2) && (tmp[0] == 0x1f) && ((unsigned char)(tmp[1]) == 0x8b) ) {
+ //g_message(" the file being read is gzip'd. extract it");
+ fclose(fp);
+ fp = 0;
+ fp = Inkscape::IO::fopen_utf8name(filename, "r");
+ instr = new Inkscape::IO::UriInputStream(fp, dummy);
+ gzin = new Inkscape::IO::GzipInputStream(*instr);
+ int single = 0;
+ while ( (int)got < len && single >= 0 )
+ {
+ single = gzin->get();
+ if ( single >= 0 ) {
+ buffer[got++] = 0x0ff & single;
+ } else {
+ break;
+ }
+ }
+ //g_message(" extracted %d bytes this pass", got );
+ } else {
+ memcpy( buffer, tmp, some );
+ got = some;
+ }
+ } else if ( gzin ) {
+ int single = 0;
+ while ( (int)got < len && single >= 0 )
+ {
+ single = gzin->get();
+ if ( single >= 0 ) {
+ buffer[got++] = 0x0ff & single;
+ } else {
+ break;
+ }
+ }
+ //g_message(" extracted %d bytes this pass b", got );
+ } else {
+ got = fread( buffer, 1, len, fp );
+ }
+
+ if ( feof(fp) ) {
+ retVal = got;
+ }
+ else if ( ferror(fp) ) {
+ retVal = -1;
+ }
+ else {
+ retVal = got;
+ }
+
+ return retVal;
+}
+
+int XmlSource::close()
+{
+ if ( gzin ) {
+ gzin->close();
+ delete gzin;
+ gzin = 0;
+ }
+ if ( instr ) {
+ instr->close();
+ fp = 0;
+ delete instr;
+ instr = 0;
+ }
+ if ( fp ) {
+ fclose(fp);
+ fp = 0;
+ }
+ return 0;
+}
+
+/**
+ * Reads XML from a file, including WMF files, and returns the Document.
+ * The default namespace can also be specified, if desired.
+ */
+Document *
+sp_repr_read_file (const gchar * filename, const gchar *default_ns)
+{
+ xmlDocPtr doc = 0;
+ Document * rdoc = 0;
+
+ xmlSubstituteEntitiesDefault(1);
+
+ g_return_val_if_fail (filename != NULL, NULL);
+ g_return_val_if_fail (Inkscape::IO::file_test( filename, G_FILE_TEST_EXISTS ), NULL);
+
+ // TODO: bulia, please look over
+ gsize bytesRead = 0;
+ gsize bytesWritten = 0;
+ GError* error = NULL;
+ // TODO: need to replace with our own fopen and reading
+ gchar* localFilename = g_filename_from_utf8 ( filename,
+ -1, &bytesRead, &bytesWritten, &error);
+ g_return_val_if_fail( localFilename != NULL, NULL );
+
+ Inkscape::IO::dump_fopen_call( filename, "N" );
+
+ XmlSource src;
+ src.setFile(filename);
+
+ xmlDocPtr doubleDoc = xmlReadIO( XmlSource::readCb,
+ XmlSource::closeCb,
+ &src,
+ localFilename,
+ NULL, //"UTF-8",
+ XML_PARSE_NOENT );
+
+
+#ifdef HAVE_LIBWMF
+ if (strlen (localFilename) > 4) {
+ if ( (strcmp (localFilename + strlen (localFilename) - 4,".wmf") == 0)
+ || (strcmp (localFilename + strlen (localFilename) - 4,".WMF") == 0))
+ doc = sp_wmf_convert (localFilename);
+ else
+ doc = xmlParseFile (localFilename);
+ }
+ else {
+ doc = xmlParseFile (localFilename);
+ }
+#else /* !HAVE_LIBWMF */
+ //doc = xmlParseFile (localFilename);
+#endif /* !HAVE_LIBWMF */
+
+ //rdoc = sp_repr_do_read (doc, default_ns);
+ rdoc = sp_repr_do_read (doubleDoc, default_ns);
+ if (doc)
+ xmlFreeDoc (doc);
+
+ if ( localFilename != NULL )
+ g_free (localFilename);
+
+ if ( doubleDoc != NULL )
+ {
+ xmlFreeDoc( doubleDoc );
+ }
+
+ return rdoc;
+}
+
+/**
+ * Reads and parses XML from a buffer, returning it as an Document
+ */
+Document *
+sp_repr_read_mem (const gchar * buffer, gint length, const gchar *default_ns)
+{
+ xmlDocPtr doc;
+ Document * rdoc;
+
+ xmlSubstituteEntitiesDefault(1);
+
+ g_return_val_if_fail (buffer != NULL, NULL);
+
+ doc = xmlParseMemory ((gchar *) buffer, length);
+
+ rdoc = sp_repr_do_read (doc, default_ns);
+ if (doc)
+ xmlFreeDoc (doc);
+ return rdoc;
+}
+
+namespace Inkscape {
+
+struct compare_quark_ids {
+ bool operator()(Glib::QueryQuark const &a, Glib::QueryQuark const &b) {
+ return a.id() < b.id();
+ }
+};
+
+}
+
+namespace {
+
+typedef std::map<Glib::QueryQuark, Glib::QueryQuark, Inkscape::compare_quark_ids> PrefixMap;
+
+Glib::QueryQuark qname_prefix(Glib::QueryQuark qname) {
+ static PrefixMap prefix_map;
+ PrefixMap::iterator iter = prefix_map.find(qname);
+ if ( iter != prefix_map.end() ) {
+ return (*iter).second;
+ } else {
+ gchar const *name_string=g_quark_to_string(qname);
+ gchar const *prefix_end=strchr(name_string, ':');
+ if (prefix_end) {
+ Glib::Quark prefix=Glib::ustring(name_string, prefix_end);
+ prefix_map.insert(PrefixMap::value_type(qname, prefix));
+ return prefix;
+ } else {
+ return GQuark(0);
+ }
+ }
+}
+
+}
+
+namespace {
+
+void promote_to_svg_namespace(Node *repr) {
+ if ( repr->type() == Inkscape::XML::ELEMENT_NODE ) {
+ GQuark code = repr->code();
+ if (!qname_prefix(code).id()) {
+ gchar *svg_name = g_strconcat("svg:", g_quark_to_string(code), NULL);
+ repr->setCodeUnsafe(g_quark_from_string(svg_name));
+ g_free(svg_name);
+ }
+ for ( Node *child = sp_repr_children(repr) ; child ; child = sp_repr_next(child) ) {
+ promote_to_svg_namespace(child);
+ }
+ }
+}
+
+}
+
+/**
+ * Reads in a XML file to create a Document
+ */
+Document *
+sp_repr_do_read (xmlDocPtr doc, const gchar *default_ns)
+{
+ if (doc == NULL) return NULL;
+ xmlNodePtr node=xmlDocGetRootElement (doc);
+ if (node == NULL) return NULL;
+
+ GHashTable * prefix_map;
+ prefix_map = g_hash_table_new (g_str_hash, g_str_equal);
+
+ GSList *reprs=NULL;
+ Node *root=NULL;
+
+ for ( node = doc->children ; node != NULL ; node = node->next ) {
+ if (node->type == XML_ELEMENT_NODE) {
+ Node *repr=sp_repr_svg_read_node (node, default_ns, prefix_map);
+ reprs = g_slist_append(reprs, repr);
+
+ if (!root) {
+ root = repr;
+ } else {
+ root = NULL;
+ break;
+ }
+ } else if ( node->type == XML_COMMENT_NODE ) {
+ Node *comment=sp_repr_svg_read_node(node, default_ns, prefix_map);
+ reprs = g_slist_append(reprs, comment);
+ }
+ }
+
+ Document *rdoc=NULL;
+
+ if (root != NULL) {
+ /* promote elements of SVG documents that don't use namespaces
+ * into the SVG namespace */
+ if ( default_ns && !strcmp(default_ns, SP_SVG_NS_URI)
+ && !strcmp(root->name(), "svg") )
+ {
+ promote_to_svg_namespace(root);
+ }
+
+ rdoc = sp_repr_document_new_list(reprs);
+ }
+
+ for ( GSList *iter = reprs ; iter ; iter = iter->next ) {
+ Node *repr=(Node *)iter->data;
+ Inkscape::GC::release(repr);
+ }
+ g_slist_free(reprs);
+
+ g_hash_table_destroy (prefix_map);
+
+ return rdoc;
+}
+
+gint
+sp_repr_qualified_name (gchar *p, gint len, xmlNsPtr ns, const xmlChar *name, const gchar *default_ns, GHashTable *prefix_map)
+{
+ const xmlChar *prefix;
+ if ( ns && ns->href ) {
+ prefix = (xmlChar*)sp_xml_ns_uri_prefix ((gchar*)ns->href, (char*)ns->prefix);
+ g_hash_table_insert (prefix_map, (gpointer)prefix, (gpointer)ns->href);
+ } else {
+ prefix = NULL;
+ }
+
+ if (prefix)
+ return g_snprintf (p, len, "%s:%s", (gchar*)prefix, name);
+ else
+ return g_snprintf (p, len, "%s", name);
+}
+
+static Node *
+sp_repr_svg_read_node (xmlNodePtr node, const gchar *default_ns, GHashTable *prefix_map)
+{
+ Node *repr, *crepr;
+ xmlAttrPtr prop;
+ xmlNodePtr child;
+ gchar c[256];
+
+ if (node->type == XML_TEXT_NODE || node->type == XML_CDATA_SECTION_NODE) {
+
+ if (node->content == NULL || *(node->content) == '\0')
+ return NULL; // empty text node
+
+ bool preserve = (xmlNodeGetSpacePreserve (node) == 1);
+
+ xmlChar *p;
+ for (p = node->content; *p && g_ascii_isspace (*p) && !preserve; p++)
+ ; // skip all whitespace
+
+ if (!(*p)) { // this is an all-whitespace node, and preserve == default
+ return NULL; // we do not preserve all-whitespace nodes unless we are asked to
+ }
+
+ Node *rdoc = sp_repr_new_text((const gchar *)node->content);
+ return rdoc;
+ }
+
+ if (node->type == XML_COMMENT_NODE)
+ return sp_repr_new_comment((const gchar *)node->content);
+
+ if (node->type == XML_ENTITY_DECL) return NULL;
+
+ sp_repr_qualified_name (c, 256, node->ns, node->name, default_ns, prefix_map);
+ repr = sp_repr_new (c);
+ /* TODO remember node->ns->prefix if node->ns != NULL */
+
+ for (prop = node->properties; prop != NULL; prop = prop->next) {
+ if (prop->children) {
+ sp_repr_qualified_name (c, 256, prop->ns, prop->name, default_ns, prefix_map);
+ repr->setAttribute(c, (gchar*)prop->children->content);
+ /* TODO remember prop->ns->prefix if prop->ns != NULL */
+ }
+ }
+
+ if (node->content)
+ repr->setContent((gchar*)node->content);
+
+ child = node->xmlChildrenNode;
+ for (child = node->xmlChildrenNode; child != NULL; child = child->next) {
+ crepr = sp_repr_svg_read_node (child, default_ns, prefix_map);
+ if (crepr) {
+ repr->appendChild(crepr);
+ Inkscape::GC::release(crepr);
+ }
+ }
+
+ return repr;
+}
+
+void
+sp_repr_save_stream (Document *doc, FILE *fp, gchar const *default_ns, bool compress)
+{
+ Node *repr;
+ const gchar *str;
+
+ Inkscape::URI dummy("x");
+ Inkscape::IO::UriOutputStream bout(fp, dummy);
+ Inkscape::IO::GzipOutputStream *gout = compress ? new Inkscape::IO::GzipOutputStream(bout) : NULL;
+ Inkscape::IO::OutputStreamWriter *out = compress ? new Inkscape::IO::OutputStreamWriter( *gout ) : new Inkscape::IO::OutputStreamWriter( bout );
+
+ /* fixme: do this The Right Way */
+
+ out->writeString( "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?>\n" );
+
+ str = ((Node *)doc)->attribute("doctype");
+ if (str) {
+ out->writeString( str );
+ }
+
+ repr = sp_repr_document_first_child(doc);
+ for ( repr = sp_repr_document_first_child(doc) ;
+ repr ; repr = sp_repr_next(repr) )
+ {
+ if ( repr->type() == Inkscape::XML::ELEMENT_NODE ) {
+ sp_repr_write_stream_root_element(repr, *out, TRUE, default_ns);
+ } else if ( repr->type() == Inkscape::XML::COMMENT_NODE ) {
+ sp_repr_write_stream(repr, *out, 0, TRUE, GQuark(0));
+ out->writeChar( '\n' );
+ } else {
+ sp_repr_write_stream(repr, *out, 0, TRUE, GQuark(0));
+ }
+ }
+ if ( out ) {
+ delete out;
+ out = NULL;
+ }
+ if ( gout ) {
+ delete gout;
+ gout = NULL;
+ }
+}
+
+/* Returns TRUE if file successfully saved; FALSE if not
+ */
+gboolean
+sp_repr_save_file (Document *doc, const gchar *filename,
+ gchar const *default_ns)
+{
+ if (filename == NULL) {
+ return FALSE;
+ }
+ bool compress = false;
+ {
+ if (strlen (filename) > 5) {
+ gchar tmp[] = {0,0,0,0,0,0};
+ strncpy( tmp, filename + strlen (filename) - 5, 6 );
+ tmp[5] = 0;
+ if ( strcasecmp(".svgz", tmp ) == 0 )
+ {
+ //g_message("TIME TO COMPRESS THE OUTPUT FOR SVGZ");
+ compress = true;
+ }
+ }
+ }
+
+ Inkscape::IO::dump_fopen_call( filename, "B" );
+ FILE *file = Inkscape::IO::fopen_utf8name(filename, "w");
+ if (file == NULL) {
+ return FALSE;
+ }
+
+ sp_repr_save_stream (doc, file, default_ns, compress);
+
+ if (fclose (file) != 0) {
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+void
+sp_repr_print (Node * repr)
+{
+ Inkscape::IO::StdOutputStream bout;
+ Inkscape::IO::OutputStreamWriter out(bout);
+
+ sp_repr_write_stream (repr, out, 0, TRUE, GQuark(0));
+
+ return;
+}
+
+/* (No doubt this function already exists elsewhere.) */
+static void
+repr_quote_write (Writer &out, const gchar * val)
+{
+ if (!val) return;
+
+ for (; *val != '\0'; val++) {
+ switch (*val) {
+ case '"': out.writeString( "&quot;" ); break;
+ case '&': out.writeString( "&amp;" ); break;
+ case '<': out.writeString( "&lt;" ); break;
+ case '>': out.writeString( "&gt;" ); break;
+ default: out.writeChar( *val ); break;
+ }
+ }
+}
+
+namespace {
+
+typedef std::map<Glib::QueryQuark, gchar const *, Inkscape::compare_quark_ids> LocalNameMap;
+typedef std::map<Glib::QueryQuark, Inkscape::Util::SharedCStringPtr, Inkscape::compare_quark_ids> NSMap;
+
+gchar const *qname_local_name(Glib::QueryQuark qname) {
+ static LocalNameMap local_name_map;
+ LocalNameMap::iterator iter = local_name_map.find(qname);
+ if ( iter != local_name_map.end() ) {
+ return (*iter).second;
+ } else {
+ gchar const *name_string=g_quark_to_string(qname);
+ gchar const *prefix_end=strchr(name_string, ':');
+ if (prefix_end) {
+ return prefix_end + 1;
+ } else {
+ return name_string;
+ }
+ }
+}
+
+void add_ns_map_entry(NSMap &ns_map, Glib::QueryQuark prefix) {
+ using Inkscape::Util::SharedCStringPtr;
+
+ static const Glib::QueryQuark xml_prefix("xml");
+
+ NSMap::iterator iter=ns_map.find(prefix);
+ if ( iter == ns_map.end() ) {
+ if (prefix.id()) {
+ gchar const *uri=sp_xml_ns_prefix_uri(g_quark_to_string(prefix));
+ if (uri) {
+ ns_map.insert(NSMap::value_type(prefix, SharedCStringPtr::coerce(uri)));
+ } else if ( prefix != xml_prefix ) {
+ g_warning("No namespace known for normalized prefix %s", g_quark_to_string(prefix));
+ }
+ } else {
+ ns_map.insert(NSMap::value_type(prefix, SharedCStringPtr()));
+ }
+ }
+}
+
+void populate_ns_map(NSMap &ns_map, Node &repr) {
+ if ( repr.type() == Inkscape::XML::ELEMENT_NODE ) {
+ add_ns_map_entry(ns_map, qname_prefix(repr.code()));
+ for ( List<AttributeRecord const> iter=repr.attributeList() ;
+ iter ; ++iter )
+ {
+ Glib::QueryQuark prefix=qname_prefix(iter->key);
+ if (prefix.id()) {
+ add_ns_map_entry(ns_map, prefix);
+ }
+ }
+ for ( Node *child=sp_repr_children(&repr) ;
+ child ; child = sp_repr_next(child) )
+ {
+ populate_ns_map(ns_map, *child);
+ }
+ }
+}
+
+}
+
+void
+sp_repr_write_stream_root_element (Node *repr, Writer &out, gboolean add_whitespace, gchar const *default_ns)
+{
+ using Inkscape::Util::SharedCStringPtr;
+ g_assert(repr != NULL);
+ Glib::QueryQuark xml_prefix=g_quark_from_static_string("xml");
+
+ NSMap ns_map;
+ populate_ns_map(ns_map, *repr);
+
+ Glib::QueryQuark elide_prefix=GQuark(0);
+ if ( default_ns && ns_map.find(GQuark(0)) == ns_map.end() ) {
+ elide_prefix = g_quark_from_string(sp_xml_ns_uri_prefix(default_ns, NULL));
+ }
+
+ List<AttributeRecord const> attributes=repr->attributeList();
+ for ( NSMap::iterator iter=ns_map.begin() ; iter != ns_map.end() ; ++iter )
+ {
+ Glib::QueryQuark prefix=(*iter).first;
+ SharedCStringPtr ns_uri=(*iter).second;
+
+ if (prefix.id()) {
+ if ( prefix != xml_prefix ) {
+ if ( elide_prefix == prefix ) {
+ attributes = cons(AttributeRecord(g_quark_from_static_string("xmlns"), ns_uri), attributes);
+ }
+
+ Glib::ustring attr_name="xmlns:";
+ attr_name.append(g_quark_to_string(prefix));
+ GQuark key = g_quark_from_string(attr_name.c_str());
+ attributes = cons(AttributeRecord(key, ns_uri), attributes);
+ }
+ } else {
+ // if there are non-namespaced elements, we can't globally
+ // use a default namespace
+ elide_prefix = GQuark(0);
+ }
+ }
+
+ return sp_repr_write_stream_element(repr, out, 0, add_whitespace, elide_prefix, attributes);
+}
+
+void
+sp_repr_write_stream (Node *repr, Writer &out, gint indent_level,
+ gboolean add_whitespace, Glib::QueryQuark elide_prefix)
+{
+ if (repr->type() == Inkscape::XML::TEXT_NODE) {
+ repr_quote_write (out, repr->content());
+ } else if (repr->type() == Inkscape::XML::COMMENT_NODE) {
+ out.printf( "<!--%s-->", repr->content() );
+ } else if (repr->type() == Inkscape::XML::ELEMENT_NODE) {
+ sp_repr_write_stream_element(repr, out, indent_level, add_whitespace, elide_prefix, repr->attributeList());
+ } else {
+ g_assert_not_reached();
+ }
+}
+
+void
+sp_repr_write_stream_element (Node * repr, Writer & out, gint indent_level,
+ gboolean add_whitespace,
+ Glib::QueryQuark elide_prefix,
+ List<AttributeRecord const> attributes)
+{
+ Node *child;
+ gboolean loose;
+ gint i;
+
+ g_return_if_fail (repr != NULL);
+
+ if ( indent_level > 16 )
+ indent_level = 16;
+
+ if (add_whitespace) {
+ for ( i = 0 ; i < indent_level ; i++ ) {
+ out.writeString( " " );
+ }
+ }
+
+ GQuark code = repr->code();
+ gchar const *element_name;
+ if ( elide_prefix == qname_prefix(code) ) {
+ element_name = qname_local_name(code);
+ } else {
+ element_name = g_quark_to_string(code);
+ }
+ out.printf( "<%s", element_name );
+
+ // if this is a <text> element, suppress formatting whitespace
+ // for its content and children:
+ gchar const *xml_space_attr = repr->attribute("xml:space");
+ if (xml_space_attr != NULL && !strcmp(xml_space_attr, "preserve")) {
+ add_whitespace = FALSE;
+ }
+
+ for ( List<AttributeRecord const> iter = attributes ;
+ iter ; ++iter )
+ {
+ out.writeString("\n");
+ for ( i = 0 ; i < indent_level + 1 ; i++ ) {
+ out.writeString(" ");
+ }
+ out.printf(" %s=\"", g_quark_to_string(iter->key));
+ repr_quote_write(out, iter->value);
+ out.writeChar('"');
+ }
+
+ loose = TRUE;
+ for (child = repr->firstChild() ; child != NULL; child = child->next()) {
+ if (child->type() == Inkscape::XML::TEXT_NODE) {
+ loose = FALSE;
+ break;
+ }
+ }
+ if (repr->firstChild()) {
+ out.writeString( ">" );
+ if (loose && add_whitespace) {
+ out.writeString( "\n" );
+ }
+ for (child = repr->firstChild(); child != NULL; child = child->next()) {
+ sp_repr_write_stream (child, out, (loose) ? (indent_level + 1) : 0, add_whitespace, elide_prefix);
+ }
+
+ if (loose && add_whitespace) {
+ for (i = 0; i < indent_level; i++) {
+ out.writeString( " " );
+ }
+ }
+ out.printf( "</%s>", element_name );
+ } else {
+ out.writeString( " />" );
+ }
+
+ // text elements cannot nest, so we can output newline
+ // after closing text
+
+ if (add_whitespace || !strcmp (repr->name(), "svg:text")) {
+ out.writeString( "\n" );
+ }
+}
+
+
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
+ indent-tabs-mode:nil
+ fill-column:99
+ End:
+*/
+// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :
diff --git a/src/xml/repr-sorting.cpp b/src/xml/repr-sorting.cpp
new file mode 100644
index 000000000..123df33ee
--- /dev/null
+++ b/src/xml/repr-sorting.cpp
@@ -0,0 +1,53 @@
+
+#include "algorithms/longest-common-suffix.h"
+#include "xml/repr.h"
+#include "xml/node-iterators.h"
+
+static bool
+same_repr(Inkscape::XML::Node &a, Inkscape::XML::Node &b)
+{
+ /* todo: I'm not certain that it's legal to take the address of a reference. Check the exact wording of the spec on this matter. */
+ return &a == &b;
+}
+
+Inkscape::XML::Node *
+LCA(Inkscape::XML::Node *a, Inkscape::XML::Node *b)
+{
+ using Inkscape::Algorithms::longest_common_suffix;
+ Inkscape::XML::Node *ancestor = longest_common_suffix<Inkscape::XML::NodeParentIterator>(
+ a, b, NULL, &same_repr
+ );
+ if ( ancestor && ancestor->type() != Inkscape::XML::DOCUMENT_NODE ) {
+ return ancestor;
+ } else {
+ return NULL;
+ }
+}
+
+/**
+ * Returns a child of \a ancestor such that ret is itself an ancestor of \a descendent.
+ *
+ * The current version returns NULL if ancestor or descendent is NULL, though future versions may
+ * call g_log. Please update this comment if you rely on the current behaviour.
+ */
+Inkscape::XML::Node *
+AncetreFils(Inkscape::XML::Node *descendent, Inkscape::XML::Node *ancestor)
+{
+ if (descendent == NULL || ancestor == NULL)
+ return NULL;
+ if (sp_repr_parent(descendent) == ancestor)
+ return descendent;
+ return AncetreFils(sp_repr_parent(descendent), ancestor);
+}
+
+
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
+ indent-tabs-mode:nil
+ fill-column:99
+ End:
+*/
+// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :
diff --git a/src/xml/repr-sorting.h b/src/xml/repr-sorting.h
new file mode 100644
index 000000000..534caa2d1
--- /dev/null
+++ b/src/xml/repr-sorting.h
@@ -0,0 +1,11 @@
+/** \file Some functions relevant sorting reprs by position within document. */
+
+namespace Inkscape {
+namespace XML {
+class Node;
+}
+}
+
+
+Inkscape::XML::Node *LCA(Inkscape::XML::Node *a, Inkscape::XML::Node *b);
+Inkscape::XML::Node *AncetreFils(Inkscape::XML::Node *descendent, Inkscape::XML::Node *ancestor);
diff --git a/src/xml/repr-util.cpp b/src/xml/repr-util.cpp
new file mode 100644
index 000000000..75ae692af
--- /dev/null
+++ b/src/xml/repr-util.cpp
@@ -0,0 +1,553 @@
+#define __SP_REPR_UTIL_C__
+
+/** \file
+ * Miscellaneous helpers for reprs.
+ */
+
+/*
+ * Authors:
+ * Lauris Kaplinski <lauris@ximian.com>
+ *
+ * Copyright (C) 1999-2000 Lauris Kaplinski
+ * Copyright (C) 2000-2001 Ximian, Inc.
+ * g++ port Copyright (C) 2003 Nathan Hurst
+ *
+ * Licensed under GNU GPL
+ */
+
+#include "config.h"
+
+#include <math.h>
+
+#if HAVE_STRING_H
+# include <string.h>
+#endif
+
+#if HAVE_STDLIB_H
+# include <stdlib.h>
+#endif
+
+
+#include <glib.h>
+
+#include "svg/stringstream.h"
+#include "svg/css-ostringstream.h"
+
+#include "xml/repr.h"
+#include "xml/repr-sorting.h"
+
+struct SPXMLNs {
+ SPXMLNs *next;
+ unsigned int uri, prefix;
+};
+
+/*#####################
+# DEFINITIONS
+#####################*/
+
+#ifndef FALSE
+# define FALSE 0
+#endif
+
+#ifndef TRUE
+# define TRUE (!FALSE)
+#endif
+
+#ifndef MAX
+# define MAX(a,b) (((a) < (b)) ? (b) : (a))
+#endif
+
+/*#####################
+# FORWARD DECLARATIONS
+#####################*/
+
+static void sp_xml_ns_register_defaults();
+static char *sp_xml_ns_auto_prefix(char const *uri);
+
+/*#####################
+# UTILITY
+#####################*/
+
+/**
+ * Locale-independent double to string conversion
+ */
+unsigned int
+sp_xml_dtoa(gchar *buf, double val, unsigned int tprec, unsigned int fprec, unsigned int padf)
+{
+ double dival, fval, epsilon;
+ int idigits, ival, i;
+ i = 0;
+ if (val < 0.0) {
+ buf[i++] = '-';
+ val = -val;
+ }
+ /* Determine number of integral digits */
+ if (val >= 1.0) {
+ idigits = (int) floor(log10(val));
+ } else {
+ idigits = 0;
+ }
+ /* Determine the actual number of fractional digits */
+ fprec = MAX(fprec, tprec - idigits);
+ /* Find epsilon */
+ epsilon = 0.5 * pow(10.0, - (double) fprec);
+ /* Round value */
+ val += epsilon;
+ /* Extract integral and fractional parts */
+ dival = floor(val);
+ ival = (int) dival;
+ fval = val - dival;
+ /* Write integra */
+ if (ival > 0) {
+ char c[32];
+ int j;
+ j = 0;
+ while (ival > 0) {
+ c[32 - (++j)] = '0' + (ival % 10);
+ ival /= 10;
+ }
+ memcpy(buf + i, &c[32 - j], j);
+ i += j;
+ tprec -= j;
+ } else {
+ buf[i++] = '0';
+ tprec -= 1;
+ }
+ if ((fprec > 0) && (padf || (fval > epsilon))) {
+ buf[i++] = '.';
+ while ((fprec > 0) && (padf || (fval > epsilon))) {
+ fval *= 10.0;
+ dival = floor(fval);
+ fval -= dival;
+ buf[i++] = '0' + (int) dival;
+ fprec -= 1;
+ }
+
+ }
+ buf[i] = 0;
+ return i;
+}
+
+
+
+
+
+/*#####################
+# MAIN
+#####################*/
+
+/**
+ * SPXMLNs
+ */
+
+static SPXMLNs *namespaces=NULL;
+
+/*
+ * There are the prefixes to use for the XML namespaces defined
+ * in repr.h
+ */
+static void
+sp_xml_ns_register_defaults()
+{
+ static SPXMLNs defaults[7];
+
+ defaults[0].uri = g_quark_from_static_string(SP_SODIPODI_NS_URI);
+ defaults[0].prefix = g_quark_from_static_string("sodipodi");
+ defaults[0].next = &defaults[1];
+
+ defaults[1].uri = g_quark_from_static_string(SP_XLINK_NS_URI);
+ defaults[1].prefix = g_quark_from_static_string("xlink");
+ defaults[1].next = &defaults[2];
+
+ defaults[2].uri = g_quark_from_static_string(SP_SVG_NS_URI);
+ defaults[2].prefix = g_quark_from_static_string("svg");
+ defaults[2].next = &defaults[3];
+
+ defaults[3].uri = g_quark_from_static_string(SP_INKSCAPE_NS_URI);
+ defaults[3].prefix = g_quark_from_static_string("inkscape");
+ defaults[3].next = &defaults[4];
+
+ defaults[4].uri = g_quark_from_static_string(SP_RDF_NS_URI);
+ defaults[4].prefix = g_quark_from_static_string("rdf");
+ defaults[4].next = &defaults[5];
+
+ defaults[5].uri = g_quark_from_static_string(SP_CC_NS_URI);
+ defaults[5].prefix = g_quark_from_static_string("cc");
+ defaults[5].next = &defaults[6];
+
+ defaults[6].uri = g_quark_from_static_string(SP_DC_NS_URI);
+ defaults[6].prefix = g_quark_from_static_string("dc");
+ defaults[6].next = NULL;
+
+ namespaces = &defaults[0];
+}
+
+char *
+sp_xml_ns_auto_prefix(char const *uri)
+{
+ char const *start, *end;
+ char *new_prefix;
+ start = uri;
+ while ((end = strpbrk(start, ":/"))) {
+ start = end + 1;
+ }
+ end = start + strspn(start, "abcdefghijklmnopqrstuvwxyz");
+ if (end == start) {
+ start = "ns";
+ end = start + 2;
+ }
+ new_prefix = g_strndup(start, end - start);
+ if (sp_xml_ns_prefix_uri(new_prefix)) {
+ char *temp;
+ int counter=0;
+ do {
+ temp = g_strdup_printf("%s%d", new_prefix, counter++);
+ } while (sp_xml_ns_prefix_uri(temp));
+ g_free(new_prefix);
+ new_prefix = temp;
+ }
+ return new_prefix;
+}
+
+gchar const *
+sp_xml_ns_uri_prefix(gchar const *uri, gchar const *suggested)
+{
+ SPXMLNs *iter;
+ char const *prefix;
+
+ if (!uri) return NULL;
+
+ if (!namespaces) {
+ sp_xml_ns_register_defaults();
+ }
+
+ GQuark const key = g_quark_from_string(uri);
+ prefix = NULL;
+ for ( iter = namespaces ; iter ; iter = iter->next ) {
+ if ( iter->uri == key ) {
+ prefix = g_quark_to_string(iter->prefix);
+ break;
+ }
+ }
+ if (!prefix) {
+ char const *new_prefix;
+ SPXMLNs *ns;
+ if (suggested) {
+ new_prefix = suggested;
+ } else {
+ new_prefix = sp_xml_ns_auto_prefix(uri);
+ }
+ ns = g_new(SPXMLNs, 1);
+ if (ns) {
+ ns->uri = g_quark_from_string(uri);
+ ns->prefix = g_quark_from_string(new_prefix);
+ ns->next = namespaces;
+ namespaces = ns;
+ prefix = g_quark_to_string(ns->prefix);
+ }
+ if (!suggested) {
+ g_free((char *)new_prefix);
+ }
+ }
+ return prefix;
+}
+
+gchar const *
+sp_xml_ns_prefix_uri(gchar const *prefix)
+{
+ SPXMLNs *iter;
+ char const *uri;
+
+ if (!prefix) return NULL;
+
+ if (!namespaces) {
+ sp_xml_ns_register_defaults();
+ }
+
+ GQuark const key = g_quark_from_string(prefix);
+ uri = NULL;
+ for ( iter = namespaces ; iter ; iter = iter->next ) {
+ if ( iter->prefix == key ) {
+ uri = g_quark_to_string(iter->uri);
+ break;
+ }
+ }
+ return uri;
+}
+
+double sp_repr_get_double_attribute(Inkscape::XML::Node *repr, char const *key, double def)
+{
+ char *result;
+
+ g_return_val_if_fail(repr != NULL, def);
+ g_return_val_if_fail(key != NULL, def);
+
+ result = (char *) repr->attribute(key);
+
+ if (result == NULL) return def;
+
+ return g_ascii_strtod(result, NULL);
+}
+
+int sp_repr_get_int_attribute(Inkscape::XML::Node *repr, char const *key, int def)
+{
+ char *result;
+
+ g_return_val_if_fail(repr != NULL, def);
+ g_return_val_if_fail(key != NULL, def);
+
+ result = (char *) repr->attribute(key);
+
+ if (result == NULL) return def;
+
+ return atoi(result);
+}
+
+/**
+ * Works for different-parent objects, so long as they have a common ancestor. Return value:
+ * 0 positions are equivalent
+ * 1 first object's position is greater than the second
+ * -1 first object's position is less than the second
+ */
+int
+sp_repr_compare_position(Inkscape::XML::Node *first, Inkscape::XML::Node *second)
+{
+ int p1, p2;
+ if (sp_repr_parent(first) == sp_repr_parent(second)) {
+ /* Basic case - first and second have same parent */
+ p1 = first->position();
+ p2 = second->position();
+ } else {
+ /* Special case - the two objects have different parents. They
+ could be in different groups or on different layers for
+ instance. */
+
+ // Find the lowest common ancestor(LCA)
+ Inkscape::XML::Node *ancestor = LCA(first, second);
+ g_assert(ancestor != NULL);
+
+ if (ancestor == first) {
+ return 1;
+ } else if (ancestor == second) {
+ return -1;
+ } else {
+ Inkscape::XML::Node const *to_first = AncetreFils(first, ancestor);
+ Inkscape::XML::Node const *to_second = AncetreFils(second, ancestor);
+ g_assert(sp_repr_parent(to_second) == sp_repr_parent(to_first));
+ p1 = to_first->position();
+ p2 = to_second->position();
+ }
+ }
+
+ if (p1 > p2) return 1;
+ if (p1 < p2) return -1;
+ return 0;
+
+ /* effic: Assuming that the parent--child relationship is consistent
+ (i.e. that the parent really does contain first and second among
+ its list of children), it should be equivalent to walk along the
+ children and see which we encounter first (returning 0 iff first
+ == second).
+
+ Given that this function is used solely for sorting, we can use a
+ similar approach to do the sort: gather the things to be sorted,
+ into an STL vector (to allow random access and faster
+ traversals). Do a single pass of the parent's children; for each
+ child, do a pass on whatever items in the vector we haven't yet
+ encountered. If the child is found, then swap it to the
+ beginning of the yet-unencountered elements of the vector.
+ Continue until no more than one remains unencountered. --
+ pjrm */
+}
+
+/**
+ * lookup child by \a key, \a value.
+ */
+Inkscape::XML::Node *
+sp_repr_lookup_child(Inkscape::XML::Node *repr,
+ gchar const *key,
+ gchar const *value)
+{
+ g_return_val_if_fail(repr != NULL, NULL);
+ for ( Inkscape::XML::Node *child = repr->firstChild() ; child ; child = child->next() ) {
+ gchar const *child_value = child->attribute(key);
+ if ( child_value == value ||
+ value && child_value && !strcmp(child_value, value) )
+ {
+ return child;
+ }
+ }
+ return NULL;
+}
+
+/**
+ * \brief Recursively find the Inkscape::XML::Node matching the given XML name.
+ * \return A pointer to the matching Inkscape::XML::Node
+ * \param repr The Inkscape::XML::Node to start from
+ * \param name The desired XML name
+ *
+ */
+Inkscape::XML::Node *
+sp_repr_lookup_name( Inkscape::XML::Node *repr, gchar const *name, gint maxdepth )
+{
+ g_return_val_if_fail(repr != NULL, NULL);
+ g_return_val_if_fail(name != NULL, NULL);
+
+ GQuark const quark = g_quark_from_string(name);
+
+ if ( (GQuark)repr->code() == quark ) return repr;
+ if ( maxdepth == 0 ) return NULL;
+
+ // maxdepth == -1 means unlimited
+ if ( maxdepth == -1 ) maxdepth = 0;
+
+ Inkscape::XML::Node *found = NULL;
+ for (Inkscape::XML::Node *child = repr->firstChild() ; child && !found; child = child->next() ) {
+ found = sp_repr_lookup_name( child, name, maxdepth-1 );
+ }
+
+ return found;
+}
+
+/**
+ * Parses the boolean value of an attribute "key" in repr and sets val accordingly, or to FALSE if
+ * the attr is not set.
+ *
+ * \return TRUE if the attr was set, FALSE otherwise.
+ */
+unsigned int
+sp_repr_get_boolean(Inkscape::XML::Node *repr, gchar const *key, unsigned int *val)
+{
+ gchar const *v;
+
+ g_return_val_if_fail(repr != NULL, FALSE);
+ g_return_val_if_fail(key != NULL, FALSE);
+ g_return_val_if_fail(val != NULL, FALSE);
+
+ v = repr->attribute(key);
+
+ if (v != NULL) {
+ if (!g_strcasecmp(v, "true") ||
+ !g_strcasecmp(v, "yes" ) ||
+ !g_strcasecmp(v, "y" ) ||
+ (atoi(v) != 0)) {
+ *val = TRUE;
+ } else {
+ *val = FALSE;
+ }
+ return TRUE;
+ } else {
+ *val = FALSE;
+ return FALSE;
+ }
+}
+
+unsigned int
+sp_repr_get_int(Inkscape::XML::Node *repr, gchar const *key, int *val)
+{
+ gchar const *v;
+
+ g_return_val_if_fail(repr != NULL, FALSE);
+ g_return_val_if_fail(key != NULL, FALSE);
+ g_return_val_if_fail(val != NULL, FALSE);
+
+ v = repr->attribute(key);
+
+ if (v != NULL) {
+ *val = atoi(v);
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+unsigned int
+sp_repr_get_double(Inkscape::XML::Node *repr, gchar const *key, double *val)
+{
+ gchar const *v;
+
+ g_return_val_if_fail(repr != NULL, FALSE);
+ g_return_val_if_fail(key != NULL, FALSE);
+ g_return_val_if_fail(val != NULL, FALSE);
+
+ v = repr->attribute(key);
+
+ if (v != NULL) {
+ *val = g_ascii_strtod(v, NULL);
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+unsigned int
+sp_repr_set_boolean(Inkscape::XML::Node *repr, gchar const *key, unsigned int val)
+{
+ g_return_val_if_fail(repr != NULL, FALSE);
+ g_return_val_if_fail(key != NULL, FALSE);
+
+ repr->setAttribute(key, (val) ? "true" : "false");
+ return true;
+}
+
+unsigned int
+sp_repr_set_int(Inkscape::XML::Node *repr, gchar const *key, int val)
+{
+ gchar c[32];
+
+ g_return_val_if_fail(repr != NULL, FALSE);
+ g_return_val_if_fail(key != NULL, FALSE);
+
+ g_snprintf(c, 32, "%d", val);
+
+ repr->setAttribute(key, c);
+ return true;
+}
+
+/**
+ * Set a property attribute to \a val [slightly rounded], in the format
+ * required for CSS properties: in particular, it never uses exponent
+ * notation.
+ */
+unsigned int
+sp_repr_set_css_double(Inkscape::XML::Node *repr, gchar const *key, double val)
+{
+ g_return_val_if_fail(repr != NULL, FALSE);
+ g_return_val_if_fail(key != NULL, FALSE);
+
+ Inkscape::CSSOStringStream os;
+ os << val;
+
+ repr->setAttribute(key, os.str().c_str());
+ return true;
+}
+
+/**
+ * For attributes where an exponent is allowed.
+ *
+ * Not suitable for property attributes (fill-opacity, font-size etc.).
+ */
+unsigned int
+sp_repr_set_svg_double(Inkscape::XML::Node *repr, gchar const *key, double val)
+{
+ g_return_val_if_fail(repr != NULL, FALSE);
+ g_return_val_if_fail(key != NULL, FALSE);
+
+ Inkscape::SVGOStringStream os;
+ os << val;
+
+ repr->setAttribute(key, os.str().c_str());
+ return true;
+}
+
+
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
+ indent-tabs-mode:nil
+ fill-column:99
+ End:
+*/
+// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :
diff --git a/src/xml/repr.cpp b/src/xml/repr.cpp
new file mode 100644
index 000000000..2cd770a52
--- /dev/null
+++ b/src/xml/repr.cpp
@@ -0,0 +1,107 @@
+#define __SP_REPR_C__
+
+/** \file
+ * A few non-inline functions of the C facade to Inkscape::XML::Node.
+ */
+
+/*
+ * Authors:
+ * Lauris Kaplinski <lauris@kaplinski.com>
+ * MenTaLguY <mental@rydia.net>
+ *
+ * Copyright (C) 1999-2003 authors
+ * Copyright (C) 2000-2002 Ximian, Inc.
+ * g++ port Copyright (C) 2003 Nathan Hurst
+ *
+ * Released under GNU GPL, read the file 'COPYING' for more information
+ */
+
+#define noREPR_VERBOSE
+
+#ifdef HAVE_CONFIG_H
+# include "config.h"
+#endif
+
+#include "xml/repr.h"
+#include "xml/text-node.h"
+#include "xml/element-node.h"
+#include "xml/comment-node.h"
+#include "xml/simple-document.h"
+
+using Inkscape::Util::SharedCStringPtr;
+
+/// Returns new node.
+Inkscape::XML::Node *
+sp_repr_new(gchar const *name)
+{
+ g_return_val_if_fail(name != NULL, NULL);
+ g_return_val_if_fail(*name != '\0', NULL);
+
+ return new Inkscape::XML::ElementNode(g_quark_from_string(name));
+}
+
+/// Returns new textnode with content. See Inkscape::XML::TextNode.
+Inkscape::XML::Node *
+sp_repr_new_text(gchar const *content)
+{
+ g_return_val_if_fail(content != NULL, NULL);
+ return new Inkscape::XML::TextNode(SharedCStringPtr::copy(content));
+}
+
+/// Returns new commentnode with comment. See Inkscape::XML::CommentNode.
+Inkscape::XML::Node *
+sp_repr_new_comment(gchar const *comment)
+{
+ g_return_val_if_fail(comment != NULL, NULL);
+ return new Inkscape::XML::CommentNode(SharedCStringPtr::copy(comment));
+}
+
+/// Returns new document having as first child a node named rootname.
+Inkscape::XML::Document *
+sp_repr_document_new(char const *rootname)
+{
+ Inkscape::XML::Document *doc = new Inkscape::XML::SimpleDocument(g_quark_from_static_string("xml"));
+ if (!strcmp(rootname, "svg:svg")) {
+ doc->setAttribute("version", "1.0");
+ doc->setAttribute("standalone", "no");
+ Inkscape::XML::Node *comment = sp_repr_new_comment(" Created with Inkscape (http://www.inkscape.org/) ");
+ doc->appendChild(comment);
+ Inkscape::GC::release(comment);
+ }
+
+ Inkscape::XML::Node *root = sp_repr_new(rootname);
+ doc->appendChild(root);
+ Inkscape::GC::release(root);
+
+ return doc;
+}
+
+/// Returns new document having reprs as first child.
+Inkscape::XML::Document *
+sp_repr_document_new_list(GSList *reprs)
+{
+ g_assert(reprs != NULL);
+
+ Inkscape::XML::Document *doc = sp_repr_document_new("void");
+ doc->removeChild(doc->firstChild());
+
+ for ( GSList *iter = reprs ; iter ; iter = iter->next ) {
+ Inkscape::XML::Node *repr = (Inkscape::XML::Node *) iter->data;
+ doc->appendChild(repr);
+ }
+
+ g_assert(sp_repr_document_root(doc) != NULL);
+
+ return doc;
+}
+
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
+ indent-tabs-mode:nil
+ fill-column:99
+ End:
+*/
+// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 :
diff --git a/src/xml/repr.h b/src/xml/repr.h
new file mode 100644
index 000000000..b260082d4
--- /dev/null
+++ b/src/xml/repr.h
@@ -0,0 +1,261 @@
+#ifndef __SP_REPR_H__
+#define __SP_REPR_H__
+
+/** \file
+ * C facade to Inkscape::XML::Node.
+ *
+ * Authors:
+ * Lauris Kaplinski <lauris@kaplinski.com>
+ *
+ * Copyright (C) 1999-2002 authors
+ * Copyright (C) 2000-2002 Ximian, Inc.
+ *
+ * Released under GNU GPL, read the file 'COPYING' for more information
+ */
+
+#include <stdio.h>
+#include <glib/gtypes.h>
+#include "gc-anchored.h"
+
+#include "xml/node.h"
+#include "xml/document.h"
+#include "xml/sp-css-attr.h"
+
+#define SP_SODIPODI_NS_URI "http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+#define SP_INKSCAPE_NS_URI "http://www.inkscape.org/namespaces/inkscape"
+#define SP_XLINK_NS_URI "http://www.w3.org/1999/xlink"
+#define SP_SVG_NS_URI "http://www.w3.org/2000/svg"
+#define SP_RDF_NS_URI "http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+#define SP_CC_NS_URI "http://web.resource.org/cc/"
+#define SP_DC_NS_URI "http://purl.org/dc/elements/1.1/"
+
+/**
+ * \note NB! Unless explicitly stated all methods are noref/nostrcpy
+ */
+
+/** \todo
+ * Though Inkscape::XML::Node provides "signals" for notification when
+ * individual nodes change, there is no mechanism to receive notification
+ * for overall document changes.
+ * However, with the addition of the transactions code, it would not be
+ * very hard to implement if you wanted it.
+ *
+ * \class Inkscape::XML::Node
+ * \note
+ * Inkscape::XML::Node itself doesn't use GObject signals at present --
+ * Inkscape::XML::Nodes maintain lists of Inkscape::XML::NodeEventVectors
+ * (added via sp_repr_add_listener), which are used to specify callbacks
+ * when something changes.
+ *
+ * Here are the current callbacks in an event vector (they may be NULL):
+ *
+ * void (* child_added)(Inkscape::XML::Node *repr, Inkscape::XML::Node *child,
+ * Inkscape::XML::Node *ref, void *data); Called once a child has been added.
+ *
+ * void (* child_removed)(Inkscape::XML::Node *repr,
+ * Inkscape::XML::Node *child, Inkscape::XML::Node *ref, void *data);
+ * Called after a child is removed; ref is the child that used to precede
+ * the removed child.
+ *
+ * void (* attr_changed)(Inkscape::XML::Node *repr, gchar const *key,
+ * gchar const *oldval, gchar const *newval, void *data);
+ * Called after an attribute has been changed.
+ *
+ * void (* content_changed)(Inkscape::XML::Node *repr, gchar const *oldcontent,
+ * gchar const *newcontent, void *data);
+ * Called after an element's content has been changed.
+ *
+ * void (* order_changed)(Inkscape::XML::Node *repr, Inkscape::XML::Node *child,
+ * Inkscape::XML::Node *oldref, Inkscape::XML::Node *newref, void *data);
+ * Called once the child has been moved to its new position in the child order.
+ *
+ * <b> Inkscape::XML::Node mini-FAQ </b>
+ *
+ * Since I'm not very familiar with this section of code but I need to use
+ * it heavily for the RDF work, I'm going to answer various questions I run
+ * into with my best-guess answers so others can follow along too.
+ *
+ * \arg
+ * Q: How do I find the root Inkscape::XML::Node? <br>
+ * A: If you have an SPDocument, use doc->rroot. For example:
+ *
+ * \code SP_ACTIVE_DOCUMENT->rroot \endcode <br>
+ *
+ * (but it's better to arrange for your caller to pass in the relevent
+ * document rather than assuming it's necessarily the active one and
+ * using SP_ACTIVE_DOCUMENT)
+ *
+ * \arg
+ * Q: How do I find an Inkscape::XML::Node by unique key/value? <br>
+ * A: Use sp_repr_lookup_child
+ *
+ * \arg
+ * Q: How do I find an Inkscape::XML::Node by unique namespace name? <br>
+ * A: Use sp_repr_lookup_name
+ *
+ * \arg
+ * Q: How do I make an Inkscape::XML::Node namespace prefix constant in
+ * the application? <br>
+ * A: Add the XML namespace URL as a #define to repr.h at the top with the
+ * other SP_<NAMESPACE>_NS_URI #define's, and then in repr-util.cpp,
+ * in sp_xml_ns_register_defaults, bump "defaults" up in size one, and
+ * add another section. Don't forget to increment the array offset and
+ * keep ".next" pointed to the next (if any) array entry.
+ *
+ * \arg
+ * Q: How do I create a new Inkscape::XML::Node? <br>
+ * A: Use "sp_repr_new*". Then attach it to a parent somewhere with
+ * parent->appendChild(child), and then use Inkscape::GC::release(child) to
+ * let go of it (the parent will hold it in memory).
+ *
+ * \arg
+ * Q: How do I destroy an Inkscape::XML::Node?
+ * A: Just call "sp_repr_unparent" on it and release any references
+ * you may be retaining to it. Any attached SPObjects will
+ * clean themselves up automatically, as will any children.
+ *
+ * \arg
+ * Q: What about listeners? <br>
+ * A: I have no idea yet...
+ *
+ * \arg
+ * Q: How do I add a namespace to a newly created document? <br>
+ * A: The current hack is in document.cpp:sp_document_create
+ *
+ * Kees Cook 2004-07-01, updated MenTaLguY 2005-01-25
+ */
+
+/* SPXMLNs */
+char const *sp_xml_ns_uri_prefix(gchar const *uri, gchar const *suggested);
+char const *sp_xml_ns_prefix_uri(gchar const *prefix);
+
+
+Inkscape::XML::Node *sp_repr_new(gchar const *name);
+Inkscape::XML::Node *sp_repr_new_text(gchar const *content);
+Inkscape::XML::Node *sp_repr_new_comment(gchar const *comment);
+
+/*inline Inkscape::XML::Node *sp_repr_duplicate(Inkscape::XML::Node const *repr) {
+ return repr->duplicate();
+}*/
+
+Inkscape::XML::Document *sp_repr_document_new(gchar const *rootname);
+
+/// Returns root node of document.
+inline Inkscape::XML::Node *sp_repr_document_root(Inkscape::XML::Document const *doc) {
+ return const_cast<Inkscape::XML::Node *>(doc->root());
+}
+
+/// Returns the node's document.
+inline Inkscape::XML::Document *sp_repr_document(Inkscape::XML::Node const *repr) {
+ return const_cast<Inkscape::XML::Document *>(repr->document());
+}
+
+/* Contents */
+/// Sets the node's \a key attribute to \a value.
+inline unsigned sp_repr_set_attr(Inkscape::XML::Node *repr, gchar const *key, gchar const *value,
+ bool is_interactive = false) {
+ repr->setAttribute(key, value, is_interactive);
+ return true;
+}
+
+/* Tree */
+/// Returns the node's parent.
+inline Inkscape::XML::Node *sp_repr_parent(Inkscape::XML::Node const *repr) {
+ return const_cast<Inkscape::XML::Node *>(repr->parent());
+}
+
+/// Returns first child of node, resets iterator.
+inline Inkscape::XML::Node *sp_repr_children(Inkscape::XML::Node *repr) {
+ return ( repr ? repr->firstChild() : NULL );
+}
+
+/// Returns next child of node or NULL.
+inline Inkscape::XML::Node *sp_repr_next(Inkscape::XML::Node *repr) {
+ return ( repr ? repr->next() : NULL );
+}
+
+/* IO */
+
+Inkscape::XML::Document *sp_repr_read_file(gchar const *filename, gchar const *default_ns);
+Inkscape::XML::Document *sp_repr_read_mem(gchar const *buffer, int length, gchar const *default_ns);
+void sp_repr_save_stream(Inkscape::XML::Document *doc, FILE *to_file, gchar const *default_ns=NULL, bool compress = false);
+gboolean sp_repr_save_file(Inkscape::XML::Document *doc, gchar const *filename, gchar const *default_ns=NULL);
+
+void sp_repr_print(Inkscape::XML::Node *repr);
+
+/* CSS stuff */
+
+SPCSSAttr *sp_repr_css_attr_new(void);
+void sp_repr_css_attr_unref(SPCSSAttr *css);
+SPCSSAttr *sp_repr_css_attr(Inkscape::XML::Node *repr, gchar const *attr);
+SPCSSAttr *sp_repr_css_attr_inherited(Inkscape::XML::Node *repr, gchar const *attr);
+
+gchar const *sp_repr_css_property(SPCSSAttr *css, gchar const *name, gchar const *defval);
+void sp_repr_css_set_property(SPCSSAttr *css, gchar const *name, gchar const *value);
+void sp_repr_css_unset_property(SPCSSAttr *css, gchar const *name);
+bool sp_repr_css_property_is_unset(SPCSSAttr *css, gchar const *name);
+double sp_repr_css_double_property(SPCSSAttr *css, gchar const *name, double defval);
+
+gchar *sp_repr_css_write_string(SPCSSAttr *css);
+void sp_repr_css_set(Inkscape::XML::Node *repr, SPCSSAttr *css, gchar const *key);
+void sp_repr_css_merge(SPCSSAttr *dst, SPCSSAttr *src);
+void sp_repr_css_attr_add_from_string(SPCSSAttr *css, const gchar *data);
+void sp_repr_css_change(Inkscape::XML::Node *repr, SPCSSAttr *css, gchar const *key);
+void sp_repr_css_change_recursive(Inkscape::XML::Node *repr, SPCSSAttr *css, gchar const *key);
+
+void sp_repr_css_print(SPCSSAttr *css);
+
+/* Utility finctions */
+/// Remove \a repr from children of its parent node.
+inline void sp_repr_unparent(Inkscape::XML::Node *repr) {
+ Inkscape::XML::Node *parent=repr->parent();
+ if (parent) {
+ parent->removeChild(repr);
+ }
+}
+
+/* Convenience */
+unsigned sp_repr_get_boolean(Inkscape::XML::Node *repr, gchar const *key, unsigned *val);
+unsigned sp_repr_get_int(Inkscape::XML::Node *repr, gchar const *key, int *val);
+unsigned sp_repr_get_double(Inkscape::XML::Node *repr, gchar const *key, double *val);
+unsigned sp_repr_set_boolean(Inkscape::XML::Node *repr, gchar const *key, unsigned val);
+unsigned sp_repr_set_int(Inkscape::XML::Node *repr, gchar const *key, int val);
+unsigned sp_repr_set_css_double(Inkscape::XML::Node *repr, gchar const *key, double val);
+unsigned sp_repr_set_svg_double(Inkscape::XML::Node *repr, gchar const *key, double val);
+
+/// \deprecated !
+double sp_repr_get_double_attribute(Inkscape::XML::Node *repr, gchar const *key, double def);
+/// \deprecated !
+int sp_repr_get_int_attribute(Inkscape::XML::Node *repr, gchar const *key, int def);
+/* End Deprecated? */
+
+int sp_repr_compare_position(Inkscape::XML::Node *first, Inkscape::XML::Node *second);
+
+/* Searching */
+Inkscape::XML::Node *sp_repr_lookup_name(Inkscape::XML::Node *repr,
+ gchar const *name,
+ gint maxdepth = -1);
+Inkscape::XML::Node *sp_repr_lookup_child(Inkscape::XML::Node *repr,
+ gchar const *key,
+ gchar const *value);
+
+
+Inkscape::XML::Document *sp_repr_document_new_list(GSList *reprs);
+
+inline Inkscape::XML::Node *sp_repr_document_first_child(Inkscape::XML::Document const *doc) {
+ return const_cast<Inkscape::XML::Node *>(doc->firstChild());
+}
+
+#endif
+
+
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
+ indent-tabs-mode:nil
+ fill-column:99
+ End:
+*/
+// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :
diff --git a/src/xml/session.h b/src/xml/session.h
new file mode 100644
index 000000000..967373a23
--- /dev/null
+++ b/src/xml/session.h
@@ -0,0 +1,56 @@
+/*
+ * Inkscape::XML::Session - context for transactions
+ *
+ * Copyright 2005 MenTaLguY <mental@rydia.net>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * See the file COPYING for details.
+ *
+ */
+
+#ifndef SEEN_INKSCAPE_XML_SESSION_H
+#define SEEN_INKSCAPE_XML_SESSION_H
+
+namespace Inkscape {
+
+namespace XML {
+
+class Event;
+class Node;
+
+class Session {
+public:
+ Session() {}
+ virtual ~Session() {}
+
+ virtual bool inTransaction()=0;
+
+ virtual void beginTransaction()=0;
+ virtual void rollback()=0;
+ virtual void commit()=0;
+ virtual Inkscape::XML::Event *commitUndoable()=0;
+
+ virtual Node *createElementNode(char const *name)=0;
+ virtual Node *createTextNode(char const *content)=0;
+ virtual Node *createCommentNode(char const *content)=0;
+};
+
+}
+
+}
+
+#endif
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
+ indent-tabs-mode:nil
+ fill-column:99
+ End:
+*/
+// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :
diff --git a/src/xml/simple-document.cpp b/src/xml/simple-document.cpp
new file mode 100644
index 000000000..3e7a17ff5
--- /dev/null
+++ b/src/xml/simple-document.cpp
@@ -0,0 +1,40 @@
+/*
+ * Inkscape::XML::SimpleDocument - generic XML document implementation
+ *
+ * Copyright 2004-2005 MenTaLguY <mental@rydia.net>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * See the file COPYING for details.
+ *
+ */
+
+#include "xml/simple-document.h"
+#include "xml/simple-session.h"
+
+namespace Inkscape {
+
+namespace XML {
+
+void SimpleDocument::_initBindings() {
+ _bindDocument(*this);
+ _bindLogger(*(new Inkscape::XML::SimpleSession()));
+}
+
+}
+
+}
+
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
+ indent-tabs-mode:nil
+ fill-column:99
+ End:
+*/
+// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :
diff --git a/src/xml/simple-document.h b/src/xml/simple-document.h
new file mode 100644
index 000000000..a2e58fe79
--- /dev/null
+++ b/src/xml/simple-document.h
@@ -0,0 +1,57 @@
+/*
+ * Inkscape::XML::SimpleDocument - generic XML document implementation
+ *
+ * Copyright 2004-2005 MenTaLguY <mental@rydia.net>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * See the file COPYING for details.
+ *
+ */
+
+#ifndef SEEN_INKSCAPE_XML_SIMPLE_DOCUMENT_H
+#define SEEN_INKSCAPE_XML_SIMPLE_DOCUMENT_H
+
+#include "xml/document.h"
+#include "xml/simple-node.h"
+
+namespace Inkscape {
+
+namespace XML {
+
+struct SimpleDocument : public SimpleNode, public Inkscape::XML::Document {
+ explicit SimpleDocument(int code) : SimpleNode(code) {
+ _initBindings();
+ }
+
+ Inkscape::XML::NodeType type() const { return Inkscape::XML::DOCUMENT_NODE; }
+
+protected:
+ SimpleDocument(SimpleDocument const &doc) : Inkscape::XML::Node(), SimpleNode(doc), Inkscape::XML::Document() {
+ _initBindings();
+ }
+
+ SimpleNode *_duplicate() const { return new SimpleDocument(*this); }
+
+private:
+ void _initBindings();
+};
+
+}
+
+}
+
+#endif
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
+ indent-tabs-mode:nil
+ fill-column:99
+ End:
+*/
+// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :
diff --git a/src/xml/simple-node.cpp b/src/xml/simple-node.cpp
new file mode 100644
index 000000000..d3e047431
--- /dev/null
+++ b/src/xml/simple-node.cpp
@@ -0,0 +1,731 @@
+/*
+ * SimpleNode - simple XML node implementation
+ *
+ * Copyright 2003-2005 MenTaLguY <mental@rydia.net>
+ * Copyright 2003 Nathan Hurst
+ * Copyright 1999-2003 Lauris Kaplinski
+ * Copyright 2000-2002 Ximian Inc.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * See the file COPYING for details.
+ *
+ */
+
+#include <glib/gstrfuncs.h>
+#include "xml/simple-node.h"
+#include "xml/node-event-vector.h"
+#include "xml/node-fns.h"
+#include "xml/repr.h"
+#include "debug/event-tracker.h"
+
+namespace Inkscape {
+
+namespace XML {
+
+namespace {
+
+Util::SharedCStringPtr stringify_node(Node const &node) {
+ gchar *string;
+ switch (node.type()) {
+ case ELEMENT_NODE: {
+ char const *id=node.attribute("id");
+ if (id) {
+ string = g_strdup_printf("element(%p)=%s(#%s)", &node, node.name(), id);
+ } else {
+ string = g_strdup_printf("element(%p)=%s", &node, node.name());
+ }
+ } break;
+ case TEXT_NODE:
+ string = g_strdup_printf("text(%p)=%s", &node, node.content());
+ break;
+ case COMMENT_NODE:
+ string = g_strdup_printf("comment(%p)=<!--%s-->", &node, node.content());
+ break;
+ case DOCUMENT_NODE:
+ string = g_strdup_printf("document(%p)", &node);
+ break;
+ default:
+ string = g_strdup_printf("unknown(%p)", &node);
+ }
+ Util::SharedCStringPtr result=Util::SharedCStringPtr::copy(string);
+ g_free(string);
+ return result;
+}
+
+Util::SharedCStringPtr stringify_unsigned(unsigned n) {
+ gchar *string = g_strdup_printf("%u", n);
+ Util::SharedCStringPtr result=Util::SharedCStringPtr::copy(string);
+ g_free(string);
+ return result;
+}
+
+}
+
+class DebugAddChild : public Debug::Event {
+public:
+ DebugAddChild(Node const &node, Node const &child, Node const *prev)
+ : _parent(stringify_node(node)),
+ _child(stringify_node(child)),
+ _position(prev ? prev->position() + 1 : 0)
+ {}
+
+ static Category category() { return XML; }
+
+ Util::SharedCStringPtr name() const {
+ return Util::SharedCStringPtr::coerce("add-child");
+ }
+ unsigned propertyCount() const { return 3; }
+ PropertyPair property(unsigned i) const {
+ switch (i) {
+ case 0:
+ return PropertyPair("parent", _parent);
+ case 1:
+ return PropertyPair("child", _child);
+ case 2:
+ return PropertyPair("position", stringify_unsigned(_position));
+ default:
+ return PropertyPair();
+ }
+ }
+private:
+ Util::SharedCStringPtr _parent;
+ Util::SharedCStringPtr _child;
+ unsigned _position;
+};
+
+class DebugRemoveChild : public Debug::Event {
+public:
+ DebugRemoveChild(Node const &node, Node const &child, Node const *prev)
+ : _parent(stringify_node(node)),
+ _child(stringify_node(child))
+ {}
+
+ static Category category() { return XML; }
+
+ Util::SharedCStringPtr name() const {
+ return Util::SharedCStringPtr::coerce("remove-child");
+ }
+ unsigned propertyCount() const { return 2; }
+ PropertyPair property(unsigned i) const {
+ switch (i) {
+ case 0:
+ return PropertyPair("parent", _parent);
+ case 1:
+ return PropertyPair("child", _child);
+ default:
+ return PropertyPair();
+ }
+ }
+private:
+ Util::SharedCStringPtr _parent;
+ Util::SharedCStringPtr _child;
+};
+
+class DebugSetChildPosition : public Debug::Event {
+public:
+ DebugSetChildPosition(Node const &node, Node const &child, Node const *old_prev, Node const *new_prev)
+ : _parent(stringify_node(node)),
+ _child(stringify_node(child))
+ {
+ unsigned old_position = ( old_prev ? old_prev->position() : 0 );
+ _position = ( new_prev ? new_prev->position() : 0 );
+ if ( _position > old_position ) {
+ --_position;
+ }
+ }
+
+ static Category category() { return XML; }
+
+ Util::SharedCStringPtr name() const {
+ return Util::SharedCStringPtr::coerce("set-child-position");
+ }
+ unsigned propertyCount() const { return 3; }
+ PropertyPair property(unsigned i) const {
+ switch (i) {
+ case 0:
+ return PropertyPair("parent", _parent);
+ case 1:
+ return PropertyPair("child", _child);
+ case 2:
+ return PropertyPair("position", stringify_unsigned(_position));
+ default:
+ return PropertyPair();
+ }
+ }
+private:
+ Util::SharedCStringPtr _parent;
+ Util::SharedCStringPtr _child;
+ unsigned _position;
+};
+
+class DebugSetContent : public Debug::Event {
+public:
+ DebugSetContent(Node const &node,
+ Util::SharedCStringPtr old_content,
+ Util::SharedCStringPtr new_content)
+ : _node(stringify_node(node)), _content(new_content) {}
+
+ static Category category() { return XML; }
+
+ Util::SharedCStringPtr name() const {
+ if (_content) {
+ return Util::SharedCStringPtr::coerce("set-content");
+ } else {
+ return Util::SharedCStringPtr::coerce("clear-content");
+ }
+ }
+ unsigned propertyCount() const {
+ if (_content) {
+ return 2;
+ } else {
+ return 1;
+ }
+ }
+ PropertyPair property(unsigned i) const {
+ switch (i) {
+ case 0:
+ return PropertyPair("node", _node);
+ case 1:
+ return PropertyPair("content", _content);
+ default:
+ return PropertyPair();
+ }
+ }
+private:
+ Util::SharedCStringPtr _node;
+ Util::SharedCStringPtr _content;
+};
+
+class DebugSetAttribute : public Debug::Event {
+public:
+ DebugSetAttribute(Node const &node, GQuark name,
+ Util::SharedCStringPtr old_value,
+ Util::SharedCStringPtr new_value)
+ : _node(stringify_node(node)),
+ _name(Util::SharedCStringPtr::coerce(g_quark_to_string(name))),
+ _value(new_value) {}
+
+ static Category category() { return XML; }
+
+ Util::SharedCStringPtr name() const {
+ if (_value) {
+ return Util::SharedCStringPtr::coerce("set-attribute");
+ } else {
+ return Util::SharedCStringPtr::coerce("clear-attribute");
+ }
+ }
+ unsigned propertyCount() const {
+ if (_value) {
+ return 3;
+ } else {
+ return 2;
+ }
+ }
+ PropertyPair property(unsigned i) const {
+ switch (i) {
+ case 0:
+ return PropertyPair("node", _node);
+ case 1:
+ return PropertyPair("name", _name);
+ case 2:
+ return PropertyPair("value", _value);
+ default:
+ return PropertyPair();
+ }
+ }
+
+private:
+ Util::SharedCStringPtr _node;
+ Util::SharedCStringPtr _name;
+ Util::SharedCStringPtr _value;
+};
+
+using Inkscape::Util::SharedCStringPtr;
+using Inkscape::Util::List;
+using Inkscape::Util::MutableList;
+using Inkscape::Util::cons;
+using Inkscape::Util::rest;
+using Inkscape::Util::set_rest;
+
+SimpleNode::SimpleNode(int code)
+: Node(), _name(code), _attributes(), _child_count(0),
+ _cached_positions_valid(false)
+{
+ this->_logger = NULL;
+ this->_document = NULL;
+ this->_parent = this->_next = NULL;
+ this->_first_child = this->_last_child = NULL;
+}
+
+SimpleNode::SimpleNode(SimpleNode const &node)
+: Node(),
+ _cached_position(node._cached_position),
+ _name(node._name), _attributes(), _content(node._content),
+ _child_count(node._child_count),
+ _cached_positions_valid(node._cached_positions_valid)
+{
+ _logger = NULL;
+ _document = NULL;
+ _parent = _next = NULL;
+ _first_child = _last_child = NULL;
+
+ for ( Node *child = node._first_child ;
+ child != NULL ; child = child->next() )
+ {
+ Node *child_copy=child->duplicate();
+
+ child_copy->_setParent(this);
+ if (_last_child) {
+ _last_child->_setNext(child_copy);
+ } else {
+ _first_child = child_copy;
+ }
+ _last_child = child_copy;
+
+ child_copy->release(); // release to avoid a leak
+ }
+
+ for ( List<AttributeRecord const> iter = node._attributes ;
+ iter ; ++iter )
+ {
+ _attributes = cons(*iter, _attributes);
+ }
+}
+
+gchar const *SimpleNode::name() const {
+ return g_quark_to_string(_name);
+}
+
+gchar const *SimpleNode::content() const {
+ return this->_content;
+}
+
+gchar const *SimpleNode::attribute(gchar const *name) const {
+ g_return_val_if_fail(name != NULL, NULL);
+
+ GQuark const key = g_quark_from_string(name);
+
+ for ( List<AttributeRecord const> iter = _attributes ;
+ iter ; ++iter )
+ {
+ if ( iter->key == key ) {
+ return iter->value;
+ }
+ }
+
+ return NULL;
+}
+
+unsigned SimpleNode::position() const {
+ g_return_val_if_fail(_parent != NULL, 0);
+ return _parent->_childPosition(*this);
+}
+
+unsigned SimpleNode::_childPosition(Node const &child) const {
+ if (!_cached_positions_valid) {
+ unsigned position=0;
+ for ( Node *sibling = _first_child ;
+ sibling ; sibling = sibling->next() )
+ {
+ sibling->_setCachedPosition(position);
+ position++;
+ }
+ _cached_positions_valid = true;
+ }
+ return child._cachedPosition();
+}
+
+Node *SimpleNode::nthChild(unsigned index) {
+ Node *child = _first_child;
+ for ( ; index > 0 && child ; child = child->next() ) {
+ index--;
+ }
+ return child;
+}
+
+bool SimpleNode::matchAttributeName(gchar const *partial_name) const {
+ g_return_val_if_fail(partial_name != NULL, false);
+
+ for ( List<AttributeRecord const> iter = _attributes ;
+ iter ; ++iter )
+ {
+ gchar const *name = g_quark_to_string(iter->key);
+ if (std::strstr(name, partial_name)) {
+ return true;
+ }
+ }
+
+ return false;
+}
+
+void SimpleNode::setContent(gchar const *content) {
+ SharedCStringPtr old_content=_content;
+ SharedCStringPtr new_content = ( content ? SharedCStringPtr::copy(content) : SharedCStringPtr() );
+
+ Debug::EventTracker<DebugSetContent> tracker(
+ *this, old_content, new_content
+ );
+
+ _content = new_content;
+
+ if ( _content != old_content ) {
+ if (_logger) {
+ _logger->notifyContentChanged(*this, old_content, _content);
+ }
+
+ _observers.notifyContentChanged(*this, old_content, _content);
+ }
+}
+
+void
+SimpleNode::setAttribute(gchar const *name, gchar const *value, bool const is_interactive)
+{
+ g_return_if_fail(name && *name);
+
+ GQuark const key = g_quark_from_string(name);
+
+ MutableList<AttributeRecord> ref;
+ MutableList<AttributeRecord> existing;
+ for ( existing = _attributes ; existing ; ++existing ) {
+ if ( existing->key == key ) {
+ break;
+ }
+ ref = existing;
+ }
+
+ Debug::EventTracker<> tracker;
+
+ SharedCStringPtr old_value=( existing ? existing->value : SharedCStringPtr() );
+
+ SharedCStringPtr new_value=SharedCStringPtr();
+ if (value) {
+ new_value = SharedCStringPtr::copy(value);
+ tracker.set<DebugSetAttribute>(*this, key, old_value, new_value);
+ if (!existing) {
+ if (ref) {
+ set_rest(ref, MutableList<AttributeRecord>(AttributeRecord(key, new_value)));
+ } else {
+ _attributes = MutableList<AttributeRecord>(AttributeRecord(key, new_value));
+ }
+ } else {
+ existing->value = new_value;
+ }
+ } else {
+ tracker.set<DebugSetAttribute>(*this, key, old_value, new_value);
+ if (existing) {
+ if (ref) {
+ set_rest(ref, rest(existing));
+ } else {
+ _attributes = rest(existing);
+ }
+ set_rest(existing, MutableList<AttributeRecord>());
+ }
+ }
+
+ if ( new_value != old_value ) {
+ if (_logger) {
+ _logger->notifyAttributeChanged(*this, key, old_value, new_value);
+ }
+
+ _observers.notifyAttributeChanged(*this, key, old_value, new_value);
+ }
+}
+
+void SimpleNode::addChild(Node *child, Node *ref) {
+ g_assert(child);
+ g_assert(!ref || ref->parent() == this);
+ g_assert(!child->parent());
+
+ Debug::EventTracker<DebugAddChild> tracker(*this, *child, ref);
+
+ Node *next;
+ if (ref) {
+ next = ref->next();
+ ref->_setNext(child);
+ } else {
+ next = _first_child;
+ _first_child = child;
+ }
+ if (!next) { // appending?
+ _last_child = child;
+ // set cached position if possible when appending
+ if (!ref) {
+ // if !next && !ref, child is sole child
+ child->_setCachedPosition(0);
+ _cached_positions_valid = true;
+ } else if (_cached_positions_valid) {
+ child->_setCachedPosition(ref->_cachedPosition() + 1);
+ }
+ } else {
+ // invalidate cached positions otherwise
+ _cached_positions_valid = false;
+ }
+
+ child->_setParent(this);
+ child->_setNext(next);
+ _child_count++;
+
+ if (_document) {
+ child->_bindDocument(*_document);
+ }
+ if (_logger) {
+ child->_bindLogger(*_logger);
+ _logger->notifyChildAdded(*this, *child, ref);
+ }
+
+ _observers.notifyChildAdded(*this, *child, ref);
+}
+
+void SimpleNode::_bindDocument(Document &document) {
+ g_assert(!_document || _document == &document);
+
+ if (!_document) {
+ _document = &document;
+
+ for ( Node *child = _first_child ; child != NULL ; child = child->next() ) {
+ child->_bindDocument(document);
+ }
+ }
+}
+
+void SimpleNode::_bindLogger(TransactionLogger &logger) {
+ g_assert(!_logger || _logger == &logger);
+
+ if (!_logger) {
+ _logger = &logger;
+
+ for ( Node *child = _first_child ; child != NULL ; child = child->next() ) {
+ child->_bindLogger(logger);
+ }
+ }
+}
+
+void SimpleNode::removeChild(Node *child) {
+ g_assert(child);
+ g_assert(child->parent() == this);
+
+ Node *ref = ( child != _first_child ? previous_node(child) : NULL );
+
+ Debug::EventTracker<DebugRemoveChild> tracker(*this, *child, ref);
+
+ Node *next = child->next();
+ if (ref) {
+ ref->_setNext(next);
+ } else {
+ _first_child = next;
+ }
+ if (!next) { // removing the last child?
+ _last_child = ref;
+ } else {
+ // removing any other child invalidates the cached positions
+ _cached_positions_valid = false;
+ }
+
+ child->_setNext(NULL);
+ child->_setParent(NULL);
+ _child_count--;
+
+ if (_logger) {
+ _logger->notifyChildRemoved(*this, *child, ref);
+ }
+
+ _observers.notifyChildRemoved(*this, *child, ref);
+}
+
+void SimpleNode::changeOrder(Node *child, Node *ref) {
+ g_return_if_fail(child);
+ g_return_if_fail(child->parent() == this);
+ g_return_if_fail(child != ref);
+ g_return_if_fail(!ref || ref->parent() == this);
+
+ Node *const prev = previous_node(child);
+
+ Debug::EventTracker<DebugSetChildPosition> tracker(*this, *child, prev, ref);
+
+ if (prev == ref) { return; }
+
+ Node *next;
+
+ /* Remove from old position. */
+ next=child->next();
+ if (prev) {
+ prev->_setNext(next);
+ } else {
+ _first_child = next;
+ }
+ if (!next) {
+ _last_child = prev;
+ }
+
+ /* Insert at new position. */
+ if (ref) {
+ next = ref->next();
+ ref->_setNext(child);
+ } else {
+ next = _first_child;
+ _first_child = child;
+ }
+ child->_setNext(next);
+ if (!next) {
+ _last_child = child;
+ }
+
+ _cached_positions_valid = false;
+
+ if (_logger) {
+ _logger->notifyChildOrderChanged(*this, *child, prev, ref);
+ }
+
+ _observers.notifyChildOrderChanged(*this, *child, prev, ref);
+}
+
+void SimpleNode::setPosition(int pos) {
+ g_return_if_fail(_parent != NULL);
+
+ // a position beyond the end of the list means the end of the list;
+ // a negative position is the same as an infinitely large position
+
+ Node *ref=NULL;
+ for ( Node *sibling = _parent->firstChild() ;
+ sibling && pos ; sibling = sibling->next() )
+ {
+ if ( sibling != this ) {
+ ref = sibling;
+ pos--;
+ }
+ }
+
+ _parent->changeOrder(this, ref);
+}
+
+namespace {
+
+void child_added(Node *node, Node *child, Node *ref, void *data) {
+ reinterpret_cast<NodeObserver *>(data)->notifyChildAdded(*node, *child, ref);
+}
+
+void child_removed(Node *node, Node *child, Node *ref, void *data) {
+ reinterpret_cast<NodeObserver *>(data)->notifyChildRemoved(*node, *child, ref);
+}
+
+void content_changed(Node *node, gchar const *old_content, gchar const *new_content, void *data) {
+ reinterpret_cast<NodeObserver *>(data)->notifyContentChanged(*node, Util::SharedCStringPtr::coerce((const char *)old_content), Util::SharedCStringPtr::coerce((const char *)new_content));
+}
+
+void attr_changed(Node *node, gchar const *name, gchar const *old_value, gchar const *new_value, bool is_interactive, void *data) {
+ reinterpret_cast<NodeObserver *>(data)->notifyAttributeChanged(*node, g_quark_from_string(name), Util::SharedCStringPtr::coerce((const char *)old_value), Util::SharedCStringPtr::coerce((const char *)new_value));
+}
+
+void order_changed(Node *node, Node *child, Node *old_ref, Node *new_ref, void *data) {
+ reinterpret_cast<NodeObserver *>(data)->notifyChildOrderChanged(*node, *child, old_ref, new_ref);
+}
+
+const NodeEventVector OBSERVER_EVENT_VECTOR = {
+ &child_added,
+ &child_removed,
+ &attr_changed,
+ &content_changed,
+ &order_changed
+};
+
+};
+
+void SimpleNode::synthesizeEvents(NodeEventVector const *vector, void *data) {
+ if (vector->attr_changed) {
+ for ( List<AttributeRecord const> iter = _attributes ;
+ iter ; ++iter )
+ {
+ vector->attr_changed(this, g_quark_to_string(iter->key), NULL, iter->value, false, data);
+ }
+ }
+ if (vector->child_added) {
+ Node *ref = NULL;
+ for ( Node *child = this->_first_child ;
+ child ; child = child->next() )
+ {
+ vector->child_added(this, child, ref, data);
+ ref = child;
+ }
+ }
+ if (vector->content_changed) {
+ vector->content_changed(this, NULL, this->_content, data);
+ }
+}
+
+void SimpleNode::synthesizeEvents(NodeObserver &observer) {
+ synthesizeEvents(&OBSERVER_EVENT_VECTOR, &observer);
+}
+
+Node *SimpleNode::root() {
+ Node *parent=this;
+ while (parent->parent()) {
+ parent = parent->parent();
+ }
+
+ if ( parent->type() == DOCUMENT_NODE ) {
+ for ( Node *child = _document->firstChild() ;
+ child ; child = child->next() )
+ {
+ if ( child->type() == ELEMENT_NODE ) {
+ return child;
+ }
+ }
+ return NULL;
+ } else if ( parent->type() == ELEMENT_NODE ) {
+ return parent;
+ } else {
+ return NULL;
+ }
+}
+
+void SimpleNode::mergeFrom(Node const *src, gchar const *key) {
+ g_return_if_fail(src != NULL);
+ g_return_if_fail(key != NULL);
+ g_assert(src != this);
+
+ setContent(src->content());
+
+ for ( Node const *child = src->firstChild() ; child != NULL ; child = child->next() )
+ {
+ gchar const *id = child->attribute(key);
+ if (id) {
+ Node *rch=sp_repr_lookup_child(this, key, id);
+ if (rch) {
+ rch->mergeFrom(child, key);
+ } else {
+ rch = child->duplicate();
+ appendChild(rch);
+ rch->release();
+ }
+ } else {
+ Node *rch=child->duplicate();
+ appendChild(rch);
+ rch->release();
+ }
+ }
+
+ for ( List<AttributeRecord const> iter = src->attributeList() ;
+ iter ; ++iter )
+ {
+ setAttribute(g_quark_to_string(iter->key), iter->value);
+ }
+}
+
+}
+
+}
+
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
+ indent-tabs-mode:nil
+ fill-column:99
+ End:
+*/
+// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 :
diff --git a/src/xml/simple-node.h b/src/xml/simple-node.h
new file mode 100644
index 000000000..75fe1db07
--- /dev/null
+++ b/src/xml/simple-node.h
@@ -0,0 +1,167 @@
+/*
+ * SimpleNode - generic XML node implementation
+ *
+ * Copyright 2004-2005 MenTaLguY <mental@rydia.net>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * See the file COPYING for details.
+ *
+ */
+
+#ifndef SEEN_INKSCAPE_XML_SIMPLE_NODE_H
+#define SEEN_INKSCAPE_XML_SIMPLE_NODE_H
+
+#include "xml/node.h"
+#include "xml/attribute-record.h"
+#include "xml/transaction-logger.h"
+#include "xml/composite-node-observer.h"
+#include "util/list-container.h"
+
+namespace Inkscape {
+
+namespace XML {
+
+class SimpleNode
+: virtual public Node, public Inkscape::GC::Managed<>
+{
+public:
+ Session *session() {
+ return ( _logger ? &_logger->session() : NULL );
+ }
+
+ gchar const *name() const;
+ int code() const { return _name; }
+ void setCodeUnsafe(int code) {
+ g_assert(_logger == NULL);
+ _name = code;
+ }
+
+ Document *document() { return _document; }
+ Document const *document() const {
+ return const_cast<SimpleNode *>(this)->document();
+ }
+
+ Node *duplicate() const { return _duplicate(); }
+
+ Node *root();
+ Node const *root() const {
+ return const_cast<SimpleNode *>(this)->root();
+ }
+
+ Node *parent() { return _parent; }
+ Node const *parent() const { return _parent; }
+
+ Node *next() { return _next; }
+ Node const *next() const { return _next; }
+
+ Node *firstChild() { return _first_child; }
+ Node const *firstChild() const { return _first_child; }
+ Node *lastChild() { return _last_child; }
+ Node const *lastChild() const { return _last_child; }
+
+ unsigned childCount() const { return _child_count; }
+ Node *nthChild(unsigned index);
+ Node const *nthChild(unsigned index) const {
+ return const_cast<SimpleNode *>(this)->nthChild(index);
+ }
+
+ void addChild(Node *child, Node *ref);
+ void appendChild(Node *child) {
+ SimpleNode::addChild(child, _last_child);
+ }
+ void removeChild(Node *child);
+ void changeOrder(Node *child, Node *ref);
+
+ unsigned position() const;
+ void setPosition(int pos);
+
+ gchar const *attribute(gchar const *key) const;
+ void setAttribute(gchar const *key, gchar const *value, bool is_interactive=false);
+ bool matchAttributeName(gchar const *partial_name) const;
+
+ gchar const *content() const;
+ void setContent(gchar const *value);
+
+ void mergeFrom(Node const *src, gchar const *key);
+
+ Inkscape::Util::List<AttributeRecord const> attributeList() const {
+ return _attributes;
+ }
+
+ void synthesizeEvents(NodeEventVector const *vector, void *data);
+ void synthesizeEvents(NodeObserver &observer);
+
+ void addListener(NodeEventVector const *vector, void *data) {
+ g_assert(vector != NULL);
+ _observers.addListener(*vector, data);
+ }
+ void addObserver(NodeObserver &observer) {
+ _observers.add(observer);
+ }
+ void removeListenerByData(void *data) {
+ _observers.removeListenerByData(data);
+ }
+ void removeObserver(NodeObserver &observer) {
+ _observers.remove(observer);
+ }
+
+protected:
+ SimpleNode(int code);
+ SimpleNode(SimpleNode const &repr);
+
+ virtual SimpleNode *_duplicate() const=0;
+
+public: // ideally these should be protected somehow...
+ void _setParent(Node *parent) { _parent = parent; }
+ void _setNext(Node *next) { _next = next; }
+ void _bindDocument(Document &document);
+ void _bindLogger(TransactionLogger &logger);
+
+ unsigned _childPosition(Node const &child) const;
+ unsigned _cachedPosition() const { return _cached_position; }
+ void _setCachedPosition(unsigned position) const {
+ _cached_position = position;
+ }
+
+private:
+ void operator=(Node const &); // no assign
+
+ Node *_parent;
+ Node *_next;
+ Document *_document;
+ TransactionLogger *_logger;
+ mutable unsigned _cached_position;
+
+ int _name;
+
+ Inkscape::Util::MutableList<AttributeRecord> _attributes;
+
+ Inkscape::Util::SharedCStringPtr _content;
+
+ unsigned _child_count;
+ mutable bool _cached_positions_valid;
+ Node *_first_child;
+ Node *_last_child;
+
+ CompositeNodeObserver _observers;
+};
+
+}
+
+}
+
+#endif
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
+ indent-tabs-mode:nil
+ fill-column:99
+ End:
+*/
+// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :
diff --git a/src/xml/simple-session.cpp b/src/xml/simple-session.cpp
new file mode 100644
index 000000000..76caf1e54
--- /dev/null
+++ b/src/xml/simple-session.cpp
@@ -0,0 +1,122 @@
+/*
+ * Inkscape::XML::SimpleSession - simple session/logging implementation
+ *
+ * Copyright 2005 MenTaLguY <mental@rydia.net>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * See the file COPYING for details.
+ *
+ */
+
+#include "xml/simple-session.h"
+#include "xml/event-fns.h"
+#include "xml/element-node.h"
+#include "xml/text-node.h"
+#include "xml/comment-node.h"
+
+namespace Inkscape {
+
+namespace XML {
+
+void SimpleSession::beginTransaction() {
+ g_assert(!_in_transaction);
+ _in_transaction = true;
+}
+
+void SimpleSession::rollback() {
+ g_assert(_in_transaction);
+ _in_transaction = false;
+ Event *log = _log_builder.detach();
+ sp_repr_undo_log(log);
+ sp_repr_free_log(log);
+}
+
+void SimpleSession::commit() {
+ g_assert(_in_transaction);
+ _in_transaction = false;
+ _log_builder.discard();
+}
+
+Inkscape::XML::Event *SimpleSession::commitUndoable() {
+ g_assert(_in_transaction);
+ _in_transaction = false;
+ return _log_builder.detach();
+}
+
+Node *SimpleSession::createElementNode(char const *name) {
+ return new ElementNode(g_quark_from_string(name));
+}
+
+Node *SimpleSession::createTextNode(char const *content) {
+ return new TextNode(Util::SharedCStringPtr::copy(content));
+}
+
+Node *SimpleSession::createCommentNode(char const *content) {
+ return new CommentNode(Util::SharedCStringPtr::copy(content));
+}
+
+void SimpleSession::notifyChildAdded(Node &parent,
+ Node &child,
+ Node *prev)
+{
+ if (_in_transaction) {
+ _log_builder.addChild(parent, child, prev);
+ }
+}
+
+void SimpleSession::notifyChildRemoved(Node &parent,
+ Node &child,
+ Node *prev)
+{
+ if (_in_transaction) {
+ _log_builder.removeChild(parent, child, prev);
+ }
+}
+
+void SimpleSession::notifyChildOrderChanged(Node &parent,
+ Node &child,
+ Node *old_prev,
+ Node *new_prev)
+{
+ if (_in_transaction) {
+ _log_builder.setChildOrder(parent, child, old_prev, new_prev);
+ }
+}
+
+void SimpleSession::notifyContentChanged(Node &node,
+ Util::SharedCStringPtr old_content,
+ Util::SharedCStringPtr new_content)
+{
+ if (_in_transaction) {
+ _log_builder.setContent(node, old_content, new_content);
+ }
+}
+
+void SimpleSession::notifyAttributeChanged(Node &node,
+ GQuark name,
+ Util::SharedCStringPtr old_value,
+ Util::SharedCStringPtr new_value)
+{
+ if (_in_transaction) {
+ _log_builder.setAttribute(node, name, old_value, new_value);
+ }
+}
+
+}
+
+}
+
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
+ indent-tabs-mode:nil
+ fill-column:99
+ End:
+*/
+// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :
diff --git a/src/xml/simple-session.h b/src/xml/simple-session.h
new file mode 100644
index 000000000..5e9e3a7c1
--- /dev/null
+++ b/src/xml/simple-session.h
@@ -0,0 +1,84 @@
+/*
+ * Inkscape::XML::SimpleSession - simple session/logging implementation
+ *
+ * Copyright 2005 MenTaLguY <mental@rydia.net>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * See the file COPYING for details.
+ *
+ */
+
+#ifndef SEEN_INKSCAPE_XML_SIMPLE_SESSION_H
+#define SEEN_INKSCAPE_XML_SIMPLE_SESSION_H
+
+#include "gc-managed.h"
+#include "xml/session.h"
+#include "xml/transaction-logger.h"
+#include "xml/log-builder.h"
+
+namespace Inkscape {
+
+namespace XML {
+
+class SimpleSession : public GC::Managed<>,
+ public Session,
+ public TransactionLogger
+{
+public:
+ SimpleSession() : _in_transaction(false) {}
+
+ bool inTransaction() { return _in_transaction; }
+
+ void beginTransaction();
+ void rollback();
+ void commit();
+ Inkscape::XML::Event *commitUndoable();
+
+ Node *createElementNode(char const *name);
+ Node *createTextNode(char const *content);
+ Node *createCommentNode(char const *content);
+
+ Session &session() { return *this; }
+
+ void notifyChildAdded(Inkscape::XML::Node &parent, Inkscape::XML::Node &child, Inkscape::XML::Node *prev);
+
+ void notifyChildRemoved(Inkscape::XML::Node &parent, Inkscape::XML::Node &child, Inkscape::XML::Node *prev);
+
+ void notifyChildOrderChanged(Inkscape::XML::Node &parent, Inkscape::XML::Node &child,
+ Inkscape::XML::Node *old_prev, Inkscape::XML::Node *new_prev);
+
+ void notifyContentChanged(Inkscape::XML::Node &node,
+ Util::SharedCStringPtr old_content,
+ Util::SharedCStringPtr new_content);
+
+ void notifyAttributeChanged(Inkscape::XML::Node &node, GQuark name,
+ Util::SharedCStringPtr old_value,
+ Util::SharedCStringPtr new_value);
+
+private:
+ SimpleSession(SimpleSession const &); // no copy
+ void operator=(SimpleSession const &); // no assign
+
+ bool _in_transaction;
+ LogBuilder _log_builder;
+};
+
+}
+
+}
+
+#endif
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
+ indent-tabs-mode:nil
+ fill-column:99
+ End:
+*/
+// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :
diff --git a/src/xml/sp-css-attr.h b/src/xml/sp-css-attr.h
new file mode 100644
index 000000000..f6a0d9682
--- /dev/null
+++ b/src/xml/sp-css-attr.h
@@ -0,0 +1,33 @@
+/*
+ * SPCSSAttr - interface for CSS Attributes
+ *
+ * Copyright 2005 Kees Cook <kees@outflux.net>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * See the file COPYING for details.
+ *
+ */
+
+#ifndef SEEN_INKSCAPE_XML_SP_SPCSSATTR_H
+#define SEEN_INKSCAPE_XML_SP_SPCSSATTR_H
+
+#include "xml/node.h"
+
+class SPCSSAttr : virtual public Inkscape::XML::Node {
+};
+
+#endif
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
+ indent-tabs-mode:nil
+ fill-column:99
+ End:
+*/
+// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :
diff --git a/src/xml/text-node.h b/src/xml/text-node.h
new file mode 100644
index 000000000..c07d70f65
--- /dev/null
+++ b/src/xml/text-node.h
@@ -0,0 +1,52 @@
+/*
+ * Inkscape::XML::TextNode - simple text node implementation
+ *
+ * Copyright 2004-2005 MenTaLguY <mental@rydia.net>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * See the file COPYING for details.
+ *
+ */
+
+#ifndef SEEN_INKSCAPE_XML_TEXT_NODE_H
+#define SEEN_INKSCAPE_XML_TEXT_NODE_H
+
+#include <glib/gquark.h>
+#include "xml/simple-node.h"
+
+namespace Inkscape {
+
+namespace XML {
+
+struct TextNode : public SimpleNode {
+ TextNode(Util::SharedCStringPtr content)
+ : SimpleNode(g_quark_from_static_string("string"))
+ {
+ setContent(content);
+ }
+
+ Inkscape::XML::NodeType type() const { return Inkscape::XML::TEXT_NODE; }
+
+protected:
+ SimpleNode *_duplicate() const { return new TextNode(*this); }
+};
+
+}
+
+}
+
+#endif
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
+ indent-tabs-mode:nil
+ fill-column:99
+ End:
+*/
+// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :
diff --git a/src/xml/transaction-logger.h b/src/xml/transaction-logger.h
new file mode 100644
index 000000000..9f7bfbc50
--- /dev/null
+++ b/src/xml/transaction-logger.h
@@ -0,0 +1,52 @@
+/*
+ * Inkscape::XML::TransactionLogger - logs changes for transactions
+ *
+ * Copyright 2005 MenTaLguY <mental@rydia.net>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * See the file COPYING for details.
+ *
+ */
+
+#ifndef SEEN_INKSCAPE_XML_TRANSACTION_LOGGER_H
+#define SEEN_INKSCAPE_XML_TRANSACTION_LOGGER_H
+
+#include "xml/node-observer.h"
+
+namespace Inkscape {
+namespace XML {
+class Event;
+}
+}
+
+
+namespace Inkscape {
+
+namespace XML {
+
+class Session;
+
+class TransactionLogger : public NodeObserver {
+public:
+ virtual Session &session()=0;
+};
+
+}
+
+}
+
+#endif
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
+ indent-tabs-mode:nil
+ fill-column:99
+ End:
+*/
+// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :