1
+ require " weak_ref"
2
+
1
3
class XML::Node
2
4
LOOKS_LIKE_XPATH = /^(\.\/ |\/ |\.\. |\. $) /
3
5
4
6
# Every Node must keep a reference to its document Node. To keep things
5
7
# simple, a document Node merely references itself. An unlinked node must
6
8
# still reference its original document Node until adopted into another
7
- # document's tree.
9
+ # document's tree (the libxml nodes keep a pointer to their libxml doc) .
8
10
@document : Node
9
11
10
- # Remembers subtree nodes. Avoids allocating a XML::Node twice for the same
11
- # libxml node. This allows to keep centralized information about the nodes so
12
- # the finalizer won't try to access the libxml node to detect some state
13
- # —which is unsafe because it may have been freed already, and won't try to
14
- # free an unlinked node twice (or leak unlinked libxml nodes).
15
- protected getter! cache : Hash (UInt64 , UInt64 )?
16
-
17
- # Unlinked Nodes must still reference their original document Node. They don't
18
- # appear in the document's tree anymore and thus won't be freed along with the
19
- # document, but the libxml node still referes to the libxml doc and thus need
20
- # to keep the document alive.
12
+ # The constructors allocate a XML::Node for a libxml node once, so we don't
13
+ # finalize a document twice for example.
14
+ #
15
+ # We store the reference into the libxml struct (_private) for documents
16
+ # because every a document's XML::Node lives as long as their libxml doc.
17
+ #
18
+ # However we can lose references to subtree XML::Node, so using _private would
19
+ # leave dangling pointers. We thus keep a cache of weak references to all
20
+ # nodes in the document, so we can still collect lost references, and at worst
21
+ # reinstantiate a XML::Node if needed.
22
+ protected getter! cache : Hash (LibXML ::Node * , WeakRef (Node ))?
23
+
24
+ # Unlinked Nodes (and all descendant nodes) don't appear in the document's
25
+ # tree anymore, and must be manually freed, which will free all their
26
+ # descendants.
21
27
@unlinked = false
22
28
23
29
# :nodoc:
24
- #
25
- # Allocates a XML::Node for a libxml document node once, so we don't finalize
26
- # a document twice. We can store the pointer right into the libxml struct
27
- # because the XML::Node lives as long as the libxml doc.
28
30
def self.new (doc : LibXML ::Doc * , errors : Array (Error )? = nil )
29
31
if ptr = doc.value._private
30
32
ptr.as(Node )
@@ -35,17 +37,18 @@ class XML::Node
35
37
36
38
# :nodoc:
37
39
def self.new (node : LibXML ::Node * , document : self ) : self
38
- # should never happen, but just in case
39
- return document if node == document.@node
40
+ if node == document.@node
41
+ # should never happen, but just in case
42
+ return document
43
+ end
40
44
41
- cache = document.cache
42
- if (addr = cache[node.address]?) && addr != 0
43
- return Pointer (Void ).new(addr).as(Node )
45
+ if (ref = document.cache[node]?) && (obj = ref.value)
46
+ return obj
44
47
end
45
48
46
- this = new(node_: node, document_: document)
47
- cache[node.address ] = this.as( Void * ).address
48
- this
49
+ obj = new(node_: node, document_: document)
50
+ document. cache[node] = WeakRef .new(obj)
51
+ obj
49
52
end
50
53
51
54
# :nodoc:
@@ -63,7 +66,7 @@ class XML::Node
63
66
private def initialize (* , doc_ : LibXML ::Doc * , errors_ : Array (Error )?)
64
67
@node = doc_.as(LibXML ::Node * )
65
68
@errors = errors_
66
- @cache = Hash (UInt64 , UInt64 ).new
69
+ @cache = Hash (LibXML :: Node * , WeakRef ( Node ) ).new
67
70
@document = uninitialized Node
68
71
@document = self
69
72
doc_.value._private = self .as(Void * )
@@ -77,10 +80,8 @@ class XML::Node
77
80
# :nodoc:
78
81
def finalize
79
82
if @document == self
80
- # free the document, which will recursively free the DOM tree, NS, ...
81
83
LibXML .xmlFreeDoc(@node .as(LibXML ::Doc * ))
82
84
elsif @unlinked
83
- # unlinked nodes must be managed manually
84
85
LibXML .xmlFreeNode(@node )
85
86
end
86
87
end
0 commit comments