| 1 | #!/usr/bin/env python3 |
| 2 | """Compare native captures while excluding cache identities and selection state.""" |
| 3 | import argparse |
| 4 | import base64 |
| 5 | import difflib |
| 6 | import hashlib |
| 7 | import json |
| 8 | from pathlib import Path |
| 9 | import xml.etree.ElementTree as ET |
| 10 | from native_xml import ns, pages |
| 11 | |
| 12 | |
| 13 | def content(node): |
| 14 | attrs = {key: value for key, value in node.attrib.items() |
| 15 | if key not in ('ID', 'objectID', 'pathCache', 'selected', 'isCurrentlyViewed')} |
| 16 | text = node.text |
| 17 | if node.tag == '{%s}Data' % ns['one'] and text: |
| 18 | text = hashlib.sha256(base64.b64decode(''.join(text.split()), validate=True)).hexdigest() |
| 19 | elif text is not None and not text.strip() and node.tag != '{%s}T' % ns['one']: |
| 20 | text = None |
| 21 | return node.tag, sorted(attrs.items()), text, [content(child) for child in node] |
| 22 | |
| 23 | |
| 24 | def compare(actual, expected): |
| 25 | captured = [] |
| 26 | for directory in (actual, expected): |
| 27 | by_id = {page.get('ID'): page for page in pages(directory)} |
| 28 | hierarchy = ET.parse(directory / 'hierarchy.xml') |
| 29 | ordered = [page.get('ID') for page in hierarchy.findall('.//one:Page', ns)] |
| 30 | if len(ordered) != len(by_id) or set(ordered) != set(by_id): |
| 31 | raise ValueError('Native page capture does not match its hierarchy: ' + str(directory)) |
| 32 | captured.append({ |
| 33 | 'pages': [content(by_id[key]) for key in ordered], |
| 34 | 'attachments': sorted(hashlib.sha256(path.read_bytes()).hexdigest() |
| 35 | for path in directory.glob('*.attachment')), |
| 36 | }) |
| 37 | if captured[0] != captured[1]: |
| 38 | difference = '\n'.join(difflib.unified_diff( |
| 39 | json.dumps(captured[1], indent=2, ensure_ascii=False).splitlines(), |
| 40 | json.dumps(captured[0], indent=2, ensure_ascii=False).splitlines(), |
| 41 | fromfile=str(expected), tofile=str(actual), |
| 42 | )) |
| 43 | (actual.parent / 'native.diff').write_text(difference + '\n') |
| 44 | raise AssertionError('Native content differs; inspect ' + str(actual.parent / 'native.diff')) |
| 45 | |
| 46 | |
| 47 | if __name__ == '__main__': |
| 48 | parser = argparse.ArgumentParser(description=__doc__) |
| 49 | parser.add_argument('actual', type=Path) |
| 50 | parser.add_argument('expected', type=Path) |
| 51 | args = parser.parse_args() |
| 52 | compare(args.actual, args.expected) |
| 53 | print('Native document content and attachment bytes match') |