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.
Files changed (112) hide show
  1. pyemaps/COPYING +798 -0
  2. pyemaps/CifFile/CifFile_module.py +3117 -0
  3. pyemaps/CifFile/StarFile.py +2496 -0
  4. pyemaps/CifFile/TypeContentsParser.py +82 -0
  5. pyemaps/CifFile/YappsStarParser_1_0.py +289 -0
  6. pyemaps/CifFile/YappsStarParser_1_1.py +288 -0
  7. pyemaps/CifFile/YappsStarParser_2_0.py +369 -0
  8. pyemaps/CifFile/YappsStarParser_STAR2.py +382 -0
  9. pyemaps/CifFile/__init__.py +7 -0
  10. pyemaps/CifFile/drel/__init__.py +1 -0
  11. pyemaps/CifFile/drel/drel_ast_yacc.py +582 -0
  12. pyemaps/CifFile/drel/drel_lex.py +228 -0
  13. pyemaps/CifFile/drel/drel_runtime.py +126 -0
  14. pyemaps/CifFile/drel/parsetab.py +185 -0
  15. pyemaps/CifFile/drel/py_from_ast.py +628 -0
  16. pyemaps/CifFile/parsetab.py +185 -0
  17. pyemaps/CifFile/yapps3_compiled_rt.py +390 -0
  18. pyemaps/README.md +201 -0
  19. pyemaps/__config__.py +91 -0
  20. pyemaps/__init__.py +233 -0
  21. pyemaps/__main__.py +219 -0
  22. pyemaps/__version__.py +1 -0
  23. pyemaps/cdata/Aluminium.xtl +4 -0
  24. pyemaps/cdata/AluminiumOxide.xtl +5 -0
  25. pyemaps/cdata/Aluminium_FCC.xtl +4 -0
  26. pyemaps/cdata/BariumTitanate_180k.xtl +6 -0
  27. pyemaps/cdata/BariumTitanate_270k.xtl +7 -0
  28. pyemaps/cdata/BariumTitanate_Tetra.xtl +7 -0
  29. pyemaps/cdata/BiMnO3.xtl +27 -0
  30. pyemaps/cdata/Boron_Tetra.xtl +8 -0
  31. pyemaps/cdata/CadmiumSelenide_Hex.xtl +5 -0
  32. pyemaps/cdata/CadmiumSulfide_Cubic.xtl +5 -0
  33. pyemaps/cdata/CadmiumSulfide_Hex.xtl +5 -0
  34. pyemaps/cdata/Chromium_BCC.xtl +4 -0
  35. pyemaps/cdata/CoSb3_Skutterudite.xtl +5 -0
  36. pyemaps/cdata/CopperOxide.xtl +5 -0
  37. pyemaps/cdata/Copper_FCC.xtl +4 -0
  38. pyemaps/cdata/Cu2O_Cuprite.xtl +5 -0
  39. pyemaps/cdata/Diamond.xtl +4 -0
  40. pyemaps/cdata/ErbiumPyrogermanate.xtl +9 -0
  41. pyemaps/cdata/FePd_Tetra.xtl +5 -0
  42. pyemaps/cdata/FeS2_Pyrite.xtl +15 -0
  43. pyemaps/cdata/GalliumAntimonide.xtl +5 -0
  44. pyemaps/cdata/GalliumArsenide.xtl +5 -0
  45. pyemaps/cdata/GalliumNitride.xtl +5 -0
  46. pyemaps/cdata/Germanium.xtl +4 -0
  47. pyemaps/cdata/Gold_FCC.xtl +4 -0
  48. pyemaps/cdata/IndiumArsenide.xtl +5 -0
  49. pyemaps/cdata/LaMnO3.xtl +7 -0
  50. pyemaps/cdata/LeadZirconateTitanate.xtl +8 -0
  51. pyemaps/cdata/Li2MnO3.xtl +13 -0
  52. pyemaps/cdata/NaFeO2.xtl +6 -0
  53. pyemaps/cdata/Nb3Sn.xtl +5 -0
  54. pyemaps/cdata/Pentacene.xtl +39 -0
  55. pyemaps/cdata/Silicon.xtl +4 -0
  56. pyemaps/cdata/StrontiumTitanate.xtl +6 -0
  57. pyemaps/cdata/TelluriumDioxide.xtl +5 -0
  58. pyemaps/cdata/TinDioxide_RT.xtl +5 -0
  59. pyemaps/cdata/TitaniumDioxide_Anatase.xtl +5 -0
  60. pyemaps/cdata/TitaniumDioxide_Rutile.xtl +5 -0
  61. pyemaps/cdata/TungstenDiselenide.xtl +5 -0
  62. pyemaps/cdata/VanadiumDioxide_RT.xtl +6 -0
  63. pyemaps/cdata/ZincOxide.xtl +5 -0
  64. pyemaps/cdata/Zinc_HCP.xtl +4 -0
  65. pyemaps/cdata/ZirconiumNitride.xtl +5 -0
  66. pyemaps/cdata/limno2.xtl +6 -0
  67. pyemaps/crystals.py +1415 -0
  68. pyemaps/ddiffs.py +147 -0
  69. pyemaps/diffract/__init__.py +70 -0
  70. pyemaps/diffract/bloch_dec.py +779 -0
  71. pyemaps/diffract/csf_dec.py +242 -0
  72. pyemaps/diffract/dif_dec.py +519 -0
  73. pyemaps/diffract/dpgen_dec.py +175 -0
  74. pyemaps/diffract/mxtal_dec.py +201 -0
  75. pyemaps/diffract/powder_dec.py +137 -0
  76. pyemaps/diffract/stereo_dec.py +111 -0
  77. pyemaps/display.py +791 -0
  78. pyemaps/emcontrols.py +1017 -0
  79. pyemaps/errors.py +266 -0
  80. pyemaps/fileutils.py +1001 -0
  81. pyemaps/kdiffs.py +1221 -0
  82. pyemaps/license.txt +42 -0
  83. pyemaps/samples/adf.py +137 -0
  84. pyemaps/samples/al.img +0 -0
  85. pyemaps/samples/al_dpgen.py +57 -0
  86. pyemaps/samples/al_ediom.py +94 -0
  87. pyemaps/samples/convert_image.py +88 -0
  88. pyemaps/samples/dm_diff.py +225 -0
  89. pyemaps/samples/mask.im3 +0 -0
  90. pyemaps/samples/powder.py +51 -0
  91. pyemaps/samples/raw.img +0 -0
  92. pyemaps/samples/si_bloch.py +120 -0
  93. pyemaps/samples/si_constructor.py +51 -0
  94. pyemaps/samples/si_csf.py +59 -0
  95. pyemaps/samples/si_dif.py +134 -0
  96. pyemaps/samples/si_lacbed.py +59 -0
  97. pyemaps/samples/si_pyemaps.py +80 -0
  98. pyemaps/samples/si_scm.py +156 -0
  99. pyemaps/samples/si_stereo.py +116 -0
  100. pyemaps/scattering/__init__.py +26 -0
  101. pyemaps/scattering/sct_dec.py +31 -0
  102. pyemaps/spg/__init__.py +25 -0
  103. pyemaps/spg/spg_dec.py +60 -0
  104. pyemaps/stackimg.py +1031 -0
  105. pyemaps/xtal.py +222 -0
  106. pyemaps-2.0.0.dist-info/METADATA +231 -0
  107. pyemaps-2.0.0.dist-info/RECORD +112 -0
  108. pyemaps-2.0.0.dist-info/WHEEL +5 -0
  109. pyemaps-2.0.0.dist-info/entry_points.txt +2 -0
  110. pyemaps-2.0.0.dist-info/licenses/COPYING +798 -0
  111. pyemaps-2.0.0.dist-info/licenses/license.txt +42 -0
  112. 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
+