diff --git a/odml/doc.py b/odml/doc.py
index 279a9b2c..4a33ccea 100644
--- a/odml/doc.py
+++ b/odml/doc.py
@@ -141,3 +141,21 @@ def get_terminology_equivalent(self):
return None
term = terminology.load(self.repository)
return term
+def pprint(self, indent=2, max_depth=1, max_length=80, current_depth=0):
+ """
+ Pretty print method to visualize Document-Section trees.
+
+ :param indent: number of leading spaces for every child Section or Property.
+ :param max_depth: maximum number of hierarchical levels printed from the
+ starting Section.
+ :param max_length: maximum number of characters printed in one line.
+ :param current_depth: number of hierarchical levels printed from the
+ starting Section.
+ """
+ doc_str = "[{} [{}] {}, sections: {}, repository: {}]".format(self.author, self.version,
+ self.date, len(self._sections), self.repository)
+ print(doc_str)
+
+ for s in self._sections:
+ s.pprint(current_depth=current_depth+1, max_depth=max_depth,
+ indent=indent, max_length=max_length)
diff --git a/odml/dtypes.py b/odml/dtypes.py
index c6e27c3d..69cae553 100644
--- a/odml/dtypes.py
+++ b/odml/dtypes.py
@@ -62,6 +62,8 @@ def default_values(dtype):
_dtype_map = {'str': 'string', 'bool': 'boolean'}
+special_dtypes = ["url", "person", "text"]
+
def infer_dtype(value):
dtype = (type(value)).__name__
diff --git a/odml/property.py b/odml/property.py
index 5da97893..a120ab38 100644
--- a/odml/property.py
+++ b/odml/property.py
@@ -82,7 +82,7 @@ def __init__(self, name=None, values=None, parent=None, unit=None,
self._values = []
self.values = values
- if not values and (value or isinstance(value, bool)):
+ if not values and (value or isinstance(value, bool) or isinstance(value, int)):
self.values = value
self.parent = parent
@@ -588,8 +588,10 @@ def extend(self, obj, strict=True):
new_value = self._convert_value_input(obj)
if len(new_value) > 0 and strict and dtypes.infer_dtype(new_value[0]) != self.dtype:
- raise ValueError("odml.Property.extend: "
- "passed value data type does not match dtype!")
+
+ if not (dtypes.infer_dtype(new_value[0]) == "string" and self.dtype in dtypes.special_dtypes):
+ raise ValueError("odml.Property.extend: passed value data type found (\"%s\") "
+ "does not match expected dtype \"%s\"!" % (dtypes.infer_dtype(new_value[0]), self._dtype))
if not self._validate_values(new_value):
raise ValueError("odml.Property.extend: passed value(s) cannot be converted "
@@ -618,8 +620,10 @@ def append(self, obj, strict=True):
raise ValueError("odml.property.append: Use extend to add a list of values!")
if len(new_value) > 0 and strict and dtypes.infer_dtype(new_value[0]) != self.dtype:
- raise ValueError("odml.Property.append: "
- "passed value data type does not match dtype!")
+
+ if not (dtypes.infer_dtype(new_value[0]) == "string" and self.dtype in dtypes.special_dtypes):
+ raise ValueError("odml.Property.append: passed value data type found (\"%s\") "
+ "does not match expected dtype \"%s\"!" % (dtypes.infer_dtype(new_value[0]), self._dtype))
if not self._validate_values(new_value):
raise ValueError("odml.Property.append: passed value(s) cannot be converted "
diff --git a/odml/tools/version_converter.py b/odml/tools/version_converter.py
index 3d842e89..c7b22ab4 100644
--- a/odml/tools/version_converter.py
+++ b/odml/tools/version_converter.py
@@ -9,6 +9,8 @@
from ..info import FORMAT_VERSION
from ..terminology import Terminologies, REPOSITORY_BASE
+import uuid
+
try:
unicode = unicode
except NameError:
@@ -194,6 +196,8 @@ def _convert(self, tree):
if e.tag == "repository":
self._handle_repository(e)
+ tree = self._check_add_ids(tree)
+
return tree
def _handle_include(self, element):
@@ -410,6 +414,41 @@ def _change_entity_name(tree, elem_map, name):
elem_map[named_path] += 1
name.text += "-" + str(elem_map[named_path])
+ def _check_add_ids(self, tree):
+ """
+ Checks, whether elements (properties) possess an UUID
+ and adds one in case of absence.
+ :param tree: ElementTree of the doc
+ :return: ElementTree
+ """
+ root = tree.getroot()
+ self._add_id(root)
+ for sec in root.iter("section"):
+ self._add_id(sec)
+ for prop in sec.iter("property"):
+ self._add_id(prop)
+
+ return tree
+
+ @staticmethod
+ def _add_id(element):
+ """
+ Checks, whether an element possesses an ID. If yes, make sure it has
+ the right format. Otherwise a new UUID is created.
+ :param element: lxml element.
+ """
+ oid = element.find("id")
+ new_id = ET.Element("id")
+ new_id.text = str(uuid.uuid4())
+ if oid is not None:
+ try:
+ if oid.text is not None:
+ new_id.text = str(uuid.UUID(oid.text))
+ except ValueError as e:
+ print(e)
+ element.remove(oid)
+ element.append(new_id)
+
def _log(self, msg):
"""
Adds the passed message to the conversion_log attribute and
diff --git a/test/test_property.py b/test/test_property.py
index 80a67d61..80f6c1cc 100644
--- a/test/test_property.py
+++ b/test/test_property.py
@@ -103,6 +103,18 @@ def test_value(self):
with self.assertRaises(ValueError):
_ = Property(name="Public-Key", value='(5689; 1254; 687)', dtype='2-tuple')
+ p3 = Property('myprop', value=0, dtype=DType.int)
+ self.assertEqual(p3.value, [0])
+ self.assertEqual(p3.values, [0])
+
+ p4 = Property('myprop', value=0, dtype=DType.boolean)
+ self.assertEqual(p4.value, [False])
+ self.assertEqual(p4.values, [False])
+
+ p5 = Property('myprop', value=0)
+ self.assertEqual(p5.value, [0])
+ self.assertEqual(p5.values, [0])
+
def test_value_append(self):
# Test append w/o Property value or dtype
prop = Property(name="append")
@@ -183,6 +195,27 @@ def test_value_append(self):
self.assertEqual(len(p5), 2)
self.assertRaises(ValueError, p5.append, "[a, b, c]")
+ p6 = Property(name="prop", value=["A Abraham", "B Barnes", "C Clark"], dtype=DType.person)
+ p6.append("D Dickins")
+ self.assertEqual(len(p6), 4)
+ self.assertRaises(ValueError, p6.append, 1)
+ self.assertRaises(ValueError, p6.append, 1.3)
+ self.assertRaises(ValueError, p6.append, True)
+
+ p7 = Property(name="prop", value=["https://en.wikipedia.org/wiki/Earth"], dtype=DType.url)
+ p7.append("https://en.wikipedia.org/wiki/Mars")
+ self.assertEqual(len(p7), 2)
+ self.assertRaises(ValueError, p7.append, 1)
+ self.assertRaises(ValueError, p7.append, 1.3)
+ self.assertRaises(ValueError, p7.append, True)
+
+ p8 = Property(name="prop", value=["Earth is No. 3."], dtype=DType.text)
+ p8.append("Mars is No. 4.")
+ self.assertEqual(len(p8), 2)
+ self.assertRaises(ValueError, p8.append, 1)
+ self.assertRaises(ValueError, p8.append, 1.3)
+ self.assertRaises(ValueError, p8.append, True)
+
def test_value_extend(self):
prop = Property(name="extend")
@@ -260,6 +293,27 @@ def test_value_extend(self):
with self.assertRaises(ValueError):
prop.extend([6, "some text"])
+ p1 = Property(name="prop", value=["A Abraham", "B Barnes", "C Clark"], dtype=DType.person)
+ p1.extend("D Dickins")
+ self.assertEqual(len(p1), 4)
+ self.assertRaises(ValueError, p1.extend, 1)
+ self.assertRaises(ValueError, p1.extend, 1.3)
+ self.assertRaises(ValueError, p1.extend, True)
+
+ p2 = Property(name="prop", value=["https://en.wikipedia.org/wiki/Earth"], dtype=DType.url)
+ p2.extend("https://en.wikipedia.org/wiki/Mars")
+ self.assertEqual(len(p2), 2)
+ self.assertRaises(ValueError, p2.append, 1)
+ self.assertRaises(ValueError, p2.append, 1.3)
+ self.assertRaises(ValueError, p2.append, True)
+
+ p3 = Property(name="prop", value=["Earth is No. 3."], dtype=DType.text)
+ p3.extend("Mars is No. 4.")
+ self.assertEqual(len(p3), 2)
+ self.assertRaises(ValueError, p3.append, 1)
+ self.assertRaises(ValueError, p3.append, 1.3)
+ self.assertRaises(ValueError, p3.append, True)
+
def test_get_set_value(self):
values = [1, 2, 3, 4, 5]
p = Property("property", value=values)
diff --git a/test/test_version_converter.py b/test/test_version_converter.py
index 29c28ab3..8b166e47 100644
--- a/test/test_version_converter.py
+++ b/test/test_version_converter.py
@@ -272,11 +272,13 @@ def test_convert_odml_file_section(self):
conv_doc = vc._convert(vc._parse_xml())
root = conv_doc.getroot()
+ root_id = root.findall("id")
+ self.assertEqual(len(root_id), 1)
sec = root.findall("section")
self.assertEqual(len(sec), 2)
# Test valid section tags.
- self.assertEqual(len(sec[0]), 10)
+ self.assertEqual(len(sec[0]), 11)
self.assertEqual(sec[0].find("name").text, "Section name")
self.assertEqual(sec[0].find("type").text, "Section type")
self.assertEqual(sec[0].find("definition").text, "Section definition")
@@ -286,10 +288,11 @@ def test_convert_odml_file_section(self):
self.assertEqual(sec[0].find("include").text, "Section include")
self.assertEqual(len(sec[0].findall("property")), 2)
self.assertEqual(len(sec[0].findall("section")), 1)
+ self.assertEqual(len(sec[0].findall("id")), 1)
# Test valid subsection tags.
subsec = sec[0].find("section")
- self.assertEqual(len(subsec), 8)
+ self.assertEqual(len(subsec), 9)
self.assertEqual(subsec.find("name").text, "SubSection name")
self.assertEqual(subsec.find("type").text, "SubSection type")
self.assertEqual(subsec.find("definition").text, "SubSection definition")
@@ -298,10 +301,12 @@ def test_convert_odml_file_section(self):
self.assertEqual(subsec.find("repository").text, local_url)
self.assertEqual(subsec.find("include").text, "SubSection include")
self.assertEqual(len(subsec.findall("property")), 1)
+ self.assertEqual(len(subsec.findall("id")), 1)
# Test absence of non-Section tags
- self.assertEqual(len(sec[1]), 1)
+ self.assertEqual(len(sec[1]), 2)
self.assertEqual(len(sec[1].findall("name")), 1)
+ self.assertEqual(len(sec[1].findall("id")), 1)
# Test presence of v1.0 repository tag and warning log entry
doc = """
@@ -379,19 +384,21 @@ def test_convert_odml_file_property(self):
self.assertEqual(sec[0].find("name").text, "Valid Property tags test")
self.assertEqual(len(sec[0].findall("property")), 1)
prop = sec[0].find("property")
- self.assertEqual(len(prop), 5)
+ self.assertEqual(len(prop), 6)
self.assertEqual(prop.find("name").text, "Property name")
self.assertEqual(prop.find("type").text, "Property type")
self.assertEqual(prop.find("definition").text, "Property definition")
self.assertEqual(prop.find("dependency").text, "Property dependency")
self.assertEqual(prop.find("dependencyvalue").text, "Property dependency value")
+ self.assertEqual(len(prop.findall("id")), 1)
# Test non-import of Property w/o name
self.assertEqual(len(sec[1].findall("property")), 1)
# Test absence of non-Property tags
prop = sec[1].find("property")
- self.assertEqual(len(prop), 1)
+ self.assertEqual(len(prop), 2)
self.assertEqual(len(prop.findall("name")), 1)
+ self.assertEqual(len(prop.findall("id")), 1)
def test_convert_odml_file_value(self):
"""Test proper conversion of the odml.Value entity from
@@ -511,6 +518,22 @@ def test_convert_odml_file_value(self):
3 int
testIntListWhiteSpace
+
+
+ Single value with UUID
+ 1
+ aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa
+
+
+
+ Single value with none-UUID ID
+ 1
+
+
+
+ Single value with empty ID
+
+
@@ -520,27 +543,33 @@ def test_convert_odml_file_value(self):
vc = self.VC(file)
conv_doc = vc._convert(vc._parse_xml())
root = conv_doc.getroot()
+ root_id = root.findall("id")
+ self.assertEqual(len(root_id), 1)
sec = root.find("section")
- self.assertEqual(len(sec), 14)
+ self.assertEqual(len(sec), 18)
+ self.assertEqual(len(sec.findall("id")), 1)
# Test single value export
prop = sec.findall("property")[0]
- self.assertEqual(len(prop), 2)
+ self.assertEqual(len(prop), 3)
self.assertEqual(prop.find("value").text, "1")
+ self.assertEqual(len(prop.findall("id")), 1)
# Test multiple value export
prop = sec.findall("property")[1]
- self.assertEqual(len(prop), 2)
+ self.assertEqual(len(prop), 3)
self.assertEqual(prop.find("value").text, "[1,2,3]")
+ self.assertEqual(len(prop.findall("id")), 1)
# Test empty value export
prop = sec.findall("property")[2]
- self.assertEqual(len(prop), 1)
+ self.assertEqual(len(prop), 2)
self.assertEqual(prop.find("name").text, "Empty value export")
+ self.assertEqual(len(prop.findall("id")), 1)
# Test valid Value tags
prop = sec.findall("property")[3]
- self.assertEqual(len(prop), 7)
+ self.assertEqual(len(prop), 8)
self.assertEqual(prop.find("value").text, "0.1")
self.assertEqual(prop.find("type").text, "float")
self.assertEqual(prop.find("uncertainty").text, "0.05")
@@ -548,58 +577,88 @@ def test_convert_odml_file_value(self):
self.assertEqual(prop.find("value_origin").text, "raw.txt")
self.assertEqual(prop.find("reference").text, "Value reference")
self.assertEqual(len(prop.findall("filename")), 0)
+ self.assertEqual(len(prop.findall("id")), 1)
# Test valid multiple Value tag export
prop = sec.findall("property")[4]
- self.assertEqual(len(prop), 7)
+ self.assertEqual(len(prop), 8)
self.assertEqual(prop.find("value").text, "[0.1,0.2,3]")
self.assertEqual(prop.find("type").text, "float")
self.assertEqual(prop.find("uncertainty").text, "0.05")
self.assertEqual(prop.find("unit").text, "mV")
self.assertEqual(prop.find("value_origin").text, "raw.txt")
self.assertEqual(prop.find("reference").text, "Value reference")
+ self.assertEqual(len(prop.findall("id")), 1)
# Test non-export of invalid Value tags
prop = sec.findall("property")[5]
- self.assertEqual(len(prop), 1)
+ self.assertEqual(len(prop), 2)
self.assertEqual(len(prop.findall("name")), 1)
+ self.assertEqual(len(prop.findall("id")), 1)
# Test dtype 'binary' replacement
prop = sec.findall("property")[6]
self.assertEqual(prop.find("name").text, "Unsupported binary value type replace")
self.assertEqual(prop.find("type").text, "text")
+ self.assertEqual(len(prop.findall("id")), 1)
prop = sec.findall("property")[7]
self.assertEqual(prop.find("name").text, "Unsupported binary value dtype replace")
self.assertEqual(prop.find("type").text, "text")
+ self.assertEqual(len(prop.findall("id")), 1)
# Test single string value with commata
prop = sec.findall("property")[8]
self.assertEqual(prop.find("name").text, "testSingleString")
self.assertEqual(prop.find("value").text,
"Single, string, value, with, many, commata.")
+ self.assertEqual(len(prop.findall("id")), 1)
# Test string list import
prop = sec.findall("property")[9]
self.assertEqual(prop.find("name").text, "testStringList")
self.assertEqual(prop.find("value").text, "[A,B,C]")
+ self.assertEqual(len(prop.findall("id")), 1)
# Test single string values wrapping whitespace removal
prop = sec.findall("property")[10]
self.assertEqual(prop.find("name").text, "testStringWhiteSpace")
self.assertEqual(prop.find("value").text,
"Single string value with wrapping whitespace")
+ self.assertEqual(len(prop.findall("id")), 1)
# Test multiple string values with wrapping whitespace removal
prop = sec.findall("property")[11]
self.assertEqual(prop.find("name").text, "testStringListWhiteSpace")
self.assertEqual(prop.find("value").text,
"[Multiple Strings,with wrapping,Whitespace]")
+ self.assertEqual(len(prop.findall("id")), 1)
# Test multiple int values with wrapping whitespaces
prop = sec.findall("property")[12]
self.assertEqual(prop.find("name").text, "testIntListWhiteSpace")
self.assertEqual(prop.find("type").text, "int")
self.assertEqual(prop.find("value").text, "[1,2,3]")
+ self.assertEqual(len(prop.findall("id")), 1)
+
+ # Test single value export
+ prop = sec.findall("property")[13]
+ self.assertEqual(len(prop), 3)
+ self.assertEqual(prop.find("value").text, "1")
+ self.assertEqual(len(prop.findall("id")), 1)
+ self.assertEqual(prop.find("id").text, "aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa")
+
+ # Test single value export
+ prop = sec.findall("property")[14]
+ self.assertEqual(len(prop), 2)
+ self.assertEqual(len(prop.findall("id")), 1)
+ self.assertNotEqual(prop.find("id").text, "1")
+
+ # Test single value export
+ prop = sec.findall("property")[15]
+ self.assertEqual(len(prop), 2)
+ self.assertEqual(len(prop.findall("id")), 1)
+ self.assertNotEqual(prop.find("id").text, "1")
+
def test_parse_dict_document(self):
# Test appending tags; not appending empty sections