pyemaps 2.0.0__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.
- pyemaps/COPYING +798 -0
- pyemaps/CifFile/CifFile_module.py +3117 -0
- pyemaps/CifFile/StarFile.py +2496 -0
- pyemaps/CifFile/TypeContentsParser.py +82 -0
- pyemaps/CifFile/YappsStarParser_1_0.py +289 -0
- pyemaps/CifFile/YappsStarParser_1_1.py +288 -0
- pyemaps/CifFile/YappsStarParser_2_0.py +369 -0
- pyemaps/CifFile/YappsStarParser_STAR2.py +382 -0
- pyemaps/CifFile/__init__.py +7 -0
- pyemaps/CifFile/drel/__init__.py +1 -0
- pyemaps/CifFile/drel/drel_ast_yacc.py +582 -0
- pyemaps/CifFile/drel/drel_lex.py +228 -0
- pyemaps/CifFile/drel/drel_runtime.py +126 -0
- pyemaps/CifFile/drel/parsetab.py +185 -0
- pyemaps/CifFile/drel/py_from_ast.py +628 -0
- pyemaps/CifFile/parsetab.py +185 -0
- pyemaps/CifFile/yapps3_compiled_rt.py +390 -0
- pyemaps/README.md +201 -0
- pyemaps/__config__.py +91 -0
- pyemaps/__init__.py +233 -0
- pyemaps/__main__.py +219 -0
- pyemaps/__version__.py +1 -0
- pyemaps/cdata/Aluminium.xtl +4 -0
- pyemaps/cdata/AluminiumOxide.xtl +5 -0
- pyemaps/cdata/Aluminium_FCC.xtl +4 -0
- pyemaps/cdata/BariumTitanate_180k.xtl +6 -0
- pyemaps/cdata/BariumTitanate_270k.xtl +7 -0
- pyemaps/cdata/BariumTitanate_Tetra.xtl +7 -0
- pyemaps/cdata/BiMnO3.xtl +27 -0
- pyemaps/cdata/Boron_Tetra.xtl +8 -0
- pyemaps/cdata/CadmiumSelenide_Hex.xtl +5 -0
- pyemaps/cdata/CadmiumSulfide_Cubic.xtl +5 -0
- pyemaps/cdata/CadmiumSulfide_Hex.xtl +5 -0
- pyemaps/cdata/Chromium_BCC.xtl +4 -0
- pyemaps/cdata/CoSb3_Skutterudite.xtl +5 -0
- pyemaps/cdata/CopperOxide.xtl +5 -0
- pyemaps/cdata/Copper_FCC.xtl +4 -0
- pyemaps/cdata/Cu2O_Cuprite.xtl +5 -0
- pyemaps/cdata/Diamond.xtl +4 -0
- pyemaps/cdata/ErbiumPyrogermanate.xtl +9 -0
- pyemaps/cdata/FePd_Tetra.xtl +5 -0
- pyemaps/cdata/FeS2_Pyrite.xtl +15 -0
- pyemaps/cdata/GalliumAntimonide.xtl +5 -0
- pyemaps/cdata/GalliumArsenide.xtl +5 -0
- pyemaps/cdata/GalliumNitride.xtl +5 -0
- pyemaps/cdata/Germanium.xtl +4 -0
- pyemaps/cdata/Gold_FCC.xtl +4 -0
- pyemaps/cdata/IndiumArsenide.xtl +5 -0
- pyemaps/cdata/LaMnO3.xtl +7 -0
- pyemaps/cdata/LeadZirconateTitanate.xtl +8 -0
- pyemaps/cdata/Li2MnO3.xtl +13 -0
- pyemaps/cdata/NaFeO2.xtl +6 -0
- pyemaps/cdata/Nb3Sn.xtl +5 -0
- pyemaps/cdata/Pentacene.xtl +39 -0
- pyemaps/cdata/Silicon.xtl +4 -0
- pyemaps/cdata/StrontiumTitanate.xtl +6 -0
- pyemaps/cdata/TelluriumDioxide.xtl +5 -0
- pyemaps/cdata/TinDioxide_RT.xtl +5 -0
- pyemaps/cdata/TitaniumDioxide_Anatase.xtl +5 -0
- pyemaps/cdata/TitaniumDioxide_Rutile.xtl +5 -0
- pyemaps/cdata/TungstenDiselenide.xtl +5 -0
- pyemaps/cdata/VanadiumDioxide_RT.xtl +6 -0
- pyemaps/cdata/ZincOxide.xtl +5 -0
- pyemaps/cdata/Zinc_HCP.xtl +4 -0
- pyemaps/cdata/ZirconiumNitride.xtl +5 -0
- pyemaps/cdata/limno2.xtl +6 -0
- pyemaps/crystals.py +1415 -0
- pyemaps/ddiffs.py +147 -0
- pyemaps/diffract/__init__.py +70 -0
- pyemaps/diffract/bloch_dec.py +779 -0
- pyemaps/diffract/csf_dec.py +242 -0
- pyemaps/diffract/dif_dec.py +519 -0
- pyemaps/diffract/dpgen_dec.py +175 -0
- pyemaps/diffract/mxtal_dec.py +201 -0
- pyemaps/diffract/powder_dec.py +137 -0
- pyemaps/diffract/stereo_dec.py +111 -0
- pyemaps/display.py +791 -0
- pyemaps/emcontrols.py +1017 -0
- pyemaps/errors.py +266 -0
- pyemaps/fileutils.py +1001 -0
- pyemaps/kdiffs.py +1221 -0
- pyemaps/license.txt +42 -0
- pyemaps/samples/adf.py +137 -0
- pyemaps/samples/al.img +0 -0
- pyemaps/samples/al_dpgen.py +57 -0
- pyemaps/samples/al_ediom.py +94 -0
- pyemaps/samples/convert_image.py +88 -0
- pyemaps/samples/dm_diff.py +225 -0
- pyemaps/samples/mask.im3 +0 -0
- pyemaps/samples/powder.py +51 -0
- pyemaps/samples/raw.img +0 -0
- pyemaps/samples/si_bloch.py +120 -0
- pyemaps/samples/si_constructor.py +51 -0
- pyemaps/samples/si_csf.py +59 -0
- pyemaps/samples/si_dif.py +134 -0
- pyemaps/samples/si_lacbed.py +59 -0
- pyemaps/samples/si_pyemaps.py +80 -0
- pyemaps/samples/si_scm.py +156 -0
- pyemaps/samples/si_stereo.py +116 -0
- pyemaps/scattering/__init__.py +26 -0
- pyemaps/scattering/sct_dec.py +31 -0
- pyemaps/spg/__init__.py +25 -0
- pyemaps/spg/spg_dec.py +60 -0
- pyemaps/stackimg.py +1031 -0
- pyemaps/xtal.py +222 -0
- pyemaps-2.0.0.dist-info/METADATA +231 -0
- pyemaps-2.0.0.dist-info/RECORD +112 -0
- pyemaps-2.0.0.dist-info/WHEEL +5 -0
- pyemaps-2.0.0.dist-info/entry_points.txt +2 -0
- pyemaps-2.0.0.dist-info/licenses/COPYING +798 -0
- pyemaps-2.0.0.dist-info/licenses/license.txt +42 -0
- pyemaps-2.0.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,3117 @@
|
|
|
1
|
+
# To maximize python3/python2 compatibility
|
|
2
|
+
from __future__ import print_function
|
|
3
|
+
from __future__ import unicode_literals
|
|
4
|
+
from __future__ import division
|
|
5
|
+
from __future__ import absolute_import
|
|
6
|
+
|
|
7
|
+
try:
|
|
8
|
+
from cStringIO import StringIO
|
|
9
|
+
except ImportError:
|
|
10
|
+
from io import StringIO
|
|
11
|
+
|
|
12
|
+
# Python 2,3 compatibility
|
|
13
|
+
try:
|
|
14
|
+
from urllib import urlopen # for arbitrary opening
|
|
15
|
+
from urlparse import urlparse, urljoin
|
|
16
|
+
except:
|
|
17
|
+
from urllib.request import urlopen
|
|
18
|
+
from urllib.parse import urlparse, urljoin
|
|
19
|
+
|
|
20
|
+
# The unicode type does not exist in Python3 as the str type
|
|
21
|
+
# encompasses unicode. PyCIFRW tests for 'unicode' would fail
|
|
22
|
+
# Suggestions for a better approach welcome.
|
|
23
|
+
#
|
|
24
|
+
# long type no longer exists in Python3, so we alias to int
|
|
25
|
+
#
|
|
26
|
+
if isinstance(u"abc",str): #Python3
|
|
27
|
+
unicode = str
|
|
28
|
+
long = int
|
|
29
|
+
|
|
30
|
+
__copyright = """
|
|
31
|
+
PYCIFRW License Agreement (Python License, Version 2)
|
|
32
|
+
-----------------------------------------------------
|
|
33
|
+
|
|
34
|
+
1. This LICENSE AGREEMENT is between the Australian Nuclear Science
|
|
35
|
+
and Technology Organisation ("ANSTO"), and the Individual or
|
|
36
|
+
Organization ("Licensee") accessing and otherwise using this software
|
|
37
|
+
("PyCIFRW") in source or binary form and its associated documentation.
|
|
38
|
+
|
|
39
|
+
2. Subject to the terms and conditions of this License Agreement,
|
|
40
|
+
ANSTO hereby grants Licensee a nonexclusive, royalty-free, world-wide
|
|
41
|
+
license to reproduce, analyze, test, perform and/or display publicly,
|
|
42
|
+
prepare derivative works, distribute, and otherwise use PyCIFRW alone
|
|
43
|
+
or in any derivative version, provided, however, that this License
|
|
44
|
+
Agreement and ANSTO's notice of copyright, i.e., "Copyright (c)
|
|
45
|
+
2001-2014 ANSTO; All Rights Reserved" are retained in PyCIFRW alone or
|
|
46
|
+
in any derivative version prepared by Licensee.
|
|
47
|
+
|
|
48
|
+
3. In the event Licensee prepares a derivative work that is based on
|
|
49
|
+
or incorporates PyCIFRW or any part thereof, and wants to make the
|
|
50
|
+
derivative work available to others as provided herein, then Licensee
|
|
51
|
+
hereby agrees to include in any such work a brief summary of the
|
|
52
|
+
changes made to PyCIFRW.
|
|
53
|
+
|
|
54
|
+
4. ANSTO is making PyCIFRW available to Licensee on an "AS IS"
|
|
55
|
+
basis. ANSTO MAKES NO REPRESENTATIONS OR WARRANTIES, EXPRESS OR
|
|
56
|
+
IMPLIED. BY WAY OF EXAMPLE, BUT NOT LIMITATION, ANSTO MAKES NO AND
|
|
57
|
+
DISCLAIMS ANY REPRESENTATION OR WARRANTY OF MERCHANTABILITY OR FITNESS
|
|
58
|
+
FOR ANY PARTICULAR PURPOSE OR THAT THE USE OF PYCIFRW WILL NOT
|
|
59
|
+
INFRINGE ANY THIRD PARTY RIGHTS.
|
|
60
|
+
|
|
61
|
+
5. ANSTO SHALL NOT BE LIABLE TO LICENSEE OR ANY OTHER USERS OF PYCIFRW
|
|
62
|
+
FOR ANY INCIDENTAL, SPECIAL, OR CONSEQUENTIAL DAMAGES OR LOSS AS A
|
|
63
|
+
RESULT OF MODIFYING, DISTRIBUTING, OR OTHERWISE USING PYCIFRW, OR ANY
|
|
64
|
+
DERIVATIVE THEREOF, EVEN IF ADVISED OF THE POSSIBILITY THEREOF.
|
|
65
|
+
|
|
66
|
+
6. This License Agreement will automatically terminate upon a material
|
|
67
|
+
breach of its terms and conditions.
|
|
68
|
+
|
|
69
|
+
7. Nothing in this License Agreement shall be deemed to create any
|
|
70
|
+
relationship of agency, partnership, or joint venture between ANSTO
|
|
71
|
+
and Licensee. This License Agreement does not grant permission to use
|
|
72
|
+
ANSTO trademarks or trade name in a trademark sense to endorse or
|
|
73
|
+
promote products or services of Licensee, or any third party.
|
|
74
|
+
|
|
75
|
+
8. By copying, installing or otherwise using PyCIFRW, Licensee agrees
|
|
76
|
+
to be bound by the terms and conditions of this License Agreement.
|
|
77
|
+
|
|
78
|
+
"""
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
import re,sys
|
|
82
|
+
from . import StarFile
|
|
83
|
+
from .StarFile import StarList #put in global scope for exec statement
|
|
84
|
+
try:
|
|
85
|
+
import numpy #put in global scope for exec statement
|
|
86
|
+
from .drel import drel_runtime #put in global scope for exec statement
|
|
87
|
+
except ImportError:
|
|
88
|
+
pass #will fail when using dictionaries for calcs
|
|
89
|
+
from copy import copy #must be in global scope for exec statement
|
|
90
|
+
|
|
91
|
+
def track_recursion(in_this_func):
|
|
92
|
+
"""Keep an eye on a function call to make sure that the key argument hasn't been
|
|
93
|
+
seen before"""
|
|
94
|
+
def wrapper(*args,**kwargs):
|
|
95
|
+
key_arg = args[1]
|
|
96
|
+
if key_arg in wrapper.called_list:
|
|
97
|
+
print('Recursion watch: %s already called %d times' % (key_arg,wrapper.called_list.count(key_arg)))
|
|
98
|
+
raise CifRecursionError( key_arg,wrapper.called_list[:]) #failure
|
|
99
|
+
if len(wrapper.called_list) == 0: #first time
|
|
100
|
+
wrapper.stored_use_defaults = kwargs.get("allow_defaults",False)
|
|
101
|
+
print('All recursive calls will set allow_defaults to ' + repr(wrapper.stored_use_defaults))
|
|
102
|
+
else:
|
|
103
|
+
kwargs["allow_defaults"] = wrapper.stored_use_defaults
|
|
104
|
+
wrapper.called_list.append(key_arg)
|
|
105
|
+
print('Recursion watch: call stack: ' + repr(wrapper.called_list))
|
|
106
|
+
try:
|
|
107
|
+
result = in_this_func(*args,**kwargs)
|
|
108
|
+
except StarFile.StarDerivationError as s:
|
|
109
|
+
if len(wrapper.called_list) == 1: #no more
|
|
110
|
+
raise StarFile.StarDerivationFailure(wrapper.called_list[0])
|
|
111
|
+
else:
|
|
112
|
+
raise
|
|
113
|
+
finally:
|
|
114
|
+
wrapper.called_list.pop()
|
|
115
|
+
if len(wrapper.called_list) == 0:
|
|
116
|
+
wrapper.stored_used_defaults = 'error'
|
|
117
|
+
return result
|
|
118
|
+
wrapper.called_list = []
|
|
119
|
+
return wrapper
|
|
120
|
+
|
|
121
|
+
class CifBlock(StarFile.StarBlock):
|
|
122
|
+
"""
|
|
123
|
+
A class to hold a single block of a CIF file. A `CifBlock` object can be treated as
|
|
124
|
+
a Python dictionary, in particular, individual items can be accessed using square
|
|
125
|
+
brackets e.g. `b['_a_dataname']`. All other Python dictionary methods are also
|
|
126
|
+
available (e.g. `keys()`, `values()`). Looped datanames will return a list of values.
|
|
127
|
+
|
|
128
|
+
## Initialisation
|
|
129
|
+
|
|
130
|
+
When provided, `data` should be another `CifBlock` whose contents will be copied to
|
|
131
|
+
this block.
|
|
132
|
+
|
|
133
|
+
* if `strict` is set, maximum name lengths will be enforced
|
|
134
|
+
|
|
135
|
+
* `maxoutlength` is the maximum length for output lines
|
|
136
|
+
|
|
137
|
+
* `wraplength` is the ideal length to make output lines
|
|
138
|
+
|
|
139
|
+
* When set, `overwrite` allows the values of datanames to be changed (otherwise an error
|
|
140
|
+
is raised).
|
|
141
|
+
|
|
142
|
+
* `compat_mode` will allow deprecated behaviour of creating single-dataname loops using
|
|
143
|
+
the syntax `a[_dataname] = [1,2,3,4]`. This should now be done by calling `CreateLoop`
|
|
144
|
+
after setting the dataitem value.
|
|
145
|
+
"""
|
|
146
|
+
def __init__(self,data = (), strict = 1, compat_mode=False, **kwargs):
|
|
147
|
+
"""When provided, `data` should be another CifBlock whose contents will be copied to
|
|
148
|
+
this block.
|
|
149
|
+
|
|
150
|
+
* if `strict` is set, maximum name lengths will be enforced
|
|
151
|
+
|
|
152
|
+
* `maxoutlength` is the maximum length for output lines
|
|
153
|
+
|
|
154
|
+
* `wraplength` is the ideal length to make output lines
|
|
155
|
+
|
|
156
|
+
* When set, `overwrite` allows the values of datanames to be changed (otherwise an error
|
|
157
|
+
is raised).
|
|
158
|
+
|
|
159
|
+
* `compat_mode` will allow deprecated behaviour of creating single-dataname loops using
|
|
160
|
+
the syntax `a[_dataname] = [1,2,3,4]`. This should now be done by calling `CreateLoop`
|
|
161
|
+
after setting the dataitem value.
|
|
162
|
+
"""
|
|
163
|
+
if strict: maxnamelength=75
|
|
164
|
+
else:
|
|
165
|
+
maxnamelength=-1
|
|
166
|
+
super(CifBlock,self).__init__(data=data,maxnamelength=maxnamelength,**kwargs)
|
|
167
|
+
self.dictionary = None #DDL dictionary referring to this block
|
|
168
|
+
self.compat_mode = compat_mode #old-style behaviour of setitem
|
|
169
|
+
|
|
170
|
+
def RemoveCifItem(self,itemname):
|
|
171
|
+
"""Remove `itemname` from the CifBlock"""
|
|
172
|
+
self.RemoveItem(itemname)
|
|
173
|
+
|
|
174
|
+
def __setitem__(self,key,value):
|
|
175
|
+
self.AddItem(key,value)
|
|
176
|
+
# for backwards compatibility make a single-element loop
|
|
177
|
+
if self.compat_mode:
|
|
178
|
+
if isinstance(value,(tuple,list)) and not isinstance(value,StarFile.StarList):
|
|
179
|
+
# single element loop
|
|
180
|
+
self.CreateLoop([key])
|
|
181
|
+
|
|
182
|
+
def copy(self):
|
|
183
|
+
newblock = super(CifBlock,self).copy()
|
|
184
|
+
return self.copy.im_class(newblock) #catch inheritance
|
|
185
|
+
|
|
186
|
+
def AddCifItem(self,data):
|
|
187
|
+
""" *DEPRECATED*. Use `AddItem` instead."""
|
|
188
|
+
# we accept only tuples, strings and lists!!
|
|
189
|
+
if not (isinstance(data[0],(unicode,tuple,list,str))):
|
|
190
|
+
raise TypeError('Cif datanames are either a string, tuple or list')
|
|
191
|
+
# we catch single item loops as well...
|
|
192
|
+
if isinstance(data[0],(unicode,str)):
|
|
193
|
+
self.AddSingleCifItem(data[0],list(data[1]))
|
|
194
|
+
if isinstance(data[1],(tuple,list)) and not isinstance(data[1],StarFile.StarList): # a single element loop
|
|
195
|
+
self.CreateLoop([data[0]])
|
|
196
|
+
return
|
|
197
|
+
# otherwise, we loop over the datanames
|
|
198
|
+
keyvals = zip(data[0][0],[list(a) for a in data[1][0]])
|
|
199
|
+
[self.AddSingleCifItem(a,b) for a,b in keyvals]
|
|
200
|
+
# and create the loop
|
|
201
|
+
self.CreateLoop(data[0][0])
|
|
202
|
+
|
|
203
|
+
def AddSingleCifItem(self,key,value):
|
|
204
|
+
"""*Deprecated*. Use `AddItem` instead"""
|
|
205
|
+
"""Add a single data item. If it is part of a loop, a separate call should be made"""
|
|
206
|
+
self.AddItem(key,value)
|
|
207
|
+
|
|
208
|
+
def loopnames(self):
|
|
209
|
+
return [self.loops[a] for a in self.loops]
|
|
210
|
+
|
|
211
|
+
|
|
212
|
+
class CifFile(StarFile.StarFile):
|
|
213
|
+
def __init__(self,datasource=None,strict=1,standard='CIF',**kwargs):
|
|
214
|
+
super(CifFile,self).__init__(datasource=datasource,standard=standard, **kwargs)
|
|
215
|
+
self.strict = strict
|
|
216
|
+
self.header_comment = \
|
|
217
|
+
"""
|
|
218
|
+
##########################################################################
|
|
219
|
+
# Crystallographic Information Format file
|
|
220
|
+
# Produced by PyCifRW module
|
|
221
|
+
#
|
|
222
|
+
# This is a CIF file. CIF has been adopted by the International
|
|
223
|
+
# Union of Crystallography as the standard for data archiving and
|
|
224
|
+
# transmission.
|
|
225
|
+
#
|
|
226
|
+
# For information on this file format, follow the CIF links at
|
|
227
|
+
# http://www.iucr.org
|
|
228
|
+
##########################################################################
|
|
229
|
+
"""
|
|
230
|
+
|
|
231
|
+
|
|
232
|
+
class CifError(Exception):
|
|
233
|
+
def __init__(self,value):
|
|
234
|
+
self.value = value
|
|
235
|
+
def __str__(self):
|
|
236
|
+
return '\nCif Format error: '+ self.value
|
|
237
|
+
|
|
238
|
+
class ValidCifError(Exception):
|
|
239
|
+
def __init__(self,value):
|
|
240
|
+
self.value = value
|
|
241
|
+
def __str__(self):
|
|
242
|
+
return '\nCif Validity error: ' + self.value
|
|
243
|
+
|
|
244
|
+
class CifRecursionError(Exception):
|
|
245
|
+
def __init__(self,key_value,call_stack):
|
|
246
|
+
self.key_value = key_value
|
|
247
|
+
self.call_stack = call_stack
|
|
248
|
+
def __str__(self):
|
|
249
|
+
return "Derivation has recursed, %s seen twice (call stack %s)" % (self.key_value,repr(self.call_stack))
|
|
250
|
+
|
|
251
|
+
|
|
252
|
+
class DicBlock(StarFile.StarBlock):
|
|
253
|
+
"""A definition block within a dictionary, which allows imports
|
|
254
|
+
to be transparently followed"""
|
|
255
|
+
|
|
256
|
+
def __init__(self,*args,**kwargs):
|
|
257
|
+
super(DicBlock,self).__init__(*args,**kwargs)
|
|
258
|
+
self._import_cache = {}
|
|
259
|
+
|
|
260
|
+
def __getitem__(self,dataname):
|
|
261
|
+
value = None
|
|
262
|
+
if super(DicBlock,self).has_key("_import.get") and self._import_cache:
|
|
263
|
+
value = self.follow_import(super(DicBlock,self).__getitem__("_import.get"),dataname)
|
|
264
|
+
try:
|
|
265
|
+
final_value = super(DicBlock,self).__getitem__(dataname)
|
|
266
|
+
except KeyError: #not there
|
|
267
|
+
final_value = value
|
|
268
|
+
if final_value is None:
|
|
269
|
+
raise KeyError("%s not found" % dataname)
|
|
270
|
+
return final_value
|
|
271
|
+
|
|
272
|
+
def has_key(self,key):
|
|
273
|
+
try:
|
|
274
|
+
self[key]
|
|
275
|
+
except KeyError:
|
|
276
|
+
return False
|
|
277
|
+
return True
|
|
278
|
+
|
|
279
|
+
def add_dict_cache(self,name,cached):
|
|
280
|
+
"""Add a loaded dictionary to this block's cache"""
|
|
281
|
+
self._import_cache[name]=cached
|
|
282
|
+
|
|
283
|
+
def follow_import(self,import_info,dataname):
|
|
284
|
+
"""Find the dataname values from the imported dictionary. `import_info`
|
|
285
|
+
is a list of import locations"""
|
|
286
|
+
latest_value = None
|
|
287
|
+
for import_ref in import_info:
|
|
288
|
+
file_loc = import_ref["file"]
|
|
289
|
+
if file_loc not in self._import_cache:
|
|
290
|
+
raise ValueError("Dictionary for import %s not found" % file_loc)
|
|
291
|
+
import_from = self._import_cache[file_loc]
|
|
292
|
+
miss = import_ref.get('miss','Exit')
|
|
293
|
+
target_key = import_ref["save"]
|
|
294
|
+
try:
|
|
295
|
+
import_target = import_from[target_key]
|
|
296
|
+
except KeyError:
|
|
297
|
+
if miss == 'Exit':
|
|
298
|
+
raise CifError('Import frame %s not found in %s' % (target_key,file_loc))
|
|
299
|
+
else: continue
|
|
300
|
+
# now import appropriately
|
|
301
|
+
mode = import_ref.get("mode",'Contents').lower()
|
|
302
|
+
if mode == "contents": #only this is used at this level
|
|
303
|
+
latest_value = import_target.get(dataname,latest_value)
|
|
304
|
+
return latest_value
|
|
305
|
+
|
|
306
|
+
class CifDic(StarFile.StarFile):
|
|
307
|
+
"""Create a Cif Dictionary object from the provided source, which can
|
|
308
|
+
be a filename/URL or a emapsCifFile. Optional arguments (relevant to DDLm
|
|
309
|
+
only):
|
|
310
|
+
|
|
311
|
+
* do_minimum (Boolean):
|
|
312
|
+
Do not set up the dREL system for auto-calculation or perform
|
|
313
|
+
imports. This implies do_imports=False and do_dREL=False
|
|
314
|
+
|
|
315
|
+
* do_imports = No/Full/Contents/All:
|
|
316
|
+
If not 'No', intepret _import.get statements for
|
|
317
|
+
Full mode/Contents mode/Both respectively. See also option 'heavy'
|
|
318
|
+
|
|
319
|
+
* do_dREL = True/False:
|
|
320
|
+
Parse and convert all dREL methods to Python. Implies do_imports=All
|
|
321
|
+
|
|
322
|
+
* heavy = True/False:
|
|
323
|
+
(Experimental). If True, importation overwrites definitions. If False,
|
|
324
|
+
attributes are resolved dynamically.
|
|
325
|
+
"""
|
|
326
|
+
def __init__(self,dic,do_minimum=False,do_imports='All', do_dREL=True,
|
|
327
|
+
grammar='auto',heavy=True,**kwargs):
|
|
328
|
+
self.do_minimum = do_minimum
|
|
329
|
+
if do_minimum:
|
|
330
|
+
do_imports = 'No'
|
|
331
|
+
do_dREL = False
|
|
332
|
+
if do_dREL: do_imports = 'All'
|
|
333
|
+
if heavy == 'Light' and do_imports not in ('contents','No'):
|
|
334
|
+
raise(ValueError,"Light imports only available for mode 'contents'")
|
|
335
|
+
self.template_cache = {} #for DDLm imports
|
|
336
|
+
self.ddlm_functions = {} #for DDLm functions
|
|
337
|
+
self.switch_numpy(False) #no Numpy arrays returned
|
|
338
|
+
super(CifDic,self).__init__(datasource=dic,grammar=grammar,blocktype=DicBlock,**kwargs)
|
|
339
|
+
self.standard = 'Dic' #for correct output order
|
|
340
|
+
self.scoping = 'dictionary'
|
|
341
|
+
(self.dicname,self.diclang) = self.dic_determine()
|
|
342
|
+
print('%s is a %s dictionary' % (self.dicname,self.diclang))
|
|
343
|
+
self.scopes_mandatory = {}
|
|
344
|
+
self.scopes_naughty = {}
|
|
345
|
+
self._import_dics = [] #Non-empty for DDLm only
|
|
346
|
+
# rename and expand out definitions using "_name" in DDL dictionaries
|
|
347
|
+
if self.diclang == "DDL1":
|
|
348
|
+
self.DDL1_normalise() #this removes any non-definition entries
|
|
349
|
+
self.create_def_block_table() #From now on, [] uses definition_id
|
|
350
|
+
if self.diclang == "DDL1":
|
|
351
|
+
self.ddl1_cat_load()
|
|
352
|
+
elif self.diclang == "DDL2":
|
|
353
|
+
self.DDL2_normalise() #iron out some DDL2 tricky bits
|
|
354
|
+
elif self.diclang == "DDLm":
|
|
355
|
+
self.scoping = 'dictionary' #expose all save frames
|
|
356
|
+
if do_imports != 'No':
|
|
357
|
+
self.obtain_imports(import_mode=do_imports,heavy=heavy)#recursively calls this routine
|
|
358
|
+
self.create_alias_table()
|
|
359
|
+
self.create_cat_obj_table()
|
|
360
|
+
self.create_cat_key_table()
|
|
361
|
+
if do_dREL:
|
|
362
|
+
print('Doing full dictionary initialisation')
|
|
363
|
+
self.initialise_drel()
|
|
364
|
+
self.add_category_info(full=do_dREL)
|
|
365
|
+
# initialise type information
|
|
366
|
+
self.typedic={}
|
|
367
|
+
self.primdic = {} #typecode<->primitive type translation
|
|
368
|
+
self.add_type_info()
|
|
369
|
+
self.install_validation_functions()
|
|
370
|
+
|
|
371
|
+
def dic_determine(self):
|
|
372
|
+
if "on_this_dictionary" in self:
|
|
373
|
+
self.master_block = super(CifDic,self).__getitem__("on_this_dictionary")
|
|
374
|
+
self.def_id_spec = "_name"
|
|
375
|
+
self.cat_id_spec = "_category.id" #we add this ourselves
|
|
376
|
+
self.type_spec = "_type"
|
|
377
|
+
self.enum_spec = "_enumeration"
|
|
378
|
+
self.cat_spec = "_category"
|
|
379
|
+
self.esd_spec = "_type_conditions"
|
|
380
|
+
self.must_loop_spec = "_list"
|
|
381
|
+
self.must_exist_spec = "_list_mandatory"
|
|
382
|
+
self.list_ref_spec = "_list_reference"
|
|
383
|
+
self.key_spec = "_list_mandatory"
|
|
384
|
+
self.unique_spec = "_list_uniqueness"
|
|
385
|
+
self.child_spec = "_list_link_child"
|
|
386
|
+
self.parent_spec = "_list_link_parent"
|
|
387
|
+
self.related_func = "_related_function"
|
|
388
|
+
self.related_item = "_related_item"
|
|
389
|
+
self.primitive_type = "_type"
|
|
390
|
+
self.dep_spec = "xxx"
|
|
391
|
+
self.cat_list = [] #to save searching all the time
|
|
392
|
+
name = super(CifDic,self).__getitem__("on_this_dictionary")["_dictionary_name"]
|
|
393
|
+
version = super(CifDic,self).__getitem__("on_this_dictionary")["_dictionary_version"]
|
|
394
|
+
return (name+version,"DDL1")
|
|
395
|
+
elif len(self.get_roots()) == 1: # DDL2/DDLm
|
|
396
|
+
self.master_block = super(CifDic,self).__getitem__(self.get_roots()[0][0])
|
|
397
|
+
# now change to dictionary scoping
|
|
398
|
+
self.scoping = 'dictionary'
|
|
399
|
+
name = self.master_block["_dictionary.title"]
|
|
400
|
+
version = self.master_block["_dictionary.version"]
|
|
401
|
+
if self.master_block.has_key("_dictionary.class"): #DDLm
|
|
402
|
+
self.enum_spec = '_enumeration_set.state'
|
|
403
|
+
self.key_spec = '_category.key_id'
|
|
404
|
+
self.must_exist_spec = None
|
|
405
|
+
self.cat_spec = '_name.category_id'
|
|
406
|
+
self.primitive_type = '_type.contents'
|
|
407
|
+
self.cat_id_spec = "_definition.id"
|
|
408
|
+
self.def_id_spec = "_definition.id"
|
|
409
|
+
return(name+version,"DDLm")
|
|
410
|
+
else: #DDL2
|
|
411
|
+
self.cat_id_spec = "_category.id"
|
|
412
|
+
self.def_id_spec = "_item.name"
|
|
413
|
+
self.key_spec = "_category_mandatory.name"
|
|
414
|
+
self.type_spec = "_item_type.code"
|
|
415
|
+
self.enum_spec = "_item_enumeration.value"
|
|
416
|
+
self.esd_spec = "_item_type_conditions.code"
|
|
417
|
+
self.cat_spec = "_item.category_id"
|
|
418
|
+
self.loop_spec = "there_is_no_loop_spec!"
|
|
419
|
+
self.must_loop_spec = "xxx"
|
|
420
|
+
self.must_exist_spec = "_item.mandatory_code"
|
|
421
|
+
self.child_spec = "_item_linked.child_name"
|
|
422
|
+
self.parent_spec = "_item_linked.parent_name"
|
|
423
|
+
self.related_func = "_item_related.function_code"
|
|
424
|
+
self.related_item = "_item_related.related_name"
|
|
425
|
+
self.unique_spec = "_category_key.name"
|
|
426
|
+
self.list_ref_spec = "xxx"
|
|
427
|
+
self.primitive_type = "_type"
|
|
428
|
+
self.dep_spec = "_item_dependent.dependent_name"
|
|
429
|
+
return (name+version,"DDL2")
|
|
430
|
+
else:
|
|
431
|
+
raise CifError("Unable to determine dictionary DDL version")
|
|
432
|
+
|
|
433
|
+
def DDL1_normalise(self):
|
|
434
|
+
# switch off block name collision checks
|
|
435
|
+
self.standard = None
|
|
436
|
+
# add default type information in DDL2 style
|
|
437
|
+
# initial types and constructs
|
|
438
|
+
base_types = ["char","numb","null"]
|
|
439
|
+
prim_types = base_types[:]
|
|
440
|
+
base_constructs = [".*",
|
|
441
|
+
'(-?(([0-9]*[.][0-9]+)|([0-9]+)[.]?)([(][0-9]+[)])?([eEdD][+-]?[0-9]+)?)|\\?|\\.',
|
|
442
|
+
"\"\" "]
|
|
443
|
+
for key,value in self.items():
|
|
444
|
+
newnames = [key] #keep by default
|
|
445
|
+
if "_name" in value:
|
|
446
|
+
real_name = value["_name"]
|
|
447
|
+
if isinstance(real_name,list): #looped values
|
|
448
|
+
for looped_name in real_name:
|
|
449
|
+
new_value = value.copy()
|
|
450
|
+
new_value["_name"] = looped_name #only looped name
|
|
451
|
+
self[looped_name] = new_value
|
|
452
|
+
newnames = real_name
|
|
453
|
+
else:
|
|
454
|
+
self[real_name] = value
|
|
455
|
+
newnames = [real_name]
|
|
456
|
+
# delete the old one
|
|
457
|
+
if key not in newnames:
|
|
458
|
+
del self[key]
|
|
459
|
+
# loop again to normalise the contents of each definition
|
|
460
|
+
for key,value in self.items():
|
|
461
|
+
#unlock the block
|
|
462
|
+
save_overwrite = value.overwrite
|
|
463
|
+
value.overwrite = True
|
|
464
|
+
# deal with a missing _list, _type_conditions
|
|
465
|
+
if "_list" not in value: value["_list"] = 'no'
|
|
466
|
+
if "_type_conditions" not in value: value["_type_conditions"] = 'none'
|
|
467
|
+
# deal with enumeration ranges
|
|
468
|
+
if "_enumeration_range" in value:
|
|
469
|
+
max,min = self.getmaxmin(value["_enumeration_range"])
|
|
470
|
+
if min == ".":
|
|
471
|
+
self[key].AddLoopItem((("_item_range.maximum","_item_range.minimum"),((max,max),(max,min))))
|
|
472
|
+
elif max == ".":
|
|
473
|
+
self[key].AddLoopItem((("_item_range.maximum","_item_range.minimum"),((max,min),(min,min))))
|
|
474
|
+
else:
|
|
475
|
+
self[key].AddLoopItem((("_item_range.maximum","_item_range.minimum"),((max,max,min),(max,min,min))))
|
|
476
|
+
#add any type construct information
|
|
477
|
+
if "_type_construct" in value:
|
|
478
|
+
base_types.append(value["_name"]+"_type") #ie dataname_type
|
|
479
|
+
base_constructs.append(value["_type_construct"]+"$")
|
|
480
|
+
prim_types.append(value["_type"]) #keep a record
|
|
481
|
+
value["_type"] = base_types[-1] #the new type name
|
|
482
|
+
|
|
483
|
+
#make categories conform with ddl2
|
|
484
|
+
#note that we must remove everything from the last underscore
|
|
485
|
+
if value.get("_category",None) == "category_overview":
|
|
486
|
+
last_under = value["_name"].rindex("_")
|
|
487
|
+
catid = value["_name"][1:last_under]
|
|
488
|
+
value["_category.id"] = catid #remove square bracks
|
|
489
|
+
if catid not in self.cat_list: self.cat_list.append(catid)
|
|
490
|
+
value.overwrite = save_overwrite
|
|
491
|
+
# we now add any missing categories before filling in the rest of the
|
|
492
|
+
# information
|
|
493
|
+
for key,value in self.items():
|
|
494
|
+
#print('processing ddl1 definition %s' % key)
|
|
495
|
+
if "_category" in self[key]:
|
|
496
|
+
if self[key]["_category"] not in self.cat_list:
|
|
497
|
+
# rogue category, add it in
|
|
498
|
+
newcat = self[key]["_category"]
|
|
499
|
+
fake_name = "_" + newcat + "_[]"
|
|
500
|
+
newcatdata = CifBlock()
|
|
501
|
+
newcatdata["_category"] = "category_overview"
|
|
502
|
+
newcatdata["_category.id"] = newcat
|
|
503
|
+
newcatdata["_type"] = "null"
|
|
504
|
+
self[fake_name] = newcatdata
|
|
505
|
+
self.cat_list.append(newcat)
|
|
506
|
+
# write out the type information in DDL2 style
|
|
507
|
+
self.master_block.AddLoopItem((
|
|
508
|
+
("_item_type_list.code","_item_type_list.construct",
|
|
509
|
+
"_item_type_list.primitive_code"),
|
|
510
|
+
(base_types,base_constructs,prim_types)
|
|
511
|
+
))
|
|
512
|
+
|
|
513
|
+
def ddl1_cat_load(self):
|
|
514
|
+
deflist = self.keys() #slight optimization
|
|
515
|
+
cat_mand_dic = {}
|
|
516
|
+
cat_unique_dic = {}
|
|
517
|
+
# a function to extract any necessary information from each definition
|
|
518
|
+
def get_cat_info(single_def):
|
|
519
|
+
if self[single_def].get(self.must_exist_spec)=='yes':
|
|
520
|
+
thiscat = self[single_def]["_category"]
|
|
521
|
+
curval = cat_mand_dic.get(thiscat,[])
|
|
522
|
+
curval.append(single_def)
|
|
523
|
+
cat_mand_dic[thiscat] = curval
|
|
524
|
+
# now the unique items...
|
|
525
|
+
# cif_core.dic throws us a curly one: the value of list_uniqueness is
|
|
526
|
+
# not the same as the defined item for publ_body_label, so we have
|
|
527
|
+
# to collect both together. We assume a non-listed entry, which
|
|
528
|
+
# is true for all current (May 2005) ddl1 dictionaries.
|
|
529
|
+
if self[single_def].get(self.unique_spec,None)!=None:
|
|
530
|
+
thiscat = self[single_def]["_category"]
|
|
531
|
+
new_unique = self[single_def][self.unique_spec]
|
|
532
|
+
uis = cat_unique_dic.get(thiscat,[])
|
|
533
|
+
if single_def not in uis: uis.append(single_def)
|
|
534
|
+
if new_unique not in uis: uis.append(new_unique)
|
|
535
|
+
cat_unique_dic[thiscat] = uis
|
|
536
|
+
|
|
537
|
+
[get_cat_info(a) for a in deflist] # apply the above function
|
|
538
|
+
for cat in cat_mand_dic.keys():
|
|
539
|
+
self[cat]["_category_mandatory.name"] = cat_mand_dic[cat]
|
|
540
|
+
for cat in cat_unique_dic.keys():
|
|
541
|
+
self[cat]["_category_key.name"] = cat_unique_dic[cat]
|
|
542
|
+
|
|
543
|
+
def create_pcloop(self,definition):
|
|
544
|
+
old_children = self[definition].get('_item_linked.child_name',[])
|
|
545
|
+
old_parents = self[definition].get('_item_linked.parent_name',[])
|
|
546
|
+
if isinstance(old_children,unicode):
|
|
547
|
+
old_children = [old_children]
|
|
548
|
+
if isinstance(old_parents,unicode):
|
|
549
|
+
old_parents = [old_parents]
|
|
550
|
+
if (len(old_children)==0 and len(old_parents)==0) or \
|
|
551
|
+
(len(old_children) > 1 and len(old_parents)>1):
|
|
552
|
+
return
|
|
553
|
+
if len(old_children)==0:
|
|
554
|
+
old_children = [definition]*len(old_parents)
|
|
555
|
+
if len(old_parents)==0:
|
|
556
|
+
old_parents = [definition]*len(old_children)
|
|
557
|
+
newloop = CifLoopBlock(dimension=1)
|
|
558
|
+
newloop.AddLoopItem(('_item_linked.parent_name',old_parents))
|
|
559
|
+
newloop.AddLoopItem(('_item_linked.child_name',old_children))
|
|
560
|
+
try:
|
|
561
|
+
del self[definition]['_item_linked.parent_name']
|
|
562
|
+
del self[definition]['_item_linked.child_name']
|
|
563
|
+
except KeyError:
|
|
564
|
+
pass
|
|
565
|
+
self[definition].insert_loop(newloop)
|
|
566
|
+
|
|
567
|
+
|
|
568
|
+
|
|
569
|
+
def DDL2_normalise(self):
|
|
570
|
+
listed_defs = filter(lambda a:isinstance(self[a].get('_item.name'),list),self.keys())
|
|
571
|
+
# now filter out all the single element lists!
|
|
572
|
+
dodgy_defs = filter(lambda a:len(self[a]['_item.name']) > 1, listed_defs)
|
|
573
|
+
for item_def in dodgy_defs:
|
|
574
|
+
# print("DDL2 norm: processing %s" % item_def)
|
|
575
|
+
thisdef = self[item_def]
|
|
576
|
+
packet_no = thisdef['_item.name'].index(item_def)
|
|
577
|
+
realcat = thisdef['_item.category_id'][packet_no]
|
|
578
|
+
realmand = thisdef['_item.mandatory_code'][packet_no]
|
|
579
|
+
# first add in all the missing categories
|
|
580
|
+
# we don't replace the entry in the list corresponding to the
|
|
581
|
+
# current item, as that would wipe out the information we want
|
|
582
|
+
for child_no in range(len(thisdef['_item.name'])):
|
|
583
|
+
if child_no == packet_no: continue
|
|
584
|
+
child_name = thisdef['_item.name'][child_no]
|
|
585
|
+
child_cat = thisdef['_item.category_id'][child_no]
|
|
586
|
+
child_mand = thisdef['_item.mandatory_code'][child_no]
|
|
587
|
+
if child_name not in self:
|
|
588
|
+
self[child_name] = CifBlock()
|
|
589
|
+
self[child_name]['_item.name'] = child_name
|
|
590
|
+
self[child_name]['_item.category_id'] = child_cat
|
|
591
|
+
self[child_name]['_item.mandatory_code'] = child_mand
|
|
592
|
+
self[item_def]['_item.name'] = item_def
|
|
593
|
+
self[item_def]['_item.category_id'] = realcat
|
|
594
|
+
self[item_def]['_item.mandatory_code'] = realmand
|
|
595
|
+
|
|
596
|
+
target_defs = [a for a in self.keys() if '_item_linked.child_name' in self[a] or \
|
|
597
|
+
'_item_linked.parent_name' in self[a]]
|
|
598
|
+
# now dodgy_defs contains all definition blocks with more than one child/parent link
|
|
599
|
+
for item_def in dodgy_defs: self.create_pcloop(item_def) #regularise appearance
|
|
600
|
+
for item_def in dodgy_defs:
|
|
601
|
+
print('Processing %s' % item_def)
|
|
602
|
+
thisdef = self[item_def]
|
|
603
|
+
child_list = thisdef['_item_linked.child_name']
|
|
604
|
+
parents = thisdef['_item_linked.parent_name']
|
|
605
|
+
# for each parent, find the list of children.
|
|
606
|
+
family = list(zip(parents,child_list))
|
|
607
|
+
notmychildren = family #We aim to remove non-children
|
|
608
|
+
# Loop over the parents, relocating as necessary
|
|
609
|
+
while len(notmychildren):
|
|
610
|
+
# get all children of first entry
|
|
611
|
+
mychildren = [a for a in family if a[0]==notmychildren[0][0]]
|
|
612
|
+
print("Parent %s: %d children" % (notmychildren[0][0],len(mychildren)))
|
|
613
|
+
for parent,child in mychildren: #parent is the same for all
|
|
614
|
+
# Make sure that we simply add in the new entry for the child, not replace it,
|
|
615
|
+
# otherwise we might spoil the child entry loop structure
|
|
616
|
+
try:
|
|
617
|
+
childloop = self[child].GetLoop('_item_linked.parent_name')
|
|
618
|
+
except KeyError:
|
|
619
|
+
print('Creating new parent entry %s for definition %s' % (parent,child))
|
|
620
|
+
self[child]['_item_linked.parent_name'] = [parent]
|
|
621
|
+
childloop = self[child].GetLoop('_item_linked.parent_name')
|
|
622
|
+
childloop.AddLoopItem(('_item_linked.child_name',[child]))
|
|
623
|
+
continue
|
|
624
|
+
else:
|
|
625
|
+
# A parent loop already exists and so will a child loop due to the
|
|
626
|
+
# call to create_pcloop above
|
|
627
|
+
pars = [a for a in childloop if getattr(a,'_item_linked.child_name','')==child]
|
|
628
|
+
goodpars = [a for a in pars if getattr(a,'_item_linked.parent_name','')==parent]
|
|
629
|
+
if len(goodpars)>0: #no need to add it
|
|
630
|
+
print('Skipping duplicated parent - child entry in %s: %s - %s' % (child,parent,child))
|
|
631
|
+
continue
|
|
632
|
+
print('Adding %s to %s entry' % (parent,child))
|
|
633
|
+
newpacket = childloop.GetPacket(0) #essentially a copy, I hope
|
|
634
|
+
setattr(newpacket,'_item_linked.child_name',child)
|
|
635
|
+
setattr(newpacket,'_item_linked.parent_name',parent)
|
|
636
|
+
childloop.AddPacket(newpacket)
|
|
637
|
+
#
|
|
638
|
+
# Make sure the parent also points to the children. We get
|
|
639
|
+
# the current entry, then add our
|
|
640
|
+
# new values if they are not there already
|
|
641
|
+
#
|
|
642
|
+
parent_name = mychildren[0][0]
|
|
643
|
+
old_children = self[parent_name].get('_item_linked.child_name',[])
|
|
644
|
+
old_parents = self[parent_name].get('_item_linked.parent_name',[])
|
|
645
|
+
oldfamily = zip(old_parents,old_children)
|
|
646
|
+
newfamily = []
|
|
647
|
+
print('Old parents -> %s' % repr(old_parents))
|
|
648
|
+
for jj, childname in mychildren:
|
|
649
|
+
alreadythere = [a for a in oldfamily if a[0]==parent_name and a[1] ==childname]
|
|
650
|
+
if len(alreadythere)>0: continue
|
|
651
|
+
'Adding new child %s to parent definition at %s' % (childname,parent_name)
|
|
652
|
+
old_children.append(childname)
|
|
653
|
+
old_parents.append(parent_name)
|
|
654
|
+
# Now output the loop, blowing away previous definitions. If there is something
|
|
655
|
+
# else in this category, we are destroying it.
|
|
656
|
+
newloop = CifLoopBlock(dimension=1)
|
|
657
|
+
newloop.AddLoopItem(('_item_linked.parent_name',old_parents))
|
|
658
|
+
newloop.AddLoopItem(('_item_linked.child_name',old_children))
|
|
659
|
+
del self[parent_name]['_item_linked.parent_name']
|
|
660
|
+
del self[parent_name]['_item_linked.child_name']
|
|
661
|
+
self[parent_name].insert_loop(newloop)
|
|
662
|
+
print('New parents -> %s' % repr(self[parent_name]['_item_linked.parent_name']))
|
|
663
|
+
# now make a new,smaller list
|
|
664
|
+
notmychildren = [a for a in notmychildren if a[0]!=mychildren[0][0]]
|
|
665
|
+
|
|
666
|
+
# now flatten any single element lists
|
|
667
|
+
single_defs = filter(lambda a:len(self[a]['_item.name'])==1,listed_defs)
|
|
668
|
+
for flat_def in single_defs:
|
|
669
|
+
flat_keys = self[flat_def].GetLoop('_item.name').keys()
|
|
670
|
+
for flat_key in flat_keys: self[flat_def][flat_key] = self[flat_def][flat_key][0]
|
|
671
|
+
# now deal with the multiple lists
|
|
672
|
+
# next we do aliases
|
|
673
|
+
all_aliases = [a for a in self.keys() if self[a].has_key('_item_aliases.alias_name')]
|
|
674
|
+
for aliased in all_aliases:
|
|
675
|
+
my_aliases = listify(self[aliased]['_item_aliases.alias_name'])
|
|
676
|
+
for alias in my_aliases:
|
|
677
|
+
self[alias] = self[aliased].copy() #we are going to delete stuff...
|
|
678
|
+
del self[alias]["_item_aliases.alias_name"]
|
|
679
|
+
|
|
680
|
+
def ddlm_parse_valid(self):
|
|
681
|
+
if "_dictionary_valid.application" not in self.master_block:
|
|
682
|
+
return
|
|
683
|
+
for scope_pack in self.master_block.GetLoop("_dictionary_valid.application"):
|
|
684
|
+
scope = getattr(scope_pack,"_dictionary_valid.application")
|
|
685
|
+
valid_info = getattr(scope_pack,"_dictionary_valid.attributes")
|
|
686
|
+
if scope[1] == "Mandatory":
|
|
687
|
+
self.scopes_mandatory[scope[0]] = self.expand_category_opt(valid_info)
|
|
688
|
+
elif scope[1] == "Prohibited":
|
|
689
|
+
self.scopes_naughty[scope[0]] = self.expand_category_opt(valid_info)
|
|
690
|
+
|
|
691
|
+
def obtain_imports(self,import_mode,heavy=False):
|
|
692
|
+
"""Collate import information"""
|
|
693
|
+
self._import_dics = []
|
|
694
|
+
import_frames = list([(a,self[a]['_import.get']) for a in self.keys() if '_import.get' in self[a]])
|
|
695
|
+
print('Import mode %s applied to following frames' % import_mode)
|
|
696
|
+
print(str([a[0] for a in import_frames]))
|
|
697
|
+
if import_mode != 'All':
|
|
698
|
+
for i in range(len(import_frames)):
|
|
699
|
+
import_frames[i] = (import_frames[i][0],[a for a in import_frames[i][1] if a.get('mode','Contents').lower() == import_mode.lower()])
|
|
700
|
+
print('Importing following frames in mode %s' % import_mode)
|
|
701
|
+
print(str(import_frames))
|
|
702
|
+
#resolve all references
|
|
703
|
+
for parent_block,import_list in import_frames:
|
|
704
|
+
for import_ref in import_list:
|
|
705
|
+
file_loc = import_ref["file"]
|
|
706
|
+
full_uri = self.resolve_path(file_loc)
|
|
707
|
+
if full_uri not in self.template_cache:
|
|
708
|
+
dic_as_cif = CifFile(full_uri,grammar=self.grammar)
|
|
709
|
+
self.template_cache[full_uri] = CifDic(dic_as_cif,do_imports=import_mode,heavy=heavy,do_dREL=False) #this will recurse internal imports
|
|
710
|
+
print('Added %s to cached dictionaries' % full_uri)
|
|
711
|
+
import_from = self.template_cache[full_uri]
|
|
712
|
+
dupl = import_ref.get('dupl','Exit')
|
|
713
|
+
miss = import_ref.get('miss','Exit')
|
|
714
|
+
target_key = import_ref["save"]
|
|
715
|
+
try:
|
|
716
|
+
import_target = import_from[target_key]
|
|
717
|
+
except KeyError:
|
|
718
|
+
if miss == 'Exit':
|
|
719
|
+
raise CifError('Import frame %s not found in %s' % (target_key,full_uri))
|
|
720
|
+
else: continue
|
|
721
|
+
# now import appropriately
|
|
722
|
+
mode = import_ref.get("mode",'Contents').lower()
|
|
723
|
+
if target_key in self and mode=='full': #so blockname will be duplicated
|
|
724
|
+
if dupl == 'Exit':
|
|
725
|
+
raise CifError('Import frame %s already in dictionary' % target_key)
|
|
726
|
+
elif dupl == 'Ignore':
|
|
727
|
+
continue
|
|
728
|
+
if heavy:
|
|
729
|
+
self.ddlm_import(parent_block,import_from,import_target,target_key,mode)
|
|
730
|
+
else:
|
|
731
|
+
self.ddlm_import_light(parent_block,import_from,import_target,target_key,file_loc,mode)
|
|
732
|
+
|
|
733
|
+
def ddlm_import(self,parent_block,import_from,import_target,target_key,mode='All'):
|
|
734
|
+
"""Import other dictionaries in place"""
|
|
735
|
+
if mode == 'contents': #merge attributes only
|
|
736
|
+
self[parent_block].merge(import_target)
|
|
737
|
+
elif mode =="full":
|
|
738
|
+
# Do the syntactic merge
|
|
739
|
+
syntactic_head = self[self.get_parent(parent_block)] #root frame if no nesting
|
|
740
|
+
from_cat_head = import_target['_name.object_id']
|
|
741
|
+
child_frames = import_from.ddlm_all_children(from_cat_head)
|
|
742
|
+
# Check for Head merging Head
|
|
743
|
+
if self[parent_block].get('_definition.class','Datum')=='Head' and \
|
|
744
|
+
import_target.get('_definition.class','Datum')=='Head':
|
|
745
|
+
head_to_head = True
|
|
746
|
+
else:
|
|
747
|
+
head_to_head = False
|
|
748
|
+
child_frames.remove(from_cat_head)
|
|
749
|
+
# As we are in syntax land, we call the CifFile methods
|
|
750
|
+
child_blocks = list([import_from.block_id_table[a.lower()] for a in child_frames])
|
|
751
|
+
child_blocks = super(CifDic,import_from).makebc(child_blocks)
|
|
752
|
+
# Prune out any datablocks that have identical definitions
|
|
753
|
+
from_defs = dict([(a,child_blocks[a].get('_definition.id','').lower()) for a in child_blocks.keys()])
|
|
754
|
+
double_defs = list([b for b in from_defs.items() if self.has_key(b[1])])
|
|
755
|
+
print('Definitions for %s superseded' % repr(double_defs))
|
|
756
|
+
for b in double_defs:
|
|
757
|
+
del child_blocks[b[0]]
|
|
758
|
+
super(CifDic,self).merge_fast(child_blocks,parent=syntactic_head) #
|
|
759
|
+
print('Syntactic merge of %s (%d defs) in %s mode, now have %d defs' % (target_key,len(child_frames),
|
|
760
|
+
mode,len(self)))
|
|
761
|
+
# Now the semantic merge
|
|
762
|
+
# First expand our definition <-> blockname tree
|
|
763
|
+
self.create_def_block_table()
|
|
764
|
+
merging_cat = self[parent_block]['_name.object_id'] #new parent
|
|
765
|
+
if head_to_head:
|
|
766
|
+
child_frames = self.ddlm_immediate_children(from_cat_head) #old children
|
|
767
|
+
#the new parent is the importing category for all old children
|
|
768
|
+
for f in child_frames:
|
|
769
|
+
self[f].overwrite = True
|
|
770
|
+
self[f]['_name.category_id'] = merging_cat
|
|
771
|
+
self[f].overwrite = False
|
|
772
|
+
# remove the old head
|
|
773
|
+
del self[from_cat_head]
|
|
774
|
+
print('Semantic merge: %d defs reparented from %s to %s' % (len(child_frames),from_cat_head,merging_cat))
|
|
775
|
+
else: #imported category is only child
|
|
776
|
+
from_frame = import_from[target_key]['_definition.id'] #so we can find it
|
|
777
|
+
child_frame = [d for d in self.keys() if self[d]['_definition.id']==from_frame][0]
|
|
778
|
+
self[child_frame]['_name.category_id'] = merging_cat
|
|
779
|
+
print('Semantic merge: category for %s : now %s' % (from_frame,merging_cat))
|
|
780
|
+
# it will never happen again...
|
|
781
|
+
del self[parent_block]["_import.get"]
|
|
782
|
+
|
|
783
|
+
def resolve_path(self,file_loc):
|
|
784
|
+
url_comps = urlparse(file_loc)
|
|
785
|
+
if url_comps[0]: return file_loc #already full URI
|
|
786
|
+
new_url = urljoin(self.my_uri,file_loc)
|
|
787
|
+
#print("Transformed %s to %s for import " % (file_loc,new_url))
|
|
788
|
+
return new_url
|
|
789
|
+
|
|
790
|
+
def ddlm_import_light(self,parent_block,import_from,import_target,target_key,file_loc,mode='All'):
|
|
791
|
+
"""Register the imported dictionaries but do not alter any definitions. `parent_block`
|
|
792
|
+
contains the id of the block that is importing. `import_target` is the block that
|
|
793
|
+
should be imported. `import_from` is the CifFile that contains the definitions."""
|
|
794
|
+
if mode == 'contents': #merge attributes only
|
|
795
|
+
self[parent_block].add_dict_cache(file_loc,import_from)
|
|
796
|
+
elif mode =="full":
|
|
797
|
+
# Check for Head merging Head
|
|
798
|
+
if self[parent_block].get('_definition.class','Datum')=='Head' and \
|
|
799
|
+
import_target.get('_definition.class','Datum')=='Head':
|
|
800
|
+
head_to_head = True
|
|
801
|
+
else:
|
|
802
|
+
head_to_head = False
|
|
803
|
+
# Figure out the actual definition ID
|
|
804
|
+
head_id = import_target["_definition.id"]
|
|
805
|
+
# Adjust parent information
|
|
806
|
+
merging_cat = self[parent_block]['_name.object_id']
|
|
807
|
+
from_cat_head = import_target['_name.object_id']
|
|
808
|
+
if not head_to_head: # imported category is only child
|
|
809
|
+
import_target["_name.category_id"]=merging_cat
|
|
810
|
+
self._import_dics = [(import_from,head_id)]+self._import_dics #prepend
|
|
811
|
+
|
|
812
|
+
def lookup_imports(self,key):
|
|
813
|
+
"""Check the list of imported dictionaries for this definition"""
|
|
814
|
+
for one_dic,head_def in self._import_dics:
|
|
815
|
+
from_cat_head = one_dic[head_def]['_name.object_id']
|
|
816
|
+
possible_keys = one_dic.ddlm_all_children(from_cat_head)
|
|
817
|
+
if key in possible_keys:
|
|
818
|
+
return one_dic[key]
|
|
819
|
+
raise KeyError("%s not found in import dictionaries" % key)
|
|
820
|
+
|
|
821
|
+
|
|
822
|
+
|
|
823
|
+
def create_def_block_table(self):
|
|
824
|
+
""" Create an internal table matching definition to block id """
|
|
825
|
+
proto_table = [(super(CifDic,self).__getitem__(a),a) for a in super(CifDic,self).keys()]
|
|
826
|
+
# now get the actual ids instead of blocks
|
|
827
|
+
proto_table = list([(a[0].get(self.cat_id_spec,a[0].get(self.def_id_spec,a[1])),a[1]) for a in proto_table])
|
|
828
|
+
# remove non-definitions
|
|
829
|
+
if self.diclang != "DDL1":
|
|
830
|
+
top_blocks = list([a[0].lower() for a in self.get_roots()])
|
|
831
|
+
else:
|
|
832
|
+
top_blocks = ["on_this_dictionary"]
|
|
833
|
+
# catch dodgy duplicates
|
|
834
|
+
uniques = set([a[0] for a in proto_table])
|
|
835
|
+
if len(uniques)<len(proto_table):
|
|
836
|
+
def_names = list([a[0] for a in proto_table])
|
|
837
|
+
dodgy = [a for a in def_names if def_names.count(a)>1]
|
|
838
|
+
raise CifError('Duplicate definitions in dictionary:' + repr(dodgy))
|
|
839
|
+
self.block_id_table = dict([(a[0].lower(),a[1].lower()) for a in proto_table if a[1].lower() not in top_blocks])
|
|
840
|
+
|
|
841
|
+
def __getitem__(self,key):
|
|
842
|
+
"""Access a datablock by definition id, after the lookup has been created"""
|
|
843
|
+
try:
|
|
844
|
+
return super(CifDic,self).__getitem__(self.block_id_table[key.lower()])
|
|
845
|
+
except AttributeError: #block_id_table not present yet
|
|
846
|
+
return super(CifDic,self).__getitem__(key)
|
|
847
|
+
except KeyError: # key is missing
|
|
848
|
+
try: # print(Definition for %s not found, reverting to CifFile' % key)
|
|
849
|
+
return super(CifDic,self).__getitem__(key)
|
|
850
|
+
except KeyError: # try imports
|
|
851
|
+
return self.lookup_imports(key)
|
|
852
|
+
|
|
853
|
+
def __setitem__(self,key,value):
|
|
854
|
+
"""Add a new definition block"""
|
|
855
|
+
super(CifDic,self).__setitem__(key,value)
|
|
856
|
+
try:
|
|
857
|
+
self.block_id_table[value['_definition.id']]=key
|
|
858
|
+
except AttributeError: #does not exist yet
|
|
859
|
+
pass
|
|
860
|
+
|
|
861
|
+
def NewBlock(self,*args,**kwargs):
|
|
862
|
+
newname = super(CifDic,self).NewBlock(*args,**kwargs)
|
|
863
|
+
try:
|
|
864
|
+
self.block_id_table[self[newname]['_definition.id']]=newname
|
|
865
|
+
except AttributeError: #no block_id table
|
|
866
|
+
pass
|
|
867
|
+
|
|
868
|
+
def __delitem__(self,key):
|
|
869
|
+
"""Remove a definition"""
|
|
870
|
+
try:
|
|
871
|
+
super(CifDic,self).__delitem__(self.block_id_table[key.lower()])
|
|
872
|
+
del self.block_id_table[key.lower()]
|
|
873
|
+
except (AttributeError,KeyError): #block_id_table not present yet
|
|
874
|
+
super(CifDic,self).__delitem__(key)
|
|
875
|
+
return
|
|
876
|
+
# fix other datastructures
|
|
877
|
+
# cat_obj table
|
|
878
|
+
|
|
879
|
+
def keys(self):
|
|
880
|
+
"""Return all definitions"""
|
|
881
|
+
try:
|
|
882
|
+
return self.block_id_table.keys()
|
|
883
|
+
except AttributeError:
|
|
884
|
+
return super(CifDic,self).keys()
|
|
885
|
+
|
|
886
|
+
def has_key(self,key):
|
|
887
|
+
return key in self
|
|
888
|
+
|
|
889
|
+
def __contains__(self,key):
|
|
890
|
+
try:
|
|
891
|
+
return key.lower() in self.block_id_table
|
|
892
|
+
except AttributeError:
|
|
893
|
+
return super(CifDic,self).__contains__(key)
|
|
894
|
+
|
|
895
|
+
def items(self):
|
|
896
|
+
"""Return (key,value) pairs"""
|
|
897
|
+
return list([(a,self[a]) for a in self.keys()])
|
|
898
|
+
|
|
899
|
+
def unlock(self):
|
|
900
|
+
"""Allow overwriting of all definitions in this collection"""
|
|
901
|
+
for a in self.keys():
|
|
902
|
+
self[a].overwrite=True
|
|
903
|
+
|
|
904
|
+
def lock(self):
|
|
905
|
+
"""Disallow changes in definitions"""
|
|
906
|
+
for a in self.keys():
|
|
907
|
+
self[a].overwrite=False
|
|
908
|
+
|
|
909
|
+
def rename(self,oldname,newname,blockname_as_well=True):
|
|
910
|
+
"""Change a _definition.id from oldname to newname, and if `blockname_as_well` is True,
|
|
911
|
+
change the underlying blockname too."""
|
|
912
|
+
if blockname_as_well:
|
|
913
|
+
super(CifDic,self).rename(self.block_id_table[oldname.lower()],newname)
|
|
914
|
+
self.block_id_table[newname.lower()]=newname
|
|
915
|
+
if oldname.lower() in self.block_id_table: #not removed
|
|
916
|
+
del self.block_id_table[oldname.lower()]
|
|
917
|
+
else:
|
|
918
|
+
self.block_id_table[newname.lower()]=self.block_id_table[oldname.lower()]
|
|
919
|
+
del self.block_id_table[oldname.lower()]
|
|
920
|
+
return
|
|
921
|
+
|
|
922
|
+
def get_root_category(self):
|
|
923
|
+
"""Get the single 'Head' category of this dictionary"""
|
|
924
|
+
root_cats = [r for r in self.keys() if self[r].get('_definition.class','Datum')=='Head']
|
|
925
|
+
if len(root_cats)>1 or len(root_cats)==0:
|
|
926
|
+
raise CifError("Cannot determine a unique Head category, got" % repr(root_cats))
|
|
927
|
+
return root_cats[0]
|
|
928
|
+
|
|
929
|
+
def ddlm_immediate_children(self,catname):
|
|
930
|
+
"""Return a list of datanames for the immediate children of catname. These are
|
|
931
|
+
semantic children (i.e. based on _name.category_id), not structural children as
|
|
932
|
+
in the case of StarFile.get_immediate_children"""
|
|
933
|
+
|
|
934
|
+
straight_children = [a for a in self.keys() if self[a].get('_name.category_id','').lower() == catname.lower()]
|
|
935
|
+
return list(straight_children)
|
|
936
|
+
|
|
937
|
+
def ddlm_all_children(self,catname):
|
|
938
|
+
"""Return a list of all children, including the `catname`"""
|
|
939
|
+
all_children = self.ddlm_immediate_children(catname)
|
|
940
|
+
cat_children = [a for a in all_children if self[a].get('_definition.scope','Item') == 'Category']
|
|
941
|
+
for c in cat_children:
|
|
942
|
+
all_children.remove(c)
|
|
943
|
+
all_children += self.ddlm_all_children(c)
|
|
944
|
+
return all_children + [catname]
|
|
945
|
+
|
|
946
|
+
def is_semantic_child(self,parent,maybe_child):
|
|
947
|
+
"""Return true if `maybe_child` is a child of `parent`"""
|
|
948
|
+
all_children = self.ddlm_all_children(parent)
|
|
949
|
+
return maybe_child in all_children
|
|
950
|
+
|
|
951
|
+
def ddlm_danglers(self):
|
|
952
|
+
"""Return a list of definitions that do not have a category defined
|
|
953
|
+
for them, or are children of an unattached category"""
|
|
954
|
+
top_block = self.get_root_category()
|
|
955
|
+
connected = set(self.ddlm_all_children(top_block))
|
|
956
|
+
all_keys = set(self.keys())
|
|
957
|
+
unconnected = all_keys - connected
|
|
958
|
+
return list(unconnected)
|
|
959
|
+
|
|
960
|
+
def get_ddlm_parent(self,itemname):
|
|
961
|
+
"""Get the parent category of itemname"""
|
|
962
|
+
parent = self[itemname].get('_name.category_id','')
|
|
963
|
+
if parent == '': # use the top block by default
|
|
964
|
+
raise CifError("%s has no parent" % itemname)
|
|
965
|
+
return parent
|
|
966
|
+
|
|
967
|
+
def expand_category_opt(self,name_list):
|
|
968
|
+
"""Return a list of all non-category items in a category or return the name
|
|
969
|
+
if the name is not a category"""
|
|
970
|
+
new_list = []
|
|
971
|
+
for name in name_list:
|
|
972
|
+
if self.get(name,{}).get('_definition.scope','Item') == 'Category':
|
|
973
|
+
new_list += self.expand_category_opt([a for a in self.keys() if \
|
|
974
|
+
self[a].get('_name.category_id','').lower() == name.lower()])
|
|
975
|
+
else:
|
|
976
|
+
new_list.append(name)
|
|
977
|
+
return new_list
|
|
978
|
+
|
|
979
|
+
def get_categories(self):
|
|
980
|
+
"""Return a list of category names"""
|
|
981
|
+
return list([c for c in self.keys() if self[c].get("_definition.scope")=='Category'])
|
|
982
|
+
|
|
983
|
+
def names_in_cat(self,cat,names_only=False):
|
|
984
|
+
names = [a for a in self.keys() if self[a].get('_name.category_id','').lower()==cat.lower()]
|
|
985
|
+
if not names_only:
|
|
986
|
+
return list([a for a in names if self[a].get('_definition.scope','Item')=='Item'])
|
|
987
|
+
else:
|
|
988
|
+
return list([self[a]["_name.object_id"] for a in names])
|
|
989
|
+
|
|
990
|
+
|
|
991
|
+
|
|
992
|
+
def create_alias_table(self):
|
|
993
|
+
"""Populate an alias table that we can look up when searching for a dataname"""
|
|
994
|
+
all_aliases = [a for a in self.keys() if '_alias.definition_id' in self[a]]
|
|
995
|
+
self.alias_table = dict([[a,self[a]['_alias.definition_id']] for a in all_aliases])
|
|
996
|
+
|
|
997
|
+
def create_cat_obj_table(self):
|
|
998
|
+
"""Populate a table indexed by (cat,obj) and returning the correct dataname"""
|
|
999
|
+
base_table = dict([((self[a].get('_name.category_id','').lower(),self[a].get('_name.object_id','').lower()),[self[a].get('_definition.id','')]) \
|
|
1000
|
+
for a in self.keys() if self[a].get('_definition.scope','Item')=='Item'])
|
|
1001
|
+
loopable = self.get_loopable_cats()
|
|
1002
|
+
loopers = [self.ddlm_immediate_children(a) for a in loopable]
|
|
1003
|
+
print('Loopable cats:' + repr(loopable))
|
|
1004
|
+
loop_children = [[b for b in a if b.lower() in loopable ] for a in loopers]
|
|
1005
|
+
expand_list = dict([(a,b) for a,b in zip(loopable,loop_children) if len(b)>0])
|
|
1006
|
+
print("Expansion list:" + repr(expand_list))
|
|
1007
|
+
extra_table = {} #for debugging we keep it separate from base_table until the end
|
|
1008
|
+
def expand_base_table(parent_cat,child_cats):
|
|
1009
|
+
extra_names = []
|
|
1010
|
+
# first deal with all the child categories
|
|
1011
|
+
for child_cat in child_cats:
|
|
1012
|
+
nn = []
|
|
1013
|
+
if child_cat in expand_list: # a nested category: grab its names
|
|
1014
|
+
nn = expand_base_table(child_cat,expand_list[child_cat])
|
|
1015
|
+
# store child names
|
|
1016
|
+
extra_names += nn
|
|
1017
|
+
# add all child names to the table
|
|
1018
|
+
child_names = [(self[n]['_name.object_id'].lower(),self[n]['_definition.id']) \
|
|
1019
|
+
for n in self.names_in_cat(child_cat) if self[n].get('_type.purpose','') != 'Key']
|
|
1020
|
+
child_names += extra_names
|
|
1021
|
+
extra_table.update(dict([((parent_cat,obj),[name]) for obj,name in child_names if (parent_cat,name) not in extra_table]))
|
|
1022
|
+
# and the repeated ones get appended instead
|
|
1023
|
+
repeats = [a for a in child_names if a in extra_table]
|
|
1024
|
+
for obj,name in repeats:
|
|
1025
|
+
extra_table[(parent_cat,obj)] += [name]
|
|
1026
|
+
# and finally, add our own names to the return list
|
|
1027
|
+
child_names += [(self[n]['_name.object_id'].lower(),self[n]['_definition.id']) \
|
|
1028
|
+
for n in self.names_in_cat(parent_cat) if self[n].get('_type.purpose','')!='Key']
|
|
1029
|
+
return child_names
|
|
1030
|
+
[expand_base_table(parent,child) for parent,child in expand_list.items()]
|
|
1031
|
+
print('Expansion cat/obj values: ' + repr(extra_table))
|
|
1032
|
+
# append repeated ones
|
|
1033
|
+
non_repeats = dict([a for a in extra_table.items() if a[0] not in base_table])
|
|
1034
|
+
repeats = [a for a in extra_table.keys() if a in base_table]
|
|
1035
|
+
base_table.update(non_repeats)
|
|
1036
|
+
for k in repeats:
|
|
1037
|
+
base_table[k] += extra_table[k]
|
|
1038
|
+
self.cat_obj_lookup_table = base_table
|
|
1039
|
+
self.loop_expand_list = expand_list
|
|
1040
|
+
|
|
1041
|
+
def get_loopable_cats(self):
|
|
1042
|
+
"""A short utility function which returns a list of looped categories. This
|
|
1043
|
+
is preferred to a fixed attribute as that fixed attribute would need to be
|
|
1044
|
+
updated after any edits"""
|
|
1045
|
+
return [a.lower() for a in self.keys() if self[a].get('_definition.class','')=='Loop']
|
|
1046
|
+
|
|
1047
|
+
def create_cat_key_table(self):
|
|
1048
|
+
"""Create a utility table with a list of keys applicable to each category. A key is
|
|
1049
|
+
a compound key, that is, it is a list"""
|
|
1050
|
+
self.cat_key_table = dict([(c,[listify(self[c].get("_category_key.name",
|
|
1051
|
+
[self[c].get("_category.key_id")]))]) for c in self.get_loopable_cats()])
|
|
1052
|
+
def collect_keys(parent_cat,child_cats):
|
|
1053
|
+
kk = []
|
|
1054
|
+
for child_cat in child_cats:
|
|
1055
|
+
if child_cat in self.loop_expand_list:
|
|
1056
|
+
kk += collect_keys(child_cat)
|
|
1057
|
+
# add these keys to our list
|
|
1058
|
+
kk += [listify(self[child_cat].get('_category_key.name',[self[child_cat].get('_category.key_id')]))]
|
|
1059
|
+
self.cat_key_table[parent_cat] = self.cat_key_table[parent_cat] + kk
|
|
1060
|
+
return kk
|
|
1061
|
+
for k,v in self.loop_expand_list.items():
|
|
1062
|
+
collect_keys(k,v)
|
|
1063
|
+
print('Keys for categories' + repr(self.cat_key_table))
|
|
1064
|
+
|
|
1065
|
+
def add_type_info(self):
|
|
1066
|
+
if "_item_type_list.construct" in self.master_block:
|
|
1067
|
+
types = self.master_block["_item_type_list.code"]
|
|
1068
|
+
prim_types = self.master_block["_item_type_list.primitive_code"]
|
|
1069
|
+
constructs = list([a + "$" for a in self.master_block["_item_type_list.construct"]])
|
|
1070
|
+
# add in \r wherever we see \n, and change \{ to \\{
|
|
1071
|
+
def regex_fiddle(mm_regex):
|
|
1072
|
+
brack_match = r"((.*\[.+)(\\{)(.*\].*))"
|
|
1073
|
+
ret_match = r"((.*\[.+)(\\n)(.*\].*))"
|
|
1074
|
+
fixed_regexp = mm_regex[:] #copy
|
|
1075
|
+
# fix the brackets
|
|
1076
|
+
bm = re.match(brack_match,mm_regex)
|
|
1077
|
+
if bm != None:
|
|
1078
|
+
fixed_regexp = bm.expand(r"\2\\\\{\4")
|
|
1079
|
+
# fix missing \r
|
|
1080
|
+
rm = re.match(ret_match,fixed_regexp)
|
|
1081
|
+
if rm != None:
|
|
1082
|
+
fixed_regexp = rm.expand(r"\2\3\\r\4")
|
|
1083
|
+
#print("Regexp %s becomes %s" % (mm_regex,fixed_regexp))
|
|
1084
|
+
return fixed_regexp
|
|
1085
|
+
constructs = map(regex_fiddle,constructs)
|
|
1086
|
+
for typecode,construct in zip(types,constructs):
|
|
1087
|
+
self.typedic[typecode] = re.compile(construct,re.MULTILINE|re.DOTALL)
|
|
1088
|
+
# now make a primitive <-> type construct mapping
|
|
1089
|
+
for typecode,primtype in zip(types,prim_types):
|
|
1090
|
+
self.primdic[typecode] = primtype
|
|
1091
|
+
|
|
1092
|
+
def add_category_info(self,full=True):
|
|
1093
|
+
if self.diclang == "DDLm":
|
|
1094
|
+
catblocks = [c for c in self.keys() if self[c].get('_definition.scope')=='Category']
|
|
1095
|
+
looped_cats = [a for a in catblocks if self[a].get('_definition.class','Set') == 'Loop']
|
|
1096
|
+
self.parent_lookup = {}
|
|
1097
|
+
for one_cat in looped_cats:
|
|
1098
|
+
parent_cat = one_cat
|
|
1099
|
+
parent_def = self[parent_cat]
|
|
1100
|
+
next_up = parent_def['_name.category_id'].lower()
|
|
1101
|
+
while next_up in self and self[next_up].get('_definition.class','Set') == 'Loop':
|
|
1102
|
+
parent_def = self[next_up]
|
|
1103
|
+
parent_cat = next_up
|
|
1104
|
+
next_up = parent_def['_name.category_id'].lower()
|
|
1105
|
+
self.parent_lookup[one_cat] = parent_cat
|
|
1106
|
+
|
|
1107
|
+
if full:
|
|
1108
|
+
self.key_equivs = {}
|
|
1109
|
+
for one_cat in looped_cats: #follow them up
|
|
1110
|
+
lower_keys = listify(self[one_cat]['_category_key.name'])
|
|
1111
|
+
start_keys = lower_keys[:]
|
|
1112
|
+
while len(lower_keys)>0:
|
|
1113
|
+
this_cat = self[lower_keys[0]]['_name.category_id']
|
|
1114
|
+
parent = [a for a in looped_cats if self[this_cat]['_name.category_id'].lower()==a]
|
|
1115
|
+
#print(Processing %s, keys %s, parent %s" % (this_cat,repr(lower_keys),repr(parent)))
|
|
1116
|
+
if len(parent)>1:
|
|
1117
|
+
raise CifError("Category %s has more than one parent: %s" % (one_cat,repr(parent)))
|
|
1118
|
+
if len(parent)==0: break
|
|
1119
|
+
parent = parent[0]
|
|
1120
|
+
parent_keys = listify(self[parent]['_category_key.name'])
|
|
1121
|
+
linked_keys = [self[l]["_name.linked_item_id"] for l in lower_keys]
|
|
1122
|
+
# sanity check
|
|
1123
|
+
if set(parent_keys) != set(linked_keys):
|
|
1124
|
+
raise CifError("Parent keys and linked keys are different! %s/%s" % (parent_keys,linked_keys))
|
|
1125
|
+
# now add in our information
|
|
1126
|
+
for parent,child in zip(linked_keys,start_keys):
|
|
1127
|
+
self.key_equivs[child] = self.key_equivs.get(child,[])+[parent]
|
|
1128
|
+
lower_keys = linked_keys #preserves order of start keys
|
|
1129
|
+
|
|
1130
|
+
else:
|
|
1131
|
+
self.parent_lookup = {}
|
|
1132
|
+
self.key_equivs = {}
|
|
1133
|
+
|
|
1134
|
+
def change_category_name(self,oldname,newname):
|
|
1135
|
+
self.unlock()
|
|
1136
|
+
"""Change the category name from [[oldname]] to [[newname]]"""
|
|
1137
|
+
if oldname not in self:
|
|
1138
|
+
raise KeyError('Cannot rename non-existent category %s to %s' % (oldname,newname))
|
|
1139
|
+
if newname in self:
|
|
1140
|
+
raise KeyError('Cannot rename %s to %s as %s already exists' % (oldname,newname,oldname))
|
|
1141
|
+
child_defs = self.ddlm_immediate_children(oldname)
|
|
1142
|
+
self.rename(oldname,newname) #NB no name integrity checks
|
|
1143
|
+
self[newname]['_name.object_id']=newname
|
|
1144
|
+
self[newname]['_definition.id']=newname
|
|
1145
|
+
for child_def in child_defs:
|
|
1146
|
+
self[child_def]['_name.category_id'] = newname
|
|
1147
|
+
if self[child_def].get('_definition.scope','Item')=='Item':
|
|
1148
|
+
newid = self.create_catobj_name(newname,self[child_def]['_name.object_id'])
|
|
1149
|
+
self[child_def]['_definition.id']=newid
|
|
1150
|
+
self.rename(child_def,newid[1:]) #no underscore at the beginning
|
|
1151
|
+
self.lock()
|
|
1152
|
+
|
|
1153
|
+
def create_catobj_name(self,cat,obj):
|
|
1154
|
+
"""Combine category and object in approved fashion to create id"""
|
|
1155
|
+
return ('_'+cat+'.'+obj)
|
|
1156
|
+
|
|
1157
|
+
def change_category(self,itemname,catname):
|
|
1158
|
+
"""Move itemname into catname, return new handle"""
|
|
1159
|
+
defid = self[itemname]
|
|
1160
|
+
if defid['_name.category_id'].lower()==catname.lower():
|
|
1161
|
+
print('Already in category, no change')
|
|
1162
|
+
return itemname
|
|
1163
|
+
if catname not in self: #don't have it
|
|
1164
|
+
print('No such category %s' % catname)
|
|
1165
|
+
return itemname
|
|
1166
|
+
self.unlock()
|
|
1167
|
+
objid = defid['_name.object_id']
|
|
1168
|
+
defid['_name.category_id'] = catname
|
|
1169
|
+
newid = itemname # stays the same for categories
|
|
1170
|
+
if defid.get('_definition.scope','Item') == 'Item':
|
|
1171
|
+
newid = self.create_catobj_name(catname,objid)
|
|
1172
|
+
defid['_definition.id']= newid
|
|
1173
|
+
self.rename(itemname,newid)
|
|
1174
|
+
self.set_parent(catname,newid)
|
|
1175
|
+
self.lock()
|
|
1176
|
+
return newid
|
|
1177
|
+
|
|
1178
|
+
def change_name(self,one_def,newobj):
|
|
1179
|
+
"""Change the object_id of one_def to newobj. This is not used for
|
|
1180
|
+
categories, but can be used for dictionaries"""
|
|
1181
|
+
if '_dictionary.title' not in self[one_def]: #a dictionary block
|
|
1182
|
+
newid = self.create_catobj_name(self[one_def]['_name.category_id'],newobj)
|
|
1183
|
+
self.unlock()
|
|
1184
|
+
self.rename(one_def,newid)
|
|
1185
|
+
self[newid]['_definition.id']=newid
|
|
1186
|
+
self[newid]['_name.object_id']=newobj
|
|
1187
|
+
else:
|
|
1188
|
+
self.unlock()
|
|
1189
|
+
newid = newobj
|
|
1190
|
+
self.rename(one_def,newobj)
|
|
1191
|
+
self[newid]['_dictionary.title'] = newid
|
|
1192
|
+
self.lock()
|
|
1193
|
+
return newid
|
|
1194
|
+
|
|
1195
|
+
# Note that our semantic parent is given by catparent, but our syntactic parent is
|
|
1196
|
+
# always just the root block
|
|
1197
|
+
def add_category(self,catname,catparent=None,is_loop=True,allow_dangler=False):
|
|
1198
|
+
"""Add a new category to the dictionary with name [[catname]].
|
|
1199
|
+
If [[catparent]] is None, the category will be a child of
|
|
1200
|
+
the topmost 'Head' category or else the top data block. If
|
|
1201
|
+
[[is_loop]] is false, a Set category is created. If [[allow_dangler]]
|
|
1202
|
+
is true, the parent category does not have to exist."""
|
|
1203
|
+
if catname in self:
|
|
1204
|
+
raise CifError('Attempt to add existing category %s' % catname)
|
|
1205
|
+
self.unlock()
|
|
1206
|
+
syntactic_root = self.get_roots()[0][0]
|
|
1207
|
+
if catparent is None:
|
|
1208
|
+
semantic_root = [a for a in self.keys() if self[a].get('_definition.class',None)=='Head']
|
|
1209
|
+
if len(semantic_root)>0:
|
|
1210
|
+
semantic_root = semantic_root[0]
|
|
1211
|
+
else:
|
|
1212
|
+
semantic_root = syntactic_root
|
|
1213
|
+
else:
|
|
1214
|
+
semantic_root = catparent
|
|
1215
|
+
realname = super(CifDic,self).NewBlock(catname,parent=syntactic_root)
|
|
1216
|
+
self.block_id_table[catname.lower()]=realname
|
|
1217
|
+
self[catname]['_name.object_id'] = catname
|
|
1218
|
+
if not allow_dangler or catparent is None:
|
|
1219
|
+
self[catname]['_name.category_id'] = self[semantic_root]['_name.object_id']
|
|
1220
|
+
else:
|
|
1221
|
+
self[catname]['_name.category_id'] = catparent
|
|
1222
|
+
self[catname]['_definition.id'] = catname
|
|
1223
|
+
self[catname]['_definition.scope'] = 'Category'
|
|
1224
|
+
if is_loop:
|
|
1225
|
+
self[catname]['_definition.class'] = 'Loop'
|
|
1226
|
+
else:
|
|
1227
|
+
self[catname]['_definition.class'] = 'Set'
|
|
1228
|
+
self[catname]['_description.text'] = 'No definition provided'
|
|
1229
|
+
self.lock()
|
|
1230
|
+
return catname
|
|
1231
|
+
|
|
1232
|
+
def add_definition(self,itemname,catparent,def_text='PLEASE DEFINE ME',allow_dangler=False):
|
|
1233
|
+
"""Add itemname to category [[catparent]]. If itemname contains periods,
|
|
1234
|
+
all text before the final period is ignored. If [[allow_dangler]] is True,
|
|
1235
|
+
no check for a parent category is made."""
|
|
1236
|
+
self.unlock()
|
|
1237
|
+
if '.' in itemname:
|
|
1238
|
+
objname = itemname.split('.')[-1]
|
|
1239
|
+
else:
|
|
1240
|
+
objname = itemname
|
|
1241
|
+
objname = objname.strip('_')
|
|
1242
|
+
if not allow_dangler and (catparent not in self or self[catparent]['_definition.scope']!='Category'):
|
|
1243
|
+
raise CifError('No category %s in dictionary' % catparent)
|
|
1244
|
+
fullname = '_'+catparent.lower()+'.'+objname
|
|
1245
|
+
print('New name: %s' % fullname)
|
|
1246
|
+
syntactic_root = self.get_roots()[0][0]
|
|
1247
|
+
realname = super(CifDic,self).NewBlock(fullname, fix=False, parent=syntactic_root) #low-level change
|
|
1248
|
+
# update our dictionary structures
|
|
1249
|
+
self.block_id_table[fullname]=realname
|
|
1250
|
+
self[fullname]['_definition.id']=fullname
|
|
1251
|
+
self[fullname]['_name.object_id']=objname
|
|
1252
|
+
self[fullname]['_name.category_id']=catparent
|
|
1253
|
+
self[fullname]['_definition.class']='Datum'
|
|
1254
|
+
self[fullname]['_description.text']=def_text
|
|
1255
|
+
return realname
|
|
1256
|
+
|
|
1257
|
+
def remove_definition(self,defname):
|
|
1258
|
+
"""Remove a definition from the dictionary."""
|
|
1259
|
+
if defname not in self:
|
|
1260
|
+
return
|
|
1261
|
+
if self[defname].get('_definition.scope')=='Category':
|
|
1262
|
+
children = self.ddlm_immediate_children(defname)
|
|
1263
|
+
[self.remove_definition(a) for a in children]
|
|
1264
|
+
cat_id = self[defname]['_definition.id'].lower()
|
|
1265
|
+
del self[defname]
|
|
1266
|
+
|
|
1267
|
+
def get_cat_obj(self,name):
|
|
1268
|
+
"""Return (cat,obj) tuple. [[name]] must contain only a single period"""
|
|
1269
|
+
cat,obj = name.split('.')
|
|
1270
|
+
return (cat.strip('_'),obj)
|
|
1271
|
+
|
|
1272
|
+
def get_name_by_cat_obj(self,category,object,give_default=False):
|
|
1273
|
+
"""Return the dataname corresponding to the given category and object"""
|
|
1274
|
+
if category[0] == '_': #accidentally left in
|
|
1275
|
+
true_cat = category[1:].lower()
|
|
1276
|
+
else:
|
|
1277
|
+
true_cat = category.lower()
|
|
1278
|
+
try:
|
|
1279
|
+
return self.cat_obj_lookup_table[(true_cat,object.lower())][0]
|
|
1280
|
+
except KeyError:
|
|
1281
|
+
if give_default:
|
|
1282
|
+
return '_'+true_cat+'.'+object
|
|
1283
|
+
raise KeyError('No such category,object in the dictionary: %s %s' % (true_cat,object))
|
|
1284
|
+
|
|
1285
|
+
|
|
1286
|
+
def WriteOut(self,**kwargs):
|
|
1287
|
+
myblockorder = self.get_full_child_list()
|
|
1288
|
+
self.set_grammar(self.grammar)
|
|
1289
|
+
self.standard = 'Dic'
|
|
1290
|
+
return super(CifDic,self).WriteOut(blockorder = myblockorder,**kwargs)
|
|
1291
|
+
|
|
1292
|
+
def get_full_child_list(self):
|
|
1293
|
+
"""Return a list of definition blocks in order parent-child-child-child-parent-child..."""
|
|
1294
|
+
top_block = self.get_roots()[0][0]
|
|
1295
|
+
root_cat = [a for a in self.keys() if self[a].get('_definition.class','Datum')=='Head']
|
|
1296
|
+
if len(root_cat) == 1:
|
|
1297
|
+
all_names = [top_block] + self.recurse_child_list(root_cat[0])
|
|
1298
|
+
unrooted = self.ddlm_danglers()
|
|
1299
|
+
double_names = set(unrooted).intersection(set(all_names))
|
|
1300
|
+
if len(double_names)>0:
|
|
1301
|
+
raise CifError('Names are children of internal and external categories:%s' % repr(double_names))
|
|
1302
|
+
remaining = unrooted[:]
|
|
1303
|
+
for no_root in unrooted:
|
|
1304
|
+
if self[no_root].get('_definition.scope','Item')=='Category':
|
|
1305
|
+
all_names += [no_root]
|
|
1306
|
+
remaining.remove(no_root)
|
|
1307
|
+
these_children = [n for n in unrooted if self[n]['_name.category_id'].lower()==no_root.lower()]
|
|
1308
|
+
all_names += these_children
|
|
1309
|
+
[remaining.remove(n) for n in these_children]
|
|
1310
|
+
# now sort by category
|
|
1311
|
+
ext_cats = set([self[r].get('_name.category_id',self.cat_from_name(r)).lower() for r in remaining])
|
|
1312
|
+
for e in ext_cats:
|
|
1313
|
+
cat_items = [r for r in remaining if self[r].get('_name.category_id',self.cat_from_name(r)).lower() == e]
|
|
1314
|
+
[remaining.remove(n) for n in cat_items]
|
|
1315
|
+
all_names += cat_items
|
|
1316
|
+
if len(remaining)>0:
|
|
1317
|
+
print('WARNING: following items do not seem to belong to a category??')
|
|
1318
|
+
print(repr(remaining))
|
|
1319
|
+
all_names += remaining
|
|
1320
|
+
print('Final block order: ' + repr(all_names))
|
|
1321
|
+
return all_names
|
|
1322
|
+
raise ValueError('Dictionary contains no/multiple Head categories, please print as plain CIF instead')
|
|
1323
|
+
|
|
1324
|
+
def cat_from_name(self,one_name):
|
|
1325
|
+
"""Guess the category from the name. This should be used only when this is not important semantic information,
|
|
1326
|
+
for example, when printing out"""
|
|
1327
|
+
(cat,obj) = one_name.split(".")
|
|
1328
|
+
if cat[0] == "_": cat = cat[1:]
|
|
1329
|
+
return cat
|
|
1330
|
+
|
|
1331
|
+
def recurse_child_list(self,parentname):
|
|
1332
|
+
"""Recursively expand the logical child list of [[parentname]]"""
|
|
1333
|
+
final_list = [parentname]
|
|
1334
|
+
child_blocks = [a for a in self.child_table.keys() if self[a].get('_name.category_id','').lower() == parentname.lower()]
|
|
1335
|
+
child_blocks.sort() #we love alphabetical order
|
|
1336
|
+
child_items = [a for a in child_blocks if self[a].get('_definition.scope','Item') == 'Item']
|
|
1337
|
+
final_list += child_items
|
|
1338
|
+
child_cats = [a for a in child_blocks if self[a].get('_definition.scope','Item') == 'Category']
|
|
1339
|
+
for child_cat in child_cats:
|
|
1340
|
+
final_list += self.recurse_child_list(child_cat)
|
|
1341
|
+
return final_list
|
|
1342
|
+
|
|
1343
|
+
|
|
1344
|
+
|
|
1345
|
+
def get_key_pack(self,category,value,data):
|
|
1346
|
+
keyname = self[category][self.unique_spec]
|
|
1347
|
+
onepack = data.GetPackKey(keyname,value)
|
|
1348
|
+
return onepack
|
|
1349
|
+
|
|
1350
|
+
def get_number_with_esd(numstring):
|
|
1351
|
+
numb_re = '((-?(([0-9]*[.]([0-9]+))|([0-9]+)[.]?))([(][0-9]+[)])?([eEdD][+-]?[0-9]+)?)|(\\?)|(\\.)'
|
|
1352
|
+
our_match = re.match(numb_re,numstring)
|
|
1353
|
+
if our_match:
|
|
1354
|
+
a,base_num,b,c,dad,dbd,esd,exp,q,dot = our_match.groups()
|
|
1355
|
+
# print("Debug: {} -> {!r}".format(numstring, our_match.groups()))
|
|
1356
|
+
else:
|
|
1357
|
+
return None,None
|
|
1358
|
+
if dot or q: return None,None #a dot or question mark
|
|
1359
|
+
if exp: #has exponent
|
|
1360
|
+
exp = exp.replace("d","e") # mop up old fashioned numbers
|
|
1361
|
+
exp = exp.replace("D","e")
|
|
1362
|
+
base_num = base_num + exp
|
|
1363
|
+
# print("Debug: have %s for base_num from %s" % (base_num,numstring))
|
|
1364
|
+
base_num = float(base_num)
|
|
1365
|
+
# work out esd, if present.
|
|
1366
|
+
if esd:
|
|
1367
|
+
esd = float(esd[1:-1]) # no brackets
|
|
1368
|
+
if dad: # decimal point + digits
|
|
1369
|
+
esd = esd * (10 ** (-1* len(dad)))
|
|
1370
|
+
if exp:
|
|
1371
|
+
esd = esd * (10 ** (float(exp[1:])))
|
|
1372
|
+
return base_num,esd
|
|
1373
|
+
|
|
1374
|
+
def getmaxmin(self,rangeexp):
|
|
1375
|
+
regexp = '(-?(([0-9]*[.]([0-9]+))|([0-9]+)[.]?)([eEdD][+-]?[0-9]+)?)*'
|
|
1376
|
+
regexp = regexp + ":" + regexp
|
|
1377
|
+
regexp = re.match(regexp,rangeexp)
|
|
1378
|
+
try:
|
|
1379
|
+
minimum = regexp.group(1)
|
|
1380
|
+
maximum = regexp.group(7)
|
|
1381
|
+
except AttributeError:
|
|
1382
|
+
print("Can't match %s" % rangeexp)
|
|
1383
|
+
if minimum == None: minimum = "."
|
|
1384
|
+
else: minimum = float(minimum)
|
|
1385
|
+
if maximum == None: maximum = "."
|
|
1386
|
+
else: maximum = float(maximum)
|
|
1387
|
+
return maximum,minimum
|
|
1388
|
+
|
|
1389
|
+
def initialise_drel(self):
|
|
1390
|
+
"""Parse drel functions and prepare data structures in dictionary"""
|
|
1391
|
+
self.ddlm_parse_valid() #extract validity information from data block
|
|
1392
|
+
self.transform_drel() #parse the drel functions
|
|
1393
|
+
self.add_drel_funcs() #put the drel functions into the namespace
|
|
1394
|
+
|
|
1395
|
+
def transform_drel(self):
|
|
1396
|
+
from .drel import drel_ast_yacc
|
|
1397
|
+
from .drel import py_from_ast
|
|
1398
|
+
import traceback
|
|
1399
|
+
parser = drel_ast_yacc.parser
|
|
1400
|
+
lexer = drel_ast_yacc.lexer
|
|
1401
|
+
my_namespace = self.keys()
|
|
1402
|
+
my_namespace = dict(zip(my_namespace,my_namespace))
|
|
1403
|
+
# we provide a table of loopable categories {cat_name:((key1,key2..),[item_name,...]),...})
|
|
1404
|
+
loopable_cats = self.get_loopable_cats()
|
|
1405
|
+
loop_keys = [listify(self[a]["_category_key.name"]) for a in loopable_cats]
|
|
1406
|
+
loop_keys = [[self[a]['_name.object_id'] for a in b] for b in loop_keys]
|
|
1407
|
+
cat_names = [self.names_in_cat(a,names_only=True) for a in loopable_cats]
|
|
1408
|
+
loop_info = dict(zip(loopable_cats,zip(loop_keys,cat_names)))
|
|
1409
|
+
# parser.listable_items = [a for a in self.keys() if "*" in self[a].get("_type.dimension","")]
|
|
1410
|
+
derivable_list = [a for a in self.keys() if "_method.expression" in self[a] \
|
|
1411
|
+
and self[a].get("_name.category_id","")!= "function"]
|
|
1412
|
+
for derivable in derivable_list:
|
|
1413
|
+
target_id = derivable
|
|
1414
|
+
# reset the list of visible names for parser
|
|
1415
|
+
special_ids = [dict(zip(self.keys(),self.keys()))]
|
|
1416
|
+
print("Target id: %s" % derivable)
|
|
1417
|
+
drel_exprs = self[derivable]["_method.expression"]
|
|
1418
|
+
drel_purposes = self[derivable]["_method.purpose"]
|
|
1419
|
+
all_methods = []
|
|
1420
|
+
if not isinstance(drel_exprs,list):
|
|
1421
|
+
drel_exprs = [drel_exprs]
|
|
1422
|
+
drel_purposes = [drel_purposes]
|
|
1423
|
+
for drel_purpose,drel_expr in zip(drel_purposes,drel_exprs):
|
|
1424
|
+
if drel_purpose != 'Evaluation':
|
|
1425
|
+
continue
|
|
1426
|
+
drel_expr = "\n".join(drel_expr.splitlines())
|
|
1427
|
+
# print("Transforming %s" % drel_expr)
|
|
1428
|
+
# List categories are treated differently...
|
|
1429
|
+
try:
|
|
1430
|
+
meth_ast = parser.parse(drel_expr+"\n",lexer=lexer)
|
|
1431
|
+
except:
|
|
1432
|
+
print('Syntax error in method for %s; leaving as is' % derivable)
|
|
1433
|
+
a,b = sys.exc_info()[:2]
|
|
1434
|
+
print((repr(a),repr(b)))
|
|
1435
|
+
print(traceback.print_tb(sys.exc_info()[-1],None,sys.stdout))
|
|
1436
|
+
# reset the lexer
|
|
1437
|
+
lexer.begin('INITIAL')
|
|
1438
|
+
continue
|
|
1439
|
+
# Construct the python method
|
|
1440
|
+
cat_meth = False
|
|
1441
|
+
if self[derivable].get('_definition.scope','Item') == 'Category':
|
|
1442
|
+
cat_meth = True
|
|
1443
|
+
pyth_meth = py_from_ast.make_python_function(meth_ast,"pyfunc",target_id,
|
|
1444
|
+
loopable=loop_info,
|
|
1445
|
+
cif_dic = self,cat_meth=cat_meth)
|
|
1446
|
+
all_methods.append(pyth_meth)
|
|
1447
|
+
if len(all_methods)>0:
|
|
1448
|
+
save_overwrite = self[derivable].overwrite
|
|
1449
|
+
self[derivable].overwrite = True
|
|
1450
|
+
self[derivable]["_method.py_expression"] = all_methods
|
|
1451
|
+
self[derivable].overwrite = save_overwrite
|
|
1452
|
+
#print("Final result:\n " + repr(self[derivable]["_method.py_expression"]))
|
|
1453
|
+
|
|
1454
|
+
def add_drel_funcs(self):
|
|
1455
|
+
from .drel import drel_ast_yacc
|
|
1456
|
+
from .drel import py_from_ast
|
|
1457
|
+
funclist = [a for a in self.keys() if self[a].get("_name.category_id","")=='function']
|
|
1458
|
+
funcnames = [(self[a]["_name.object_id"],
|
|
1459
|
+
getattr(self[a].GetKeyedPacket("_method.purpose","Evaluation"),"_method.expression")) for a in funclist]
|
|
1460
|
+
# create executable python code...
|
|
1461
|
+
parser = drel_ast_yacc.parser
|
|
1462
|
+
# we provide a table of loopable categories {cat_name:(key,[item_name,...]),...})
|
|
1463
|
+
loopable_cats = self.get_loopable_cats()
|
|
1464
|
+
loop_keys = [listify(self[a]["_category_key.name"]) for a in loopable_cats]
|
|
1465
|
+
loop_keys = [[self[a]['_name.object_id'] for a in b] for b in loop_keys]
|
|
1466
|
+
cat_names = [self.names_in_cat(a,names_only=True) for a in loopable_cats]
|
|
1467
|
+
loop_info = dict(zip(loopable_cats,zip(loop_keys,cat_names)))
|
|
1468
|
+
for funcname,funcbody in funcnames:
|
|
1469
|
+
newline_body = "\n".join(funcbody.splitlines())
|
|
1470
|
+
parser.target_id = funcname
|
|
1471
|
+
res_ast = parser.parse(newline_body)
|
|
1472
|
+
py_function = py_from_ast.make_python_function(res_ast,None,targetname=funcname,func_def=True,loopable=loop_info,cif_dic = self)
|
|
1473
|
+
#print('dREL library function ->\n' + py_function)
|
|
1474
|
+
global_table = globals()
|
|
1475
|
+
exec(py_function, global_table) #add to namespace
|
|
1476
|
+
#print('Globals after dREL functions added:' + repr(globals()))
|
|
1477
|
+
self.ddlm_functions = globals() #for outside access
|
|
1478
|
+
|
|
1479
|
+
@track_recursion
|
|
1480
|
+
def derive_item(self,start_key,cifdata,store_value = False,allow_defaults=True):
|
|
1481
|
+
key = start_key #starting value
|
|
1482
|
+
result = None #success is a non-None value
|
|
1483
|
+
default_result = False #we have not used a default value
|
|
1484
|
+
# check for aliases
|
|
1485
|
+
# check for an older form of a new value
|
|
1486
|
+
found_it = [k for k in self.alias_table.get(key,[]) if k in cifdata]
|
|
1487
|
+
if len(found_it)>0:
|
|
1488
|
+
corrected_type = self.change_type(key,cifdata[found_it[0]])
|
|
1489
|
+
return corrected_type
|
|
1490
|
+
# now do the reverse check - any alternative form
|
|
1491
|
+
alias_name = [a for a in self.alias_table.items() if key in a[1]]
|
|
1492
|
+
print('Aliases for %s: %s' % (key,repr(alias_name)))
|
|
1493
|
+
if len(alias_name)==1:
|
|
1494
|
+
key = alias_name[0][0] #actual definition name
|
|
1495
|
+
if key in cifdata: return self.change_type(key,cifdata[key])
|
|
1496
|
+
found_it = [k for k in alias_name[0][1] if k in cifdata]
|
|
1497
|
+
if len(found_it)>0:
|
|
1498
|
+
return self.change_type(key,cifdata[found_it[0]])
|
|
1499
|
+
elif len(alias_name)>1:
|
|
1500
|
+
raise CifError('Dictionary error: dataname alias appears in different definitions: ' + repr(alias_name))
|
|
1501
|
+
|
|
1502
|
+
the_category = self[key]["_name.category_id"]
|
|
1503
|
+
cat_names = [a for a in self.keys() if self[a].get("_name.category_id",None)==the_category]
|
|
1504
|
+
has_cat_names = [a for a in cat_names if cifdata.has_key_or_alias(a)]
|
|
1505
|
+
# store any default value in case we have a problem
|
|
1506
|
+
def_val = self[key].get("_enumeration.default","")
|
|
1507
|
+
def_index_val = self[key].get("_enumeration.def_index_id","")
|
|
1508
|
+
if len(has_cat_names)==0: # try category method
|
|
1509
|
+
cat_result = {}
|
|
1510
|
+
pulled_from_cats = [k for k in self.keys() if '_category_construct_local.components' in self[k]]
|
|
1511
|
+
pulled_from_cats = [(k,[
|
|
1512
|
+
self[n]['_name.category_id'] for n in self[k]['_category_construct_local.components']]
|
|
1513
|
+
) for k in pulled_from_cats]
|
|
1514
|
+
pulled_to_cats = [k[0] for k in pulled_from_cats if the_category in k[1]]
|
|
1515
|
+
if '_category_construct_local.type' in self[the_category]:
|
|
1516
|
+
print("**Now constructing category %s using DDLm attributes**" % the_category)
|
|
1517
|
+
try:
|
|
1518
|
+
cat_result = self.construct_category(the_category,cifdata,store_value=True)
|
|
1519
|
+
except (CifRecursionError,StarFile.StarDerivationError):
|
|
1520
|
+
print('** Failed to construct category %s (error)' % the_category)
|
|
1521
|
+
# Trying a pull-back when the category is partially populated
|
|
1522
|
+
# will not work, hence we test that cat_result has no keys
|
|
1523
|
+
if len(pulled_to_cats)>0 and len(cat_result)==0:
|
|
1524
|
+
print("**Now populating category %s from pulled-back category %s" % (the_category,repr(pulled_to_cats)))
|
|
1525
|
+
try:
|
|
1526
|
+
cat_result = self.push_from_pullback(the_category,pulled_to_cats,cifdata,store_value=True)
|
|
1527
|
+
except (CifRecursionError,StarFile.StarDerivationError):
|
|
1528
|
+
print('** Failed to construct category %s from pullback information (error)' % the_category)
|
|
1529
|
+
if '_method.py_expression' in self[the_category] and key not in cat_result:
|
|
1530
|
+
print("**Now applying category method for %s in search of %s**" % (the_category,key))
|
|
1531
|
+
cat_result = self.derive_item(the_category,cifdata,store_value=True)
|
|
1532
|
+
print("**Tried pullbacks, obtained for %s " % the_category + repr(cat_result))
|
|
1533
|
+
# do we now have our value?
|
|
1534
|
+
if key in cat_result:
|
|
1535
|
+
return cat_result[key]
|
|
1536
|
+
|
|
1537
|
+
# Recalculate in case it actually worked
|
|
1538
|
+
has_cat_names = [a for a in cat_names if cifdata.has_key_or_alias(a)]
|
|
1539
|
+
the_funcs = self[key].get('_method.py_expression',"")
|
|
1540
|
+
if the_funcs: #attempt to calculate it
|
|
1541
|
+
#global_table = globals()
|
|
1542
|
+
#global_table.update(self.ddlm_functions)
|
|
1543
|
+
for one_func in the_funcs:
|
|
1544
|
+
print('Executing function for %s:' % key)
|
|
1545
|
+
#print(one_func)
|
|
1546
|
+
exec(one_func, globals()) #will access dREL functions, puts "pyfunc" in scope
|
|
1547
|
+
# print('in following global environment: ' + repr(global_table))
|
|
1548
|
+
stored_setting = cifdata.provide_value
|
|
1549
|
+
cifdata.provide_value = True
|
|
1550
|
+
try:
|
|
1551
|
+
result = pyfunc(cifdata)
|
|
1552
|
+
except CifRecursionError as s:
|
|
1553
|
+
print(s)
|
|
1554
|
+
result = None
|
|
1555
|
+
except StarFile.StarDerivationError as s:
|
|
1556
|
+
print(s)
|
|
1557
|
+
result = None
|
|
1558
|
+
finally:
|
|
1559
|
+
cifdata.provide_value = stored_setting
|
|
1560
|
+
if result is not None:
|
|
1561
|
+
break
|
|
1562
|
+
#print("Function returned {!r}".format(result))
|
|
1563
|
+
|
|
1564
|
+
if result is None and allow_defaults: # try defaults
|
|
1565
|
+
if def_val:
|
|
1566
|
+
result = self.change_type(key,def_val)
|
|
1567
|
+
default_result = True
|
|
1568
|
+
elif def_index_val: #derive a default value
|
|
1569
|
+
index_vals = self[key]["_enumeration_default.index"]
|
|
1570
|
+
val_to_index = cifdata[def_index_val] #what we are keying on
|
|
1571
|
+
if self[def_index_val]['_type.contents'] in ['Code','Name','Tag']:
|
|
1572
|
+
lcase_comp = True
|
|
1573
|
+
index_vals = [a.lower() for a in index_vals]
|
|
1574
|
+
# Handle loops
|
|
1575
|
+
if isinstance(val_to_index,list):
|
|
1576
|
+
if lcase_comp:
|
|
1577
|
+
val_to_index = [a.lower() for a in val_to_index]
|
|
1578
|
+
keypos = [index_vals.index(a) for a in val_to_index]
|
|
1579
|
+
result = [self[key]["_enumeration_default.value"][a] for a in keypos]
|
|
1580
|
+
else:
|
|
1581
|
+
if lcase_comp:
|
|
1582
|
+
val_to_index = val_to_index.lower()
|
|
1583
|
+
keypos = index_vals.index(val_to_index) #value error if no such value available
|
|
1584
|
+
result = self[key]["_enumeration_default.value"][keypos]
|
|
1585
|
+
default_result = True #flag that it must be extended
|
|
1586
|
+
result = self.change_type(key,result)
|
|
1587
|
+
print("Indexed on %s to get %s for %s" % (def_index_val,repr(result),repr(val_to_index)))
|
|
1588
|
+
|
|
1589
|
+
# read it in
|
|
1590
|
+
if result is None: #can't do anything else
|
|
1591
|
+
print('Warning: no way of deriving item %s, allow_defaults is %s' % (key,repr(allow_defaults)))
|
|
1592
|
+
raise StarFile.StarDerivationError(start_key)
|
|
1593
|
+
is_looped = False
|
|
1594
|
+
if self[the_category].get('_definition.class','Set')=='Loop':
|
|
1595
|
+
is_looped = True
|
|
1596
|
+
if len(has_cat_names)>0: #this category already exists
|
|
1597
|
+
if result is None or default_result: #need to create a list of values
|
|
1598
|
+
loop_len = len(cifdata[has_cat_names[0]])
|
|
1599
|
+
out_result = [result]*loop_len
|
|
1600
|
+
result = out_result
|
|
1601
|
+
else: #nothing exists in this category, we can't store this at all
|
|
1602
|
+
print('Resetting result %s for %s to null list as category is empty' % (key,result))
|
|
1603
|
+
result = []
|
|
1604
|
+
|
|
1605
|
+
# now try to insert the new information into the right place
|
|
1606
|
+
# find if items of this category already appear...
|
|
1607
|
+
# Never cache empty values
|
|
1608
|
+
if not (isinstance(result,list) and len(result)==0) and\
|
|
1609
|
+
store_value:
|
|
1610
|
+
if self[key].get("_definition.scope","Item")=='Item':
|
|
1611
|
+
if is_looped:
|
|
1612
|
+
result = self.store_new_looped_value(key,cifdata,result,default_result)
|
|
1613
|
+
else:
|
|
1614
|
+
result = self.store_new_unlooped_value(key,cifdata,result)
|
|
1615
|
+
else:
|
|
1616
|
+
self.store_new_cat_values(cifdata,result,the_category)
|
|
1617
|
+
return result
|
|
1618
|
+
|
|
1619
|
+
def store_new_looped_value(self,key,cifdata,result,default_result):
|
|
1620
|
+
"""Store a looped value from the dREL system into a CifFile"""
|
|
1621
|
+
# try to change any matrices etc. to lists
|
|
1622
|
+
the_category = self[key]["_name.category_id"]
|
|
1623
|
+
out_result = result
|
|
1624
|
+
if result is not None and not default_result:
|
|
1625
|
+
# find any numpy arrays
|
|
1626
|
+
def conv_from_numpy(one_elem):
|
|
1627
|
+
if not hasattr(one_elem,'dtype'):
|
|
1628
|
+
if isinstance(one_elem,(list,tuple)):
|
|
1629
|
+
return StarFile.StarList([conv_from_numpy(a) for a in one_elem])
|
|
1630
|
+
return one_elem
|
|
1631
|
+
if one_elem.size > 1: #so is not a float
|
|
1632
|
+
return StarFile.StarList([conv_from_numpy(a) for a in one_elem.tolist()])
|
|
1633
|
+
else:
|
|
1634
|
+
try:
|
|
1635
|
+
return one_elem.item(0)
|
|
1636
|
+
except:
|
|
1637
|
+
return one_elem
|
|
1638
|
+
out_result = [conv_from_numpy(a) for a in result]
|
|
1639
|
+
# so out_result now contains a value suitable for storage
|
|
1640
|
+
cat_names = [a for a in self.keys() if self[a].get("_name.category_id",None)==the_category]
|
|
1641
|
+
has_cat_names = [a for a in cat_names if a in cifdata]
|
|
1642
|
+
print('Adding {}, found pre-existing names: '.format(key) + repr(has_cat_names))
|
|
1643
|
+
if len(has_cat_names)>0: #this category already exists
|
|
1644
|
+
cifdata[key] = out_result #lengths must match or else!!
|
|
1645
|
+
cifdata.AddLoopName(has_cat_names[0],key)
|
|
1646
|
+
else:
|
|
1647
|
+
cifdata[key] = out_result
|
|
1648
|
+
cifdata.CreateLoop([key])
|
|
1649
|
+
print('Loop info:' + repr(cifdata.loops))
|
|
1650
|
+
return out_result
|
|
1651
|
+
|
|
1652
|
+
def store_new_unlooped_value(self,key,cifdata,result):
|
|
1653
|
+
"""Store a single value from the dREL system"""
|
|
1654
|
+
if result is not None and hasattr(result,'dtype'):
|
|
1655
|
+
if result.size > 1:
|
|
1656
|
+
out_result = StarFile.StarList(result.tolist())
|
|
1657
|
+
cifdata[key] = out_result
|
|
1658
|
+
else:
|
|
1659
|
+
cifdata[key] = result.item(0)
|
|
1660
|
+
else:
|
|
1661
|
+
cifdata[key] = result
|
|
1662
|
+
return result
|
|
1663
|
+
|
|
1664
|
+
def construct_category(self,category,cifdata,store_value=True):
|
|
1665
|
+
"""Construct a category using DDLm attributes"""
|
|
1666
|
+
con_type = self[category].get('_category_construct_local.type',None)
|
|
1667
|
+
if con_type == None:
|
|
1668
|
+
return {}
|
|
1669
|
+
if con_type == 'Pullback' or con_type == 'Filter':
|
|
1670
|
+
morphisms = self[category]['_category_construct_local.components']
|
|
1671
|
+
morph_values = [cifdata[a] for a in morphisms] # the mapped values for each cat
|
|
1672
|
+
cats = [self[a]['_name.category_id'] for a in morphisms]
|
|
1673
|
+
cat_keys = [self[a]['_category.key_id'] for a in cats]
|
|
1674
|
+
cat_values = [list(cifdata[a]) for a in cat_keys] #the category key values for each cat
|
|
1675
|
+
if con_type == 'Filter':
|
|
1676
|
+
int_filter = self[category].get('_category_construct_local.integer_filter',None)
|
|
1677
|
+
text_filter = self[category].get('_category_construct_local.text_filter',None)
|
|
1678
|
+
if int_filter is not None:
|
|
1679
|
+
morph_values.append([int(a) for a in int_filter])
|
|
1680
|
+
if text_filter is not None:
|
|
1681
|
+
morph_values.append(text_filter)
|
|
1682
|
+
cat_values.append(range(len(morph_values[-1])))
|
|
1683
|
+
# create the mathematical product filtered by equality of dataname values
|
|
1684
|
+
pullback_ids = [(x,y) for x in cat_values[0] for y in cat_values[1] \
|
|
1685
|
+
if morph_values[0][cat_values[0].index(x)]==morph_values[1][cat_values[1].index(y)]]
|
|
1686
|
+
# now prepare for return
|
|
1687
|
+
if len(pullback_ids)==0:
|
|
1688
|
+
return {}
|
|
1689
|
+
newids = self[category]['_category_construct_local.new_ids']
|
|
1690
|
+
fullnewids = [self.cat_obj_lookup_table[(category,n)][0] for n in newids]
|
|
1691
|
+
if con_type == 'Pullback':
|
|
1692
|
+
final_results = {fullnewids[0]:[x[0] for x in pullback_ids],fullnewids[1]:[x[1] for x in pullback_ids]}
|
|
1693
|
+
final_results.update(self.duplicate_datanames(cifdata,cats[0],category,key_vals = final_results[fullnewids[0]],skip_names=newids))
|
|
1694
|
+
final_results.update(self.duplicate_datanames(cifdata,cats[1],category,key_vals = final_results[fullnewids[1]],skip_names=newids))
|
|
1695
|
+
elif con_type == 'Filter': #simple filter
|
|
1696
|
+
final_results = {fullnewids[0]:[x[0] for x in pullback_ids]}
|
|
1697
|
+
final_results.update(self.duplicate_datanames(cifdata,cats[0],category,key_vals = final_results[fullnewids[0]],skip_names=newids))
|
|
1698
|
+
if store_value:
|
|
1699
|
+
self.store_new_cat_values(cifdata,final_results,category)
|
|
1700
|
+
return final_results
|
|
1701
|
+
|
|
1702
|
+
def push_from_pullback(self,target_category,source_categories,cifdata,store_value=True):
|
|
1703
|
+
"""Each of the categories in source_categories are pullbacks that include
|
|
1704
|
+
the target_category"""
|
|
1705
|
+
target_key = self[target_category]['_category.key_id']
|
|
1706
|
+
result = {target_key:[]}
|
|
1707
|
+
first_time = True
|
|
1708
|
+
# for each source category, determine which element goes to the target
|
|
1709
|
+
for sc in source_categories:
|
|
1710
|
+
components = self[sc]['_category_construct_local.components']
|
|
1711
|
+
comp_cats = [self[c]['_name.category_id'] for c in components]
|
|
1712
|
+
new_ids = self[sc]['_category_construct_local.new_ids']
|
|
1713
|
+
source_ids = [self.cat_obj_lookup_table[(sc,n)][0] for n in new_ids]
|
|
1714
|
+
if len(components) == 2: # not a filter
|
|
1715
|
+
element_pos = comp_cats.index(target_category)
|
|
1716
|
+
old_id = source_ids[element_pos]
|
|
1717
|
+
print('Using %s to populate %s' % (old_id,target_key))
|
|
1718
|
+
result[target_key].extend(cifdata[old_id])
|
|
1719
|
+
# project through all identical names
|
|
1720
|
+
extra_result = self.duplicate_datanames(cifdata,sc,target_category,skip_names=new_ids+[target_key])
|
|
1721
|
+
# we only include keys that are common to all categories
|
|
1722
|
+
if first_time:
|
|
1723
|
+
result.update(extra_result)
|
|
1724
|
+
else:
|
|
1725
|
+
for k in extra_result.keys():
|
|
1726
|
+
if k in result:
|
|
1727
|
+
print('Updating %s: was %s' % (k,repr(result[k])))
|
|
1728
|
+
result[k].extend(extra_result[k])
|
|
1729
|
+
else:
|
|
1730
|
+
extra_result = self.duplicate_datanames(cifdata,sc,target_category,skip_names=new_ids)
|
|
1731
|
+
if len(extra_result)>0 or source_ids[0] in cifdata: #something is present
|
|
1732
|
+
result[target_key].extend(cifdata[source_ids[0]])
|
|
1733
|
+
for k in extra_result.keys():
|
|
1734
|
+
if k in result:
|
|
1735
|
+
print('Reverse filter: Updating %s: was %s' % (k,repr(result[k])))
|
|
1736
|
+
result[k].extend(extra_result[k])
|
|
1737
|
+
else:
|
|
1738
|
+
result[k]=extra_result[k]
|
|
1739
|
+
# Bonus derivation if there is a singleton filter
|
|
1740
|
+
if self[sc]['_category_construct_local.type'] == 'Filter':
|
|
1741
|
+
int_filter = self[sc].get('_category_construct_local.integer_filter',None)
|
|
1742
|
+
text_filter = self[sc].get('_category_construct_local.text_filter',None)
|
|
1743
|
+
if int_filter is not None:
|
|
1744
|
+
filter_values = int_filter
|
|
1745
|
+
else:
|
|
1746
|
+
filter_values = text_filter
|
|
1747
|
+
if len(filter_values)==1: #a singleton
|
|
1748
|
+
extra_dataname = self[sc]['_category_construct_local.components'][0]
|
|
1749
|
+
if int_filter is not None:
|
|
1750
|
+
new_value = [int(filter_values[0])] * len(cifdata[source_ids[0]])
|
|
1751
|
+
else:
|
|
1752
|
+
new_value = filter_values * len(cifdata[source_ids[0]])
|
|
1753
|
+
if extra_dataname not in result:
|
|
1754
|
+
result[extra_dataname] = new_value
|
|
1755
|
+
else:
|
|
1756
|
+
result[extra_dataname].extend(new_value)
|
|
1757
|
+
else:
|
|
1758
|
+
raise ValueError('Unexpected category construct type' + self[sc]['_category_construct_local.type'])
|
|
1759
|
+
first_time = False
|
|
1760
|
+
# check for sanity - all dataname lengths must be identical
|
|
1761
|
+
datalen = len(set([len(a) for a in result.values()]))
|
|
1762
|
+
if datalen != 1:
|
|
1763
|
+
raise AssertionError('Failed to construct equal-length category items,'+ repr(result))
|
|
1764
|
+
if store_value:
|
|
1765
|
+
print('Now storing ' + repr(result))
|
|
1766
|
+
self.store_new_cat_values(cifdata,result,target_category)
|
|
1767
|
+
return result
|
|
1768
|
+
|
|
1769
|
+
def duplicate_datanames(self,cifdata,from_category,to_category,key_vals=None,skip_names=[]):
|
|
1770
|
+
"""Copy across datanames for which the from_category key equals [[key_vals]]"""
|
|
1771
|
+
result = {}
|
|
1772
|
+
s_names_in_cat = set(self.names_in_cat(from_category,names_only=True))
|
|
1773
|
+
t_names_in_cat = set(self.names_in_cat(to_category,names_only=True))
|
|
1774
|
+
can_project = s_names_in_cat & t_names_in_cat
|
|
1775
|
+
can_project -= set(skip_names) #already dealt with
|
|
1776
|
+
source_key = self[from_category]['_category.key_id']
|
|
1777
|
+
print('Source dataname set: ' + repr(s_names_in_cat))
|
|
1778
|
+
print('Target dataname set: ' + repr(t_names_in_cat))
|
|
1779
|
+
print('Projecting through following datanames from %s to %s' % (from_category,to_category) + repr(can_project))
|
|
1780
|
+
for project_name in can_project:
|
|
1781
|
+
full_from_name = self.cat_obj_lookup_table[(from_category.lower(),project_name.lower())][0]
|
|
1782
|
+
full_to_name = self.cat_obj_lookup_table[(to_category.lower(),project_name.lower())][0]
|
|
1783
|
+
if key_vals is None:
|
|
1784
|
+
try:
|
|
1785
|
+
result[full_to_name] = cifdata[full_from_name]
|
|
1786
|
+
except StarFile.StarDerivationError:
|
|
1787
|
+
pass
|
|
1788
|
+
else:
|
|
1789
|
+
all_key_vals = cifdata[source_key]
|
|
1790
|
+
filter_pos = [all_key_vals.index(a) for a in key_vals]
|
|
1791
|
+
try:
|
|
1792
|
+
all_data_vals = cifdata[full_from_name]
|
|
1793
|
+
except StarFile.StarDerivationError:
|
|
1794
|
+
pass
|
|
1795
|
+
result[full_to_name] = [all_data_vals[i] for i in filter_pos]
|
|
1796
|
+
return result
|
|
1797
|
+
|
|
1798
|
+
def store_new_cat_values(self,cifdata,result,the_category):
|
|
1799
|
+
"""Store the values in [[result]] into [[cifdata]]"""
|
|
1800
|
+
the_key = [a for a in result.keys() if self[a].get('_type.purpose','')=='Key']
|
|
1801
|
+
double_names = [a for a in result.keys() if a in cifdata]
|
|
1802
|
+
if len(double_names)>0:
|
|
1803
|
+
already_present = [a for a in self.names_in_cat(the_category) if a in cifdata]
|
|
1804
|
+
if set(already_present) != set(result.keys()):
|
|
1805
|
+
print("Category %s not updated, mismatched datanames: %s" % (the_category, repr(set(already_present)^set(result.keys()))))
|
|
1806
|
+
return
|
|
1807
|
+
#check key values
|
|
1808
|
+
old_keys = set(cifdata[the_key])
|
|
1809
|
+
common_keys = old_keys & set(result[the_key])
|
|
1810
|
+
if len(common_keys)>0:
|
|
1811
|
+
print("Category %s not updated, key values in common:" % (common_keys))
|
|
1812
|
+
return
|
|
1813
|
+
#extend result values with old values
|
|
1814
|
+
for one_name,one_value in result.items():
|
|
1815
|
+
result[one_name].extend(cifdata[one_name])
|
|
1816
|
+
for one_name, one_value in result.items():
|
|
1817
|
+
try:
|
|
1818
|
+
self.store_new_looped_value(one_name,cifdata,one_value,False)
|
|
1819
|
+
except StarFile.StarError:
|
|
1820
|
+
print('%s: Not replacing %s with calculated %s' % (one_name,repr(cifdata[one_name]),repr(one_value)))
|
|
1821
|
+
#put the key as the first item
|
|
1822
|
+
print('Fixing item order for {}'.format(repr(the_key)))
|
|
1823
|
+
for one_key in the_key: #should only be one
|
|
1824
|
+
cifdata.ChangeItemOrder(one_key,0)
|
|
1825
|
+
|
|
1826
|
+
|
|
1827
|
+
def generate_default_packet(self,catname,catkey,keyvalue):
|
|
1828
|
+
"""Return a StarPacket with items from ``catname`` and a key value
|
|
1829
|
+
of ``keyvalue``"""
|
|
1830
|
+
newpack = StarPacket()
|
|
1831
|
+
for na in self.names_in_cat(catname):
|
|
1832
|
+
def_val = self[na].get("_enumeration.default","")
|
|
1833
|
+
if def_val:
|
|
1834
|
+
final_val = self.change_type(na,def_val)
|
|
1835
|
+
newpack.extend(final_val)
|
|
1836
|
+
setattr(newpack,na,final_val)
|
|
1837
|
+
if len(newpack)>0:
|
|
1838
|
+
newpack.extend(keyvalue)
|
|
1839
|
+
setattr(newpack,catkey,keyvalue)
|
|
1840
|
+
return newpack
|
|
1841
|
+
|
|
1842
|
+
|
|
1843
|
+
def switch_numpy(self,to_val):
|
|
1844
|
+
pass
|
|
1845
|
+
|
|
1846
|
+
def change_type(self,itemname,inval):
|
|
1847
|
+
if inval == "?": return inval
|
|
1848
|
+
change_function = convert_type(self[itemname])
|
|
1849
|
+
if isinstance(inval,list) and not isinstance(inval,StarFile.StarList): #from a loop
|
|
1850
|
+
newval = list([change_function(a) for a in inval])
|
|
1851
|
+
else:
|
|
1852
|
+
newval = change_function(inval)
|
|
1853
|
+
return newval
|
|
1854
|
+
|
|
1855
|
+
def install_validation_functions(self):
|
|
1856
|
+
"""Install the DDL-appropriate validation checks"""
|
|
1857
|
+
if self.diclang != 'DDLm':
|
|
1858
|
+
# functions which check conformance
|
|
1859
|
+
self.item_validation_funs = [
|
|
1860
|
+
self.validate_item_type,
|
|
1861
|
+
self.validate_item_esd,
|
|
1862
|
+
self.validate_item_enum,
|
|
1863
|
+
self.validate_enum_range,
|
|
1864
|
+
self.validate_looping
|
|
1865
|
+
]
|
|
1866
|
+
# functions checking loop values
|
|
1867
|
+
self.loop_validation_funs = [
|
|
1868
|
+
self.validate_loop_membership,
|
|
1869
|
+
self.validate_loop_key,
|
|
1870
|
+
self.validate_loop_references
|
|
1871
|
+
]
|
|
1872
|
+
# where we need to look at other values
|
|
1873
|
+
self.global_validation_funs = [
|
|
1874
|
+
self.validate_exclusion,
|
|
1875
|
+
self.validate_parent,
|
|
1876
|
+
self.validate_child,
|
|
1877
|
+
self.validate_dependents,
|
|
1878
|
+
self.validate_uniqueness
|
|
1879
|
+
]
|
|
1880
|
+
# where only a full block will do
|
|
1881
|
+
self.block_validation_funs = [
|
|
1882
|
+
self.validate_mandatory_category
|
|
1883
|
+
]
|
|
1884
|
+
# removal is quicker with special checks
|
|
1885
|
+
self.global_remove_validation_funs = [
|
|
1886
|
+
self.validate_remove_parent_child
|
|
1887
|
+
]
|
|
1888
|
+
elif self.diclang == 'DDLm':
|
|
1889
|
+
self.item_validation_funs = [
|
|
1890
|
+
self.validate_item_enum,
|
|
1891
|
+
self.validate_item_esd_ddlm,
|
|
1892
|
+
]
|
|
1893
|
+
self.loop_validation_funs = [
|
|
1894
|
+
self.validate_looping_ddlm,
|
|
1895
|
+
self.validate_loop_key_ddlm,
|
|
1896
|
+
self.validate_loop_membership
|
|
1897
|
+
]
|
|
1898
|
+
self.global_validation_funs = []
|
|
1899
|
+
self.block_validation_funs = [
|
|
1900
|
+
self.check_mandatory_items,
|
|
1901
|
+
self.check_prohibited_items
|
|
1902
|
+
]
|
|
1903
|
+
self.global_remove_validation_funs = []
|
|
1904
|
+
self.optimize = False # default value
|
|
1905
|
+
self.done_parents = []
|
|
1906
|
+
self.done_children = []
|
|
1907
|
+
self.done_keys = []
|
|
1908
|
+
|
|
1909
|
+
def validate_item_type(self,item_name,item_value):
|
|
1910
|
+
def mymatch(m,a):
|
|
1911
|
+
res = m.match(a)
|
|
1912
|
+
if res != None: return res.group()
|
|
1913
|
+
else: return ""
|
|
1914
|
+
target_type = self[item_name].get(self.type_spec)
|
|
1915
|
+
if target_type == None: # e.g. a category definition
|
|
1916
|
+
return {"result":True} # not restricted in any way
|
|
1917
|
+
matchexpr = self.typedic[target_type]
|
|
1918
|
+
item_values = listify(item_value)
|
|
1919
|
+
#for item in item_values:
|
|
1920
|
+
#print("Type match " + item_name + " " + item + ":",)
|
|
1921
|
+
#skip dots and question marks
|
|
1922
|
+
check_all = [a for a in item_values if a !="." and a != "?"]
|
|
1923
|
+
check_all = [a for a in check_all if mymatch(matchexpr,a) != a]
|
|
1924
|
+
if len(check_all)>0: return {"result":False,"bad_values":check_all}
|
|
1925
|
+
else: return {"result":True}
|
|
1926
|
+
|
|
1927
|
+
def decide(self,result_list):
|
|
1928
|
+
"""Construct the return list"""
|
|
1929
|
+
if len(result_list)==0:
|
|
1930
|
+
return {"result":True}
|
|
1931
|
+
else:
|
|
1932
|
+
return {"result":False,"bad_values":result_list}
|
|
1933
|
+
|
|
1934
|
+
def validate_item_container(self, item_name,item_value):
|
|
1935
|
+
container_type = self[item_name]['_type.container']
|
|
1936
|
+
item_values = listify(item_value)
|
|
1937
|
+
if container_type == 'Single':
|
|
1938
|
+
okcheck = [a for a in item_values if not isinstance(a,(int,float,long,unicode))]
|
|
1939
|
+
return decide(okcheck)
|
|
1940
|
+
if container_type in ('Multiple','List'):
|
|
1941
|
+
okcheck = [a for a in item_values if not isinstance(a,StarList)]
|
|
1942
|
+
return decide(okcheck)
|
|
1943
|
+
if container_type == 'Array': #A list with numerical values
|
|
1944
|
+
okcheck = [a for a in item_values if not isinstance(a,StarList)]
|
|
1945
|
+
first_check = decide(okcheck)
|
|
1946
|
+
if not first_check['result']: return first_check
|
|
1947
|
+
#num_check = [a for a in item_values if len([b for b in a if not isinstance
|
|
1948
|
+
|
|
1949
|
+
def validate_item_esd(self,item_name,item_value):
|
|
1950
|
+
if self[item_name].get(self.primitive_type) != 'numb':
|
|
1951
|
+
return {"result":None}
|
|
1952
|
+
can_esd = self[item_name].get(self.esd_spec,"none") == "esd"
|
|
1953
|
+
if can_esd: return {"result":True} #must be OK!
|
|
1954
|
+
item_values = listify(item_value)
|
|
1955
|
+
check_all = list([a for a in item_values if get_number_with_esd(a)[1] != None])
|
|
1956
|
+
if len(check_all)>0: return {"result":False,"bad_values":check_all}
|
|
1957
|
+
return {"result":True}
|
|
1958
|
+
|
|
1959
|
+
def validate_item_esd_ddlm(self,item_name,item_value):
|
|
1960
|
+
if self[item_name].get('self.primitive_type') not in \
|
|
1961
|
+
['Count','Index','Integer','Real','Imag','Complex','Binary','Hexadecimal','Octal']:
|
|
1962
|
+
return {"result":None}
|
|
1963
|
+
can_esd = True
|
|
1964
|
+
if self[item_name].get('_type.purpose') != 'Measurand':
|
|
1965
|
+
can_esd = False
|
|
1966
|
+
item_values = listify(item_value)
|
|
1967
|
+
check_all = [get_number_with_esd(a)[1] for a in item_values]
|
|
1968
|
+
check_all = [v for v in check_all if (can_esd and v == None) or \
|
|
1969
|
+
(not can_esd and v != None)]
|
|
1970
|
+
if len(check_all)>0: return {"result":False,"bad_values":check_all}
|
|
1971
|
+
return {"result":True}
|
|
1972
|
+
|
|
1973
|
+
def validate_enum_range(self,item_name,item_value):
|
|
1974
|
+
if "_item_range.minimum" not in self[item_name] and \
|
|
1975
|
+
"_item_range.maximum" not in self[item_name]:
|
|
1976
|
+
return {"result":None}
|
|
1977
|
+
minvals = self[item_name].get("_item_range.minimum",default = ["."])
|
|
1978
|
+
maxvals = self[item_name].get("_item_range.maximum",default = ["."])
|
|
1979
|
+
def makefloat(a):
|
|
1980
|
+
if a == ".": return a
|
|
1981
|
+
else: return float(a)
|
|
1982
|
+
maxvals = map(makefloat, maxvals)
|
|
1983
|
+
minvals = map(makefloat, minvals)
|
|
1984
|
+
rangelist = list(zip(minvals,maxvals))
|
|
1985
|
+
item_values = listify(item_value)
|
|
1986
|
+
def map_check(rangelist,item_value):
|
|
1987
|
+
if item_value == "?" or item_value == ".": return True
|
|
1988
|
+
iv,esd = get_number_with_esd(item_value)
|
|
1989
|
+
if iv==None: return None #shouldn't happen as is numb type
|
|
1990
|
+
for lower,upper in rangelist:
|
|
1991
|
+
#check the minima
|
|
1992
|
+
if lower == ".": lower = iv - 1
|
|
1993
|
+
if upper == ".": upper = iv + 1
|
|
1994
|
+
if iv > lower and iv < upper: return True
|
|
1995
|
+
if upper == lower and iv == upper: return True
|
|
1996
|
+
# debug
|
|
1997
|
+
# print("Value %s fails range check %d < x < %d" % (item_value,lower,upper))
|
|
1998
|
+
return False
|
|
1999
|
+
check_all = [a for a in item_values if map_check(rangelist,a) != True]
|
|
2000
|
+
if len(check_all)>0: return {"result":False,"bad_values":check_all}
|
|
2001
|
+
else: return {"result":True}
|
|
2002
|
+
|
|
2003
|
+
def validate_item_enum(self,item_name,item_value):
|
|
2004
|
+
try:
|
|
2005
|
+
enum_list = self[item_name][self.enum_spec][:]
|
|
2006
|
+
except KeyError:
|
|
2007
|
+
return {"result":None}
|
|
2008
|
+
enum_list.append(".") #default value
|
|
2009
|
+
enum_list.append("?") #unknown
|
|
2010
|
+
item_values = listify(item_value)
|
|
2011
|
+
#print("Enum check: {!r} in {!r}".format(item_values, enum_list))
|
|
2012
|
+
check_all = [a for a in item_values if a not in enum_list]
|
|
2013
|
+
if len(check_all)>0: return {"result":False,"bad_values":check_all}
|
|
2014
|
+
else: return {"result":True}
|
|
2015
|
+
|
|
2016
|
+
def validate_looping(self,item_name,item_value):
|
|
2017
|
+
try:
|
|
2018
|
+
must_loop = self[item_name][self.must_loop_spec]
|
|
2019
|
+
except KeyError:
|
|
2020
|
+
return {"result":None}
|
|
2021
|
+
if must_loop == 'yes' and isinstance(item_value,(unicode,str)): # not looped
|
|
2022
|
+
return {"result":False} #this could be triggered
|
|
2023
|
+
if must_loop == 'no' and not isinstance(item_value,(unicode,str)):
|
|
2024
|
+
return {"result":False}
|
|
2025
|
+
return {"result":True}
|
|
2026
|
+
|
|
2027
|
+
def validate_looping_ddlm(self,loop_names):
|
|
2028
|
+
"""Check that all names are loopable"""
|
|
2029
|
+
truly_loopy = self.get_final_cats(loop_names)
|
|
2030
|
+
if len(truly_loopy)<len(loop_names): #some are bad
|
|
2031
|
+
categories = [(a,self[a][self.cat_spec].lower()) for a in loop_names]
|
|
2032
|
+
not_looped = [a[0] for a in categories if a[1] not in self.parent_lookup.keys()]
|
|
2033
|
+
return {"result":False,"bad_items":not_looped}
|
|
2034
|
+
return {"result":True}
|
|
2035
|
+
|
|
2036
|
+
|
|
2037
|
+
def validate_loop_membership(self,loop_names):
|
|
2038
|
+
final_cat = self.get_final_cats(loop_names)
|
|
2039
|
+
bad_items = [a for a in final_cat if a != final_cat[0]]
|
|
2040
|
+
if len(bad_items)>0:
|
|
2041
|
+
return {"result":False,"bad_items":bad_items}
|
|
2042
|
+
else: return {"result":True}
|
|
2043
|
+
|
|
2044
|
+
def get_final_cats(self,loop_names):
|
|
2045
|
+
"""Return a list of the uppermost parent categories for the loop_names. Names
|
|
2046
|
+
that are not from loopable categories are ignored."""
|
|
2047
|
+
try:
|
|
2048
|
+
categories = [self[a][self.cat_spec].lower() for a in loop_names]
|
|
2049
|
+
except KeyError: #category_id is mandatory
|
|
2050
|
+
raise ValidCifError( "%s missing from dictionary %s for item in loop containing %s" % (self.cat_spec,self.dicname,loop_names[0]))
|
|
2051
|
+
truly_looped = [a for a in categories if a in self.parent_lookup.keys()]
|
|
2052
|
+
return [self.parent_lookup[a] for a in truly_looped]
|
|
2053
|
+
|
|
2054
|
+
def validate_loop_key(self,loop_names):
|
|
2055
|
+
category = self[loop_names[0]][self.cat_spec]
|
|
2056
|
+
# find any unique values which must be present
|
|
2057
|
+
key_spec = self[category].get(self.key_spec,[])
|
|
2058
|
+
for names_to_check in key_spec:
|
|
2059
|
+
if isinstance(names_to_check,unicode): #only one
|
|
2060
|
+
names_to_check = [names_to_check]
|
|
2061
|
+
for loop_key in names_to_check:
|
|
2062
|
+
if loop_key not in loop_names:
|
|
2063
|
+
#is this one of those dang implicit items?
|
|
2064
|
+
if self[loop_key].get(self.must_exist_spec,None) == "implicit":
|
|
2065
|
+
continue #it is virtually there...
|
|
2066
|
+
alternates = self.get_alternates(loop_key)
|
|
2067
|
+
if alternates == []:
|
|
2068
|
+
return {"result":False,"bad_items":loop_key}
|
|
2069
|
+
for alt_names in alternates:
|
|
2070
|
+
alt = [a for a in alt_names if a in loop_names]
|
|
2071
|
+
if len(alt) == 0:
|
|
2072
|
+
return {"result":False,"bad_items":loop_key} # no alternates
|
|
2073
|
+
return {"result":True}
|
|
2074
|
+
|
|
2075
|
+
def validate_loop_key_ddlm(self,loop_names):
|
|
2076
|
+
"""Make sure at least one of the necessary keys are available"""
|
|
2077
|
+
final_cats = self.get_final_cats(loop_names)
|
|
2078
|
+
if len(final_cats)>0:
|
|
2079
|
+
poss_keys = self.cat_key_table[final_cats[0]][0] #
|
|
2080
|
+
found_keys = [a for a in poss_keys if a in loop_names]
|
|
2081
|
+
if len(found_keys)>0:
|
|
2082
|
+
return {"result":True}
|
|
2083
|
+
else:
|
|
2084
|
+
return {"result":False,"bad_items":poss_keys}
|
|
2085
|
+
else:
|
|
2086
|
+
return {"result":True}
|
|
2087
|
+
|
|
2088
|
+
def validate_loop_references(self,loop_names):
|
|
2089
|
+
must_haves = [self[a].get(self.list_ref_spec,None) for a in loop_names]
|
|
2090
|
+
must_haves = [a for a in must_haves if a != None]
|
|
2091
|
+
# build a flat list. For efficiency we don't remove duplicates,as
|
|
2092
|
+
# we expect no more than the order of 10 or 20 looped names.
|
|
2093
|
+
def flat_func(a,b):
|
|
2094
|
+
if isinstance(b,unicode):
|
|
2095
|
+
a.append(b) #single name
|
|
2096
|
+
else:
|
|
2097
|
+
a.extend(b) #list of names
|
|
2098
|
+
return a
|
|
2099
|
+
flat_mh = []
|
|
2100
|
+
[flat_func(flat_mh,a) for a in must_haves]
|
|
2101
|
+
group_mh = filter(lambda a:a[-1]=="_",flat_mh)
|
|
2102
|
+
single_mh = filter(lambda a:a[-1]!="_",flat_mh)
|
|
2103
|
+
res = [a for a in single_mh if a not in loop_names]
|
|
2104
|
+
def check_gr(s_item, name_list):
|
|
2105
|
+
nl = map(lambda a:a[:len(s_item)],name_list)
|
|
2106
|
+
if s_item in nl: return True
|
|
2107
|
+
return False
|
|
2108
|
+
res_g = [a for a in group_mh if check_gr(a,loop_names)]
|
|
2109
|
+
if len(res) == 0 and len(res_g) == 0: return {"result":True}
|
|
2110
|
+
# construct alternate list
|
|
2111
|
+
alternates = map(lambda a: (a,self.get_alternates(a)),res)
|
|
2112
|
+
alternates = [a for a in alternates if a[1] != []]
|
|
2113
|
+
# next line purely for error reporting
|
|
2114
|
+
missing_alts = [a[0] for a in alternates if a[1] == []]
|
|
2115
|
+
if len(alternates) != len(res):
|
|
2116
|
+
return {"result":False,"bad_items":missing_alts} #short cut; at least one
|
|
2117
|
+
#doesn't have an altern
|
|
2118
|
+
#loop over alternates
|
|
2119
|
+
for orig_name,alt_names in alternates:
|
|
2120
|
+
alt = [a for a in alt_names if a in loop_names]
|
|
2121
|
+
if len(alt) == 0: return {"result":False,"bad_items":orig_name}# no alternates
|
|
2122
|
+
return {"result":True} #found alternates
|
|
2123
|
+
|
|
2124
|
+
def get_alternates(self,main_name,exclusive_only=False):
|
|
2125
|
+
alternates = self[main_name].get(self.related_func,None)
|
|
2126
|
+
alt_names = []
|
|
2127
|
+
if alternates != None:
|
|
2128
|
+
alt_names = self[main_name].get(self.related_item,None)
|
|
2129
|
+
if isinstance(alt_names,unicode):
|
|
2130
|
+
alt_names = [alt_names]
|
|
2131
|
+
alternates = [alternates]
|
|
2132
|
+
together = zip(alt_names,alternates)
|
|
2133
|
+
if exclusive_only:
|
|
2134
|
+
alt_names = [a for a in together if a[1]=="alternate_exclusive" \
|
|
2135
|
+
or a[1]=="replace"]
|
|
2136
|
+
else:
|
|
2137
|
+
alt_names = [a for a in together if a[1]=="alternate" or a[1]=="replace"]
|
|
2138
|
+
alt_names = list([a[0] for a in alt_names])
|
|
2139
|
+
# now do the alias thing
|
|
2140
|
+
alias_names = listify(self[main_name].get("_item_aliases.alias_name",[]))
|
|
2141
|
+
alt_names.extend(alias_names)
|
|
2142
|
+
# print("Alternates for {}: {!r}".format(main_name, alt_names))
|
|
2143
|
+
return alt_names
|
|
2144
|
+
|
|
2145
|
+
|
|
2146
|
+
def validate_exclusion(self,item_name,item_value,whole_block,provisional_items={},globals={}):
|
|
2147
|
+
alternates = [a.lower() for a in self.get_alternates(item_name,exclusive_only=True)]
|
|
2148
|
+
item_name_list = [a.lower() for a in whole_block.keys()]
|
|
2149
|
+
item_name_list.extend([a.lower() for a in provisional_items.keys()])
|
|
2150
|
+
bad = [a for a in alternates if a in item_name_list]
|
|
2151
|
+
if len(bad)>0:
|
|
2152
|
+
print("Bad: %s, alternates %s" % (repr(bad),repr(alternates)))
|
|
2153
|
+
return {"result":False,"bad_items":bad}
|
|
2154
|
+
else: return {"result":True}
|
|
2155
|
+
|
|
2156
|
+
# validate that parent exists and contains matching values
|
|
2157
|
+
def validate_parent(self,item_name,item_value,whole_block,provisional_items={},globals={}):
|
|
2158
|
+
parent_item = self[item_name].get(self.parent_spec)
|
|
2159
|
+
if not parent_item: return {"result":None} #no parent specified
|
|
2160
|
+
if isinstance(parent_item,list):
|
|
2161
|
+
parent_item = parent_item[0]
|
|
2162
|
+
if self.optimize:
|
|
2163
|
+
if parent_item in self.done_parents:
|
|
2164
|
+
return {"result":None}
|
|
2165
|
+
else:
|
|
2166
|
+
self.done_parents.append(parent_item)
|
|
2167
|
+
print("Done parents %s" % repr(self.done_parents))
|
|
2168
|
+
# initialise parent/child values
|
|
2169
|
+
if isinstance(item_value,unicode):
|
|
2170
|
+
child_values = [item_value]
|
|
2171
|
+
else: child_values = item_value[:] #copy for safety
|
|
2172
|
+
# track down the parent
|
|
2173
|
+
# print("Looking for {} parent item {} in {!r}".format(item_name, parent_item, whole_block))
|
|
2174
|
+
# if globals contains the parent values, we are doing a DDL2 dictionary, and so
|
|
2175
|
+
# we have collected all parent values into the global block - so no need to search
|
|
2176
|
+
# for them elsewhere.
|
|
2177
|
+
# print("Looking for {!r}".format(parent_item))
|
|
2178
|
+
parent_values = globals.get(parent_item)
|
|
2179
|
+
if not parent_values:
|
|
2180
|
+
parent_values = provisional_items.get(parent_item,whole_block.get(parent_item))
|
|
2181
|
+
if not parent_values:
|
|
2182
|
+
# go for alternates
|
|
2183
|
+
namespace = whole_block.keys()
|
|
2184
|
+
namespace.extend(provisional_items.keys())
|
|
2185
|
+
namespace.extend(globals.keys())
|
|
2186
|
+
alt_names = filter_present(self.get_alternates(parent_item),namespace)
|
|
2187
|
+
if len(alt_names) == 0:
|
|
2188
|
+
if len([a for a in child_values if a != "." and a != "?"])>0:
|
|
2189
|
+
return {"result":False,"parent":parent_item}#no parent available -> error
|
|
2190
|
+
else:
|
|
2191
|
+
return {"result":None} #maybe True is more appropriate??
|
|
2192
|
+
parent_item = alt_names[0] #should never be more than one??
|
|
2193
|
+
parent_values = provisional_items.get(parent_item,whole_block.get(parent_item))
|
|
2194
|
+
if not parent_values: # check global block
|
|
2195
|
+
parent_values = globals.get(parent_item)
|
|
2196
|
+
if isinstance(parent_values,unicode):
|
|
2197
|
+
parent_values = [parent_values]
|
|
2198
|
+
#print("Checking parent %s against %s, values %r/%r" % (parent_item,
|
|
2199
|
+
# item_name, parent_values, child_values))
|
|
2200
|
+
missing = self.check_parent_child(parent_values,child_values)
|
|
2201
|
+
if len(missing) > 0:
|
|
2202
|
+
return {"result":False,"bad_values":missing,"parent":parent_item}
|
|
2203
|
+
return {"result":True}
|
|
2204
|
+
|
|
2205
|
+
def validate_child(self,item_name,item_value,whole_block,provisional_items={},globals={}):
|
|
2206
|
+
try:
|
|
2207
|
+
child_items = self[item_name][self.child_spec][:] #copy
|
|
2208
|
+
except KeyError:
|
|
2209
|
+
return {"result":None} #not relevant
|
|
2210
|
+
# special case for dictionaries -> we check parents of children only
|
|
2211
|
+
if item_name in globals: #dictionary so skip
|
|
2212
|
+
return {"result":None}
|
|
2213
|
+
if isinstance(child_items,unicode): # only one child
|
|
2214
|
+
child_items = [child_items]
|
|
2215
|
+
if isinstance(item_value,unicode): # single value
|
|
2216
|
+
parent_values = [item_value]
|
|
2217
|
+
else: parent_values = item_value[:]
|
|
2218
|
+
# expand child list with list of alternates
|
|
2219
|
+
for child_item in child_items[:]:
|
|
2220
|
+
child_items.extend(self.get_alternates(child_item))
|
|
2221
|
+
# now loop over the children
|
|
2222
|
+
for child_item in child_items:
|
|
2223
|
+
if self.optimize:
|
|
2224
|
+
if child_item in self.done_children:
|
|
2225
|
+
return {"result":None}
|
|
2226
|
+
else:
|
|
2227
|
+
self.done_children.append(child_item)
|
|
2228
|
+
print("Done children %s" % repr(self.done_children))
|
|
2229
|
+
if child_item in provisional_items:
|
|
2230
|
+
child_values = provisional_items[child_item][:]
|
|
2231
|
+
elif child_item in whole_block:
|
|
2232
|
+
child_values = whole_block[child_item][:]
|
|
2233
|
+
else: continue
|
|
2234
|
+
if isinstance(child_values,unicode):
|
|
2235
|
+
child_values = [child_values]
|
|
2236
|
+
# print("Checking child %s against %s, values %r/%r" % (child_item,
|
|
2237
|
+
# item_name, child_values, parent_values))
|
|
2238
|
+
missing = self.check_parent_child(parent_values,child_values)
|
|
2239
|
+
if len(missing)>0:
|
|
2240
|
+
return {"result":False,"bad_values":missing,"child":child_item}
|
|
2241
|
+
return {"result":True} #could mean that no child items present
|
|
2242
|
+
|
|
2243
|
+
#a generic checker: all child vals should appear in parent_vals
|
|
2244
|
+
def check_parent_child(self,parent_vals,child_vals):
|
|
2245
|
+
# shield ourselves from dots and question marks
|
|
2246
|
+
pv = parent_vals[:]
|
|
2247
|
+
pv.extend([".","?"])
|
|
2248
|
+
res = [a for a in child_vals if a not in pv]
|
|
2249
|
+
#print("Missing: %s" % res)
|
|
2250
|
+
return res
|
|
2251
|
+
|
|
2252
|
+
def validate_remove_parent_child(self,item_name,whole_block):
|
|
2253
|
+
try:
|
|
2254
|
+
child_items = self[item_name][self.child_spec]
|
|
2255
|
+
except KeyError:
|
|
2256
|
+
return {"result":None}
|
|
2257
|
+
if isinstance(child_items,unicode): # only one child
|
|
2258
|
+
child_items = [child_items]
|
|
2259
|
+
for child_item in child_items:
|
|
2260
|
+
if child_item in whole_block:
|
|
2261
|
+
return {"result":False,"child":child_item}
|
|
2262
|
+
return {"result":True}
|
|
2263
|
+
|
|
2264
|
+
def validate_dependents(self,item_name,item_value,whole_block,prov={},globals={}):
|
|
2265
|
+
try:
|
|
2266
|
+
dep_items = self[item_name][self.dep_spec][:]
|
|
2267
|
+
except KeyError:
|
|
2268
|
+
return {"result":None} #not relevant
|
|
2269
|
+
if isinstance(dep_items,unicode):
|
|
2270
|
+
dep_items = [dep_items]
|
|
2271
|
+
actual_names = whole_block.keys()
|
|
2272
|
+
actual_names.extend(prov.keys())
|
|
2273
|
+
actual_names.extend(globals.keys())
|
|
2274
|
+
missing = [a for a in dep_items if a not in actual_names]
|
|
2275
|
+
if len(missing) > 0:
|
|
2276
|
+
alternates = map(lambda a:[self.get_alternates(a),a],missing)
|
|
2277
|
+
# compact way to get a list of alternative items which are
|
|
2278
|
+
# present
|
|
2279
|
+
have_check = [(filter_present(b[0],actual_names),
|
|
2280
|
+
b[1]) for b in alternates]
|
|
2281
|
+
have_check = list([a for a in have_check if len(a[0])==0])
|
|
2282
|
+
if len(have_check) > 0:
|
|
2283
|
+
have_check = [a[1] for a in have_check]
|
|
2284
|
+
return {"result":False,"bad_items":have_check}
|
|
2285
|
+
return {"result":True}
|
|
2286
|
+
|
|
2287
|
+
def validate_uniqueness(self,item_name,item_value,whole_block,provisional_items={},
|
|
2288
|
+
globals={}):
|
|
2289
|
+
category = self[item_name].get(self.cat_spec)
|
|
2290
|
+
if category == None:
|
|
2291
|
+
print("No category found for %s" % item_name)
|
|
2292
|
+
return {"result":None}
|
|
2293
|
+
# print("Category {!r} for item {}".format(category, item_name))
|
|
2294
|
+
# we make a copy in the following as we will be removing stuff later!
|
|
2295
|
+
unique_i = self[category].get("_category_key.name",[])[:]
|
|
2296
|
+
if isinstance(unique_i,unicode):
|
|
2297
|
+
unique_i = [unique_i]
|
|
2298
|
+
if item_name not in unique_i: #no need to verify
|
|
2299
|
+
return {"result":None}
|
|
2300
|
+
if isinstance(item_value,unicode): #not looped
|
|
2301
|
+
return {"result":None}
|
|
2302
|
+
# print("Checking %s -> %s -> %s ->Unique: %r" % (item_name,category,catentry, unique_i))
|
|
2303
|
+
# check that we can't optimize by not doing this check
|
|
2304
|
+
if self.optimize:
|
|
2305
|
+
if unique_i in self.done_keys:
|
|
2306
|
+
return {"result":None}
|
|
2307
|
+
else:
|
|
2308
|
+
self.done_keys.append(unique_i)
|
|
2309
|
+
val_list = []
|
|
2310
|
+
# get the matching data from any other data items
|
|
2311
|
+
unique_i.remove(item_name)
|
|
2312
|
+
other_data = []
|
|
2313
|
+
if len(unique_i) > 0: # i.e. do have others to think about
|
|
2314
|
+
for other_name in unique_i:
|
|
2315
|
+
# we look for the value first in the provisional dict, then the main block
|
|
2316
|
+
# the logic being that anything in the provisional dict overrides the
|
|
2317
|
+
# main block
|
|
2318
|
+
if other_name in provisional_items:
|
|
2319
|
+
other_data.append(provisional_items[other_name])
|
|
2320
|
+
elif other_name in whole_block:
|
|
2321
|
+
other_data.append(whole_block[other_name])
|
|
2322
|
+
elif self[other_name].get(self.must_exist_spec)=="implicit":
|
|
2323
|
+
other_data.append([item_name]*len(item_value)) #placeholder
|
|
2324
|
+
else:
|
|
2325
|
+
return {"result":False,"bad_items":other_name}#missing data name
|
|
2326
|
+
# ok, so we go through all of our values
|
|
2327
|
+
# this works by comparing lists of strings to one other, and
|
|
2328
|
+
# so could be fooled if you think that '1.' and '1' are
|
|
2329
|
+
# identical
|
|
2330
|
+
for i in range(len(item_value)):
|
|
2331
|
+
#print("Value no. %d" % i, end=" ")
|
|
2332
|
+
this_entry = item_value[i]
|
|
2333
|
+
for j in range(len(other_data)):
|
|
2334
|
+
this_entry = " ".join([this_entry,other_data[j][i]])
|
|
2335
|
+
#print("Looking for {!r} in {!r}: ".format(this_entry, val_list))
|
|
2336
|
+
if this_entry in val_list:
|
|
2337
|
+
return {"result":False,"bad_values":this_entry}
|
|
2338
|
+
val_list.append(this_entry)
|
|
2339
|
+
return {"result":True}
|
|
2340
|
+
|
|
2341
|
+
|
|
2342
|
+
def validate_mandatory_category(self,whole_block):
|
|
2343
|
+
mand_cats = [self[a]['_category.id'] for a in self.keys() if self[a].get("_category.mandatory_code","no")=="yes"]
|
|
2344
|
+
if len(mand_cats) == 0:
|
|
2345
|
+
return {"result":True}
|
|
2346
|
+
# print("Mandatory categories - {!r}".format(mand_cats)
|
|
2347
|
+
# find which categories each of our datanames belongs to
|
|
2348
|
+
all_cats = [self[a].get(self.cat_spec) for a in whole_block.keys()]
|
|
2349
|
+
missing = set(mand_cats) - set(all_cats)
|
|
2350
|
+
if len(missing) > 0:
|
|
2351
|
+
return {"result":False,"bad_items":repr(missing)}
|
|
2352
|
+
return {"result":True}
|
|
2353
|
+
|
|
2354
|
+
def check_mandatory_items(self,whole_block,default_scope='Item'):
|
|
2355
|
+
"""Return an error if any mandatory items are missing"""
|
|
2356
|
+
if len(self.scopes_mandatory)== 0: return {"result":True}
|
|
2357
|
+
if default_scope == 'Datablock':
|
|
2358
|
+
return {"result":True} #is a data file
|
|
2359
|
+
scope = whole_block.get('_definition.scope',default_scope)
|
|
2360
|
+
if '_dictionary.title' in whole_block:
|
|
2361
|
+
scope = 'Dictionary'
|
|
2362
|
+
missing = list([a for a in self.scopes_mandatory[scope] if a not in whole_block])
|
|
2363
|
+
if len(missing)==0:
|
|
2364
|
+
return {"result":True}
|
|
2365
|
+
else:
|
|
2366
|
+
return {"result":False,"bad_items":missing}
|
|
2367
|
+
|
|
2368
|
+
def check_prohibited_items(self,whole_block,default_scope='Item'):
|
|
2369
|
+
"""Return an error if any prohibited items are present"""
|
|
2370
|
+
if len(self.scopes_naughty)== 0: return {"result":True}
|
|
2371
|
+
if default_scope == 'Datablock':
|
|
2372
|
+
return {"result":True} #is a data file
|
|
2373
|
+
scope = whole_block.get('_definition.scope',default_scope)
|
|
2374
|
+
if '_dictionary.title' in whole_block:
|
|
2375
|
+
scope = 'Dictionary'
|
|
2376
|
+
present = list([a for a in self.scopes_naughty[scope] if a in whole_block])
|
|
2377
|
+
if len(present)==0:
|
|
2378
|
+
return {"result":True}
|
|
2379
|
+
else:
|
|
2380
|
+
return {"result":False,"bad_items":present}
|
|
2381
|
+
|
|
2382
|
+
|
|
2383
|
+
def run_item_validation(self,item_name,item_value):
|
|
2384
|
+
return {item_name:list([(f.__name__,f(item_name,item_value)) for f in self.item_validation_funs])}
|
|
2385
|
+
|
|
2386
|
+
def run_loop_validation(self,loop_names):
|
|
2387
|
+
return {loop_names[0]:list([(f.__name__,f(loop_names)) for f in self.loop_validation_funs])}
|
|
2388
|
+
|
|
2389
|
+
def run_global_validation(self,item_name,item_value,data_block,provisional_items={},globals={}):
|
|
2390
|
+
results = list([(f.__name__,f(item_name,item_value,data_block,provisional_items,globals)) for f in self.global_validation_funs])
|
|
2391
|
+
return {item_name:results}
|
|
2392
|
+
|
|
2393
|
+
def run_block_validation(self,whole_block,block_scope='Item'):
|
|
2394
|
+
results = list([(f.__name__,f(whole_block)) for f in self.block_validation_funs])
|
|
2395
|
+
# fix up the return values
|
|
2396
|
+
return {"whole_block":results}
|
|
2397
|
+
|
|
2398
|
+
def optimize_on(self):
|
|
2399
|
+
self.optimize = True
|
|
2400
|
+
self.done_keys = []
|
|
2401
|
+
self.done_children = []
|
|
2402
|
+
self.done_parents = []
|
|
2403
|
+
|
|
2404
|
+
def optimize_off(self):
|
|
2405
|
+
self.optimize = False
|
|
2406
|
+
self.done_keys = []
|
|
2407
|
+
self.done_children = []
|
|
2408
|
+
self.done_parents = []
|
|
2409
|
+
|
|
2410
|
+
|
|
2411
|
+
|
|
2412
|
+
class ValidCifBlock(CifBlock):
|
|
2413
|
+
"""A `CifBlock` that is valid with respect to a given CIF dictionary. Methods
|
|
2414
|
+
of `CifBlock` are overridden where necessary to disallow addition of invalid items to the
|
|
2415
|
+
`CifBlock`.
|
|
2416
|
+
|
|
2417
|
+
## Initialisation
|
|
2418
|
+
|
|
2419
|
+
* `dic` is a `CifDic` object to be used for validation.
|
|
2420
|
+
|
|
2421
|
+
"""
|
|
2422
|
+
def __init__(self,dic = None, diclist=[], mergemode = "replace",*args,**kwords):
|
|
2423
|
+
CifBlock.__init__(self,*args,**kwords)
|
|
2424
|
+
if dic and diclist:
|
|
2425
|
+
print("Warning: diclist argument ignored when initialising ValidCifBlock")
|
|
2426
|
+
if isinstance(dic,CifDic):
|
|
2427
|
+
self.fulldic = dic
|
|
2428
|
+
else:
|
|
2429
|
+
raise TypeError( "ValidCifBlock passed non-CifDic type in dic argument")
|
|
2430
|
+
if len(diclist)==0 and not dic:
|
|
2431
|
+
raise ValidCifError( "At least one dictionary must be specified")
|
|
2432
|
+
if diclist and not dic:
|
|
2433
|
+
self.fulldic = merge_dic(diclist,mergemode)
|
|
2434
|
+
if not self.run_data_checks()[0]:
|
|
2435
|
+
raise ValidCifError( self.report())
|
|
2436
|
+
|
|
2437
|
+
def run_data_checks(self,verbose=False):
|
|
2438
|
+
self.v_result = {}
|
|
2439
|
+
self.fulldic.optimize_on()
|
|
2440
|
+
for dataname in self.keys():
|
|
2441
|
+
update_value(self.v_result,self.fulldic.run_item_validation(dataname,self[dataname]))
|
|
2442
|
+
update_value(self.v_result,self.fulldic.run_global_validation(dataname,self[dataname],self))
|
|
2443
|
+
for loop_names in self.loops.values():
|
|
2444
|
+
update_value(self.v_result,self.fulldic.run_loop_validation(loop_names))
|
|
2445
|
+
# now run block-level checks
|
|
2446
|
+
update_value(self.v_result,self.fulldic.run_block_validation(self))
|
|
2447
|
+
# return false and list of baddies if anything didn't match
|
|
2448
|
+
self.fulldic.optimize_off()
|
|
2449
|
+
all_keys = list(self.v_result.keys()) #dictionary will change
|
|
2450
|
+
for test_key in all_keys:
|
|
2451
|
+
#print("%s: %r" % (test_key, self.v_result[test_key]))
|
|
2452
|
+
self.v_result[test_key] = [a for a in self.v_result[test_key] if a[1]["result"]==False]
|
|
2453
|
+
if len(self.v_result[test_key]) == 0:
|
|
2454
|
+
del self.v_result[test_key]
|
|
2455
|
+
isvalid = len(self.v_result)==0
|
|
2456
|
+
#if not isvalid:
|
|
2457
|
+
# print("Baddies: {!r}".format(self.v_result))
|
|
2458
|
+
return isvalid,self.v_result
|
|
2459
|
+
|
|
2460
|
+
def single_item_check(self,item_name,item_value):
|
|
2461
|
+
#self.match_single_item(item_name)
|
|
2462
|
+
if item_name not in self.fulldic:
|
|
2463
|
+
result = {item_name:[]}
|
|
2464
|
+
else:
|
|
2465
|
+
result = self.fulldic.run_item_validation(item_name,item_value)
|
|
2466
|
+
baddies = list([a for a in result[item_name] if a[1]["result"]==False])
|
|
2467
|
+
# if even one false one is found, this should trigger
|
|
2468
|
+
isvalid = (len(baddies) == 0)
|
|
2469
|
+
# if not isvalid: print("Failures for {}: {!r}".format(item_name, baddies))
|
|
2470
|
+
return isvalid,baddies
|
|
2471
|
+
|
|
2472
|
+
def loop_item_check(self,loop_names):
|
|
2473
|
+
in_dic_names = list([a for a in loop_names if a in self.fulldic])
|
|
2474
|
+
if len(in_dic_names)==0:
|
|
2475
|
+
result = {loop_names[0]:[]}
|
|
2476
|
+
else:
|
|
2477
|
+
result = self.fulldic.run_loop_validation(in_dic_names)
|
|
2478
|
+
baddies = list([a for a in result[in_dic_names[0]] if a[1]["result"]==False])
|
|
2479
|
+
# if even one false one is found, this should trigger
|
|
2480
|
+
isvalid = (len(baddies) == 0)
|
|
2481
|
+
# if not isvalid: print("Failures for {}: {!r}".format(loop_names, baddies))
|
|
2482
|
+
return isvalid,baddies
|
|
2483
|
+
|
|
2484
|
+
def global_item_check(self,item_name,item_value,provisional_items={}):
|
|
2485
|
+
if item_name not in self.fulldic:
|
|
2486
|
+
result = {item_name:[]}
|
|
2487
|
+
else:
|
|
2488
|
+
result = self.fulldic.run_global_validation(item_name,
|
|
2489
|
+
item_value,self,provisional_items = provisional_items)
|
|
2490
|
+
baddies = list([a for a in result[item_name] if a[1]["result"] is False])
|
|
2491
|
+
# if even one false one is found, this should trigger
|
|
2492
|
+
isvalid = (len(baddies) == 0)
|
|
2493
|
+
# if not isvalid: print("Failures for {}: {!r}".format(item_name, baddies))
|
|
2494
|
+
return isvalid,baddies
|
|
2495
|
+
|
|
2496
|
+
def remove_global_item_check(self,item_name):
|
|
2497
|
+
if item_name not in self.fulldic:
|
|
2498
|
+
result = {item_name:[]}
|
|
2499
|
+
else:
|
|
2500
|
+
result = self.fulldic.run_remove_global_validation(item_name,self,False)
|
|
2501
|
+
baddies = list([a for a in result[item_name] if a[1]["result"]==False])
|
|
2502
|
+
# if even one false one is found, this should trigger
|
|
2503
|
+
isvalid = (len(baddies) == 0)
|
|
2504
|
+
# if not isvalid: print("Failures for {}: {!r}".format(item_name, baddies))
|
|
2505
|
+
return isvalid,baddies
|
|
2506
|
+
|
|
2507
|
+
def AddToLoop(self,dataname,loopdata):
|
|
2508
|
+
# single item checks
|
|
2509
|
+
paired_data = loopdata.items()
|
|
2510
|
+
for name,value in paired_data:
|
|
2511
|
+
valid,problems = self.single_item_check(name,value)
|
|
2512
|
+
self.report_if_invalid(valid,problems)
|
|
2513
|
+
# loop item checks; merge with current loop
|
|
2514
|
+
found = 0
|
|
2515
|
+
for aloop in self.block["loops"]:
|
|
2516
|
+
if dataname in aloop:
|
|
2517
|
+
loopnames = aloop.keys()
|
|
2518
|
+
for new_name in loopdata.keys():
|
|
2519
|
+
if new_name not in loopnames: loopnames.append(new_name)
|
|
2520
|
+
valid,problems = self.looped_item_check(loopnames)
|
|
2521
|
+
self.report_if_invalid(valid,problems)
|
|
2522
|
+
prov_dict = loopdata.copy()
|
|
2523
|
+
for name,value in paired_data:
|
|
2524
|
+
del prov_dict[name] # remove temporarily
|
|
2525
|
+
valid,problems = self.global_item_check(name,value,prov_dict)
|
|
2526
|
+
prov_dict[name] = value # add back in
|
|
2527
|
+
self.report_if_invalid(valid,problems)
|
|
2528
|
+
CifBlock.AddToLoop(self,dataname,loopdata)
|
|
2529
|
+
|
|
2530
|
+
def AddCifItem(self,data):
|
|
2531
|
+
if isinstance(data[0],(unicode,str)): # single item
|
|
2532
|
+
valid,problems = self.single_item_check(data[0],data[1])
|
|
2533
|
+
self.report_if_invalid(valid,problems,data[0])
|
|
2534
|
+
valid,problems = self.global_item_check(data[0],data[1])
|
|
2535
|
+
self.report_if_invalid(valid,problems,data[0])
|
|
2536
|
+
elif isinstance(data[0],tuple) or isinstance(data[0],list):
|
|
2537
|
+
paired_data = list(zip(data[0],data[1]))
|
|
2538
|
+
for name,value in paired_data:
|
|
2539
|
+
valid,problems = self.single_item_check(name,value)
|
|
2540
|
+
self.report_if_invalid(valid,problems,name)
|
|
2541
|
+
valid,problems = self.loop_item_check(data[0])
|
|
2542
|
+
self.report_if_invalid(valid,problems,data[0])
|
|
2543
|
+
prov_dict = {} # for storing temporary items
|
|
2544
|
+
for name,value in paired_data: prov_dict[name]=value
|
|
2545
|
+
for name,value in paired_data:
|
|
2546
|
+
del prov_dict[name] # remove temporarily
|
|
2547
|
+
valid,problems = self.global_item_check(name,value,prov_dict)
|
|
2548
|
+
prov_dict[name] = value # add back in
|
|
2549
|
+
self.report_if_invalid(valid,problems,name)
|
|
2550
|
+
else:
|
|
2551
|
+
raise ValueError("Programming error: AddCifItem passed non-tuple,non-string item")
|
|
2552
|
+
super(ValidCifBlock,self).AddCifItem(data)
|
|
2553
|
+
|
|
2554
|
+
def AddItem(self,key,value,**kwargs):
|
|
2555
|
+
"""Set value of dataname `key` to `value` after checking for conformance with CIF dictionary"""
|
|
2556
|
+
valid,problems = self.single_item_check(key,value)
|
|
2557
|
+
self.report_if_invalid(valid,problems,key)
|
|
2558
|
+
valid,problems = self.global_item_check(key,value)
|
|
2559
|
+
self.report_if_invalid(valid,problems,key)
|
|
2560
|
+
super(ValidCifBlock,self).AddItem(key,value,**kwargs)
|
|
2561
|
+
|
|
2562
|
+
# utility function
|
|
2563
|
+
def report_if_invalid(self,valid,bad_list,data_name):
|
|
2564
|
+
if not valid:
|
|
2565
|
+
bad_tests = [a[0] for a in bad_list]
|
|
2566
|
+
error_string = ",".join(bad_tests)
|
|
2567
|
+
error_string = repr(data_name) + " fails following validity checks: " + error_string
|
|
2568
|
+
raise ValidCifError( error_string)
|
|
2569
|
+
|
|
2570
|
+
def __delitem__(self,key):
|
|
2571
|
+
# we don't need to run single item checks; we do need to run loop and
|
|
2572
|
+
# global checks.
|
|
2573
|
+
if key in self:
|
|
2574
|
+
try:
|
|
2575
|
+
loop_items = self.GetLoop(key)
|
|
2576
|
+
except TypeError:
|
|
2577
|
+
loop_items = []
|
|
2578
|
+
if loop_items: #need to check loop conformance
|
|
2579
|
+
loop_names = [a[0] for a in loop_items if a[0] != key]
|
|
2580
|
+
valid,problems = self.loop_item_check(loop_names)
|
|
2581
|
+
self.report_if_invalid(valid,problems)
|
|
2582
|
+
valid,problems = self.remove_global_item_check(key)
|
|
2583
|
+
self.report_if_invalid(valid,problems)
|
|
2584
|
+
self.RemoveCifItem(key)
|
|
2585
|
+
|
|
2586
|
+
|
|
2587
|
+
def report(self):
|
|
2588
|
+
outstr = StringIO()
|
|
2589
|
+
outstr.write( "Validation results\n")
|
|
2590
|
+
outstr.write( "------------------\n")
|
|
2591
|
+
print("%d invalid items found\n" % len(self.v_result))
|
|
2592
|
+
for item_name,val_func_list in self.v_result.items():
|
|
2593
|
+
outstr.write("%s fails following tests:\n" % item_name)
|
|
2594
|
+
for val_func in val_func_list:
|
|
2595
|
+
outstr.write("\t%s\n")
|
|
2596
|
+
return outstr.getvalue()
|
|
2597
|
+
|
|
2598
|
+
|
|
2599
|
+
class ValidCifFile(CifFile):
|
|
2600
|
+
"""A CIF file for which all datablocks are valid. Argument `dic` to
|
|
2601
|
+
initialisation specifies a `CifDic` object to use for validation."""
|
|
2602
|
+
def __init__(self,dic=None,diclist=[],mergemode="replace",*args,**kwargs):
|
|
2603
|
+
print("WARNING: ValidCifFile will be removed in the next release.")
|
|
2604
|
+
if not diclist and not dic and not hasattr(self,'bigdic'):
|
|
2605
|
+
raise ValidCifError( "At least one dictionary is required to create a ValidCifFile object")
|
|
2606
|
+
if not dic and diclist: #merge here for speed
|
|
2607
|
+
self.bigdic = merge_dic(diclist,mergemode)
|
|
2608
|
+
elif dic and not diclist:
|
|
2609
|
+
self.bigdic = dic
|
|
2610
|
+
emapsCifFile.__init__(self,*args,**kwargs)
|
|
2611
|
+
for blockname in self.keys():
|
|
2612
|
+
self.dictionary[blockname]=ValidCifBlock(data=self.dictionary[blockname],dic=self.bigdic)
|
|
2613
|
+
|
|
2614
|
+
def NewBlock(self,blockname,blockcontents,**kwargs):
|
|
2615
|
+
emapsCifFile.NewBlock(self,blockname,blockcontents,**kwargs)
|
|
2616
|
+
# dictionary[blockname] is now a CifBlock object. We
|
|
2617
|
+
# turn it into a ValidCifBlock object
|
|
2618
|
+
self.dictionary[blockname] = ValidCifBlock(dic=self.bigdic,
|
|
2619
|
+
data=self.dictionary[blockname])
|
|
2620
|
+
|
|
2621
|
+
|
|
2622
|
+
class ValidationResult:
|
|
2623
|
+
"""Represents validation result. It is initialised with """
|
|
2624
|
+
def __init__(self,results):
|
|
2625
|
+
"""results is return value of validate function"""
|
|
2626
|
+
self.valid_result, self.no_matches = results
|
|
2627
|
+
|
|
2628
|
+
def report(self,use_html):
|
|
2629
|
+
"""Return string with human-readable description of validation result"""
|
|
2630
|
+
return validate_report((self.valid_result, self.no_matches),use_html)
|
|
2631
|
+
|
|
2632
|
+
def is_valid(self,block_name=None):
|
|
2633
|
+
"""Return True for valid CIF file, otherwise False"""
|
|
2634
|
+
if block_name is not None:
|
|
2635
|
+
block_names = [block_name]
|
|
2636
|
+
else:
|
|
2637
|
+
block_names = self.valid_result.iterkeys()
|
|
2638
|
+
for block_name in block_names:
|
|
2639
|
+
if not self.valid_result[block_name] == (True,{}):
|
|
2640
|
+
valid = False
|
|
2641
|
+
break
|
|
2642
|
+
else:
|
|
2643
|
+
valid = True
|
|
2644
|
+
return valid
|
|
2645
|
+
|
|
2646
|
+
def has_no_match_items(self,block_name=None):
|
|
2647
|
+
"""Return true if some items are not found in dictionary"""
|
|
2648
|
+
if block_name is not None:
|
|
2649
|
+
block_names = [block_name]
|
|
2650
|
+
else:
|
|
2651
|
+
block_names = self.no_matches.iter_keys()
|
|
2652
|
+
for block_name in block_names:
|
|
2653
|
+
if self.no_matches[block_name]:
|
|
2654
|
+
has_no_match_items = True
|
|
2655
|
+
break
|
|
2656
|
+
else:
|
|
2657
|
+
has_no_match_items = False
|
|
2658
|
+
return has_no_match_items
|
|
2659
|
+
|
|
2660
|
+
|
|
2661
|
+
|
|
2662
|
+
def Validate(ciffile,dic = "", diclist=[],mergemode="replace",isdic=False):
|
|
2663
|
+
"""Validate the `ciffile` conforms to the definitions in `CifDic` object `dic`, or if `dic` is missing,
|
|
2664
|
+
to the results of merging the `CifDic` objects in `diclist` according to `mergemode`. Flag
|
|
2665
|
+
`isdic` indicates that `ciffile` is a CIF dictionary meaning that save frames should be
|
|
2666
|
+
accessed for validation and that mandatory_category should be interpreted differently for DDL2."""
|
|
2667
|
+
if not isinstance(ciffile,CifFile):
|
|
2668
|
+
check_file = CifFile(ciffile)
|
|
2669
|
+
else:
|
|
2670
|
+
check_file = ciffile
|
|
2671
|
+
if not dic:
|
|
2672
|
+
fulldic = merge_dic(diclist,mergemode)
|
|
2673
|
+
else:
|
|
2674
|
+
fulldic = dic
|
|
2675
|
+
no_matches = {}
|
|
2676
|
+
valid_result = {}
|
|
2677
|
+
if isdic: #assume one block only
|
|
2678
|
+
check_file.scoping = 'instance' #only data blocks visible
|
|
2679
|
+
top_level = check_file.keys()[0]
|
|
2680
|
+
check_file.scoping = 'dictionary' #all blocks visible
|
|
2681
|
+
# collect a list of parents for speed
|
|
2682
|
+
if fulldic.diclang == 'DDL2':
|
|
2683
|
+
poss_parents = fulldic.get_all("_item_linked.parent_name")
|
|
2684
|
+
for parent in poss_parents:
|
|
2685
|
+
curr_parent = listify(check_file.get(parent,[]))
|
|
2686
|
+
new_vals = check_file.get_all(parent)
|
|
2687
|
+
new_vals.extend(curr_parent)
|
|
2688
|
+
if len(new_vals)>0:
|
|
2689
|
+
check_file[parent] = new_vals
|
|
2690
|
+
print("Added %s (len %d)" % (parent,len(check_file[parent])))
|
|
2691
|
+
# now run the validations
|
|
2692
|
+
for block in check_file.keys():
|
|
2693
|
+
if isdic and block == top_level:
|
|
2694
|
+
block_scope = 'Dictionary'
|
|
2695
|
+
elif isdic:
|
|
2696
|
+
block_scope = 'Item'
|
|
2697
|
+
else:
|
|
2698
|
+
block_scope = 'Datablock'
|
|
2699
|
+
no_matches[block] = [a for a in check_file[block].keys() if a not in fulldic]
|
|
2700
|
+
# remove non-matching items
|
|
2701
|
+
print("Not matched: " + repr(no_matches[block]))
|
|
2702
|
+
for nogood in no_matches[block]:
|
|
2703
|
+
del check_file[block][nogood]
|
|
2704
|
+
print("Validating block %s, scope %s" % (block,block_scope))
|
|
2705
|
+
valid_result[block] = run_data_checks(check_file[block],fulldic,block_scope=block_scope)
|
|
2706
|
+
return valid_result,no_matches
|
|
2707
|
+
|
|
2708
|
+
def validate_report(val_result,use_html=False):
|
|
2709
|
+
valid_result,no_matches = val_result
|
|
2710
|
+
outstr = StringIO()
|
|
2711
|
+
if use_html:
|
|
2712
|
+
outstr.write("<h2>Validation results</h2>")
|
|
2713
|
+
else:
|
|
2714
|
+
outstr.write( "Validation results\n")
|
|
2715
|
+
outstr.write( "------------------\n")
|
|
2716
|
+
if len(valid_result) > 10:
|
|
2717
|
+
suppress_valid = True #don't clutter with valid messages
|
|
2718
|
+
if use_html:
|
|
2719
|
+
outstr.write("<p>For brevity, valid blocks are not reported in the output.</p>")
|
|
2720
|
+
else:
|
|
2721
|
+
suppress_valid = False
|
|
2722
|
+
for block in valid_result.keys():
|
|
2723
|
+
block_result = valid_result[block]
|
|
2724
|
+
if block_result[0]:
|
|
2725
|
+
out_line = "Block '%s' is VALID" % block
|
|
2726
|
+
else:
|
|
2727
|
+
out_line = "Block '%s' is INVALID" % block
|
|
2728
|
+
if use_html:
|
|
2729
|
+
if (block_result[0] and (not suppress_valid or len(no_matches[block])>0)) or not block_result[0]:
|
|
2730
|
+
outstr.write( "<h3>%s</h3><p>" % out_line)
|
|
2731
|
+
else:
|
|
2732
|
+
outstr.write( "\n %s\n" % out_line)
|
|
2733
|
+
if len(no_matches[block])!= 0:
|
|
2734
|
+
if use_html:
|
|
2735
|
+
outstr.write( "<p>The following items were not found in the dictionary")
|
|
2736
|
+
outstr.write(" (note that this does not invalidate the data block):</p>")
|
|
2737
|
+
outstr.write("<p><table>\n")
|
|
2738
|
+
[outstr.write("<tr><td>%s</td></tr>" % it) for it in no_matches[block]]
|
|
2739
|
+
outstr.write("</table>\n")
|
|
2740
|
+
else:
|
|
2741
|
+
outstr.write( "\n The following items were not found in the dictionary:\n")
|
|
2742
|
+
outstr.write("Note that this does not invalidate the data block\n")
|
|
2743
|
+
[outstr.write("%s\n" % it) for it in no_matches[block]]
|
|
2744
|
+
# now organise our results by type of error, not data item...
|
|
2745
|
+
error_type_dic = {}
|
|
2746
|
+
for error_item, error_list in block_result[1].items():
|
|
2747
|
+
for func_name,bad_result in error_list:
|
|
2748
|
+
bad_result.update({"item_name":error_item})
|
|
2749
|
+
try:
|
|
2750
|
+
error_type_dic[func_name].append(bad_result)
|
|
2751
|
+
except KeyError:
|
|
2752
|
+
error_type_dic[func_name] = [bad_result]
|
|
2753
|
+
# make a table of test name, test message
|
|
2754
|
+
info_table = {\
|
|
2755
|
+
'validate_item_type':\
|
|
2756
|
+
"The following data items had badly formed values",
|
|
2757
|
+
'validate_item_esd':\
|
|
2758
|
+
"The following data items should not have esds appended",
|
|
2759
|
+
'validate_enum_range':\
|
|
2760
|
+
"The following data items have values outside permitted range",
|
|
2761
|
+
'validate_item_enum':\
|
|
2762
|
+
"The following data items have values outside permitted set",
|
|
2763
|
+
'validate_looping':\
|
|
2764
|
+
"The following data items violate looping constraints",
|
|
2765
|
+
'validate_loop_membership':\
|
|
2766
|
+
"The following looped data names are of different categories to the first looped data name",
|
|
2767
|
+
'validate_loop_key':\
|
|
2768
|
+
"A required dataname for this category is missing from the loop\n containing the dataname",
|
|
2769
|
+
'validate_loop_key_ddlm':\
|
|
2770
|
+
"A loop key is missing for the category containing the dataname",
|
|
2771
|
+
'validate_loop_references':\
|
|
2772
|
+
"A dataname required by the item is missing from the loop",
|
|
2773
|
+
'validate_parent':\
|
|
2774
|
+
"A parent dataname is missing or contains different values",
|
|
2775
|
+
'validate_child':\
|
|
2776
|
+
"A child dataname contains different values to the parent",
|
|
2777
|
+
'validate_uniqueness':\
|
|
2778
|
+
"One or more data items do not take unique values",
|
|
2779
|
+
'validate_dependents':\
|
|
2780
|
+
"A dataname required by the item is missing from the data block",
|
|
2781
|
+
'validate_exclusion': \
|
|
2782
|
+
"Both dataname and exclusive alternates or aliases are present in data block",
|
|
2783
|
+
'validate_mandatory_category':\
|
|
2784
|
+
"A required category is missing from this block",
|
|
2785
|
+
'check_mandatory_items':\
|
|
2786
|
+
"A required data attribute is missing from this block",
|
|
2787
|
+
'check_prohibited_items':\
|
|
2788
|
+
"A prohibited data attribute is present in this block"}
|
|
2789
|
+
|
|
2790
|
+
for test_name,test_results in error_type_dic.items():
|
|
2791
|
+
if use_html:
|
|
2792
|
+
outstr.write(html_error_report(test_name,info_table[test_name],test_results))
|
|
2793
|
+
else:
|
|
2794
|
+
outstr.write(error_report(test_name,info_table[test_name],test_results))
|
|
2795
|
+
outstr.write("\n\n")
|
|
2796
|
+
return outstr.getvalue()
|
|
2797
|
+
|
|
2798
|
+
# A function to lay out a single error report. We are passed
|
|
2799
|
+
# the name of the error (one of our validation functions), the
|
|
2800
|
+
# explanation to print out, and a dictionary with the error
|
|
2801
|
+
# information. We print no more than 50 characters of the item
|
|
2802
|
+
|
|
2803
|
+
def error_report(error_name,error_explanation,error_dics):
|
|
2804
|
+
retstring = "\n\n " + error_explanation + ":\n\n"
|
|
2805
|
+
headstring = "%-32s" % "Item name"
|
|
2806
|
+
bodystring = ""
|
|
2807
|
+
if "bad_values" in error_dics[0]:
|
|
2808
|
+
headstring += "%-20s" % "Bad value(s)"
|
|
2809
|
+
if "bad_items" in error_dics[0]:
|
|
2810
|
+
headstring += "%-20s" % "Bad dataname(s)"
|
|
2811
|
+
if "child" in error_dics[0]:
|
|
2812
|
+
headstring += "%-20s" % "Child"
|
|
2813
|
+
if "parent" in error_dics[0]:
|
|
2814
|
+
headstring += "%-20s" % "Parent"
|
|
2815
|
+
headstring +="\n"
|
|
2816
|
+
for error in error_dics:
|
|
2817
|
+
bodystring += "\n%-32s" % error["item_name"]
|
|
2818
|
+
if "bad_values" in error:
|
|
2819
|
+
out_vals = [repr(a)[:50] for a in error["bad_values"]]
|
|
2820
|
+
bodystring += "%-20s" % out_vals
|
|
2821
|
+
if "bad_items" in error:
|
|
2822
|
+
bodystring += "%-20s" % repr(error["bad_items"])
|
|
2823
|
+
if "child" in error:
|
|
2824
|
+
bodystring += "%-20s" % repr(error["child"])
|
|
2825
|
+
if "parent" in error:
|
|
2826
|
+
bodystring += "%-20s" % repr(error["parent"])
|
|
2827
|
+
return retstring + headstring + bodystring
|
|
2828
|
+
|
|
2829
|
+
# This lays out an HTML error report
|
|
2830
|
+
|
|
2831
|
+
def html_error_report(error_name,error_explanation,error_dics,annotate=[]):
|
|
2832
|
+
retstring = "<h4>" + error_explanation + ":</h4>"
|
|
2833
|
+
retstring = retstring + "<table cellpadding=5><tr>"
|
|
2834
|
+
headstring = "<th>Item name</th>"
|
|
2835
|
+
bodystring = ""
|
|
2836
|
+
if "bad_values" in error_dics[0]:
|
|
2837
|
+
headstring += "<th>Bad value(s)</th>"
|
|
2838
|
+
if "bad_items" in error_dics[0]:
|
|
2839
|
+
headstring += "<th>Bad dataname(s)</th>"
|
|
2840
|
+
if "child" in error_dics[0]:
|
|
2841
|
+
headstring += "<th>Child</th>"
|
|
2842
|
+
if "parent" in error_dics[0]:
|
|
2843
|
+
headstring += "<th>Parent</th>"
|
|
2844
|
+
headstring +="</tr>\n"
|
|
2845
|
+
for error in error_dics:
|
|
2846
|
+
bodystring += "<tr><td><tt>%s</tt></td>" % error["item_name"]
|
|
2847
|
+
if "bad_values" in error:
|
|
2848
|
+
bodystring += "<td>%s</td>" % error["bad_values"]
|
|
2849
|
+
if "bad_items" in error:
|
|
2850
|
+
bodystring += "<td><tt>%s</tt></td>" % error["bad_items"]
|
|
2851
|
+
if "child" in error:
|
|
2852
|
+
bodystring += "<td><tt>%s</tt></td>" % error["child"]
|
|
2853
|
+
if "parent" in error:
|
|
2854
|
+
bodystring += "<td><tt>%s</tt></td>" % error["parent"]
|
|
2855
|
+
bodystring += "</tr>\n"
|
|
2856
|
+
return retstring + headstring + bodystring + "</table>\n"
|
|
2857
|
+
|
|
2858
|
+
def run_data_checks(check_block,fulldic,block_scope='Item'):
|
|
2859
|
+
v_result = {}
|
|
2860
|
+
for key in check_block.keys():
|
|
2861
|
+
update_value(v_result, fulldic.run_item_validation(key,check_block[key]))
|
|
2862
|
+
update_value(v_result, fulldic.run_global_validation(key,check_block[key],check_block))
|
|
2863
|
+
for loopnames in check_block.loops.values():
|
|
2864
|
+
update_value(v_result, fulldic.run_loop_validation(loopnames))
|
|
2865
|
+
update_value(v_result,fulldic.run_block_validation(check_block,block_scope=block_scope))
|
|
2866
|
+
# return false and list of baddies if anything didn't match
|
|
2867
|
+
all_keys = list(v_result.keys())
|
|
2868
|
+
for test_key in all_keys:
|
|
2869
|
+
v_result[test_key] = [a for a in v_result[test_key] if a[1]["result"]==False]
|
|
2870
|
+
if len(v_result[test_key]) == 0:
|
|
2871
|
+
del v_result[test_key]
|
|
2872
|
+
# if even one false one is found, this should trigger
|
|
2873
|
+
# print("Baddies: {!r}".format(v_result))
|
|
2874
|
+
isvalid = len(v_result)==0
|
|
2875
|
+
return isvalid,v_result
|
|
2876
|
+
|
|
2877
|
+
|
|
2878
|
+
def get_number_with_esd(numstring):
|
|
2879
|
+
numb_re = '((-?(([0-9]*[.]([0-9]+))|([0-9]+)[.]?))([(][0-9]+[)])?([eEdD][+-]?[0-9]+)?)|(\\?)|(\\.)'
|
|
2880
|
+
our_match = re.match(numb_re,numstring)
|
|
2881
|
+
if our_match:
|
|
2882
|
+
a,base_num,b,c,dad,dbd,esd,exp,q,dot = our_match.groups()
|
|
2883
|
+
# print("Debug: {} -> {!r}".format(numstring, our_match.groups()))
|
|
2884
|
+
else:
|
|
2885
|
+
return None,None
|
|
2886
|
+
if dot or q: return None,None #a dot or question mark
|
|
2887
|
+
if exp: #has exponent
|
|
2888
|
+
exp = exp.replace("d","e") # mop up old fashioned numbers
|
|
2889
|
+
exp = exp.replace("D","e")
|
|
2890
|
+
base_num = base_num + exp
|
|
2891
|
+
# print("Debug: have %s for base_num from %s" % (base_num,numstring))
|
|
2892
|
+
base_num = float(base_num)
|
|
2893
|
+
# work out esd, if present.
|
|
2894
|
+
if esd:
|
|
2895
|
+
esd = float(esd[1:-1]) # no brackets
|
|
2896
|
+
if dad: # decimal point + digits
|
|
2897
|
+
esd = esd * (10 ** (-1* len(dad)))
|
|
2898
|
+
if exp:
|
|
2899
|
+
esd = esd * (10 ** (float(exp[1:])))
|
|
2900
|
+
return base_num,esd
|
|
2901
|
+
|
|
2902
|
+
def float_with_esd(inval):
|
|
2903
|
+
if isinstance(inval,unicode):
|
|
2904
|
+
j = inval.find("(")
|
|
2905
|
+
if j>=0: return float(inval[:j])
|
|
2906
|
+
return float(inval)
|
|
2907
|
+
|
|
2908
|
+
|
|
2909
|
+
|
|
2910
|
+
def convert_type(definition):
|
|
2911
|
+
"""Convert value to have the type given by definition"""
|
|
2912
|
+
#extract the actual required type information
|
|
2913
|
+
container = definition['_type.container']
|
|
2914
|
+
dimension = definition.get('_type.dimension',StarFile.StarList([]))
|
|
2915
|
+
structure = interpret_structure(definition['_type.contents'])
|
|
2916
|
+
if container == 'Single': #a single value to convert
|
|
2917
|
+
return convert_single_value(structure)
|
|
2918
|
+
elif container == 'List': #lots of the same value
|
|
2919
|
+
return convert_list_values(structure,dimension)
|
|
2920
|
+
elif container == 'Multiple': #no idea
|
|
2921
|
+
return None
|
|
2922
|
+
elif container in ('Array','Matrix'): #numpy array
|
|
2923
|
+
return convert_matrix_values(structure)
|
|
2924
|
+
return lambda a:a #unable to convert
|
|
2925
|
+
|
|
2926
|
+
def convert_single_value(type_spec):
|
|
2927
|
+
"""Convert a single item according to type_spec"""
|
|
2928
|
+
if type_spec == 'Real':
|
|
2929
|
+
return float_with_esd
|
|
2930
|
+
if type_spec in ('Count','Integer','Index','Binary','Hexadecimal','Octal'):
|
|
2931
|
+
return int
|
|
2932
|
+
if type_spec == 'Complex':
|
|
2933
|
+
return complex
|
|
2934
|
+
if type_spec == 'Imag':
|
|
2935
|
+
return lambda a:complex(0,a)
|
|
2936
|
+
if type_spec in ('Code','Name','Tag'): #case-insensitive -> lowercase
|
|
2937
|
+
return lambda a:a.lower()
|
|
2938
|
+
return lambda a:a #can't do anything numeric
|
|
2939
|
+
|
|
2940
|
+
class convert_simple_list(object):
|
|
2941
|
+
|
|
2942
|
+
"""\
|
|
2943
|
+
Callable object that converts values in a simple list according
|
|
2944
|
+
to the specified element structure.
|
|
2945
|
+
"""
|
|
2946
|
+
|
|
2947
|
+
def __init__(self, structure):
|
|
2948
|
+
self.converters = [convert_single_value(tp) for tp in structure]
|
|
2949
|
+
return
|
|
2950
|
+
|
|
2951
|
+
def __call__(self, element):
|
|
2952
|
+
if len(element) != len(self.converters):
|
|
2953
|
+
emsg = "Expected iterable of %i values, got %i." % (
|
|
2954
|
+
(len(self.converters), len(element)))
|
|
2955
|
+
raise ValueError(emsg)
|
|
2956
|
+
rv = [f(e) for f, e in zip(self.converters, element)]
|
|
2957
|
+
return rv
|
|
2958
|
+
|
|
2959
|
+
# End of class convert_single_value
|
|
2960
|
+
|
|
2961
|
+
def convert_list_values(structure, dimension):
|
|
2962
|
+
"""Convert the values according to the element
|
|
2963
|
+
structure given in [[structure]]"""
|
|
2964
|
+
# simple repetition
|
|
2965
|
+
if isinstance(structure, (unicode, str)):
|
|
2966
|
+
fcnv = convert_single_value(structure)
|
|
2967
|
+
# assume structure is a list of types
|
|
2968
|
+
else:
|
|
2969
|
+
fcnv = convert_simple_list(structure)
|
|
2970
|
+
rv = fcnv
|
|
2971
|
+
# setup nested conversion function when dimension differs from 1.
|
|
2972
|
+
if len(dimension) > 0 and int(dimension[0]) != 1:
|
|
2973
|
+
rv = lambda args : [fcnv(a) for a in args]
|
|
2974
|
+
return rv
|
|
2975
|
+
|
|
2976
|
+
def convert_matrix_values(valtype):
|
|
2977
|
+
"""Convert a dREL String or Float valued List structure to a numpy matrix structure"""
|
|
2978
|
+
# first convert to numpy array, then let numpy do the work
|
|
2979
|
+
try:
|
|
2980
|
+
import numpy
|
|
2981
|
+
except ImportError:
|
|
2982
|
+
return lambda a:a #cannot do it
|
|
2983
|
+
if valtype == 'Real':
|
|
2984
|
+
dtype = float
|
|
2985
|
+
elif valtype == 'Integer':
|
|
2986
|
+
dtype = int
|
|
2987
|
+
elif valtype == 'Complex':
|
|
2988
|
+
dtype = complex
|
|
2989
|
+
else:
|
|
2990
|
+
raise ValueError('Unknown matrix value type')
|
|
2991
|
+
fcnv = lambda a : numpy.asarray(a, dtype=dtype)
|
|
2992
|
+
return fcnv
|
|
2993
|
+
|
|
2994
|
+
def interpret_structure(struc_spec):
|
|
2995
|
+
"""Interpret a DDLm structure specification"""
|
|
2996
|
+
from . import TypeContentsParser as t
|
|
2997
|
+
p = t.TypeParser(t.TypeParserScanner(struc_spec))
|
|
2998
|
+
return getattr(p,"input")()
|
|
2999
|
+
|
|
3000
|
+
|
|
3001
|
+
# A utility function to append to item values rather than replace them
|
|
3002
|
+
def update_value(base_dict,new_items):
|
|
3003
|
+
for new_key in new_items.keys():
|
|
3004
|
+
if new_key in base_dict:
|
|
3005
|
+
base_dict[new_key].extend(new_items[new_key])
|
|
3006
|
+
else:
|
|
3007
|
+
base_dict[new_key] = new_items[new_key]
|
|
3008
|
+
|
|
3009
|
+
#Transpose the list of lists passed to us
|
|
3010
|
+
def transpose(base_list):
|
|
3011
|
+
new_lofl = []
|
|
3012
|
+
full_length = len(base_list)
|
|
3013
|
+
opt_range = range(full_length)
|
|
3014
|
+
for i in range(len(base_list[0])):
|
|
3015
|
+
new_packet = []
|
|
3016
|
+
for j in opt_range:
|
|
3017
|
+
new_packet.append(base_list[j][i])
|
|
3018
|
+
new_lofl.append(new_packet)
|
|
3019
|
+
return new_lofl
|
|
3020
|
+
|
|
3021
|
+
# listify strings - used surprisingly often
|
|
3022
|
+
def listify(item):
|
|
3023
|
+
if isinstance(item,(unicode,str)): return [item]
|
|
3024
|
+
else: return item
|
|
3025
|
+
|
|
3026
|
+
# given a list of search items, return a list of items
|
|
3027
|
+
# actually contained in the given data block
|
|
3028
|
+
def filter_present(namelist,datablocknames):
|
|
3029
|
+
return [a for a in namelist if a in datablocknames]
|
|
3030
|
+
|
|
3031
|
+
# Make an item immutable, used if we want a list to be a key
|
|
3032
|
+
def make_immutable(values):
|
|
3033
|
+
"""Turn list of StarList values into a list of immutable items"""
|
|
3034
|
+
if not isinstance(values[0],StarList):
|
|
3035
|
+
return values
|
|
3036
|
+
else:
|
|
3037
|
+
return [tuple(a) for a in values]
|
|
3038
|
+
|
|
3039
|
+
# merge ddl dictionaries. We should be passed filenames or CifFile
|
|
3040
|
+
# objects
|
|
3041
|
+
def merge_dic(diclist,mergemode="replace",ddlspec=None):
|
|
3042
|
+
dic_as_cif_list = []
|
|
3043
|
+
for dic in diclist:
|
|
3044
|
+
if not isinstance(dic,CifFile) and \
|
|
3045
|
+
not isinstance(dic,(unicode,str)):
|
|
3046
|
+
raise TypeError("Require list of CifFile names/objects for dictionary merging")
|
|
3047
|
+
if not isinstance(dic,CifFile): dic_as_cif_list.append(CifFile(dic))
|
|
3048
|
+
else: dic_as_cif_list.append(dic)
|
|
3049
|
+
# we now merge left to right
|
|
3050
|
+
basedic = dic_as_cif_list[0]
|
|
3051
|
+
if "on_this_dictionary" in basedic: #DDL1 style only
|
|
3052
|
+
for dic in dic_as_cif_list[1:]:
|
|
3053
|
+
basedic.merge(dic,mode=mergemode,match_att=["_name"])
|
|
3054
|
+
elif len(basedic.keys()) == 1: #One block: DDL2/m style
|
|
3055
|
+
old_block = basedic[basedic.keys()[0]]
|
|
3056
|
+
for dic in dic_as_cif_list[1:]:
|
|
3057
|
+
new_block = dic[dic.keys()[0]]
|
|
3058
|
+
basedic.merge(dic,mode=mergemode,
|
|
3059
|
+
single_block=[basedic.keys()[0],dic.keys()[0]],
|
|
3060
|
+
match_att=["_item.name"],match_function=find_parent)
|
|
3061
|
+
return CifDic(basedic)
|
|
3062
|
+
|
|
3063
|
+
def find_parent(ddl2_def):
|
|
3064
|
+
if "_item.name" not in ddl2_def:
|
|
3065
|
+
return None
|
|
3066
|
+
if isinstance(ddl2_def["_item.name"],unicode):
|
|
3067
|
+
return ddl2_def["_item.name"]
|
|
3068
|
+
if "_item_linked.child_name" not in ddl2_def:
|
|
3069
|
+
raise CifError("Asked to find parent in block with no child_names")
|
|
3070
|
+
if "_item_linked.parent_name" not in ddl2_def:
|
|
3071
|
+
raise CifError("Asked to find parent in block with no parent_names")
|
|
3072
|
+
result = list([a for a in ddl2_def["_item.name"] if a not in ddl2_def["_item_linked.child_name"]])
|
|
3073
|
+
if len(result)>1 or len(result)==0:
|
|
3074
|
+
raise CifError("Unable to find single unique parent data item")
|
|
3075
|
+
return result[0]
|
|
3076
|
+
|
|
3077
|
+
|
|
3078
|
+
def ReadCif(filename,grammar='auto',scantype='standard',scoping='instance',standard='CIF',
|
|
3079
|
+
permissive=False):
|
|
3080
|
+
""" Read in a CIF file, returning a `CifFile` object.
|
|
3081
|
+
|
|
3082
|
+
* `filename` may be a URL, a file
|
|
3083
|
+
path on the local system, or any object with a `read` method.
|
|
3084
|
+
|
|
3085
|
+
* `grammar` chooses the CIF grammar variant. `1.0` is the original 1992 grammar and `1.1`
|
|
3086
|
+
is identical except for the exclusion of square brackets as the first characters in
|
|
3087
|
+
undelimited datanames. `2.0` will read files in the CIF2.0 standard, and `STAR2` will
|
|
3088
|
+
read files according to the STAR2 publication. If grammar is `None`, autodetection
|
|
3089
|
+
will be attempted in the order `2.0`, `1.1` and `1.0`. This will always succeed for
|
|
3090
|
+
properly-formed CIF2.0 files. Note that only Unicode characters in the basic multilingual
|
|
3091
|
+
plane are recognised (this will be fixed when PyCIFRW is ported to Python 3).
|
|
3092
|
+
|
|
3093
|
+
* `scantype` can be `standard` or `flex`. `standard` provides pure Python parsing at the
|
|
3094
|
+
cost of a factor of 10 or so in speed. `flex` will tokenise the input CIF file using
|
|
3095
|
+
fast C routines, but is not available for CIF2/STAR2 files. Note that running PyCIFRW in
|
|
3096
|
+
Jython uses native Java regular expressions
|
|
3097
|
+
to provide a speedup regardless of this argument (and does not yet support CIF2).
|
|
3098
|
+
|
|
3099
|
+
* `scoping` is only relevant where nested save frames are expected (STAR2 only).
|
|
3100
|
+
`instance` scoping makes nested save frames
|
|
3101
|
+
invisible outside their hierarchy, allowing duplicate save frame names in separate
|
|
3102
|
+
hierarchies. `dictionary` scoping makes all save frames within a data block visible to each
|
|
3103
|
+
other, thereby restricting all save frames to have unique names.
|
|
3104
|
+
Currently the only recognised value for `standard` is `CIF`, which when set enforces a
|
|
3105
|
+
maximum length of 75 characters for datanames and has no other effect. """
|
|
3106
|
+
|
|
3107
|
+
finalcif = CifFile(scoping=scoping,standard=standard)
|
|
3108
|
+
return StarFile.ReadStar(filename,prepared=finalcif,grammar=grammar,scantype=scantype,
|
|
3109
|
+
permissive=permissive)
|
|
3110
|
+
#return StarFile.StarFile(filename,maxlength,scantype=scantype,grammar=grammar,**kwargs)
|
|
3111
|
+
|
|
3112
|
+
class CifLoopBlock(StarFile.LoopBlock):
|
|
3113
|
+
def __init__(self,data=(),**kwargs):
|
|
3114
|
+
super(CifLoopBlock,self).__init__(data,**kwargs)
|
|
3115
|
+
|
|
3116
|
+
#No documentation flags
|
|
3117
|
+
|