se extrae libreria de etrivial
FossilOrigin-Name: 5cae2a8c5985850e97b3d416ba14a90c66e2c05e3a4f9c28fdbc767d6c29748f
This commit is contained in:
193
facho/facho.py
Normal file
193
facho/facho.py
Normal file
@@ -0,0 +1,193 @@
|
||||
# This file is part of facho. The COPYRIGHT file at the top level of
|
||||
# this repository contains the full copyright notices and license terms.
|
||||
|
||||
from lxml import etree
|
||||
from lxml.etree import Element, SubElement, tostring
|
||||
import re
|
||||
|
||||
|
||||
class LXMLBuilder:
|
||||
"""
|
||||
extrae la manipulacion de XML
|
||||
"""
|
||||
# TODO buscar el termino mas adecuado
|
||||
# ya que son varios lo procesos que se
|
||||
# exponen en la misma clase
|
||||
# * creacion
|
||||
# * busquedad
|
||||
# * comparacion
|
||||
|
||||
def __init__(self, nsmap):
|
||||
self.nsmap = nsmap
|
||||
self._re_node_expr = re.compile(r'^(?P<path>((?P<ns>\w+):)?(?P<tag>[a-zA-Z0-9_-]+))(?P<attrs>\[.+\])?')
|
||||
self._re_attrs = re.compile(r'(\w+)\s*=\s*\"?(\w+)\"?')
|
||||
|
||||
def match_expression(self, node_expr):
|
||||
match = re.search(self._re_node_expr, node_expr)
|
||||
return match.groupdict()
|
||||
|
||||
@classmethod
|
||||
def from_string(cls, content, clean_namespaces=False):
|
||||
if clean_namespaces:
|
||||
content = re.sub(r'\<\s*[a-zA-Z\-0-9]+\s*:', '<', content)
|
||||
content = re.sub(r'\<\/\s*[a-zA-Z\-0-9]+\s*:', '</', content)
|
||||
|
||||
return etree.fromstring(content)
|
||||
|
||||
@classmethod
|
||||
def build_element_from_string(cls, string, nsmap):
|
||||
return Element(string, nsmap=nsmap)
|
||||
|
||||
def build_element(self, tag, ns=None, attribs={}):
|
||||
attribs['nsmap'] = ns
|
||||
if ns:
|
||||
tag = '{%s}%s' % (self.nsmap[ns], tag)
|
||||
return Element(tag, **attribs)
|
||||
|
||||
def build_from_expression(self, node_expr):
|
||||
match = re.search(self._re_node_expr, node_expr)
|
||||
expr = match.groupdict()
|
||||
attrs = dict(re.findall(self._re_attrs, expr['attrs'] or ''))
|
||||
attrs['nsmap'] = None
|
||||
if expr['ns'] and expr['tag']:
|
||||
ns = expr['ns']
|
||||
tag = expr['tag']
|
||||
if self.nsmap:
|
||||
node = Element('{%s}%s' % (self.nsmap[ns], tag), **attrs)
|
||||
else:
|
||||
node = Element(tag, **attrs)
|
||||
return node
|
||||
|
||||
return Element(expr['tag'], **attrs)
|
||||
|
||||
def _normalize_tag(self, tag):
|
||||
return re.sub(r'^(\{.+\}|.+:)', '', tag)
|
||||
|
||||
def get_tag(self, elem):
|
||||
return self._normalize_tag(elem.tag)
|
||||
|
||||
def same_tag(self, a, b):
|
||||
return self._normalize_tag(a) \
|
||||
== self._normalize_tag(b)
|
||||
|
||||
def find_relative(self, elem, xpath, ns):
|
||||
return elem.find(xpath, ns)
|
||||
|
||||
def append(self, elem, child):
|
||||
elem.append(child)
|
||||
|
||||
def set_text(self, elem, text):
|
||||
elem.text = text
|
||||
|
||||
def get_text(self, elem):
|
||||
return elem.text
|
||||
|
||||
def set_attribute(self, elem, key, value):
|
||||
elem.attrib[key] = value
|
||||
|
||||
def tostring(self, elem):
|
||||
return tostring(elem).decode('utf-8')
|
||||
|
||||
|
||||
class FachoXML:
|
||||
"""
|
||||
Decora XML con funciones de consulta XPATH de un solo elemento
|
||||
"""
|
||||
def __init__(self, root, builder=None, nsmap=None):
|
||||
if builder is None:
|
||||
self.builder = LXMLBuilder(nsmap)
|
||||
else:
|
||||
self.builder = builder
|
||||
|
||||
self.nsmap = nsmap
|
||||
|
||||
if isinstance(root, str):
|
||||
self.root = self.builder.build_element_from_string(root, nsmap)
|
||||
else:
|
||||
self.root = root
|
||||
|
||||
self.xpath_for = {}
|
||||
self.extensions = []
|
||||
|
||||
def add_extension(self, extension):
|
||||
self.extensions.append(extension)
|
||||
|
||||
def attach_extensions(self):
|
||||
root_tag = self.builder.get_tag(self.root)
|
||||
|
||||
# construir las extensiones o adicionar en caso de indicar
|
||||
for extension in self.extensions:
|
||||
xpath, elements = extension.build(self)
|
||||
for new_element in elements:
|
||||
elem = self.find_or_create_element('/'+ root_tag + xpath)
|
||||
self.builder.append(elem, new_element)
|
||||
|
||||
def fragment(self, xpath, append=False):
|
||||
parent = self.find_or_create_element(xpath, append=append)
|
||||
return FachoXML(parent, nsmap=self.nsmap)
|
||||
|
||||
def register_alias_xpath(self, alias, xpath):
|
||||
self.xpath_for[alias] = xpath
|
||||
|
||||
def _normalize_xpath(self, xpath):
|
||||
return xpath.replace('//', '/')
|
||||
|
||||
def find_or_create_element(self, xpath, append=False):
|
||||
"""
|
||||
@param xpath ruta xpath para crear o consultar de un solo elemendo
|
||||
@param append True si se debe adicionar en la ruta xpath indicada
|
||||
@return elemento segun self.builder
|
||||
"""
|
||||
xpath = self._normalize_xpath(xpath)
|
||||
if xpath in self.xpath_for:
|
||||
xpath = self.xpath_for[xpath]
|
||||
|
||||
node_paths = xpath.split('/')
|
||||
node_paths.pop(0) #remove empty /
|
||||
|
||||
root_node = self.builder.build_from_expression(node_paths[0])
|
||||
if not self.builder.same_tag(root_node.tag, self.root.tag):
|
||||
raise ValueError('xpath %s must be absolute to /%s' % (xpath, self.root.tag))
|
||||
else:
|
||||
node_paths.pop(0)
|
||||
|
||||
# crea jerarquia segun xpath indicado
|
||||
parent = None
|
||||
current_elem = self.root
|
||||
for node_path in node_paths:
|
||||
node_expr = self.builder.match_expression(node_path)
|
||||
node = self.builder.build_from_expression(node_path)
|
||||
child = self.builder.find_relative(current_elem, node_expr['path'], self.nsmap)
|
||||
|
||||
parent = current_elem
|
||||
if child is not None:
|
||||
current_elem = child
|
||||
else:
|
||||
self.builder.append(current_elem, node)
|
||||
current_elem = node
|
||||
|
||||
# se fuerza la adicion como un nuevo elemento
|
||||
if append:
|
||||
node = self.builder.build_from_expression(node_paths[-1])
|
||||
self.builder.append(parent, node)
|
||||
return node
|
||||
|
||||
return current_elem
|
||||
|
||||
def set_element(self, xpath, content, **attrs):
|
||||
xpath = self._normalize_xpath(xpath)
|
||||
format_ = attrs.pop('format_', '%s')
|
||||
elem = self.find_or_create_element(xpath)
|
||||
if content:
|
||||
self.builder.set_text(elem, format_ % content)
|
||||
for k, v in attrs.items():
|
||||
self.builder.set_attribute(elem, k, v)
|
||||
return elem
|
||||
|
||||
def get_element_text(self, xpath, format_=str):
|
||||
xpath = self._normalize_xpath(xpath)
|
||||
text = self.builder.get_text(self.find_or_create_element(xpath))
|
||||
return format_(text)
|
||||
|
||||
def tostring(self):
|
||||
return self.builder.tostring(self.root)
|
||||
Reference in New Issue
Block a user