diff --git a/examples/emmodoc/emmodoc-meta.yaml b/examples/emmodoc/emmodoc-meta.yaml index f86c70338..87eb569f9 100644 --- a/examples/emmodoc/emmodoc-meta.yaml +++ b/examples/emmodoc/emmodoc-meta.yaml @@ -1,6 +1,6 @@ --- title: 'Elemental Multiperspective Material Ontology' -version: 1.0.0-alpha2 +version: 1.0.0-beta4 author: - name: Emanuele Ghedini affiliation: University of Bologne diff --git a/examples/emmodoc/figs/emmo-multidisciplinary.png b/examples/emmodoc/figs/emmo-multidisciplinary.png index d4623f4f6..e1a6f7152 100644 Binary files a/examples/emmodoc/figs/emmo-multidisciplinary.png and b/examples/emmodoc/figs/emmo-multidisciplinary.png differ diff --git a/ontopy/graph.py b/ontopy/graph.py index ba69d4dff..42fd2ef81 100644 --- a/ontopy/graph.py +++ b/ontopy/graph.py @@ -567,7 +567,9 @@ def add_source_edges( # pylint: disable=too-many-arguments,too-many-branches obj = self.add_class_construct(relation.value) else: continue - pred = asstring(relation, exclude_object=True) + pred = asstring( + relation, exclude_object=True, ontology=self.ontology + ) self.add_edge( label, pred, obj, edgelabel=edgelabels, **attrs ) diff --git a/ontopy/ontodoc.py b/ontopy/ontodoc.py index 3aa840c85..efdad809f 100644 --- a/ontopy/ontodoc.py +++ b/ontopy/ontodoc.py @@ -54,7 +54,8 @@ class OntoDoc: "figwidth": "{{ width={width:.0f}px }}", "figure": "![{caption}]({path}){figwidth}\n", "header": "\n{:#<{level}} {label} {{#{anchor}}}", - "link": "[{name}]({lowerurl})", + # Use ref instead of iri for local references in links + "link": "[{label}]({ref})", "point": " - {point}\n", "points": "\n\n{points}\n", "annotation": "**{key}:** {value}\n", @@ -138,7 +139,7 @@ class OntoDoc: "figwidth": 'width="{width:.0f}"', "figure": '{caption}', "header": '{label}', - "link": '{name}', + "link": '{label}', "point": "
  • {point}
  • \n", "points": " \n", "annotation": "
    {key}:\n{value}
    \n", @@ -173,9 +174,11 @@ def get_default_template(self): os.path.basename(self.onto.base_iri.rstrip("/#")) )[0] irilink = self.style.get("link", "{name}").format( + iri=self.onto.base_iri, name=self.onto.base_iri, - url=self.onto.base_iri, - lowerurl=self.onto.base_iri, + ref=self.onto.base_iri, + label=self.onto.base_iri, + lowerlabel=self.onto.base_iri, ) template = dedent( """\ @@ -282,7 +285,9 @@ def itemdoc( # Add iri doc.append( annotation_style.format( - key="IRI", value=asstring(item.iri, link_style), ontology=onto + key="IRI", + value=asstring(item.iri, link_style, ontology=onto), + ontology=onto, ) ) @@ -300,7 +305,8 @@ def itemdoc( if self.url_regex.match(value): doc.append( annotation_style.format( - key=key, value=asstring(value, link_style) + key=key, + value=asstring(value, link_style, ontology=onto), ) ) else: @@ -323,14 +329,16 @@ def itemdoc( ): points.append( point_style.format( - point="is_a " + asstring(prop, link_style), + point="is_a " + + asstring(prop, link_style, ontology=onto), ontology=onto, ) ) else: points.append( point_style.format( - point=asstring(prop, link_style), ontology=onto + point=asstring(prop, link_style, ontology=onto), + ontology=onto, ) ) @@ -338,7 +346,8 @@ def itemdoc( for entity in item.equivalent_to: points.append( point_style.format( - point="equivalent_to " + asstring(entity, link_style) + point="equivalent_to " + + asstring(entity, link_style, ontology=onto) ) ) @@ -348,7 +357,9 @@ def itemdoc( points.append( point_style.format( point="disjoint_with " - + ", ".join(asstring(_, link_style) for _ in subjects), + + ", ".join( + asstring(s, link_style, ontology=onto) for s in subjects + ), ontology=onto, ) ) @@ -356,7 +367,9 @@ def itemdoc( # ...add disjoint_unions if hasattr(item, "disjoint_unions"): for unions in item.disjoint_unions: - string = ", ".join(asstring(_, link_style) for _ in unions) + string = ", ".join( + asstring(u, link_style, ontology=onto) for u in unions + ) points.append( point_style.format( point=f"disjoint_union_of {string}", ontology=onto @@ -368,7 +381,7 @@ def itemdoc( points.append( point_style.format( point="inverse_of " - + asstring(item.inverse_property, link_style) + + asstring(item.inverse_property, link_style, ontology=onto) ) ) @@ -376,7 +389,8 @@ def itemdoc( for domain in getattr(item, "domain", ()): points.append( point_style.format( - point=f"domain {asstring(domain, link_style)}" + point="domain " + + asstring(domain, link_style, ontology=onto) ) ) @@ -384,7 +398,8 @@ def itemdoc( for restriction in getattr(item, "range", ()): points.append( point_style.format( - point=f"range {asstring(restriction, link_style)}" + point="range " + + asstring(restriction, link_style, ontology=onto) ) ) @@ -412,7 +427,7 @@ def itemdoc( if item in instance.is_instance_of: points.append( point_style.format( - point=asstring(instance, link_style), + point=asstring(instance, link_style, ontology=onto), ontology=onto, ) ) @@ -994,7 +1009,7 @@ def process_alls(self): raise InvalidTemplateError( f"Invalid argument to %%ALL: {token}" ) - items = sorted(items, key=asstring) + items = sorted(items, key=get_label) del self.lines[i] self.lines[i:i] = self.ontodoc.itemsdoc( items, int(opts.header_level) # pylint: disable=no-member @@ -1051,7 +1066,7 @@ def process_allfig(self): # pylint: disable=too-many-locals sec = [] for root in roots: - name = asstring(root) + name = asstring(root, link="{label}", ontology=onto) filepath, _, width = self._make_branchfig( name, opts.path, # pylint: disable=no-member diff --git a/ontopy/ontology.py b/ontopy/ontology.py index d3fd267b1..54e379c04 100644 --- a/ontopy/ontology.py +++ b/ontopy/ontology.py @@ -299,7 +299,7 @@ def get_by_label( except ValueError: pass - splitlabel = label.split(":") + splitlabel = label.split(":", 1) if len(splitlabel) > 2: raise ValueError( f"Invalid label definition, {label!r}" diff --git a/ontopy/utils.py b/ontopy/utils.py index a519e0215..6a1b48f0b 100644 --- a/ontopy/utils.py +++ b/ontopy/utils.py @@ -98,34 +98,74 @@ def get_label(entity): return repr(entity) +def getiriname(iri): + """Return name part of an IRI. + + The name part is what follows after the last slash or hash. + """ + res = urllib.parse.urlparse(iri) + return res.fragment if res.fragment else res.path.rsplit("/", 1)[-1] + + def asstring( # pylint: disable=too-many-return-statements,too-many-branches,too-many-statements - expr, link="{name}", recursion_depth=0, exclude_object=False -): - """Returns a string representation of `expr`, which may be an entity, - restriction, or logical expression of these. `link` is a format - string for formatting references to entities or relations. It may - contain the keywords "name", "url" and "lowerurl". - `recursion_depth` is the recursion depth and only intended for internal - use. If `exclude_object` is true, the object will be excluded in - restrictions. + expr, + link="{lowerlabel}", + recursion_depth=0, + exclude_object=False, + ontology=None, +) -> str: + """Returns a string representation of `expr`. + + Arguments: + expr: The entity, restriction or a logical expression or these + to represent. + link: A template for links. May contain the following variables: + - {iri}: The full IRI of the concept. + - {name}: Name-part of IRI. + - {ref}: "#{name}" if the base iri of hte ontology has the same + root as {iri}, otherwise "{iri}". + - {label}: The label of the concept. + - {lowerlabel}: The label of the concept in lower case and with + spaces replaced with hyphens. + recursion_depth: Recursion depth. Only intended for internal use. + exclude_object: If true, the object will be excluded in restrictions. + ontology: Ontology object. + + Returns: + String representation of `expr`. """ + if ontology is None: + ontology = expr.ontology def fmt(entity): """Returns the formatted label of an entity.""" - name = None - for attr in ("prefLabel", "label", "__name__", "name"): - if hasattr(entity, attr) and getattr(entity, attr): - name = getattr(entity, attr) - if not isinstance(name, str) and hasattr(name, "__getitem__"): - name = name[0] - break - if not name: - if entity.startswith("http://") or entity.startswith("https://"): - name = entity + if isinstance(entity, str): + if ontology and ontology.world[entity]: + iri = ontology.world[entity].iri + elif ( + ontology + and re.match("^[a-zA-Z0-9_+-]+$", entity) + and entity in ontology + ): + iri = ontology[entity].iri else: - name = str(entity).replace(".", ":") - url = name if re.match(r"^[a-z]+://", name) else "#" + name - return link.format(name=name, url=url, lowerurl=url.lower()) + # This may not be a valid IRI, but the best we can do + iri = entity + label = entity + else: + iri = entity.iri + label = get_label(entity) + name = getiriname(iri) + start = iri.split("#", 1)[0] if "#" in iri else iri.rsplit("/", 1)[0] + ref = f"#{name}" if ontology.base_iri.startswith(start) else iri + return link.format( + entity=entity, + name=name, + ref=ref, + iri=iri, + label=label, + lowerlabel=label.lower().replace(" ", "-"), + ) if isinstance(expr, str): # return link.format(name=expr) @@ -139,7 +179,13 @@ def fmt(entity): ): res = fmt(expr.property) elif isinstance(expr.property, owlready2.Inverse): - res = f"Inverse({asstring(expr.property.property, link, recursion_depth + 1)})" # pylint: disable=line-too-long + string = asstring( + expr.property.property, + link, + recursion_depth + 1, + ontology=ontology, + ) + res = f"Inverse({string})" else: print( f"*** WARNING: unknown restriction property: {expr.property!r}" @@ -162,23 +208,34 @@ def fmt(entity): res += f" {rlabel}" if not exclude_object: - if isinstance(expr.value, str): - res += f" {asstring(expr.value, link, recursion_depth + 1)!r}" - else: - res += f" {asstring(expr.value, link, recursion_depth + 1)}" + string = asstring( + expr.value, link, recursion_depth + 1, ontology=ontology + ) + res += ( + f" {string!r}" if isinstance(expr.value, str) else f" {string}" + ) return res if isinstance(expr, owlready2.Or): res = " or ".join( - [asstring(c, link, recursion_depth + 1) for c in expr.Classes] + [ + asstring(c, link, recursion_depth + 1, ontology=ontology) + for c in expr.Classes + ] ) return res if recursion_depth == 0 else f"({res})" if isinstance(expr, owlready2.And): res = " and ".join( - [asstring(c, link, recursion_depth + 1) for c in expr.Classes] + [ + asstring(c, link, recursion_depth + 1, ontology=ontology) + for c in expr.Classes + ] ) return res if recursion_depth == 0 else f"({res})" if isinstance(expr, owlready2.Not): - return f"not {asstring(expr.Class, link, recursion_depth + 1)}" + string = asstring( + expr.Class, link, recursion_depth + 1, ontology=ontology + ) + return f"not {string}" if isinstance(expr, owlready2.ThingClass): return fmt(expr) if isinstance(expr, owlready2.PropertyClass): diff --git a/tools/ontodoc b/tools/ontodoc index 608e1aae4..80dd6a359 100755 --- a/tools/ontodoc +++ b/tools/ontodoc @@ -209,8 +209,9 @@ def main(argv: list = None): onto_path.append(path) # Load ontology + iri = args.iri if args.iri[-1] in "#/" else f"{args.iri}#" world = World(filename=args.database) - if args.database != ":memory:" and args.iri not in world.ontologies: + if args.database != ":memory:" and iri not in world.ontologies: parser.error( "The IRI argument should be one of the ontologies in the database:" "\n " + "\n ".join(world.ontologies.keys())