diff --git a/.travis.yml b/.travis.yml index 19a49155..e39fca9f 100644 --- a/.travis.yml +++ b/.travis.yml @@ -17,7 +17,7 @@ matrix: install: - export PYVER=${TRAVIS_PYTHON_VERSION:0:1} - pip install --upgrade coveralls - - pip install lxml enum34 + - pip install lxml enum34 rdflib script: - python setup.py build @@ -28,4 +28,4 @@ script: fi; after_success: -- if [ $COVERALLS = 1 ]; then coveralls; fi; +- if [ $COVERALLS = 1 ]; then coveralls; fi; \ No newline at end of file diff --git a/odml/doc.py b/odml/doc.py index 596e7a27..bfcd6229 100644 --- a/odml/doc.py +++ b/odml/doc.py @@ -1,6 +1,8 @@ # -*- coding: utf-8 -import odml.dtypes as dtypes +import uuid + import odml.base as base +import odml.dtypes as dtypes import odml.format as format import odml.terminology as terminology from odml.tools.doc_inherit import inherit_docstring, allow_inherit_docstring @@ -14,9 +16,7 @@ class Document(base._baseobj): class BaseDocument(base.sectionable, Document): """ A represenation of an odML document in memory. - Its odml attributes are: *author*, *date*, *version* and *repository*. - A Document behaves very much like a section, except that it cannot hold properties. """ @@ -25,11 +25,19 @@ class BaseDocument(base.sectionable, Document): def __init__(self, author=None, date=None, version=None, repository=None): super(BaseDocument, self).__init__() + self._id = str(uuid.uuid4()) self._author = author self._date = date # date must be a datetime self._version = version self._repository = repository + @property + def id(self): + """ + The uuid for the document. + """ + return self._id + @property def author(self): """ @@ -78,7 +86,6 @@ def finalize(self): """ This needs to be called after the document is set up from parsing it will perform additional operations, that need the complete document. - In particular, this method will resolve all *link* and *include* attributes accordingly. """ @@ -96,4 +103,4 @@ def get_terminology_equivalent(self): if self.repository is None: return None term = terminology.load(self.repository) - return term + return term \ No newline at end of file diff --git a/odml/format.py b/odml/format.py index 7f9c40b2..0e272aed 100644 --- a/odml/format.py +++ b/odml/format.py @@ -1,4 +1,7 @@ +from rdflib import Namespace + import odml + """ A module providing general format information and mappings of xml-attributes to their python class equivalents @@ -8,11 +11,27 @@ class Format(object): _map = {} _rev_map = None + _rdf_map = {} + _rdf_type = None + _ns = Namespace("https://g-node.org/projects/odml-rdf#") def map(self, name): """ Maps an odml name to a python name """ return self._map.get(name, name) + def rdf_map(self, name): + """ Maps a python name to a odml rdf namespace """ + return self._rdf_map.get(name, name) + + def rdf_type(self): + """ Return rdf type of an object """ + return self._rdf_type + + @staticmethod + def namespace(): + """ Return current link to current odml namespace""" + return Format._ns + def revmap(self, name): """ Maps a python name to an odml name """ if self._rev_map is None: @@ -33,7 +52,10 @@ def create(self, *args, **kargs): class Property(Format): _name = "property" + _ns = Format._ns + _rdf_type = _ns.Property _args = { + 'id': 0, 'name': 1, 'value': 0, 'unit': 0, @@ -48,11 +70,23 @@ class Property(Format): 'dependencyvalue': 'dependency_value', 'type': 'dtype' } + _rdf_map = { + 'id': _ns.id, + 'name': _ns.name, + 'definition': _ns.definition, + 'dtype': _ns.dtype, + 'unit': _ns.unit, + 'uncertainty': _ns.uncertainty, + 'value': _ns.hasValue + } class Section(Format): _name = "section" + _ns = Format._ns + _rdf_type = _ns.Section _args = { + 'id': 0, 'type': 1, 'name': 0, 'definition': 0, @@ -67,11 +101,24 @@ class Section(Format): 'section': 'sections', 'property': 'properties', } + _rdf_map = { + 'id': _ns.id, + 'name': _ns.name, + 'definition': _ns.definition, + 'type': _ns.type, + 'repository': _ns.terminology, + 'reference': _ns.reference, + 'sections': _ns.hasSection, + 'properties': _ns.hasProperty, + } class Document(Format): _name = "odML" + _ns = Format._ns + _rdf_type = _ns.Document _args = { + 'id': 0, 'version': 0, 'author': 0, 'date': 0, @@ -81,6 +128,15 @@ class Document(Format): _map = { 'section': 'sections' } + _rdf_map = { + 'id': _ns.id, + 'author': _ns.author, + 'date': _ns.date, + # 'doc_version': _ns.docversion, # discuss about the changes to the data model + 'repository': _ns.terminology, + 'sections': _ns.hasSection, + 'version': _ns['doc-version'], + } Document = Document() diff --git a/odml/property.py b/odml/property.py index a5846d83..70da20e9 100644 --- a/odml/property.py +++ b/odml/property.py @@ -1,8 +1,10 @@ # -*- coding: utf-8 +import uuid + import odml.base as base -import odml.format as frmt import odml.dtypes as dtypes +import odml.format as frmt from odml.tools.doc_inherit import inherit_docstring, allow_inherit_docstring @@ -21,7 +23,6 @@ def __init__(self, name, value=None, parent=None, unit=None, """ Create a new Property with a single value. The method will try to infer the value's dtype from the type of the value if not explicitly stated. - Example for a property with >>> p = Property("property1", "a string") >>> p.dtype @@ -46,6 +47,7 @@ def __init__(self, name, value=None, parent=None, unit=None, if dtype is not given, the type is deduced from the values """ # TODO validate arguments + self._id = str(uuid.uuid4()) self._name = name self._parent = None self._value = [] @@ -59,6 +61,10 @@ def __init__(self, name, value=None, parent=None, unit=None, self.value = value self.parent = parent + @property + def id(self): + return self._id + @property def name(self): return self._name @@ -74,10 +80,8 @@ def __repr__(self): def dtype(self): """ The data type of the value - If the data type is changed, it is tried, to convert the value to the new type. - If this doesn't work, the change is refused. This behaviour can be overridden by directly accessing the *_dtype* attribute and adjusting the *data* attribute manually. @@ -224,7 +228,6 @@ def dependency_value(self, new_value): def remove(self, value): """ Remove a value from this property and unset its parent. - Raises a TypeError if this would cause the property not to hold any value at all. This can be circumvented by using the *_values* property. """ @@ -284,4 +287,4 @@ def __len__(self): return len(self._value) def __getitem__(self, key): - return self._value[key] + return self._value[key] \ No newline at end of file diff --git a/odml/section.py b/odml/section.py index 407bf31a..8b99a58c 100644 --- a/odml/section.py +++ b/odml/section.py @@ -1,12 +1,14 @@ # -*- coding: utf-8 +import uuid + import odml.base as base import odml.format as format import odml.terminology as terminology +from odml.doc import BaseDocument # this is supposedly ok, as we only use it for an isinstance check from odml.property import Property # it MUST however not be used to create any Property objects from odml.tools.doc_inherit import inherit_docstring, allow_inherit_docstring -from odml.doc import BaseDocument class Section(base._baseobj): @@ -17,7 +19,7 @@ class Section(base._baseobj): class BaseSection(base.sectionable, Section): """ An odML Section """ type = None - id = None + # id = None _link = None _include = None reference = None # the *import* property @@ -28,6 +30,7 @@ class BaseSection(base.sectionable, Section): def __init__(self, name, type=None, parent=None, definition=None, reference=None): + self._id = str(uuid.uuid4()) self._parent = None self._name = name self._props = base.SmartList() @@ -42,6 +45,10 @@ def __repr__(self): return "
" % (self._name, self.type, len(self._sections)) + @property + def id(self): + return self._id + @property def name(self): return self._name @@ -94,15 +101,12 @@ def include(self, new_value): def link(self): """ Specifies a softlink, i.e. a path within the document - When the merge()-method is called, the link will be resolved creating according copies of the section referenced by the link attribute. - When the unmerge() method is called (happens when running clean()) the link is unresolved, i.e. all properties and sections that are completely equivalent to the merged object will be removed. (They will be restored accordingly when calling merge()). - When changing the *link* attribute, the previously merged section is unmerged, and the new reference will be immediately resolved. To avoid this side-effect, directly change the *_link* attribute. @@ -312,9 +316,7 @@ def contains(self, obj): def merge(self, section=None): """ Merges this section with another *section* - See also: :py:attr:`odml.section.BaseSection.link` - If section is none, sets the link/include attribute (if _link or _include are set), causing the section to be automatically merged to the referenced section. @@ -378,7 +380,6 @@ def is_merged(self): Returns True if the section is merged with another one (e.g. through :py:attr:`odml.section.BaseSection.link` or :py:attr:`odml.section.BaseSection.include`) - The merged object can be accessed through the *_merged* attribute. """ return self._merged is not None @@ -388,4 +389,4 @@ def can_be_merged(self): """ Returns True if either a *link* or an *include* attribute is specified """ - return self._link is not None or self._include is not None + return self._link is not None or self._include is not None \ No newline at end of file diff --git a/odml/tools/xmlparser.py b/odml/tools/xmlparser.py index 9030a9a3..3bee0615 100644 --- a/odml/tools/xmlparser.py +++ b/odml/tools/xmlparser.py @@ -1,15 +1,14 @@ #!/usr/bin/env python """ The XML parsing module. - Parses odML files. Can be invoked standalone: - python -m odml.tools.xmlparser file.odml """ # TODO make this module a parser class, allow arguments (e.g. # skip_errors=1 to parse even broken documents) -import sys import csv +import sys + try: from StringIO import StringIO except ImportError: @@ -18,7 +17,6 @@ from lxml import etree as ET from lxml.builder import E # this is needed for py2exe to include lxml completely -from lxml import _elementpath as _dummy try: unicode = unicode @@ -49,10 +47,10 @@ def to_csv(val): def from_csv(value_string): - if len(value_string) == 0: - return [] if value_string[0] == "[": value_string = value_string[1:-1] + if len(value_string) == 0: + return [] stream = StringIO(value_string) stream.seek(0) reader = csv.reader(stream, dialect="excel") @@ -164,9 +162,7 @@ class ParserException(Exception): class XMLReader(object): """ A reader to parse xml-files or strings into odml data structures - Usage: - >>> doc = XMLReader().fromFile(open("file.odml")) """ @@ -298,9 +294,12 @@ def parse_tag(self, root, fmt, insert_children=True, create=None): obj = create(args=arguments, text=''.join(text), children=children) for k, v in arguments.items(): - if hasattr(obj, k) and getattr(obj, k) is None: + if hasattr(obj, k) and (getattr(obj, k) is None or k == 'id'): try: - setattr(obj, k, v) + if k == 'id' and v is not None: + obj._id = v + else: + setattr(obj, k, v) except Exception as e: self.warn("cannot set '%s' property on <%s>: %s" % (k, root.tag, repr(e)), root) @@ -353,4 +352,4 @@ def parse_value(self, root, fmt): if len(args) < 1: parser.print_help() else: - dumper.dumpDoc(load(args[0])) + dumper.dumpDoc(load(args[0])) \ No newline at end of file diff --git a/setup.py b/setup.py index 4878975b..149ffa79 100644 --- a/setup.py +++ b/setup.py @@ -14,7 +14,7 @@ with open('README.rst') as f: description_text = f.read() -install_req = ["lxml"] +install_req = ["lxml", "rdflib"] if sys.version_info < (3, 4): install_req += ["enum34"] @@ -28,4 +28,4 @@ test_suite='test', install_requires=install_req, long_description=description_text, - ) + ) \ No newline at end of file