From 179fa413b047bede6e32109e2ce82437c5fb8d34 Mon Sep 17 00:00:00 2001 From: MenTaLguY Date: Mon, 16 Jan 2006 02:36:01 +0000 Subject: moving trunk for module inkscape (bzr r1) --- src/xml/.cvsignore | 10 + src/xml/Makefile_insert | 86 ++++ src/xml/attribute-record.h | 39 ++ src/xml/comment-node.h | 52 +++ src/xml/composite-node-observer.cpp | 308 +++++++++++++ src/xml/composite-node-observer.h | 86 ++++ src/xml/croco-node-iface.cpp | 68 +++ src/xml/croco-node-iface.h | 12 + src/xml/document.h | 50 +++ src/xml/element-node.h | 49 +++ src/xml/event-fns.h | 28 ++ src/xml/event.cpp | 489 +++++++++++++++++++++ src/xml/event.h | 150 +++++++ src/xml/invalid-operation-exception.h | 47 ++ src/xml/log-builder.cpp | 78 ++++ src/xml/log-builder.h | 66 +++ src/xml/makefile.in | 17 + src/xml/node-event-vector.h | 47 ++ src/xml/node-fns.cpp | 103 +++++ src/xml/node-fns.h | 44 ++ src/xml/node-iterators.h | 61 +++ src/xml/node-observer.h | 68 +++ src/xml/node.h | 131 ++++++ src/xml/quote-test.cpp | 82 ++++ src/xml/quote-test.h | 31 ++ src/xml/quote.cpp | 86 ++++ src/xml/quote.h | 7 + src/xml/repr-action-test.cpp | 77 ++++ src/xml/repr-action-test.h | 31 ++ src/xml/repr-css.cpp | 261 +++++++++++ src/xml/repr-io.cpp | 786 ++++++++++++++++++++++++++++++++++ src/xml/repr-sorting.cpp | 53 +++ src/xml/repr-sorting.h | 11 + src/xml/repr-util.cpp | 553 ++++++++++++++++++++++++ src/xml/repr.cpp | 107 +++++ src/xml/repr.h | 261 +++++++++++ src/xml/session.h | 56 +++ src/xml/simple-document.cpp | 40 ++ src/xml/simple-document.h | 57 +++ src/xml/simple-node.cpp | 731 +++++++++++++++++++++++++++++++ src/xml/simple-node.h | 167 ++++++++ src/xml/simple-session.cpp | 122 ++++++ src/xml/simple-session.h | 84 ++++ src/xml/sp-css-attr.h | 33 ++ src/xml/text-node.h | 52 +++ src/xml/transaction-logger.h | 52 +++ 46 files changed, 5829 insertions(+) create mode 100644 src/xml/.cvsignore create mode 100644 src/xml/Makefile_insert create mode 100644 src/xml/attribute-record.h create mode 100644 src/xml/comment-node.h create mode 100644 src/xml/composite-node-observer.cpp create mode 100644 src/xml/composite-node-observer.h create mode 100644 src/xml/croco-node-iface.cpp create mode 100644 src/xml/croco-node-iface.h create mode 100644 src/xml/document.h create mode 100644 src/xml/element-node.h create mode 100644 src/xml/event-fns.h create mode 100644 src/xml/event.cpp create mode 100644 src/xml/event.h create mode 100644 src/xml/invalid-operation-exception.h create mode 100644 src/xml/log-builder.cpp create mode 100644 src/xml/log-builder.h create mode 100644 src/xml/makefile.in create mode 100644 src/xml/node-event-vector.h create mode 100644 src/xml/node-fns.cpp create mode 100644 src/xml/node-fns.h create mode 100644 src/xml/node-iterators.h create mode 100644 src/xml/node-observer.h create mode 100644 src/xml/node.h create mode 100644 src/xml/quote-test.cpp create mode 100644 src/xml/quote-test.h create mode 100644 src/xml/quote.cpp create mode 100644 src/xml/quote.h create mode 100644 src/xml/repr-action-test.cpp create mode 100644 src/xml/repr-action-test.h create mode 100644 src/xml/repr-css.cpp create mode 100644 src/xml/repr-io.cpp create mode 100644 src/xml/repr-sorting.cpp create mode 100644 src/xml/repr-sorting.h create mode 100644 src/xml/repr-util.cpp create mode 100644 src/xml/repr.cpp create mode 100644 src/xml/repr.h create mode 100644 src/xml/session.h create mode 100644 src/xml/simple-document.cpp create mode 100644 src/xml/simple-document.h create mode 100644 src/xml/simple-node.cpp create mode 100644 src/xml/simple-node.h create mode 100644 src/xml/simple-session.cpp create mode 100644 src/xml/simple-session.h create mode 100644 src/xml/sp-css-attr.h create mode 100644 src/xml/text-node.h create mode 100644 src/xml/transaction-logger.h (limited to 'src/xml') 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 +# +# 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 +#include +#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 + * + * 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 +#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 + * + * 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 > 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 +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 +bool mark_one(ObserverRecordList &observers, unsigned &marked_count, + Predicate p) +{ + ObserverRecordList::iterator found=std::find_if( + observers.begin(), observers.end(), + unmarked_record_satisfying(p) + ); + + if ( found != observers.end() ) { + found->marked = true; + return true; + } else { + return false; + } +} + +template +bool remove_one(ObserverRecordList &observers, unsigned &marked_count, + Predicate p) +{ + if (observers.empty()) { + return false; + } + + if (unmarked_record_satisfying(p)(observers.front())) { + observers.pop_front(); + return true; + } + + ObserverRecordList::iterator found=find_if_before( + observers.begin(), observers.end(), + unmarked_record_satisfying(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(&observer); + return vo && vo->data == data; + } +}; + +} + +void CompositeNodeObserver::removeListenerByData(void *data) { + Debug::EventTracker > 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 + * + * 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 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 + +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(n)->parent(); } +static CRXMLNodePtr get_first_child(CRXMLNodePtr n) { return static_cast(n)->firstChild(); } +static CRXMLNodePtr get_next(CRXMLNodePtr n) { return static_cast(n)->next(); } + +static CRXMLNodePtr get_prev(CRXMLNodePtr cn) +{ + Node const *n = static_cast(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(n)->attribute(a)); +} + +static char const *get_local_name(CRXMLNodePtr n) { return local_part(static_cast(n)->name()); } +static gboolean is_element_node(CRXMLNodePtr n) { return static_cast(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 + +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 + * + * 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 + * + * 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 + * MenTaLguY + * + * 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 > 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 > 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 > 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 > 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 > 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 reversed = + reverse_list(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 > 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 struct ActionRelations; + +template <> +struct ActionRelations { + typedef Inkscape::XML::EventDel Opposite; +}; + +template <> +struct ActionRelations { + typedef Inkscape::XML::EventAdd Opposite; +}; + +template +Inkscape::XML::Event *cancel_add_or_remove(A *action) { + typedef typename ActionRelations::Opposite Opposite; + Opposite *opposite=dynamic_cast(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(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(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(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 +#include +#include + +#include +#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 +{ +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 Iterator; + typedef Inkscape::Util::ForwardPointerIterator 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 + * + * 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 +#include + +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 + * + * 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 + * + * 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 + * Frank Felfe + * + * 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 + +#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 + +#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 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( + 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)); +} +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 + * + * 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 + NodeSiblingIterator; + +typedef Inkscape::Util::ForwardPointerIterator + NodeConstSiblingIterator; + +typedef Inkscape::Util::ForwardPointerIterator + NodeParentIterator; + +typedef Inkscape::Util::ForwardPointerIterator + 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 + * + * 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 +#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 + * + * 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 +#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 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 +#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 cases[] = { + {"", 0}, + {"x", 1}, + {"Foo", 3}, + {"\"", 6}, + {"&", 5}, + {"<", 4}, + {">", 4}, + {"a\"b", 8}, + {"a\"bd;!@#$%^*(\\)?", 30} + }; + test_1ary_cases >("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 cases[] = { + {"", ""}, + {"x", "x"}, + {"Foo", "Foo"}, + {"\"", """}, + {"&", "&"}, + {"<", "<"}, + {">", ">"}, + {"a\"bd;!@#$%^*(\\)?", "a"b<c>d;!@#$%^*(\\)?"} + }; + test_1ary_cases("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 + +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 +#include + + +/** \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(""") - 1; break; + case '&': ret += sizeof("&") - 1; break; + case '<': ret += sizeof("<") - 1; break; + case '>': ret += sizeof(">") - 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("""); break; + case '&': COPY_LIT("&"); break; + case '<': COPY_LIT("<"); break; + case '>': COPY_LIT(">"); 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 +#include +#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 + +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 +*/ + +#define SP_REPR_CSS_C + + +#include + +#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 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 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 + * bulia byak + * + * Copyright (C) 1999-2002 Lauris Kaplinski + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#ifdef HAVE_CONFIG_H +# include +#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 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(context); + retVal = self->read( buffer, len ); + } + return retVal; +} + +int XmlSource::closeCb(void * context) +{ + if ( context ) { + XmlSource* self = static_cast(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 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( "\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( """ ); break; + case '&': out.writeString( "&" ); break; + case '<': out.writeString( "<" ); break; + case '>': out.writeString( ">" ); break; + default: out.writeChar( *val ); break; + } + } +} + +namespace { + +typedef std::map LocalNameMap; +typedef std::map 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 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 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( "", 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 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 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 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( "", 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( + 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 + * + * 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 + +#if HAVE_STRING_H +# include +#endif + +#if HAVE_STDLIB_H +# include +#endif + + +#include + +#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 + * MenTaLguY + * + * 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 + * + * Copyright (C) 1999-2002 authors + * Copyright (C) 2000-2002 Ximian, Inc. + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#include +#include +#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. + * + * Inkscape::XML::Node mini-FAQ + * + * 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?
+ * A: If you have an SPDocument, use doc->rroot. For example: + * + * \code SP_ACTIVE_DOCUMENT->rroot \endcode
+ * + * (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?
+ * A: Use sp_repr_lookup_child + * + * \arg + * Q: How do I find an Inkscape::XML::Node by unique namespace name?
+ * A: Use sp_repr_lookup_name + * + * \arg + * Q: How do I make an Inkscape::XML::Node namespace prefix constant in + * the application?
+ * A: Add the XML namespace URL as a #define to repr.h at the top with the + * other SP__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?
+ * 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?
+ * A: I have no idea yet... + * + * \arg + * Q: How do I add a namespace to a newly created document?
+ * 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(doc->root()); +} + +/// Returns the node's document. +inline Inkscape::XML::Document *sp_repr_document(Inkscape::XML::Node const *repr) { + return const_cast(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(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(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 + * + * 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 + * + * 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 + * + * 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 + * 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 +#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)=", &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 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 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 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 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 ref; + MutableList 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(*this, key, old_value, new_value); + if (!existing) { + if (ref) { + set_rest(ref, MutableList(AttributeRecord(key, new_value))); + } else { + _attributes = MutableList(AttributeRecord(key, new_value)); + } + } else { + existing->value = new_value; + } + } else { + tracker.set(*this, key, old_value, new_value); + if (existing) { + if (ref) { + set_rest(ref, rest(existing)); + } else { + _attributes = rest(existing); + } + set_rest(existing, MutableList()); + } + } + + 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 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 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 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(data)->notifyChildAdded(*node, *child, ref); +} + +void child_removed(Node *node, Node *child, Node *ref, void *data) { + reinterpret_cast(data)->notifyChildRemoved(*node, *child, ref); +} + +void content_changed(Node *node, gchar const *old_content, gchar const *new_content, void *data) { + reinterpret_cast(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(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(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 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 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 + * + * 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(this)->document(); + } + + Node *duplicate() const { return _duplicate(); } + + Node *root(); + Node const *root() const { + return const_cast(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(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 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 _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 + * + * 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 + * + * 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 + * + * 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 + * + * 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 +#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 + * + * 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 : -- cgit v1.2.3