summaryrefslogtreecommitdiffstats
path: root/src/xml/node.h
blob: 4fcf177e67ef232ccd0052019e750bc4b34e7932 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
// SPDX-License-Identifier: GPL-2.0-or-later
/** @file
 * @brief Interface for XML nodes
 *
 * Authors:
 *   MenTaLguY <mental@rydia.net>
 *   Krzysztof Kosiński <tweenk.pl@gmail.com> (documentation)
 *
 * Copyright (C) 2018 Authors
 * Released under GNU GPL v2+, read the file 'COPYING' for more information.
 */

#ifndef SEEN_INKSCAPE_XML_NODE_H
#define SEEN_INKSCAPE_XML_NODE_H

#include <cassert>
#include <vector>
#include <list>
#include <2geom/point.h>

#include "gc-anchored.h"
#include "inkgc/gc-alloc.h"
#include "node-iterators.h"
#include "util/const_char_ptr.h"
#include "svg/svg-length.h"

namespace Inkscape {
namespace XML {

class AttributeRecord;
struct Document;
class Event;
class NodeObserver;
struct NodeEventVector;

typedef std::vector<AttributeRecord, Inkscape::GC::Alloc<AttributeRecord, Inkscape::GC::MANUAL>> AttributeVector;

/**
 * @brief Enumeration containing all supported node types.
 */
enum class NodeType
{
    DOCUMENT_NODE, ///< Top-level document node. Do not confuse with the root node.
    ELEMENT_NODE,  ///< Regular element node, e.g. &lt;group /&gt;.
    TEXT_NODE, ///< Text node, e.g. "Some text" in &lt;group&gt;Some text&lt;/group&gt; is represented by a text node.
    COMMENT_NODE, ///< Comment node, e.g. &lt;!-- some comment --&gt;
    PI_NODE       ///< Processing instruction node, e.g. &lt;?xml version="1.0" encoding="utf-8" standalone="no"?&gt;
};

// careful; GC::Anchored should only appear once in the inheritance
// hierarchy; otherwise there will be leaks

/**
 * @brief Interface for refcounted XML nodes
 *
 * This class is an abstract base type for all nodes in an XML document - this includes
 * everything except attributes. An XML document is also a node itself. This is the main
 * class used for interfacing with Inkscape's documents. Everything that has to be stored
 * in the SVG has to go through this class at some point.
 *
 * Each node unconditionally has to belong to a document. There are no "documentless" nodes,
 * and it's not possible to move nodes between documents - they have to be duplicated.
 * Each node can only refer to the nodes in the same document. Name of the node is immutable,
 * it cannot be changed after its creation. Same goes for the type of the node. To simplify
 * the use of this class, you can perform all operations on all nodes, but only some of them
 * make any sense. For example, only element nodes can have attributes, only element and
 * document nodes can have children, and all nodes except element and document nodes can
 * have content. Although you can set content for element nodes, it won't make any difference
 * in the XML output.
 *
 * To create new nodes, use the methods of the Inkscape::XML::Document class. You can obtain
 * the nodes' document using the document() method. To destroy a node, just unparent it
 * by calling sp_repr_unparent() or node->parent->removeChild() and release any references
 * to it. The garbage collector will reclaim the memory in the next pass.
 *
 * In addition to regular DOM manipulations, you can register observer objects that will
 * receive notifications about changes made to the node. See the NodeObserver class.
 *
 * @see Inkscape::XML::Document
 * @see Inkscape::XML::NodeObserver
 */
class Node : public Inkscape::GC::Anchored {
public:
    Node() = default;
    ~Node() override = default;

    /**
     * @name Retrieve information about the node
     * @{
     */

    /**
     * @brief Get the type of the node
     * @return NodeType enumeration member corresponding to the type of the node.
     */
    virtual NodeType type() const = 0;

    /**
     * @brief Get the name of the element node
     *
     * This method only makes sense for element nodes. Names are stored as
     * GQuarks to accelerate conversions.
     *
     * @return Name for element nodes, NULL for others
     */
    virtual char const *name() const = 0;
    /**
     * @brief Get the integer code corresponding to the node's name
     * @return GQuark code corresponding to the name
     */
    virtual int code() const = 0;

    /**
     * @brief Get the index of this node in parent's child order
     *
     * If this method is used on a node that doesn't have a parent, the method will return 0,
     * and a warning will be printed on the console.
     *
     * @return The node's index, or 0 if the node does not have a parent
     */
    virtual unsigned position() const = 0;

    /**
     * @brief Get the number of children of this node
     * @return The number of children
     */
    virtual unsigned childCount() const = 0;

