from typing import List, Union, TextIO, Any import logging import datetime from dataclasses import fields from xml.etree import ElementTree from .main import Map, Device logger = logging.getLogger(__name__) def read(stream: TextIO) -> List[Map]: tree = ElementTree.parse(stream) el_root = tree.getroot() if _tag(el_root) != 'Maps': logger.warning(f'Root tag is "{_tag(el_root)}", expected "Maps"') maps = read_wmaps(el_root) return maps def read_wmaps(el_root: ElementTree.Element) -> List[Map]: map_fields = [ff.name for ff in fields(Map)] maps = [] for el_map in el_root: if _tag(el_map) != 'Map': logger.warning(f'Skipping map-level tag "{_tag(el_map)}"') continue wmap = Map() for key, val in el_map.attrib.items(): if key in map_fields and key[0].isupper(): setattr(wmap, key, val) else: wmap.misc[key] = val wmap.devices = read_devices(el_map) maps.append(wmap) return maps def read_devices(el_map: ElementTree.Element) -> List[Device]: dev_fields = [ff.name for ff in fields(Device)] devices = [] for el_device in el_map: if _tag(el_device) != 'Device': logger.warning(f'Skipping device-level tag "{_tag(el_device)}"') continue bin_type = el_device.attrib['BinType'] null_bin: Union[int, str] if bin_type == 'Decimal': null_bin = int(el_device.attrib['NullBin']) else: null_bin = el_device.attrib['NullBin'] device = Device(BinType=bin_type, NullBin=null_bin) val: Any for key, val in el_device.attrib.items(): if key in ('BinType', 'NullBin'): continue if key in ('WaferSize', 'DeviceSizeX', 'DeviceSizeY', 'Orientation'): val = float(val) elif key in ('OriginLocation',): val = int(val) elif key == 'CreateDate': val = datetime.datetime.strptime(val + '000', '%Y%m%d%H%M%S%f') if key in dev_fields and key[0].isupper(): setattr(device, key, val) else: device.misc[key] = val for el_entry in el_device: tag = _tag(el_entry) attrib = el_entry.attrib if tag == 'ReferenceDevice': if device.reference_xy is not None: logger.warning('Duplicate ReferenceDevice entry; overwriting!') xy = (attrib.get('ReferenceDeviceX', None), attrib.get('ReferenceDeviceY', None)) if xy[0] is None or xy[1] is None: logger.error('Malformed ReferenceDevice, ignoring!') continue device.reference_xy = (int(xy[0]), int(xy[1])) elif tag == 'Bin': if 'BinCode' not in attrib: logger.error(f'Bin without any associated BinCode, ' f'with attributes {el_entry.attrib}') continue bin_code = attrib['BinCode'] if bin_code in device.bin_pass: logger.error(f'Bin code {bin_code} was repeated; ignoring later entry!') continue device.bin_pass[bin_code] = attrib['BinQuality'].lower() == 'pass' elif tag == 'Data': data_strs = [read_row(rr) for rr in el_entry] data: Union[List[List[str]], List[List[int]]] if device.BinType == 'Decimal': data = [[int(vv) for vv in rr] for rr in data_strs] else: data = data_strs device.map = data devices.append(device) return devices def read_row(el_row: ElementTree.Element) -> List[str]: assert _tag(el_row) == 'Row' row_stripped = (el_row.text or '').strip() if ' ' in row_stripped or '\t' in row_stripped: row_data = row_stripped.split() else: row_data = list(row_stripped) return row_data def _tag(element: ElementTree.Element) -> str: ''' Get the element's tag, excluding any namespaces. ''' return element.tag.split('}')[-1]