sbtabpy 1.1.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- sbtab/SBtab.py +1714 -0
- sbtab/__init__.py +1 -0
- sbtab/__main__.py +147 -0
- sbtab/data/__init__.py +0 -0
- sbtab/data/definitions.tsv +409 -0
- sbtab/data/template_sbtab_online.html +89 -0
- sbtab/data/template_standalone.html +34 -0
- sbtab/sbml2sbtab.py +1089 -0
- sbtab/sbtab2sbml.py +2304 -0
- sbtab/sbtab_objtables2sbtab.py +112 -0
- sbtab/sbtab_sbml2sbtab.py +87 -0
- sbtab/sbtab_sbtab2html.py +193 -0
- sbtab/sbtab_sbtab2objtables.py +124 -0
- sbtab/sbtab_sbtab2sbml.py +91 -0
- sbtab/sbtab_validator.py +114 -0
- sbtab/utils.py +609 -0
- sbtab/validatorSBtab.py +408 -0
- sbtabpy-1.1.1.dist-info/METADATA +163 -0
- sbtabpy-1.1.1.dist-info/RECORD +23 -0
- sbtabpy-1.1.1.dist-info/WHEEL +5 -0
- sbtabpy-1.1.1.dist-info/entry_points.txt +2 -0
- sbtabpy-1.1.1.dist-info/licenses/LICENSE +25 -0
- sbtabpy-1.1.1.dist-info/top_level.txt +1 -0
sbtab/SBtab.py
ADDED
|
@@ -0,0 +1,1714 @@
|
|
|
1
|
+
"""
|
|
2
|
+
sbtab is a table format for Systems Biology.
|
|
3
|
+
|
|
4
|
+
It can store different kinds of data. The SBtabTable and SBtabDocument
|
|
5
|
+
classes allow the storage, manipulation, and removal of data from the tables.
|
|
6
|
+
|
|
7
|
+
See specification for further information.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
import datetime
|
|
11
|
+
import logging
|
|
12
|
+
import re
|
|
13
|
+
from collections.abc import Mapping
|
|
14
|
+
from pathlib import Path
|
|
15
|
+
|
|
16
|
+
from . import utils
|
|
17
|
+
|
|
18
|
+
# Sentinel for get_attribute(), so that default=None can be told from no default.
|
|
19
|
+
_MISSING = object()
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def _attribute_pattern(attribute):
|
|
23
|
+
"""
|
|
24
|
+
Return a regex matching attribute='value' in a declaration row.
|
|
25
|
+
|
|
26
|
+
The attribute name is escaped and must not be preceded by a word character,
|
|
27
|
+
so that e.g. 'Name' does not match inside "TableName='...'".
|
|
28
|
+
"""
|
|
29
|
+
return r"(?<![\w])%s='([^']*)'" % re.escape(attribute)
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def _bare_column_name(column):
|
|
33
|
+
"""Return a column name without its (single) leading '!'."""
|
|
34
|
+
return column[1:] if column.startswith("!") else column
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def read_csv(filepath, document_name, xlsx=False, definitions_file=None):
|
|
38
|
+
"""
|
|
39
|
+
Read an sbtab file; it can be csv, but also tsv.
|
|
40
|
+
|
|
41
|
+
Parameters
|
|
42
|
+
----------
|
|
43
|
+
filepath: str
|
|
44
|
+
Path to the file that shall be read.
|
|
45
|
+
document_name: str
|
|
46
|
+
A name for the document to be created.
|
|
47
|
+
xlsx: Bool
|
|
48
|
+
Boolean flag that indicates if the file is in xlsx format.
|
|
49
|
+
|
|
50
|
+
Returns: sbtab.SBtabDocument
|
|
51
|
+
sbtab document which is created from the given file.
|
|
52
|
+
"""
|
|
53
|
+
sbtab_file = False
|
|
54
|
+
|
|
55
|
+
if xlsx:
|
|
56
|
+
try:
|
|
57
|
+
sbtab_xlsx = open(filepath, "rb")
|
|
58
|
+
sbtab_tsv = utils.xlsx_to_tsv(sbtab_xlsx, f="file")
|
|
59
|
+
sbtab_xlsx.close()
|
|
60
|
+
sbtab_doc = SBtabDocument(
|
|
61
|
+
document_name, sbtab_tsv, filepath, definitions_file
|
|
62
|
+
)
|
|
63
|
+
return sbtab_doc
|
|
64
|
+
except Exception as e:
|
|
65
|
+
raise SBtabError("The sbtab could not be generated: %s" % (str(e)))
|
|
66
|
+
|
|
67
|
+
try:
|
|
68
|
+
sbtab_file = open(filepath, "r")
|
|
69
|
+
sbtab_doc = SBtabDocument(
|
|
70
|
+
document_name, sbtab_file.read(), filepath, definitions_file
|
|
71
|
+
)
|
|
72
|
+
sbtab_file.close()
|
|
73
|
+
return sbtab_doc
|
|
74
|
+
except Exception as e:
|
|
75
|
+
if sbtab_file:
|
|
76
|
+
sbtab_file.close()
|
|
77
|
+
raise SBtabError("The sbtab could not be generated: %s" % (str(e)))
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
class SBtabError(Exception):
|
|
81
|
+
"""Base class for errors in the sbtab class."""
|
|
82
|
+
|
|
83
|
+
def __init__(self, *args):
|
|
84
|
+
"""Initialize SBtabError with message."""
|
|
85
|
+
super().__init__(*args)
|
|
86
|
+
self.message = args[0] if args else ""
|
|
87
|
+
|
|
88
|
+
def __str__(self):
|
|
89
|
+
"""Return string representation of error."""
|
|
90
|
+
return self.message
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
class SBtabTable:
|
|
94
|
+
"""SBtabTable (version 1.0.0 24/12/2018)."""
|
|
95
|
+
|
|
96
|
+
def __init__(self, table_string=None, filename=None):
|
|
97
|
+
"""
|
|
98
|
+
Create sbtab table object from string.
|
|
99
|
+
|
|
100
|
+
Parameters
|
|
101
|
+
----------
|
|
102
|
+
table : str
|
|
103
|
+
One sbtab string from read file.
|
|
104
|
+
filename : str
|
|
105
|
+
Filename with extension.
|
|
106
|
+
"""
|
|
107
|
+
if filename:
|
|
108
|
+
self.set_filename(filename)
|
|
109
|
+
else:
|
|
110
|
+
self.filename = None
|
|
111
|
+
|
|
112
|
+
if table_string:
|
|
113
|
+
self.add_sbtab_string(table_string)
|
|
114
|
+
|
|
115
|
+
self.object_type = "table"
|
|
116
|
+
|
|
117
|
+
def _validate_extension(self, test=None):
|
|
118
|
+
"""Check the extension of the file for invalid formats."""
|
|
119
|
+
valid_extensions = {".tsv", ".csv", ".xlsx"}
|
|
120
|
+
filename = test if test is not None else self.filename
|
|
121
|
+
suffix = Path(filename).suffix
|
|
122
|
+
if suffix not in valid_extensions:
|
|
123
|
+
raise SBtabError(
|
|
124
|
+
"The file extension of %s is not valid for an sbtab file." % filename
|
|
125
|
+
)
|
|
126
|
+
return True
|
|
127
|
+
|
|
128
|
+
def _singular(self):
|
|
129
|
+
"""
|
|
130
|
+
Check that SBtabTable contains only one sbtab.
|
|
131
|
+
|
|
132
|
+
If more than one SBtabs are contained, an error is issued and
|
|
133
|
+
the usage of SBtabDocument is suggested.
|
|
134
|
+
"""
|
|
135
|
+
header_row_count = 0
|
|
136
|
+
for row in self.table_string.split("\n"):
|
|
137
|
+
if row.startswith("!!SBtab") or row.startswith("!!ObjTables"):
|
|
138
|
+
header_row_count += 1
|
|
139
|
+
|
|
140
|
+
if header_row_count > 1:
|
|
141
|
+
raise SBtabError(
|
|
142
|
+
"There are more than one sbtab tables in this file. Please"
|
|
143
|
+
" use the SBtabDocument class instead of SBtabTable."
|
|
144
|
+
)
|
|
145
|
+
|
|
146
|
+
def _preprocess_table_string(self, table_string):
|
|
147
|
+
"""
|
|
148
|
+
Preprocess the table string to fix common formatting problems.
|
|
149
|
+
|
|
150
|
+
There is so much stuff that can be made wrong with the input files.
|
|
151
|
+
This function tries to catch some of the common problems.
|
|
152
|
+
"""
|
|
153
|
+
table_string = table_string.replace("\r", "")
|
|
154
|
+
table_string = table_string.replace("^M", "")
|
|
155
|
+
table_string_prep = ""
|
|
156
|
+
|
|
157
|
+
for row in table_string.split("\n"):
|
|
158
|
+
row = self._dequote(row)
|
|
159
|
+
row = row.replace("=''", "X@X")
|
|
160
|
+
while "''" in row:
|
|
161
|
+
row = row.replace("''", "'")
|
|
162
|
+
row = row.replace("X@X", "=''")
|
|
163
|
+
row.replace(
|
|
164
|
+
"%s,%s" % (self.delimiter, self.delimiter),
|
|
165
|
+
"%s%s" % (self.delimiter, self.delimiter),
|
|
166
|
+
)
|
|
167
|
+
table_string_prep += row + "\n"
|
|
168
|
+
|
|
169
|
+
return table_string_prep
|
|
170
|
+
|
|
171
|
+
def _cut_table_string(self, table_string, delimiter_test=None):
|
|
172
|
+
"""
|
|
173
|
+
Cut the sbtab string into a list to harvest content.
|
|
174
|
+
|
|
175
|
+
The sbtab is initially given as one long string; cut down this
|
|
176
|
+
string into list to harvest content.
|
|
177
|
+
"""
|
|
178
|
+
if delimiter_test:
|
|
179
|
+
delimiter = delimiter_test
|
|
180
|
+
else:
|
|
181
|
+
delimiter = self.delimiter
|
|
182
|
+
|
|
183
|
+
table_list = []
|
|
184
|
+
for row in table_string.split("\n"):
|
|
185
|
+
if row.replace(delimiter, "") != "" and row.replace(delimiter, "") != "[]":
|
|
186
|
+
if not row.startswith('"!') and not row.startswith("!"):
|
|
187
|
+
if "'" in row or "{" in row or "[" in row:
|
|
188
|
+
try:
|
|
189
|
+
cut_row = self._handle_row(row, delimiter)
|
|
190
|
+
table_list.append(cut_row)
|
|
191
|
+
except Exception:
|
|
192
|
+
print(
|
|
193
|
+
"Row %s could not be attached due to bad syntax." % row
|
|
194
|
+
)
|
|
195
|
+
else:
|
|
196
|
+
table_list.append(row.split(delimiter))
|
|
197
|
+
else:
|
|
198
|
+
table_list.append(row.split(delimiter))
|
|
199
|
+
|
|
200
|
+
return table_list
|
|
201
|
+
|
|
202
|
+
def _handle_row(self, row, delimiter):
|
|
203
|
+
"""
|
|
204
|
+
Handle rows that may contain characters causing format troubles.
|
|
205
|
+
|
|
206
|
+
Some rows may contain characters that cause format troubles:
|
|
207
|
+
quotes, commas, JSON strings, and combinations of them. This
|
|
208
|
+
function carefully handles these issues and cuts the row into
|
|
209
|
+
its correct single pieces (which are the columns).
|
|
210
|
+
"""
|
|
211
|
+
# provide an anchor for the end of the row to support our regex
|
|
212
|
+
row += delimiter
|
|
213
|
+
|
|
214
|
+
# first, unify the employed quotes to '
|
|
215
|
+
row = self._dequote(row)
|
|
216
|
+
|
|
217
|
+
# then, find all quoted columns
|
|
218
|
+
if "'" in row or "{" in row or "[" in row:
|
|
219
|
+
# find beginning and start of quoted columns
|
|
220
|
+
iterators = re.finditer(r"('.*?')%s" % delimiter, row)
|
|
221
|
+
indices = [0]
|
|
222
|
+
|
|
223
|
+
for i in iterators:
|
|
224
|
+
indices.append(i.start())
|
|
225
|
+
indices.append(i.end())
|
|
226
|
+
|
|
227
|
+
# remove duplicates
|
|
228
|
+
indices_set = list(sorted(set(indices)))
|
|
229
|
+
|
|
230
|
+
# cut row at the beginning and start indices
|
|
231
|
+
items_pre = [
|
|
232
|
+
row[i : j - 1]
|
|
233
|
+
for i, j in zip(indices_set, indices_set[1:] + [len(row) + 1])
|
|
234
|
+
]
|
|
235
|
+
|
|
236
|
+
# further cut row at the delimiter and finish off items
|
|
237
|
+
items = []
|
|
238
|
+
jsons = []
|
|
239
|
+
jlist = []
|
|
240
|
+
running_json = False
|
|
241
|
+
running_jlist = False
|
|
242
|
+
|
|
243
|
+
for item in items_pre:
|
|
244
|
+
# in the case of a comma as separator, we need to be careful
|
|
245
|
+
# with the JSONs which naturally hold commas
|
|
246
|
+
if delimiter == ",":
|
|
247
|
+
# 1st case: we have a currently open JSON column
|
|
248
|
+
if running_json and not item.endswith("}'"):
|
|
249
|
+
jsons.append(item)
|
|
250
|
+
# 2nd case: we have a JSON column start
|
|
251
|
+
elif item.startswith("'{"):
|
|
252
|
+
jsons.append(item)
|
|
253
|
+
running_json = True
|
|
254
|
+
if item.endswith("}'"):
|
|
255
|
+
while "" in jsons:
|
|
256
|
+
jsons.remove("")
|
|
257
|
+
items.append(",".join(jsons))
|
|
258
|
+
jsons = []
|
|
259
|
+
running_json = False
|
|
260
|
+
# 3rd case: we have a JSON column end
|
|
261
|
+
elif running_json and item.endswith("}'"):
|
|
262
|
+
jsons.append(item)
|
|
263
|
+
while "" in jsons:
|
|
264
|
+
jsons.remove("")
|
|
265
|
+
items.append(",".join(jsons))
|
|
266
|
+
jsons = []
|
|
267
|
+
running_json = False
|
|
268
|
+
|
|
269
|
+
# 1bst case: we have a currently open Jlist column
|
|
270
|
+
elif running_jlist and not item.endswith("]'"):
|
|
271
|
+
if item.strip() != "":
|
|
272
|
+
jlist.append(item)
|
|
273
|
+
# 2bnd case: we have a Jlist column start
|
|
274
|
+
elif item.startswith("'["):
|
|
275
|
+
jlist.append(item)
|
|
276
|
+
running_jlist = True
|
|
277
|
+
if item.endswith("]'"):
|
|
278
|
+
while "" in jlist:
|
|
279
|
+
jlist.remove("")
|
|
280
|
+
items.append(",".join(jlist))
|
|
281
|
+
jlist = []
|
|
282
|
+
running_jlist = False
|
|
283
|
+
# 3brd case: we have a Jlist column end
|
|
284
|
+
elif running_jlist and item.endswith("]'"):
|
|
285
|
+
jlist.append(item)
|
|
286
|
+
while "" in jlist:
|
|
287
|
+
jlist.remove("")
|
|
288
|
+
items.append(",".join(jlist))
|
|
289
|
+
jlist = []
|
|
290
|
+
running_jlist = False
|
|
291
|
+
|
|
292
|
+
# 4th case: we have a quoted column
|
|
293
|
+
elif (
|
|
294
|
+
item.startswith("'")
|
|
295
|
+
and not item.startswith("'{")
|
|
296
|
+
and not item.endswith("}'")
|
|
297
|
+
and not item.startswith("'[")
|
|
298
|
+
and not item.endswith("]'")
|
|
299
|
+
):
|
|
300
|
+
items.append(item)
|
|
301
|
+
|
|
302
|
+
# 5th case: we have a normal column
|
|
303
|
+
elif item == "":
|
|
304
|
+
items += [""]
|
|
305
|
+
else:
|
|
306
|
+
items = items + item.split(delimiter)
|
|
307
|
+
else:
|
|
308
|
+
# for all other delimiters we are comparably easy going:
|
|
309
|
+
if item.startswith("'"):
|
|
310
|
+
items.append(item)
|
|
311
|
+
else:
|
|
312
|
+
items = items + item.split(delimiter)
|
|
313
|
+
|
|
314
|
+
# remove the last element which was added in the beginning
|
|
315
|
+
items.pop()
|
|
316
|
+
|
|
317
|
+
return items
|
|
318
|
+
|
|
319
|
+
def _initialize_table(self):
|
|
320
|
+
"""Load table information and class variables."""
|
|
321
|
+
# read a potential document row
|
|
322
|
+
self.doc_row = self._get_doc_row()
|
|
323
|
+
|
|
324
|
+
# Read the header row from table
|
|
325
|
+
self.header_row = self._get_header_row()
|
|
326
|
+
|
|
327
|
+
# Read the table information from header row
|
|
328
|
+
(
|
|
329
|
+
self.table_id,
|
|
330
|
+
self.table_type,
|
|
331
|
+
self.table_name,
|
|
332
|
+
self.table_document,
|
|
333
|
+
self.table_version,
|
|
334
|
+
self.standard_concentration,
|
|
335
|
+
) = self._get_table_information()
|
|
336
|
+
|
|
337
|
+
# Read the columns of the table
|
|
338
|
+
self.columns, self.columns_dict = self._get_columns()
|
|
339
|
+
|
|
340
|
+
# Read data rows
|
|
341
|
+
self.value_rows = self._get_rows()
|
|
342
|
+
|
|
343
|
+
def _get_doc_row(self):
|
|
344
|
+
"""
|
|
345
|
+
Check if the sbtab Table holds a !!!-line to declare a belonging sbtab document.
|
|
346
|
+
|
|
347
|
+
See if the sbtab Table holds a !!!-line to declare a belonging sbtab
|
|
348
|
+
document.
|
|
349
|
+
"""
|
|
350
|
+
doc_row_dq = False
|
|
351
|
+
|
|
352
|
+
for row in self.table:
|
|
353
|
+
for entry in row:
|
|
354
|
+
if str(entry).startswith("!!!"):
|
|
355
|
+
doc_row = row
|
|
356
|
+
doc_row_dq = self._dequote(doc_row)
|
|
357
|
+
|
|
358
|
+
# determine if this is an sbtab or ObjTables Document
|
|
359
|
+
if "!!!ObjTables" in doc_row_dq:
|
|
360
|
+
self.document_format = "ObjTables"
|
|
361
|
+
elif "!!!SBtab" in doc_row_dq:
|
|
362
|
+
self.document_format = "sbtab"
|
|
363
|
+
else:
|
|
364
|
+
self.document_format = None
|
|
365
|
+
|
|
366
|
+
return doc_row_dq
|
|
367
|
+
elif str(entry).startswith('"!!!'):
|
|
368
|
+
rm2 = row.replace('""', "#").replace('"', "")
|
|
369
|
+
doc_row = rm2.replace("#", '"')
|
|
370
|
+
doc_row_dq = self._dequote(doc_row)
|
|
371
|
+
|
|
372
|
+
# determine if this is an sbtab or ObjTables Document
|
|
373
|
+
if "!!!ObjTables" in doc_row_dq:
|
|
374
|
+
self.document_format = "ObjTables"
|
|
375
|
+
elif "!!!SBtab" in doc_row_dq:
|
|
376
|
+
self.document_format = "sbtab"
|
|
377
|
+
else:
|
|
378
|
+
self.document_format = None
|
|
379
|
+
|
|
380
|
+
return doc_row_dq
|
|
381
|
+
|
|
382
|
+
def _get_header_row(self):
|
|
383
|
+
"""Extract the declaration row from the sbtab file."""
|
|
384
|
+
header_row = None
|
|
385
|
+
# Find header row
|
|
386
|
+
for row in self.table:
|
|
387
|
+
for entry in row:
|
|
388
|
+
if str(entry).startswith("!!") and not str(entry).startswith("!!!"):
|
|
389
|
+
header_row = "".join(row).rstrip("\n")
|
|
390
|
+
break
|
|
391
|
+
elif str(entry).startswith("'!!") and not str(entry).startswith("'!!!"):
|
|
392
|
+
rm1 = entry.replace("''", "#")
|
|
393
|
+
rm2 = rm1.replace("'", "")
|
|
394
|
+
header_row = rm2.replace("#", "'")
|
|
395
|
+
break
|
|
396
|
+
|
|
397
|
+
# Save string or raise error
|
|
398
|
+
if not header_row:
|
|
399
|
+
raise SBtabError("""This is not a valid sbtab table, please use
|
|
400
|
+
validator to check format or have a look in the specification!""")
|
|
401
|
+
|
|
402
|
+
header_row_dq = self._dequote(header_row)
|
|
403
|
+
|
|
404
|
+
# determine if this is an sbtab or ObjTables Document
|
|
405
|
+
if "!!ObjTables" in header_row_dq:
|
|
406
|
+
self.table_format = "ObjTables"
|
|
407
|
+
elif "!!SBtab" in header_row_dq:
|
|
408
|
+
self.table_format = "sbtab"
|
|
409
|
+
else:
|
|
410
|
+
raise SBtabError("""This is not a valid sbtab table, please use
|
|
411
|
+
validator to check format or have a look in the specification!""")
|
|
412
|
+
|
|
413
|
+
return header_row_dq
|
|
414
|
+
|
|
415
|
+
def _dequote(self, row):
|
|
416
|
+
"""
|
|
417
|
+
Bring consistency in the multifarious quotation mark problems.
|
|
418
|
+
|
|
419
|
+
Bring consistency in the multifarious quotation mark problems.
|
|
420
|
+
"""
|
|
421
|
+
stupid_quotes = [
|
|
422
|
+
"\xe2\x80\x9d",
|
|
423
|
+
"\xe2\x80\x98",
|
|
424
|
+
"\xe2\x80\x99",
|
|
425
|
+
"\xe2\x80\x9b",
|
|
426
|
+
"\xe2\x80\x9c",
|
|
427
|
+
"\xe2\x80\x9f",
|
|
428
|
+
'"',
|
|
429
|
+
"\xe2\x80\xb2",
|
|
430
|
+
"\xe2\x80\xb3",
|
|
431
|
+
"\xe2\x80\xb4",
|
|
432
|
+
"\xe2\x80\xb5",
|
|
433
|
+
"\xe2\x80\xb6",
|
|
434
|
+
"\xe2\x80\xb7",
|
|
435
|
+
]
|
|
436
|
+
|
|
437
|
+
for squote in stupid_quotes:
|
|
438
|
+
try:
|
|
439
|
+
row = row.replace(squote, "'")
|
|
440
|
+
except Exception:
|
|
441
|
+
pass
|
|
442
|
+
|
|
443
|
+
return row
|
|
444
|
+
|
|
445
|
+
def _generate_random_table_id(self):
|
|
446
|
+
"""
|
|
447
|
+
Generate a random TableID for backwards compatibility.
|
|
448
|
+
|
|
449
|
+
The attribute TableID is enforced in sbtab. But to remain backwards compatible,
|
|
450
|
+
we generate a random TableID if none is given in the input file.
|
|
451
|
+
"""
|
|
452
|
+
import random
|
|
453
|
+
|
|
454
|
+
i = random.randint(0, 1000)
|
|
455
|
+
table_id = "ID_%s" % str(i)
|
|
456
|
+
|
|
457
|
+
return table_id
|
|
458
|
+
|
|
459
|
+
def _get_table_information(self):
|
|
460
|
+
"""Read declaration row and store the sbtab table attributes."""
|
|
461
|
+
# Save table id, otherwise raise error
|
|
462
|
+
try:
|
|
463
|
+
table_id = self._get_custom_table_information("TableID")
|
|
464
|
+
except Exception:
|
|
465
|
+
table_id = self._generate_random_table_id()
|
|
466
|
+
if "TableID=" not in self.header_row:
|
|
467
|
+
self.header_row = (
|
|
468
|
+
self.header_row.replace(self.delimiter, "").strip()
|
|
469
|
+
+ " TableID='%s'" % table_id
|
|
470
|
+
)
|
|
471
|
+
|
|
472
|
+
# Save table type, otherwise raise error
|
|
473
|
+
try:
|
|
474
|
+
if self.table_format == "sbtab":
|
|
475
|
+
table_type = self._get_custom_table_information("TableType")
|
|
476
|
+
elif self.table_format == "ObjTables":
|
|
477
|
+
table_type = self._get_custom_table_information("class")
|
|
478
|
+
except Exception:
|
|
479
|
+
raise SBtabError("The TableType of the sbtab is not defined!")
|
|
480
|
+
|
|
481
|
+
# Save table name, otherwise create name
|
|
482
|
+
try:
|
|
483
|
+
table_name = self._get_custom_table_information("TableName")
|
|
484
|
+
except Exception:
|
|
485
|
+
table_name = table_type.capitalize() + "_unnamed"
|
|
486
|
+
self.header_row += " TableName='%s'" % table_name
|
|
487
|
+
|
|
488
|
+
# Save table document, otherwise return None
|
|
489
|
+
try:
|
|
490
|
+
table_document = self._get_custom_table_information("Document")
|
|
491
|
+
except Exception:
|
|
492
|
+
table_document = None
|
|
493
|
+
|
|
494
|
+
# save table version, otherwise return None
|
|
495
|
+
try:
|
|
496
|
+
table_version = self._get_custom_table_information("SBtabVersion")
|
|
497
|
+
except Exception:
|
|
498
|
+
table_version = None
|
|
499
|
+
|
|
500
|
+
# save table version, otherwise return None
|
|
501
|
+
try:
|
|
502
|
+
standard_concentration = self._get_custom_table_information(
|
|
503
|
+
"StandardConcentration"
|
|
504
|
+
)
|
|
505
|
+
except Exception:
|
|
506
|
+
standard_concentration = None
|
|
507
|
+
|
|
508
|
+
# save date
|
|
509
|
+
try:
|
|
510
|
+
self.date = self._get_custom_table_information("Date")
|
|
511
|
+
except Exception:
|
|
512
|
+
now = datetime.datetime.now()
|
|
513
|
+
self.date = "-".join([str(now.year), str(now.month), str(now.day)])
|
|
514
|
+
if "Date=" not in self.header_row:
|
|
515
|
+
self.header_row = (
|
|
516
|
+
self.header_row.replace(self.delimiter, "")
|
|
517
|
+
+ " Date='%s'" % self.date
|
|
518
|
+
)
|
|
519
|
+
|
|
520
|
+
return (
|
|
521
|
+
table_id,
|
|
522
|
+
table_type,
|
|
523
|
+
table_name,
|
|
524
|
+
table_document,
|
|
525
|
+
table_version,
|
|
526
|
+
standard_concentration,
|
|
527
|
+
)
|
|
528
|
+
|
|
529
|
+
def _get_custom_table_information(self, attribute_name):
|
|
530
|
+
"""
|
|
531
|
+
Retrieve the value of a table attribute in the declaration line.
|
|
532
|
+
|
|
533
|
+
Parameters
|
|
534
|
+
----------
|
|
535
|
+
attribute_name : str
|
|
536
|
+
Name of the table attribute.
|
|
537
|
+
"""
|
|
538
|
+
if re.search("%s='([^']*)'" % attribute_name, self.header_row) is not None:
|
|
539
|
+
return re.search("%s='([^']*)'" % attribute_name, self.header_row).group(1)
|
|
540
|
+
else:
|
|
541
|
+
raise SBtabError(
|
|
542
|
+
"""The %s of the sbtab is
|
|
543
|
+
not defined!"""
|
|
544
|
+
% attribute_name
|
|
545
|
+
)
|
|
546
|
+
|
|
547
|
+
def _get_columns(self):
|
|
548
|
+
"""Extract column headers, add mandatory first column name if necessary."""
|
|
549
|
+
# Save list of main columns
|
|
550
|
+
column_names = None
|
|
551
|
+
for row in self.table:
|
|
552
|
+
for _entry in row:
|
|
553
|
+
if str(row[0]).startswith("!") and not str(row[0]).startswith("!!"):
|
|
554
|
+
column_names = list(filter(lambda a: a != "", row))
|
|
555
|
+
break
|
|
556
|
+
|
|
557
|
+
if column_names is None:
|
|
558
|
+
raise SBtabError(
|
|
559
|
+
"No column header row (a row starting with a single '!') was found."
|
|
560
|
+
)
|
|
561
|
+
|
|
562
|
+
# Get column positions
|
|
563
|
+
columns = dict(map(reversed, enumerate(column_names)))
|
|
564
|
+
|
|
565
|
+
return column_names, columns
|
|
566
|
+
|
|
567
|
+
def _get_rows(self):
|
|
568
|
+
"""Extract the rows of the sbtab."""
|
|
569
|
+
value_rows = []
|
|
570
|
+
# Add to comments, if row starts with '%'
|
|
571
|
+
self.comments = []
|
|
572
|
+
|
|
573
|
+
for row in self.table:
|
|
574
|
+
if str(row[0]).startswith("!"):
|
|
575
|
+
continue
|
|
576
|
+
elif str(row[0]).startswith("%"):
|
|
577
|
+
self.comments.append(list(row))
|
|
578
|
+
else:
|
|
579
|
+
if len(list(row)) >= len(self.columns):
|
|
580
|
+
value_rows.append(list(row)[: len(self.columns)])
|
|
581
|
+
else:
|
|
582
|
+
value_rows.append(
|
|
583
|
+
list(row) + [""] * (len(self.columns) - len(list(row)))
|
|
584
|
+
)
|
|
585
|
+
|
|
586
|
+
return value_rows
|
|
587
|
+
|
|
588
|
+
# Here, the sbtab API starts
|
|
589
|
+
def to_str(self):
|
|
590
|
+
"""
|
|
591
|
+
Return the sbtab table as a string.
|
|
592
|
+
|
|
593
|
+
Returns: str
|
|
594
|
+
The sbtab table in form of a string.
|
|
595
|
+
"""
|
|
596
|
+
table_string = [self.header_row]
|
|
597
|
+
table_string.append("\t".join(self.columns))
|
|
598
|
+
for row in self.value_rows:
|
|
599
|
+
row = "\t".join(row)
|
|
600
|
+
table_string.append(row)
|
|
601
|
+
|
|
602
|
+
return "\n".join(table_string)
|
|
603
|
+
|
|
604
|
+
def change_attribute(self, attribute, value):
|
|
605
|
+
"""
|
|
606
|
+
Change the value of an sbtab attribute.
|
|
607
|
+
|
|
608
|
+
Parameters
|
|
609
|
+
----------
|
|
610
|
+
attribute : str
|
|
611
|
+
Attribute from the sbtab object's declaration row.
|
|
612
|
+
value: str
|
|
613
|
+
Value that the attribute should take.
|
|
614
|
+
"""
|
|
615
|
+
att_value_new = "%s='%s'" % (attribute, value)
|
|
616
|
+
|
|
617
|
+
match = re.search(_attribute_pattern(attribute), self.header_row)
|
|
618
|
+
if match is None:
|
|
619
|
+
self.header_row = self.header_row + " " + att_value_new
|
|
620
|
+
else:
|
|
621
|
+
self.header_row = (
|
|
622
|
+
self.header_row[: match.start()]
|
|
623
|
+
+ att_value_new
|
|
624
|
+
+ self.header_row[match.end() :]
|
|
625
|
+
)
|
|
626
|
+
|
|
627
|
+
def set_filename(self, filename):
|
|
628
|
+
"""
|
|
629
|
+
Set the filename of the sbtab.
|
|
630
|
+
|
|
631
|
+
Parameters
|
|
632
|
+
----------
|
|
633
|
+
filename: str or Path
|
|
634
|
+
New filename for the sbtab object.
|
|
635
|
+
"""
|
|
636
|
+
self.filename = Path(filename)
|
|
637
|
+
# validate file extension
|
|
638
|
+
self._validate_extension()
|
|
639
|
+
|
|
640
|
+
def add_sbtab_string(self, sbtab_string, definitions_file=None):
|
|
641
|
+
"""Set the content of the sbtab Table in form of a string."""
|
|
642
|
+
self.table_string = sbtab_string
|
|
643
|
+
# validate singular sbtab
|
|
644
|
+
self._singular()
|
|
645
|
+
|
|
646
|
+
# process string
|
|
647
|
+
self.delimiter = utils.check_delimiter(sbtab_string)
|
|
648
|
+
self.preprocess = self._preprocess_table_string(sbtab_string)
|
|
649
|
+
self.table = self._cut_table_string(self.preprocess)
|
|
650
|
+
|
|
651
|
+
# Initialise table
|
|
652
|
+
self._initialize_table()
|
|
653
|
+
|
|
654
|
+
def unset_attribute(self, attribute):
|
|
655
|
+
"""
|
|
656
|
+
Remove an attribute from sbtab declaration row.
|
|
657
|
+
|
|
658
|
+
Parameters
|
|
659
|
+
----------
|
|
660
|
+
attribute : str
|
|
661
|
+
Attribute that shall be removed from the sbtab object's declaration row.
|
|
662
|
+
"""
|
|
663
|
+
obligatory_attributes = ["TableType", "TableID"]
|
|
664
|
+
if attribute in obligatory_attributes:
|
|
665
|
+
raise SBtabError(
|
|
666
|
+
"Attribute %s cannot be removed as it is obligatory." % attribute
|
|
667
|
+
)
|
|
668
|
+
|
|
669
|
+
match = re.search(_attribute_pattern(attribute), self.header_row)
|
|
670
|
+
if match is None:
|
|
671
|
+
raise SBtabError(
|
|
672
|
+
"Attribute %s is not in the header of the SBtabTable." % attribute
|
|
673
|
+
)
|
|
674
|
+
start = match.start()
|
|
675
|
+
if self.header_row[start - 1 : start] == " ":
|
|
676
|
+
start -= 1
|
|
677
|
+
self.header_row = self.header_row[:start] + self.header_row[match.end() :]
|
|
678
|
+
|
|
679
|
+
def get_attribute(self, attribute, default=_MISSING):
|
|
680
|
+
"""
|
|
681
|
+
Return the value of an sbtab attribute.
|
|
682
|
+
|
|
683
|
+
Parameters
|
|
684
|
+
----------
|
|
685
|
+
attribute : str
|
|
686
|
+
Attribute from the sbtab object's declaration row.
|
|
687
|
+
default : object
|
|
688
|
+
Returned if the attribute is absent. If no default is given, a
|
|
689
|
+
missing attribute raises SBtabError.
|
|
690
|
+
|
|
691
|
+
Returns: str
|
|
692
|
+
Value of the requested attribute.
|
|
693
|
+
"""
|
|
694
|
+
match = re.search(_attribute_pattern(attribute), self.header_row)
|
|
695
|
+
if match is not None:
|
|
696
|
+
return match.group(1)
|
|
697
|
+
if default is not _MISSING:
|
|
698
|
+
return default
|
|
699
|
+
raise SBtabError(
|
|
700
|
+
"The attribute %s was not found in the header row." % attribute
|
|
701
|
+
)
|
|
702
|
+
|
|
703
|
+
@property
|
|
704
|
+
def column_names(self):
|
|
705
|
+
"""
|
|
706
|
+
The column names without their leading '!', in table order.
|
|
707
|
+
|
|
708
|
+
Returns: list of str
|
|
709
|
+
A new list; modifying it does not change the table.
|
|
710
|
+
"""
|
|
711
|
+
return [_bare_column_name(column) for column in self.columns]
|
|
712
|
+
|
|
713
|
+
def to_dict_rows(self, strip=True):
|
|
714
|
+
"""
|
|
715
|
+
Return the rows as dictionaries keyed by column name (without the '!').
|
|
716
|
+
|
|
717
|
+
Every dictionary has every column as a key. The dictionaries are copies:
|
|
718
|
+
modifying them does not change the table; use change_value_by_name for
|
|
719
|
+
that.
|
|
720
|
+
|
|
721
|
+
Parameters
|
|
722
|
+
----------
|
|
723
|
+
strip : bool
|
|
724
|
+
Strip leading and trailing whitespace from every value.
|
|
725
|
+
|
|
726
|
+
Returns: list of dict
|
|
727
|
+
One dictionary per row, e.g. {'ID': 'R1', 'ReactionFormula': 'A <=> B'}.
|
|
728
|
+
"""
|
|
729
|
+
names = self.column_names
|
|
730
|
+
dict_rows = []
|
|
731
|
+
for row in self.value_rows:
|
|
732
|
+
values = [str(value) for value in row[: len(names)]]
|
|
733
|
+
values += [""] * (len(names) - len(values))
|
|
734
|
+
if strip:
|
|
735
|
+
values = [value.strip() for value in values]
|
|
736
|
+
dict_rows.append(dict(zip(names, values)))
|
|
737
|
+
return dict_rows
|
|
738
|
+
|
|
739
|
+
def change_value(self, row, column, new):
|
|
740
|
+
"""
|
|
741
|
+
Change a single value in the sbtab table by position in the table.
|
|
742
|
+
|
|
743
|
+
Parameters
|
|
744
|
+
----------
|
|
745
|
+
row : int
|
|
746
|
+
Number of rows in the table. First row is number 1.
|
|
747
|
+
column : int
|
|
748
|
+
Number of columns in the table. First column is number 1.
|
|
749
|
+
new : str
|
|
750
|
+
New entry.
|
|
751
|
+
"""
|
|
752
|
+
try:
|
|
753
|
+
self.value_rows[row - 1]
|
|
754
|
+
except Exception:
|
|
755
|
+
raise SBtabError("The sbtab has only %s rows." % len(self.value_rows))
|
|
756
|
+
try:
|
|
757
|
+
self.columns[column - 1]
|
|
758
|
+
except Exception:
|
|
759
|
+
raise SBtabError("The sbtab has only %s columns." % len(self.columns))
|
|
760
|
+
|
|
761
|
+
try:
|
|
762
|
+
self.value_rows[row - 1][column - 1] = str(new)
|
|
763
|
+
except Exception:
|
|
764
|
+
raise SBtabError("Could not set the given value.")
|
|
765
|
+
|
|
766
|
+
return True
|
|
767
|
+
|
|
768
|
+
def change_value_by_name(self, name, column_name, new):
|
|
769
|
+
"""
|
|
770
|
+
Change single value in the sbtab by column name and first row entry name.
|
|
771
|
+
|
|
772
|
+
Parameters
|
|
773
|
+
----------
|
|
774
|
+
row : str
|
|
775
|
+
Name of the entry in the ID column.
|
|
776
|
+
column : str
|
|
777
|
+
Name of the column (with '!').
|
|
778
|
+
new : str
|
|
779
|
+
New entry.
|
|
780
|
+
"""
|
|
781
|
+
try:
|
|
782
|
+
self.columns_dict[column_name]
|
|
783
|
+
except Exception:
|
|
784
|
+
raise SBtabError("The column %s is not in the sbtab." % column_name)
|
|
785
|
+
|
|
786
|
+
success = False
|
|
787
|
+
col = self.columns_dict[column_name]
|
|
788
|
+
for r in self.value_rows:
|
|
789
|
+
if r[0] == name:
|
|
790
|
+
r[col] = str(new)
|
|
791
|
+
success = True
|
|
792
|
+
|
|
793
|
+
if not success:
|
|
794
|
+
raise SBtabError("Row %s was not found in the sbtab." % name)
|
|
795
|
+
|
|
796
|
+
return True
|
|
797
|
+
|
|
798
|
+
def create_list(self):
|
|
799
|
+
"""
|
|
800
|
+
Create a list object of the sbtab table object.
|
|
801
|
+
|
|
802
|
+
Returns: list
|
|
803
|
+
List containing of sbtab content: [declaration row, columns, value_rows]
|
|
804
|
+
"""
|
|
805
|
+
# Create new list
|
|
806
|
+
sbtab_list = []
|
|
807
|
+
|
|
808
|
+
# Append the parts header row, main column row and
|
|
809
|
+
# value rows to the list
|
|
810
|
+
sbtab_list.append([self.header_row])
|
|
811
|
+
sbtab_list.append(self.columns)
|
|
812
|
+
sbtab_list.append(self.value_rows)
|
|
813
|
+
|
|
814
|
+
return sbtab_list
|
|
815
|
+
|
|
816
|
+
def add_row(self, row_list, position=None):
|
|
817
|
+
"""
|
|
818
|
+
Add a row to the table, if position is None at the end of it.
|
|
819
|
+
|
|
820
|
+
Parameters
|
|
821
|
+
----------
|
|
822
|
+
row_list : list
|
|
823
|
+
List of strings, containing the entries of the new row.
|
|
824
|
+
position : int
|
|
825
|
+
Position of new row in the table, 0 is on top.
|
|
826
|
+
"""
|
|
827
|
+
if not isinstance(row_list, list):
|
|
828
|
+
raise SBtabError("%s is not a list" % row_list)
|
|
829
|
+
|
|
830
|
+
if len(row_list) != len(self.columns):
|
|
831
|
+
raise SBtabError("Given row %s has not the correct length." % row_list)
|
|
832
|
+
|
|
833
|
+
if position is not None and not isinstance(position, int):
|
|
834
|
+
raise SBtabError("Please provide an integer row position.")
|
|
835
|
+
|
|
836
|
+
for element in row_list:
|
|
837
|
+
if not isinstance(element, str):
|
|
838
|
+
raise SBtabError("Please only provide string elements in the list")
|
|
839
|
+
|
|
840
|
+
# If no position is set, add new row to the end
|
|
841
|
+
if position is None:
|
|
842
|
+
self.value_rows.append(row_list)
|
|
843
|
+
else:
|
|
844
|
+
self.value_rows.insert(position, row_list)
|
|
845
|
+
|
|
846
|
+
return True
|
|
847
|
+
|
|
848
|
+
def remove_row(self, position):
|
|
849
|
+
"""
|
|
850
|
+
Remove one row from the table.
|
|
851
|
+
|
|
852
|
+
Parameters
|
|
853
|
+
----------
|
|
854
|
+
position : int
|
|
855
|
+
Position of row to be removed. Starting with 1.
|
|
856
|
+
"""
|
|
857
|
+
if not isinstance(position, int):
|
|
858
|
+
raise SBtabError("Please provide an integer row position.")
|
|
859
|
+
|
|
860
|
+
if position > len(self.value_rows):
|
|
861
|
+
raise SBtabError("The sbtab only has %s row/s." % len(self.value_rows))
|
|
862
|
+
|
|
863
|
+
del self.value_rows[position - 1]
|
|
864
|
+
|
|
865
|
+
return True
|
|
866
|
+
|
|
867
|
+
def add_column(self, column_list, position=None):
|
|
868
|
+
"""
|
|
869
|
+
Add a column to the table, if position is None at the end of it.
|
|
870
|
+
|
|
871
|
+
Parameters
|
|
872
|
+
----------
|
|
873
|
+
column_list : list
|
|
874
|
+
List of strings, containing the entries of the new column.
|
|
875
|
+
position : int
|
|
876
|
+
Position of new column in the table, 0 is right.
|
|
877
|
+
"""
|
|
878
|
+
if not isinstance(column_list, list):
|
|
879
|
+
raise SBtabError("%s is not a list" % column_list)
|
|
880
|
+
|
|
881
|
+
if len(column_list) != (len(self.value_rows) + 1):
|
|
882
|
+
raise SBtabError("The given column list has not the correct length.")
|
|
883
|
+
|
|
884
|
+
if position is not None and not isinstance(position, int):
|
|
885
|
+
raise SBtabError("Please provide an integer column position.")
|
|
886
|
+
|
|
887
|
+
# If no position is set, add new column to the end
|
|
888
|
+
if not position:
|
|
889
|
+
for i, row in enumerate(self.value_rows):
|
|
890
|
+
row.append(str(column_list[i + 1]))
|
|
891
|
+
self.columns_dict[str(column_list[0])] = len(self.columns)
|
|
892
|
+
self.columns.append(str(column_list[0]))
|
|
893
|
+
else:
|
|
894
|
+
for i, row in enumerate(self.value_rows):
|
|
895
|
+
row.insert(position - 1, str(column_list[i + 1]))
|
|
896
|
+
self.columns_dict[str(column_list[0])] = position - 1
|
|
897
|
+
self.columns.insert(position - 1, str(column_list[0]))
|
|
898
|
+
|
|
899
|
+
return True
|
|
900
|
+
|
|
901
|
+
def remove_column(self, position):
|
|
902
|
+
"""
|
|
903
|
+
Remove column from the table.
|
|
904
|
+
|
|
905
|
+
Parameters
|
|
906
|
+
----------
|
|
907
|
+
position : int
|
|
908
|
+
Position of column to be removed. Sarting with 1.
|
|
909
|
+
"""
|
|
910
|
+
if not isinstance(position, int):
|
|
911
|
+
raise SBtabError("Please provide an integer column position.")
|
|
912
|
+
|
|
913
|
+
if position > len(self.columns):
|
|
914
|
+
raise SBtabError(
|
|
915
|
+
"There are only %s columns in the sbtab" % str(len(self.columns))
|
|
916
|
+
)
|
|
917
|
+
|
|
918
|
+
# Remove entries on position
|
|
919
|
+
for row in self.value_rows:
|
|
920
|
+
del row[position - 1]
|
|
921
|
+
|
|
922
|
+
# Remove column from column list
|
|
923
|
+
column_to_remove = self.columns[position - 1]
|
|
924
|
+
del self.columns[position - 1]
|
|
925
|
+
|
|
926
|
+
# Remove column from columns dict
|
|
927
|
+
self.columns_dict.pop(column_to_remove)
|
|
928
|
+
|
|
929
|
+
return True
|
|
930
|
+
|
|
931
|
+
def write(self, filename):
|
|
932
|
+
"""
|
|
933
|
+
Write sbtab to hard disk.
|
|
934
|
+
|
|
935
|
+
Parameters
|
|
936
|
+
----------
|
|
937
|
+
filename: str or Path
|
|
938
|
+
Name for the output file.
|
|
939
|
+
"""
|
|
940
|
+
path = Path(filename)
|
|
941
|
+
if path.suffix not in {".tsv", ".csv"}:
|
|
942
|
+
if self.delimiter == "\t":
|
|
943
|
+
path = path.with_suffix(".tsv")
|
|
944
|
+
elif self.delimiter in {",", ";"}:
|
|
945
|
+
path = path.with_suffix(".csv")
|
|
946
|
+
else:
|
|
947
|
+
raise SBtabError(
|
|
948
|
+
"The file extension is missing and the delimiter is no standard."
|
|
949
|
+
)
|
|
950
|
+
|
|
951
|
+
try:
|
|
952
|
+
table_string = self.to_str().replace("^M", "\n")
|
|
953
|
+
path.write_text(table_string)
|
|
954
|
+
return True
|
|
955
|
+
except Exception:
|
|
956
|
+
raise SBtabError("The file could not be written.")
|
|
957
|
+
|
|
958
|
+
def transpose_table(self):
|
|
959
|
+
"""
|
|
960
|
+
Transpose sbtab table. Switch columns and rows.
|
|
961
|
+
|
|
962
|
+
Returns: sbtab.SBtabTable
|
|
963
|
+
sbtab object with transposed content.
|
|
964
|
+
"""
|
|
965
|
+
# Initialize new table data
|
|
966
|
+
trans_columns = []
|
|
967
|
+
trans_columns_dict = {}
|
|
968
|
+
trans_value_rows = []
|
|
969
|
+
|
|
970
|
+
# Save old table data
|
|
971
|
+
columns = self.columns
|
|
972
|
+
value_rows = self.value_rows
|
|
973
|
+
|
|
974
|
+
# Append first entry to new column
|
|
975
|
+
trans_columns.append(columns.pop(0))
|
|
976
|
+
|
|
977
|
+
# Set new rows
|
|
978
|
+
for column in columns:
|
|
979
|
+
trans_value_rows.append([column])
|
|
980
|
+
|
|
981
|
+
# Set new values in tables
|
|
982
|
+
for row in value_rows:
|
|
983
|
+
trans_columns.append(row.pop(0))
|
|
984
|
+
for i, entry in enumerate(row):
|
|
985
|
+
trans_value_rows[i].append(entry)
|
|
986
|
+
|
|
987
|
+
# Write new columns dict
|
|
988
|
+
for i, column in enumerate(trans_columns):
|
|
989
|
+
trans_columns_dict[column] = i
|
|
990
|
+
|
|
991
|
+
# Overwrite old table data
|
|
992
|
+
self.columns = trans_columns
|
|
993
|
+
self.columns_dict = trans_columns_dict
|
|
994
|
+
self.value_rows = trans_value_rows
|
|
995
|
+
|
|
996
|
+
return True
|
|
997
|
+
|
|
998
|
+
def to_data_frame(self):
|
|
999
|
+
"""
|
|
1000
|
+
Export sbtab table object as pandas dataframe.
|
|
1001
|
+
|
|
1002
|
+
Returns: pandas.DataFrame
|
|
1003
|
+
sbtab table object as pandas dataframe.
|
|
1004
|
+
"""
|
|
1005
|
+
pd = utils.require_optional("pandas", "pandas")
|
|
1006
|
+
try:
|
|
1007
|
+
# value_rows, unlike the raw parsed grid, exists on every table,
|
|
1008
|
+
# including those built by from_data_frame and from_rows
|
|
1009
|
+
rows = [list(row) for row in self.value_rows]
|
|
1010
|
+
column_names = self.column_names
|
|
1011
|
+
n_cols = max(map(len, rows), default=len(column_names))
|
|
1012
|
+
while len(column_names) < n_cols:
|
|
1013
|
+
column_names += ["Col%d" % len(column_names)]
|
|
1014
|
+
df = pd.DataFrame(data=rows, columns=column_names)
|
|
1015
|
+
return df
|
|
1016
|
+
except Exception:
|
|
1017
|
+
raise SBtabError("Pandas dataframe could not be built.")
|
|
1018
|
+
|
|
1019
|
+
@staticmethod
|
|
1020
|
+
def from_data_frame(
|
|
1021
|
+
df,
|
|
1022
|
+
table_id,
|
|
1023
|
+
table_type,
|
|
1024
|
+
table_name=None,
|
|
1025
|
+
document_name=None,
|
|
1026
|
+
document=None,
|
|
1027
|
+
unit=None,
|
|
1028
|
+
sbtab_version="1.0",
|
|
1029
|
+
) -> "SBtabTable":
|
|
1030
|
+
"""
|
|
1031
|
+
Create sbtab table object from pandas dataframe.
|
|
1032
|
+
|
|
1033
|
+
Column names are expected to start with '!' (e.g. '!QuantityType');
|
|
1034
|
+
the '!' is added where it is missing. Values are converted to str.
|
|
1035
|
+
|
|
1036
|
+
Parameters
|
|
1037
|
+
----------
|
|
1038
|
+
df : pandas.DataFrame
|
|
1039
|
+
Dataframe of the Python library pandas.
|
|
1040
|
+
table_id: str
|
|
1041
|
+
Mandatory table ID for the sbtab object.
|
|
1042
|
+
table_type: str
|
|
1043
|
+
Mandatory table type for the sbtab object.
|
|
1044
|
+
table_name: str
|
|
1045
|
+
Optional table name for the sbtab object.
|
|
1046
|
+
document_name: str
|
|
1047
|
+
Optional document name for the sbtab object.
|
|
1048
|
+
document: str
|
|
1049
|
+
Optional document for the sbtab object.
|
|
1050
|
+
unit: str
|
|
1051
|
+
Optional unit for an sbtab TableType Quantity.
|
|
1052
|
+
sbtab_version: str
|
|
1053
|
+
Optional sbtab Version.
|
|
1054
|
+
|
|
1055
|
+
Returns: sbtab.SBtabTable
|
|
1056
|
+
sbtab table object created from pandas dataframe.
|
|
1057
|
+
"""
|
|
1058
|
+
return SBtabTable.from_rows(
|
|
1059
|
+
[str(column) for column in df.columns],
|
|
1060
|
+
df.values.tolist(),
|
|
1061
|
+
table_id=table_id,
|
|
1062
|
+
table_type=table_type,
|
|
1063
|
+
table_name=table_name,
|
|
1064
|
+
document_name=document_name,
|
|
1065
|
+
document=document,
|
|
1066
|
+
unit=unit,
|
|
1067
|
+
sbtab_version=sbtab_version,
|
|
1068
|
+
)
|
|
1069
|
+
|
|
1070
|
+
@classmethod
|
|
1071
|
+
def from_rows(
|
|
1072
|
+
cls,
|
|
1073
|
+
columns,
|
|
1074
|
+
rows,
|
|
1075
|
+
table_id,
|
|
1076
|
+
table_type,
|
|
1077
|
+
table_name=None,
|
|
1078
|
+
document_name=None,
|
|
1079
|
+
document=None,
|
|
1080
|
+
unit=None,
|
|
1081
|
+
sbtab_version="1.0",
|
|
1082
|
+
**attributes,
|
|
1083
|
+
) -> "SBtabTable":
|
|
1084
|
+
"""
|
|
1085
|
+
Create sbtab table object from column names and rows, without pandas.
|
|
1086
|
+
|
|
1087
|
+
The table is built directly, not parsed from text, so unlike a parsed
|
|
1088
|
+
table no Date attribute is added to its declaration row.
|
|
1089
|
+
|
|
1090
|
+
Parameters
|
|
1091
|
+
----------
|
|
1092
|
+
columns : list of str
|
|
1093
|
+
Column names, with or without the leading '!' (added if missing).
|
|
1094
|
+
rows : list
|
|
1095
|
+
Each row is either a list of values, used by position (padded with
|
|
1096
|
+
'' or truncated to the number of columns), or a dict keyed by
|
|
1097
|
+
column name without the '!' (missing columns become '', unknown
|
|
1098
|
+
keys raise SBtabError). All values are converted to str.
|
|
1099
|
+
table_id: str
|
|
1100
|
+
Mandatory table ID for the sbtab object.
|
|
1101
|
+
table_type: str
|
|
1102
|
+
Mandatory table type for the sbtab object.
|
|
1103
|
+
table_name: str
|
|
1104
|
+
Optional table name for the sbtab object (defaults to table_id).
|
|
1105
|
+
document_name: str
|
|
1106
|
+
Optional document name for the sbtab object.
|
|
1107
|
+
document: str
|
|
1108
|
+
Optional document for the sbtab object.
|
|
1109
|
+
unit: str
|
|
1110
|
+
Optional unit for an sbtab TableType Quantity.
|
|
1111
|
+
sbtab_version: str
|
|
1112
|
+
Optional sbtab Version.
|
|
1113
|
+
**attributes: str
|
|
1114
|
+
Further declaration row attributes, in the order given,
|
|
1115
|
+
e.g. StandardConcentration='M'.
|
|
1116
|
+
|
|
1117
|
+
Returns: sbtab.SBtabTable
|
|
1118
|
+
sbtab table object created from the rows.
|
|
1119
|
+
"""
|
|
1120
|
+
column_list = [
|
|
1121
|
+
column if column.startswith("!") else "!" + column for column in columns
|
|
1122
|
+
]
|
|
1123
|
+
bare_names = [_bare_column_name(column) for column in column_list]
|
|
1124
|
+
n_cols = len(column_list)
|
|
1125
|
+
|
|
1126
|
+
value_rows = []
|
|
1127
|
+
for row in rows:
|
|
1128
|
+
if isinstance(row, Mapping):
|
|
1129
|
+
unknown = [key for key in row if key not in bare_names]
|
|
1130
|
+
if unknown:
|
|
1131
|
+
raise SBtabError(
|
|
1132
|
+
"Unknown column(s) %s; the columns are %s."
|
|
1133
|
+
% (", ".join(map(str, unknown)), ", ".join(bare_names))
|
|
1134
|
+
)
|
|
1135
|
+
values = [row.get(name, "") for name in bare_names]
|
|
1136
|
+
else:
|
|
1137
|
+
values = list(row)[:n_cols]
|
|
1138
|
+
values += [""] * (n_cols - len(values))
|
|
1139
|
+
value_rows.append([str(value) for value in values])
|
|
1140
|
+
|
|
1141
|
+
sbtab = cls()
|
|
1142
|
+
|
|
1143
|
+
sbtab.table_id = table_id
|
|
1144
|
+
sbtab.table_type = table_type
|
|
1145
|
+
sbtab.table_name = table_name or table_id
|
|
1146
|
+
sbtab.table_document = document
|
|
1147
|
+
sbtab.table_version = sbtab_version
|
|
1148
|
+
|
|
1149
|
+
header = [
|
|
1150
|
+
("TableID", sbtab.table_id),
|
|
1151
|
+
("TableType", sbtab.table_type),
|
|
1152
|
+
("TableName", sbtab.table_name),
|
|
1153
|
+
("SBtabVersion", sbtab.table_version),
|
|
1154
|
+
]
|
|
1155
|
+
if document_name:
|
|
1156
|
+
header += [("DocumentName", document_name)]
|
|
1157
|
+
if document:
|
|
1158
|
+
header += [("Document", sbtab.table_document)]
|
|
1159
|
+
if unit:
|
|
1160
|
+
header += [("Unit", unit)]
|
|
1161
|
+
header += list(attributes.items())
|
|
1162
|
+
header_strings = ["!!SBtab"] + list(map(lambda x: "%s='%s'" % x, header))
|
|
1163
|
+
|
|
1164
|
+
sbtab.doc_row = None
|
|
1165
|
+
sbtab.header_row = " ".join(header_strings)
|
|
1166
|
+
|
|
1167
|
+
sbtab.columns = column_list
|
|
1168
|
+
sbtab.columns_dict = dict(map(reversed, enumerate(column_list)))
|
|
1169
|
+
sbtab.value_rows = value_rows
|
|
1170
|
+
|
|
1171
|
+
return sbtab
|
|
1172
|
+
|
|
1173
|
+
|
|
1174
|
+
class SBtabDocument:
|
|
1175
|
+
"""The sbtab document class can consist of one or more sbtab Table objects."""
|
|
1176
|
+
|
|
1177
|
+
def __init__(
|
|
1178
|
+
self, name=None, sbtab_init=None, filename=None, definitions_file=None
|
|
1179
|
+
):
|
|
1180
|
+
"""
|
|
1181
|
+
Create SBtabDocument with an optional sbtab table object.
|
|
1182
|
+
|
|
1183
|
+
Parameters
|
|
1184
|
+
----------
|
|
1185
|
+
name: str
|
|
1186
|
+
Name for the sbtab document.
|
|
1187
|
+
sbtab_init: str | sbtab.SBtabTable
|
|
1188
|
+
Initial sbtab table as either string or sbtab table object
|
|
1189
|
+
filename: str
|
|
1190
|
+
If sbtab_init is string, also provide a fiename.
|
|
1191
|
+
"""
|
|
1192
|
+
if filename:
|
|
1193
|
+
self.filename = filename
|
|
1194
|
+
else:
|
|
1195
|
+
self.filename = None
|
|
1196
|
+
self.sbtabs = []
|
|
1197
|
+
self.id_to_sbtab = {}
|
|
1198
|
+
self.name_to_sbtab = {}
|
|
1199
|
+
self.type_to_sbtab = {}
|
|
1200
|
+
self.sbtab_filenames = []
|
|
1201
|
+
self.doc_row = False
|
|
1202
|
+
self.document_format = None
|
|
1203
|
+
|
|
1204
|
+
if name:
|
|
1205
|
+
self.name = name
|
|
1206
|
+
self._get_doc_row_attributes()
|
|
1207
|
+
else:
|
|
1208
|
+
self.name = None
|
|
1209
|
+
|
|
1210
|
+
# if there is an initial sbtab given, see if it is
|
|
1211
|
+
# a string or an sbtab
|
|
1212
|
+
if sbtab_init and isinstance(sbtab_init, str):
|
|
1213
|
+
self.add_sbtab_string(sbtab_init, filename, definitions_file)
|
|
1214
|
+
elif sbtab_init:
|
|
1215
|
+
self.add_sbtab(sbtab_init, definitions_file)
|
|
1216
|
+
|
|
1217
|
+
self.object_type = "doc"
|
|
1218
|
+
|
|
1219
|
+
def add_sbtab(self, sbtab, definitions_file=None):
|
|
1220
|
+
"""
|
|
1221
|
+
Add an sbtab Table object to the sbtab Document.
|
|
1222
|
+
|
|
1223
|
+
Parameters
|
|
1224
|
+
----------
|
|
1225
|
+
sbtab: sbtab.SBtabTable
|
|
1226
|
+
sbtab table object to be added to the document.
|
|
1227
|
+
"""
|
|
1228
|
+
if not self.filename:
|
|
1229
|
+
self.filename = sbtab.filename
|
|
1230
|
+
|
|
1231
|
+
if sbtab.table_id in self.id_to_sbtab.keys():
|
|
1232
|
+
raise SBtabError(
|
|
1233
|
+
"A table with the ID %s is already in the document. Table IDs need"
|
|
1234
|
+
" to be unique within one document." % sbtab.table_id
|
|
1235
|
+
)
|
|
1236
|
+
|
|
1237
|
+
valid_type = self.check_type_validity(sbtab.table_type, definitions_file)
|
|
1238
|
+
if valid_type:
|
|
1239
|
+
self.name_to_sbtab[sbtab.table_name] = sbtab
|
|
1240
|
+
self.id_to_sbtab[sbtab.table_id] = sbtab
|
|
1241
|
+
self.sbtabs.append(sbtab)
|
|
1242
|
+
self.sbtab_filenames.append(sbtab.filename)
|
|
1243
|
+
if sbtab.table_type in self.type_to_sbtab:
|
|
1244
|
+
tabs = self.type_to_sbtab[sbtab.table_type]
|
|
1245
|
+
tabs.append(sbtab)
|
|
1246
|
+
self.type_to_sbtab[sbtab.table_type] = tabs
|
|
1247
|
+
else:
|
|
1248
|
+
self.type_to_sbtab[sbtab.table_type] = [sbtab]
|
|
1249
|
+
|
|
1250
|
+
self._get_doc_row_attributes()
|
|
1251
|
+
return True
|
|
1252
|
+
|
|
1253
|
+
def add_sbtab_string(self, sbtab_string, filename, definitions_file=None):
|
|
1254
|
+
"""
|
|
1255
|
+
Add one or multiple sbtab files as a string.
|
|
1256
|
+
|
|
1257
|
+
Parameters
|
|
1258
|
+
----------
|
|
1259
|
+
sbtab_string: str
|
|
1260
|
+
One string holding one or more sbtab tables.
|
|
1261
|
+
filename: str
|
|
1262
|
+
Name of the given sbtab.
|
|
1263
|
+
"""
|
|
1264
|
+
# set filename if not given
|
|
1265
|
+
if not filename:
|
|
1266
|
+
sbtab_count = len(self.sbtabs)
|
|
1267
|
+
filename = "unnamed_sbtab_%s.tsv" % (str(sbtab_count))
|
|
1268
|
+
|
|
1269
|
+
if not self.filename:
|
|
1270
|
+
self.filename = filename
|
|
1271
|
+
if not self.name:
|
|
1272
|
+
self.name = filename
|
|
1273
|
+
|
|
1274
|
+
# see if there are more than one SBtabs in the string
|
|
1275
|
+
try:
|
|
1276
|
+
sbtab_amount = utils.count_tabs(sbtab_string)
|
|
1277
|
+
except Exception:
|
|
1278
|
+
raise SBtabError("The sbtab file could not be read properly.")
|
|
1279
|
+
|
|
1280
|
+
# here, we find a possible doc row
|
|
1281
|
+
for row in sbtab_string.split("\n"):
|
|
1282
|
+
if row.startswith("!!!"):
|
|
1283
|
+
self.doc_row = self._dequote(row)
|
|
1284
|
+
break
|
|
1285
|
+
elif row.startswith('"!!!'):
|
|
1286
|
+
rm1 = row.replace('""', "#")
|
|
1287
|
+
rm2 = rm1.replace('"', "")
|
|
1288
|
+
self.doc_row = self._dequote(rm2.replace("#", '"'))
|
|
1289
|
+
break
|
|
1290
|
+
else:
|
|
1291
|
+
self.doc_row = None
|
|
1292
|
+
self.document_format = None
|
|
1293
|
+
|
|
1294
|
+
# determine if this is an sbtab or ObjTables Document
|
|
1295
|
+
if self.doc_row:
|
|
1296
|
+
if "!!!ObjTables" in self.doc_row:
|
|
1297
|
+
self.document_format = "ObjTables"
|
|
1298
|
+
elif "!!!SBtab" in self.doc_row:
|
|
1299
|
+
self.document_format = "sbtab"
|
|
1300
|
+
|
|
1301
|
+
# if there are more than one SBtabs, cut them in single SBtabs
|
|
1302
|
+
try:
|
|
1303
|
+
if sbtab_amount > 1:
|
|
1304
|
+
sbtab_strings = utils.split_sbtabs(sbtab_string)
|
|
1305
|
+
for i, sbtab_s in enumerate(sbtab_strings):
|
|
1306
|
+
name_single = str(i) + "_" + str(self.filename)
|
|
1307
|
+
sbtab_single = SBtabTable(sbtab_s, name_single)
|
|
1308
|
+
logging.debug(
|
|
1309
|
+
"name = %s, type = %s"
|
|
1310
|
+
% (sbtab_single.table_name, sbtab_single.table_type)
|
|
1311
|
+
)
|
|
1312
|
+
self.add_sbtab(sbtab_single, definitions_file)
|
|
1313
|
+
else:
|
|
1314
|
+
sbtab = SBtabTable(sbtab_string, filename)
|
|
1315
|
+
self.add_sbtab(sbtab)
|
|
1316
|
+
except Exception as e:
|
|
1317
|
+
raise SBtabError(
|
|
1318
|
+
"The sbtab Table object could not be created properly: " + str(e)
|
|
1319
|
+
)
|
|
1320
|
+
return True
|
|
1321
|
+
|
|
1322
|
+
def _dequote(self, row):
|
|
1323
|
+
"""Bring consistency in the multifarious quotation mark problems."""
|
|
1324
|
+
stupid_quotes = [
|
|
1325
|
+
'"',
|
|
1326
|
+
"\xe2\x80\x9d",
|
|
1327
|
+
"\xe2\x80\x98",
|
|
1328
|
+
"\xe2\x80\x99",
|
|
1329
|
+
"\xe2\x80\x9b",
|
|
1330
|
+
"\xe2\x80\x9c",
|
|
1331
|
+
"\xe2\x80\x9f",
|
|
1332
|
+
"\xe2\x80\xb2",
|
|
1333
|
+
"\xe2\x80\xb3",
|
|
1334
|
+
"\xe2\x80\xb4",
|
|
1335
|
+
"\xe2\x80\xb5",
|
|
1336
|
+
"\xe2\x80\xb6",
|
|
1337
|
+
"\xe2\x80\xb7",
|
|
1338
|
+
]
|
|
1339
|
+
|
|
1340
|
+
for squote in stupid_quotes:
|
|
1341
|
+
try:
|
|
1342
|
+
row = row.replace(squote, "'")
|
|
1343
|
+
except Exception:
|
|
1344
|
+
pass
|
|
1345
|
+
|
|
1346
|
+
row = row.replace("\n", "")
|
|
1347
|
+
|
|
1348
|
+
return row
|
|
1349
|
+
|
|
1350
|
+
def check_type_validity(self, ttype, definitions_file=None):
|
|
1351
|
+
"""
|
|
1352
|
+
Check if the given table type is supported by default.
|
|
1353
|
+
|
|
1354
|
+
Parameters
|
|
1355
|
+
----------
|
|
1356
|
+
ttype: str
|
|
1357
|
+
Table type to be tested.
|
|
1358
|
+
|
|
1359
|
+
Returns: Bool
|
|
1360
|
+
Flag that indicates if the given table type is supported by default.
|
|
1361
|
+
|
|
1362
|
+
"""
|
|
1363
|
+
try:
|
|
1364
|
+
supported_types = utils.extract_supported_table_types(definitions_file)
|
|
1365
|
+
except Exception:
|
|
1366
|
+
raise SBtabError(
|
|
1367
|
+
"The definition file could not be found to"
|
|
1368
|
+
" establish supported table types."
|
|
1369
|
+
)
|
|
1370
|
+
|
|
1371
|
+
if ttype in supported_types:
|
|
1372
|
+
return True
|
|
1373
|
+
else:
|
|
1374
|
+
raise SBtabError(
|
|
1375
|
+
"The table type %s is not supported. Make sure to provide a"
|
|
1376
|
+
" definition file that contains this table type." % ttype
|
|
1377
|
+
)
|
|
1378
|
+
|
|
1379
|
+
def _get_doc_row_attributes(self):
|
|
1380
|
+
"""Read content of the !!!-document declaration row."""
|
|
1381
|
+
now = datetime.datetime.now()
|
|
1382
|
+
self.date = "-".join([str(now.year), str(now.month), str(now.day)])
|
|
1383
|
+
|
|
1384
|
+
if not self.doc_row:
|
|
1385
|
+
self.doc_row = "!!!SBtab DocumentName='%s' SBtabVersion='1.0' Date='%s'" % (
|
|
1386
|
+
self.name,
|
|
1387
|
+
self.date,
|
|
1388
|
+
)
|
|
1389
|
+
else:
|
|
1390
|
+
# save document name, otherwise raise error
|
|
1391
|
+
# (overrides name given at document initialisation)
|
|
1392
|
+
try:
|
|
1393
|
+
self.name = self.get_custom_doc_information("DocumentName")
|
|
1394
|
+
except Exception:
|
|
1395
|
+
pass
|
|
1396
|
+
|
|
1397
|
+
# save SBtabVersion
|
|
1398
|
+
try:
|
|
1399
|
+
self.version = self.get_custom_doc_information("SBtabVersion")
|
|
1400
|
+
except Exception:
|
|
1401
|
+
self.version = None
|
|
1402
|
+
|
|
1403
|
+
# save date
|
|
1404
|
+
try:
|
|
1405
|
+
self.date = self.get_custom_doc_information("Date")
|
|
1406
|
+
except Exception:
|
|
1407
|
+
if "Date=" not in self.doc_row:
|
|
1408
|
+
self.doc_row = self.doc_row + " Date='%s'" % self.date
|
|
1409
|
+
|
|
1410
|
+
# save document type
|
|
1411
|
+
try:
|
|
1412
|
+
self.doc_type = self.get_custom_doc_information("DocumentType")
|
|
1413
|
+
except Exception:
|
|
1414
|
+
self.doc_type = None
|
|
1415
|
+
|
|
1416
|
+
def change_attribute(self, attribute, value):
|
|
1417
|
+
"""
|
|
1418
|
+
Change the value of an sbtab attribute.
|
|
1419
|
+
|
|
1420
|
+
Parameters
|
|
1421
|
+
----------
|
|
1422
|
+
attribute: str
|
|
1423
|
+
Attribute from the sbtab document object's declaration row.
|
|
1424
|
+
value: str
|
|
1425
|
+
Value for the attribute.
|
|
1426
|
+
"""
|
|
1427
|
+
try:
|
|
1428
|
+
att_value_new = "%s='%s'" % (attribute, value)
|
|
1429
|
+
except Exception:
|
|
1430
|
+
raise SBtabError("Please provide only strings as attribute and value.")
|
|
1431
|
+
|
|
1432
|
+
match = re.search(_attribute_pattern(attribute), self.doc_row)
|
|
1433
|
+
if match is None:
|
|
1434
|
+
self.doc_row = self.doc_row + " " + att_value_new
|
|
1435
|
+
else:
|
|
1436
|
+
self.doc_row = (
|
|
1437
|
+
self.doc_row[: match.start()]
|
|
1438
|
+
+ att_value_new
|
|
1439
|
+
+ self.doc_row[match.end() :]
|
|
1440
|
+
)
|
|
1441
|
+
|
|
1442
|
+
def unset_attribute(self, attribute):
|
|
1443
|
+
"""
|
|
1444
|
+
Remove attribute from sbtab document object's declaration row.
|
|
1445
|
+
|
|
1446
|
+
Parameters
|
|
1447
|
+
----------
|
|
1448
|
+
attribute: str
|
|
1449
|
+
Attribute that shall be removed.
|
|
1450
|
+
"""
|
|
1451
|
+
obligatory_attributes = ["DocumentName"]
|
|
1452
|
+
if attribute in obligatory_attributes:
|
|
1453
|
+
raise SBtabError(
|
|
1454
|
+
"Attribute %s cannot be removed as it is obligatory." % attribute
|
|
1455
|
+
)
|
|
1456
|
+
|
|
1457
|
+
match = re.search(_attribute_pattern(attribute), self.doc_row)
|
|
1458
|
+
if match is None:
|
|
1459
|
+
raise SBtabError(
|
|
1460
|
+
"Attribute %s is not in the doc row of the SBtabDocument." % attribute
|
|
1461
|
+
)
|
|
1462
|
+
start = match.start()
|
|
1463
|
+
if self.doc_row[start - 1 : start] == " ":
|
|
1464
|
+
start -= 1
|
|
1465
|
+
self.doc_row = self.doc_row[:start] + self.doc_row[match.end() :]
|
|
1466
|
+
|
|
1467
|
+
def get_attribute(self, attribute, default=_MISSING):
|
|
1468
|
+
"""
|
|
1469
|
+
Return the value of an sbtab attribute.
|
|
1470
|
+
|
|
1471
|
+
Parameters
|
|
1472
|
+
----------
|
|
1473
|
+
attribute: str
|
|
1474
|
+
Attribute from the sbtab document object's declaration row.
|
|
1475
|
+
default : object
|
|
1476
|
+
Returned if the attribute is absent. If no default is given, a
|
|
1477
|
+
missing attribute raises SBtabError.
|
|
1478
|
+
|
|
1479
|
+
Returns: str
|
|
1480
|
+
Value of the requested attribute.
|
|
1481
|
+
"""
|
|
1482
|
+
match = re.search(_attribute_pattern(attribute), self.doc_row or "")
|
|
1483
|
+
if match is not None:
|
|
1484
|
+
return match.group(1)
|
|
1485
|
+
if default is not _MISSING:
|
|
1486
|
+
return default
|
|
1487
|
+
raise SBtabError("The attribute %s was not found in the doc row." % attribute)
|
|
1488
|
+
|
|
1489
|
+
def set_version(self, version):
|
|
1490
|
+
"""
|
|
1491
|
+
Set SBtabVersion of the document.
|
|
1492
|
+
|
|
1493
|
+
Parameters
|
|
1494
|
+
----------
|
|
1495
|
+
version: str
|
|
1496
|
+
Version number of sbtab document.
|
|
1497
|
+
"""
|
|
1498
|
+
try:
|
|
1499
|
+
self.version = version
|
|
1500
|
+
except Exception:
|
|
1501
|
+
raise SBtabError("Version could not be set to %s" % version)
|
|
1502
|
+
|
|
1503
|
+
def set_date(self, date):
|
|
1504
|
+
"""
|
|
1505
|
+
Set date of the document.
|
|
1506
|
+
|
|
1507
|
+
Parameters
|
|
1508
|
+
----------
|
|
1509
|
+
date: str
|
|
1510
|
+
Date of the sbtab document.
|
|
1511
|
+
"""
|
|
1512
|
+
try:
|
|
1513
|
+
self.date = date
|
|
1514
|
+
except Exception:
|
|
1515
|
+
raise SBtabError("Date could not be set to %s" % date)
|
|
1516
|
+
|
|
1517
|
+
def set_doc_type(self, doc_type):
|
|
1518
|
+
"""
|
|
1519
|
+
Set the type of the document.
|
|
1520
|
+
|
|
1521
|
+
Parameters
|
|
1522
|
+
----------
|
|
1523
|
+
doc_type: str
|
|
1524
|
+
Type of the sbtab document.
|
|
1525
|
+
"""
|
|
1526
|
+
try:
|
|
1527
|
+
self.doc_type = doc_type
|
|
1528
|
+
except Exception:
|
|
1529
|
+
raise SBtabError("Doc type could not be set to %s" % doc_type)
|
|
1530
|
+
|
|
1531
|
+
def remove_sbtab_by_name(self, name):
|
|
1532
|
+
"""
|
|
1533
|
+
Remove sbtab Table from sbtab Document.
|
|
1534
|
+
|
|
1535
|
+
Parameters
|
|
1536
|
+
----------
|
|
1537
|
+
name: str
|
|
1538
|
+
Name of the sbtab table to be removed.
|
|
1539
|
+
"""
|
|
1540
|
+
for i, sbtab in enumerate(self.sbtabs):
|
|
1541
|
+
if sbtab.table_name == name:
|
|
1542
|
+
del self.sbtabs[i]
|
|
1543
|
+
del self.name_to_sbtab[sbtab.table_name]
|
|
1544
|
+
del self.id_to_sbtab[sbtab.table_id]
|
|
1545
|
+
del self.sbtab_filenames[i]
|
|
1546
|
+
tabs = self.type_to_sbtab[sbtab.table_type]
|
|
1547
|
+
for tab in tabs:
|
|
1548
|
+
if tab.table_name == sbtab.table_name:
|
|
1549
|
+
tabs.remove(tab)
|
|
1550
|
+
break
|
|
1551
|
+
|
|
1552
|
+
self.type_to_sbtab[sbtab.table_type] = tabs
|
|
1553
|
+
break
|
|
1554
|
+
|
|
1555
|
+
return True
|
|
1556
|
+
|
|
1557
|
+
def get_sbtab_by_name(self, name):
|
|
1558
|
+
"""
|
|
1559
|
+
Return sbtab table object by given name.
|
|
1560
|
+
|
|
1561
|
+
Parameters
|
|
1562
|
+
----------
|
|
1563
|
+
name: str
|
|
1564
|
+
Name of sbtab table to be returned.
|
|
1565
|
+
|
|
1566
|
+
Returns: sbtab.SBtabTable
|
|
1567
|
+
sbtab table object of requested name.
|
|
1568
|
+
"""
|
|
1569
|
+
try:
|
|
1570
|
+
return self.name_to_sbtab[name]
|
|
1571
|
+
except Exception:
|
|
1572
|
+
return None
|
|
1573
|
+
|
|
1574
|
+
def get_sbtab_by_id(self, name):
|
|
1575
|
+
"""
|
|
1576
|
+
Return sbtab table object by given ID.
|
|
1577
|
+
|
|
1578
|
+
Parameters
|
|
1579
|
+
----------
|
|
1580
|
+
name: str
|
|
1581
|
+
TableID of a sbtab table object.
|
|
1582
|
+
|
|
1583
|
+
Returns: sbtab.SBtabTable
|
|
1584
|
+
sbtab table object of requested ID.
|
|
1585
|
+
"""
|
|
1586
|
+
try:
|
|
1587
|
+
return self.id_to_sbtab[name]
|
|
1588
|
+
except Exception:
|
|
1589
|
+
return None
|
|
1590
|
+
|
|
1591
|
+
def get_sbtab_by_type(self, ttype):
|
|
1592
|
+
"""
|
|
1593
|
+
Return list of sbtab objects by given table type.
|
|
1594
|
+
|
|
1595
|
+
Parameters
|
|
1596
|
+
----------
|
|
1597
|
+
ttype: str
|
|
1598
|
+
Supported sbtab table type.
|
|
1599
|
+
|
|
1600
|
+
Returns: list
|
|
1601
|
+
List of sbtab.SBtabTable objects of the requested table type.
|
|
1602
|
+
"""
|
|
1603
|
+
try:
|
|
1604
|
+
return self.type_to_sbtab[ttype]
|
|
1605
|
+
except Exception:
|
|
1606
|
+
return None
|
|
1607
|
+
|
|
1608
|
+
def set_filename(self, filename):
|
|
1609
|
+
"""
|
|
1610
|
+
Set filename of sbtab document.
|
|
1611
|
+
|
|
1612
|
+
Parameters
|
|
1613
|
+
----------
|
|
1614
|
+
filename: str or Path
|
|
1615
|
+
Filename for the sbtab document.
|
|
1616
|
+
"""
|
|
1617
|
+
self.filename = Path(filename)
|
|
1618
|
+
|
|
1619
|
+
def set_name(self, name):
|
|
1620
|
+
"""
|
|
1621
|
+
Set name of sbtab document.
|
|
1622
|
+
|
|
1623
|
+
Parameters
|
|
1624
|
+
----------
|
|
1625
|
+
name: str
|
|
1626
|
+
Name for the sbtab document.
|
|
1627
|
+
"""
|
|
1628
|
+
self.name = name
|
|
1629
|
+
|
|
1630
|
+
def write(self, filename=None):
|
|
1631
|
+
"""
|
|
1632
|
+
Write SBtabDocument to hard disk.
|
|
1633
|
+
|
|
1634
|
+
Parameters
|
|
1635
|
+
----------
|
|
1636
|
+
filename: str or Path
|
|
1637
|
+
Name for the output file.
|
|
1638
|
+
"""
|
|
1639
|
+
path = Path(filename if filename is not None else self.filename)
|
|
1640
|
+
if path.suffix not in {".tsv", ".csv"}:
|
|
1641
|
+
delimiter = self.sbtabs[0].delimiter
|
|
1642
|
+
if delimiter == "\t":
|
|
1643
|
+
path = path.with_suffix(".tsv")
|
|
1644
|
+
elif delimiter in {",", ";"}:
|
|
1645
|
+
path = path.with_suffix(".csv")
|
|
1646
|
+
else:
|
|
1647
|
+
raise SBtabError(
|
|
1648
|
+
"The file extension is missing and the delimiter is not set."
|
|
1649
|
+
)
|
|
1650
|
+
|
|
1651
|
+
try:
|
|
1652
|
+
path.write_text(self.to_str())
|
|
1653
|
+
return True
|
|
1654
|
+
except Exception:
|
|
1655
|
+
raise SBtabError("The file could not be written.")
|
|
1656
|
+
|
|
1657
|
+
def to_str(self):
|
|
1658
|
+
"""
|
|
1659
|
+
Return sbtab Document as one string.
|
|
1660
|
+
|
|
1661
|
+
Returns: str
|
|
1662
|
+
sbtab.SBtabDocument as string representation.
|
|
1663
|
+
"""
|
|
1664
|
+
sbtab_document = self.doc_row + "\r\n"
|
|
1665
|
+
for sbtab in self.sbtabs:
|
|
1666
|
+
sbtab_document += sbtab.to_str() + "\n\n"
|
|
1667
|
+
|
|
1668
|
+
return sbtab_document
|
|
1669
|
+
|
|
1670
|
+
def get_custom_doc_information(self, attribute_name, test_row=None):
|
|
1671
|
+
"""
|
|
1672
|
+
Retrieve the value of a doc attribute in the sbtab document declaration row.
|
|
1673
|
+
|
|
1674
|
+
Parameters
|
|
1675
|
+
----------
|
|
1676
|
+
attribute_name: str
|
|
1677
|
+
Name of the requested attribute.
|
|
1678
|
+
|
|
1679
|
+
Returns: str
|
|
1680
|
+
Value of the requested attribute.
|
|
1681
|
+
"""
|
|
1682
|
+
if test_row:
|
|
1683
|
+
doc_row = test_row
|
|
1684
|
+
else:
|
|
1685
|
+
doc_row = self.doc_row
|
|
1686
|
+
|
|
1687
|
+
if re.search("%s='([^']*)'" % attribute_name, doc_row) is not None:
|
|
1688
|
+
return re.search("%s='([^']*)'" % attribute_name, doc_row).group(1)
|
|
1689
|
+
else:
|
|
1690
|
+
raise SBtabError(
|
|
1691
|
+
"""The %s of the Document is
|
|
1692
|
+
not defined!"""
|
|
1693
|
+
% attribute_name
|
|
1694
|
+
)
|
|
1695
|
+
|
|
1696
|
+
def set_doc_row(self, new_doc_row):
|
|
1697
|
+
"""
|
|
1698
|
+
Set a new sbtab document declaration row.
|
|
1699
|
+
|
|
1700
|
+
Parameters
|
|
1701
|
+
----------
|
|
1702
|
+
new_doc_row: str
|
|
1703
|
+
New sbtab document declaration row.
|
|
1704
|
+
"""
|
|
1705
|
+
if not new_doc_row.startswith("!!!SBtab") and not new_doc_row.startswith(
|
|
1706
|
+
"!!!ObjTables"
|
|
1707
|
+
):
|
|
1708
|
+
raise SBtabError('A doc row needs to be preceded with "!!!SBtab".')
|
|
1709
|
+
|
|
1710
|
+
if "DocumentName=" not in new_doc_row:
|
|
1711
|
+
raise SBtabError("A doc row needs to define the DocumentName attribute.")
|
|
1712
|
+
|
|
1713
|
+
self.doc_row = new_doc_row
|
|
1714
|
+
self._get_doc_row_attributes()
|