summaryrefslogtreecommitdiffstats
path: root/src/xml/simple-node.cpp
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 18:24:48 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 18:24:48 +0000
commitcca66b9ec4e494c1d919bff0f71a820d8afab1fa (patch)
tree146f39ded1c938019e1ed42d30923c2ac9e86789 /src/xml/simple-node.cpp
parentInitial commit. (diff)
downloadinkscape-upstream.tar.xz
inkscape-upstream.zip
Adding upstream version 1.2.2.upstream/1.2.2upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'src/xml/simple-node.cpp')
-rw-r--r--src/xml/simple-node.cpp796
1 files changed, 796 insertions, 0 deletions
diff --git a/src/xml/simple-node.cpp b/src/xml/simple-node.cpp
new file mode 100644
index 0000000..82cac3a
--- /dev/null
+++ b/src/xml/simple-node.cpp
@@ -0,0 +1,796 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/** @file
+ * Garbage collected XML node implementation
+ *//*
+ * Authors: see git history
+ *
+ * Copyright (C) 2018 Authors
+ * Copyright 2003-2005 MenTaLguY <mental@rydia.net>
+ * Copyright 2003 Nathan Hurst
+ * Copyright 1999-2003 Lauris Kaplinski
+ * Copyright 2000-2002 Ximian Inc.
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+// clang-format off
+#include "xml/node.h"
+#include "xml/simple-node.h"
+// clang-format on
+
+#include <algorithm>
+#include <cstring>
+#include <string>
+
+#include <glib.h>
+
+#include "preferences.h"
+
+#include "xml/node-event-vector.h"
+#include "xml/node-fns.h"
+#include "debug/event-tracker.h"
+#include "debug/simple-event.h"
+#include "util/format.h"
+
+#include "attribute-rel-util.h"
+
+namespace Inkscape {
+
+namespace XML {
+
+namespace {
+
+std::shared_ptr<std::string> stringify_node(Node const &node) {
+ gchar *string;
+ switch (node.type()) {
+ case NodeType::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 NodeType::TEXT_NODE:
+ string = g_strdup_printf("text(%p)=%s", &node, node.content());
+ break;
+ case NodeType::COMMENT_NODE:
+ string = g_strdup_printf("comment(%p)=<!--%s-->", &node, node.content());
+ break;
+ case NodeType::DOCUMENT_NODE:
+ string = g_strdup_printf("document(%p)", &node);
+ break;
+ default:
+ string = g_strdup_printf("unknown(%p)", &node);
+ }
+ std::shared_ptr<std::string> result = std::make_shared<std::string>(string);
+ g_free(string);
+ return result;
+}
+
+typedef Debug::SimpleEvent<Debug::Event::XML> DebugXML;
+
+class DebugXMLNode : public DebugXML {
+public:
+ DebugXMLNode(Node const &node, char const *name)
+ : DebugXML(name)
+ {
+ _addProperty("node", stringify_node(node));
+ }
+};
+
+class DebugAddChild : public DebugXMLNode {
+public:
+ DebugAddChild(Node const &node, Node const &child, Node const *prev)
+ : DebugXMLNode(node, "add-child")
+ {
+ _addProperty("child", stringify_node(child));
+ _addProperty("position", prev ? prev->position() + 1 : 0 );
+ }
+};
+
+class DebugRemoveChild : public DebugXMLNode {
+public:
+ DebugRemoveChild(Node const &node, Node const &child)
+ : DebugXMLNode(node, "remove-child")
+ {
+ _addProperty("child", stringify_node(child));
+ }
+};
+
+class DebugSetChildPosition : public DebugXMLNode {
+public:
+ DebugSetChildPosition(Node const &node, Node const &child,
+ Node const *old_prev, Node const *new_prev)
+ : DebugXMLNode(node, "set-child-position")
+ {
+ _addProperty("child", stringify_node(child));
+
+ unsigned old_position = ( old_prev ? old_prev->position() : 0 );
+ unsigned position = ( new_prev ? new_prev->position() : 0 );
+ if ( position > old_position ) {
+ --position;
+ }
+
+ _addProperty("position", position);
+ }
+};
+
+class DebugSetContent : public DebugXMLNode {
+public:
+ DebugSetContent(Node const &node,
+ Util::ptr_shared content)
+ : DebugXMLNode(node, "set-content")
+ {
+ _addProperty("content", content.pointer());
+ }
+};
+
+class DebugClearContent : public DebugXMLNode {
+public:
+ DebugClearContent(Node const &node)
+ : DebugXMLNode(node, "clear-content")
+ {}
+};
+
+class DebugSetAttribute : public DebugXMLNode {
+public:
+ DebugSetAttribute(Node const &node,
+ GQuark name,
+ Util::ptr_shared value)
+ : DebugXMLNode(node, "set-attribute")
+ {
+ _addProperty("name", g_quark_to_string(name));
+ _addProperty("value", value.pointer());
+ }
+};
+
+class DebugClearAttribute : public DebugXMLNode {
+public:
+ DebugClearAttribute(Node const &node, GQuark name)
+ : DebugXMLNode(node, "clear-attribute")
+ {
+ _addProperty("name", g_quark_to_string(name));
+ }
+};
+
+class DebugSetElementName : public DebugXMLNode {
+public:
+ DebugSetElementName(Node const& node, GQuark name)
+ : DebugXMLNode(node, "set-name")
+ {
+ _addProperty("name", g_quark_to_string(name));
+ }
+};
+
+}
+
+using Util::ptr_shared;
+using Util::share_string;
+using Util::share_unsafe;
+
+SimpleNode::SimpleNode(int code, Document *document)
+: Node(), _name(code), _attributes(), _child_count(0),
+ _cached_positions_valid(false)
+{
+ g_assert(document != nullptr);
+
+ this->_document = document;
+ this->_parent = this->_next = this->_prev = nullptr;
+ this->_first_child = this->_last_child = nullptr;
+
+ _observers.add(_subtree_observers);
+}
+
+SimpleNode::SimpleNode(SimpleNode const &node, Document *document)
+: 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)
+{
+ g_assert(document != nullptr);
+
+ _document = document;
+ _parent = _next = _prev = nullptr;
+ _first_child = _last_child = nullptr;
+
+ for ( SimpleNode *child = node._first_child ;
+ child != nullptr ; child = child->_next )
+ {
+ SimpleNode *child_copy=dynamic_cast<SimpleNode *>(child->duplicate(document));
+
+ child_copy->_setParent(this);
+ if (_last_child) { // not the first iteration
+ _last_child->_next = child_copy;
+ child_copy->_prev = _last_child;
+ } else {
+ _first_child = child_copy;
+ }
+ _last_child = child_copy;
+
+ child_copy->release(); // release to avoid a leak
+ }
+
+ _attributes = node._attributes;
+
+ _observers.add(_subtree_observers);
+}
+
+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 != nullptr, NULL);
+
+ GQuark const key = g_quark_from_string(name);
+
+ for (const auto & iter : _attributes)
+ {
+ if ( iter.key == key ) {
+ return iter.value;
+ }
+ }
+
+ return nullptr;
+}
+
+unsigned SimpleNode::position() const {
+ g_return_val_if_fail(_parent != nullptr, 0);
+ return _parent->_childPosition(*this);
+}
+
+unsigned SimpleNode::_childPosition(SimpleNode const &child) const {
+ if (!_cached_positions_valid) {
+ unsigned position=0;
+ for ( SimpleNode *sibling = _first_child ;
+ sibling ; sibling = sibling->_next )
+ {
+ sibling->_cached_position = position;
+ position++;
+ }
+ _cached_positions_valid = true;
+ }
+ return child._cached_position;
+}
+
+Node *SimpleNode::nthChild(unsigned index) {
+ SimpleNode *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 != nullptr, false);
+
+ for ( const auto & iter : _attributes )
+ {
+ gchar const *name = g_quark_to_string(iter.key);
+ if (std::strstr(name, partial_name)) {
+ return true;
+ }
+ }
+
+ return false;
+}
+
+void SimpleNode::_setParent(SimpleNode *parent) {
+ if (_parent) {
+ _subtree_observers.remove(_parent->_subtree_observers);
+ }
+ _parent = parent;
+ if (parent) {
+ _subtree_observers.add(parent->_subtree_observers);
+ }
+}
+
+void SimpleNode::setContent(gchar const *content) {
+ ptr_shared old_content=_content;
+ ptr_shared new_content = ( content ? share_string(content) : ptr_shared() );
+
+ Debug::EventTracker<> tracker;
+ if (new_content) {
+ tracker.set<DebugSetContent>(*this, new_content);
+ } else {
+ tracker.set<DebugClearContent>(*this);
+ }
+
+ _content = new_content;
+
+ if ( _content != old_content ) {
+ _document->logger()->notifyContentChanged(*this, old_content, _content);
+ _observers.notifyContentChanged(*this, old_content, _content);
+ }
+}
+
+void
+SimpleNode::setAttributeImpl(gchar const *name, gchar const *value)
+{
+ g_return_if_fail(name && *name);
+
+ // sanity check: `name` must not contain whitespace
+ g_assert(std::none_of(name, name + strlen(name), [](char c) { return g_ascii_isspace(c); }));
+
+ // Check usefulness of attributes on elements in the svg namespace, optionally don't add them to tree.
+ Glib::ustring element = g_quark_to_string(_name);
+ //g_message("setAttribute: %s: %s: %s", element.c_str(), name, value);
+ gchar* cleaned_value = g_strdup( value );
+
+ // Only check elements in SVG name space and don't block setting attribute to NULL.
+ if( element.substr(0,4) == "svg:" && value != nullptr) {
+
+ Inkscape::Preferences *prefs = Inkscape::Preferences::get();
+ if( prefs->getBool("/options/svgoutput/check_on_editing") ) {
+
+ gchar const *id_char = attribute("id");
+ Glib::ustring id = (id_char == nullptr ? "" : id_char );
+ unsigned int flags = sp_attribute_clean_get_prefs();
+ bool attr_warn = flags & SP_ATTRCLEAN_ATTR_WARN;
+ bool attr_remove = flags & SP_ATTRCLEAN_ATTR_REMOVE;
+
+ // Check attributes
+ if( (attr_warn || attr_remove) && value != nullptr ) {
+ bool is_useful = sp_attribute_check_attribute( element, id, name, attr_warn );
+ if( !is_useful && attr_remove ) {
+ g_free( cleaned_value );
+ return; // Don't add to tree.
+ }
+ }
+
+ // Check style properties -- Note: if element is not yet inserted into
+ // tree (and thus has no parent), default values will not be tested.
+ if( !strcmp( name, "style" ) && (flags >= SP_ATTRCLEAN_STYLE_WARN) ) {
+ g_free( cleaned_value );
+ cleaned_value = g_strdup( sp_attribute_clean_style( this, value, flags ).c_str() );
+ // if( g_strcmp0( value, cleaned_value ) ) {
+ // g_warning( "SimpleNode::setAttribute: %s", id.c_str() );
+ // g_warning( " original: %s", value);
+ // g_warning( " cleaned: %s", cleaned_value);
+ // }
+ }
+ }
+ }
+
+ GQuark const key = g_quark_from_string(name);
+
+ AttributeRecord *ref = nullptr;
+ for ( auto & existing : _attributes ) {
+ if ( existing.key == key ) {
+ ref = &existing;
+ break;
+ }
+ }
+ Debug::EventTracker<> tracker;
+
+ ptr_shared old_value=( ref ? ref->value : ptr_shared() );
+
+ ptr_shared new_value=ptr_shared();
+ if (cleaned_value) { // set value of attribute
+ new_value = share_string(cleaned_value);
+ tracker.set<DebugSetAttribute>(*this, key, new_value);
+ if (!ref) {
+ _attributes.emplace_back(key, new_value);
+ } else {
+ ref->value = new_value;
+ }
+ } else { //clearing attribute
+ tracker.set<DebugClearAttribute>(*this, key);
+ if (ref) {
+ _attributes.erase(std::find(_attributes.begin(),_attributes.end(),(*ref)));
+ }
+ }
+
+ if ( new_value != old_value && (!old_value || !new_value || strcmp(old_value, new_value))) {
+ _document->logger()->notifyAttributeChanged(*this, key, old_value, new_value);
+ _observers.notifyAttributeChanged(*this, key, old_value, new_value);
+ //g_warning( "setAttribute notified: %s: %s: %s: %s", name, element.c_str(), old_value, new_value );
+ }
+ g_free( cleaned_value );
+}
+
+void SimpleNode::setCodeUnsafe(int code) {
+ GQuark old_code = static_cast<GQuark>(_name);
+ GQuark new_code = static_cast<GQuark>(code);
+
+ Debug::EventTracker<> tracker;
+ tracker.set<DebugSetElementName>(*this, new_code);
+
+ _name = static_cast<int>(new_code);
+
+ if (new_code != old_code) {
+ _document->logger()->notifyElementNameChanged(*this, old_code, new_code);
+ _observers.notifyElementNameChanged(*this, old_code, new_code);
+ }
+}
+
+void SimpleNode::addChild(Node *generic_child, Node *generic_ref) {
+ g_assert(generic_child);
+ g_assert(generic_child->document() == _document);
+ g_assert(!generic_ref || generic_ref->document() == _document);
+
+ SimpleNode *child=dynamic_cast<SimpleNode *>(generic_child);
+ SimpleNode *ref=dynamic_cast<SimpleNode *>(generic_ref);
+
+ g_assert(!ref || ref->_parent == this);
+ g_assert(!child->_parent);
+
+ Debug::EventTracker<DebugAddChild> tracker(*this, *child, ref);
+
+ SimpleNode *next;
+ if (ref) {
+ next = ref->_next;
+ ref->_next = child;
+
+ child->_prev = ref;
+ } else {
+ if(_first_child) _first_child->_prev = child;
+ 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->_cached_position = 0;
+ _cached_positions_valid = true;
+ } else if (_cached_positions_valid) {
+ child->_cached_position = ref->_cached_position + 1;
+ }
+ } else {
+ next->_prev = child;
+ // invalidate cached positions otherwise
+ _cached_positions_valid = false;
+ }
+
+ child->_setParent(this);
+ child->_next = next;
+ _child_count++;
+
+ _document->logger()->notifyChildAdded(*this, *child, ref);
+ _observers.notifyChildAdded(*this, *child, ref);
+}
+
+void SimpleNode::removeChild(Node *generic_child) {
+ g_assert(generic_child);
+ g_assert(generic_child->document() == _document);
+
+ SimpleNode *child=dynamic_cast<SimpleNode *>(generic_child);
+ SimpleNode *ref=child->_prev;
+ SimpleNode *next = child->_next;
+
+ g_assert(child->_parent == this);
+
+ Debug::EventTracker<DebugRemoveChild> tracker(*this, *child);
+
+ if (ref) {
+ ref->_next = next;
+ } else {
+ _first_child = next;
+ }
+ if (next) { // removing the last child?
+ next->_prev = ref;
+ } else {
+ // removing any other child invalidates the cached positions
+ _last_child = ref;
+ _cached_positions_valid = false;
+ }
+
+ child->_next = nullptr;
+ child->_prev = nullptr;
+ child->_setParent(nullptr);
+ _child_count--;
+
+ _document->logger()->notifyChildRemoved(*this, *child, ref);
+ _observers.notifyChildRemoved(*this, *child, ref);
+}
+
+void SimpleNode::changeOrder(Node *generic_child, Node *generic_ref) {
+ g_assert(generic_child);
+ g_assert(generic_child->document() == this->_document);
+ g_assert(!generic_ref || generic_ref->document() == this->_document);
+
+ SimpleNode *const child=dynamic_cast<SimpleNode *>(generic_child);
+ SimpleNode *const ref=dynamic_cast<SimpleNode *>(generic_ref);
+
+ g_return_if_fail(child->parent() == this);
+ g_return_if_fail(child != ref);
+ g_return_if_fail(!ref || ref->parent() == this);
+
+ SimpleNode *const prev= child->_prev;
+
+ Debug::EventTracker<DebugSetChildPosition> tracker(*this, *child, prev, ref);
+
+ if (prev == ref) { return; }
+
+ SimpleNode *next;
+
+ /* Remove from old position. */
+ next = child->_next;
+ if (prev) {
+ prev->_next = next;
+ } else {
+ _first_child = next;
+ }
+ if (next) {
+ next->_prev = prev;
+ } else {
+ _last_child = prev;
+ }
+
+ /* Insert at new position. */
+ if (ref) {
+ next = ref->_next;
+ ref->_next = child;
+ } else {
+ next = _first_child;
+ _first_child = child;
+ }
+
+ child->_prev = ref;
+ child->_next = next;
+
+ if (next) {
+ next->_prev = child;
+ } else {
+ _last_child = child;
+ }
+
+ _cached_positions_valid = false;
+
+ _document->logger()->notifyChildOrderChanged(*this, *child, prev, ref);
+ _observers.notifyChildOrderChanged(*this, *child, prev, ref);
+}
+
+void SimpleNode::setPosition(int pos) {
+ g_return_if_fail(_parent != nullptr);
+
+ // 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
+
+ SimpleNode *ref=nullptr;
+ for ( SimpleNode *sibling = _parent->_first_child ;
+ sibling && pos ; sibling = sibling->_next )
+ {
+ if ( sibling != this ) {
+ ref = sibling;
+ pos--;
+ }
+ }
+
+ _parent->changeOrder(this, ref);
+}
+
+namespace {
+
+void child_added(Node *node, Node *child, Node *ref, void *data) {
+ reinterpret_cast<NodeObserver *>(data)->notifyChildAdded(*node, *child, ref);
+}
+
+void child_removed(Node *node, Node *child, Node *ref, void *data) {
+ reinterpret_cast<NodeObserver *>(data)->notifyChildRemoved(*node, *child, ref);
+}
+
+void content_changed(Node *node, gchar const *old_content, gchar const *new_content, void *data) {
+ reinterpret_cast<NodeObserver *>(data)->notifyContentChanged(*node, Util::share_unsafe((const char *)old_content), Util::share_unsafe((const char *)new_content));
+}
+
+void attr_changed(Node *node, gchar const *name, gchar const *old_value, gchar const *new_value, bool /*is_interactive*/, void *data) {
+ reinterpret_cast<NodeObserver *>(data)->notifyAttributeChanged(*node, g_quark_from_string(name), Util::share_unsafe((const char *)old_value), Util::share_unsafe((const char *)new_value));
+}
+
+void order_changed(Node *node, Node *child, Node *old_ref, Node *new_ref, void *data) {
+ reinterpret_cast<NodeObserver *>(data)->notifyChildOrderChanged(*node, *child, old_ref, new_ref);
+}
+
+const NodeEventVector OBSERVER_EVENT_VECTOR = {
+ &child_added,
+ &child_removed,
+ &attr_changed,
+ &content_changed,
+ &order_changed
+};
+
+};
+
+void SimpleNode::synthesizeEvents(NodeEventVector const *vector, void *data) {
+ if (vector->attr_changed) {
+ for ( const auto & iter : _attributes )
+ {
+ vector->attr_changed(this, g_quark_to_string(iter.key), nullptr, iter.value, false, data);
+ }
+ }
+ if (vector->child_added) {
+ SimpleNode *ref = nullptr;
+ for ( SimpleNode *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, nullptr, this->_content, data);
+ }
+}
+
+void SimpleNode::synthesizeEvents(NodeObserver &observer) {
+ synthesizeEvents(&OBSERVER_EVENT_VECTOR, &observer);
+}
+
+void SimpleNode::recursivePrintTree(unsigned level) {
+
+ if (level == 0) {
+ std::cout << "XML Node Tree" << std::endl;
+ }
+ std::cout << "XML: ";
+ for (unsigned i = 0; i < level; ++i) {
+ std::cout << " ";
+ }
+ char const *id=attribute("id");
+ if (id) {
+ std::cout << id << std::endl;
+ } else {
+ std::cout << name() << std::endl;
+ }
+ for (SimpleNode *child = _first_child; child != nullptr; child = child->_next) {
+ child->recursivePrintTree( level+1 );
+ }
+}
+
+Node *SimpleNode::root() {
+ Node *parent=this;
+ while (parent->parent()) {
+ parent = parent->parent();
+ }
+
+ if ( parent->type() == NodeType::DOCUMENT_NODE ) {
+ for ( Node *child = _document->firstChild() ;
+ child ; child = child->next() )
+ {
+ if ( child->type() == NodeType::ELEMENT_NODE ) {
+ return child;
+ }
+ }
+ return nullptr;
+ } else if ( parent->type() == NodeType::ELEMENT_NODE ) {
+ return parent;
+ } else {
+ return nullptr;
+ }
+}
+
+void SimpleNode::cleanOriginal(Node *src, gchar const *key){
+ std::vector<Node *> to_delete;
+ for ( Node *child = this->firstChild() ; child != nullptr ; child = child->next() )
+ {
+ gchar const *id = child->attribute(key);
+ if (id) {
+ Node *rch = sp_repr_lookup_child(src, key, id);
+ if (rch) {
+ child->cleanOriginal(rch, key);
+ } else {
+ to_delete.push_back(child);
+ }
+ } else {
+ to_delete.push_back(child);
+ }
+ }
+ for (auto & i : to_delete) {
+ removeChild(i);
+ }
+}
+
+bool SimpleNode::equal(Node const *other, bool recursive) {
+ if(strcmp(name(),other->name())!= 0){
+ return false;
+ }
+ if (!(strcmp("sodipodi:namedview", name()))) {
+ return true;
+ }
+ guint orig_length = 0;
+ guint other_length = 0;
+
+ if(content() && other->content() && strcmp(content(), other->content()) != 0){
+ return false;
+ }
+ for (auto orig_attr : attributeList()) {
+ for (auto other_attr : other->attributeList()) {
+ const gchar * key_orig = g_quark_to_string(orig_attr.key);
+ const gchar * key_other = g_quark_to_string(other_attr.key);
+ if (!strcmp(key_orig, key_other) &&
+ !strcmp(orig_attr.value, other_attr.value))
+ {
+ other_length++;
+ break;
+ }
+ }
+ orig_length++;
+ }
+ if (orig_length != other_length) {
+ return false;
+ }
+ if (recursive) {
+ //NOTE: for faster the childs need to be in the same order
+ Node const *other_child = other->firstChild();
+ for ( Node *child = firstChild();
+ child;
+ child = child->next())
+ {
+ if (!child->equal(other_child, recursive)) {
+ return false;
+ }
+ other_child = other_child->next();
+ if(!other_child) {
+ return false;
+ }
+ }
+ }
+ return true;
+}
+
+void SimpleNode::mergeFrom(Node const *src, gchar const *key, bool extension, bool clean) {
+ g_return_if_fail(src != nullptr);
+ g_return_if_fail(key != nullptr);
+ g_assert(src != this);
+
+ setContent(src->content());
+ if(_parent) {
+ setPosition(src->position());
+ }
+
+ if (clean) {
+ Node * srcp = const_cast<Node *>(src);
+ cleanOriginal(srcp, key);
+ }
+
+ for ( Node const *child = src->firstChild() ; child != nullptr ; child = child->next() )
+ {
+ gchar const *id = child->attribute(key);
+ if (id) {
+ Node *rch=sp_repr_lookup_child(this, key, id);
+ if (rch && (!extension || rch->equal(child, false))) {
+ rch->mergeFrom(child, key, extension);
+ continue;
+ } else {
+ if(rch) {
+ removeChild(rch);
+ }
+ }
+ }
+ {
+ guint pos = child->position();
+ Node *rch=child->duplicate(_document);
+ addChildAtPos(rch, pos);
+ rch->release();
+ }
+ }
+
+ for ( const auto & iter : src->attributeList() )
+ {
+ 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:fileencoding=utf-8:textwidth=99 :