from typing import List import functools import re import os.path from trygvis.eda import EdaException from trygvis.eda.kicad import rdf as kicad_rdf from urllib.parse import quote_plus import xml.etree.ElementTree def _clean(s): if s is None: return None s = s.strip() return None if len(s) == 0 else s def _cleaned_text(e, name): child = e.find(name) if child is None: return None return child.text def _cleaned_attr(e, name): value = e.get(name) if value is None: return None return _clean(value) def _iso_size_to_factor(size): if size == 'k': return 3 if size == 'M': return 6 if size == 'm': return -3 if size == 'u' or size == '\N{GREEK SMALL LETTER MU}' or size == '\N{MICRO SIGN}': return -6 if size == 'n': return -9 if size == 'p': return -12 return None @functools.total_ordering class Value: valueRe = re.compile('([0-9]+\.?[0-9]*)(.*)') typeRe = re.compile('(.*)(H|Hz)$') def __init__(self, text, value, factor, typ): self.text = text self.value = value self.factor = factor self.type = typ def __eq__(self, other): return self.text == other.text def __lt__(self, other): if self.value is not None: if other.value is None: return 1 else: return (self.factor, self.value) < (other.factor, other.value) else: if other.value is None: return self.text < other.text else: return -1 def __str__(self): if self.value is not None: s = str(self.value) + 'e' + str(self.factor) if self.type is not None: s = s + ' ' + self.type return s if self.text is not None: return self.text return 'unknown' @staticmethod def parse(text): if text is None: return Value(None, None, None, None) m = Value.valueRe.match(text) if m: value_int = float(m.group(1)) suffix = m.group(2) m = Value.typeRe.match(suffix) if m: suffix = m.group(1) typ = m.group(2) else: typ = None factor = _iso_size_to_factor(suffix) # cli.info(text + ': suffix:' + suffix + ' => value_int: ' + str(value_int) + ', factor: ' + str( # factor) + ', type=' + str(type)) if value_int is not None and factor is not None: return Value(text, value_int, factor, typ) return Value(text, None, None, None) class Component: refRe = re.compile('([a-zA-Z]+)([0-9]+)') def __init__(self, ref, value, footprint, fields): self.ref = ref # type: str self.value = Value.parse(value) # type: Value self.footprint = footprint # type: str self.fields = fields m = Component.refRe.match(ref) if m: self.ref_class = m.group(1) self.ref_instance = int(m.group(2)) else: self.ref_class = None self.ref_instance = None def __str__(self): s = 'ref=' + self.ref if self.value is not None: s += ', value=' + str(self.value) if self.footprint is not None: s += ', footprint=' + self.footprint for name, value in self.fields.items(): s += ', ' + name + '=' + value return s def find_field(self, key): return self.fields[key] if key in self.fields else None @staticmethod def from_xml(e): ref = e.get('ref') value = _cleaned_text(e, 'value') footprint = _cleaned_text(e, 'footprint') fs = {} fields = e.find('fields') if fields is not None: for field in fields.findall('field'): fs[field.get('name')] = field.text if ref is not None: return Component(ref, value, footprint, fs) return None class TitleBlock(object): def __init__(self, title=None, rev=None, date=None, source=None): self.title = title self.rev = rev self.date = date self.source = source @staticmethod def from_xml(tb): title = _cleaned_text(tb, 'title') rev = _cleaned_text(tb, 'rev') date = _cleaned_text(tb, 'date') source = _cleaned_text(tb, 'source') return TitleBlock(title, rev, date, source) class Sheet(object): def __init__(self, number, name, title_block): self.number = number self.name = name self.title_block = title_block @staticmethod def from_xml(s): number = _cleaned_attr(s, 'number') name = _cleaned_attr(s, 'name') node = s.find('title_block') title_block = TitleBlock.from_xml(node) if node is not None else TitleBlock() return Sheet(int(number) if number is not None else None, name, title_block) class Design(object): def __init__(self, source=None, date=None, tool=None, sheets=None): self.source = source self.date = date self.tool = tool self.sheets = sheets if sheets is not None else [] @staticmethod def from_xml(d): source = _cleaned_text(d, 'source') date = _cleaned_text(d, 'date') tool = _cleaned_text(d, 'tool') sheets = [] for s in d.iterfind('sheet'): sheets.append(Sheet.from_xml(s)) return Design(source, date, tool, sheets) class Export(object): def __init__(self, design, components): self.design = design if design is not None else Design() # type: Design self.components = components # type: List[Component] @staticmethod def from_xml_document(doc) -> 'Export': cs = [] node = doc.find('design') design = Design.from_xml(node) if node is not None else None components = doc.find('components') for e in components.iter('comp'): c = Component.from_xml(e) if c is not None: cs.append(c) return Export(design, cs) @staticmethod def from_xml_file(src) -> 'Export': tree = xml.etree.ElementTree.parse(src) root = tree.getroot() return Export.from_xml_document(root) def component_fields(self): fs = set() for c in self.components: for key, value in c.fields.items(): fs.add(key) return fs # def generate_project_url(self): # s = next((s for s in self.design.sheets if s.number == 1), None) # title = s.title_block.title if s is not None else None # if title is None: # if self.design.source is not None: # title = _clean(os.path.basename(self.design.source)) # # if title is None: # raise EdaException('Could not generate a stable, identifying URL for the project') # # title = quote_plus(title) # return kicad_rdf.KICAD_BOARD[title]