ontodoc¶
A module for documenting ontologies.
AttributeDict (dict)
¶
A dict with attribute access.
Note that methods like key() and update() may be overridden.
Source code in ontopy/ontodoc.py
class AttributeDict(dict):
"""A dict with attribute access.
Note that methods like key() and update() may be overridden."""
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.__dict__ = self
DocPP
¶
Documentation pre-processor.
It supports the following features:
-
Comment lines
%% Comment line...
-
Insert header with given level
%HEADER label [level=1]
-
Insert figure with optional caption and width.
filepath
should be relative tobasedir
. If width is 0, no width will be specified.%FIGURE filepath [caption='' width=0px]
-
Include other markdown files. Header levels may be up or down with
shift
%INCLUDE filepath [shift=0]
-
Insert generated documentation for ontology entity. The header level may be set with
header_level
.%ENTITY name [header_level=3]
-
Insert generated documentation for ontology branch
name
. Options:- header_level: Header level.
- terminated: Whether to branch should be terminated at all branch names in the final document.
-
include_leaves: Whether to include leaves as end points to the branch.
%BRANCH name [header_level=3 terminated=1 include_leaves=0 namespaces='' ontologies='']
-
Insert generated figure of ontology branch
name
. The figure is written topath
. The default path isfigdir
/name
, wherefigdir
is given at class initiation. It is recommended to exclude the file extension frompath
. In this case, the default figformat will be used (and easily adjusted to the correct format required by the backend).leaves
may be a comma- separated list of leaf node names.%BRANCHFIG name [path='' caption='' terminated=1 include_leaves=1 strict_leaves=1, width=0px leaves='' relations=all edgelabels=0 namespaces='' ontologies='']
-
This is a combination of the %HEADER and %BRANCHFIG directives.
%BRANCHHEAD name [level=2 path='' caption='' terminated=1 include_leaves=1 width=0px leaves='']
-
This is a combination of the %HEADER, %BRANCHFIG and %BRANCH directives. It inserts documentation of branch
name
, with a header followed by a figure and then documentation of each element.%BRANCHDOC name [level=2 path='' title='' caption='' terminated=1 strict_leaves=1 width=0px leaves='' relations='all' rankdir='BT' legend=1 namespaces='' ontologies='']
-
Insert generated documentation for all entities of the given type. Valid values of
type
are: "classes", "individuals", "object_properties", "data_properties", "annotations_properties"%ALL type [header_level=3, namespaces='', ontologies='']
-
Insert generated figure of all entities of the given type. Valid values of
type
are: "classes", "object_properties" and "data_properties".%ALLFIG type
Parameters¶
template : str Input template. ontodoc : OntoDoc instance Instance of OntoDoc basedir : str Base directory for including relative file paths. figdir : str Default directory to store generated figures. figformat : str Default format for generated figures. figscale : float Default scaling of generated figures. maxwidth : float Maximum figure width. Figures larger than this will be rescaled. imported : bool Whether to include imported entities.
Source code in ontopy/ontodoc.py
class DocPP: # pylint: disable=too-many-instance-attributes
"""Documentation pre-processor.
It supports the following features:
* Comment lines
%% Comment line...
* Insert header with given level
%HEADER label [level=1]
* Insert figure with optional caption and width. `filepath`
should be relative to `basedir`. If width is 0, no width will
be specified.
%FIGURE filepath [caption='' width=0px]
* Include other markdown files. Header levels may be up or down with
`shift`
%INCLUDE filepath [shift=0]
* Insert generated documentation for ontology entity. The header
level may be set with `header_level`.
%ENTITY name [header_level=3]
* Insert generated documentation for ontology branch `name`. Options:
- header_level: Header level.
- terminated: Whether to branch should be terminated at all branch
names in the final document.
- include_leaves: Whether to include leaves as end points
to the branch.
%BRANCH name [header_level=3 terminated=1 include_leaves=0
namespaces='' ontologies='']
* Insert generated figure of ontology branch `name`. The figure
is written to `path`. The default path is `figdir`/`name`,
where `figdir` is given at class initiation. It is recommended
to exclude the file extension from `path`. In this case, the
default figformat will be used (and easily adjusted to the
correct format required by the backend). `leaves` may be a comma-
separated list of leaf node names.
%BRANCHFIG name [path='' caption='' terminated=1 include_leaves=1
strict_leaves=1, width=0px leaves='' relations=all
edgelabels=0 namespaces='' ontologies='']
* This is a combination of the %HEADER and %BRANCHFIG directives.
%BRANCHHEAD name [level=2 path='' caption='' terminated=1
include_leaves=1 width=0px leaves='']
* This is a combination of the %HEADER, %BRANCHFIG and %BRANCH
directives. It inserts documentation of branch `name`, with a
header followed by a figure and then documentation of each
element.
%BRANCHDOC name [level=2 path='' title='' caption='' terminated=1
strict_leaves=1 width=0px leaves='' relations='all'
rankdir='BT' legend=1 namespaces='' ontologies='']
* Insert generated documentation for all entities of the given type.
Valid values of `type` are: "classes", "individuals",
"object_properties", "data_properties", "annotations_properties"
%ALL type [header_level=3, namespaces='', ontologies='']
* Insert generated figure of all entities of the given type.
Valid values of `type` are: "classes", "object_properties" and
"data_properties".
%ALLFIG type
Parameters
----------
template : str
Input template.
ontodoc : OntoDoc instance
Instance of OntoDoc
basedir : str
Base directory for including relative file paths.
figdir : str
Default directory to store generated figures.
figformat : str
Default format for generated figures.
figscale : float
Default scaling of generated figures.
maxwidth : float
Maximum figure width. Figures larger than this will be rescaled.
imported : bool
Whether to include imported entities.
"""
# FIXME - this class should be refractured:
# * Instead of rescan the entire document for each pre-processer
# directive, we should scan the source like by line and handle
# each directive as they occour.
# * The current implementation has a lot of dublicated code.
# * Instead of modifying the source in-place, we should copy to a
# result list. This will make good error reporting much easier.
# * Branch leaves are only looked up in the file witht the %BRANCH
# directive, not in all included files as expedted.
def __init__( # pylint: disable=too-many-arguments
self,
template,
ontodoc,
basedir=".",
figdir="genfigs",
figformat="png",
figscale=1.0,
maxwidth=None,
imported=False,
):
self.lines = template.split("\n")
self.ontodoc = ontodoc
self.basedir = basedir
self.figdir = os.path.join(basedir, figdir)
self.figformat = figformat
self.figscale = figscale
self.maxwidth = maxwidth
self.imported = imported
self._branch_cache = None
self._processed = False # Whether process() has been called
def __str__(self):
return self.get_buffer()
def get_buffer(self):
"""Returns the current buffer."""
return "\n".join(self.lines)
def copy(self):
"""Returns a copy of self."""
docpp = DocPP(
"",
self.ontodoc,
self.basedir,
figformat=self.figformat,
figscale=self.figscale,
maxwidth=self.maxwidth,
)
docpp.lines[:] = self.lines
docpp.figdir = self.figdir
return docpp
def get_branches(self):
"""Returns a list with all branch names as specified with %BRANCH
(in current and all included documents). The returned value is
cached for efficiency purposes and so that it is not lost after
processing branches."""
if self._branch_cache is None:
names = []
docpp = self.copy()
docpp.process_includes()
for line in docpp.lines:
if line.startswith("%BRANCH"):
names.append(shlex.split(line)[1])
self._branch_cache = names
return self._branch_cache
def shift_header_levels(self, shift):
"""Shift header level of all hashtag-headers in buffer. Underline
headers are ignored."""
if not shift:
return
pat = re.compile("^#+ ")
for i, line in enumerate(self.lines):
match = pat.match(line)
if match:
if shift > 0:
self.lines[i] = "#" * shift + line
elif shift < 0:
counter = match.end()
if shift > counter:
self.lines[i] = line.lstrip("# ")
else:
self.lines[i] = line[counter:]
def process_comments(self):
"""Strips out comment lines starting with "%%"."""
self.lines = [line for line in self.lines if not line.startswith("%%")]
def process_headers(self):
"""Expand all %HEADER specifications."""
for i, line in reversed(list(enumerate(self.lines))):
if line.startswith("%HEADER "):
tokens = shlex.split(line)
name = tokens[1]
opts = get_options(tokens[2:], level=1)
del self.lines[i]
self.lines[i:i] = self.ontodoc.get_header(
name, int(opts.level) # pylint: disable=no-member
).split("\n")
def process_figures(self):
"""Expand all %FIGURE specifications."""
for i, line in reversed(list(enumerate(self.lines))):
if line.startswith("%FIGURE "):
tokens = shlex.split(line)
path = tokens[1]
opts = get_options(tokens[2:], caption="", width=0)
del self.lines[i]
self.lines[i:i] = self.ontodoc.get_figure(
os.path.join(self.basedir, path),
caption=opts.caption, # pylint: disable=no-member
width=opts.width, # pylint: disable=no-member
).split("\n")
def process_entities(self):
"""Expand all %ENTITY specifications."""
for i, line in reversed(list(enumerate(self.lines))):
if line.startswith("%ENTITY "):
tokens = shlex.split(line)
name = tokens[1]
opts = get_options(tokens[2:], header_level=3)
del self.lines[i]
self.lines[i:i] = self.ontodoc.itemdoc(
name, int(opts.header_level) # pylint: disable=no-member
).split("\n")
def process_branches(self):
"""Expand all %BRANCH specifications."""
onto = self.ontodoc.onto
# Get all branch names in final document
names = self.get_branches()
for i, line in reversed(list(enumerate(self.lines))):
if line.startswith("%BRANCH "):
tokens = shlex.split(line)
name = tokens[1]
opts = get_options(
tokens[2:],
header_level=3,
terminated=1,
include_leaves=0,
namespaces="",
ontologies="",
)
leaves = (
names if opts.terminated else ()
) # pylint: disable=no-member
included_namespaces = (
opts.namespaces.split(",")
if opts.namespaces
else () # pylint: disable=no-member
)
included_ontologies = (
opts.ontologies.split(",")
if opts.ontologies
else () # pylint: disable=no-member
)
branch = filter_classes(
onto.get_branch(
name, leaves, opts.include_leaves
), # pylint: disable=no-member
included_namespaces=included_namespaces,
included_ontologies=included_ontologies,
)
del self.lines[i]
self.lines[i:i] = self.ontodoc.itemsdoc(
branch, int(opts.header_level) # pylint: disable=no-member
).split("\n")
def _make_branchfig( # pylint: disable=too-many-arguments,too-many-locals
self,
name: str,
path: "Union[Path, str]",
terminated: bool,
include_leaves: bool,
strict_leaves: bool,
width: float,
leaves: "Union[str, list[str]]",
relations: str,
edgelabels: str,
rankdir: str,
legend: bool,
included_namespaces: "Iterable[str]",
included_ontologies: "Iterable[str]",
) -> "tuple[str, list[str], float]":
"""Help method for process_branchfig().
Args:
name: name of branch root
path: optional figure path name
include_leaves: whether to include leaves as end points
to the branch.
strict_leaves: whether to strictly exclude leave descendants
terminated: whether the graph should be terminated at leaf nodes
width: optional figure width
leaves: optional leaf node names for graph termination
relations: comma-separated list of relations to include
edgelabels: whether to include edgelabels
rankdir: graph direction (BT, TB, RL, LR)
legend: whether to add legend
included_namespaces: sequence of names of namespaces to be included
included_ontologies: sequence of names of ontologies to be included
Returns:
filepath: path to generated figure
leaves: used list of leaf node names
width: actual figure width
"""
onto = self.ontodoc.onto
if leaves:
if isinstance(leaves, str):
leaves = leaves.split(",")
elif terminated:
leaves = set(self.get_branches())
leaves.discard(name)
else:
leaves = None
if path:
figdir = os.path.dirname(path)
formatext = os.path.splitext(path)[1]
if formatext:
fmt = formatext.lstrip(".")
else:
fmt = self.figformat
path += f".{fmt}"
else:
figdir = self.figdir
fmt = self.figformat
term = "T" if terminated else ""
path = os.path.join(figdir, name + term) + f".{fmt}"
# Create graph
graph = OntoGraph(onto, graph_attr={"rankdir": rankdir})
graph.add_branch(
root=name,
leaves=leaves,
include_leaves=include_leaves,
strict_leaves=strict_leaves,
relations=relations,
edgelabels=edgelabels,
included_namespaces=included_namespaces,
included_ontologies=included_ontologies,
)
if legend:
graph.add_legend()
if not width:
figwidth, _ = graph.get_figsize()
width = self.figscale * figwidth
if self.maxwidth and width > self.maxwidth:
width = self.maxwidth
filepath = os.path.join(self.basedir, path)
destdir = os.path.dirname(filepath)
if not os.path.exists(destdir):
os.makedirs(destdir)
graph.save(filepath, fmt=fmt)
return filepath, leaves, width
def process_branchfigs(self):
"""Process all %BRANCHFIG directives."""
for i, line in reversed(list(enumerate(self.lines))):
if line.startswith("%BRANCHFIG "):
tokens = shlex.split(line)
name = tokens[1]
opts = get_options(
tokens[2:],
path="",
caption="",
terminated=1,
include_leaves=1,
strict_leaves=1,
width=0,
leaves="",
relations="all",
edgelabels=0,
rankdir="BT",
legend=1,
namespaces="",
ontologies="",
)
included_namespaces = (
opts.namespaces.split(",")
if opts.namespaces
else () # pylint: disable=no-member
)
included_ontologies = (
opts.ontologies.split(",")
if opts.ontologies
else () # pylint: disable=no-member
)
filepath, _, width = self._make_branchfig(
name,
opts.path, # pylint: disable=no-member
opts.terminated, # pylint: disable=no-member
opts.include_leaves, # pylint: disable=no-member
opts.strict_leaves, # pylint: disable=no-member
opts.width, # pylint: disable=no-member
opts.leaves, # pylint: disable=no-member
opts.relations, # pylint: disable=no-member
opts.edgelabels, # pylint: disable=no-member
opts.rankdir, # pylint: disable=no-member
opts.legend, # pylint: disable=no-member
included_namespaces,
included_ontologies,
)
del self.lines[i]
self.lines[i:i] = self.ontodoc.get_figure(
filepath,
caption=opts.caption,
width=width, # pylint: disable=no-member
).split("\n")
def process_branchdocs(self): # pylint: disable=too-many-locals
"""Process all %BRANCHDOC and %BRANCHEAD directives."""
onto = self.ontodoc.onto
for i, line in reversed(list(enumerate(self.lines))):
if line.startswith("%BRANCHDOC ") or line.startswith(
"%BRANCHHEAD "
):
with_branch = bool(line.startswith("%BRANCHDOC "))
tokens = shlex.split(line)
name = tokens[1]
title = camelsplit(name)
title = title[0].upper() + title[1:] + " branch"
opts = get_options(
tokens[2:],
level=2,
path="",
title=title,
caption=title + ".",
terminated=1,
strict_leaves=1,
width=0,
leaves="",
relations="all",
edgelabels=0,
rankdir="BT",
legend=1,
namespaces="",
ontologies="",
)
included_namespaces = (
opts.namespaces.split(",")
if opts.namespaces
else () # pylint: disable=no-member
)
included_ontologies = (
opts.ontologies.split(",")
if opts.ontologies
else () # pylint: disable=no-member
)
include_leaves = 1
filepath, leaves, width = self._make_branchfig(
name,
opts.path, # pylint: disable=no-member
opts.terminated, # pylint: disable=no-member
include_leaves,
opts.strict_leaves, # pylint: disable=no-member
opts.width, # pylint: disable=no-member
opts.leaves, # pylint: disable=no-member
opts.relations, # pylint: disable=no-member
opts.edgelabels, # pylint: disable=no-member
opts.rankdir, # pylint: disable=no-member
opts.legend, # pylint: disable=no-member
included_namespaces,
included_ontologies,
)
sec = []
sec.append(
self.ontodoc.get_header(opts.title, int(opts.level))
) # pylint: disable=no-member
sec.append(
self.ontodoc.get_figure(
filepath,
caption=opts.caption,
width=width, # pylint: disable=no-member
)
)
if with_branch:
include_leaves = 0
branch = filter_classes(
onto.get_branch(name, leaves, include_leaves),
included_namespaces=included_namespaces,
included_ontologies=included_ontologies,
)
sec.append(
self.ontodoc.itemsdoc(
branch, int(opts.level + 1)
) # pylint: disable=no-member
)
del self.lines[i]
self.lines[i:i] = sec
def process_alls(self):
"""Expand all %ALL specifications."""
onto = self.ontodoc.onto
for i, line in reversed(list(enumerate(self.lines))):
if line.startswith("%ALL "):
tokens = shlex.split(line)
token = tokens[1]
opts = get_options(tokens[2:], header_level=3)
if token == "classes": # nosec
items = onto.classes(imported=self.imported)
elif token in ("object_properties", "relations"):
items = onto.object_properties(imported=self.imported)
elif token == "data_properties": # nosec
items = onto.data_properties(imported=self.imported)
elif token == "annotation_properties": # nosec
items = onto.annotation_properties(imported=self.imported)
elif token == "individuals": # nosec
items = onto.individuals(imported=self.imported)
else:
raise InvalidTemplateError(
f"Invalid argument to %%ALL: {token}"
)
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
).split("\n")
def process_allfig(self): # pylint: disable=too-many-locals
"""Process all %ALLFIG directives."""
onto = self.ontodoc.onto
for i, line in reversed(list(enumerate(self.lines))):
if line.startswith("%ALLFIG "):
tokens = shlex.split(line)
token = tokens[1]
opts = get_options(
tokens[2:],
path="",
level=3,
terminated=0,
include_leaves=1,
strict_leaves=1,
width=0,
leaves="",
relations="isA",
edgelabels=0,
rankdir="BT",
legend=1,
namespaces="",
ontologies="",
)
if token == "classes": # nosec
roots = onto.get_root_classes(imported=self.imported)
elif token in ("object_properties", "relations"):
roots = onto.get_root_object_properties(
imported=self.imported
)
elif token == "data_properties": # nosec
roots = onto.get_root_data_properties(
imported=self.imported
)
else:
raise InvalidTemplateError(
f"Invalid argument to %%ALLFIG: {token}"
)
included_namespaces = (
opts.namespaces.split(",")
if opts.namespaces
else () # pylint: disable=no-member
)
included_ontologies = (
opts.ontologies.split(",")
if opts.ontologies
else () # pylint: disable=no-member
)
sec = []
for root in roots:
name = asstring(root, link="{label}", ontology=onto)
filepath, _, width = self._make_branchfig(
name,
opts.path, # pylint: disable=no-member
opts.terminated, # pylint: disable=no-member
opts.include_leaves, # pylint: disable=no-member
opts.strict_leaves, # pylint: disable=no-member
opts.width, # pylint: disable=no-member
opts.leaves, # pylint: disable=no-member
opts.relations, # pylint: disable=no-member
opts.edgelabels, # pylint: disable=no-member
opts.rankdir, # pylint: disable=no-member
opts.legend, # pylint: disable=no-member
included_namespaces,
included_ontologies,
)
title = f"Taxonomy of {name}."
sec.append(
self.ontodoc.get_header(title, int(opts.level))
) # pylint: disable=no-member
sec.extend(
self.ontodoc.get_figure(
filepath, caption=title, width=width
).split("\n")
)
del self.lines[i]
self.lines[i:i] = sec
def process_includes(self):
"""Process all %INCLUDE directives."""
for i, line in reversed(list(enumerate(self.lines))):
if line.startswith("%INCLUDE "):
tokens = shlex.split(line)
filepath = tokens[1]
opts = get_options(tokens[2:], shift=0)
with open(
os.path.join(self.basedir, filepath), "rt", encoding="utf8"
) as handle:
docpp = DocPP(
handle.read(),
self.ontodoc,
basedir=os.path.dirname(filepath),
figformat=self.figformat,
figscale=self.figscale,
maxwidth=self.maxwidth,
)
docpp.figdir = self.figdir
if opts.shift: # pylint: disable=no-member
docpp.shift_header_levels(
int(opts.shift)
) # pylint: disable=no-member
docpp.process()
del self.lines[i]
self.lines[i:i] = docpp.lines
def process(self):
"""Perform all pre-processing steps."""
if not self._processed:
self.process_comments()
self.process_headers()
self.process_figures()
self.process_entities()
self.process_branches()
self.process_branchfigs()
self.process_branchdocs()
self.process_alls()
self.process_allfig()
self.process_includes()
self._processed = True
def write( # pylint: disable=too-many-arguments
self,
outfile,
fmt=None,
pandoc_option_files=(),
pandoc_options=(),
genfile=None,
verbose=True,
):
"""Writes documentation to `outfile`.
Parameters
----------
outfile : str
File that the documentation is written to.
fmt : str
Output format. If it is "md" or "simple-html",
the built-in template generator is used. Otherwise
pandoc is used. If not given, the format is inferred
from the `outfile` name extension.
pandoc_option_files : sequence
Sequence with command line arguments provided to pandoc.
pandoc_options : sequence
Additional pandoc options overriding options read from
`pandoc_option_files`.
genfile : str
Store temporary generated markdown input file to pandoc
to this file (for debugging).
verbose : bool
Whether to show some messages when running pandoc.
"""
self.process()
content = self.get_buffer()
substitutions = self.ontodoc.style.get("substitutions", [])
for reg, sub in substitutions:
content = re.sub(reg, sub, content)
fmt = get_format(outfile, default="html", fmt=fmt)
if fmt not in ("simple-html", "markdown", "md"): # Run pandoc
if not genfile:
with NamedTemporaryFile(mode="w+t", suffix=".md") as temp_file:
temp_file.write(content)
temp_file.flush()
genfile = temp_file.name
run_pandoc(
genfile,
outfile,
fmt,
pandoc_option_files=pandoc_option_files,
pandoc_options=pandoc_options,
verbose=verbose,
)
else:
with open(genfile, "wt") as handle:
handle.write(content)
run_pandoc(
genfile,
outfile,
fmt,
pandoc_option_files=pandoc_option_files,
pandoc_options=pandoc_options,
verbose=verbose,
)
else:
if verbose:
print("Writing:", outfile)
with open(outfile, "wt") as handle:
handle.write(content)
copy(self)
¶
Returns a copy of self.
Source code in ontopy/ontodoc.py
def copy(self):
"""Returns a copy of self."""
docpp = DocPP(
"",
self.ontodoc,
self.basedir,
figformat=self.figformat,
figscale=self.figscale,
maxwidth=self.maxwidth,
)
docpp.lines[:] = self.lines
docpp.figdir = self.figdir
return docpp
get_branches(self)
¶
Returns a list with all branch names as specified with %BRANCH (in current and all included documents). The returned value is cached for efficiency purposes and so that it is not lost after processing branches.
Source code in ontopy/ontodoc.py
def get_branches(self):
"""Returns a list with all branch names as specified with %BRANCH
(in current and all included documents). The returned value is
cached for efficiency purposes and so that it is not lost after
processing branches."""
if self._branch_cache is None:
names = []
docpp = self.copy()
docpp.process_includes()
for line in docpp.lines:
if line.startswith("%BRANCH"):
names.append(shlex.split(line)[1])
self._branch_cache = names
return self._branch_cache
get_buffer(self)
¶
Returns the current buffer.
Source code in ontopy/ontodoc.py
def get_buffer(self):
"""Returns the current buffer."""
return "\n".join(self.lines)
process(self)
¶
Perform all pre-processing steps.
Source code in ontopy/ontodoc.py
def process(self):
"""Perform all pre-processing steps."""
if not self._processed:
self.process_comments()
self.process_headers()
self.process_figures()
self.process_entities()
self.process_branches()
self.process_branchfigs()
self.process_branchdocs()
self.process_alls()
self.process_allfig()
self.process_includes()
self._processed = True
process_allfig(self)
¶
Process all %ALLFIG directives.
Source code in ontopy/ontodoc.py
def process_allfig(self): # pylint: disable=too-many-locals
"""Process all %ALLFIG directives."""
onto = self.ontodoc.onto
for i, line in reversed(list(enumerate(self.lines))):
if line.startswith("%ALLFIG "):
tokens = shlex.split(line)
token = tokens[1]
opts = get_options(
tokens[2:],
path="",
level=3,
terminated=0,
include_leaves=1,
strict_leaves=1,
width=0,
leaves="",
relations="isA",
edgelabels=0,
rankdir="BT",
legend=1,
namespaces="",
ontologies="",
)
if token == "classes": # nosec
roots = onto.get_root_classes(imported=self.imported)
elif token in ("object_properties", "relations"):
roots = onto.get_root_object_properties(
imported=self.imported
)
elif token == "data_properties": # nosec
roots = onto.get_root_data_properties(
imported=self.imported
)
else:
raise InvalidTemplateError(
f"Invalid argument to %%ALLFIG: {token}"
)
included_namespaces = (
opts.namespaces.split(",")
if opts.namespaces
else () # pylint: disable=no-member
)
included_ontologies = (
opts.ontologies.split(",")
if opts.ontologies
else () # pylint: disable=no-member
)
sec = []
for root in roots:
name = asstring(root, link="{label}", ontology=onto)
filepath, _, width = self._make_branchfig(
name,
opts.path, # pylint: disable=no-member
opts.terminated, # pylint: disable=no-member
opts.include_leaves, # pylint: disable=no-member
opts.strict_leaves, # pylint: disable=no-member
opts.width, # pylint: disable=no-member
opts.leaves, # pylint: disable=no-member
opts.relations, # pylint: disable=no-member
opts.edgelabels, # pylint: disable=no-member
opts.rankdir, # pylint: disable=no-member
opts.legend, # pylint: disable=no-member
included_namespaces,
included_ontologies,
)
title = f"Taxonomy of {name}."
sec.append(
self.ontodoc.get_header(title, int(opts.level))
) # pylint: disable=no-member
sec.extend(
self.ontodoc.get_figure(
filepath, caption=title, width=width
).split("\n")
)
del self.lines[i]
self.lines[i:i] = sec
process_alls(self)
¶
Expand all %ALL specifications.
Source code in ontopy/ontodoc.py
def process_alls(self):
"""Expand all %ALL specifications."""
onto = self.ontodoc.onto
for i, line in reversed(list(enumerate(self.lines))):
if line.startswith("%ALL "):
tokens = shlex.split(line)
token = tokens[1]
opts = get_options(tokens[2:], header_level=3)
if token == "classes": # nosec
items = onto.classes(imported=self.imported)
elif token in ("object_properties", "relations"):
items = onto.object_properties(imported=self.imported)
elif token == "data_properties": # nosec
items = onto.data_properties(imported=self.imported)
elif token == "annotation_properties": # nosec
items = onto.annotation_properties(imported=self.imported)
elif token == "individuals": # nosec
items = onto.individuals(imported=self.imported)
else:
raise InvalidTemplateError(
f"Invalid argument to %%ALL: {token}"
)
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
).split("\n")
process_branchdocs(self)
¶
Process all %BRANCHDOC and %BRANCHEAD directives.
Source code in ontopy/ontodoc.py
def process_branchdocs(self): # pylint: disable=too-many-locals
"""Process all %BRANCHDOC and %BRANCHEAD directives."""
onto = self.ontodoc.onto
for i, line in reversed(list(enumerate(self.lines))):
if line.startswith("%BRANCHDOC ") or line.startswith(
"%BRANCHHEAD "
):
with_branch = bool(line.startswith("%BRANCHDOC "))
tokens = shlex.split(line)
name = tokens[1]
title = camelsplit(name)
title = title[0].upper() + title[1:] + " branch"
opts = get_options(
tokens[2:],
level=2,
path="",
title=title,
caption=title + ".",
terminated=1,
strict_leaves=1,
width=0,
leaves="",
relations="all",
edgelabels=0,
rankdir="BT",
legend=1,
namespaces="",
ontologies="",
)
included_namespaces = (
opts.namespaces.split(",")
if opts.namespaces
else () # pylint: disable=no-member
)
included_ontologies = (
opts.ontologies.split(",")
if opts.ontologies
else () # pylint: disable=no-member
)
include_leaves = 1
filepath, leaves, width = self._make_branchfig(
name,
opts.path, # pylint: disable=no-member
opts.terminated, # pylint: disable=no-member
include_leaves,
opts.strict_leaves, # pylint: disable=no-member
opts.width, # pylint: disable=no-member
opts.leaves, # pylint: disable=no-member
opts.relations, # pylint: disable=no-member
opts.edgelabels, # pylint: disable=no-member
opts.rankdir, # pylint: disable=no-member
opts.legend, # pylint: disable=no-member
included_namespaces,
included_ontologies,
)
sec = []
sec.append(
self.ontodoc.get_header(opts.title, int(opts.level))
) # pylint: disable=no-member
sec.append(
self.ontodoc.get_figure(
filepath,
caption=opts.caption,
width=width, # pylint: disable=no-member
)
)
if with_branch:
include_leaves = 0
branch = filter_classes(
onto.get_branch(name, leaves, include_leaves),
included_namespaces=included_namespaces,
included_ontologies=included_ontologies,
)
sec.append(
self.ontodoc.itemsdoc(
branch, int(opts.level + 1)
) # pylint: disable=no-member
)
del self.lines[i]
self.lines[i:i] = sec
process_branches(self)
¶
Expand all %BRANCH specifications.
Source code in ontopy/ontodoc.py
def process_branches(self):
"""Expand all %BRANCH specifications."""
onto = self.ontodoc.onto
# Get all branch names in final document
names = self.get_branches()
for i, line in reversed(list(enumerate(self.lines))):
if line.startswith("%BRANCH "):
tokens = shlex.split(line)
name = tokens[1]
opts = get_options(
tokens[2:],
header_level=3,
terminated=1,
include_leaves=0,
namespaces="",
ontologies="",
)
leaves = (
names if opts.terminated else ()
) # pylint: disable=no-member
included_namespaces = (
opts.namespaces.split(",")
if opts.namespaces
else () # pylint: disable=no-member
)
included_ontologies = (
opts.ontologies.split(",")
if opts.ontologies
else () # pylint: disable=no-member
)
branch = filter_classes(
onto.get_branch(
name, leaves, opts.include_leaves
), # pylint: disable=no-member
included_namespaces=included_namespaces,
included_ontologies=included_ontologies,
)
del self.lines[i]
self.lines[i:i] = self.ontodoc.itemsdoc(
branch, int(opts.header_level) # pylint: disable=no-member
).split("\n")
process_branchfigs(self)
¶
Process all %BRANCHFIG directives.
Source code in ontopy/ontodoc.py
def process_branchfigs(self):
"""Process all %BRANCHFIG directives."""
for i, line in reversed(list(enumerate(self.lines))):
if line.startswith("%BRANCHFIG "):
tokens = shlex.split(line)
name = tokens[1]
opts = get_options(
tokens[2:],
path="",
caption="",
terminated=1,
include_leaves=1,
strict_leaves=1,
width=0,
leaves="",
relations="all",
edgelabels=0,
rankdir="BT",
legend=1,
namespaces="",
ontologies="",
)
included_namespaces = (
opts.namespaces.split(",")
if opts.namespaces
else () # pylint: disable=no-member
)
included_ontologies = (
opts.ontologies.split(",")
if opts.ontologies
else () # pylint: disable=no-member
)
filepath, _, width = self._make_branchfig(
name,
opts.path, # pylint: disable=no-member
opts.terminated, # pylint: disable=no-member
opts.include_leaves, # pylint: disable=no-member
opts.strict_leaves, # pylint: disable=no-member
opts.width, # pylint: disable=no-member
opts.leaves, # pylint: disable=no-member
opts.relations, # pylint: disable=no-member
opts.edgelabels, # pylint: disable=no-member
opts.rankdir, # pylint: disable=no-member
opts.legend, # pylint: disable=no-member
included_namespaces,
included_ontologies,
)
del self.lines[i]
self.lines[i:i] = self.ontodoc.get_figure(
filepath,
caption=opts.caption,
width=width, # pylint: disable=no-member
).split("\n")
process_comments(self)
¶
Strips out comment lines starting with "%%".
Source code in ontopy/ontodoc.py
def process_comments(self):
"""Strips out comment lines starting with "%%"."""
self.lines = [line for line in self.lines if not line.startswith("%%")]
process_entities(self)
¶
Expand all %ENTITY specifications.
Source code in ontopy/ontodoc.py
def process_entities(self):
"""Expand all %ENTITY specifications."""
for i, line in reversed(list(enumerate(self.lines))):
if line.startswith("%ENTITY "):
tokens = shlex.split(line)
name = tokens[1]
opts = get_options(tokens[2:], header_level=3)
del self.lines[i]
self.lines[i:i] = self.ontodoc.itemdoc(
name, int(opts.header_level) # pylint: disable=no-member
).split("\n")
process_figures(self)
¶
Expand all %FIGURE specifications.
Source code in ontopy/ontodoc.py
def process_figures(self):
"""Expand all %FIGURE specifications."""
for i, line in reversed(list(enumerate(self.lines))):
if line.startswith("%FIGURE "):
tokens = shlex.split(line)
path = tokens[1]
opts = get_options(tokens[2:], caption="", width=0)
del self.lines[i]
self.lines[i:i] = self.ontodoc.get_figure(
os.path.join(self.basedir, path),
caption=opts.caption, # pylint: disable=no-member
width=opts.width, # pylint: disable=no-member
).split("\n")
process_headers(self)
¶
Expand all %HEADER specifications.
Source code in ontopy/ontodoc.py
def process_headers(self):
"""Expand all %HEADER specifications."""
for i, line in reversed(list(enumerate(self.lines))):
if line.startswith("%HEADER "):
tokens = shlex.split(line)
name = tokens[1]
opts = get_options(tokens[2:], level=1)
del self.lines[i]
self.lines[i:i] = self.ontodoc.get_header(
name, int(opts.level) # pylint: disable=no-member
).split("\n")
process_includes(self)
¶
Process all %INCLUDE directives.
Source code in ontopy/ontodoc.py
def process_includes(self):
"""Process all %INCLUDE directives."""
for i, line in reversed(list(enumerate(self.lines))):
if line.startswith("%INCLUDE "):
tokens = shlex.split(line)
filepath = tokens[1]
opts = get_options(tokens[2:], shift=0)
with open(
os.path.join(self.basedir, filepath), "rt", encoding="utf8"
) as handle:
docpp = DocPP(
handle.read(),
self.ontodoc,
basedir=os.path.dirname(filepath),
figformat=self.figformat,
figscale=self.figscale,
maxwidth=self.maxwidth,
)
docpp.figdir = self.figdir
if opts.shift: # pylint: disable=no-member
docpp.shift_header_levels(
int(opts.shift)
) # pylint: disable=no-member
docpp.process()
del self.lines[i]
self.lines[i:i] = docpp.lines
shift_header_levels(self, shift)
¶
Shift header level of all hashtag-headers in buffer. Underline headers are ignored.
Source code in ontopy/ontodoc.py
def shift_header_levels(self, shift):
"""Shift header level of all hashtag-headers in buffer. Underline
headers are ignored."""
if not shift:
return
pat = re.compile("^#+ ")
for i, line in enumerate(self.lines):
match = pat.match(line)
if match:
if shift > 0:
self.lines[i] = "#" * shift + line
elif shift < 0:
counter = match.end()
if shift > counter:
self.lines[i] = line.lstrip("# ")
else:
self.lines[i] = line[counter:]
write(self, outfile, fmt=None, pandoc_option_files=(), pandoc_options=(), genfile=None, verbose=True)
¶
Writes documentation to outfile
.
Parameters¶
outfile : str
File that the documentation is written to.
fmt : str
Output format. If it is "md" or "simple-html",
the built-in template generator is used. Otherwise
pandoc is used. If not given, the format is inferred
from the outfile
name extension.
pandoc_option_files : sequence
Sequence with command line arguments provided to pandoc.
pandoc_options : sequence
Additional pandoc options overriding options read from
pandoc_option_files
.
genfile : str
Store temporary generated markdown input file to pandoc
to this file (for debugging).
verbose : bool
Whether to show some messages when running pandoc.
Source code in ontopy/ontodoc.py
def write( # pylint: disable=too-many-arguments
self,
outfile,
fmt=None,
pandoc_option_files=(),
pandoc_options=(),
genfile=None,
verbose=True,
):
"""Writes documentation to `outfile`.
Parameters
----------
outfile : str
File that the documentation is written to.
fmt : str
Output format. If it is "md" or "simple-html",
the built-in template generator is used. Otherwise
pandoc is used. If not given, the format is inferred
from the `outfile` name extension.
pandoc_option_files : sequence
Sequence with command line arguments provided to pandoc.
pandoc_options : sequence
Additional pandoc options overriding options read from
`pandoc_option_files`.
genfile : str
Store temporary generated markdown input file to pandoc
to this file (for debugging).
verbose : bool
Whether to show some messages when running pandoc.
"""
self.process()
content = self.get_buffer()
substitutions = self.ontodoc.style.get("substitutions", [])
for reg, sub in substitutions:
content = re.sub(reg, sub, content)
fmt = get_format(outfile, default="html", fmt=fmt)
if fmt not in ("simple-html", "markdown", "md"): # Run pandoc
if not genfile:
with NamedTemporaryFile(mode="w+t", suffix=".md") as temp_file:
temp_file.write(content)
temp_file.flush()
genfile = temp_file.name
run_pandoc(
genfile,
outfile,
fmt,
pandoc_option_files=pandoc_option_files,
pandoc_options=pandoc_options,
verbose=verbose,
)
else:
with open(genfile, "wt") as handle:
handle.write(content)
run_pandoc(
genfile,
outfile,
fmt,
pandoc_option_files=pandoc_option_files,
pandoc_options=pandoc_options,
verbose=verbose,
)
else:
if verbose:
print("Writing:", outfile)
with open(outfile, "wt") as handle:
handle.write(content)
InvalidTemplateError (NameError)
¶
Raised on errors in template files.
Source code in ontopy/ontodoc.py
class InvalidTemplateError(NameError):
"""Raised on errors in template files."""
OntoDoc
¶
A class for helping documentating ontologies.
Parameters¶
onto : Ontology instance The ontology that should be documented. style : dict | "html" | "markdown" | "markdown_tex" A dict defining the following template strings (and substitutions):
:header: Formats an header.
Substitutions: {level}, {label}
:link: Formats a link.
Substitutions: {name}
:point: Formats a point (list item).
Substitutions: {point}, {ontology}
:points: Formats a list of points. Used within annotations.
Substitutions: {points}, {ontology}
:annotation: Formats an annotation.
Substitutions: {key}, {value}, {ontology}
:substitutions: list of ``(regex, sub)`` pairs for substituting
annotation values.
Source code in ontopy/ontodoc.py
class OntoDoc:
"""A class for helping documentating ontologies.
Parameters
----------
onto : Ontology instance
The ontology that should be documented.
style : dict | "html" | "markdown" | "markdown_tex"
A dict defining the following template strings (and substitutions):
:header: Formats an header.
Substitutions: {level}, {label}
:link: Formats a link.
Substitutions: {name}
:point: Formats a point (list item).
Substitutions: {point}, {ontology}
:points: Formats a list of points. Used within annotations.
Substitutions: {points}, {ontology}
:annotation: Formats an annotation.
Substitutions: {key}, {value}, {ontology}
:substitutions: list of ``(regex, sub)`` pairs for substituting
annotation values.
"""
_markdown_style = {
"sep": "\n",
"figwidth": "{{ width={width:.0f}px }}",
"figure": "![{caption}]({path}){figwidth}\n",
"header": "\n{:#<{level}} {label} {{#{anchor}}}",
# 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",
"substitutions": [],
}
# Extra style settings for markdown+tex (e.g. pdf generation with pandoc)
_markdown_tex_extra_style = {
"substitutions": [
# logic/math symbols
("\u2200", r"$\\forall$"),
("\u2203", r"$\\exists$"),
("\u2206", r"$\\nabla$"),
("\u2227", r"$\\land$"),
("\u2228", r"$\\lor$"),
("\u2207", r"$\\nabla$"),
("\u2212", r"-"),
("->", r"$\\rightarrow$"),
# uppercase greek letters
("\u0391", r"$\\Upalpha$"),
("\u0392", r"$\\Upbeta$"),
("\u0393", r"$\\Upgamma$"),
("\u0394", r"$\\Updelta$"),
("\u0395", r"$\\Upepsilon$"),
("\u0396", r"$\\Upzeta$"),
("\u0397", r"$\\Upeta$"),
("\u0398", r"$\\Uptheta$"),
("\u0399", r"$\\Upiota$"),
("\u039a", r"$\\Upkappa$"),
("\u039b", r"$\\Uplambda$"),
("\u039c", r"$\\Upmu$"),
("\u039d", r"$\\Upnu$"),
("\u039e", r"$\\Upxi$"),
("\u039f", r"$\\Upomekron$"),
("\u03a0", r"$\\Uppi$"),
("\u03a1", r"$\\Uprho$"),
("\u03a3", r"$\\Upsigma$"), # no \u0302
("\u03a4", r"$\\Uptau$"),
("\u03a5", r"$\\Upupsilon$"),
("\u03a6", r"$\\Upvarphi$"),
("\u03a7", r"$\\Upchi$"),
("\u03a8", r"$\\Uppsi$"),
("\u03a9", r"$\\Upomega$"),
# lowercase greek letters
("\u03b1", r"$\\upalpha$"),
("\u03b2", r"$\\upbeta$"),
("\u03b3", r"$\\upgamma$"),
("\u03b4", r"$\\updelta$"),
("\u03b5", r"$\\upepsilon$"),
("\u03b6", r"$\\upzeta$"),
("\u03b7", r"$\\upeta$"),
("\u03b8", r"$\\uptheta$"),
("\u03b9", r"$\\upiota$"),
("\u03ba", r"$\\upkappa$"),
("\u03bb", r"$\\uplambda$"),
("\u03bc", r"$\\upmu$"),
("\u03bd", r"$\\upnu$"),
("\u03be", r"$\\upxi$"),
("\u03bf", r"o"), # no \upomicron
("\u03c0", r"$\\uppi$"),
("\u03c1", r"$\\uprho$"),
("\u03c2", r"$\\upvarsigma$"),
("\u03c3", r"$\\upsigma$"),
("\u03c4", r"$\\uptau$"),
("\u03c5", r"$\\upupsilon$"),
("\u03c6", r"$\\upvarphi$"),
("\u03c7", r"$\\upchi$"),
("\u03c8", r"$\\uppsi$"),
("\u03c9", r"$\\upomega$"),
# acutes, accents, etc...
("\u03ae", r"$\\acute{\\upeta}$"),
("\u1e17", r"$\\acute{\\bar{\\mathrm{e}}}$"),
("\u03ac", r"$\\acute{\\upalpha}$"),
("\u00e1", r"$\\acute{\\mathrm{a}}$"),
("\u03cc", r"$\\acute{o}$"), # no \upomicron
("\u014d", r"$\\bar{\\mathrm{o}}$"),
("\u1f45", r"$\\acute{o}$"), # no \omicron
],
}
_html_style = {
"sep": "<p>\n",
"figwidth": 'width="{width:.0f}"',
"figure": '<img src="{path}" alt="{caption}"{figwidth}>',
"header": '<h{level} id="{anchor}">{label}</h{level}>',
"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",
"substitutions": [
(r"&", r"‒"),
(r"<p>", r"<p>\n\n"),
(r"\u2018([^\u2019]*)\u2019", r"<q>\1</q>"),
(r"\u2019", r"'"),
(r"\u2260", r"≠"),
(r"\u2264", r"≤"),
(r"\u2265", r"≥"),
(r"\u226A", r"&x226A;"),
(r"\u226B", r"&x226B;"),
(r'"Y$', r""), # strange noice added by owlready2
],
}
def __init__(self, onto, style="markdown"):
if isinstance(style, str):
if style == "markdown_tex":
style = self._markdown_style.copy()
style.update(self._markdown_tex_extra_style)
else:
style = getattr(self, f"_{style}_style")
self.onto = onto
self.style = style
self.url_regex = re.compile(r"https?:\/\/[^\s ]+")
def get_default_template(self):
"""Returns default template."""
title = os.path.splitext(
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,
ref=self.onto.base_iri,
label=self.onto.base_iri,
lowerlabel=self.onto.base_iri,
)
template = dedent(
"""\
%HEADER {title}
Documentation of {irilink}
%HEADER Relations level=2
%ALL object_properties
%HEADER Classes level=2
%ALL classes
%HEADER Individuals level=2
%ALL individuals
%HEADER Appendix level=1
%HEADER "Relation taxonomies" level=2
%ALLFIG object_properties
%HEADER "Class taxonomies" level=2
%ALLFIG classes
"""
).format(ontology=self.onto, title=title, irilink=irilink)
return template
def get_header(self, label, header_level=1, anchor=None):
"""Returns `label` formatted as a header of given level."""
header_style = self.style.get("header", "{label}\n")
return header_style.format(
"",
level=header_level,
label=label,
anchor=anchor if anchor else label.lower().replace(" ", "-"),
)
def get_figure(self, path, caption="", width=None):
"""Returns a formatted insert-figure-directive."""
figwidth_style = self.style.get("figwidth", "")
figure_style = self.style.get("figure", "")
figwidth = figwidth_style.format(width=width) if width else ""
return figure_style.format(
path=path, caption=caption, figwidth=figwidth
)
def itemdoc(
self, item, header_level=3, show_disjoints=False
): # pylint: disable=too-many-locals,too-many-branches,too-many-statements
"""Returns documentation of `item`.
Parameters
----------
item : obj | label
The class, individual or relation to document.
header_level : int
Header level. Defaults to 3.
show_disjoints : Bool
Whether to show `disjoint_with` relations.
"""
onto = self.onto
if isinstance(item, str):
item = self.onto.get_by_label(item)
header_style = self.style.get("header", "{label}\n")
link_style = self.style.get("link", "{name}")
point_style = self.style.get("point", "{point}")
points_style = self.style.get("points", "{points}")
annotation_style = self.style.get("annotation", "{key}: {value}\n")
substitutions = self.style.get("substitutions", [])
# Logical "sorting" of annotations
order = {
"definition": "00",
"axiom": "01",
"theorem": "02",
"elucidation": "03",
"domain": "04",
"range": "05",
"example": "06",
}
doc = []
# Header
label = get_label(item)
iriname = item.iri.partition("#")[2]
anchor = iriname if iriname else label.lower()
doc.append(
header_style.format(
"",
level=header_level,
label=label,
anchor=anchor,
)
)
# Add warning about missing prefLabel
if not hasattr(item, "prefLabel") or not item.prefLabel.first():
doc.append(
annotation_style.format(
key="Warning", value="Missing prefLabel"
)
)
# Add iri
doc.append(
annotation_style.format(
key="IRI",
value=asstring(item.iri, link_style, ontology=onto),
ontology=onto,
)
)
# Add annotations
if isinstance(item, owlready2.Thing):
annotations = item.get_individual_annotations()
else:
annotations = item.get_annotations()
for key in sorted(
annotations.keys(), key=lambda key: order.get(key, key)
):
for value in annotations[key]:
value = str(value)
if self.url_regex.match(value):
doc.append(
annotation_style.format(
key=key,
value=asstring(value, link_style, ontology=onto),
)
)
else:
for reg, sub in substitutions:
value = re.sub(reg, sub, value)
doc.append(annotation_style.format(key=key, value=value))
# ...add relations from is_a
points = []
non_prop = (
owlready2.ThingClass, # owlready2.Restriction,
owlready2.And,
owlready2.Or,
owlready2.Not,
)
for prop in item.is_a:
if isinstance(prop, non_prop) or (
isinstance(item, owlready2.PropertyClass)
and isinstance(prop, owlready2.PropertyClass)
):
points.append(
point_style.format(
point="is_a "
+ asstring(prop, link_style, ontology=onto),
ontology=onto,
)
)
else:
points.append(
point_style.format(
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, ontology=onto)
)
)
# ...add disjoint_with relations
if show_disjoints and hasattr(item, "disjoint_with"):
subjects = set(item.disjoint_with(reduce=True))
points.append(
point_style.format(
point="disjoint_with "
+ ", ".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(u, link_style, ontology=onto) for u in unions
)
points.append(
point_style.format(
point=f"disjoint_union_of {string}", ontology=onto
)
)
# ...add inverse_of relations
if hasattr(item, "inverse_property") and item.inverse_property:
points.append(
point_style.format(
point="inverse_of "
+ asstring(item.inverse_property, link_style, ontology=onto)
)
)
# ...add domain restrictions
for domain in getattr(item, "domain", ()):
points.append(
point_style.format(
point="domain "
+ asstring(domain, link_style, ontology=onto)
)
)
# ...add range restrictions
for restriction in getattr(item, "range", ()):
points.append(
point_style.format(
point="range "
+ asstring(restriction, link_style, ontology=onto)
)
)
# Add points (from is_a)
if points:
value = points_style.format(points="".join(points), ontology=onto)
doc.append(
annotation_style.format(
key="Subclass of", value=value, ontology=onto
)
)
# Instances (individuals)
if hasattr(item, "instances"):
points = []
for instance in item.instances():
if isinstance(instance.is_instance_of, property):
warnings.warn(
f'Ignoring instance "{instance}" which is both and '
"indivudual and class. Ontodoc does not support "
"punning at the present moment."
)
continue
if item in instance.is_instance_of:
points.append(
point_style.format(
point=asstring(instance, link_style, ontology=onto),
ontology=onto,
)
)
if points:
value = points_style.format(
points="".join(points), ontology=onto
)
doc.append(
annotation_style.format(
key="Individuals", value=value, ontology=onto
)
)
return "\n".join(doc)
def itemsdoc(self, items, header_level=3):
"""Returns documentation of `items`."""
sep_style = self.style.get("sep", "\n")
doc = []
for item in items:
doc.append(self.itemdoc(item, header_level))
doc.append(sep_style.format(ontology=self.onto))
return "\n".join(doc)
get_default_template(self)
¶
Returns default template.
Source code in ontopy/ontodoc.py
def get_default_template(self):
"""Returns default template."""
title = os.path.splitext(
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,
ref=self.onto.base_iri,
label=self.onto.base_iri,
lowerlabel=self.onto.base_iri,
)
template = dedent(
"""\
%HEADER {title}
Documentation of {irilink}
%HEADER Relations level=2
%ALL object_properties
%HEADER Classes level=2
%ALL classes
%HEADER Individuals level=2
%ALL individuals
%HEADER Appendix level=1
%HEADER "Relation taxonomies" level=2
%ALLFIG object_properties
%HEADER "Class taxonomies" level=2
%ALLFIG classes
"""
).format(ontology=self.onto, title=title, irilink=irilink)
return template
get_figure(self, path, caption='', width=None)
¶
Returns a formatted insert-figure-directive.
Source code in ontopy/ontodoc.py
def get_figure(self, path, caption="", width=None):
"""Returns a formatted insert-figure-directive."""
figwidth_style = self.style.get("figwidth", "")
figure_style = self.style.get("figure", "")
figwidth = figwidth_style.format(width=width) if width else ""
return figure_style.format(
path=path, caption=caption, figwidth=figwidth
)
get_header(self, label, header_level=1, anchor=None)
¶
Returns label
formatted as a header of given level.
Source code in ontopy/ontodoc.py
def get_header(self, label, header_level=1, anchor=None):
"""Returns `label` formatted as a header of given level."""
header_style = self.style.get("header", "{label}\n")
return header_style.format(
"",
level=header_level,
label=label,
anchor=anchor if anchor else label.lower().replace(" ", "-"),
)
itemdoc(self, item, header_level=3, show_disjoints=False)
¶
Returns documentation of item
.
Parameters¶
item : obj | label
The class, individual or relation to document.
header_level : int
Header level. Defaults to 3.
show_disjoints : Bool
Whether to show disjoint_with
relations.
Source code in ontopy/ontodoc.py
def itemdoc(
self, item, header_level=3, show_disjoints=False
): # pylint: disable=too-many-locals,too-many-branches,too-many-statements
"""Returns documentation of `item`.
Parameters
----------
item : obj | label
The class, individual or relation to document.
header_level : int
Header level. Defaults to 3.
show_disjoints : Bool
Whether to show `disjoint_with` relations.
"""
onto = self.onto
if isinstance(item, str):
item = self.onto.get_by_label(item)
header_style = self.style.get("header", "{label}\n")
link_style = self.style.get("link", "{name}")
point_style = self.style.get("point", "{point}")
points_style = self.style.get("points", "{points}")
annotation_style = self.style.get("annotation", "{key}: {value}\n")
substitutions = self.style.get("substitutions", [])
# Logical "sorting" of annotations
order = {
"definition": "00",
"axiom": "01",
"theorem": "02",
"elucidation": "03",
"domain": "04",
"range": "05",
"example": "06",
}
doc = []
# Header
label = get_label(item)
iriname = item.iri.partition("#")[2]
anchor = iriname if iriname else label.lower()
doc.append(
header_style.format(
"",
level=header_level,
label=label,
anchor=anchor,
)
)
# Add warning about missing prefLabel
if not hasattr(item, "prefLabel") or not item.prefLabel.first():
doc.append(
annotation_style.format(
key="Warning", value="Missing prefLabel"
)
)
# Add iri
doc.append(
annotation_style.format(
key="IRI",
value=asstring(item.iri, link_style, ontology=onto),
ontology=onto,
)
)
# Add annotations
if isinstance(item, owlready2.Thing):
annotations = item.get_individual_annotations()
else:
annotations = item.get_annotations()
for key in sorted(
annotations.keys(), key=lambda key: order.get(key, key)
):
for value in annotations[key]:
value = str(value)
if self.url_regex.match(value):
doc.append(
annotation_style.format(
key=key,
value=asstring(value, link_style, ontology=onto),
)
)
else:
for reg, sub in substitutions:
value = re.sub(reg, sub, value)
doc.append(annotation_style.format(key=key, value=value))
# ...add relations from is_a
points = []
non_prop = (
owlready2.ThingClass, # owlready2.Restriction,
owlready2.And,
owlready2.Or,
owlready2.Not,
)
for prop in item.is_a:
if isinstance(prop, non_prop) or (
isinstance(item, owlready2.PropertyClass)
and isinstance(prop, owlready2.PropertyClass)
):
points.append(
point_style.format(
point="is_a "
+ asstring(prop, link_style, ontology=onto),
ontology=onto,
)
)
else:
points.append(
point_style.format(
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, ontology=onto)
)
)
# ...add disjoint_with relations
if show_disjoints and hasattr(item, "disjoint_with"):
subjects = set(item.disjoint_with(reduce=True))
points.append(
point_style.format(
point="disjoint_with "
+ ", ".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(u, link_style, ontology=onto) for u in unions
)
points.append(
point_style.format(
point=f"disjoint_union_of {string}", ontology=onto
)
)
# ...add inverse_of relations
if hasattr(item, "inverse_property") and item.inverse_property:
points.append(
point_style.format(
point="inverse_of "
+ asstring(item.inverse_property, link_style, ontology=onto)
)
)
# ...add domain restrictions
for domain in getattr(item, "domain", ()):
points.append(
point_style.format(
point="domain "
+ asstring(domain, link_style, ontology=onto)
)
)
# ...add range restrictions
for restriction in getattr(item, "range", ()):
points.append(
point_style.format(
point="range "
+ asstring(restriction, link_style, ontology=onto)
)
)
# Add points (from is_a)
if points:
value = points_style.format(points="".join(points), ontology=onto)
doc.append(
annotation_style.format(
key="Subclass of", value=value, ontology=onto
)
)
# Instances (individuals)
if hasattr(item, "instances"):
points = []
for instance in item.instances():
if isinstance(instance.is_instance_of, property):
warnings.warn(
f'Ignoring instance "{instance}" which is both and '
"indivudual and class. Ontodoc does not support "
"punning at the present moment."
)
continue
if item in instance.is_instance_of:
points.append(
point_style.format(
point=asstring(instance, link_style, ontology=onto),
ontology=onto,
)
)
if points:
value = points_style.format(
points="".join(points), ontology=onto
)
doc.append(
annotation_style.format(
key="Individuals", value=value, ontology=onto
)
)
return "\n".join(doc)
itemsdoc(self, items, header_level=3)
¶
Returns documentation of items
.
Source code in ontopy/ontodoc.py
def itemsdoc(self, items, header_level=3):
"""Returns documentation of `items`."""
sep_style = self.style.get("sep", "\n")
doc = []
for item in items:
doc.append(self.itemdoc(item, header_level))
doc.append(sep_style.format(ontology=self.onto))
return "\n".join(doc)
append_pandoc_options(options, updates)
¶
Append updates
to pandoc options options
.
Parameters¶
options : sequence
Sequence with initial Pandoc options.
updates : sequence of str
Sequence of strings of the form "--longoption=value", where
longoption
is a valid pandoc long option and value
is the
new value. The "=value" part is optional.
Strings of the form "no-longoption" will filter out "--longoption"
from `options`.
Returns¶
new_options : list Updated pandoc options.
Source code in ontopy/ontodoc.py
def append_pandoc_options(options, updates):
"""Append `updates` to pandoc options `options`.
Parameters
----------
options : sequence
Sequence with initial Pandoc options.
updates : sequence of str
Sequence of strings of the form "--longoption=value", where
``longoption`` is a valid pandoc long option and ``value`` is the
new value. The "=value" part is optional.
Strings of the form "no-longoption" will filter out "--longoption"
from `options`.
Returns
-------
new_options : list
Updated pandoc options.
"""
# Valid pandoc options starting with "--no-XXX"
no_options = set("no-highlight")
if not updates:
return list(options)
curated_updates = {}
for update in updates:
key, sep, value = update.partition("=")
curated_updates[key.lstrip("-")] = value if sep else None
filter_out = set(
_
for _ in curated_updates
if _.startswith("no-") and _ not in no_options
)
_filter_out = set(f"--{_[3:]}" for _ in filter_out)
new_options = [
opt for opt in options if opt.partition("=")[0] not in _filter_out
]
new_options.extend(
[
f"--{key}" if value is None else f"--{key}={value}"
for key, value in curated_updates.items()
if key not in filter_out
]
)
return new_options
get_docpp(ontodoc, infile, figdir='genfigs', figformat='png', maxwidth=None, imported=False)
¶
Read infile
and return a new docpp instance.
Source code in ontopy/ontodoc.py
def get_docpp( # pylint: disable=too-many-arguments
ontodoc,
infile,
figdir="genfigs",
figformat="png",
maxwidth=None,
imported=False,
):
"""Read `infile` and return a new docpp instance."""
if infile:
with open(infile, "rt") as handle:
template = handle.read()
basedir = os.path.dirname(infile)
else:
template = ontodoc.get_default_template()
basedir = "."
docpp = DocPP(
template,
ontodoc,
basedir=basedir,
figdir=figdir,
figformat=figformat,
maxwidth=maxwidth,
imported=imported,
)
return docpp
get_figformat(fmt)
¶
Infer preferred figure format from output format.
Source code in ontopy/ontodoc.py
def get_figformat(fmt):
"""Infer preferred figure format from output format."""
if fmt == "pdf":
figformat = "pdf" # XXX
elif "html" in fmt:
figformat = "svg"
else:
figformat = "png"
return figformat
get_maxwidth(fmt)
¶
Infer preferred max figure width from output format.
Source code in ontopy/ontodoc.py
def get_maxwidth(fmt):
"""Infer preferred max figure width from output format."""
if fmt == "pdf":
maxwidth = 668
else:
maxwidth = 1024
return maxwidth
get_options(opts, **kwargs)
¶
Returns a dict with options from the sequence opts
with
"name=value" pairs. Valid option names and default values are
provided with the keyword arguments.
Source code in ontopy/ontodoc.py
def get_options(opts, **kwargs):
"""Returns a dict with options from the sequence `opts` with
"name=value" pairs. Valid option names and default values are
provided with the keyword arguments."""
res = AttributeDict(kwargs)
for opt in opts:
if "=" not in opt:
raise InvalidTemplateError(
f'Missing "=" in template option: {opt!r}'
)
name, value = opt.split("=", 1)
if name not in res:
raise InvalidTemplateError(f"Invalid template option: {name!r}")
res_type = type(res[name])
res[name] = res_type(value)
return res
get_style(fmt)
¶
Infer style from output format.
Source code in ontopy/ontodoc.py
def get_style(fmt):
"""Infer style from output format."""
if fmt == "simple-html":
style = "html"
elif fmt in ("tex", "latex", "pdf"):
style = "markdown_tex"
else:
style = "markdown"
return style
load_pandoc_option_file(yamlfile)
¶
Loads pandoc options from yamlfile
and return a list with
corresponding pandoc command line arguments.
Source code in ontopy/ontodoc.py
def load_pandoc_option_file(yamlfile):
"""Loads pandoc options from `yamlfile` and return a list with
corresponding pandoc command line arguments."""
with open(yamlfile) as handle:
pandoc_options = yaml.safe_load(handle)
options = pandoc_options.pop("input-files", [])
variables = pandoc_options.pop("variables", {})
for key, value in pandoc_options.items():
if isinstance(value, bool):
if value:
options.append(f"--{key}")
else:
options.append(f"--{key}={value}")
for key, value in variables.items():
if key == "date" and value == "now":
value = time.strftime("%B %d, %Y")
options.append(f"--variable={key}:{value}")
return options
run_pandoc(genfile, outfile, fmt, pandoc_option_files=(), pandoc_options=(), verbose=True)
¶
Runs pandoc.
Parameters¶
genfile : str
Name of markdown input file.
outfile : str
Output file name.
fmt : str
Output format.
pandoc_option_files : sequence
List of files with additional pandoc options. Default is to read
"pandoc-options.yaml" and "pandoc-FORMAT-options.yml", where
FORMAT
is the output format.
pandoc_options : sequence
Additional pandoc options overriding options read from
pandoc_option_files
.
verbose : bool
Whether to print the pandoc command before execution.
Raises¶
subprocess.CalledProcessError
If the pandoc process returns with non-zero status. The returncode
attribute will hold the exit code.
Source code in ontopy/ontodoc.py
def run_pandoc( # pylint: disable=too-many-arguments
genfile,
outfile,
fmt,
pandoc_option_files=(),
pandoc_options=(),
verbose=True,
):
"""Runs pandoc.
Parameters
----------
genfile : str
Name of markdown input file.
outfile : str
Output file name.
fmt : str
Output format.
pandoc_option_files : sequence
List of files with additional pandoc options. Default is to read
"pandoc-options.yaml" and "pandoc-FORMAT-options.yml", where
`FORMAT` is the output format.
pandoc_options : sequence
Additional pandoc options overriding options read from
`pandoc_option_files`.
verbose : bool
Whether to print the pandoc command before execution.
Raises
------
subprocess.CalledProcessError
If the pandoc process returns with non-zero status. The `returncode`
attribute will hold the exit code.
"""
# Create pandoc argument list
args = [genfile]
files = ["pandoc-options.yaml", f"pandoc-{fmt}-options.yaml"]
if pandoc_option_files:
files = pandoc_option_files
for fname in files:
if os.path.exists(fname):
args.extend(load_pandoc_option_file(fname))
else:
warnings.warn(f"missing pandoc option file: {fname}")
# Update pandoc argument list
args = append_pandoc_options(args, pandoc_options)
# pdf output requires a special attention...
if fmt == "pdf":
pdf_engine = "pdflatex"
for arg in args:
if arg.startswith("--pdf-engine"):
pdf_engine = arg.split("=", 1)[1]
break
with TemporaryDirectory() as tmpdir:
run_pandoc_pdf(tmpdir, pdf_engine, outfile, args, verbose=verbose)
else:
args.append(f"--output={outfile}")
cmd = ["pandoc"] + args
if verbose:
print()
print("* Executing command:")
print(" ".join(shlex.quote(_) for _ in cmd))
subprocess.check_call(cmd) # nosec
run_pandoc_pdf(latex_dir, pdf_engine, outfile, args, verbose=True)
¶
Run pandoc for pdf generation.
Source code in ontopy/ontodoc.py
def run_pandoc_pdf(latex_dir, pdf_engine, outfile, args, verbose=True):
"""Run pandoc for pdf generation."""
basename = os.path.join(
latex_dir, os.path.splitext(os.path.basename(outfile))[0]
)
# Run pandoc
texfile = basename + ".tex"
args.append(f"--output={texfile}")
cmd = ["pandoc"] + args
if verbose:
print()
print("* Executing commands:")
print(" ".join(shlex.quote(s) for s in cmd))
subprocess.check_call(cmd) # nosec
# Fixing tex output
texfile2 = basename + "2.tex"
with open(texfile, "rt") as handle:
content = handle.read().replace(r"\$\Uptheta\$", r"$\Uptheta$")
with open(texfile2, "wt") as handle:
handle.write(content)
# Run latex
pdffile = basename + "2.pdf"
cmd = [
pdf_engine,
texfile2,
"-halt-on-error",
f"-output-directory={latex_dir}",
]
if verbose:
print()
print(" ".join(shlex.quote(s) for s in cmd))
output = subprocess.check_output(cmd, timeout=60) # nosec
output = subprocess.check_output(cmd, timeout=60) # nosec
# Workaround for non-working "-output-directory" latex option
if not os.path.exists(pdffile):
if os.path.exists(os.path.basename(pdffile)):
pdffile = os.path.basename(pdffile)
for ext in "aux", "out", "toc", "log":
filename = os.path.splitext(pdffile)[0] + "." + ext
if os.path.exists(filename):
os.remove(filename)
else:
print()
print(output)
print()
raise RuntimeError("latex did not produce pdf file: " + pdffile)
# Copy pdffile
if not os.path.exists(outfile) or not os.path.samefile(pdffile, outfile):
if verbose:
print()
print(f"move {pdffile} to {outfile}")
shutil.move(pdffile, outfile)