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