Skip to content

Commit

Permalink
Merge pull request #58 from Harold-Solbrig/issue_57
Browse files Browse the repository at this point in the history
Add examples of how to convert using python.
  • Loading branch information
hsolbrig authored Dec 10, 2022
2 parents ec69860 + be28fc1 commit d146cfd
Show file tree
Hide file tree
Showing 22 changed files with 17,251 additions and 16 deletions.
11 changes: 11 additions & 0 deletions ChangeLog
Original file line number Diff line number Diff line change
@@ -1,6 +1,17 @@
CHANGES
=======

* Removed packaging import. Not needed
* Documentation and official CLI added. Should be good to go
* Unit tests complete
* Add the ability to suppress the axiom progress indicator
* Add examples of how to convert using python
* Remove Pipfile.lock from distro
* Disable update-requriements step
* Disable update-requriements step
* Attempt to fix pipenv-to-requirements failure
* Passes both rdflib 5.0.0 and 6.2.0
* minor formatting in README
* Checkpoint to clean up the main branch
* Demonstration of some of the issues we face in rdflib 6.2
* All v6 tests work as intended
Expand Down
98 changes: 98 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,104 @@ Ontology(
)
```

## Transforming Functional Syntax to RDF
See: [test_issue_57.py](tests/test_issues/test_issue_57.py) for full example
```python
from rdflib import Graph
from funowl.converters.functional_converter import to_python

# The functional syntax input can be a string, URL, file loc or open file
function_pizza = "https://github.com/Harold-Solbrig/funowl/blob/main/tests/data/pizza.owl"
internal_pizza = to_python(function_pizza)

# Emit the internal representation as an rdflib graph
g = Graph()
internal_pizza.to_rdf(g)

# Serialize the rdflib graph in your favorite format
print(g.serialize(format="ttl"))
```

## Command Line Interface
`funowl` can be installed with either `pip` or `pipenv`.

```shell
> funowl -h
usage: funowl [-h]
[-f {ttl,hext,json-ld,longturtle,n3,nquads,nt,nt11,ntriples,pretty-xml,trig,trix,ttl,turtle,xml}]
[-np]
input [output]

Convert OWL Functional Syntax to RDF

positional arguments:
input Input OWL functional syntax. Can be a file name or URL
output Output file. If omitted, output goes to stdout

options:
-h, --help show this help message and exit
-f {ttl,hext,json-ld,longturtle,n3,nquads,nt,nt11,ntriples,pretty-xml,trig,trix,ttl,turtle,xml}, --format {ttl,hext,json-ld,longturtle,n3,nquads,nt,nt11,ntriples,pretty-xml,trig,trix,ttl,turtle,xml}
Output RDF Format. If omitted, guess from output file
suffix. If guessing doesn't work, assume 'turtle'
-np, --noProgressBar Don't output the progress indicators
```

To convert an OWL functional representation of the pizza ontology to RDF:
```shell
> funowl https://raw.githubusercontent.com/Harold-Solbrig/funowl/main/tests/data/pizza.owl
@prefix dc: <http://purl.org/dc/elements/1.1/> .
@prefix owl: <http://www.w3.org/2002/07/owl#> .
@prefix pizza: <http://www.co-ode.org/ontologies/pizza/pizza.owl#> .
@prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> .
@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .
@prefix skos: <http://www.w3.org/2004/02/skos/core#> .
@prefix terms: <http://purl.org/dc/terms/> .
@prefix xsd: <http://www.w3.org/2001/XMLSchema#> .

dc:description a owl:AnnotationProperty .

dc:title a owl:AnnotationProperty .

terms:contributor a owl:AnnotationProperty .

terms:license a owl:AnnotationProperty .

terms:provenance a owl:AnnotationProperty .

