try: import doctest doctest.OutputChecker except (AttributeError, ImportError): # Python < 2.4 import util.doctest24 as doctest try: import xml.etree.ElementTree as ET except ImportError: import elementtree.ElementTree as ET from xml.parsers.expat import ExpatError as XMLParseError RealOutputChecker = doctest.OutputChecker def debug(*msg): import sys print >> sys.stderr, ' '.join(map(str, msg)) class HTMLOutputChecker(RealOutputChecker): def check_output(self, want, got, optionflags): normal = RealOutputChecker.check_output(self, want, got, optionflags) if normal or not got: return normal try: want_xml = make_xml(want) except XMLParseError: pass else: try: got_xml = make_xml(got) except XMLParseError: pass else: if xml_compare(want_xml, got_xml): return True return False def output_difference(self, example, got, optionflags): actual = RealOutputChecker.output_difference( self, example, got, optionflags) want_xml = got_xml = None try: want_xml = make_xml(example.want) want_norm = make_string(want_xml) except XMLParseError as e: if example.want.startswith('<'): want_norm = '(bad XML: %s)' % e # '%s' % example.want else: return actual try: got_xml = make_xml(got) got_norm = make_string(got_xml) except XMLParseError as e: if example.want.startswith('<'): got_norm = '(bad XML: %s)' % e else: return actual s = '%s\nXML Wanted: %s\nXML Got : %s\n' % ( actual, want_norm, got_norm) if got_xml and want_xml: result = [] xml_compare(want_xml, got_xml, result.append) s += 'Difference report:\n%s\n' % '\n'.join(result) return s def xml_sort(children): tcl1 = {} #idx = 0 for child in children: if 'name' in child.attrib: key = child.attrib['name'] else: key = child.tag if key not in tcl1: tcl1[key] = [] tcl1[key].append(child) cl1_keys = list(tcl1.keys()) cl1_keys.sort() cl1 = [] for key in cl1_keys: cl1.extend(tcl1[key]) return cl1 def xml_compare(x1, x2): if x1.tag != x2.tag: print ('Tags do not match: %s and %s' % (x1.tag, x2.tag)) return False for name, value in x1.attrib.items(): if x2.attrib.get(name) != value: print ('Attributes do not match: %s=%r, %s=%r' % (name, value, name, x2.attrib.get(name))) return False for name in x2.attrib: if name not in x1.attrib: print ('x2 has an attribute x1 is missing: %s' % name) return False if not text_compare(x1.text, x2.text): print ('text: %r != %r' % (x1.text, x2.text)) return False if not text_compare(x1.tail, x2.tail): print ('tail: %r != %r' % (x1.tail, x2.tail)) return False cl1 = xml_sort(x1.getchildren()) cl2 = xml_sort(x2.getchildren()) if len(cl1) != len(cl2): cl1_tags = [] for c in cl1: cl1_tags.append(c.tag) cl2_tags = [] for c in cl2: cl2_tags.append(c.tag) print ('children length differs, %i != %i (%s != %s)' % (len(cl1), len(cl2), cl1_tags, cl2_tags)) return False i = 0 for c1, c2 in zip(cl1, cl2): i += 1 if not xml_compare(c1, c2): if 'name' in c1.attrib: name = c1.attrib['name'] else: name = i print ('in tag "%s" with name "%s"' % (c1.tag, name)) return False return True def text_compare(t1, t2): if not t1 and not t2: return True if t1 == '*' or t2 == '*': return True return (t1 or '').strip() == (t2 or '').strip() def make_xml(s): return ET.XML('%s' % s) def make_string(xml): if isinstance(xml, (str, unicode)): xml = make_xml(xml) s = ET.tostring(xml) if s == '': return '' assert s.startswith('') and s.endswith(''), repr(s) return s[5:-6] def install(): doctest.OutputChecker = HTMLOutputChecker