1 // SPDX-License-Identifier: GPL-2.0-or-later
2 /** @file
3  *  Garbage collected XML node implementation
4  *//*
5  * Authors: see git history
6  *
7  * Copyright (C) 2018 Authors
8  * Copyright 2003-2005 MenTaLguY <mental@rydia.net>
9  * Copyright 2003 Nathan Hurst
10  * Copyright 1999-2003 Lauris Kaplinski
11  * Copyright 2000-2002 Ximian Inc.
12  *
13  * Released under GNU GPL v2+, read the file 'COPYING' for more information.
14  */
15 
16 // clang-format off
17 #include "xml/node.h"
18 #include "xml/simple-node.h"
19 // clang-format on
20 
21 #include <algorithm>
22 #include <cstring>
23 #include <string>
24 
25 #include <glib.h>
26 
27 #include "preferences.h"
28 
29 #include "xml/node-event-vector.h"
30 #include "xml/node-fns.h"
31 #include "debug/event-tracker.h"
32 #include "debug/simple-event.h"
33 #include "util/format.h"
34 
35 #include "attribute-rel-util.h"
36 
37 namespace Inkscape {
38 
39 namespace XML {
40 
41 namespace {
42 
stringify_node(Node const & node)43 std::shared_ptr<std::string> stringify_node(Node const &node) {
44     gchar *string;
45     switch (node.type()) {
46     case NodeType::ELEMENT_NODE: {
47         char const *id=node.attribute("id");
48         if (id) {
49             string = g_strdup_printf("element(%p)=%s(#%s)", &node, node.name(), id);
50         } else {
51             string = g_strdup_printf("element(%p)=%s", &node, node.name());
52         }
53     } break;
54     case NodeType::TEXT_NODE:
55         string = g_strdup_printf("text(%p)=%s", &node, node.content());
56         break;
57     case NodeType::COMMENT_NODE:
58         string = g_strdup_printf("comment(%p)=<!--%s-->", &node, node.content());
59         break;
60     case NodeType::DOCUMENT_NODE:
61         string = g_strdup_printf("document(%p)", &node);
62         break;
63     default:
64         string = g_strdup_printf("unknown(%p)", &node);
65     }
66     std::shared_ptr<std::string> result = std::make_shared<std::string>(string);
67     g_free(string);
68     return result;
69 }
70 
71 typedef Debug::SimpleEvent<Debug::Event::XML> DebugXML;
72 
73 class DebugXMLNode : public DebugXML {
74 public:
DebugXMLNode(Node const & node,char const * name)75     DebugXMLNode(Node const &node, char const *name)
76     : DebugXML(name)
77     {
78         _addProperty("node", stringify_node(node));
79     }
80 };
81 
82 class DebugAddChild : public DebugXMLNode {
83 public:
DebugAddChild(Node const & node,Node const & child,Node const * prev)84     DebugAddChild(Node const &node, Node const &child, Node const *prev)
85     : DebugXMLNode(node, "add-child")
86     {
87         _addProperty("child", stringify_node(child));
88         _addProperty("position", prev ? prev->position() + 1 : 0 );
89     }
90 };
91 
92 class DebugRemoveChild : public DebugXMLNode {
93 public:
DebugRemoveChild(Node const & node,Node const & child)94     DebugRemoveChild(Node const &node, Node const &child)
95     : DebugXMLNode(node, "remove-child")
96     {
97         _addProperty("child", stringify_node(child));
98     }
99 };
100 
101 class DebugSetChildPosition : public DebugXMLNode {
102 public:
DebugSetChildPosition(Node const & node,Node const & child,Node const * old_prev,Node const * new_prev)103     DebugSetChildPosition(Node const &node, Node const &child,
104                           Node const *old_prev, Node const *new_prev)
105     : DebugXMLNode(node, "set-child-position")
106     {
107         _addProperty("child", stringify_node(child));
108 
109         unsigned old_position = ( old_prev ? old_prev->position() : 0 );
110         unsigned position = ( new_prev ? new_prev->position() : 0 );
111         if ( position > old_position ) {
112             --position;
113         }
114 
115         _addProperty("position", position);
116     }
117 };
118 
119 class DebugSetContent : public DebugXMLNode {
120 public:
DebugSetContent(Node const & node,Util::ptr_shared content)121     DebugSetContent(Node const &node,
122                     Util::ptr_shared content)
123     : DebugXMLNode(node, "set-content")
124     {
125         _addProperty("content", content.pointer());
126     }
127 };
128 
129 class DebugClearContent : public DebugXMLNode {
130 public:
DebugClearContent(Node const & node)131     DebugClearContent(Node const &node)
132     : DebugXMLNode(node, "clear-content")
133     {}
134 };
135 
136 class DebugSetAttribute : public DebugXMLNode {
137 public:
DebugSetAttribute(Node const & node,GQuark name,Util::ptr_shared value)138     DebugSetAttribute(Node const &node,
139                       GQuark name,
140                       Util::ptr_shared value)
141     : DebugXMLNode(node, "set-attribute")
142     {
143         _addProperty("name", g_quark_to_string(name));
144         _addProperty("value", value.pointer());
145     }
146 };
147 
148 class DebugClearAttribute : public DebugXMLNode {
149 public:
DebugClearAttribute(Node const & node,GQuark name)150     DebugClearAttribute(Node const &node, GQuark name)
151     : DebugXMLNode(node, "clear-attribute")
152     {
153         _addProperty("name", g_quark_to_string(name));
154     }
155 };
156 
157 class DebugSetElementName : public DebugXMLNode {
158 public:
DebugSetElementName(Node const & node,GQuark name)159     DebugSetElementName(Node const& node, GQuark name)
160     : DebugXMLNode(node, "set-name")
161     {
162         _addProperty("name", g_quark_to_string(name));
163     }
164 };
165 
166 }
167 
168 using Util::ptr_shared;
169 using Util::share_string;
170 using Util::share_unsafe;
171 
SimpleNode(int code,Document * document)172 SimpleNode::SimpleNode(int code, Document *document)
173 : Node(), _name(code), _attributes(), _child_count(0),
174   _cached_positions_valid(false)
175 {
176     g_assert(document != nullptr);
177 
178     this->_document = document;
179     this->_parent = this->_next = this->_prev = nullptr;
180     this->_first_child = this->_last_child = nullptr;
181 
182     _observers.add(_subtree_observers);
183 }
184 
SimpleNode(SimpleNode const & node,Document * document)185 SimpleNode::SimpleNode(SimpleNode const &node, Document *document)
186 : Node(),
187   _cached_position(node._cached_position),
188   _name(node._name), _attributes(), _content(node._content),
189   _child_count(node._child_count),
190   _cached_positions_valid(node._cached_positions_valid)
191 {
192     g_assert(document != nullptr);
193 
194     _document = document;
195     _parent = _next = _prev = nullptr;
196     _first_child = _last_child = nullptr;
197 
198     for ( SimpleNode *child = node._first_child ;
199           child != nullptr ; child = child->_next )
200     {
201         SimpleNode *child_copy=dynamic_cast<SimpleNode *>(child->duplicate(document));
202 
203         child_copy->_setParent(this);
204         if (_last_child) { // not the first iteration
205             _last_child->_next = child_copy;
206             child_copy->_prev = _last_child;
207         } else {
208             _first_child = child_copy;
209         }
210         _last_child = child_copy;
211 
212         child_copy->release(); // release to avoid a leak
213     }
214 
215     _attributes = node._attributes;
216 
217     _observers.add(_subtree_observers);
218 }
219 
name() const220 gchar const *SimpleNode::name() const {
221     return g_quark_to_string(_name);
222 }
223 
content() const224 gchar const *SimpleNode::content() const {
225     return this->_content;
226 }
227 
attribute(gchar const * name) const228 gchar const *SimpleNode::attribute(gchar const *name) const {
229     g_return_val_if_fail(name != nullptr, NULL);
230 
231     GQuark const key = g_quark_from_string(name);
232 
233     for (const auto & iter : _attributes)
234     {
235         if ( iter.key == key ) {
236             return iter.value;
237         }
238     }
239 
240     return nullptr;
241 }
242 
position() const243 unsigned SimpleNode::position() const {
244     g_return_val_if_fail(_parent != nullptr, 0);
245     return _parent->_childPosition(*this);
246 }
247 
_childPosition(SimpleNode const & child) const248 unsigned SimpleNode::_childPosition(SimpleNode const &child) const {
249     if (!_cached_positions_valid) {
250         unsigned position=0;
251         for ( SimpleNode *sibling = _first_child ;
252               sibling ; sibling = sibling->_next )
253         {
254             sibling->_cached_position = position;
255             position++;
256         }
257         _cached_positions_valid = true;
258     }
259     return child._cached_position;
260 }
261 
nthChild(unsigned index)262 Node *SimpleNode::nthChild(unsigned index) {
263     SimpleNode *child = _first_child;
264     for ( ; index > 0 && child ; child = child->_next ) {
265         index--;
266     }
267     return child;
268 }
269 
matchAttributeName(gchar const * partial_name) const270 bool SimpleNode::matchAttributeName(gchar const *partial_name) const {
271     g_return_val_if_fail(partial_name != nullptr, false);
272 
273     for ( const auto & iter : _attributes )
274     {
275         gchar const *name = g_quark_to_string(iter.key);
276         if (std::strstr(name, partial_name)) {
277             return true;
278         }
279     }
280 
281     return false;
282 }
283 
_setParent(SimpleNode * parent)284 void SimpleNode::_setParent(SimpleNode *parent) {
285     if (_parent) {
286         _subtree_observers.remove(_parent->_subtree_observers);
287     }
288     _parent = parent;
289     if (parent) {
290         _subtree_observers.add(parent->_subtree_observers);
291     }
292 }
293 
setContent(gchar const * content)294 void SimpleNode::setContent(gchar const *content) {
295     ptr_shared old_content=_content;
296     ptr_shared new_content = ( content ? share_string(content) : ptr_shared() );
297 
298     Debug::EventTracker<> tracker;
299     if (new_content) {
300         tracker.set<DebugSetContent>(*this, new_content);
301     } else {
302         tracker.set<DebugClearContent>(*this);
303     }
304 
305     _content = new_content;
306 
307     if ( _content != old_content ) {
308         _document->logger()->notifyContentChanged(*this, old_content, _content);
309         _observers.notifyContentChanged(*this, old_content, _content);
310     }
311 }
312 
313 void
setAttributeImpl(gchar const * name,gchar const * value)314 SimpleNode::setAttributeImpl(gchar const *name, gchar const *value)
315 {
316     g_return_if_fail(name && *name);
317 
318     // sanity check: `name` must not contain whitespace
319     g_assert(std::none_of(name, name + strlen(name), [](char c) { return g_ascii_isspace(c); }));
320 
321     // Check usefulness of attributes on elements in the svg namespace, optionally don't add them to tree.
322     Glib::ustring element = g_quark_to_string(_name);
323     //g_message("setAttribute:  %s: %s: %s", element.c_str(), name, value);
324     gchar* cleaned_value = g_strdup( value );
325 
326     // Only check elements in SVG name space and don't block setting attribute to NULL.
327     if( element.substr(0,4) == "svg:" && value != nullptr) {
328 
329         Inkscape::Preferences *prefs = Inkscape::Preferences::get();
330         if( prefs->getBool("/options/svgoutput/check_on_editing") ) {
331 
332             gchar const *id_char = attribute("id");
333             Glib::ustring id = (id_char == nullptr ? "" : id_char );
334             unsigned int flags = sp_attribute_clean_get_prefs();
335             bool attr_warn   = flags & SP_ATTRCLEAN_ATTR_WARN;
336             bool attr_remove = flags & SP_ATTRCLEAN_ATTR_REMOVE;
337 
338             // Check attributes
339             if( (attr_warn || attr_remove) && value != nullptr ) {
340                 bool is_useful = sp_attribute_check_attribute( element, id, name, attr_warn );
341                 if( !is_useful && attr_remove ) {
342                     g_free( cleaned_value );
343                     return; // Don't add to tree.
344                 }
345             }
346 
347             // Check style properties -- Note: if element is not yet inserted into
348             // tree (and thus has no parent), default values will not be tested.
349             if( !strcmp( name, "style" ) && (flags >= SP_ATTRCLEAN_STYLE_WARN) ) {
350                 g_free( cleaned_value );
351                 cleaned_value = g_strdup( sp_attribute_clean_style( this, value, flags ).c_str() );
352                 // if( g_strcmp0( value, cleaned_value ) ) {
353                 //     g_warning( "SimpleNode::setAttribute: %s", id.c_str() );
354                 //     g_warning( "     original: %s", value);
355                 //     g_warning( "      cleaned: %s", cleaned_value);
356                 // }
357             }
358         }
359     }
360 
361     GQuark const key = g_quark_from_string(name);
362 
363     AttributeRecord *ref = nullptr;
364     for ( auto & existing : _attributes ) {
365         if ( existing.key == key ) {
366             ref = &existing;
367             break;
368         }
369     }
370     Debug::EventTracker<> tracker;
371 
372     ptr_shared old_value=( ref ? ref->value : ptr_shared() );
373 
374     ptr_shared new_value=ptr_shared();
375     if (cleaned_value) { // set value of attribute
376         new_value = share_string(cleaned_value);
377         tracker.set<DebugSetAttribute>(*this, key, new_value);
378         if (!ref) {
379 	    _attributes.emplace_back(key, new_value);
380         } else {
381             ref->value = new_value;
382         }
383     } else { //clearing attribute
384         tracker.set<DebugClearAttribute>(*this, key);
385         if (ref) {
386 	    _attributes.erase(std::find(_attributes.begin(),_attributes.end(),(*ref)));
387         }
388     }
389 
390     if ( new_value != old_value && (!old_value || !new_value || strcmp(old_value, new_value))) {
391         _document->logger()->notifyAttributeChanged(*this, key, old_value, new_value);
392         _observers.notifyAttributeChanged(*this, key, old_value, new_value);
393         //g_warning( "setAttribute notified: %s: %s: %s: %s", name, element.c_str(), old_value, new_value );
394     }
395     g_free( cleaned_value );
396 }
397 
setCodeUnsafe(int code)398 void SimpleNode::setCodeUnsafe(int code) {
399     GQuark old_code = static_cast<GQuark>(_name);
400     GQuark new_code = static_cast<GQuark>(code);
401 
402     Debug::EventTracker<> tracker;
403     tracker.set<DebugSetElementName>(*this, new_code);
404 
405     _name = static_cast<int>(new_code);
406 
407     if (new_code != old_code) {
408         _document->logger()->notifyElementNameChanged(*this, old_code, new_code);
409         _observers.notifyElementNameChanged(*this, old_code, new_code);
410     }
411 }
412 
addChild(Node * generic_child,Node * generic_ref)413 void SimpleNode::addChild(Node *generic_child, Node *generic_ref) {
414     g_assert(generic_child);
415     g_assert(generic_child->document() == _document);
416     g_assert(!generic_ref || generic_ref->document() == _document);
417 
418     SimpleNode *child=dynamic_cast<SimpleNode *>(generic_child);
419     SimpleNode *ref=dynamic_cast<SimpleNode *>(generic_ref);
420 
421     g_assert(!ref || ref->_parent == this);
422     g_assert(!child->_parent);
423 
424     Debug::EventTracker<DebugAddChild> tracker(*this, *child, ref);
425 
426     SimpleNode *next;
427     if (ref) {
428         next = ref->_next;
429         ref->_next = child;
430 
431         child->_prev = ref;
432     } else {
433         if(_first_child) _first_child->_prev = child;
434         next = _first_child;
435         _first_child = child;
436     }
437 
438     if (!next) { // appending?
439         _last_child = child;
440         // set cached position if possible when appending
441         if (!ref) {
442             // if !next && !ref, child is sole child
443             child->_cached_position = 0;
444             _cached_positions_valid = true;
445         } else if (_cached_positions_valid) {
446             child->_cached_position = ref->_cached_position + 1;
447         }
448     } else {
449         next->_prev = child;
450         // invalidate cached positions otherwise
451         _cached_positions_valid = false;
452     }
453 
454     child->_setParent(this);
455     child->_next = next;
456     _child_count++;
457 
458     _document->logger()->notifyChildAdded(*this, *child, ref);
459     _observers.notifyChildAdded(*this, *child, ref);
460 }
461 
removeChild(Node * generic_child)462 void SimpleNode::removeChild(Node *generic_child) {
463     g_assert(generic_child);
464     g_assert(generic_child->document() == _document);
465 
466     SimpleNode *child=dynamic_cast<SimpleNode *>(generic_child);
467     SimpleNode *ref=child->_prev;
468     SimpleNode *next = child->_next;
469 
470     g_assert(child->_parent == this);
471 
472     Debug::EventTracker<DebugRemoveChild> tracker(*this, *child);
473 
474     if (ref) {
475         ref->_next = next;
476     } else {
477         _first_child = next;
478     }
479     if (next) { // removing the last child?
480         next->_prev = ref;
481     } else {
482         // removing any other child invalidates the cached positions
483         _last_child = ref;
484         _cached_positions_valid = false;
485     }
486 
487     child->_next = nullptr;
488     child->_prev = nullptr;
489     child->_setParent(nullptr);
490     _child_count--;
491 
492     _document->logger()->notifyChildRemoved(*this, *child, ref);
493     _observers.notifyChildRemoved(*this, *child, ref);
494 }
495 
changeOrder(Node * generic_child,Node * generic_ref)496 void SimpleNode::changeOrder(Node *generic_child, Node *generic_ref) {
497     g_assert(generic_child);
498     g_assert(generic_child->document() == this->_document);
499     g_assert(!generic_ref || generic_ref->document() == this->_document);
500 
501     SimpleNode *const child=dynamic_cast<SimpleNode *>(generic_child);
502     SimpleNode *const ref=dynamic_cast<SimpleNode *>(generic_ref);
503 
504     g_return_if_fail(child->parent() == this);
505     g_return_if_fail(child != ref);
506     g_return_if_fail(!ref || ref->parent() == this);
507 
508     SimpleNode *const prev= child->_prev;
509 
510     Debug::EventTracker<DebugSetChildPosition> tracker(*this, *child, prev, ref);
511 
512     if (prev == ref) { return; }
513 
514     SimpleNode *next;
515 
516     /* Remove from old position. */
517     next = child->_next;
518     if (prev) {
519         prev->_next = next;
520     } else {
521         _first_child = next;
522     }
523     if (next) {
524         next->_prev = prev;
525     } else {
526         _last_child = prev;
527     }
528 
529     /* Insert at new position. */
530     if (ref) {
531         next = ref->_next;
532         ref->_next = child;
533     } else {
534         next = _first_child;
535         _first_child = child;
536     }
537 
538     child->_prev = ref;
539     child->_next = next;
540 
541     if (next) {
542         next->_prev = child;
543     } else {
544         _last_child = child;
545     }
546 
547     _cached_positions_valid = false;
548 
549     _document->logger()->notifyChildOrderChanged(*this, *child, prev, ref);
550     _observers.notifyChildOrderChanged(*this, *child, prev, ref);
551 }
552 
setPosition(int pos)553 void SimpleNode::setPosition(int pos) {
554     g_return_if_fail(_parent != nullptr);
555 
556     // a position beyond the end of the list means the end of the list;
557     // a negative position is the same as an infinitely large position
558 
559     SimpleNode *ref=nullptr;
560     for ( SimpleNode *sibling = _parent->_first_child ;
561           sibling && pos ; sibling = sibling->_next )
562     {
563         if ( sibling != this ) {
564             ref = sibling;
565             pos--;
566         }
567     }
568 
569     _parent->changeOrder(this, ref);
570 }
571 
572 namespace {
573 
child_added(Node * node,Node * child,Node * ref,void * data)574 void child_added(Node *node, Node *child, Node *ref, void *data) {
575     reinterpret_cast<NodeObserver *>(data)->notifyChildAdded(*node, *child, ref);
576 }
577 
child_removed(Node * node,Node * child,Node * ref,void * data)578 void child_removed(Node *node, Node *child, Node *ref, void *data) {
579     reinterpret_cast<NodeObserver *>(data)->notifyChildRemoved(*node, *child, ref);
580 }
581 
content_changed(Node * node,gchar const * old_content,gchar const * new_content,void * data)582 void content_changed(Node *node, gchar const *old_content, gchar const *new_content, void *data) {
583     reinterpret_cast<NodeObserver *>(data)->notifyContentChanged(*node, Util::share_unsafe((const char *)old_content), Util::share_unsafe((const char *)new_content));
584 }
585 
attr_changed(Node * node,gchar const * name,gchar const * old_value,gchar const * new_value,bool,void * data)586 void attr_changed(Node *node, gchar const *name, gchar const *old_value, gchar const *new_value, bool /*is_interactive*/, void *data) {
587     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));
588 }
589 
order_changed(Node * node,Node * child,Node * old_ref,Node * new_ref,void * data)590 void order_changed(Node *node, Node *child, Node *old_ref, Node *new_ref, void *data) {
591     reinterpret_cast<NodeObserver *>(data)->notifyChildOrderChanged(*node, *child, old_ref, new_ref);
592 }
593 
594 const NodeEventVector OBSERVER_EVENT_VECTOR = {
595     &child_added,
596     &child_removed,
597     &attr_changed,
598     &content_changed,
599     &order_changed
600 };
601 
602 };
603 
synthesizeEvents(NodeEventVector const * vector,void * data)604 void SimpleNode::synthesizeEvents(NodeEventVector const *vector, void *data) {
605     if (vector->attr_changed) {
606         for ( const auto & iter : _attributes )
607         {
608             vector->attr_changed(this, g_quark_to_string(iter.key), nullptr, iter.value, false, data);
609         }
610     }
611     if (vector->child_added) {
612         SimpleNode *ref = nullptr;
613         for ( SimpleNode *child = this->_first_child ;
614               child ; child = child->_next )
615         {
616             vector->child_added(this, child, ref, data);
617             ref = child;
618         }
619     }
620     if (vector->content_changed) {
621         vector->content_changed(this, nullptr, this->_content, data);
622     }
623 }
624 
synthesizeEvents(NodeObserver & observer)625 void SimpleNode::synthesizeEvents(NodeObserver &observer) {
626     synthesizeEvents(&OBSERVER_EVENT_VECTOR, &observer);
627 }
628 
recursivePrintTree(unsigned level)629 void SimpleNode::recursivePrintTree(unsigned level) {
630 
631     if (level == 0) {
632         std::cout << "XML Node Tree" << std::endl;
633     }
634     std::cout << "XML: ";
635     for (unsigned i = 0; i < level; ++i) {
636         std::cout << "  ";
637     }
638     char const *id=attribute("id");
639     if (id) {
640         std::cout << id << std::endl;
641     } else {
642         std::cout << name() << std::endl;
643     }
644     for (SimpleNode *child = _first_child; child != nullptr; child = child->_next) {
645         child->recursivePrintTree( level+1 );
646     }
647 }
648 
root()649 Node *SimpleNode::root() {
650     Node *parent=this;
651     while (parent->parent()) {
652         parent = parent->parent();
653     }
654 
655     if ( parent->type() == NodeType::DOCUMENT_NODE ) {
656         for ( Node *child = _document->firstChild() ;
657               child ; child = child->next() )
658         {
659             if ( child->type() == NodeType::ELEMENT_NODE ) {
660                 return child;
661             }
662         }
663         return nullptr;
664     } else if ( parent->type() == NodeType::ELEMENT_NODE ) {
665         return parent;
666     } else {
667         return nullptr;
668     }
669 }
670 
cleanOriginal(Node * src,gchar const * key)671 void SimpleNode::cleanOriginal(Node *src, gchar const *key){
672     std::vector<Node *> to_delete;
673     for ( Node *child = this->firstChild() ; child != nullptr ; child = child->next() )
674     {
675         gchar const *id = child->attribute(key);
676         if (id) {
677             Node *rch = sp_repr_lookup_child(src, key, id);
678             if (rch) {
679                 child->cleanOriginal(rch, key);
680             } else {
681                 to_delete.push_back(child);
682             }
683         } else {
684             to_delete.push_back(child);
685         }
686     }
687     for (auto & i : to_delete) {
688         removeChild(i);
689     }
690 }
691 
equal(Node const * other,bool recursive)692 bool SimpleNode::equal(Node const *other, bool recursive) {
693     if(strcmp(name(),other->name())!= 0){
694         return false;
695     }
696     if (!(strcmp("sodipodi:namedview", name()))) {
697         return true;
698     }
699     guint orig_length = 0;
700     guint other_length = 0;
701 
702     if(content() && other->content() && strcmp(content(), other->content()) != 0){
703         return false;
704     }
705     for (auto orig_attr : attributeList()) {
706         for (auto other_attr : other->attributeList()) {
707             const gchar * key_orig = g_quark_to_string(orig_attr.key);
708             const gchar * key_other = g_quark_to_string(other_attr.key);
709             if (!strcmp(key_orig, key_other) &&
710                 !strcmp(orig_attr.value, other_attr.value))
711             {
712                 other_length++;
713                 break;
714             }
715         }
716         orig_length++;
717     }
718     if (orig_length != other_length) {
719         return false;
720     }
721     if (recursive) {
722         //NOTE: for faster the childs need to be in the same order
723         Node const *other_child = other->firstChild();
724         for ( Node *child = firstChild();
725               child;
726               child = child->next())
727         {
728             if (!child->equal(other_child, recursive)) {
729                 return false;
730             }
731             other_child = other_child->next();
732             if(!other_child) {
733                 return false;
734             }
735         }
736     }
737     return true;
738 }
739 
mergeFrom(Node const * src,gchar const * key,bool extension,bool clean)740 void SimpleNode::mergeFrom(Node const *src, gchar const *key, bool extension, bool clean) {
741     g_return_if_fail(src != nullptr);
742     g_return_if_fail(key != nullptr);
743     g_assert(src != this);
744 
745     setContent(src->content());
746     if(_parent) {
747         setPosition(src->position());
748     }
749 
750     if (clean) {
751         Node * srcp = const_cast<Node *>(src);
752         cleanOriginal(srcp, key);
753     }
754 
755     for ( Node const *child = src->firstChild() ; child != nullptr ; child = child->next() )
756     {
757         gchar const *id = child->attribute(key);
758         if (id) {
759             Node *rch=sp_repr_lookup_child(this, key, id);
760             if (rch && (!extension || rch->equal(child, false))) {
761                 rch->mergeFrom(child, key, extension);
762                 continue;
763             } else {
764                 if(rch) {
765                     removeChild(rch);
766                 }
767             }
768         }
769         {
770             guint pos = child->position();
771             Node *rch=child->duplicate(_document);
772             addChildAtPos(rch, pos);
773             rch->release();
774         }
775     }
776 
777     for ( const auto & iter : src->attributeList() )
778     {
779         setAttribute(g_quark_to_string(iter.key), iter.value);
780     }
781 }
782 
783 }
784 
785 }
786 
787 /*
788   Local Variables:
789   mode:c++
790   c-file-style:"stroustrup"
791   c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
792   indent-tabs-mode:nil
793   fill-column:99
794   End:
795 */
796 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 :
797