Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix internal links in generated documentation generated with ontodoc #548

Merged
merged 10 commits into from
Feb 12, 2023
2 changes: 1 addition & 1 deletion examples/emmodoc/emmodoc-meta.yaml
Original file line number Diff line number Diff line change
@@ -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
Expand Down
Binary file modified examples/emmodoc/figs/emmo-multidisciplinary.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
4 changes: 3 additions & 1 deletion ontopy/graph.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
)
Expand Down
49 changes: 32 additions & 17 deletions ontopy/ontodoc.py
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down Expand Up @@ -138,7 +139,7 @@ class OntoDoc:
"figwidth": 'width="{width:.0f}"',
"figure": '<img src="{path}" alt="{caption}"{figwidth}>',
"header": '<h{level} id="{anchor}">{label}</h{level}>',
"link": '<a href="{lowerurl}">{name}</a>',
"link": '<a href="{ref}">{label}</a>',
"point": " <li>{point}</li>\n",
"points": " <ul>\n {points}\n </ul>\n",
"annotation": " <dd><strong>{key}:</strong>\n{value} </dd>\n",
Expand Down Expand Up @@ -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(
"""\
Expand Down Expand Up @@ -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,
)
)

Expand All @@ -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:
Expand All @@ -323,22 +329,25 @@ 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,
)
)

# ...add equivalent_to relations
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)
)
)

Expand All @@ -348,15 +357,19 @@ 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,
)
)

# ...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
Expand All @@ -368,23 +381,25 @@ def itemdoc(
points.append(
point_style.format(
point="inverse_of "
+ asstring(item.inverse_property, link_style)
+ asstring(item.inverse_property, link_style, ontology=onto)
)
)

# ...add domain restrictions
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)
)
)

# ...add range restrictions
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)
)
)

Expand Down Expand Up @@ -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,
)
)
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion ontopy/ontology.py
Original file line number Diff line number Diff line change
Expand Up @@ -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}"
Expand Down
117 changes: 87 additions & 30 deletions ontopy/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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}"
Expand All @@ -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):
Expand Down
3 changes: 2 additions & 1 deletion tools/ontodoc
Original file line number Diff line number Diff line change
Expand Up @@ -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())
Expand Down