1from __future__ import absolute_import, division, unicode_literals
2
3
4try:
5    from collections.abc import MutableMapping
6except ImportError:  # Python 2.7
7    from collections import MutableMapping
8from xml.dom import minidom, Node
9import weakref
10
11from . import base
12from .. import constants
13from ..constants import namespaces
14from .._utils import moduleFactoryFactory
15
16
17def getDomBuilder(DomImplementation):
18    Dom = DomImplementation
19
20    class AttrList(MutableMapping):
21        def __init__(self, element):
22            self.element = element
23
24        def __iter__(self):
25            return iter(self.element.attributes.keys())
26
27        def __setitem__(self, name, value):
28            if isinstance(name, tuple):
29                raise NotImplementedError
30            else:
31                attr = self.element.ownerDocument.createAttribute(name)
32                attr.value = value
33                self.element.attributes[name] = attr
34
35        def __len__(self):
36            return len(self.element.attributes)
37
38        def items(self):
39            return list(self.element.attributes.items())
40
41        def values(self):
42            return list(self.element.attributes.values())
43
44        def __getitem__(self, name):
45            if isinstance(name, tuple):
46                raise NotImplementedError
47            else:
48                return self.element.attributes[name].value
49
50        def __delitem__(self, name):
51            if isinstance(name, tuple):
52                raise NotImplementedError
53            else:
54                del self.element.attributes[name]
55
56    class NodeBuilder(base.Node):
57        def __init__(self, element):
58            base.Node.__init__(self, element.nodeName)
59            self.element = element
60
61        namespace = property(lambda self: hasattr(self.element, "namespaceURI") and
62                             self.element.namespaceURI or None)
63
64        def appendChild(self, node):
65            node.parent = self
66            self.element.appendChild(node.element)
67
68        def insertText(self, data, insertBefore=None):
69            text = self.element.ownerDocument.createTextNode(data)
70            if insertBefore:
71                self.element.insertBefore(text, insertBefore.element)
72            else:
73                self.element.appendChild(text)
74
75        def insertBefore(self, node, refNode):
76            self.element.insertBefore(node.element, refNode.element)
77            node.parent = self
78
79        def removeChild(self, node):
80            if node.element.parentNode == self.element:
81                self.element.removeChild(node.element)
82            node.parent = None
83
84        def reparentChildren(self, newParent):
85            while self.element.hasChildNodes():
86                child = self.element.firstChild
87                self.element.removeChild(child)
88                newParent.element.appendChild(child)
89            self.childNodes = []
90
91        def getAttributes(self):
92            return AttrList(self.element)
93
94        def setAttributes(self, attributes):
95            if attributes:
96                for name, value in list(attributes.items()):
97                    if isinstance(name, tuple):
98                        if name[0] is not None:
99                            qualifiedName = (name[0] + ":" + name[1])
100                        else:
101                            qualifiedName = name[1]
102                        self.element.setAttributeNS(name[2], qualifiedName,
103                                                    value)
104                    else:
105                        self.element.setAttribute(
106                            name, value)
107        attributes = property(getAttributes, setAttributes)
108
109        def cloneNode(self):
110            return NodeBuilder(self.element.cloneNode(False))
111
112        def hasContent(self):
113            return self.element.hasChildNodes()
114
115        def getNameTuple(self):
116            if self.namespace is None:
117                return namespaces["html"], self.name
118            else:
119                return self.namespace, self.name
120
121        nameTuple = property(getNameTuple)
122
123    class TreeBuilder(base.TreeBuilder):  # pylint:disable=unused-variable
124        def documentClass(self):
125            self.dom = Dom.getDOMImplementation().createDocument(None, None, None)
126            return weakref.proxy(self)
127
128        def insertDoctype(self, token):
129            name = token["name"]
130            publicId = token["publicId"]
131            systemId = token["systemId"]
132
133            domimpl = Dom.getDOMImplementation()
134            doctype = domimpl.createDocumentType(name, publicId, systemId)
135            self.document.appendChild(NodeBuilder(doctype))
136            if Dom == minidom:
137                doctype.ownerDocument = self.dom
138
139        def elementClass(self, name, namespace=None):
140            if namespace is None and self.defaultNamespace is None:
141                node = self.dom.createElement(name)
142            else:
143                node = self.dom.createElementNS(namespace, name)
144
145            return NodeBuilder(node)
146
147        def commentClass(self, data):
148            return NodeBuilder(self.dom.createComment(data))
149
150        def fragmentClass(self):
151            return NodeBuilder(self.dom.createDocumentFragment())
152
153        def appendChild(self, node):
154            self.dom.appendChild(node.element)
155
156        def testSerializer(self, element):
157            return testSerializer(element)
158
159        def getDocument(self):
160            return self.dom
161
162        def getFragment(self):
163            return base.TreeBuilder.getFragment(self).element
164
165        def insertText(self, data, parent=None):
166            data = data
167            if parent != self:
168                base.TreeBuilder.insertText(self, data, parent)
169            else:
170                # HACK: allow text nodes as children of the document node
171                if hasattr(self.dom, '_child_node_types'):
172                    # pylint:disable=protected-access
173                    if Node.TEXT_NODE not in self.dom._child_node_types:
174                        self.dom._child_node_types = list(self.dom._child_node_types)
175                        self.dom._child_node_types.append(Node.TEXT_NODE)
176                self.dom.appendChild(self.dom.createTextNode(data))
177
178        implementation = DomImplementation
179        name = None
180
181    def testSerializer(element):
182        element.normalize()
183        rv = []
184
185        def serializeElement(element, indent=0):
186            if element.nodeType == Node.DOCUMENT_TYPE_NODE:
187                if element.name:
188                    if element.publicId or element.systemId:
189                        publicId = element.publicId or ""
190                        systemId = element.systemId or ""
191                        rv.append("""|%s<!DOCTYPE %s "%s" "%s">""" %
192                                  (' ' * indent, element.name, publicId, systemId))
193                    else:
194                        rv.append("|%s<!DOCTYPE %s>" % (' ' * indent, element.name))
195                else:
196                    rv.append("|%s<!DOCTYPE >" % (' ' * indent,))
197            elif element.nodeType == Node.DOCUMENT_NODE:
198                rv.append("#document")
199            elif element.nodeType == Node.DOCUMENT_FRAGMENT_NODE:
200                rv.append("#document-fragment")
201            elif element.nodeType == Node.COMMENT_NODE:
202                rv.append("|%s<!-- %s -->" % (' ' * indent, element.nodeValue))
203            elif element.nodeType == Node.TEXT_NODE:
204                rv.append("|%s\"%s\"" % (' ' * indent, element.nodeValue))
205            else:
206                if (hasattr(element, "namespaceURI") and
207                        element.namespaceURI is not None):
208                    name = "%s %s" % (constants.prefixes[element.namespaceURI],
209                                      element.nodeName)
210                else:
211                    name = element.nodeName
212                rv.append("|%s<%s>" % (' ' * indent, name))
213                if element.hasAttributes():
214                    attributes = []
215                    for i in range(len(element.attributes)):
216                        attr = element.attributes.item(i)
217                        name = attr.nodeName
218                        value = attr.value
219                        ns = attr.namespaceURI
220                        if ns:
221                            name = "%s %s" % (constants.prefixes[ns], attr.localName)
222                        else:
223                            name = attr.nodeName
224                        attributes.append((name, value))
225
226                    for name, value in sorted(attributes):
227                        rv.append('|%s%s="%s"' % (' ' * (indent + 2), name, value))
228            indent += 2
229            for child in element.childNodes:
230                serializeElement(child, indent)
231        serializeElement(element, 0)
232
233        return "\n".join(rv)
234
235    return locals()
236
237
238# The actual means to get a module!
239getDomModule = moduleFactoryFactory(getDomBuilder)
240