Source code for networkapiclient.xml_utils

# -*- coding:utf-8 -*-
# Licensed to the Apache Software Foundation (ASF) under one or more
# contributor license agreements.  See the NOTICE file distributed with
# this work for additional information regarding copyright ownership.
# The ASF licenses this file to You under the Apache License, Version 2.0
# (the "License"); you may not use this file except in compliance with
# the License.  You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

from xml.dom import InvalidCharacterErr
from xml.dom.minidom import *
from xml.dom.minicompat import StringTypes
import re


[docs]class XMLErrorUtils(Exception): """Representa um erro ocorrido durante o marshall ou unmarshall do XML.""" def __init__(self, cause, message): self.cause = cause self.message = message def __str__(self): msg = u'Erro ao criar ou ler o XML: Causa: %s, Mensagem: %s' % ( self.cause, self.message) return msg.encode('utf-8')
[docs]class InvalidNodeNameXMLError(XMLErrorUtils): """Nome inválido para representá-lo como uma TAG de XML.""" def __init__(self, cause, message): XMLErrorUtils.__init__(self, cause, message)
[docs]class InvalidNodeTypeXMLError(XMLErrorUtils): """Tipo inválido para o conteúdo de uma TAG de XML.""" def __init__(self, cause, message): XMLErrorUtils.__init__(self, cause, message)
def _add_text_node(value, node, doc): if value is None: return if not isinstance(value, StringTypes): text = r'%s' % unicode(value) else: text = r'%s' % value.replace('%', '%%') try: textNode = doc.createTextNode(text) node.appendChild(textNode) except TypeError as t: raise InvalidNodeTypeXMLError( t, u'Conteúdo de um Nó do XML com tipo de dado inválido: %s ' % value) def _add_list_node(nodeName, list, parent, doc): for value in list: node = doc.createElement(nodeName) parent.appendChild(node) if isinstance(value, dict): _add_nodes_to_parent(value, node, doc) else: _add_text_node(value, node, doc) def _add_nodes_to_parent(map, parent, doc): if map is None: return for key, value in map.iteritems(): try: if isinstance(value, dict): node = doc.createElement(key) parent.appendChild(node) _add_nodes_to_parent(value, node, doc) elif isinstance(value, type([])): _add_list_node(key, value, parent, doc) else: node = doc.createElement(key) parent.appendChild(node) _add_text_node(value, node, doc) except InvalidCharacterErr as i: raise InvalidNodeNameXMLError( i, u'Valor inválido para nome de uma TAG de XML: %s' % key)
[docs]def dumps(map, root_name, root_attributes=None): """Cria um string no formato XML a partir dos elementos do map. Os elementos do mapa serão nós filhos do root_name. Cada chave do map será um Nó no XML. E o valor da chave será o conteúdo do Nó. Exemplos: :: - Mapa: {'networkapi':1} XML: <?xml version="1.0" encoding="UTF-8"?><networkapi>1</networkapi> - Mapa: {'networkapi':{'teste':1}} XML: <?xml version="1.0" encoding="UTF-8"?> <networkapi> <teste>1</teste> </networkapi> - Mapa: {'networkapi':{'teste01':01, 'teste02':02}} XML: <?xml version="1.0" encoding="UTF-8"?> <networkapi> <teste01>01</teste01> <teste02>02</teste02> </networkapi> - Mapa: {'networkapi':{'teste01':01, 'teste02':[02,03,04]}} XML: <?xml version="1.0" encoding="UTF-8"?> <networkapi> <teste01>01</teste01> <teste02>02</teste02> <teste02>03</teste02> <teste02>04</teste02> </networkapi> - Mapa: {'networkapi':{'teste01':01, 'teste02':{'a':1, 'b':2}}} XML: <?xml version="1.0" encoding="UTF-8"?> <networkapi> <teste01>01</teste01> <teste02> <a>1</a> <b>2</b> </teste02> </networkapi> :param map: Dicionário com os dados para serem convertidos em XML. :param root_name: Nome do nó root do XML. :param root_attributes: Dicionário com valores para serem adicionados como atributos para o nó root. :return: XML :raise XMLErrorUtils: Representa um erro ocorrido durante o marshall ou unmarshall do XML. :raise InvalidNodeNameXMLError: Nome inválido para representá-lo como uma TAG de XML. :raise InvalidNodeTypeXMLError: "Tipo inválido para o conteúdo de uma TAG de XML. """ xml = '' try: implementation = getDOMImplementation() except ImportError as i: raise XMLErrorUtils(i, u'Erro ao obter o DOMImplementation') doc = implementation.createDocument(None, root_name, None) try: root = doc.documentElement if (root_attributes is not None): for key, value in root_attributes.iteritems(): attribute = doc.createAttribute(key) attribute.nodeValue = value root.setAttributeNode(attribute) _add_nodes_to_parent(map, root, doc) xml = doc.toxml('UTF-8') except InvalidCharacterErr as i: raise InvalidNodeNameXMLError( i, u'Valor inválido para nome de uma TAG de XML: %s' % root_name) finally: doc.unlink() return xml
[docs]def dumps_networkapi(map, version='1.0'): """Idem ao método dump, porém, define que o nó root é o valor 'networkapi'. :param map: Dicionário com os dados para serem convertidos em XML. :param version: Versão do nó networkapi. A versão será adicionada como atributo do nó. :return: XML :raise XMLErrorUtils: Representa um erro ocorrido durante o marshall ou unmarshall do XML. :raise InvalidNodeNameXMLError: Nome inválido para representá-lo como uma TAG de XML. :raise InvalidNodeTypeXMLError: "Tipo inválido para o conteúdo de uma TAG de XML. """ return dumps(map, 'networkapi', {'versao': version})
def _create_childs_map(parent, force_list): if parent is None: return None if parent.hasChildNodes(): childs = parent.childNodes childs_map = dict() childs_values = [] for i in range(childs.length): child = childs.item(i) if child.nodeType == Node.ELEMENT_NODE: if child.nodeName in childs_map: child_value = _create_childs_map(child, force_list) if child_value is not None: value = childs_map[child.nodeName] if not isinstance(value, type([])): value = [value] value.append(child_value) childs_map[child.nodeName] = value elif child.nodeName in force_list: child_value = _create_childs_map(child, force_list) if child_value is None: child_value = [] else: child_value = [child_value] childs_map[child.nodeName] = child_value else: childs_map[ child.nodeName] = _create_childs_map( child, force_list) elif child.nodeType == Node.TEXT_NODE or child.nodeType == Node.CDATA_SECTION_NODE: if child.data.strip() != '': childs_values.append(child.data.replace('%%', '%')) if len(childs_values) == 0 and len(childs_map) == 0: return None if len(childs_values) != 0 and len(childs_map) != 0: childs_values.append(childs_map) return childs_values if len(childs_values) != 0: if len(childs_values) == 1: return childs_values[0] return childs_values return childs_map elif parent.nodeType == Node.TEXT_NODE or parent.nodeType == Node.CDATA_SECTION_NODE: if parent.data.strip() != '': return parent.data return None
[docs]def loads(xml, force_list=None): """Cria um dicionário com os dados do XML. O dicionário terá como chave o nome do nó root e como valor o conteúdo do nó root. Quando o conteúdo de um nó é uma lista de nós então o valor do nó será um dicionário com uma chave para cada nó. Entretanto, se existir nós, de um mesmo pai, com o mesmo nome, então eles serão armazenados em uma mesma chave do dicionário que terá como valor uma lista. O force_list deverá ter nomes de nós do XML que necessariamente terão seus valores armazenados em uma lista no dicionário de retorno. :: Por exemplo: xml_1 = <?xml version="1.0" encoding="UTF-8"?> <networkapi versao="1.0"> <testes> <teste>1<teste> <teste>2<teste> </testes> </networkapi> A chamada loads(xml_1), irá gerar o dicionário: {'networkapi':{'testes':{'teste':[1,2]}}} xml_2 = <?xml version="1.0" encoding="UTF-8"?> <networkapi versao="1.0"> <testes> <teste>1<teste> </testes> </networkapi> A chamada loads(xml_2), irá gerar o dicionário: {'networkapi':{'testes':{'teste':1}}} A chamada loads(xml_2, ['teste']), irá gerar o dicionário: {'networkapi':{'testes':{'teste':[1]}}} Ou seja, o XML_2 tem apenas um nó 'teste', porém, ao informar o parâmetro 'force_list' com o valor ['teste'], a chave 'teste', no dicionário, terá o valor dentro de uma lista. :param xml: XML :param force_list: Lista com os nomes dos nós do XML que deverão ter seus valores armazenados em lista dentro da chave do dicionário de retorno. :return: Dicionário com os nós do XML. :raise XMLErrorUtils: Representa um erro ocorrido durante o marshall ou unmarshall do XML. """ if force_list is None: force_list = [] try: xml = remove_illegal_characters(xml) doc = parseString(xml) except Exception as e: raise XMLErrorUtils(e, u'Falha ao realizar o parse do xml.') root = doc.documentElement map = dict() attrs_map = dict() if root.hasAttributes(): attributes = root.attributes for i in range(attributes.length): attr = attributes.item(i) attrs_map[attr.nodeName] = attr.nodeValue map[root.nodeName] = _create_childs_map(root, force_list) return map
[docs]def remove_illegal_characters(xml): RE_XML_ILLEGAL = u'([\u0000-\u0008\u000b-\u000c\u000e-\u001f\ufffe-\uffff])' + \ u'|' + \ u'([%s-%s][^%s-%s])|([^%s-%s][%s-%s])|([%s-%s]$)|(^[%s-%s])' % \ (unichr(0xd800), unichr(0xdbff), unichr(0xdc00), unichr(0xdfff), unichr(0xd800), unichr(0xdbff), unichr(0xdc00), unichr(0xdfff), unichr(0xd800), unichr(0xdbff), unichr(0xdc00), unichr(0xdfff)) xml = re.sub(RE_XML_ILLEGAL, "?", xml) return xml