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