1#!/usr/bin/env python3
2"""Compare native captures while excluding cache identities and selection state."""
3import argparse
4import base64
5import difflib
6import hashlib
7import json
8from pathlib import Path
9import xml.etree.ElementTree as ET
10from native_xml import ns, pages
11
12
13def 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
24def 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
47if __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')