diff --git a/w3/python/core/fundamental_interface/DOMException_test.py b/test/DOMException_test.py similarity index 100% rename from w3/python/core/fundamental_interface/DOMException_test.py rename to test/DOMException_test.py diff --git a/w3/python/core/fundamental_interface/NodeList_test.py b/test/NodeList_test.py similarity index 100% rename from w3/python/core/fundamental_interface/NodeList_test.py rename to test/NodeList_test.py diff --git a/w3/python/core/fundamental_interface/Node_test.py b/test/Node_test.py similarity index 94% rename from w3/python/core/fundamental_interface/Node_test.py rename to test/Node_test.py index 41e7c79..56da1ae 100644 --- a/w3/python/core/fundamental_interface/Node_test.py +++ b/test/Node_test.py @@ -2,18 +2,17 @@ from w3.dom import DOMException from w3.dom import Node -from w3.dom import NodeType def _create_document_node(): - return Node(node_type=NodeType.DOCUMENT_NODE, + return Node(node_type=Node.DOCUMENT_NODE, node_name='#document', owner_document=None) def _create_element_node(document: Node): - assert document.node_type == NodeType.DOCUMENT_NODE - return Node(node_type=NodeType.ELEMENT_NODE, + assert document.node_type == Node.DOCUMENT_NODE + return Node(node_type=Node.ELEMENT_NODE, node_name='tagName', owner_document=document) @@ -21,11 +20,11 @@ def _create_element_node(document: Node): class TestDunder_Init(unittest.TestCase): def test_Default(self): # Creating document node. - document = Node(node_type=NodeType.DOCUMENT_NODE, + document = Node(node_type=Node.DOCUMENT_NODE, node_name='#document', owner_document=None, read_only=False) - self.assertEqual(document.node_type, NodeType.DOCUMENT_NODE) + self.assertEqual(document.node_type, Node.DOCUMENT_NODE) self.assertEqual(document.node_name, '#document') self.assertEqual(document.node_value, '') @@ -36,14 +35,14 @@ def setUp(self) -> None: return super().setUp() def testGetter(self): - text = Node(node_type=NodeType.TEXT_NODE, + text = Node(node_type=Node.TEXT_NODE, node_name='#text', owner_document=self.document, node_value='lorem ipsum') self.assertEqual(text.node_value, 'lorem ipsum') def testConstructor(self): - text = Node(node_type=NodeType.TEXT_NODE, + text = Node(node_type=Node.TEXT_NODE, node_name='#text', owner_document=self.document, node_value='foo', @@ -51,7 +50,7 @@ def testConstructor(self): self.assertEqual(text.node_value, 'foo') def testSetter(self): - text = Node(node_type=NodeType.TEXT_NODE, + text = Node(node_type=Node.TEXT_NODE, node_name='#text', owner_document=self.document, read_only=False) @@ -59,7 +58,7 @@ def testSetter(self): self.assertEqual(text.node_value, 'bar') def testSetter_ReadOnly(self): - text = Node(node_type=NodeType.TEXT_NODE, + text = Node(node_type=Node.TEXT_NODE, node_name='#text', owner_document=self.document, node_value='foo', @@ -73,27 +72,27 @@ def testSetter_ReadOnly(self): self.assertEqual(text.node_value, 'foo') -class TestProperty_NodeType(unittest.TestCase): +class TestProperty_Node(unittest.TestCase): def setUp(self) -> None: self.document = _create_document_node() return super().setUp() def testGetter(self): - for node_type in NodeType: + for node_type in Node: node = Node(node_type=node_type, node_name='', owner_document=self.document) self.assertEqual(node.node_type, node_type) def testSetter_CorrectTypes(self): - for node_type in NodeType: + for node_type in Node: with self.subTest(node_type=node_type): Node(node_type=node_type, node_name='', owner_document=self.document) def testSetter_WrongTypes(self): - for node_type in [-1, None, 'hi', True, Node, NodeType, 'DOCUMENT_NODE']: + for node_type in [-1, None, 'hi', True, Node, Node, 'DOCUMENT_NODE']: with self.subTest(node_type=node_type): with self.assertRaises((TypeError, ValueError)): Node(node_type=node_type, @@ -107,7 +106,7 @@ def setUp(self) -> None: return super().setUp() def testConstrutor(self): - docfrag = Node(node_type=NodeType.DOCUMENT_FRAGMENT_NODE, + docfrag = Node(node_type=Node.DOCUMENT_FRAGMENT_NODE, node_name='#document-fragment', owner_document=self.document) self.assertIsNone(docfrag.parent_node) @@ -205,7 +204,7 @@ def test_InsertDocumentFragmentNode(self): # # ====================================== parent_node = _create_element_node(self.document) - docfrag_node = Node(node_type=NodeType.DOCUMENT_FRAGMENT_NODE, + docfrag_node = Node(node_type=Node.DOCUMENT_FRAGMENT_NODE, node_name='#document-fragment', owner_document=self.document) first_child_node = _create_element_node(self.document) @@ -248,7 +247,7 @@ def test_Raises_NO_MODIFICATION_ALLOWED_ERR(self): # # # ====================================== - parent_node_readonly = Node(node_type=NodeType.ELEMENT_NODE, + parent_node_readonly = Node(node_type=Node.ELEMENT_NODE, node_name='tagName', owner_document=self.document, read_only=True) @@ -330,7 +329,7 @@ def test_AppendDocumentFragmentNode(self): # # ====================================== parent_node = _create_element_node(self.document) - docfrag_node = Node(node_type=NodeType.DOCUMENT_FRAGMENT_NODE, + docfrag_node = Node(node_type=Node.DOCUMENT_FRAGMENT_NODE, node_name='#document-fragment', owner_document=self.document) first_child_node = _create_element_node(self.document) @@ -373,7 +372,7 @@ def test_Raises_NO_MODIFICATION_ALLOWED_ERR(self): # # # ====================================== - parent_node_readonly = Node(node_type=NodeType.ELEMENT_NODE, + parent_node_readonly = Node(node_type=Node.ELEMENT_NODE, node_name='tagName', owner_document=self.document, read_only=True) @@ -446,7 +445,7 @@ def test_Raises_NO_MODIFICATION_ALLOWED_ERR(self): # # # ====================================== - parent_node_readonly = Node(node_type=NodeType.ELEMENT_NODE, + parent_node_readonly = Node(node_type=Node.ELEMENT_NODE, node_name='tagName', owner_document=self.document, read_only=True) @@ -533,7 +532,7 @@ def test_Raises_NO_MODIFICATION_ALLOWED_ERR(self): # # ====================================== child_node = _create_element_node(self.document) - parent_node = Node(node_type=NodeType.ELEMENT_NODE, + parent_node = Node(node_type=Node.ELEMENT_NODE, node_name='tagName', owner_document=self.document, child_nodes=[child_node], diff --git a/w3/dom.py b/w3/dom.py index 12fb1a2..6da778b 100644 --- a/w3/dom.py +++ b/w3/dom.py @@ -2,9 +2,21 @@ # Bring in subpackages. -from w3.python.core import DOMException -from w3.python.core import DOMImplementation -from w3.python.core import DOMString -from w3.python.core import Node -from w3.python.core import NodeType -from w3.python.core import NodeList +from w3.python.core.type import DOMString +from w3.python.core.exception import DOMException +from w3.python.core.interface import DOMImplementation +from w3.python.core.interface import Document +from w3.python.core.interface import Node +from w3.python.core.interface import NodeList +from w3.python.core.interface import NodeList +from w3.python.core.interface import NamedNodeMap +from w3.python.core.interface import CharacterData +from w3.python.core.interface import Attr +from w3.python.core.interface import Element +from w3.python.core.interface import Text +from w3.python.core.interface import Comment +from w3.python.core.interface import CDATASection +from w3.python.core.interface import DocumentType +from w3.python.core.interface import Notation +from w3.python.core.interface import EntityReference +from w3.python.core.interface import ProcessingInstruction diff --git a/w3/python/__init__.py b/w3/python/__init__.py deleted file mode 100644 index b687bb7..0000000 --- a/w3/python/__init__.py +++ /dev/null @@ -1,6 +0,0 @@ -"""Imports core names of w3. - -Interfaces are implemented under "./core/" -""" - -from w3.python import core diff --git a/w3/python/core/__init__.py b/w3/python/core/__init__.py deleted file mode 100644 index 4a3d57d..0000000 --- a/w3/python/core/__init__.py +++ /dev/null @@ -1,8 +0,0 @@ -"""Module that contains classes of interfaces and ect.""" - -from w3.python.core.DOMString import DOMString -from w3.python.core.fundamental_interface import DOMException -from w3.python.core.fundamental_interface import DOMImplementation -from w3.python.core.fundamental_interface import Node -from w3.python.core.fundamental_interface import NodeType -from w3.python.core.fundamental_interface import NodeList diff --git a/w3/python/core/fundamental_interface/DOMException.py b/w3/python/core/exception.py similarity index 100% rename from w3/python/core/fundamental_interface/DOMException.py rename to w3/python/core/exception.py diff --git a/w3/python/core/fundamental_interface/DOMImplementation.py b/w3/python/core/fundamental_interface/DOMImplementation.py deleted file mode 100644 index 1fc9068..0000000 --- a/w3/python/core/fundamental_interface/DOMImplementation.py +++ /dev/null @@ -1,27 +0,0 @@ -from w3.python.typing import _DOMString - - -class DOMImplementation: - """Interface DOMImplementation - - The `DOMImplementation` interface provides a number of methods for performing operations that are independent of any particular instance of the document object model. - The DOM Level 1 does not specify a way of creating a document instance, and hence document creation is an operation specific to an implementation. Future Levels of the DOM specification are expected to provide methods for creating documents directly. - """ - - # TODO - def has_feature(self, - feature: _DOMString, - version: _DOMString) -> bool: - """ - Test if the DOM implementation implements a specific feature. - - Args: - feature: The package name of the feature to test. In Level 1, the legal values are "HTML" and "XML" (case-insensitive). - version: This is the version number of the package name to test. In Level 1, this is the string "1.0". If the version is not specified, supporting any version of the feature will cause the method to return true. - - Returns: - `True` if the feature is implemented in the specified version, `False` otherwise. - - This method raises no exceptions. - """ - raise NotImplementedError diff --git a/w3/python/core/fundamental_interface/DocumentFragment.py b/w3/python/core/fundamental_interface/DocumentFragment.py deleted file mode 100644 index 29ae0b3..0000000 --- a/w3/python/core/fundamental_interface/DocumentFragment.py +++ /dev/null @@ -1,35 +0,0 @@ -from w3.python.typing import _Document -from w3.python.core.fundamental_interface.Node import Node -from w3.python.core.fundamental_interface.Node import NodeType - - -class DocumentFragment(Node): - """Interface `DocumentFragment` - - `DocumentFragment` is a "lightweight" or "minimal" `Document` object. - It is very common to want to be able to extract a portion of a document's tree or to create a new fragment of a document. - Imagine implementing a user command like cut or rearranging a document by moving fragments around. - It is desirable to have an object which can hold such fragments and it is quite natural to use a `Node` for this purpose. - While it is true that a `Document` object could fulfil this role, a `Document` object can potentially be a heavyweight object, depending on the underlying implementation. - What is really needed for this is a very lightweight object. - `DocumentFragment` is such an object. - - Furthermore, various operations -- such as inserting nodes as children of another `Node` -- may take `DocumentFragment` objects as arguments; this results in all the child nodes of the `DocumentFragment` being moved to the child list of this node. - - The children of a `DocumentFragment` node are zero or more nodes representing the tops of any sub-trees defining the structure of the document. - `DocumentFragment` nodes do not need to be well-formed XML documents (although they do need to follow the rules imposed upon well-formed XML parsed entities, which can have multiple top nodes). - For example, a `DocumentFragment` might have only one child and that child node could be a `Text` node. - Such a structure model represents neither an HTML document nor a well-formed XML document. - - When a `DocumentFragment` is inserted into a `Document` (or indeed any other `Node` that may take children) the children of the `DocumentFragment` and not the `DocumentFragment` itself are inserted into the `Node`. - This makes the `DocumentFragment` very useful when the user wishes to create nodes that are siblings; the `DocumentFragment` acts as the parent of these nodes so that the user can use the standard methods from the `Node` interface, such as `insertBefore()` and `appendChild()`. - """ - - def __init__(self, - owner_document: _Document, - read_only: bool = False) -> None: - super().__init__(owner_document=owner_document, - node_type=NodeType.DOCUMENT_FRAGMENT_NODE, - node_name='#document-fragment', - node_value=None, - read_only=read_only) diff --git a/w3/python/core/fundamental_interface/Node.py b/w3/python/core/fundamental_interface/Node.py deleted file mode 100644 index fabffcf..0000000 --- a/w3/python/core/fundamental_interface/Node.py +++ /dev/null @@ -1,479 +0,0 @@ -from __future__ import annotations - -import enum -from typing import Iterable, Optional - -from w3.python.core.fundamental_interface.DOMException import DOMException -from w3.python.core.fundamental_interface.NodeList import NodeList - -from w3.python.typing import _AnyNode, _Attr, _Document, _DOMString, _NamedNodeMap - - -class NodeType(enum.IntEnum): - """Definition group `NodeType` - - An integer indicating which type of node this is. - - Attributes: - ELEMENT_NODE: The node is a `Element`. - ATTRIBUTE_NODE: The node is an `Attr`. - TEXT_NODE: The node is a `Text` node. - CDATA_SECTION_NODE: The node is a `CDATASection`. - ENTITY_REFERENCE_NODE: The node is an `EntityReference`. - ENTITY_NODE: The node is an `Entity`. - PROCESSING_INSTRUCTION_NODE: The node is a `ProcessingInstruction`. - COMMENT_NODE: The node is a `Comment`. - DOCUMENT_NODE: The node is a `Document`. - DOCUMENT_TYPE_NODE: The node is a `DocumentType`. - DOCUMENT_FRAGMENT_NODE: The node is a `DocumentFragment`. - NOTATION_NODE: The node is a `Notation`. - """ - ELEMENT_NODE = 1 - ATTRIBUTE_NODE = 2 - TEXT_NODE = 3 - CDATA_SECTION_NODE = 4 - ENTITY_REFERENCE_NODE = 5 - ENTITY_NODE = 6 - PROCESSING_INSTRUCTION_NODE = 7 - COMMENT_NODE = 8 - DOCUMENT_NODE = 9 - DOCUMENT_TYPE_NODE = 10 - DOCUMENT_FRAGMENT_NODE = 11 - NOTATION_NODE = 12 - - -class Node: - """Interface `Node` - - The `Node` interface is the primary datatype for the entire Document Object Model. It represents a single node in the document tree. While all objects implementing the `Node` interface expose methods for dealing with children, not all objects implementing the `Node` interface may have children. For example, `Text` nodes may not have children, and adding children to such nodes results in a `DOMException` being raised. - The attributes `node_name`, `node_value` and attributes are included as a mechanism to get at node information without casting down to the specific derived interface. In cases where there is no obvious mapping of these attributes for a specific `node_type` (e.g., `node_value` for an Element or `attributes` for a Comment), this returns `None`. Note that the specialized interfaces may contain additional and more convenient mechanisms to get and set the relevant information. - - Attributes: - node_name: The name of this node, depending on its type. - node_value: The value of this node, depending on its type. - node_type: A code representing the type of the underlying object. - parent_node: The parent of this node. - child_nodes: A `NodeList` that contains all children of this node. - first_child: The first child of this node. - last_child: The last child of this node. - previous_sibling: The node immediately preceding this node. - next_sibling: The node immediately following this node. - attributes: A `NamedNodeMap` containing the attributes of this node. - owner_document: The `Document` object associated with this node. - """ - - def __init__(self, - node_type: NodeType, - node_name: _DOMString, - owner_document: Optional[_Document], - node_value: Optional[_DOMString] = None, - child_nodes: Optional[Iterable[_AnyNode]] = None, - attributes: Optional[Iterable[_AnyNode]] = None, - read_only: bool = False) -> None: - if node_value is None: - node_value = '' - self._read_only = False # Allow to modify only while initiating. - self._set_node_type(node_type) - self._set_node_name(node_name) - self._set_node_value(node_value) - self._set_parent_node(None) - self._init_child_nodes(child_nodes) - self._init_attributes(attributes) - self._set_owner_document(owner_document) - self._read_only = bool(read_only) - # Attributes - self._node_type: NodeType - self._node_name: _DOMString - self._node_value: _DOMString - self._parent_node: Optional[_AnyNode] - self._child_nodes: NodeList - self._attributes: _NamedNodeMap - self._owner_document: Optional[_Document] - self._read_only: bool - - @property - def node_name(self) -> _DOMString: - """Read only; The name of this node, depending on its type.""" - return self._node_name - - def _set_node_name(self, name: _DOMString) -> None: - """Indirect accessor to set the 'node_name' property.""" - self._node_name = _DOMString(name) - - @property - def node_value(self) -> _DOMString: - """The value of this node, depending on its type. - - Raises: - DOMException: - - `NO_MODIFICATION_ALLOWED_ERR`: Raised when the node is readonly. (on setting) - - `DOMSTRING_SIZE_ERR`: Raised when it would return more characters than fit in a `DOMString` variable on the implementation platform. (on retrieval) - """ - return self._node_value - - @node_value.setter - def node_value(self, value: _DOMString) -> None: - self._set_node_value(value) - - def _set_node_value(self, value: _DOMString) -> None: - """Indirect accessor to set the 'node_value' property. - - Raises: - DOMException: - - `NO_MODIFICATION_ALLOWED_ERR`: Raised when the node is readonly. - """ - if self._read_only: - raise DOMException(DOMException.NO_MODIFICATION_ALLOWED_ERR) - self._node_value = _DOMString(value) - - @property - def node_type(self) -> NodeType: - """Read only; A code representing the type of the underlying object, as defined in `NodeType`.""" - return self._node_type - - def _set_node_type(self, node_type: NodeType) -> None: - """Indirect accessor to set the 'node_type' property.""" - if node_type not in NodeType: - raise ValueError(f'{node_type} is not a valid code ' - 'for a node type.') - self._node_type = node_type - - @property - def parent_node(self) -> Optional[_AnyNode]: - """The parent of this node. - - All nodes, except `Document`, `DocumentFragment`, and `Attr` may have a parent. - However, if a node has just been created and not yet added to the tree, or if it has been removed from the tree, this is `None`. - """ - return self._parent_node - - def _set_parent_node(self, - parent_node: Optional[_AnyNode]) -> None: - """Indirect accessor to set the 'node_type' property.""" - if not parent_node: - self._parent_node = None - else: - self._parent_node = parent_node - - @property - def child_nodes(self) -> NodeList: - """A `NodeList` that contains all children of this node. - - If there are no children, this is a `NodeList` containing no nodes. - The content of the returned `NodeList` is "live" in the sense that, for instance, changes to the children of the node object that it was created from are immediately reflected in the nodes returned by the `NodeList` accessors; it is not a static snapshot of the content of the node. - This is true for every `NodeList`, including the ones returned by the `getElementsByTagName` method. - """ - return self._child_nodes - - def _init_child_nodes(self, - child_nodes: Optional[Iterable[_AnyNode]] = None) -> None: - """Accessor to set the 'child_nodes' property.""" - if child_nodes is None: - self._child_nodes = NodeList() - else: - self._child_nodes = NodeList(iter(child_nodes)) - - @property - def first_child(self) -> Optional[_AnyNode]: - """The first child of this node. - - If there is no such node, this returns `None`. - """ - if not self.has_child_nodes(): - return None - return self.child_nodes.item(0) - - @property - def last_child(self) -> Optional[_AnyNode]: - """The last child of this node. - - If there is no such node, this returns `None`. - """ - if not self.has_child_nodes(): - return None - return self.child_nodes.item(self.child_nodes.length-1) - - @property - def previous_sibling(self) -> Optional[_AnyNode]: - """The node immediately preceding this node. - - If there is no such node, this returns `None`. - """ - if self.parent_node is None: - return None - if self.parent_node.first_child is self: - return None - nth_child = self._nth_child_of_parent() - return self.parent_node.child_nodes.item(nth_child-1) - - @property - def next_sibling(self) -> Optional[_AnyNode]: - """The node immediately following this node. - - If there is no such node, this returns `None`. - """ - if self.parent_node is None: - return None - if self.parent_node.last_child is self: - return None - nth_child = self._nth_child_of_parent() - return self.parent_node.child_nodes.item(nth_child+1) - - def _nth_child_of_parent(self) -> Optional[int]: - """Accessor that indicates how many siblings are there preceding this node. - - If there is no such parent node, this returns `None`. - """ - if self.parent_node is None: - return None - return self.parent_node.child_nodes.index(self) - - @property - def attributes(self) -> _NamedNodeMap: - """A `NamedNodeMap` containing the attributes of this node (if it is an `Element`) or `None` otherwise.""" - return self._attributes - - def _init_attributes(self, - attributes: Optional[Iterable[_Attr]] = None) -> None: - self._attributes: _NamedNodeMap = {} # TODO: Replace with real NamedNodeMap #19 - if attributes is None: - return - for attr in iter(attributes): - self._attributes.set_named_item(attr) - - @property - def owner_document(self) -> Optional[_Document]: - """The `Document` object associated with this node. - - This is also the `Document` object used to create new nodes. - When this node is a `Document` this is `None`. - """ - return self._owner_document - - def _set_owner_document(self, - owner_document: Optional[_Document] = None) -> None: - """Indirect accessor to set the 'owner_document' property.""" - if owner_document is None: - if self.node_type != NodeType.DOCUMENT_NODE: - raise ValueError('`Node` should have a `Document` object ', - 'which associated with this node, ', - 'Unless this node is a `Document`.') - self._owner_document = owner_document - - def insert_before(self, - new_child: _AnyNode, - ref_child: Optional[_AnyNode] = None) -> _AnyNode: - """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. - - 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. - - Args: - new_child: The node to insert. - ref_child: The reference node, i.e., the node before which the new node must be inserted. - - Returns: - The node being inserted. - - Raises: - DOMException: - - `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. - - `WRONG_DOCUMENT_ERR`: Raised if `new_child` was created from a different document than the one that created this node. - - `NO_MODIFICATION_ALLOWED_ERR`: Raised if this node is readonly. - - `NOT_FOUND_ERR`: Raised if `ref_child` is not a child of this node. - """ - # `HIERARCHY_REQUEST_ERR` should be checked on subclasses by overriding. - if self._read_only: - raise DOMException(DOMException.NO_MODIFICATION_ALLOWED_ERR) - if new_child.owner_document is not self.owner_document: - raise DOMException(DOMException.WRONG_DOCUMENT_ERR) - if ref_child is not None: - if ref_child not in self.child_nodes: - raise DOMException(DOMException.NOT_FOUND_ERR) - # If `new_child` and `old_child` share the same reference, - # this method does nothing. - if new_child is ref_child: - return new_child - # If `ref_child` is null, insert `new_child` at the end of the list of children. - # - # This operation is equivalent to the `append_child()` method. - if ref_child is None: - self.append_child(new_child) - return new_child - # If `new_child` is a `DocumentFragment` object, - # all of its children are inserted, in the same order. - # - # This is done by recursive calls. - if new_child.node_type == NodeType.DOCUMENT_FRAGMENT_NODE: - grand_child_node: _AnyNode - for grand_child_node in new_child.child_nodes: - self.insert_before(grand_child_node, ref_child) - return new_child - # If the `new_child` is already in the tree, - # it is first removed. - if new_child in self.child_nodes: - self.remove_child(new_child) - # Otherwise, simply insert `new_child` using the methods of `NodeList(list)`. - ref_index = self.child_nodes.index(ref_child) - self.child_nodes.insert(ref_index, new_child) - new_child._set_parent_node(self) - return new_child - - def replace_child(self, - new_child: _AnyNode, - old_child: _AnyNode) -> _AnyNode: - """Replaces the child node `old_child` with `new_child` in the list of children, and returns the `old_child` node. - - If the `new_child` is already in the tree, it is first removed. - - Args: - new_child: The new node to put in the child list. - old_child: The node being replaced in the list. - - Returns: - The node replaced. - - Raisees: - DOMException: - - `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. - - `WRONG_DOCUMENT_ERR`: Raised if `new_child` was created from a different document than the one that created this node. - - `NO_MODIFICATION_ALLOWED_ERR`: Raised if this node is readonly. - - `NOT_FOUND_ERR`: Raised if `old_child` is not a child of this node. - """ - # `HIERARCHY_REQUEST_ERR` should be checked on subclasses by overriding. - if self._read_only: - raise DOMException(DOMException.NO_MODIFICATION_ALLOWED_ERR) - if new_child.owner_document is not self.owner_document: - raise DOMException(DOMException.WRONG_DOCUMENT_ERR) - if old_child not in self.child_nodes: - raise DOMException(DOMException.NOT_FOUND_ERR) - # If `new_child` and `old_child` share the same reference, - # this method does nothing. - if new_child is old_child: - return old_child - # If the `new_child` is already in the tree, - # it is first removed. - if new_child in self.child_nodes: - self.remove_child(new_child) - repl_index = self.child_nodes.index(old_child) - self.child_nodes[repl_index] = new_child - new_child._set_parent_node(self) - return old_child - - def remove_child(self, - old_child: _AnyNode) -> _AnyNode: - """Removes the child node indicated by `old_chlid` from the list of children, and returns it. - - Args: - old_chlid: The node being removed. - - Returns: - The node removed. - - Raises: - DOMException: - - `NO_MODIFICATION_ALLOWED_ERR`: Raised if this node is readonly. - - `NOT_FOUND_ERR`: Raised if `old_chlid` is not a child of this node. - """ - # `HIERARCHY_REQUEST_ERR` should be checked on subclasses by overriding. - if self._read_only: - raise DOMException(DOMException.NO_MODIFICATION_ALLOWED_ERR) - if old_child not in self.child_nodes: - raise DOMException(DOMException.NOT_FOUND_ERR) - # Removes node using the method of `NodeList(list)`. - self.child_nodes.remove(old_child) - old_child._set_parent_node(None) - return old_child - - def append_child(self, new_child: _AnyNode) -> _AnyNode: - """Adds the node `new_child` to the end of the list of children of this node. - - If the `new_child` is already in the tree, it is first removed. - - Args: - 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 - - Returns: - The node added. - - Raises: - DOMException: - - `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. - - `WRONG_DOCUMENT_ERR`: Raised if `new_child` was created from a different document than the one that created this node. - - `NO_MODIFICATION_ALLOWED_ERR`: Raised if this node is readonly. - """ - # `HIERARCHY_REQUEST_ERR` should be checked on subclasses by overriding. - if self._read_only: - raise DOMException(DOMException.NO_MODIFICATION_ALLOWED_ERR) - if new_child.owner_document is not self.owner_document: - raise DOMException(DOMException.WRONG_DOCUMENT_ERR) - # If `new_child` is a `DocumentFragment` object, - # all of its children are appended, in the same order. - # - # This is done by recursive calls. - if new_child.node_type == NodeType.DOCUMENT_FRAGMENT_NODE: - grand_child_node: _AnyNode - for grand_child_node in new_child.child_nodes: - self.append_child(grand_child_node) - return new_child - # If the `new_child` is already in the tree, - # it is first removed. - if new_child in self.child_nodes: - self.remove_child(new_child) - # Otherwise, simply append `new_child` using the methods of `NodeList(list)`. - self.child_nodes.append(new_child) - new_child._set_parent_node(self) - return new_child - - def has_child_nodes(self) -> bool: - """This is a convenience method to allow easy determination of whether a node has any children. - - Returns: - `True` if the node has any children, `False` if the node has no children. - """ - return bool(self.child_nodes) - - def clone_node(self, deep: bool = False) -> _AnyNode: - """Returns a duplicate of this node. - - i.e., serves as a generic copy constructor for nodes. The duplicate node has no parent (parentNode returns `None`.). - 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. - Cloning any other type of node simply returns a copy of this node. - - Args: - 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`). - - Returns: - The duplicate node. - - This method raises no exceptions. - """ - if deep: - return self._deep_copy() - else: - return self._shallow_copy() - - def _shallow_copy(self) -> _AnyNode: - node = self.__class__( - owner_document=self.owner_document, - node_type=self.node_type, - node_name=self.node_name, - node_value=self.node_value, - read_only=self._read_only - ) - return node - - def _deep_copy(self) -> _AnyNode: - def copy_recursive(node_iterable: Iterable[_AnyNode]): - node: _AnyNode - for node in node_iterable: - yield node.clone_node(True) - node = self.__class__( - owner_document=self.owner_document, - node_type=self.node_type, - node_name=self.node_name, - node_value=self.node_value, - child_nodes=copy_recursive(self.child_nodes), - attributes=copy_recursive(self.attributes.values()), - read_only=self._read_only - ) - return node diff --git a/w3/python/core/fundamental_interface/NodeList.py b/w3/python/core/fundamental_interface/NodeList.py deleted file mode 100644 index 5eac3fe..0000000 --- a/w3/python/core/fundamental_interface/NodeList.py +++ /dev/null @@ -1,38 +0,0 @@ -from typing import Optional - -from w3.python.typing import _AnyNode - - -class NodeList(list): - """Interface NodeList - - The `NodeList` interface provides the abstraction of an ordered collection of nodes, without defining or constraining how this collection is implemented. - The items in the `NodeList` are accessible via an integral index, starting from 0. - """ - - @property - def length(self) -> int: - """The number of nodes in the list. - - The range of valid child node indices is 0 to `length`-1 inclusive. - """ - return len(self) - - - def item(self, index: int) -> Optional[_AnyNode]: - """Returns the indexth item in the collection. - - If index is greater than or equal to the number of nodes in the list, this returns null. - - Args: - index: Index into the collection. - - Returns: - The node at the `index`th position in the `NodeList`, or `None` if that is not a valid index. - - This method raises no exceptions. - """ - if isinstance(index, int): - if 0 <= index < self.length: - return self[index] - return None diff --git a/w3/python/core/fundamental_interface/__init__.py b/w3/python/core/fundamental_interface/__init__.py deleted file mode 100644 index fd1e595..0000000 --- a/w3/python/core/fundamental_interface/__init__.py +++ /dev/null @@ -1,10 +0,0 @@ -"""Fundamental Interfaces - -The interfaces within this section are considered fundamental, and must be fully implemented by all conforming implementations of the DOM, including all HTML DOM implementations. -""" - -from w3.python.core.fundamental_interface.DOMException import DOMException -from w3.python.core.fundamental_interface.DOMImplementation import DOMImplementation -from w3.python.core.fundamental_interface.Node import Node -from w3.python.core.fundamental_interface.Node import NodeType -from w3.python.core.fundamental_interface.NodeList import NodeList diff --git a/w3/python/core/interface.py b/w3/python/core/interface.py new file mode 100644 index 0000000..be2d3de --- /dev/null +++ b/w3/python/core/interface.py @@ -0,0 +1,222 @@ +from __future__ import annotations + +from ctypes import c_ushort, c_ulong +from typing import Callable + +from w3.python.core.type import DOMString + + +class DOMImplementation: + """Interface DOMImplementation + + The `DOMImplementation` interface provides a number of methods for performing operations that are independent of any particular instance of the document object model. + The DOM Level 1 does not specify a way of creating a document instance, and hence document creation is an operation specific to an implementation. Future Levels of the DOM specification are expected to provide methods for creating documents directly. + """ + + # TODO + def has_feature(self, + feature: DOMString, + version: DOMString) -> bool: + """ + Test if the DOM implementation implements a specific feature. + + Args: + feature: The package name of the feature to test. In Level 1, the legal values are "HTML" and "XML" (case-insensitive). + version: This is the version number of the package name to test. In Level 1, this is the string "1.0". If the version is not specified, supporting any version of the feature will cause the method to return true. + + Returns: + `True` if the feature is implemented in the specified version, `False` otherwise. + + This method raises no exceptions. + """ + raise NotImplementedError + + +class NodeList: + item: Callable[[c_ulong], Node] + length: c_ulong + + +class NamedNodeMap: + getNamedItem: Callable[[DOMString], Node] + setNamedItem: Callable[[Node], Node] + removeNamedItem: Callable[[DOMString], Node] + item: Callable[[c_ulong], Node] + length: c_ulong + + +class Node: + """Interface `Node` + + The `Node` interface is the primary datatype for the entire Document Object Model. + It represents a single node in the document tree. + While all objects implementing the `Node` interface expose methods for dealing with children, not all objects implementing the `Node` interface may have children. + For example, `Text` nodes may not have children, and adding children to such nodes results in a `DOMException` being raised. + + The attributes `node_name`, `node_value` and attributes are included as a mechanism to get at node information without casting down to the specific derived interface. + In cases where there is no obvious mapping of these attributes for a specific `node_type` (e.g., `node_value` for an Element or `attributes` for a Comment), this returns `None`. + Note that the specialized interfaces may contain additional and more convenient mechanisms to get and set the relevant information. + """ + + # Definition group `NodeType` + # An integer indicating which type of node this is. + ELEMENT_NODE: c_ushort = c_ushort(1) + ATTRIBUTE_NODE: c_ushort = c_ushort(2) + TEXT_NODE: c_ushort = c_ushort(3) + CDATA_SECTION_NODE: c_ushort = c_ushort(4) + ENTITY_REFERENCE_NODE: c_ushort = c_ushort(5) + ENTITY_NODE: c_ushort = c_ushort(6) + PROCESSING_INSTRUCTION_NODE: c_ushort = c_ushort(7) + COMMENT_NODE: c_ushort = c_ushort(8) + DOCUMENT_NODE: c_ushort = c_ushort(9) + DOCUMENT_TYPE_NODE: c_ushort = c_ushort(10) + DOCUMENT_FRAGMENT_NODE: c_ushort = c_ushort(11) + NOTATION_NODE: c_ushort = c_ushort(12) + + nodeName: DOMString + nodeValue: DOMString + nodeType: c_ushort + parentNode: Node + childNodes: NodeList + firstChild: Node + lastChild: Node + previousSibling: Node + nextSibling: Node + attributes: NamedNodeMap + ownerDocument: Document + insertBefore: Callable[[Node, Node], Node] + replaceChild: Callable[[Node, Node], Node] + removeChild: Callable[[Node], Node] + appendChild: Callable[[Node], Node] + hasChildNodes: Callable[[], bool] + cloneNodes: Callable[[bool], Node] + + +class DocumentFragment(Node): + """Interface `DocumentFragment` + + `DocumentFragment` is a "lightweight" or "minimal" `Document` object. + It is very common to want to be able to extract a portion of a document's tree or to create a new fragment of a document. + Imagine implementing a user command like cut or rearranging a document by moving fragments around. + It is desirable to have an object which can hold such fragments and it is quite natural to use a `Node` for this purpose. + While it is true that a `Document` object could fulfil this role, a `Document` object can potentially be a heavyweight object, depending on the underlying implementation. + What is really needed for this is a very lightweight object. + `DocumentFragment` is such an object. + + Furthermore, various operations -- such as inserting nodes as children of another `Node` -- may take `DocumentFragment` objects as arguments; this results in all the child nodes of the `DocumentFragment` being moved to the child list of this node. + + The children of a `DocumentFragment` node are zero or more nodes representing the tops of any sub-trees defining the structure of the document. + `DocumentFragment` nodes do not need to be well-formed XML documents (although they do need to follow the rules imposed upon well-formed XML parsed entities, which can have multiple top nodes). + For example, a `DocumentFragment` might have only one child and that child node could be a `Text` node. + Such a structure model represents neither an HTML document nor a well-formed XML document. + + When a `DocumentFragment` is inserted into a `Document` (or indeed any other `Node` that may take children) the children of the `DocumentFragment` and not the `DocumentFragment` itself are inserted into the `Node`. + This makes the `DocumentFragment` very useful when the user wishes to create nodes that are siblings; the `DocumentFragment` acts as the parent of these nodes so that the user can use the standard methods from the `Node` interface, such as `insertBefore()` and `appendChild()`. + """ + + def __init__(self, + owner_document: Document, + read_only: bool = False) -> None: + super().__init__(owner_document=owner_document, + node_type=Node.DOCUMENT_FRAGMENT_NODE, + node_name='#document-fragment', + node_value=None, + read_only=read_only) + + +class Attr(Node): + name: DOMString + specified: bool + value: DOMString + + +class Element(Node): + tagName: DOMString + getAttribute: Callable[[DOMString], DOMString] + setAttribute: Callable[[DOMString, DOMString], None] + removeAttribute: Callable[[DOMString], None] + getAttributeNode: Callable[[DOMString], Attr] + setAttributeNode: Callable[[Attr], Attr] + removeAttributeNode: Callable[[Attr], Attr] + getElementsByTagName: Callable[[DOMString], NodeList] + normalize: Callable[[], None] + + +class CharacterData(Node): + data: DOMString + length: c_ulong + substringData: Callable[[c_ulong, c_ulong], DOMString] + appendData: Callable[[DOMString], None] + insertData: Callable[[c_ulong, DOMString], None] + deleteData: Callable[[c_ulong, c_ulong], None] + replaceData: Callable[[c_ulong, c_ulong, DOMString], None] + + +class Comment(CharacterData): + pass + + +class Text(CharacterData): + splitText: Callable[[c_ulong], Text] + + +class CDATASection(Text): + pass + + +class DocumentType(Node): + name: DOMString + entities: NamedNodeMap + notations: NamedNodeMap + + +class Notation(Node): + publicId: DOMString + systemId: DOMString + + +class Entity(Node): + publicId: DOMString + systemId: DOMString + notationName: DOMString + + +class EntityReference(Node): + pass + + +class ProcessingInstruction(Node): + target: DOMString + data: DOMString + + +class Document(Node): + """Interface Document + + The `Document` interface represents the entire HTML or XML document. + Conceptually, it is the root of the document tree, and provides the primary access to the document's data. + + Since elements, text nodes, comments, processing instructions, etc. cannot exist outside the context of a `Document`, the `Document` interface also contains the factory methods needed to create these objects. + The `Node` objects created have a `ownerDocument`` attribute which associates them with the `Document` within whose context they were created. + """ + + doctype: DocumentType + implementation: DOMImplementation + documentElement: Element + createElement: Callable[[Document, DOMString], Element] + createDocumentFragment: Callable[[Document], DocumentFragment] + createTextNode: Callable[[Document, DOMString], Text] + createComment: Callable[[Document, DOMString], Comment] + createCDATASection: Callable[[Document, DOMString], CDATASection] + createProcessingInstruction: Callable[[ + Document, DOMString, DOMString], ProcessingInstruction] + createAttribute: Callable[[Document, DOMString], Attr] + createEntityReference: Callable[[Document, DOMString], EntityReference] + getElementsByTagName: Callable[[Document, DOMString], NodeList] + + def __init__(self, read_only: bool = False) -> None: + super().__init__(owner_document=None, + node_type=Node.DOCUMENT_NODE, + node_name='#document', + node_value=None, + read_only=read_only) diff --git a/w3/python/core/DOMString.py b/w3/python/core/type.py similarity index 100% rename from w3/python/core/DOMString.py rename to w3/python/core/type.py diff --git a/w3/python/typing/__init__.py b/w3/python/typing/__init__.py deleted file mode 100644 index c133fff..0000000 --- a/w3/python/typing/__init__.py +++ /dev/null @@ -1,248 +0,0 @@ -"""Module for typing purpose only. -Do not import below classes for actual usage. -""" - -from __future__ import annotations - -from typing import Iterable, Optional, Union - - -_DOMString = str - - -class _ExceptionCode(int): - INDEX_SIZE_ERR = 1 - DOMSTRING_SIZE_ERR = 2 - HIERARCHY_REQUEST_ERR = 3 - WRONG_DOCUMENT_ERR = 4 - INVALID_CHARACTER_ERR = 5 - NO_DATA_ALLOWED_ERR = 6 - NO_MODIFICATION_ALLOWED_ERR = 7 - NOT_FOUND_ERR = 8 - NOT_SUPPORTED_ERR = 9 - INUSE_ATTRIBUTE_ERR = 10 - - -class _DOMException(Exception): - code: int - - -class _DOMImplementation: - def hasFeature(self, - feature: _DOMString, - version: _DOMString) -> bool: pass - - -class _NodeType(int): - ELEMENT_NODE = 1 - ATTRIBUTE_NODE = 2 - TEXT_NODE = 3 - CDATA_SECTION_NODE = 4 - ENTITY_REFERENCE_NODE = 5 - ENTITY_NODE = 6 - PROCESSING_INSTRUCTION_NODE = 7 - COMMENT_NODE = 8 - DOCUMENT_NODE = 9 - DOCUMENT_TYPE_NODE = 10 - DOCUMENT_FRAGMENT_NODE = 11 - NOTATION_NODE = 12 - - -class _Node: - def __init__(self: _Node, - node_type: _NodeType, - node_name: _DOMString, - owner_document: Optional[_Document], - node_value: Optional[_DOMString] = None, - child_nodes: Optional[Iterable[_AnyNode]] = None, - attributes: Optional[Iterable[_AnyNode]] = None, - read_only: bool = False) -> None: pass - node_value: _DOMString - @property - def node_name(self) -> _DOMString: pass - @property - def node_type(self) -> _NodeType: pass - @property - def parent_node(self) -> Optional[_AnyNode]: pass - @property - def child_nodes(self) -> _NodeList: pass - @property - def first_child(self) -> Optional[_AnyNode]: pass - @property - def last_child(self) -> Optional[_AnyNode]: pass - @property - def previous_sibling(self) -> Optional[_AnyNode]: pass - @property - def next_sibling(self) -> Optional[_AnyNode]: pass - @property - def attributes(self) -> _NamedNodeMap: pass - @property - def owner_document(self) -> Optional[_Document]: pass - - def insert_before(self, - new_child: _AnyNode, - ref_child: Optional[_AnyNode] = None) -> _AnyNode: pass - - def replace_child(self, - new_child: _AnyNode, - old_child: _AnyNode) -> _AnyNode: pass - def remove_child(self, - old_child: _AnyNode) -> _AnyNode: pass - - def append_child(self, new_child: _AnyNode) -> _AnyNode: pass - def has_child_nodes(self) -> bool: pass - def clone_node(self, deep: bool = False) -> _AnyNode: pass - - -class _NodeList: - @property - def length(self) -> int: pass - def item(self, index: int) -> _Node: pass - - -class _NamedNodeMap: - def getNamedItem(self, name: _DOMString) -> _Node: pass - def setNamedItem(self, arg: _Node) -> _Node: pass - def removeNamedItem(self, name: _DOMString) -> _Node: pass - def item(self, index: int) -> _Node: pass - @property - def length(self) -> int: pass - - -class _DocumentFragment(_Node): - pass - - -class _Document(_Node): - @property - def doctype(self) -> _DocumentType: pass - @property - def implementation(self) -> _DOMImplementation: pass - @property - def documentElement(self) -> _Element: pass - def createElement(self, tagName: _DOMString) -> _Element: pass - def createDocumentFragment(self) -> _DocumentFragment: pass - def createTextNode(self, data: _DOMString) -> _Text: pass - def createComment(self, data: _DOMString) -> _Comment: pass - def createCDATASection(self, data: _DOMString) -> _CDATASection: pass - - def createProcessingInstruction(self, - target: _DOMString, - data: _DOMString) -> _ProcessingInstruction: pass - - def createAttribute(self, name: _DOMString) -> _Attr: pass - def createEntityReference(self, name: _DOMString) -> _EntityReference: pass - def getElementsByTagName(self, tagname: _DOMString) -> _NodeList: pass - - -class _CharacterData(_Node): - data: _DOMString - @property - def length(self) -> int: pass - - def substringData(self, - offset: int, - count: int) -> _DOMString: pass - - def appendData(self, - arg: _DOMString) -> None: pass - - def insertData(self, - offset: int, - arg: _DOMString) -> None: pass - - def deleteData(self, - offset: int, - count: int) -> None: pass - - def replaceData(self, - offset: int, - count: int, - arg: _DOMString) -> None: pass - - -class _Attr(_Node): - value: _DOMString - @property - def name(self) -> _DOMString: pass - @property - def specified(self) -> bool: pass - - -class _Element(_Node): - @property - def tagName(self) -> _DOMString: pass - def getAttribute(self, name: _DOMString) -> _DOMString: pass - - def setAttribute(self, - name: _DOMString, - value: _DOMString) -> None: pass - - def removeAttribute(self, name: _DOMString) -> None: pass - def getAttributeNode(self, name: _DOMString) -> _Attr: pass - def setAttributeNode(self, newAttr: _Attr) -> _Attr: pass - def removeAttributeNode(self, oldAttr: _Attr) -> _Attr: pass - def getElementsByTagName(self, name: _DOMString) -> _NodeList: pass - def normalize(self) -> None: pass - - -class _Text(_CharacterData): - def splitText(self, offset: int) -> _Text: pass - - -class _Comment(_CharacterData): - pass - - -class _CDATASection(_Text): - pass - - -class _DocumentType(_Node): - @property - def name(self) -> _DOMString: pass - @property - def entities(self) -> _NamedNodeMap: pass - @property - def notations(self) -> _NamedNodeMap: pass - - -class _Notation(_Node): - @property - def publicId(self) -> _DOMString: pass - @property - def systemId(self) -> _DOMString: pass - - -class _Entity(_Node): - @property - def publicId(self) -> _DOMString: pass - @property - def systemId(self) -> _DOMString: pass - @property - def notationName(self) -> _DOMString: pass - - -class _EntityReference(_Node): - pass - - -class _ProcessingInstruction(_Node): - data: _DOMString - @property - def target(self) -> _DOMString: pass - - -_AnyNode = Union[_Node, - _Document, - _DocumentFragment, - _DocumentType, - _EntityReference, - _Element, - _Attr, - _ProcessingInstruction, - _Comment, - _Text, - _CDATASection, - _Entity, - _Notation]