aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/ee/db.py143
-rw-r--r--src/ee/digikey/__init__.py41
-rw-r--r--src/ee/digikey/search_parts.py13
-rw-r--r--src/ee/order/__init__.py119
-rw-r--r--src/ee/order/templates/order.rst.j260
-rw-r--r--src/ee/part/__init__.py5
-rw-r--r--src/ee/project/report.py25
-rw-r--r--src/ee/xml/types.py221
-rw-r--r--xsd/ee.xsd14
9 files changed, 573 insertions, 68 deletions
diff --git a/src/ee/db.py b/src/ee/db.py
new file mode 100644
index 0000000..e471278
--- /dev/null
+++ b/src/ee/db.py
@@ -0,0 +1,143 @@
+from typing import TypeVar, Generic, Callable, MutableMapping, List, Iterable, Union, Any
+
+K = TypeVar('K')
+V = TypeVar('V')
+
+KeyCallable = Callable[[V], Union[K, Iterable[K]]]
+
+__all__ = [
+ "Index",
+ "ListIndex",
+ "UniqueIndex",
+ "ObjDb",
+]
+
+
+class Index(Generic[K, V]):
+ def add(self, value: V):
+ pass
+
+ def get(self, key: K) -> Iterable[V]:
+ pass
+
+ def clear(self):
+ pass
+
+ def __iter__(self):
+ pass
+
+ def items(self):
+ pass
+
+ def values(self):
+ pass
+
+
+class ListIndex(Index[K, V]):
+ def __init__(self, key_callable: KeyCallable):
+ self.idx: MutableMapping[K, V] = {}
+ self.key_callable = key_callable
+
+ def add(self, value: V):
+ keys = self.key_callable(value)
+
+ if keys is None:
+ return
+
+ for key in keys:
+ if key is None:
+ continue
+
+ values = self.idx.get(key, None)
+ if values is not None:
+ values.append(value)
+ else:
+ self.idx[key] = [value]
+
+ def get(self, key: K) -> Iterable[V]:
+ return [self.idx[key]]
+
+ def clear(self):
+ return self.idx.clear()
+
+ def __iter__(self):
+ return self.idx.__iter__()
+
+ def items(self):
+ return self.idx.items()
+
+ def values(self):
+ return self.idx.values()
+
+
+class UniqueIndex(Index[K, V]):
+ def __init__(self, key_callable: KeyCallable):
+ self.idx: MutableMapping[K, V] = {}
+ self.key_callable = key_callable
+
+ def get(self, key: K) -> Iterable[V]:
+ return [self.idx[key]]
+
+ def get_single(self, key: K) -> V:
+ return self.idx[key]
+
+ def add(self, value: V):
+ keys = self.key_callable(value)
+
+ if not isinstance(keys, Iterable):
+ keys = [keys]
+
+ for key in keys:
+ key = self.key_callable(value)
+ present = self.idx.get(key, None)
+ if present is not None:
+ raise KeyError("Duplicate part in index: {}".format(key))
+
+ self.idx[key] = True
+
+ def clear(self):
+ return self.idx.clear()
+
+ def __iter__(self):
+ return self.idx.__iter__()
+
+ def items(self):
+ return self.idx.items()
+
+ def values(self):
+ return self.idx.values()
+
+
+class ObjDb(Generic[V]):
+ def __init__(self):
+ self.values: List[V] = []
+ self._indexes: MutableMapping[str, Index[Any, V]] = {}
+
+ def add(self, value: V):
+ for idx in self._indexes.values():
+ idx.add(value)
+
+ self.values.append(value)
+
+ def __iter__(self):
+ return self.values.__iter__()
+
+ def __len__(self) -> int:
+ return len(self.values)
+
+ def add_index(self, name, key_callable: KeyCallable) -> ListIndex[Any, V]:
+ idx = ListIndex(key_callable)
+ self._indexes[name] = idx
+
+ for value in self.values:
+ idx.add(value)
+
+ return idx
+
+ def add_unique_index(self, name, key_callable: KeyCallable) -> UniqueIndex[Any, V]:
+ idx = UniqueIndex(key_callable)
+ self._indexes[name] = idx
+ return idx
+
+ def index(self, name) -> Index:
+ return self._indexes[name]
diff --git a/src/ee/digikey/__init__.py b/src/ee/digikey/__init__.py
index d0405f1..2fe7443 100644
--- a/src/ee/digikey/__init__.py
+++ b/src/ee/digikey/__init__.py
@@ -79,6 +79,13 @@ class PriceBreak(object):
self.per_quantity_price = per_quantity_price
+class Document(object):
+ def __init__(self, kind: str, title: str, url: str):
+ self.kind = kind
+ self.title = title
+ self.url = url
+
+
@total_ordering
class DigikeyProduct(object):
def __init__(self, part_number, mpn, url, attributes: List["DigikeyAttributeValue"] = None, categories=None):
@@ -90,6 +97,7 @@ class DigikeyProduct(object):
self.quantity_available = None
self.description = None
self.price_breaks: List[PriceBreak] = []
+ self.documents: List[Document] = []
assert self.part_number
assert self.mpn
@@ -303,6 +311,7 @@ class DigikeyParser(object):
if part_number and mpn:
p = DigikeyProduct(part_number, mpn, url, attributes, categories)
p.price_breaks = self._parse_price_breaks(tree)
+ p.documents = self._parse_documents(tree)
for n in tree.xpath("//*[@itemprop='description']"):
p.description = _to_string(n)
return p
@@ -348,6 +357,38 @@ class DigikeyParser(object):
return price_breaks if ok else []
@staticmethod
+ def _parse_documents(tree: html) -> List[Document]:
+ docs = []
+
+ for row in tree.xpath("//*[@class='product-details-documents-media product-details-section']//tr"):
+ # print("row={}".format(row))
+ kind: str = _first(row.xpath(".//th/text()"))
+
+ if not kind:
+ continue
+
+ kind = kind.strip()
+
+ for a in row.xpath(".//td//a[not(contains(@class, '-expander-toggle'))]"):
+ # print("a={}".format(a))
+ title = a.text
+ if not title:
+ continue
+ title = title.strip()
+
+ href = a.get("href")
+ if not href:
+ continue
+
+ href = href.strip()
+ if href.startswith("//"):
+ href = "https:" + href
+
+ docs.append(Document(kind, title, href))
+
+ return docs
+
+ @staticmethod
def _handle_product_table(tree: html, res: DigikeySearchResponse):
products = tree.xpath("//*[@itemtype='http://schema.org/Product']")
diff --git a/src/ee/digikey/search_parts.py b/src/ee/digikey/search_parts.py
index 3d271d5..358266a 100644
--- a/src/ee/digikey/search_parts.py
+++ b/src/ee/digikey/search_parts.py
@@ -13,9 +13,19 @@ def resolved(p: DigikeyProduct) -> types.Part:
# TODO: fix uri
part = types.Part(uri="https://digikey.com/pn#{}".format(p.part_number),
distributor_info=types.DistributorInfo(),
+ links=types.LinkList(),
facts=types.FactList(),
references=types.ReferencesList())
part.distributor_infoProp.stateProp = "resolved"
+ links = part.linksProp.link
+
+ if p.url:
+ links.append(types.Link(url=p.url, relation="canonical", media_type="text/html"))
+
+ for d in p.documents:
+ title = "{}: {}".format(d.kind, d.title)
+ links.append(types.Link(url=d.url, relation="http://purl.org/ee/link-relation#documentation",
+ media_type="text/html", title=title))
supplier_part_numbers = part.referencesProp.supplier_part_numberProp
supplier_part_numbers.append(types.SupplierPartNumber(value=p.part_number, supplier=DIGIKEY_URI))
@@ -48,7 +58,8 @@ def search_parts(in_path: Path, out_path: Path, cache_dir: Path):
client = DigikeyClient(cache_dir)
for part in in_db.iterparts():
- dpn = next((p.valueProp for p in bom_file_utils.supplier_part_numbers(part) if p.supplierProp == DIGIKEY_URI), None)
+ dpn = next((p.valueProp for p in bom_file_utils.supplier_part_numbers(part)
+ if p.supplierProp == DIGIKEY_URI), None)
mpn = next((p.valueProp for p in bom_file_utils.part_numbers(part)), None)
is_mpn = query = None
diff --git a/src/ee/order/__init__.py b/src/ee/order/__init__.py
index 1652f24..a384cb7 100644
--- a/src/ee/order/__init__.py
+++ b/src/ee/order/__init__.py
@@ -1,9 +1,9 @@
-from functools import total_ordering
-from itertools import groupby
+import os.path
from pathlib import Path
from typing import List, Tuple
from ee import EeException
+from ee.db import ObjDb
from ee.part import PartDb, load_db, save_db
from ee.project import Project, report
from ee.xml import types, bom_file_utils
@@ -11,96 +11,105 @@ from ee.xml import types, bom_file_utils
__all__ = ["create_order"]
-@total_ordering
-class PartInfo(object):
+class OrderPart(object):
def __init__(self, part: types.Part):
self.part = part
self.ref = next((ref.referenceProp for ref in bom_file_utils.schematic_references(part)), None)
- self.pn = next((p.valueProp for p in bom_file_utils.part_numbers(part)), None)
self.available_from: List[Tuple[str, types.Part]] = []
+ self.selected_supplier_and_part = None
rl = part.referencesProp = part.referencesProp or types.ReferencesList() # type: types.ReferencesList
rl.schematic_referenceProp = rl.schematic_referenceProp or []
rl.part_numberProp = rl.part_numberProp or []
rl.supplier_part_numberProp = rl.supplier_part_numberProp or []
- def __lt__(self, other: "PartInfo"):
- return self.ref == other.ref
+
+def make_report(out_file, order_parts: ObjDb[OrderPart], has_unresolved_parts):
+ parts_by_supplier = {}
+
+ for supplier, order_parts in order_parts.index("selected_part").items():
+ parts = {}
+ for op in order_parts:
+ p = op.selected_supplier_and_part[1]
+ parts[p.uriProp] = p
+ parts_by_supplier[supplier] = parts.values()
+
+ kwargs = {
+ "order_parts": order_parts,
+ "parts_by_supplier": parts_by_supplier,
+ "has_unresolved_parts": has_unresolved_parts,
+ }
+ report.save_report("ee.order", "order.rst.j2", out_file, **kwargs)
def create_order(project: Project, schematic_path: Path, out_path: Path, part_db_dirs: List[Path],
fail_on_missing_parts: bool):
- messages = []
-
sch_db = load_db(schematic_path)
dbs = [(path.parent.name, load_db(path)) for path in part_db_dirs]
- out_parts = PartDb()
+ def supplier_index(op: OrderPart):
+ return [spn.supplierProp for spn in bom_file_utils.supplier_part_numbers(op.part)]
+
+ def selected_part_index(op: OrderPart):
+ return [op.selected_supplier_and_part[0]] if op.selected_supplier_and_part else None
+
+ def available_from_index(op: OrderPart):
+ return set([s for s, s_part in op.available_from])
- infos = [PartInfo(sch) for sch in sch_db.iterparts()]
+ def supplier_part_index(op: OrderPart):
+ return set([s_part.uriProp for s, s_part in op.available_from])
- for info in infos:
- sch_part_numbers = info.part.referencesProp.part_number
- sch_supplier_part_numbers: List[types.SupplierPartNumber] = info.part.referencesProp.supplier_part_number
+ def pn_index(op: OrderPart):
+ return [pn.value for pn in bom_file_utils.part_numbers(op.part)]
+
+ order_parts: ObjDb[OrderPart] = ObjDb[OrderPart]()
+ for sch_part in sch_db.iterparts():
+ order_parts.add(OrderPart(sch_part))
+
+ for order_part in order_parts:
+ sch_part_numbers = order_part.part.referencesProp.part_number
+ sch_supplier_part_numbers: List[types.SupplierPartNumber] = order_part.part.referencesProp.supplier_part_number
for supplier, db in dbs:
for supplier_part in db.iterparts(sort=True):
- found = None
for sch_pn in sch_part_numbers:
for pn in bom_file_utils.part_numbers(supplier_part):
if sch_pn.valueProp == pn.valueProp:
- found = supplier_part
- break
- if found:
- break
-
- if found:
- info.available_from.append((supplier, found))
- break
+ order_part.available_from.append((supplier, supplier_part))
- found = None
for sch_spn in sch_supplier_part_numbers:
for spn in bom_file_utils.supplier_part_numbers(supplier_part):
if sch_spn.valueProp == spn.valueProp and sch_spn.supplierProp == spn.supplierProp:
- found = supplier_part
- break
- if found:
- break
-
- if found:
- info.available_from.append((supplier, found))
- break
-
- for info in infos:
- if len(info.available_from):
- strings = ["{} from {}".format(part.uri, s) for s, part in info.available_from]
- text = "Resolved {} to {}".format(info.ref, ", ".join(strings))
- messages.append(report.Message(object=info.ref, text=text))
-
- has_missing_parts = False
- for info in infos:
- if len(info.available_from) == 0:
- has_missing_parts = True
- text = "Could not find {} in any database, part numbers: {}". \
- format(info.ref, ", ".join([p.value for p in bom_file_utils.part_numbers(info.part)]))
- messages.append(report.Message(object=info.ref, text=text))
-
- if has_missing_parts and fail_on_missing_parts:
+ order_part.available_from.append((supplier, supplier_part))
+
+ has_unresolved_parts = False
+ for order_part in order_parts:
+ if len(order_part.available_from) == 0:
+ has_unresolved_parts = True
+ elif len(order_part.available_from) == 1:
+ order_part.selected_supplier_and_part = order_part.available_from[0]
+ else:
+ raise EeException("unimplemented: part available from multiple suppliers")
+
+ if has_unresolved_parts and fail_on_missing_parts:
raise EeException("The order has parts that can't be found from any supplier")
- for info in infos:
- if len(info.available_from) == 0:
+ order_parts.add_index("selected_part", selected_part_index)
+
+ out_file = project.report_dir / (os.path.splitext(out_path.name)[0] + ".rst")
+ make_report(out_file, order_parts, has_unresolved_parts)
+
+ out_parts = PartDb()
+ for order_part in order_parts:
+ if not order_part.selected_supplier_and_part:
continue
- supplier, supplier_part = info.available_from[0]
+ supplier, supplier_part = order_part.selected_supplier_and_part
- references = types.ReferencesList(schematic_reference=info.part.referencesProp.schematic_reference)
+ references = types.ReferencesList(schematic_reference=order_part.part.referencesProp.schematic_reference)
part = types.Part(uri=supplier_part.uri, references=references)
out_parts.add_entry(part, True)
save_db(out_path, out_parts)
- # messages
- kwargs = {"messages_by_object": groupby(messages, lambda m: m.object)}
- report.save_report("ee.order", "order.rst.j2", project.report_dir / "order.rst", **kwargs)
diff --git a/src/ee/order/templates/order.rst.j2 b/src/ee/order/templates/order.rst.j2
index ea1dbdf..6616167 100644
--- a/src/ee/order/templates/order.rst.j2
+++ b/src/ee/order/templates/order.rst.j2
@@ -1,9 +1,55 @@
-Messages
-========
-{% for o, messages in messages_by_object %}
-{{ o | subsection }}
-{% for m in messages %}
-* {{ m.text }}
-{% endfor %}
+Order
+=====
+
+Has unresolved parts: {{ "yes" if has_unresolved_parts else "no" }}.
+
+Parts for Order
+===============
+{% for op in order_parts %}
+{{ op.ref | subsection }}
+{% if op.available_from|length == 0 %}
+Part not resolved.
+{% elif op.available_from|length == 1 %}
+{%- set from=op.available_from[0] %}
+{%- set part=from[1] %}
+{%- set pn=part|first_pn %}
+{%- set spn=part|first_spn %}
+Selected supplier: {{ from[0] }}{{ (", pn: " + pn.valueProp) if pn else "" }}{{ (", spn: " + spn.valueProp) if spn else "" }}.
+Part: part-{{pn.valueProp}}_
+{% else %}
+MANY
+{% endif %}
+{%- endfor %}
+
+Part details
+============
+{% for supplier, parts in parts_by_supplier.items() %}
+{{ ("From " + supplier) | subsection }}
+{% for part in parts %}
+{%- set pn=part|first_pn %}
+{%- set spn=part|first_spn %}
+{%- set title=pn.valueProp if pn else (spn.valueProp if spn else "???") %}
+{%- set links=part.linksProp.link %}
+.. _part-{{title}}:
+
+{{ title|subsubsection }}
+{#-
+
+"Facts"
+.......
+
+{% for f in part.facts.fact %}
+f={{f}}
{% endfor %}
+#}
+
+"Media"
+.......
+{% for l in links %}
+{%- if l.relationProp == "http://purl.org/ee/link-relation#documentation" %}
+* `{{ l.title }} <{{ l.url }}>`__
+{%- endif %}
+{%- endfor %}
+{% endfor %}
+{% endfor %}
diff --git a/src/ee/part/__init__.py b/src/ee/part/__init__.py
index 36a3d3c..4ed6edb 100644
--- a/src/ee/part/__init__.py
+++ b/src/ee/part/__init__.py
@@ -23,9 +23,8 @@ class Entry(object):
class PartDb(object):
def __init__(self):
- self.parts = [] # type: List[Entry]
- self.pn_index = {} # type: MutableMapping[str, Entry]
- self.dpn_indexes = {} # type: MutableMapping[str, MutableMapping[str, Entry]]
+ self.parts: List[Entry] = []
+ self.pn_index: MutableMapping[str, Entry] = {}
self.new_entries = 0
def add_entry(self, part: types.Part, new: bool):
diff --git a/src/ee/project/report.py b/src/ee/project/report.py
index 5370f6c..80e590a 100644
--- a/src/ee/project/report.py
+++ b/src/ee/project/report.py
@@ -4,6 +4,7 @@ from typing import Optional
from jinja2 import Environment, PackageLoader, select_autoescape
from ee.tools import mk_parents
+from ee.xml import types, bom_file_utils
class Message(object):
@@ -12,16 +13,40 @@ class Message(object):
self.text = text
+def section_filter(s: str):
+ return "{}\n{}".format(s, "=" * len(s))
+
+
def subsection_filter(s: str):
+ return "{}\n{}".format(s, "-" * len(s))
+
+
+def subsubsection_filter(s: str):
return "{}\n{}".format(s, "~" * len(s))
+def subsubsubsection_filter(s: str):
+ return "{}\n{}".format(s, "." * len(s))
+
+
+def first_pn_filter(part: types.Part):
+ return next(iter(bom_file_utils.part_numbers(part)), None)
+
+
+def first_spn_filter(part: types.Part):
+ return next(iter(bom_file_utils.supplier_part_numbers(part)), None)
+
+
def save_report(package: str, template_name: str, out_file: Path, **kwargs):
env = Environment(
loader=PackageLoader(package, "templates"),
autoescape=select_autoescape(["html", "xml"]),
)
env.filters["subsection"] = subsection_filter
+ env.filters["subsubsection"] = subsubsection_filter
+ env.filters["subsubsubsection"] = subsubsubsection_filter
+ env.filters["first_pn"] = first_pn_filter
+ env.filters["first_spn"] = first_spn_filter
mk_parents(out_file)
with out_file.open("w") as f:
diff --git a/src/ee/xml/types.py b/src/ee/xml/types.py
index a1ab7ec..60d659d 100644
--- a/src/ee/xml/types.py
+++ b/src/ee/xml/types.py
@@ -3,7 +3,7 @@
#
# Generated by generateDS.py.
-# Python 3.7.2+ (default, Feb 27 2019, 15:41:59) [GCC 8.2.0]
+# Python 3.7.2+ (default, Feb 2 2019, 14:31:48) [GCC 8.2.0]
#
# Command line options:
# ('-f', '')
@@ -807,12 +807,13 @@ class PartDb(GeneratedsSuper):
class Part(GeneratedsSuper):
subclass = None
superclass = None
- def __init__(self, uri=None, part_type=None, description=None, references=None, distributor_info=None, facts=None, price_breaks=None, **kwargs_):
+ def __init__(self, uri=None, part_type=None, description=None, links=None, references=None, distributor_info=None, facts=None, price_breaks=None, **kwargs_):
self.original_tagname_ = None
self.parent_object_ = kwargs_.get('parent_object_')
self.uri = _cast(None, uri)
self.part_type = part_type
self.description = description
+ self.links = links
self.references = references
self.distributor_info = distributor_info
self.facts = facts
@@ -838,6 +839,11 @@ class Part(GeneratedsSuper):
def set_description(self, description):
self.description = description
descriptionProp = property(get_description, set_description)
+ def get_links(self):
+ return self.links
+ def set_links(self, links):
+ self.links = links
+ linksProp = property(get_links, set_links)
def get_references(self):
return self.references
def set_references(self, references):
@@ -867,6 +873,7 @@ class Part(GeneratedsSuper):
if (
self.part_type is not None or
self.description is not None or
+ self.links is not None or
self.references is not None or
self.distributor_info is not None or
self.facts is not None or
@@ -911,6 +918,8 @@ class Part(GeneratedsSuper):
if self.description is not None:
showIndent(outfile, level, pretty_print)
outfile.write('<%sdescription>%s</%sdescription>%s' % (namespaceprefix_ , self.gds_encode(self.gds_format_string(quote_xml(self.description), input_name='description')), namespaceprefix_ , eol_))
+ if self.links is not None:
+ self.links.export(outfile, level, namespaceprefix_, namespacedef_='', name_='links', pretty_print=pretty_print)
if self.references is not None:
self.references.export(outfile, level, namespaceprefix_, namespacedef_='', name_='references', pretty_print=pretty_print)
if self.distributor_info is not None:
@@ -940,6 +949,11 @@ class Part(GeneratedsSuper):
description_ = child_.text
description_ = self.gds_validate_string(description_, node, 'description')
self.description = description_
+ elif nodeName_ == 'links':
+ obj_ = LinkList.factory(parent_object_=self)
+ obj_.build(child_)
+ self.links = obj_
+ obj_.original_tagname_ = 'links'
elif nodeName_ == 'references':
obj_ = ReferencesList.factory(parent_object_=self)
obj_.build(child_)
@@ -2082,6 +2096,207 @@ class PriceBreakList(GeneratedsSuper):
# end class PriceBreakList
+class Link(GeneratedsSuper):
+ subclass = None
+ superclass = None
+ def __init__(self, url=None, relation=None, media_type=None, title=None, **kwargs_):
+ self.original_tagname_ = None
+ self.parent_object_ = kwargs_.get('parent_object_')
+ self.url = _cast(None, url)
+ self.relation = _cast(None, relation)
+ self.media_type = _cast(None, media_type)
+ self.title = _cast(None, title)
+ def factory(*args_, **kwargs_):
+ if CurrentSubclassModule_ is not None:
+ subclass = getSubclassFromModule_(
+ CurrentSubclassModule_, Link)
+ if subclass is not None:
+ return subclass(*args_, **kwargs_)
+ if Link.subclass:
+ return Link.subclass(*args_, **kwargs_)
+ else:
+ return Link(*args_, **kwargs_)
+ factory = staticmethod(factory)
+ def get_url(self):
+ return self.url
+ def set_url(self, url):
+ self.url = url
+ urlProp = property(get_url, set_url)
+ def get_relation(self):
+ return self.relation
+ def set_relation(self, relation):
+ self.relation = relation
+ relationProp = property(get_relation, set_relation)
+ def get_media_type(self):
+ return self.media_type
+ def set_media_type(self, media_type):
+ self.media_type = media_type
+ media_typeProp = property(get_media_type, set_media_type)
+ def get_title(self):
+ return self.title
+ def set_title(self, title):
+ self.title = title
+ titleProp = property(get_title, set_title)
+ def hasContent_(self):
+ if (
+
+ ):
+ return True
+ else:
+ return False
+ def export(self, outfile, level, namespaceprefix_='', namespacedef_='', name_='Link', pretty_print=True):
+ imported_ns_def_ = GenerateDSNamespaceDefs_.get('Link')
+ if imported_ns_def_ is not None:
+ namespacedef_ = imported_ns_def_
+ if pretty_print:
+ eol_ = '\n'
+ else:
+ eol_ = ''
+ if self.original_tagname_ is not None:
+ name_ = self.original_tagname_
+ showIndent(outfile, level, pretty_print)
+ outfile.write('<%s%s%s' % (namespaceprefix_, name_, namespacedef_ and ' ' + namespacedef_ or '', ))
+ already_processed = set()
+ self.exportAttributes(outfile, level, already_processed, namespaceprefix_, name_='Link')
+ if self.hasContent_():
+ outfile.write('>%s' % (eol_, ))
+ self.exportChildren(outfile, level + 1, namespaceprefix_, namespacedef_, name_='Link', pretty_print=pretty_print)
+ outfile.write('</%s%s>%s' % (namespaceprefix_, name_, eol_))
+ else:
+ outfile.write('/>%s' % (eol_, ))
+ def exportAttributes(self, outfile, level, already_processed, namespaceprefix_='', name_='Link'):
+ if self.url is not None and 'url' not in already_processed:
+ already_processed.add('url')
+ outfile.write(' url=%s' % (self.gds_encode(self.gds_format_string(quote_attrib(self.url), input_name='url')), ))
+ if self.relation is not None and 'relation' not in already_processed:
+ already_processed.add('relation')
+ outfile.write(' relation=%s' % (self.gds_encode(self.gds_format_string(quote_attrib(self.relation), input_name='relation')), ))
+ if self.media_type is not None and 'media_type' not in already_processed:
+ already_processed.add('media_type')
+ outfile.write(' media-type=%s' % (self.gds_encode(self.gds_format_string(quote_attrib(self.media_type), input_name='media-type')), ))
+ if self.title is not None and 'title' not in already_processed:
+ already_processed.add('title')
+ outfile.write(' title=%s' % (self.gds_encode(self.gds_format_string(quote_attrib(self.title), input_name='title')), ))
+ def exportChildren(self, outfile, level, namespaceprefix_='', namespacedef_='', name_='Link', fromsubclass_=False, pretty_print=True):
+ pass
+ def build(self, node):
+ already_processed = set()
+ self.buildAttributes(node, node.attrib, already_processed)
+ for child in node:
+ nodeName_ = Tag_pattern_.match(child.tag).groups()[-1]
+ self.buildChildren(child, node, nodeName_)
+ return self
+ def buildAttributes(self, node, attrs, already_processed):
+ value = find_attr_value_('url', node)
+ if value is not None and 'url' not in already_processed:
+ already_processed.add('url')
+ self.url = value
+ value = find_attr_value_('relation', node)
+ if value is not None and 'relation' not in already_processed:
+ already_processed.add('relation')
+ self.relation = value
+ value = find_attr_value_('media-type', node)
+ if value is not None and 'media-type' not in already_processed:
+ already_processed.add('media-type')
+ self.media_type = value
+ value = find_attr_value_('title', node)
+ if value is not None and 'title' not in already_processed:
+ already_processed.add('title')
+ self.title = value
+ def buildChildren(self, child_, node, nodeName_, fromsubclass_=False):
+ pass
+# end class Link
+
+
+class LinkList(GeneratedsSuper):
+ subclass = None
+ superclass = None
+ def __init__(self, link=None, **kwargs_):
+ self.original_tagname_ = None
+ self.parent_object_ = kwargs_.get('parent_object_')
+ if link is None:
+ self.link = []
+ else:
+ self.link = link
+ def factory(*args_, **kwargs_):
+ if CurrentSubclassModule_ is not None:
+ subclass = getSubclassFromModule_(
+ CurrentSubclassModule_, LinkList)
+ if subclass is not None:
+ return subclass(*args_, **kwargs_)
+ if LinkList.subclass:
+ return LinkList.subclass(*args_, **kwargs_)
+ else:
+ return LinkList(*args_, **kwargs_)
+ factory = staticmethod(factory)
+ def get_link(self):
+ return self.link
+ def set_link(self, link):
+ self.link = link
+ def add_link(self, value):
+ self.link.append(value)
+ def add_link(self, value):
+ self.link.append(value)
+ def insert_link_at(self, index, value):
+ self.link.insert(index, value)
+ def replace_link_at(self, index, value):
+ self.link[index] = value
+ linkProp = property(get_link, set_link)
+ def hasContent_(self):
+ if (
+ self.link
+ ):
+ return True
+ else:
+ return False
+ def export(self, outfile, level, namespaceprefix_='', namespacedef_='', name_='LinkList', pretty_print=True):
+ imported_ns_def_ = GenerateDSNamespaceDefs_.get('LinkList')
+ if imported_ns_def_ is not None:
+ namespacedef_ = imported_ns_def_
+ if pretty_print:
+ eol_ = '\n'
+ else:
+ eol_ = ''
+ if self.original_tagname_ is not None:
+ name_ = self.original_tagname_
+ showIndent(outfile, level, pretty_print)
+ outfile.write('<%s%s%s' % (namespaceprefix_, name_, namespacedef_ and ' ' + namespacedef_ or '', ))
+ already_processed = set()
+ self.exportAttributes(outfile, level, already_processed, namespaceprefix_, name_='LinkList')
+ if self.hasContent_():
+ outfile.write('>%s' % (eol_, ))
+ self.exportChildren(outfile, level + 1, namespaceprefix_, namespacedef_, name_='LinkList', pretty_print=pretty_print)
+ showIndent(outfile, level, pretty_print)
+ outfile.write('</%s%s>%s' % (namespaceprefix_, name_, eol_))
+ else:
+ outfile.write('/>%s' % (eol_, ))
+ def exportAttributes(self, outfile, level, already_processed, namespaceprefix_='', name_='LinkList'):
+ pass
+ def exportChildren(self, outfile, level, namespaceprefix_='', namespacedef_='', name_='LinkList', fromsubclass_=False, pretty_print=True):
+ if pretty_print:
+ eol_ = '\n'
+ else:
+ eol_ = ''
+ for link_ in self.link:
+ link_.export(outfile, level, namespaceprefix_, namespacedef_='', name_='link', pretty_print=pretty_print)
+ def build(self, node):
+ already_processed = set()
+ self.buildAttributes(node, node.attrib, already_processed)
+ for child in node:
+ nodeName_ = Tag_pattern_.match(child.tag).groups()[-1]
+ self.buildChildren(child, node, nodeName_)
+ return self
+ def buildAttributes(self, node, attrs, already_processed):
+ pass
+ def buildChildren(self, child_, node, nodeName_, fromsubclass_=False):
+ if nodeName_ == 'link':
+ obj_ = Link.factory(parent_object_=self)
+ obj_.build(child_)
+ self.link.append(obj_)
+ obj_.original_tagname_ = 'link'
+# end class LinkList
+
+
GDSClassesMapping = {
'part': Part,
'part-db': PartDb,
@@ -2216,6 +2431,8 @@ __all__ = [
"DistributorInfo",
"Fact",
"FactList",
+ "Link",
+ "LinkList",
"Part",
"PartDb",
"PartList",
diff --git a/xsd/ee.xsd b/xsd/ee.xsd
index 18d8665..0e322e5 100644
--- a/xsd/ee.xsd
+++ b/xsd/ee.xsd
@@ -47,6 +47,7 @@ TODO: rename 'id' to 'url'.
<!-- TODO: this should be a fact -->
<xs:element name="part-type" type="xs:anyURI"/>
<xs:element name="description" type="xs:string"/>
+ <xs:element name="links" type="LinkList"/>
<xs:element name="references" type="ReferencesList"/>
<xs:element name="distributor-info" type="DistributorInfo"/>
<xs:element name="facts" type="FactList"/>
@@ -135,4 +136,17 @@ TODO: rename 'id' to 'url'.
</xs:sequence>
</xs:complexType>
+ <xs:complexType name="Link">
+ <xs:attribute name="url" type="xs:anyURI"/>
+ <xs:attribute name="relation" type="xs:string"/>
+ <xs:attribute name="media-type" type="xs:string"/>
+ <xs:attribute name="title" type="xs:string"/>
+ </xs:complexType>
+
+ <xs:complexType name="LinkList">
+ <xs:sequence>
+ <xs:element name="link" type="Link" maxOccurs="unbounded"/>
+ </xs:sequence>
+ </xs:complexType>
+
</xs:schema>