Skip to content

Commit e51b7dc

Browse files
authored
Merge pull request #32 from Hepheir/Hepheir/issue16
Implement methods of interface `Node`
2 parents 9e233fd + 42fb184 commit e51b7dc

File tree

3 files changed

+829
-97
lines changed

3 files changed

+829
-97
lines changed

w3/python/core/fundamental_interface/Node.py

Lines changed: 231 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -64,19 +64,18 @@ class Node:
6464
def __init__(self,
6565
node_type: NodeType,
6666
node_name: DOMString,
67+
owner_document: Optional[_Document],
6768
node_value: Optional[DOMString] = None,
68-
parent_node: Optional[_AnyNode] = None,
6969
child_nodes: Optional[Iterable[_AnyNode]] = None,
7070
attributes: Optional[Iterable[_AnyNode]] = None,
71-
owner_document: Optional[_Document] = None,
7271
read_only: bool = False) -> None:
7372
if node_value is None:
7473
node_value = ''
7574
self._read_only = False # Allow to modify only while initiating.
7675
self._set_node_type(node_type)
7776
self._set_node_name(node_name)
7877
self._set_node_value(node_value)
79-
self._set_parent_node(parent_node)
78+
self._set_parent_node(None)
8079
self._init_child_nodes(child_nodes)
8180
self._init_attributes(attributes)
8281
self._set_owner_document(owner_document)
@@ -85,22 +84,12 @@ def __init__(self,
8584
self._node_type: NodeType
8685
self._node_name: DOMString
8786
self._node_value: DOMString
88-
self._parent_node: _AnyNode
87+
self._parent_node: Optional[_AnyNode]
8988
self._child_nodes: NodeList
9089
self._attributes: _NamedNodeMap
91-
self._owner_document: _Document
90+
self._owner_document: Optional[_Document]
9291
self._read_only: bool
9392

94-
def _check_modifiable(self) -> None:
95-
"""Checks if this node is modifiable.
96-
97-
Raises:
98-
DOMException:
99-
- `NO_MODIFICATION_ALLOWED_ERR`: Raised when the node is readonly.
100-
"""
101-
if self._read_only:
102-
raise DOMException(DOMException.NO_MODIFICATION_ALLOWED_ERR)
103-
10493
@property
10594
def node_name(self) -> DOMString:
10695
"""Read only; The name of this node, depending on its type."""
@@ -132,7 +121,8 @@ def _set_node_value(self, value: DOMString) -> None:
132121
DOMException:
133122
- `NO_MODIFICATION_ALLOWED_ERR`: Raised when the node is readonly.
134123
"""
135-
self._check_modifiable()
124+
if self._read_only:
125+
raise DOMException(DOMException.NO_MODIFICATION_ALLOWED_ERR)
136126
self._node_value = DOMString(value)
137127

138128
@property
@@ -188,7 +178,7 @@ def first_child(self) -> Optional[_AnyNode]:
188178
189179
If there is no such node, this returns `None`.
190180
"""
191-
if not self.child_nodes:
181+
if not self.has_child_nodes():
192182
return None
193183
return self.child_nodes.item(0)
194184

@@ -198,7 +188,7 @@ def last_child(self) -> Optional[_AnyNode]:
198188
199189
If there is no such node, this returns `None`.
200190
"""
201-
if not self.child_nodes:
191+
if not self.has_child_nodes():
202192
return None
203193
return self.child_nodes.item(self.child_nodes.length-1)
204194

@@ -257,15 +247,236 @@ def owner_document(self) -> Optional[_Document]:
257247
This is also the `Document` object used to create new nodes.
258248
When this node is a `Document` this is `None`.
259249
"""
260-
if self.node_type == NodeType.DOCUMENT_NODE:
261-
return None
262250
return self._owner_document
263251

264252
def _set_owner_document(self,
265253
owner_document: Optional[_Document] = None) -> None:
266254
"""Indirect accessor to set the 'owner_document' property."""
255+
if owner_document is None:
256+
if self.node_type != NodeType.DOCUMENT_NODE:
257+
raise ValueError('`Node` should have a `Document` object ',
258+
'which associated with this node, ',
259+
'Unless this node is a `Document`.')
267260
self._owner_document = owner_document
268261

262+
def insert_before(self,
263+
new_child: _AnyNode,
264+
ref_child: Optional[_AnyNode] = None) -> _AnyNode:
265+
"""Inserts the node `new_child` before the existing child node `ref_child`. If `ref_child` is `None`, insert `new_child` at the end of the list of children.
266+
267+
If `new_child` is a `DocumentFragment` object, all of its children are inserted, in the same order, before `ref_child`. If the `new_child` is already in the tree, it is first removed.
268+
269+
Args:
270+
new_child: The node to insert.
271+
ref_child: The reference node, i.e., the node before which the new node must be inserted.
272+
273+
Returns:
274+
The node being inserted.
275+
276+
Raises:
277+
DOMException:
278+
- `HIERARCHY_REQUEST_ERR`: Raised if this node is of a type that does not allow children of the type of the `new_child` node, or if the node to insert is one of this node's ancestors.
279+
- `WRONG_DOCUMENT_ERR`: Raised if `new_child` was created from a different document than the one that created this node.
280+
- `NO_MODIFICATION_ALLOWED_ERR`: Raised if this node is readonly.
281+
- `NOT_FOUND_ERR`: Raised if `ref_child` is not a child of this node.
282+
"""
283+
# `HIERARCHY_REQUEST_ERR` should be checked on subclasses by overriding.
284+
if self._read_only:
285+
raise DOMException(DOMException.NO_MODIFICATION_ALLOWED_ERR)
286+
if new_child.owner_document is not self.owner_document:
287+
raise DOMException(DOMException.WRONG_DOCUMENT_ERR)
288+
if ref_child is not None:
289+
if ref_child not in self.child_nodes:
290+
raise DOMException(DOMException.NOT_FOUND_ERR)
291+
# If `new_child` and `old_child` share the same reference,
292+
# this method does nothing.
293+
if new_child is ref_child:
294+
return new_child
295+
# If `ref_child` is null, insert `new_child` at the end of the list of children.
296+
#
297+
# This operation is equivalent to the `append_child()` method.
298+
if ref_child is None:
299+
self.append_child(new_child)
300+
return new_child
301+
# If `new_child` is a `DocumentFragment` object,
302+
# all of its children are inserted, in the same order.
303+
#
304+
# This is done by recursive calls.
305+
if new_child.node_type == NodeType.DOCUMENT_FRAGMENT_NODE:
306+
grand_child_node: _AnyNode
307+
for grand_child_node in new_child.child_nodes:
308+
self.insert_before(grand_child_node, ref_child)
309+
return new_child
310+
# If the `new_child` is already in the tree,
311+
# it is first removed.
312+
if new_child in self.child_nodes:
313+
self.remove_child(new_child)
314+
# Otherwise, simply insert `new_child` using the methods of `NodeList(list)`.
315+
ref_index = self.child_nodes.index(ref_child)
316+
self.child_nodes.insert(ref_index, new_child)
317+
new_child._set_parent_node(self)
318+
return new_child
319+
320+
def replace_child(self,
321+
new_child: _AnyNode,
322+
old_child: _AnyNode) -> _AnyNode:
323+
"""Replaces the child node `old_child` with `new_child` in the list of children, and returns the `old_child` node.
324+
325+
If the `new_child` is already in the tree, it is first removed.
326+
327+
Args:
328+
new_child: The new node to put in the child list.
329+
old_child: The node being replaced in the list.
330+
331+
Returns:
332+
The node replaced.
333+
334+
Raisees:
335+
DOMException:
336+
- `HIERARCHY_REQUEST_ERR`: Raised if this node is of a type that does not allow children of the type of the `new_child` node, or it the node to put in is one of this node's ancestors.
337+
- `WRONG_DOCUMENT_ERR`: Raised if `new_child` was created from a different document than the one that created this node.
338+
- `NO_MODIFICATION_ALLOWED_ERR`: Raised if this node is readonly.
339+
- `NOT_FOUND_ERR`: Raised if `old_child` is not a child of this node.
340+
"""
341+
# `HIERARCHY_REQUEST_ERR` should be checked on subclasses by overriding.
342+
if self._read_only:
343+
raise DOMException(DOMException.NO_MODIFICATION_ALLOWED_ERR)
344+
if new_child.owner_document is not self.owner_document:
345+
raise DOMException(DOMException.WRONG_DOCUMENT_ERR)
346+
if old_child not in self.child_nodes:
347+
raise DOMException(DOMException.NOT_FOUND_ERR)
348+
# If `new_child` and `old_child` share the same reference,
349+
# this method does nothing.
350+
if new_child is old_child:
351+
return old_child
352+
# If the `new_child` is already in the tree,
353+
# it is first removed.
354+
if new_child in self.child_nodes:
355+
self.remove_child(new_child)
356+
repl_index = self.child_nodes.index(old_child)
357+
self.child_nodes[repl_index] = new_child
358+
new_child._set_parent_node(self)
359+
return old_child
360+
361+
def remove_child(self,
362+
old_child: _AnyNode) -> _AnyNode:
363+
"""Removes the child node indicated by `old_chlid` from the list of children, and returns it.
364+
365+
Args:
366+
old_chlid: The node being removed.
367+
368+
Returns:
369+
The node removed.
370+
371+
Raises:
372+
DOMException:
373+
- `NO_MODIFICATION_ALLOWED_ERR`: Raised if this node is readonly.
374+
- `NOT_FOUND_ERR`: Raised if `old_chlid` is not a child of this node.
375+
"""
376+
# `HIERARCHY_REQUEST_ERR` should be checked on subclasses by overriding.
377+
if self._read_only:
378+
raise DOMException(DOMException.NO_MODIFICATION_ALLOWED_ERR)
379+
if old_child not in self.child_nodes:
380+
raise DOMException(DOMException.NOT_FOUND_ERR)
381+
# Removes node using the method of `NodeList(list)`.
382+
self.child_nodes.remove(old_child)
383+
old_child._set_parent_node(None)
384+
return old_child
385+
386+
def append_child(self, new_child: _AnyNode) -> _AnyNode:
387+
"""Adds the node `new_child` to the end of the list of children of this node.
388+
389+
If the `new_child` is already in the tree, it is first removed.
390+
391+
Args:
392+
new_child: The node to add. If it is a `DocumentFragment` object, the entire contents of the document fragment are moved into the child list of this node
393+
394+
Returns:
395+
The node added.
396+
397+
Raises:
398+
DOMException:
399+
- `HIERARCHY_REQUEST_ERR`: Raised if this node is of a type that does not allow children of the type of the `new_child` node, or if the node to append is one of this node's ancestors.
400+
- `WRONG_DOCUMENT_ERR`: Raised if `new_child` was created from a different document than the one that created this node.
401+
- `NO_MODIFICATION_ALLOWED_ERR`: Raised if this node is readonly.
402+
"""
403+
# `HIERARCHY_REQUEST_ERR` should be checked on subclasses by overriding.
404+
if self._read_only:
405+
raise DOMException(DOMException.NO_MODIFICATION_ALLOWED_ERR)
406+
if new_child.owner_document is not self.owner_document:
407+
raise DOMException(DOMException.WRONG_DOCUMENT_ERR)
408+
# If `new_child` is a `DocumentFragment` object,
409+
# all of its children are appended, in the same order.
410+
#
411+
# This is done by recursive calls.
412+
if new_child.node_type == NodeType.DOCUMENT_FRAGMENT_NODE:
413+
grand_child_node: _AnyNode
414+
for grand_child_node in new_child.child_nodes:
415+
self.append_child(grand_child_node)
416+
return new_child
417+
# If the `new_child` is already in the tree,
418+
# it is first removed.
419+
if new_child in self.child_nodes:
420+
self.remove_child(new_child)
421+
# Otherwise, simply append `new_child` using the methods of `NodeList(list)`.
422+
self.child_nodes.append(new_child)
423+
new_child._set_parent_node(self)
424+
return new_child
425+
426+
def has_child_nodes(self) -> bool:
427+
"""This is a convenience method to allow easy determination of whether a node has any children.
428+
429+
Returns:
430+
`True` if the node has any children, `False` if the node has no children.
431+
"""
432+
return bool(self.child_nodes)
433+
434+
def clone_node(self, deep: bool = False) -> _AnyNode:
435+
"""Returns a duplicate of this node.
436+
437+
i.e., serves as a generic copy constructor for nodes. The duplicate node has no parent (parentNode returns `None`.).
438+
Cloning an `Element` copies all attributes and their values, including those generated by the XML processor to represent defaulted attributes, but this method does not copy any text it contains unless it is a deep clone, since the text is contained in a child `Text` node.
439+
Cloning any other type of node simply returns a copy of this node.
440+
441+
Args:
442+
deep: If `True`, recursively clone the subtree under the specified node; if `False`, clone only the node itself (and its attributes, if it is an `Element`).
443+
444+
Returns:
445+
The duplicate node.
446+
447+
This method raises no exceptions.
448+
"""
449+
if deep:
450+
return self._deep_copy()
451+
else:
452+
return self._shallow_copy()
453+
454+
def _shallow_copy(self) -> _AnyNode:
455+
node = self.__class__(
456+
owner_document=self.owner_document,
457+
node_type=self.node_type,
458+
node_name=self.node_name,
459+
node_value=self.node_value,
460+
read_only=self._read_only
461+
)
462+
return node
463+
464+
def _deep_copy(self) -> _AnyNode:
465+
def copy_recursive(node_iterable: Iterable[_AnyNode]):
466+
node: _AnyNode
467+
for node in node_iterable:
468+
yield node.clone_node(True)
469+
node = self.__class__(
470+
owner_document=self.owner_document,
471+
node_type=self.node_type,
472+
node_name=self.node_name,
473+
node_value=self.node_value,
474+
child_nodes=copy_recursive(self.child_nodes),
475+
attributes=copy_recursive(self.attributes.values()),
476+
read_only=self._read_only
477+
)
478+
return node
479+
269480

270481
_AnyNode = Node
271482
_NamedNodeMap = Dict[str, _AnyNode] # TODO: Implement NamedNodeMap (#19)

0 commit comments

Comments
 (0)