<http://www.co-ode.org/ontologies/pizza> a owl:Ontology ;
rdfs:label "pizza"^^xsd:string ;
dc:description """An ontology about pizzas and their toppings.
...
```
To convert the same ontology into XML, either:
`funowl https://raw.githubusercontent.com/Harold-Solbrig/funowl/main/tests/data/pizza.owl -f xml > pizza.xml`
or
`funowl https://raw.githubusercontent.com/Harold-Solbrig/funowl/main/tests/data/pizza.owl pizza.xml`
```shell
> cat pizza.xml
<?xml version="1.0" encoding="utf-8"?>
<rdf:RDF
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:owl="http://www.w3.org/2002/07/owl#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:rdfs="http://www.w3.org/2000/01/rdf-schema#"
xmlns:skos="http://www.w3.org/2004/02/skos/core#"
xmlns:terms="http://purl.org/dc/terms/"
>
<rdf:Description rdf:nodeID="Nd1a614092c234a3b90971238bb6550e8">
<rdf:type rdf:resource="http://www.w3.org/2002/07/owl#Restriction"/>
<owl:onProperty rdf:resource="http://www.co-ode.org/ontologies/pizza/pizza.owl#hasTopping"/>
<owl:someValuesFrom rdf:resource="http://www.co-ode.org/ontologies/pizza/pizza.owl#TomatoTopping"/>
</rdf:Description>
...
</rdf:RDF>
```
## Other packages
While we would be happy to be corrected, to the best of our knowledge there is to be minimal support for OWL in python.
Expand Down
54 changes: 54 additions & 0 deletions funowl/cli.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import sys
from argparse import ArgumentParser
from typing import Optional, Union, List

from rdflib import Graph
from rdflib.plugin import plugins as rdflib_plugins
from rdflib.serializer import Serializer as rdflib_serializer
from rdflib.util import guess_format

from .converters.functional_converter import to_python

valid_formats = ["ttl"] + sorted(
[x.name for x in rdflib_plugins(None, rdflib_serializer) if "/" not in str(x.name)]
)
DEFAULT_FORMAT = "ttl"


def genargs(prog: Optional[str] = None) -> ArgumentParser:
"""
Create a command line parser
:return: parser
"""
parser = ArgumentParser(prog, description="Convert OWL Functional Syntax to RDF")
parser.add_argument("input", help="Input OWL functional syntax. Can be a file name or URL")
parser.add_argument("output", help="Output file. If omitted, output goes to stdout", nargs='?')
parser.add_argument("-f", "--format", help="Output RDF Format. If omitted, guess from output file suffix.\n"
" If guessing doesn't work, assume 'turtle'",
choices=valid_formats)
parser.add_argument("-np", "--noProgressBar", help="Don't output the progress indicators", action="store_true")
return parser


def evaluate_cli(argv: Optional[Union[str, List[str]]] = None, prog: Optional[str] = None) -> int:
if isinstance(argv, str):
argv = argv.split()
opts = genargs(prog).parse_args(argv if argv is not None else sys.argv[1:])

# Read the functional syntax ontology
ontology = to_python(opts.input, print_progress=bool(opts.output) and not opts.noProgressBar)

# Convert to RDF
g = Graph()
ontology.to_rdf(g)

# Emit as appropriate
if opts.output:
g.serialize(opts.output, format=opts.format or guess_format(opts.output) or DEFAULT_FORMAT)
else:
print(g.serialize(format=opts.format or DEFAULT_FORMAT))
return 0


if __name__ == '__main__':
evaluate_cli(sys.argv[1:])
11 changes: 7 additions & 4 deletions funowl/converters/functional_converter.py
Original file line number Diff line number Diff line change
Expand Up @@ -270,12 +270,14 @@ def skip_comments(inp: Union[bytes, str], start: int) -> int:
m = comments_re.match(inp, start)
return start

def fparse(inp: bytes, start: int, consumer: Callable[[FunOwlBase], None]) -> int:

def fparse(inp: bytes, start: int, consumer: Callable[[FunOwlBase], None], print_progress: bool = True) -> int:
"""
Functional parser - work through inp pulling complete functions out and processing them.
:param inp: input byte stream
:param start: current 0 based position in the stream
:param consumer: OWLFunc entry consumer
:param print_progress: Print conversion progress indicator on command line
:return: final position
"""
while True:
Expand All @@ -298,7 +300,7 @@ def fparse(inp: bytes, start: int, consumer: Callable[[FunOwlBase], None]) -> in
if vers:
o.version = vers
start = skip_comments(inp, start)
start = fparse(inp, start, lambda f: o.add_arg(f))
start = fparse(inp, start, lambda f: o.add_arg(f, print_progress=print_progress))
consumer(o)
start = skip_comments(inp, start)
m = final_pren.match(inp, start)
Expand Down Expand Up @@ -335,10 +337,11 @@ def to_bytes_array(defn: Union[str, bytes, IO]) -> Union[bytes, mmap]:
return mmap(f.fileno(), 0, access=ACCESS_READ)


def to_python(defn: Union[str, bytes, IO]) -> Optional[OntologyDocument]:
def to_python(defn: Union[str, bytes, IO], print_progress: bool = True) -> Optional[OntologyDocument]:
"""
Convert the functional syntax in defn to a Python representation
:param defn: The ontology definition
:param print_progress: Print progress indicator on command line
:return: Ontology Document
"""
def consumer(e: FunOwlBase) -> None:
Expand All @@ -350,5 +353,5 @@ def consumer(e: FunOwlBase) -> None:
logging.error("Unrecognized declaration")

ontology_doc = OntologyDocument()
fparse(to_bytes_array(defn), 0, consumer)
fparse(to_bytes_array(defn), 0, consumer, print_progress=print_progress)
return ontology_doc
25 changes: 13 additions & 12 deletions funowl/ontology_document.py
Original file line number Diff line number Diff line change
Expand Up @@ -92,20 +92,21 @@ def __init__(self, *args: Union[FunOwlBase, IRI.types()], **kwargs: Dict[str, Fu
raise ValueError(f"Unrecognized arguments to Ontology: {args}")
self._naxioms = 0

def add_arg(self, arg: [IRI.types(), Import, Axiom, Annotation]):
def add_arg(self, arg: [IRI.types(), Import, Axiom, Annotation], print_progress: bool = True):
if isinstance_(arg, Axiom):
self.axioms.append(arg)
self._naxioms += 1
if not self._naxioms % 100000:
print(self._naxioms)
elif not self._naxioms % 10000:
print(self._naxioms)
elif not self._naxioms % 1000:
print('k', end='')
sys.stdout.flush()
elif not self._naxioms % 100:
print('.', end='')
sys.stdout.flush()
if print_progress:
self._naxioms += 1
if not self._naxioms % 100000:
print(self._naxioms)
elif not self._naxioms % 10000:
print(self._naxioms)
elif not self._naxioms % 1000:
print('k', end='')
sys.stdout.flush()
elif not self._naxioms % 100:
print('.', end='')
sys.stdout.flush()
elif isinstance(arg, IRI):
if not self.iri:
self.iri = arg
Expand Down
3 changes: 3 additions & 0 deletions setup.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -28,3 +28,6 @@ keywords =
packages =
funowl

[entry_points]
console_scripts =
funowl = funowl.cli:evaluate_cli
1 change: 1 addition & 0 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,5 @@
setup(
setup_requires=['pbr'],
pbr=True,
py_modules=['funowl']
)
Loading

0 comments on commit d146cfd

Please sign in to comment.