You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
133 lines
4.2 KiB
Python
133 lines
4.2 KiB
Python
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]
|