python - machine - nltk phone number
¿Comparar fragmentos de XML? (10)
¿Qué pasa con el siguiente fragmento de código? Se puede mejorar fácilmente para incluir attribs también:
def separator(self):
return "!@#$%^&*" # Very ugly separator
def _traverseXML(self, xmlElem, tags, xpaths):
tags.append(xmlElem.tag)
for e in xmlElem:
self._traverseXML(e, tags, xpaths)
text = ''''
if (xmlElem.text):
text = xmlElem.text.strip()
xpaths.add("/".join(tags) + self.separator() + text)
tags.pop()
def _xmlToSet(self, xml):
xpaths = set() # output
tags = list()
root = ET.fromstring(xml)
self._traverseXML(root, tags, xpaths)
return xpaths
def _areXMLsAlike(self, xml1, xml2):
xpaths1 = self._xmlToSet(xml1)
xpaths2 = self._xmlToSet(xml2)`enter code here`
return xpaths1 == xpaths2
Sobre la base de otra pregunta de SO , ¿cómo se puede verificar si dos fragmentos XML bien formados son semánticamente iguales? Todo lo que necesito es "igual" o no, ya que estoy usando esto para pruebas unitarias.
En el sistema que quiero, estos serían iguales (tenga en cuenta el orden de ''inicio'' y ''final''):
<?xml version=''1.0'' encoding=''utf-8'' standalone=''yes''?>
<Stats start="1275955200" end="1276041599">
</Stats>
# Reordered start and end
<?xml version=''1.0'' encoding=''utf-8'' standalone=''yes''?>
<Stats end="1276041599" start="1275955200" >
</Stats>
Tengo lmxl y otras herramientas a mi disposición, ¡y una función simple que solo permite el reordenamiento de atributos también funcionaría bien!
Fragmento de trabajo basado en la respuesta de IanB:
from formencode.doctest_xml_compare import xml_compare
# have to strip these or fromstring carps
xml1 = """ <?xml version=''1.0'' encoding=''utf-8'' standalone=''yes''?>
<Stats start="1275955200" end="1276041599"></Stats>"""
xml2 = """ <?xml version=''1.0'' encoding=''utf-8'' standalone=''yes''?>
<Stats end="1276041599" start="1275955200"></Stats>"""
xml3 = """ <?xml version=''1.0'' encoding=''utf-8'' standalone=''yes''?>
<Stats start="1275955200"></Stats>"""
from lxml import etree
tree1 = etree.fromstring(xml1.strip())
tree2 = etree.fromstring(xml2.strip())
tree3 = etree.fromstring(xml3.strip())
import sys
reporter = lambda x: sys.stdout.write(x + "/n")
assert xml_compare(tree1,tree2,reporter)
assert xml_compare(tree1,tree3,reporter) is False
Adaptando share a Python 3 (básicamente, cambia iteritems()
a items()
y basestring
a string
):
from lxml import etree
import xmltodict # pip install xmltodict
def normalise_dict(d):
"""
Recursively convert dict-like object (eg OrderedDict) into plain dict.
Sorts list values.
"""
out = {}
for k, v in dict(d).items():
if hasattr(v, ''iteritems''):
out[k] = normalise_dict(v)
elif isinstance(v, list):
out[k] = []
for item in sorted(v):
if hasattr(item, ''iteritems''):
out[k].append(normalise_dict(item))
else:
out[k].append(item)
else:
out[k] = v
return out
def xml_compare(a, b):
"""
Compares two XML documents (as string or etree)
Does not care about element order
"""
if not isinstance(a, str):
a = etree.tostring(a)
if not isinstance(b, str):
b = etree.tostring(b)
a = normalise_dict(xmltodict.parse(a))
b = normalise_dict(xmltodict.parse(b))
return a == b
Al pensar en este problema, se me ocurrió la siguiente solución que hace que los elementos XML sean comparables y clasificables:
import xml.etree.ElementTree as ET
def cmpElement(x, y):
# compare type
r = cmp(type(x), type(y))
if r: return r
# compare tag
r = cmp(x.tag, y.tag)
if r: return r
# compare tag attributes
r = cmp(x.attrib, y.attrib)
if r: return r
# compare stripped text content
xtext = (x.text and x.text.strip()) or None
ytext = (y.text and y.text.strip()) or None
r = cmp(xtext, ytext)
if r: return r
# compare sorted children
if len(x) or len(y):
return cmp(sorted(x.getchildren()), sorted(y.getchildren()))
return 0
ET._ElementInterface.__lt__ = lambda self, other: cmpElement(self, other) == -1
ET._ElementInterface.__gt__ = lambda self, other: cmpElement(self, other) == 1
ET._ElementInterface.__le__ = lambda self, other: cmpElement(self, other) <= 0
ET._ElementInterface.__ge__ = lambda self, other: cmpElement(self, other) >= 0
ET._ElementInterface.__eq__ = lambda self, other: cmpElement(self, other) == 0
ET._ElementInterface.__ne__ = lambda self, other: cmpElement(self, other) != 0
Aquí una solución simple, convierta XML en diccionarios (con xmltodict ) y compare diccionarios juntos
import json
import xmltodict
class XmlDiff(object):
def __init__(self, xml1, xml2):
self.dict1 = json.loads(json.dumps((xmltodict.parse(xml1))))
self.dict2 = json.loads(json.dumps((xmltodict.parse(xml2))))
def equal(self):
return self.dict1 == self.dict2
prueba de unidad
import unittest
class XMLDiffTestCase(unittest.TestCase):
def test_xml_equal(self):
xml1 = """<?xml version=''1.0'' encoding=''utf-8'' standalone=''yes''?>
<Stats start="1275955200" end="1276041599">
</Stats>"""
xml2 = """<?xml version=''1.0'' encoding=''utf-8'' standalone=''yes''?>
<Stats end="1276041599" start="1275955200" >
</Stats>"""
self.assertTrue(XmlDiff(xml1, xml2).equal())
def test_xml_not_equal(self):
xml1 = """<?xml version=''1.0'' encoding=''utf-8'' standalone=''yes''?>
<Stats start="1275955200">
</Stats>"""
xml2 = """<?xml version=''1.0'' encoding=''utf-8'' standalone=''yes''?>
<Stats end="1276041599" start="1275955200" >
</Stats>"""
self.assertFalse(XmlDiff(xml1, xml2).equal())
o en el método simple de Python:
import json
import xmltodict
def xml_equal(a, b):
"""
Compares two XML documents (as string or etree)
Does not care about element order
"""
return json.loads(json.dumps((xmltodict.parse(a)))) == json.loads(json.dumps((xmltodict.parse(b))))
Dado que el orden de los atributos no es significativo en XML , quiere ignorar las diferencias debido a los diferentes ordenamientos de atributos y la canonicalización de XML (C14N) ordena los atributos de manera determinista, puede usar ese método para probar la igualdad:
xml1 = b'''''' <?xml version=''1.0'' encoding=''utf-8'' standalone=''yes''?>
<Stats start="1275955200" end="1276041599"></Stats>''''''
xml2 = b'''''' <?xml version=''1.0'' encoding=''utf-8'' standalone=''yes''?>
<Stats end="1276041599" start="1275955200"></Stats>''''''
xml3 = b'''''' <?xml version=''1.0'' encoding=''utf-8'' standalone=''yes''?>
<Stats start="1275955200"></Stats>''''''
import lxml.etree
tree1 = lxml.etree.fromstring(xml1.strip())
tree2 = lxml.etree.fromstring(xml2.strip())
tree3 = lxml.etree.fromstring(xml3.strip())
import io
b1 = io.BytesIO()
b2 = io.BytesIO()
b3 = io.BytesIO()
tree1.getroottree().write_c14n(b1)
tree2.getroottree().write_c14n(b2)
tree3.getroottree().write_c14n(b3)
assert b1.getvalue() == b2.getvalue()
assert b1.getvalue() != b3.getvalue()
Tenga en cuenta que este ejemplo supone Python 3. Con Python 3, el uso de b''''''...''''''
cadenas y io.BytesIO
es obligatorio, mientras que con Python 2 este método también funciona con cadenas normales y io.StringIO
.
El orden de los elementos puede ser significativo en XML, esta puede ser la razón por la cual la mayoría de los otros métodos sugeridos se compararán desiguales si el orden es diferente ... incluso si los elementos tienen los mismos atributos y contenido de texto.
Pero también quería una comparación insensible al orden, así que se me ocurrió esto:
from lxml import etree
import xmltodict # pip install xmltodict
def normalise_dict(d):
"""
Recursively convert dict-like object (eg OrderedDict) into plain dict.
Sorts list values.
"""
out = {}
for k, v in dict(d).iteritems():
if hasattr(v, ''iteritems''):
out[k] = normalise_dict(v)
elif isinstance(v, list):
out[k] = []
for item in sorted(v):
if hasattr(item, ''iteritems''):
out[k].append(normalise_dict(item))
else:
out[k].append(item)
else:
out[k] = v
return out
def xml_compare(a, b):
"""
Compares two XML documents (as string or etree)
Does not care about element order
"""
if not isinstance(a, basestring):
a = etree.tostring(a)
if not isinstance(b, basestring):
b = etree.tostring(b)
a = normalise_dict(xmltodict.parse(a))
b = normalise_dict(xmltodict.parse(b))
return a == b
Puede usar formencode.doctest_xml_compare : la función xml_compare compara dos árboles ElementTree o lxml.
Si toma un enfoque DOM, puede recorrer los dos árboles simultáneamente mientras compara los nodos (tipo de nodo, texto, atributos) sobre la marcha.
Una solución recursiva será la más elegante: solo una comparación adicional en cortocircuito una vez que un par de nodos no son "iguales" o una vez que detecta una hoja en un árbol cuando se trata de una rama en otro, etc.
SimpleTAL utiliza un controlador personalizado xml.sax para comparar documentos xml https://github.com/janbrohl/SimpleTAL/blob/python2/tests/TALTests/XMLTests/TALAttributeTestCases.py#L47-L112 (se comparan los resultados de getXMLChecksum) pero prefiero generar una lista en lugar de un md5-hash
Tuve el mismo problema: dos documentos que quería comparar que tenían los mismos atributos pero en diferentes órdenes.
Parece que XML Canonicalization (C14N) en lxml funciona bien para esto, pero definitivamente no soy un experto en XML. Tengo curiosidad por saber si alguien más puede señalar inconvenientes a este enfoque.
parser = etree.XMLParser(remove_blank_text=True)
xml1 = etree.fromstring(xml_string1, parser)
xml2 = etree.fromstring(xml_string2, parser)
print "xml1 == xml2: " + str(xml1 == xml2)
ppxml1 = etree.tostring(xml1, pretty_print=True)
ppxml2 = etree.tostring(xml2, pretty_print=True)
print "pretty(xml1) == pretty(xml2): " + str(ppxml1 == ppxml2)
xml_string_io1 = StringIO()
xml1.getroottree().write_c14n(xml_string_io1)
cxml1 = xml_string_io1.getvalue()
xml_string_io2 = StringIO()
xml2.getroottree().write_c14n(xml_string_io2)
cxml2 = xml_string_io2.getvalue()
print "canonicalize(xml1) == canonicalize(xml2): " + str(cxml1 == cxml2)
Ejecutar esto me da:
$ python test.py
xml1 == xml2: false
pretty(xml1) == pretty(xml2): false
canonicalize(xml1) == canonicalize(xml2): true