Skip to content

Commit c741854

Browse files
authored
Merge pull request #284 from mpsonntag/formatUpdate
nice!
2 parents bc4bade + 37bcae9 commit c741854

File tree

7 files changed

+130
-56
lines changed

7 files changed

+130
-56
lines changed

odml/format.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -130,7 +130,7 @@ class Section(Format):
130130
_args = {
131131
'id': 0,
132132
'type': 1,
133-
'name': 0,
133+
'name': 1,
134134
'definition': 0,
135135
'reference': 0,
136136
'link': 0,

odml/property.py

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ class BaseProperty(base.BaseObject):
1313
"""An odML Property"""
1414
_format = frmt.Property
1515

16-
def __init__(self, name, value=None, parent=None, unit=None,
16+
def __init__(self, name=None, value=None, parent=None, unit=None,
1717
uncertainty=None, reference=None, definition=None,
1818
dependency=None, dependency_value=None, dtype=None,
1919
value_origin=None, id=None):
@@ -58,6 +58,11 @@ def __init__(self, name, value=None, parent=None, unit=None,
5858
print(e)
5959
self._id = str(uuid.uuid4())
6060

61+
# Use id if no name was provided.
62+
if not name:
63+
name = self._id
64+
65+
self._name = name
6166
self._parent = None
6267
self._name = name
6368
self._value_origin = value_origin
@@ -118,6 +123,14 @@ def name(self):
118123

119124
@name.setter
120125
def name(self, new_name):
126+
if self.name == new_name:
127+
return
128+
129+
curr_parent = self.parent
130+
if hasattr(curr_parent, "properties") and new_name in curr_parent.properties:
131+
132+
raise KeyError("Object with the same name already exists!")
133+
121134
self._name = new_name
122135

123136
def __repr__(self):

odml/section.py

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ class BaseSection(base.Sectionable):
2525

2626
_format = format.Section
2727

28-
def __init__(self, name, type=None, parent=None,
28+
def __init__(self, name=None, type=None, parent=None,
2929
definition=None, reference=None,
3030
repository=None, link=None, include=None, id=None):
3131

@@ -42,6 +42,10 @@ def __init__(self, name, type=None, parent=None,
4242
print(e)
4343
self._id = str(uuid.uuid4())
4444

45+
# Use id if no name was provided.
46+
if not name:
47+
name = self._id
48+
4549
self._parent = None
4650
self._name = name
4751
self._definition = definition
@@ -94,6 +98,13 @@ def name(self):
9498

9599
@name.setter
96100
def name(self, new_value):
101+
if self.name == new_value:
102+
return
103+
104+
curr_parent = self.parent
105+
if hasattr(curr_parent, "sections") and new_value in curr_parent.sections:
106+
raise KeyError("Object with the same name already exists!")
107+
97108
self._name = new_value
98109

99110
@property

odml/tools/odmlparser.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,10 @@ def write_file(self, odml_document, filename):
4848
raise ParserException(msg)
4949

5050
with open(filename, 'w') as file:
51+
# Add XML header to support odML stylesheets.
52+
if self.parser == 'XML':
53+
file.write(xmlparser.XMLWriter.header)
54+
5155
file.write(self.to_string(odml_document))
5256

5357
def to_string(self, odml_document):

odml/tools/xmlparser.py

Lines changed: 21 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,11 @@
55
python -m odml.tools.xmlparser file.odml
66
"""
77
import csv
8+
import sys
89
from lxml import etree as ET
910
from lxml.builder import E
1011
# this is needed for py2exe to include lxml completely
1112
from lxml import _elementpath as _dummy
12-
import sys
1313

1414
try:
1515
from StringIO import StringIO
@@ -118,10 +118,9 @@ def write_file(self, filename):
118118
else:
119119
data = str(self)
120120

121-
f = open(filename, "w")
122-
f.write(self.header)
123-
f.write(data)
124-
f.close()
121+
with open(filename, "w") as file:
122+
file.write(self.header)
123+
file.write(data)
125124

126125

127126
def load(filename):
@@ -223,18 +222,20 @@ def parse_element(self, node):
223222
return None # won't be able to parse this one
224223
return getattr(self, "parse_" + node.tag)(node, self.tags[node.tag])
225224

226-
def parse_tag(self, root, fmt, insert_children=True, create=None):
225+
def parse_tag(self, root, fmt, insert_children=True):
227226
"""
228227
Parse an odml node based on the format description *fmt*
229-
and a function *create* to instantiate a corresponding object
228+
and instantiate the corresponding object.
229+
:param root: lxml.etree node containing an odML object or object tree.
230+
:param fmt: odML class corresponding to the content of the root node.
231+
:param insert_children: Bool value. When True, child elements of the root node
232+
will be parsed to their odML equivalents and appended to
233+
the odML document. When False, child elements of the
234+
root node will be ignored.
230235
"""
231236
arguments = {}
232237
extra_args = {}
233238
children = []
234-
text = []
235-
236-
if root.text:
237-
text.append(root.text.strip())
238239

239240
for k, v in root.attrib.iteritems():
240241
k = k.lower()
@@ -258,8 +259,6 @@ def parse_tag(self, root, fmt, insert_children=True, create=None):
258259
else:
259260
tag = fmt.map(node.tag)
260261
if tag in arguments:
261-
# TODO make this an error, however first figure out a
262-
# way to let <odML version=><version/> pass
263262
self.warn("Element <%s> is given multiple times in "
264263
"<%s> tag" % (node.tag, root.tag), node)
265264

@@ -273,63 +272,32 @@ def parse_tag(self, root, fmt, insert_children=True, create=None):
273272
else:
274273
self.error("Invalid element <%s> in odML document section <%s>"
275274
% (node.tag, root.tag), node)
276-
if node.tail:
277-
text.append(node.tail.strip())
278275

279276
if sys.version_info > (3,):
280-
self.check_mandatory_arguments(dict(list(arguments.items()) +
281-
list(extra_args.items())),
282-
fmt, root.tag, root)
277+
check_args = dict(list(arguments.items()) + list(extra_args.items()))
283278
else:
284-
self.check_mandatory_arguments(dict(arguments.items() +
285-
extra_args.items()),
286-
fmt, root.tag, root)
287-
if create is None:
288-
obj = fmt.create()
289-
else:
290-
obj = create(args=arguments, text=''.join(text), children=children)
279+
check_args = dict(arguments.items() + extra_args.items())
291280

292-
for k, v in arguments.items():
293-
if hasattr(obj, k) and (getattr(obj, k) is None or k == 'id'):
294-
try:
295-
if k == 'id' and v is not None:
296-
obj._id = v
297-
else:
298-
setattr(obj, k, v)
299-
except Exception as e:
300-
self.warn("cannot set '%s' property on <%s>: %s" %
301-
(k, root.tag, repr(e)), root)
302-
if not self.ignore_errors:
303-
raise e
281+
self.check_mandatory_arguments(check_args, fmt, root.tag, root)
282+
283+
# Instantiate the current odML object with the parsed attributes.
284+
obj = fmt.create(**arguments)
304285

305286
if insert_children:
306287
for child in children:
307288
obj.append(child)
289+
308290
return obj
309291

310292
def parse_odML(self, root, fmt):
311293
doc = self.parse_tag(root, fmt)
312294
return doc
313295

314296
def parse_section(self, root, fmt):
315-
name = root.get("name") # property name= overrides
316-
if name is None: # the element
317-
name_node = root.find("name")
318-
if name_node is not None:
319-
name = name_node.text
320-
root.remove(name_node)
321-
# delete the name_node so its value won't
322-
# be used to overwrite the already set name-attribute
323-
324-
if name is None:
325-
self.error("Missing name element in <section>", root)
326-
327-
return self.parse_tag(root, fmt,
328-
create=lambda **kargs: fmt.create(name))
297+
return self.parse_tag(root, fmt)
329298

330299
def parse_property(self, root, fmt):
331-
create = lambda children, args, **kargs: fmt.create(**args)
332-
return self.parse_tag(root, fmt, insert_children=False, create=create)
300+
return self.parse_tag(root, fmt, insert_children=False)
333301

334302

335303
if __name__ == '__main__':

test/test_property.py

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -327,6 +327,40 @@ def test_str_to_int_convert(self):
327327
assert(p.dtype == 'string')
328328
assert(p.value == ['7', '20', '1 Dog', 'Seven'])
329329

330+
def test_name(self):
331+
# Test id is used when name is not provided
332+
p = Property()
333+
self.assertIsNotNone(p.name)
334+
self.assertEqual(p.name, p.id)
335+
336+
# Test name is properly set on init
337+
name = "rumpelstilzchen"
338+
p = Property(name)
339+
self.assertEqual(p.name, name)
340+
341+
# Test name can be properly set on single and connected Properties
342+
prop = Property()
343+
self.assertNotEqual(prop.name, "prop")
344+
prop.name = "prop"
345+
self.assertEqual(prop.name, "prop")
346+
347+
sec = Section()
348+
prop_a = Property(parent=sec)
349+
self.assertNotEqual(prop_a.name, "prop_a")
350+
prop_a.name = "prop_a"
351+
self.assertEqual(prop_a.name, "prop_a")
352+
353+
# Test property name can be changed with siblings
354+
prop_b = Property(name="prop_b", parent=sec)
355+
self.assertEqual(prop_b.name, "prop_b")
356+
prop_b.name = "prop"
357+
self.assertEqual(prop_b.name, "prop")
358+
359+
# Test property name set will fail on existing sibling with same name
360+
with self.assertRaises(KeyError):
361+
prop_b.name = "prop_a"
362+
self.assertEqual(prop_b.name, "prop")
363+
330364
def test_parent(self):
331365
p = Property("property_section", parent=Section("S"))
332366
self.assertIsInstance(p.parent, BaseSection)

test/test_section.py

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,50 @@ def test_simple_attributes(self):
3939
sec.definition = ""
4040
self.assertIsNone(sec.definition)
4141

42+
def test_name(self):
43+
# Test id is used when name is not provided
44+
s = Section()
45+
self.assertIsNotNone(s.name)
46+
self.assertEqual(s.name, s.id)
47+
48+
# Test name is properly set on init
49+
name = "rumpelstilzchen"
50+
s = Section(name)
51+
self.assertEqual(s.name, name)
52+
53+
name = "rumpelstilzchen"
54+
s = Section(name=name)
55+
self.assertEqual(s.name, name)
56+
57+
# Test name can be properly set on single and connected Sections
58+
sec = Section()
59+
self.assertNotEqual(sec.name, "sec")
60+
sec.name = "sec"
61+
self.assertEqual(sec.name, "sec")
62+
63+
subsec_a = Section(parent=sec)
64+
self.assertNotEqual(subsec_a.name, "subsec_a")
65+
subsec_a.name = "subsec_a"
66+
self.assertEqual(subsec_a.name, "subsec_a")
67+
68+
# Test subsection name can be changed with siblings
69+
subsec_b = Section(name="subsec_b", parent=sec)
70+
self.assertEqual(subsec_b.name, "subsec_b")
71+
subsec_b.name = "subsec"
72+
self.assertEqual(subsec_b.name, "subsec")
73+
74+
# Test subsection name set will fail on existing sibling with same name
75+
with self.assertRaises(KeyError):
76+
subsec_b.name = "subsec_a"
77+
self.assertEqual(subsec_b.name, "subsec")
78+
79+
# Test section name set will fail on existing same name document sibling
80+
doc = Document()
81+
sec_a = Section(name="a", parent=doc)
82+
sec_b = Section(name="b", parent=doc)
83+
with self.assertRaises(KeyError):
84+
sec_b.name = "a"
85+
4286
def test_parent(self):
4387
s = Section("Section")
4488
self.assertIsNone(s.parent)

0 commit comments

Comments
 (0)