    /**
     * @brief Get the content of a text or comment node
     *
     * This method makes no sense for element nodes. To retrieve the element node's name,
     * use the name() method.
     *
     * @return The node's content
     */
    virtual char const *content() const = 0;

    /**
     * @brief Get the string representation of a node's attribute
     *
     * If there is no attribute with the given name, the method will return NULL.
     * All strings returned by this method are owned by the node and may not be freed.
     * The returned pointer will become invalid when the attribute changes. If you need
     * to store the return value, use g_strdup(). To parse the string, use methods
     * in repr.h
     *
     * @param key The name of the node's attribute
     */
    virtual char const *attribute(char const *key) const = 0;

    /**
     * @brief Get a list of the node's attributes
     *
     * The returned list is a functional programming style list rather than a standard one.
     *
     * @return A list of AttributeRecord structures describing the attributes
     * @todo This method should return std::map<Glib::Quark const, gchar const *>
     *       or something similar with a custom allocator
     */
    virtual const AttributeVector & attributeList() const=0;

    /**
     * @brief Check whether this node has any attribute that matches a string
     *
     * This method checks whether this node has any attributes whose names
     * have @c partial_name as their substrings. The check is done using
     * the strstr() function of the C library. I don't know what would require that
     * functionality, because matchAttributeName("id") matches both "identity" and "hidden".
     *
     * @param partial_name The string to match against all attributes
     * @return true if there is such an attribute, false otherwise
     */
    virtual bool matchAttributeName(char const *partial_name) const = 0;

    /*@}*/

    /**
     * @name Modify the node
     * @{
     */

    /**
     * @brief Set the position of this node in parent's child order
     *
     * To move the node to the end of the parent's child order, pass a negative argument.
     *
     * @param pos The new position in parent's child order
     */
    virtual void setPosition(int pos) = 0;

    /**
     * @brief Set the content of a text or comment node
     *
     * This method doesn't make sense for element nodes.
     *
     * @param value The node's new content
     */
    virtual void setContent(char const *value) = 0;

    //@{
    /**
     * @brief Change an attribute of this node
     *
     * The strings passed to this method are copied, so you can free them after use.
     *
     * @param key Name of the attribute to change
     * @param value The new value of the attribute
     * @param is_interactive Ignored
     */

    void setAttribute(Util::const_char_ptr key, Util::const_char_ptr value);

    /**
     * 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.
     */
    bool getAttributeBoolean(Util::const_char_ptr key, bool default_value = false) const;

    int getAttributeInt(Util::const_char_ptr key, int default_value = 0) const;

    double getAttributeDouble(Util::const_char_ptr key, double default_value = 0.0) const;

    bool setAttributeBoolean(Util::const_char_ptr key, bool val);

    bool setAttributeInt(Util::const_char_ptr key, int val);

    /**
     * Set a property attribute to \a val [slightly rounded], in the format
     * required for CSS properties: in particular, it never uses exponent
     * notation.
     */
    bool setAttributeCssDouble(Util::const_char_ptr key, double val);

    /**
     * For attributes where an exponent is allowed.
     *
     * Not suitable for property attributes (fill-opacity, font-size etc.).
     */
    bool setAttributeSvgDouble(Util::const_char_ptr key, double val);

    bool setAttributeSvgNonDefaultDouble(Util::const_char_ptr key,
                                         double val, double default_value);

    bool setAttributeSvgLength(Util::const_char_ptr key, SVGLength const &val);

    bool setAttributePoint(Util::const_char_ptr key, Geom::Point const &val);

    Geom::Point getAttributePoint(Util::const_char_ptr key, Geom::Point default_value = {}) const;

    /**
     * @brief Change an attribute of this node. Empty string deletes the attribute.
     *
     * @param key Name of the attribute to change
     * @param value The new value of the attribute
     *
     */
    void setAttributeOrRemoveIfEmpty(Inkscape::Util::const_char_ptr key, Inkscape::Util::const_char_ptr value);

    /**
     * @brief Remove an attribute of this node
     *
     * @param key Name of the attribute to delete
     *
     */
    void removeAttribute(Inkscape::Util::const_char_ptr key) { this->setAttributeImpl(key.data(), nullptr); }

    //@}
    /**
     * @brief Set the integer GQuark code for the name of the node.
     *
     * Do not use this function unless you really have a good reason.
     *
     * @param code The integer value corresponding to the string to be set as
     *        the name of this node
     */
    virtual void setCodeUnsafe(int code) = 0;

    /*@}*/

    /**
     * @name Traverse the XML tree
     * @{
     */

    //@{
    /**
     * @brief Get the node's associated document
     * @return The document to which the node belongs. Never NULL.
     */
    virtual Document *document() = 0;
    virtual Document const *document() const = 0;
    //@}

