1#!/usr/bin/env python
2# vim:fileencoding=utf-8
3# License: Apache 2.0 Copyright: 2017, Kovid Goyal <kovid at kovidgoyal.net>
4
5from __future__ import absolute_import, division, print_function, unicode_literals
6
7from xml.dom.minidom import getDOMImplementation
8
9from lxml.etree import _Comment
10
11impl = getDOMImplementation()
12
13try:
14    dict_items = dict.iteritems
15except AttributeError:
16    dict_items = dict.items
17
18
19def elem_name_parts(elem):
20    tag = elem.tag
21    if tag.startswith('{'):
22        uri, _, name = tag.rpartition('}')
23        if elem.prefix:
24            name = elem.prefix + ':' + name
25        return uri[1:], name
26    return None, tag
27
28
29def attr_name_parts(name, elem, val):
30    if name.startswith('{'):
31        uri, _, name = name.rpartition('}')
32        uri = uri[1:]
33        for prefix, quri in dict_items(elem.nsmap):
34            if quri == uri:
35                break
36        else:
37            prefix = None
38        if prefix:
39            name = prefix + ':' + name
40        return uri, name, val
41    return None, name, val
42
43
44def add_namespace_declarations(src, dest):
45    changed = src.nsmap
46    if changed:
47        p = src.getparent()
48        if p is not None:
49            # Only add namespace declarations different from the parent's
50            p = p.nsmap or {}
51            changed = {k: v for k, v in dict_items(changed) if v != p.get(k)}
52        for prefix, uri in dict_items(changed):
53            attr = ('xmlns:' + prefix) if prefix else 'xmlns'
54            dest.setAttributeNS('xmlns', attr, uri)
55
56
57def adapt(source_tree, return_root=True, **kw):
58    source_root = source_tree.getroot()
59    uri, qname = elem_name_parts(source_root)
60    dest_tree = impl.createDocument(uri, qname, None)
61    dest_tree.doctype = source_tree.docinfo.doctype
62    dest_root = dest_tree.documentElement
63    stack = [(source_root, dest_root)]
64    while stack:
65        src, dest = stack.pop()
66        if src.text:
67            dest.appendChild(dest_tree.createTextNode(src.text))
68        add_namespace_declarations(src, dest)
69        for name, val in src.items():
70            dest.setAttributeNS(*attr_name_parts(name, src, val))
71        for child in src.iterchildren():
72            if isinstance(child, _Comment):
73                dchild = dest_tree.createComment((child.text or '').replace('--', '—'))
74            else:
75                dchild = dest_tree.createElementNS(*elem_name_parts(child))
76                stack.append((child, dchild))
77            dest.appendChild(dchild)
78            if child.tail:
79                dest.appendChild(dest_tree.createTextNode(child.tail))
80
81    return dest_root if return_root else dest_tree
82