    //@{
    /**
     * @brief Get the root node of this node's document
     *
     * This method works on any node that is part of an XML document, and returns
     * the root node of the document in which it resides. For detached node hierarchies
     * (i.e. nodes that are not descendants of a document node) this method
     * returns the highest-level element node. For detached non-element nodes this method
     * returns NULL.
     *
     * @return A pointer to the root element node, or NULL if the node is detached
     */
    virtual Node *root() = 0;
    virtual Node const *root() const = 0;
    //@}

    //@{
    /**
     * @brief Get the parent of this node
     *
     * This method will return NULL for detached nodes.
     *
     * @return Pointer to the parent, or NULL
     */
    virtual Node *parent() = 0;
    virtual Node const *parent() const = 0;
    //@}

    //@{
    /**
     * @brief Get the next sibling of this node
     *
     * This method will return NULL if the node is the last sibling element of the parent.
     * The nodes form a singly-linked list, so there is no "prev()" method. Use the provided
     * external function for that.
     *
     * @return Pointer to the next sibling, or NULL
     * @see Inkscape::XML::previous_node()
     */
    virtual Node *next() = 0;
    virtual Node const *next() const = 0;
    virtual Node *prev() = 0;
    virtual Node const *prev() const = 0;
    //@}

    //@{
    /**
     * @brief Get the first child of this node
     *
     * For nodes without any children, this method returns NULL.
     *
     * @return Pointer to the first child, or NULL
     */
    virtual Node *firstChild() = 0;
    virtual Node const *firstChild() const = 0;
    //@}

    //@{
    /**
     * @brief Get the last child of this node
     *
     * For nodes without any children, this method returns NULL.
     *
     * @return Pointer to the last child, or NULL
     */
    virtual Node *lastChild() = 0;
    virtual Node const *lastChild() const = 0;
    //@}

    //@{
    /**
     * @brief Get the child of this node with a given index
     *
     * If there is no child with the specified index number, this method will return NULL.
     *
     * @param index The zero-based index of the child to retrieve
     * @return Pointer to the appropriate child, or NULL
     */
    virtual Node *nthChild(unsigned index) = 0;
    virtual Node const *nthChild(unsigned index) const = 0;
    //@}

    /*@}*/

    /**
     * @name Manipulate the XML tree
     * @{
     */

    /**
     * @brief Create a duplicate of this node
     *
     * The newly created node has no parent, and a refcount equal 1.
     * You need to manually insert it into the document, using e.g. appendChild().
     * Afterwards, call Inkscape::GC::release on it, so that it will be
     * automatically collected when the parent is collected.
     *
     * @param doc The document in which the duplicate should be created
     * @return A pointer to the duplicated node
     */
    virtual Node *duplicate(Document *doc) const = 0;

    /**
     * @brief Insert another node as a child of this node
     *
     * When @c after is NULL, the inserted node will be placed as the first child
     * of this node. @c after must be a child of this node.
     *
     * @param child The node to insert
     * @param after The node after which the inserted node should be placed, or NULL
     */
    virtual void addChild(Node *child, Node *after) = 0;

    /**
     * @brief Insert another node as a child of this node
     *
     * This is more efficient than appendChild() + setPosition().
     *
     * @param child The node to insert
     * @param pos The position in parent's child order
     */
    void addChildAtPos(Node *child, unsigned pos)
    {
        Node *after = (pos == 0) ? nullptr : nthChild(pos - 1);
        addChild(child, after);
    }

    /**
     * @brief Append a node as the last child of this node
     * @param child The node to append
     */
    virtual void appendChild(Node *child) = 0;

    /**
     * @brief Remove a child of this node
     *
     * Once the pointer to the removed node disappears from the stack, the removed node
     * will be collected in the next GC pass, but only as long as its refcount is zero.
     * You should keep a refcount of zero for all nodes in the document except for
     * the document node itself, because they will be held in memory by the parent.
     *
     * @param child The child to remove
     */
    virtual void removeChild(Node *child) = 0;

    /**
     * @brief Move a given node in this node's child order
     *
     * Both @c child and @c after must be children of this node for the method to work.
     *
     * @param child The node to move in the order
     * @param after The sibling node after which the moved node should be placed
     */
    virtual void changeOrder(Node *child, Node *after) = 0;

    /**
     * @brief Remove all elements that not in src node
     * @param src The node to check for elements into this node
     * @param key The attribute to use as the identity attribute
     */
    virtual void cleanOriginal(Node *src, gchar const *key) = 0;

    /**
     * @brief Compare 2 nodes equality
     * @param other The other node to compare
     * @param recursive Recursive mode check
     */
    virtual bool equal(Node const *other, bool recursive) = 0;
    /**
     * @brief Merge all children of another node with the current
     *
     * This method merges two node hierarchies, where @c src takes precedence.
     * @c key is the name of the attribute that determines whether two nodes are
     * corresponding (it must be the same for both, and all of their ancestors). If there is
     * a corresponding node in @c src hierarchy, their attributes and content override the ones
     * already present in this node's hierarchy. If there is no corresponding node,
     * it is copied from @c src to this node. This method is used when merging the user's
     * preferences file with the defaults, and has little use beyond that.
     *
     * @param src The node to merge into this node
     * @param key The attribute to use as the identity attribute
     * @param noid If true process noid items
     * @param key If clean callback to cleanOriginal
     */

    virtual void mergeFrom(Node const *src, char const *key, bool extension = false, bool clean = false) = 0;

    /*@}*/


    /**
     * @name Notify observers about operations on the node
     * @{
     */

    /**
     * @brief Add an object that will be notified of the changes to this node
     *
     * @c observer must be an object deriving from the NodeObserver class.
     * The virtual methods of this object will be called when a corresponding change
     * happens to this node. You can also notify the observer of the node's current state
     * using synthesizeEvents(NodeObserver &).
     *
     * @param observer The observer object
     */
    virtual void addObserver(NodeObserver &observer) = 0;
    /**
     * @brief Remove an object from the list of observers
     * @param observer The object to be removed
     */
    virtual void removeObserver(NodeObserver &observer) = 0;
    /**
     * @brief Generate a sequence of events corresponding to the state of this node
     *
     * This function notifies the specified observer of all the events that would
     * recreate the current state of this node; e.g. the observer is notified of
     * all the attributes, children and content like they were just created.
     * This function can greatly simplify observer logic.
     *
     * @param observer The node observer to notify of the events
     */
    virtual void synthesizeEvents(NodeObserver &observer) = 0;

    /**
     * @brief Add an object that will be notified of the changes to this node and its descendants
     *
     * The difference between adding a regular observer and a subtree observer is that
     * the subtree observer will also be notified if a change occurs to any of the node's
     * descendants, while a regular observer will only be notified of changes to the node
     * it was assigned to.
     *
     * @param observer The observer object
     */
    virtual void addSubtreeObserver(NodeObserver &observer) = 0;

    /**
     * @brief Remove an object from the subtree observers list
     * @param observer The object to be removed
     */
    virtual void removeSubtreeObserver(NodeObserver &observer) = 0;

    /**
     * @brief Add a set node change callbacks with an associated data
     * @deprecated Use addObserver(NodeObserver &) instead
     */
    virtual void addListener(NodeEventVector const *vector, void *data) = 0;
    /**
     * @brief Remove a set of node change callbacks by their associated data
     * @deprecated Use removeObserver(NodeObserver &) instead
     */
    virtual void removeListenerByData(void *data) = 0;
    /**
     * @brief Generate a sequence of events corresponding to the state of this node
     * @deprecated Use synthesizeEvents(NodeObserver &) instead
     */
    virtual void synthesizeEvents(NodeEventVector const *vector, void *data) = 0;

    virtual void recursivePrintTree(unsigned level) = 0;

    /*@}*/

    using iterator = Inkscape::XML::NodeSiblingIterator;

    /** @brief Iterator over children */
    iterator begin() { return iterator(this->firstChild()); }
    /** @brief Helper to use the standard lib container functions */
    iterator end() { return iterator(nullptr); }

    /** @brief Compare a node by looking at its name to a string */
    bool operator==(const std::string &name) const { return this->name() == name; }

    /** @brief depth first search to find a node
     *
     * This function takes any list structure you want and uses that
     * to compare Node's down the child tree. It will do depth first
     * searching into the tree. The key part is that since it is a template
     * you have flexibility on the container, and the comparison that is
     * being used. Typically it will be used with something like a
     * std::list<std::string> which will compare against the node's name
     * but more complex searches could be imagined.
     */
    template <typename T>
    Node *findChildPath(T list)
    {
        return findChildPath(list.cbegin(), list.cend());
    }

    /** @brief template reshuffling to make the more useful findChildPath cleaner */
    template <typename iterT>
    Node *findChildPath(iterT itr, iterT end)
    {
        if (itr == end) {
            return this;
        }

        for (auto &child : *this) {
            if (child == *itr) {
                auto found = child.findChildPath(std::next(itr), end);
                if (found != nullptr) {
                    return found;
                }
            }
        }

        return nullptr;
    }

protected:
    Node(Node const &)
        : Anchored()
    {}

    virtual void setAttributeImpl(char const *key, char const *value) = 0;
};

} // namespace XML
} // namespace Inkscape

#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:fileencoding=utf-8:textwidth=99 :