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,2496 @@
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
+ __copyright = """
8
+ PYCIFRW License Agreement (Python License, Version 2)
9
+ -----------------------------------------------------
10
+
11
+ 1. This LICENSE AGREEMENT is between the Australian Nuclear Science
12
+ and Technology Organisation ("ANSTO"), and the Individual or
13
+ Organization ("Licensee") accessing and otherwise using this software
14
+ ("PyCIFRW") in source or binary form and its associated documentation.
15
+
16
+ 2. Subject to the terms and conditions of this License Agreement,
17
+ ANSTO hereby grants Licensee a nonexclusive, royalty-free, world-wide
18
+ license to reproduce, analyze, test, perform and/or display publicly,
19
+ prepare derivative works, distribute, and otherwise use PyCIFRW alone
20
+ or in any derivative version, provided, however, that this License
21
+ Agreement and ANSTO's notice of copyright, i.e., "Copyright (c)
22
+ 2001-2014 ANSTO; All Rights Reserved" are retained in PyCIFRW alone or
23
+ in any derivative version prepared by Licensee.
24
+
25
+ 3. In the event Licensee prepares a derivative work that is based on
26
+ or incorporates PyCIFRW or any part thereof, and wants to make the
27
+ derivative work available to others as provided herein, then Licensee
28
+ hereby agrees to include in any such work a brief summary of the
29
+ changes made to PyCIFRW.
30
+
31
+ 4. ANSTO is making PyCIFRW available to Licensee on an "AS IS"
32
+ basis. ANSTO MAKES NO REPRESENTATIONS OR WARRANTIES, EXPRESS OR
33
+ IMPLIED. BY WAY OF EXAMPLE, BUT NOT LIMITATION, ANSTO MAKES NO AND
34
+ DISCLAIMS ANY REPRESENTATION OR WARRANTY OF MERCHANTABILITY OR FITNESS
35
+ FOR ANY PARTICULAR PURPOSE OR THAT THE USE OF PYCIFRW WILL NOT
36
+ INFRINGE ANY THIRD PARTY RIGHTS.
37
+
38
+ 5. ANSTO SHALL NOT BE LIABLE TO LICENSEE OR ANY OTHER USERS OF PYCIFRW
39
+ FOR ANY INCIDENTAL, SPECIAL, OR CONSEQUENTIAL DAMAGES OR LOSS AS A
40
+ RESULT OF MODIFYING, DISTRIBUTING, OR OTHERWISE USING PYCIFRW, OR ANY
41
+ DERIVATIVE THEREOF, EVEN IF ADVISED OF THE POSSIBILITY THEREOF.
42
+
43
+ 6. This License Agreement will automatically terminate upon a material
44
+ breach of its terms and conditions.
45
+
46
+ 7. Nothing in this License Agreement shall be deemed to create any
47
+ relationship of agency, partnership, or joint venture between ANSTO
48
+ and Licensee. This License Agreement does not grant permission to use
49
+ ANSTO trademarks or trade name in a trademark sense to endorse or
50
+ promote products or services of Licensee, or any third party.
51
+
52
+ 8. By copying, installing or otherwise using PyCIFRW, Licensee agrees
53
+ to be bound by the terms and conditions of this License Agreement.
54
+
55
+ """
56
+
57
+
58
+ import sys
59
+
60
+ # Python 2,3 compatibility
61
+ try:
62
+ from urllib import urlopen # for arbitrary opening
63
+ from urlparse import urlparse, urlunparse
64
+ except:
65
+ from urllib.request import urlopen
66
+ from urllib.parse import urlparse,urlunparse
67
+ import re,os
68
+ import textwrap
69
+
70
+ try:
71
+ from StringIO import StringIO #not cStringIO as we cannot subclass
72
+ except ImportError:
73
+ from io import StringIO
74
+
75
+ if isinstance(u"abc",str): #Python 3
76
+ unicode = str
77
+ long = int
78
+
79
+ try:
80
+ import numpy
81
+ have_numpy = True
82
+ except ImportError:
83
+ have_numpy = False
84
+
85
+ # Windows paths require special handling. pathlib is available from
86
+ # Python 3.4. For earlier Python versions dictionary import will not work
87
+ # on Windows
88
+ try:
89
+ from pathlib import Path
90
+ have_pathlib = True
91
+ except:
92
+ have_pathlib = False
93
+
94
+ class StarList(list):
95
+ def __getitem__(self,args):
96
+ if isinstance(args,(int,slice)):
97
+ return super(StarList,self).__getitem__(args)
98
+ elif isinstance(args,tuple) and len(args)>1: #extended comma notation
99
+ return super(StarList,self).__getitem__(args[0]).__getitem__(args[1:])
100
+ else:
101
+ return super(StarList,self).__getitem__(args[0])
102
+
103
+ def __str__(self):
104
+ return "SL("+super(StarList,self).__str__() + ")"
105
+
106
+ class StarDict(dict):
107
+ pass
108
+
109
+
110
+ class LoopBlock(object):
111
+ def __init__(self,parent_block,dataname):
112
+ self.loop_no = parent_block.FindLoop(dataname)
113
+ if self.loop_no < 0:
114
+ raise KeyError('%s is not in a loop structure' % dataname)
115
+ self.parent_block = parent_block
116
+
117
+ def keys(self):
118
+ return self.parent_block.loops[self.loop_no]
119
+
120
+ def values(self):
121
+ return [self.parent_block[a] for a in self.keys()]
122
+
123
+ #Avoid iterator even though that is Python3-esque
124
+ def items(self):
125
+ return list(zip(self.keys(),self.values()))
126
+
127
+ def __getitem__(self,dataname):
128
+ if isinstance(dataname,int): #a packet request
129
+ return self.GetPacket(dataname)
130
+ if dataname in self.keys():
131
+ return self.parent_block[dataname]
132
+ else:
133
+ raise KeyError('%s not in loop block' % dataname)
134
+
135
+ def __setitem__(self,dataname,value):
136
+ self.parent_block[dataname] = value
137
+ self.parent_block.AddLoopName(self.keys()[0],dataname)
138
+
139
+ def __contains__(self,key):
140
+ return key in self.parent_block.loops[self.loop_no]
141
+
142
+ def has_key(self,key):
143
+ return key in self
144
+
145
+ def __iter__(self):
146
+ packet_list = zip(*self.values())
147
+ names = self.keys()
148
+ for p in packet_list:
149
+ r = StarPacket(p)
150
+ for n in range(len(names)):
151
+ setattr(r,names[n].lower(),r[n])
152
+ yield r
153
+
154
+ # for compatibility
155
+ def __getattr__(self,attname):
156
+ return getattr(self.parent_block,attname)
157
+
158
+ def load_iter(self,coords=[]):
159
+ count = 0 #to create packet index
160
+ while not self.popout:
161
+ # ok, we have a new packet: append a list to our subloops
162
+ for aloop in self.loops:
163
+ aloop.new_enclosing_packet()
164
+ for iname in self.item_order:
165
+ if isinstance(iname,LoopBlock): #into a nested loop
166
+ for subitems in iname.load_iter(coords=coords+[count]):
167
+ # print 'Yielding %s' % `subitems`
168
+ yield subitems
169
+ # print 'End of internal loop'
170
+ else:
171
+ if self.dimension == 0:
172
+ # print 'Yielding %s' % `self[iname]`
173
+ yield self,self[iname]
174
+ else:
175
+ backval = self.block[iname]
176
+ for i in range(len(coords)):
177
+ # print 'backval, coords: %s, %s' % (`backval`,`coords`)
178
+ backval = backval[coords[i]]
179
+ yield self,backval
180
+ count = count + 1 # count packets
181
+ self.popout = False # reinitialise
182
+ # print 'Finished iterating'
183
+ yield self,'###Blank###' #this value should never be used
184
+
185
+ # an experimental fast iterator for level-1 loops (ie CIF)
186
+ def fast_load_iter(self):
187
+ targets = map(lambda a:self.block[a],self.item_order)
188
+ while targets:
189
+ for target in targets:
190
+ yield self,target
191
+
192
+ # Add another list of the required shape to take into account a new outer packet
193
+ def new_enclosing_packet(self):
194
+ if self.dimension > 1: #otherwise have a top-level list
195
+ for iname in self.keys(): #includes lower levels
196
+ target_list = self[iname]
197
+ for i in range(3,self.dimension): #dim 2 upwards are lists of lists of...
198
+ target_list = target_list[-1]
199
+ target_list.append([])
200
+ # print '%s now %s' % (iname,`self[iname]`)
201
+
202
+ def recursive_iter(self,dict_so_far={},coord=[]):
203
+ # print "Recursive iter: coord %s, keys %s, dim %d" % (`coord`,`self.block.keys()`,self.dimension)
204
+ my_length = 0
205
+ top_items = self.block.items()
206
+ top_values = self.block.values() #same order as items
207
+ drill_values = self.block.values()
208
+ for dimup in range(0,self.dimension): #look higher in the tree
209
+ if len(drill_values)>0: #this block has values
210
+ drill_values=drill_values[0] #drill in
211
+ else:
212
+ raise StarError("Malformed loop packet %s" % repr( top_items[0] ))
213
+ my_length = len(drill_values[0]) #length of 'string' entry
214
+ if self.dimension == 0: #top level
215
+ for aloop in self.loops:
216
+ for apacket in aloop.recursive_iter():
217
+ # print "Recursive yielding %s" % repr( dict(top_items + apacket.items()) )
218
+ prep_yield = StarPacket(top_values+apacket.values()) #straight list
219
+ for name,value in top_items + apacket.items():
220
+ setattr(prep_yield,name,value)
221
+ yield prep_yield
222
+ else: #in some loop
223
+ for i in range(my_length):
224
+ kvpairs = map(lambda a:(a,self.coord_to_group(a,coord)[i]),self.block.keys())
225
+ kvvals = map(lambda a:a[1],kvpairs) #just values
226
+ # print "Recursive kvpairs at %d: %s" % (i,repr( kvpairs ))
227
+ if self.loops:
228
+ for aloop in self.loops:
229
+ for apacket in aloop.recursive_iter(coord=coord+[i]):
230
+ # print "Recursive yielding %s" % repr( dict(kvpairs + apacket.items()) )
231
+ prep_yield = StarPacket(kvvals+apacket.values())
232
+ for name,value in kvpairs + apacket.items():
233
+ setattr(prep_yield,name,value)
234
+ yield prep_yield
235
+ else: # we're at the bottom of the tree
236
+ # print "Recursive yielding %s" % repr( dict(kvpairs) )
237
+ prep_yield = StarPacket(kvvals)
238
+ for name,value in kvpairs:
239
+ setattr(prep_yield,name,value)
240
+ yield prep_yield
241
+
242
+ # small function to use the coordinates.
243
+ def coord_to_group(self,dataname,coords):
244
+ if not isinstance(dataname,unicode):
245
+ return dataname # flag inner loop processing
246
+ newm = self[dataname] # newm must be a list or tuple
247
+ for c in coords:
248
+ # print "Coord_to_group: %s ->" % (repr( newm )),
249
+ newm = newm[c]
250
+ # print repr( newm )
251
+ return newm
252
+
253
+ def flat_iterator(self):
254
+ my_length = 0
255
+ top_keys = self.block.keys()
256
+ if len(top_keys)>0:
257
+ my_length = len(self.block[top_keys[0]])
258
+ for pack_no in range(my_length):
259
+ yield(self.collapse(pack_no))
260
+
261
+
262
+ def RemoveItem(self,itemname):
263
+ """Remove `itemname` from the block."""
264
+ # first check any loops
265
+ loop_no = self.FindLoop(itemname)
266
+ testkey = itemname.lower()
267
+ if testkey in self:
268
+ del self.block[testkey]
269
+ del self.true_case[testkey]
270
+ # now remove from loop
271
+ if loop_no >= 0:
272
+ self.loops[loop_no].remove(testkey)
273
+ if len(self.loops[loop_no])==0:
274
+ del self.loops[loop_no]
275
+ self.item_order.remove(loop_no)
276
+ else: #will appear in order list
277
+ self.item_order.remove(testkey)
278
+
279
+ def RemoveLoopItem(self,itemname):
280
+ """*Deprecated*. Use `RemoveItem` instead"""
281
+ self.RemoveItem(itemname)
282
+
283
+ def GetLoop(self,keyname):
284
+ """Return a `StarFile.LoopBlock` object constructed from the loop containing `keyname`.
285
+ `keyname` is only significant as a way to specify the loop."""
286
+ return LoopBlock(self,keyname)
287
+
288
+ def GetPacket(self,index):
289
+ thispack = StarPacket([])
290
+ for myitem in self.parent_block.loops[self.loop_no]:
291
+ thispack.append(self[myitem][index])
292
+ setattr(thispack,myitem,thispack[-1])
293
+ return thispack
294
+
295
+ def AddPacket(self,packet):
296
+ for myitem in self.parent_block.loops[self.loop_no]:
297
+ old_values = self.parent_block[myitem]
298
+ old_values.append(packet.__getattribute__(myitem))
299
+ self.parent_block[myitem] = old_values
300
+
301
+ def GetItemOrder(self):
302
+ """Return a list of datanames in this `LoopBlock` in the order that they will be
303
+ printed"""
304
+ return self.parent_block.loops[self.loop_no][:]
305
+
306
+
307
+ def ChangeItemOrder(self,itemname,newpos):
308
+ """Change the position at which `itemname` appears when printing out to `newpos`."""
309
+ self.parent_block.loops[self.loop_no].remove(itemname.lower())
310
+ self.parent_block.loops[self.loop_no].insert(newpos,itemname.lower())
311
+
312
+ def GetItemPosition(self,itemname):
313
+ """A utility function to get the numerical order in the printout
314
+ of `itemname`. An item has coordinate `(loop_no,pos)` with
315
+ the top level having a `loop_no` of -1. If an integer is passed to
316
+ the routine then it will return the position of the loop
317
+ referenced by that number."""
318
+ if isinstance(itemname,int):
319
+ # return loop position
320
+ return (-1, self.item_order.index(itemname))
321
+ if not itemname in self:
322
+ raise ValueError('No such dataname %s' % itemname)
323
+ testname = itemname.lower()
324
+ if testname in self.item_order:
325
+ return (-1,self.item_order.index(testname))
326
+ loop_no = self.FindLoop(testname)
327
+ loop_pos = self.loops[loop_no].index(testname)
328
+ return loop_no,loop_pos
329
+
330
+ def GetLoopNames(self,keyname):
331
+ if keyname in self:
332
+ return self.keys()
333
+ for aloop in self.loops:
334
+ try:
335
+ return aloop.GetLoopNames(keyname)
336
+ except KeyError:
337
+ pass
338
+ raise KeyError('Item does not exist')
339
+
340
+ def GetLoopNames(self,keyname):
341
+ """Return all datanames appearing together with `keyname`"""
342
+ loop_no = self.FindLoop(keyname)
343
+ if loop_no >= 0:
344
+ return self.loops[loop_no]
345
+ else:
346
+ raise KeyError('%s is not in any loop' % keyname)
347
+
348
+ def AddToLoop(self,dataname,loopdata):
349
+ thisloop = self.GetLoop(dataname)
350
+ for itemname,itemvalue in loopdata.items():
351
+ thisloop[itemname] = itemvalue
352
+
353
+ def AddToLoop(self,dataname,loopdata):
354
+ """*Deprecated*. Use `AddItem` followed by calls to `AddLoopName`.
355
+
356
+ Add multiple columns to the loop containing `dataname`. `loopdata` is a
357
+ collection of (key,value) pairs, where `key` is the new dataname and `value`
358
+ is a list of values for that dataname"""
359
+ self.update(loopdata)
360
+ for one_name in loopdata:
361
+ self.AddLoopName(dataname,one_name)
362
+
363
+
364
+ class StarBlock(object):
365
+ def __init__(self,data = (), maxoutlength=2048, wraplength=80, overwrite=True,
366
+ characterset='ascii',maxnamelength=-1):
367
+ self.block = {} #the actual data storage (lower case keys)
368
+ self.loops = {} #each loop is indexed by a number and contains a list of datanames
369
+ self.item_order = [] #lower case, loops referenced by integer
370
+ self.formatting_hints = {}
371
+ self.true_case = {} #transform lower case to supplied case
372
+ self.provide_value = False #prefer string version always
373
+ self.dictionary = None #DDLm dictionary
374
+ self.popout = False #used during load iteration
375
+ self.curitem = -1 #used during iteration
376
+ self.cache_vals = True #store all calculated values
377
+ self.maxoutlength = maxoutlength
378
+ self.setmaxnamelength(maxnamelength) #to enforce CIF limit of 75 characters
379
+ self.set_characterset(characterset) #to check input names
380
+ self.wraplength = wraplength
381
+ self.overwrite = overwrite
382
+ self.string_delimiters = ["'",'"',"\n;"] #universal CIF set
383
+ self.list_delimiter = " " #CIF2 default
384
+ self.wrapper = textwrap.TextWrapper()
385
+ if isinstance(data,(tuple,list)):
386
+ for item in data:
387
+ self.AddLoopItem(item)
388
+ elif isinstance(data,StarBlock):
389
+ self.block = data.block.copy()
390
+ self.item_order = data.item_order[:]
391
+ self.true_case = data.true_case.copy()
392
+ # loops as well
393
+ self.loops = data.loops.copy()
394
+
395
+ def setmaxnamelength(self,maxlength):
396
+ """Set the maximum allowable dataname length (-1 for no check)"""
397
+ self.maxnamelength = maxlength
398
+ if maxlength > 0:
399
+ bad_names = [a for a in self.keys() if len(a)>self.maxnamelength]
400
+ if len(bad_names)>0:
401
+ raise StarError('Datanames too long: ' + repr( bad_names ))
402
+
403
+ def set_characterset(self,characterset):
404
+ """Set the characterset for checking datanames: may be `ascii` or `unicode`"""
405
+ self.characterset = characterset
406
+ if characterset == 'ascii':
407
+ self.char_check = re.compile("[][ \n\r\t!%&\\(\\)*+,./:<=>?@0-9A-Za-z\\\\^`{}\\|~\"#$';_-]+",re.M)
408
+ elif characterset == 'unicode':
409
+ if sys.maxunicode < 1114111:
410
+ self.char_check = re.compile(u"[][ \n\r\t!%&\\(\\)*+,./:<=>?@0-9A-Za-z\\\\^`{}\\|~\"#$';_\u00A0-\uD7FF\uE000-\uFDCF\uFDF0-\uFFFD-]+",re.M)
411
+ else:
412
+ self.char_check = re.compile(u"[][ \n\r\t!%&\\(\\)*+,./:<=>?@0-9A-Za-z\\\\^`{}\\|~\"#$';_\u00A0-\uD7FF\uE000-\uFDCF\uFDF0-\uFFFD\U00010000-\U0010FFFD-]+",re.M)
413
+
414
+ def __str__(self):
415
+ return self.printsection()
416
+
417
+ def __setitem__(self,key,value):
418
+ if key == "saves":
419
+ raise StarError("""Setting the saves key is deprecated. Add the save block to
420
+ an enclosing block collection (e.g. CIF or STAR file) with this block as child""")
421
+ self.AddItem(key,value)
422
+
423
+ def __getitem__(self,key):
424
+ if key == "saves":
425
+ raise StarError("""The saves key is deprecated. Access the save block from
426
+ the enclosing block collection (e.g. CIF or STAR file object)""")
427
+ try:
428
+ rawitem,is_value = self.GetFullItemValue(key)
429
+ except KeyError:
430
+ if self.dictionary:
431
+ # send the dictionary the required key and a pointer to us
432
+ try:
433
+ new_value = self.dictionary.derive_item(key,self,store_value=self.cache_vals,allow_defaults=False)
434
+ except StarDerivationFailure: #try now with defaults included
435
+ try:
436
+ new_value = self.dictionary.derive_item(key,self,store_value=self.cache_vals,allow_defaults=True)
437
+ except StarDerivationFailure as s:
438
+ print("In StarBlock.__getitem__, " + repr(s))
439
+ raise KeyError('No such item: %s' % key)
440
+ print('Set %s to derived value %s' % (key, repr(new_value)))
441
+ return new_value
442
+ else:
443
+ raise KeyError('No such item: %s' % key)
444
+ # we now have an item, we can try to convert it to a number if that is appropriate
445
+ # note numpy values are never stored but are converted to lists
446
+ if not self.dictionary or not key in self.dictionary: return rawitem
447
+ print('%s: is_value %s provide_value %s value %s' % (key,repr( is_value ),repr( self.provide_value ),repr( rawitem )))
448
+ if is_value:
449
+ if self.provide_value: return rawitem
450
+ else:
451
+ print('Turning %s into string' % repr( rawitem ))
452
+ return self.convert_to_string(key)
453
+ else: # a string
454
+ if self.provide_value and ((not isinstance(rawitem,list) and rawitem != '?' and rawitem != ".") or \
455
+ (isinstance(rawitem,list) and '?' not in rawitem and '.' not in rawitem)):
456
+ return self.dictionary.change_type(key,rawitem)
457
+ elif self.provide_value: # catch the question marks
458
+ do_calculate = False
459
+ if isinstance(rawitem,(list,tuple)):
460
+ known = [a for a in rawitem if a != '?']
461
+ if len(known) == 0: #all questions
462
+ do_calculate = True
463
+ elif rawitem == '?':
464
+ do_calculate = True
465
+ if do_calculate:
466
+ # remove old value
467
+ del self[key]
468
+ try:
469
+ new_value = self.dictionary.derive_item(key,self,store_value=True,allow_defaults=False)
470
+ except StarDerivationFailure as s:
471
+ try:
472
+ new_value = self.dictionary.derive_item(key,self,store_value=True,allow_defaults=True)
473
+ except StarDerivationFailure as s:
474
+
475
+ print("Could not turn %s into a value:" + repr(s))
476
+ return rawitem
477
+ else:
478
+ print('Set %s to derived value %s' % (key, repr( new_value )))
479
+ return new_value
480
+ return rawitem #can't do anything
481
+
482
+ def __delitem__(self,key):
483
+ self.RemoveItem(key)
484
+
485
+ def __len__(self):
486
+ blen = len(self.block)
487
+ return blen
488
+
489
+ def __nonzero__(self):
490
+ if self.__len__() > 0: return 1
491
+ return 0
492
+
493
+ # keys returns all internal keys
494
+ def keys(self):
495
+ return list(self.block.keys()) #always lower case
496
+
497
+ def values(self):
498
+ return [self[a] for a in self.keys()]
499
+
500
+ def items(self):
501
+ return list(zip(self.keys(),self.values()))
502
+
503
+ def __contains__(self,key):
504
+ if isinstance(key,(unicode,str)) and key.lower() in self.keys():
505
+ return True
506
+ return False
507
+
508
+ def has_key(self,key):
509
+ return key in self
510
+
511
+ def has_key_or_alias(self,key):
512
+ """Check if a dataname or alias is available in the block"""
513
+ initial_test = key in self
514
+ if initial_test: return True
515
+ elif self.dictionary:
516
+ aliases = [k for k in self.dictionary.alias_table.get(key,[]) if self.has_key(k)]
517
+ if len(aliases)>0:
518
+ return True
519
+ return False
520
+
521
+ def get(self,key,default=None):
522
+ if key in self:
523
+ retval = self.__getitem__(key)
524
+ else:
525
+ retval = default
526
+ return retval
527
+
528
+ def clear(self):
529
+ self.block = {}
530
+ self.loops = {}
531
+ self.item_order = []
532
+ self.true_case = {}
533
+
534
+ # doesn't appear to work
535
+ def copy(self):
536
+ newcopy = StarBlock()
537
+ newcopy.block = self.block.copy()
538
+ newcopy.loops = []
539
+ newcopy.item_order = self.item_order[:]
540
+ newcopy.true_case = self.true_case.copy()
541
+ newcopy.loops = self.loops.copy()
542
+ # return self.copy.im_class(newcopy) #catch inheritance
543
+ return newcopy
544
+
545
+ def update(self,adict):
546
+ for key in adict.keys():
547
+ self.AddItem(key,adict[key])
548
+
549
+ def GetItemPosition(self,itemname):
550
+ """A utility function to get the numerical order in the printout
551
+ of `itemname`. An item has coordinate `(loop_no,pos)` with
552
+ the top level having a `loop_no` of -1. If an integer is passed to
553
+ the routine then it will return the position of the loop
554
+ referenced by that number."""
555
+ if isinstance(itemname,int):
556
+ # return loop position
557
+ return (-1, self.item_order.index(itemname))
558
+ if not itemname in self:
559
+ raise ValueError('No such dataname %s' % itemname)
560
+ testname = itemname.lower()
561
+ if testname in self.item_order:
562
+ return (-1,self.item_order.index(testname))
563
+ loop_no = self.FindLoop(testname)
564
+ loop_pos = self.loops[loop_no].index(testname)
565
+ return loop_no,loop_pos
566
+
567
+ def ChangeItemOrder(self,itemname,newpos):
568
+ """Move the printout order of `itemname` to `newpos`. If `itemname` is
569
+ in a loop, `newpos` refers to the order within the loop."""
570
+ if isinstance(itemname,(unicode,str)):
571
+ true_name = itemname.lower()
572
+ else:
573
+ true_name = itemname
574
+ loopno = self.FindLoop(true_name)
575
+ if loopno < 0: #top level
576
+ self.item_order.remove(true_name)
577
+ self.item_order.insert(newpos,true_name)
578
+ else:
579
+ self.loops[loopno].remove(true_name)
580
+ self.loops[loopno].insert(newpos,true_name)
581
+
582
+ def GetItemOrder(self):
583
+ """Return a list of datanames in the order in which they will be printed. Loops are
584
+ referred to by numerical index"""
585
+ return self.item_order[:]
586
+
587
+ def AddItem(self,key,value,precheck=False):
588
+ """Add dataname `key` to block with value `value`. `value` may be
589
+ a single value, a list or a tuple. If `precheck` is False (the default),
590
+ all values will be checked and converted to unicode strings as necessary. If
591
+ `precheck` is True, this checking is bypassed. No checking is necessary
592
+ when values are read from a CIF file as they are already in correct form."""
593
+ if not isinstance(key,(unicode,str)):
594
+ raise TypeError('Star datanames are strings only (got %s)' % repr( key ))
595
+ key = unicode(key) #everything is unicode internally
596
+ if not precheck:
597
+ self.check_data_name(key,self.maxnamelength) # make sure no nasty characters
598
+ # check for overwriting
599
+ if key in self:
600
+ if not self.overwrite:
601
+ raise StarError( 'Attempt to insert duplicate item name %s' % key)
602
+ if not precheck: #need to sanitise
603
+ regval,empty_val = self.regularise_data(value)
604
+ pure_string = check_stringiness(regval)
605
+ self.check_item_value(regval)
606
+ else:
607
+ regval,empty_val = value,None
608
+ pure_string = True
609
+ # update ancillary information first
610
+ lower_key = key.lower()
611
+ if not lower_key in self and self.FindLoop(lower_key)<0: #need to add to order
612
+ self.item_order.append(lower_key)
613
+ # always remove from our case table in case the case is different
614
+ try:
615
+ del self.true_case[lower_key]
616
+ except KeyError:
617
+ pass
618
+ self.true_case[lower_key] = key
619
+ if pure_string:
620
+ self.block.update({lower_key:[regval,empty_val]})
621
+ else:
622
+ self.block.update({lower_key:[empty_val,regval]})
623
+
624
+ def AddLoopItem(self,incomingdata,precheck=False,maxlength=-1):
625
+ """*Deprecated*. Use `AddItem` followed by `CreateLoop` if
626
+ necessary."""
627
+ # print "Received data %s" % `incomingdata`
628
+ # we accept tuples, strings, lists and dicts!!
629
+ # Direct insertion: we have a string-valued key, with an array
630
+ # of values -> single-item into our loop
631
+ if isinstance(incomingdata[0],(tuple,list)):
632
+ # a whole loop
633
+ keyvallist = zip(incomingdata[0],incomingdata[1])
634
+ for key,value in keyvallist:
635
+ self.AddItem(key,value)
636
+ self.CreateLoop(incomingdata[0])
637
+ elif not isinstance(incomingdata[0],(unicode,str)):
638
+ raise TypeError('Star datanames are strings only (got %s)' % repr( incomingdata[0] ))
639
+ else:
640
+ self.AddItem(incomingdata[0],incomingdata[1])
641
+
642
+ def check_data_name(self,dataname,maxlength=-1):
643
+ if maxlength > 0:
644
+ self.check_name_length(dataname,maxlength)
645
+ if dataname[0]!='_':
646
+ raise StarError( 'Dataname ' + dataname + ' does not begin with _')
647
+ if self.characterset=='ascii':
648
+ if len ([a for a in dataname if ord(a) < 33 or ord(a) > 126]) > 0:
649
+ raise StarError( 'Dataname ' + dataname + ' contains forbidden characters')
650
+ else:
651
+ # print 'Checking %s for unicode characterset conformance' % dataname
652
+ if len ([a for a in dataname if ord(a) < 33]) > 0:
653
+ raise StarError( 'Dataname ' + dataname + ' contains forbidden characters (below code point 33)')
654
+ if len ([a for a in dataname if ord(a) > 126 and ord(a) < 160]) > 0:
655
+ raise StarError( 'Dataname ' + dataname + ' contains forbidden characters (between code point 127-159)')
656
+ if len ([a for a in dataname if ord(a) > 0xD7FF and ord(a) < 0xE000]) > 0:
657
+ raise StarError( 'Dataname ' + dataname + ' contains unsupported characters (between U+D800 and U+E000)')
658
+ if len ([a for a in dataname if ord(a) > 0xFDCF and ord(a) < 0xFDF0]) > 0:
659
+ raise StarError( 'Dataname ' + dataname + ' contains unsupported characters (between U+FDD0 and U+FDEF)')
660
+ if len ([a for a in dataname if ord(a) == 0xFFFE or ord(a) == 0xFFFF]) > 0:
661
+ raise StarError( 'Dataname ' + dataname + ' contains unsupported characters (U+FFFE and/or U+FFFF)')
662
+ if len ([a for a in dataname if ord(a) > 0x10000 and (ord(a) & 0xE == 0xE)]) > 0:
663
+ print('%s fails' % dataname)
664
+ for a in dataname: print('%x' % ord(a),end="")
665
+ print()
666
+ raise StarError( u'Dataname ' + dataname + u' contains unsupported characters (U+xFFFE and/or U+xFFFF)')
667
+
668
+ def check_name_length(self,dataname,maxlength):
669
+ if len(dataname)>maxlength:
670
+ raise StarError( 'Dataname %s exceeds maximum length %d' % (dataname,maxlength))
671
+ return
672
+
673
+ def check_item_value(self,item):
674
+ test_item = item
675
+ if not isinstance(item,(list,dict,tuple)):
676
+ test_item = [item] #single item list
677
+ def check_one (it):
678
+ if isinstance(it,unicode):
679
+ if it=='': return
680
+ me = self.char_check.match(it)
681
+ if not me:
682
+ print("Fail value check: %s" % it)
683
+ raise StarError('Bad character in %s' % it)
684
+ else:
685
+ if me.span() != (0,len(it)):
686
+ print("Fail value check, match only %d-%d in string %s" % (me.span()[0],me.span()[1],repr( it )))
687
+ raise StarError('Data item "' + repr( it ) + u'"... contains forbidden characters')
688
+ [check_one(a) for a in test_item]
689
+
690
+ def regularise_data(self,dataitem):
691
+ """Place dataitem into a list if necessary"""
692
+ from numbers import Number
693
+ if isinstance(dataitem,str):
694
+ return unicode(dataitem),None
695
+ if isinstance(dataitem,(Number,unicode,StarList,StarDict)):
696
+ return dataitem,None #assume StarList/StarDict contain unicode if necessary
697
+ if isinstance(dataitem,(tuple,list)):
698
+ v,s = zip(*list([self.regularise_data(a) for a in dataitem]))
699
+ return list(v),list(s)
700
+ #return dataitem,[None]*len(dataitem)
701
+ # so try to make into a list
702
+ try:
703
+ regval = list(dataitem)
704
+ except TypeError as value:
705
+ raise StarError( str(dataitem) + ' is wrong type for data value\n' )
706
+ v,s = zip(*list([self.regularise_data(a) for a in regval]))
707
+ return list(v),list(s)
708
+
709
+ def RemoveItem(self,itemname):
710
+ """Remove `itemname` from the block."""
711
+ # first check any loops
712
+ loop_no = self.FindLoop(itemname)
713
+ testkey = itemname.lower()
714
+ if testkey in self:
715
+ del self.block[testkey]
716
+ del self.true_case[testkey]
717
+ # now remove from loop
718
+ if loop_no >= 0:
719
+ self.loops[loop_no].remove(testkey)
720
+ if len(self.loops[loop_no])==0:
721
+ del self.loops[loop_no]
722
+ self.item_order.remove(loop_no)
723
+ else: #will appear in order list
724
+ self.item_order.remove(testkey)
725
+
726
+ def RemoveLoopItem(self,itemname):
727
+ """*Deprecated*. Use `RemoveItem` instead"""
728
+ self.RemoveItem(itemname)
729
+
730
+ def GetItemValue(self,itemname):
731
+ """Return value of `itemname`. If `itemname` is looped, a list
732
+ of all values will be returned."""
733
+ return self.GetFullItemValue(itemname)[0]
734
+
735
+ def GetFullItemValue(self,itemname):
736
+ """Return the value associated with `itemname`, and a boolean flagging whether
737
+ (True) or not (False) it is in a form suitable for calculation. False is
738
+ always returned for strings and `StarList` objects."""
739
+ try:
740
+ s,v = self.block[itemname.lower()]
741
+ except KeyError:
742
+ raise KeyError('Itemname %s not in datablock' % itemname)
743
+ # prefer string value unless all are None
744
+ # are we a looped value?
745
+ if not isinstance(s,(tuple,list)) or isinstance(s,StarList):
746
+ if not_none(s):
747
+ return s,False #a string value
748
+ else:
749
+ return v,not isinstance(v,StarList) #a StarList is not calculation-ready
750
+ elif not_none(s):
751
+ return s,False #a list of string values
752
+ else:
753
+ if len(v)>0:
754
+ return v,not isinstance(v[0],StarList)
755
+ return v,True
756
+
757
+ def CreateLoop(self,datanames,order=-1,length_check=True):
758
+ """Create a loop in the datablock. `datanames` is a list of datanames that
759
+ together form a loop. If length_check is True, they should have been initialised in the block
760
+ to have the same number of elements (possibly 0). If `order` is given,
761
+ the loop will appear at this position in the block when printing
762
+ out. A single-row loop will be created if the provided datanames are all
763
+ non-lists. A loop counts as a single position."""
764
+
765
+ if length_check:
766
+ # check lengths: these datanames should exist
767
+ listed_values = [a for a in datanames if isinstance(self[a],list) and not isinstance(self[a],StarList)]
768
+ if len(listed_values) == len(datanames):
769
+ len_set = set([len(self[a]) for a in datanames])
770
+ if len(len_set)>1:
771
+ raise ValueError('Request to loop datanames %s with different lengths: %s' % (repr( datanames ),repr( len_set )))
772
+ elif len(listed_values) != 0:
773
+ raise ValueError('Request to loop datanames where some are single values and some are not')
774
+ else: #all are unlisted, turn into lists
775
+ for d in datanames:
776
+ self[d] = [self[d]]
777
+ # store as lower case
778
+ lc_datanames = [d.lower() for d in datanames]
779
+ # remove these datanames from all other loops
780
+ [self.loops[a].remove(b) for a in self.loops for b in lc_datanames if b in self.loops[a]]
781
+ # remove empty loops
782
+ empty_loops = [a for a in self.loops.keys() if len(self.loops[a])==0]
783
+ for a in empty_loops:
784
+ self.item_order.remove(a)
785
+ del self.loops[a]
786
+ if len(self.loops)>0:
787
+ loopno = max(self.loops.keys()) + 1
788
+ else:
789
+ loopno = 1
790
+ self.loops[loopno] = list(lc_datanames)
791
+ if order >= 0:
792
+ self.item_order.insert(order,loopno)
793
+ else:
794
+ self.item_order.append(loopno)
795
+ # remove these datanames from item ordering
796
+ self.item_order = [a for a in self.item_order if a not in lc_datanames]
797
+
798
+ def AddLoopName(self,oldname, newname):
799
+ """Add `newname` to the loop containing `oldname`. If it is already in the new loop, no
800
+ error is raised. If `newname` is in a different loop, it is removed from that loop.
801
+ The number of values associated with `newname` must match the number of values associated
802
+ with all other columns of the new loop or a `ValueError` will be raised."""
803
+ lower_newname = newname.lower()
804
+ loop_no = self.FindLoop(oldname)
805
+ if loop_no < 0:
806
+ raise KeyError('%s not in loop' % oldname)
807
+ if lower_newname in self.loops[loop_no]:
808
+ return
809
+ # check length
810
+ old_provides = self.provide_value
811
+ self.provide_value = False
812
+ loop_len = len(self[oldname])
813
+ self.provide_value = old_provides
814
+ if len(self[newname]) != loop_len:
815
+ raise StarLengthError('Mismatch of loop column lengths for %s: should be %d' % (newname,loop_len))
816
+ # remove from any other loops
817
+ [self.loops[a].remove(lower_newname) for a in self.loops if lower_newname in self.loops[a]]
818
+ # and add to this loop
819
+ self.loops[loop_no].append(lower_newname)
820
+ # remove from item_order if present
821
+ try:
822
+ self.item_order.remove(lower_newname)
823
+ except ValueError:
824
+ pass
825
+
826
+ def FindLoop(self,keyname):
827
+ """Find the loop that contains `keyname` and return its numerical index or
828
+ -1 if not present. The numerical index can be used to refer to the loop in
829
+ other routines."""
830
+ loop_no = [a for a in self.loops.keys() if keyname.lower() in self.loops[a]]
831
+ if len(loop_no)>0:
832
+ return loop_no[0]
833
+ else:
834
+ return -1
835
+
836
+ def GetLoop(self,keyname):
837
+ """Return a `StarFile.LoopBlock` object constructed from the loop containing `keyname`.
838
+ `keyname` is only significant as a way to specify the loop."""
839
+ return LoopBlock(self,keyname)
840
+
841
+ def GetLoopNames(self,keyname):
842
+ if keyname in self:
843
+ return self.keys()
844
+ for aloop in self.loops:
845
+ try:
846
+ return aloop.GetLoopNames(keyname)
847
+ except KeyError:
848
+ pass
849
+ raise KeyError('Item does not exist')
850
+
851
+ def GetLoopNames(self,keyname):
852
+ """Return all datanames appearing together with `keyname`"""
853
+ loop_no = self.FindLoop(keyname)
854
+ if loop_no >= 0:
855
+ return self.loops[loop_no]
856
+ else:
857
+ raise KeyError('%s is not in any loop' % keyname)
858
+
859
+ def AddLoopName(self,oldname, newname):
860
+ """Add `newname` to the loop containing `oldname`. If it is already in the new loop, no
861
+ error is raised. If `newname` is in a different loop, it is removed from that loop.
862
+ The number of values associated with `newname` must match the number of values associated
863
+ with all other columns of the new loop or a `ValueError` will be raised."""
864
+ lower_newname = newname.lower()
865
+ loop_no = self.FindLoop(oldname)
866
+ if loop_no < 0:
867
+ raise KeyError('%s not in loop' % oldname)
868
+ if lower_newname in self.loops[loop_no]:
869
+ return
870
+ # check length
871
+ old_provides = self.provide_value
872
+ self.provide_value = False
873
+ loop_len = len(self[oldname])
874
+ self.provide_value = old_provides
875
+ if len(self[newname]) != loop_len:
876
+ raise StarLengthError('Mismatch of loop column lengths for %s: should be %d' % (newname,loop_len))
877
+ # remove from any other loops
878
+ [self.loops[a].remove(lower_newname) for a in self.loops if lower_newname in self.loops[a]]
879
+ # and add to this loop
880
+ self.loops[loop_no].append(lower_newname)
881
+ # remove from item_order if present
882
+ try:
883
+ self.item_order.remove(lower_newname)
884
+ except ValueError:
885
+ pass
886
+
887
+ def AddToLoop(self,dataname,loopdata):
888
+ thisloop = self.GetLoop(dataname)
889
+ for itemname,itemvalue in loopdata.items():
890
+ thisloop[itemname] = itemvalue
891
+
892
+ def AddToLoop(self,dataname,loopdata):
893
+ """*Deprecated*. Use `AddItem` followed by calls to `AddLoopName`.
894
+
895
+ Add multiple columns to the loop containing `dataname`. `loopdata` is a
896
+ collection of (key,value) pairs, where `key` is the new dataname and `value`
897
+ is a list of values for that dataname"""
898
+ self.update(loopdata)
899
+ for one_name in loopdata:
900
+ self.AddLoopName(dataname,one_name)
901
+
902
+ def RemoveKeyedPacket(self,keyname,keyvalue):
903
+ """Remove the packet for which dataname `keyname` takes
904
+ value `keyvalue`. Only the first such occurrence is
905
+ removed."""
906
+ packet_coord = list(self[keyname]).index(keyvalue)
907
+ loopnames = self.GetLoopNames(keyname)
908
+ for dataname in loopnames:
909
+ self.block[dataname][0] = list(self.block[dataname][0])
910
+ del self.block[dataname][0][packet_coord]
911
+ self.block[dataname][1] = list(self.block[dataname][1])
912
+ del self.block[dataname][1][packet_coord]
913
+
914
+ def GetKeyedPacket(self,keyname,keyvalue,no_case=False):
915
+ """Return the loop packet (a `StarPacket` object) where `keyname` has value
916
+ `keyvalue`. Ignore case in `keyvalue` if `no_case` is True. `ValueError`
917
+ is raised if no packet is found or more than one packet is found."""
918
+ my_loop = self.GetLoop(keyname)
919
+ #print("Looking for %s in %s" % (keyvalue, my_loop.parent_block))
920
+ #print('Packet check on:' + keyname)
921
+ #[print(repr(getattr(a,keyname))) for a in my_loop]
922
+ if no_case:
923
+ one_pack= [a for a in my_loop if getattr(a,keyname).lower()==keyvalue.lower()]
924
+ else:
925
+ one_pack= [a for a in my_loop if getattr(a,keyname)==keyvalue]
926
+ if len(one_pack)!=1:
927
+ raise ValueError("Bad packet key %s = %s: returned %d packets" % (keyname,keyvalue,len(one_pack)))
928
+ print("Keyed packet: %s" % one_pack[0])
929
+ return one_pack[0]
930
+
931
+ def GetCompoundKeyedPacket(self,keydict):
932
+ """Return the loop packet (a `StarPacket` object) where the `{key:(value,caseless)}` pairs
933
+ in `keydict` take the appropriate values. Ignore case for a given `key` if `caseless` is
934
+ True. `ValueError` is raised if no packet is found or more than one packet is found."""
935
+ #print "Looking for %s in %s" % (keyvalue, self.parent_block[keyname])
936
+ keynames = list(keydict.keys())
937
+ my_loop = self.GetLoop(keynames[0])
938
+ for one_key in keynames:
939
+ keyval,no_case = keydict[one_key]
940
+ if no_case:
941
+ my_loop = list([a for a in my_loop if str(getattr(a,one_key)).lower()==str(keyval).lower()])
942
+ else:
943
+ my_loop = list([a for a in my_loop if getattr(a,one_key)==keyval])
944
+ if len(my_loop)!=1:
945
+ raise ValueError("Bad packet keys %s: returned %d packets" % (repr(keydict),len(my_loop)))
946
+ print("Compound keyed packet: %s" % my_loop[0])
947
+ return my_loop[0]
948
+
949
+ def GetKeyedSemanticPacket(self,keyvalue,cat_id):
950
+ """Return a complete packet for category `cat_id` where the
951
+ category key for the category equals `keyvalue`. This routine
952
+ will understand any joined loops, so if separate loops in the
953
+ datafile belong to the
954
+ same category hierarchy (e.g. `_atom_site` and `_atom_site_aniso`),
955
+ the returned `StarPacket` object will contain datanames from
956
+ both categories."""
957
+ target_keys = self.dictionary.cat_key_table[cat_id]
958
+ target_keys = [k[0] for k in target_keys] #one only in each list
959
+ p = StarPacket()
960
+ # set case-sensitivity flag
961
+ lcase = False
962
+ if self.dictionary[target_keys[0]]['_type.contents'] in ['Code','Tag','Name']:
963
+ lcase = True
964
+ for cat_key in target_keys:
965
+ try:
966
+ extra_packet = self.GetKeyedPacket(cat_key,keyvalue,no_case=lcase)
967
+ except KeyError: #missing key
968
+ try:
969
+ test_key = self[cat_key] #generate key if possible
970
+ print('Test key is %s' % repr( test_key ))
971
+ if test_key is not None and\
972
+ not (isinstance(test_key,list) and (None in test_key or len(test_key)==0)):
973
+ print('Getting packet for key %s' % repr( keyvalue ))
974
+ extra_packet = self.GetKeyedPacket(cat_key,keyvalue,no_case=lcase)
975
+ except: #cannot be generated
976
+ continue
977
+ except ValueError: #none/more than one, assume none
978
+ continue
979
+ #extra_packet = self.dictionary.generate_default_packet(cat_id,cat_key,keyvalue)
980
+ p.merge_packet(extra_packet)
981
+ # the following attributes used to calculate missing values
982
+ for keyname in target_keys:
983
+ if hasattr(p,keyname):
984
+ p.key = [keyname]
985
+ break
986
+ if not hasattr(p,"key"):
987
+ raise ValueError("No key found for %s, packet is %s" % (cat_id,str(p)))
988
+ p.cif_dictionary = self.dictionary
989
+ p.fulldata = self
990
+ return p
991
+
992
+ def GetMultiKeyedSemanticPacket(self,keydict,cat_id):
993
+ """Return a complete packet for category `cat_id` where the keyvalues are
994
+ provided as a dictionary of key:(value,caseless) pairs
995
+ This routine
996
+ will understand any joined loops, so if separate loops in the
997
+ datafile belong to the
998
+ same category hierarchy (e.g. `_atom_site` and `_atom_site_aniso`),
999
+ the returned `StarPacket` object will contain datanames from
1000
+ the requested category and any children."""
1001
+ #if len(keyvalues)==1: #simplification
1002
+ # return self.GetKeyedSemanticPacket(keydict[1][0],cat_id)
1003
+ target_keys = self.dictionary.cat_key_table[cat_id]
1004
+ # update the dictionary passed to us with all equivalents, for
1005
+ # simplicity.
1006
+ parallel_keys = list(zip(*target_keys)) #transpose
1007
+ print('Parallel keys:' + repr(parallel_keys))
1008
+ print('Keydict:' + repr(keydict))
1009
+ start_keys = list(keydict.keys())
1010
+ for one_name in start_keys:
1011
+ key_set = [a for a in parallel_keys if one_name in a]
1012
+ for one_key in key_set:
1013
+ keydict[one_key] = keydict[one_name]
1014
+ # target_keys is a list of lists, each of which is a compound key
1015
+ p = StarPacket()
1016
+ # a little function to return the dataname for a key
1017
+ def find_key(key):
1018
+ for one_key in self.dictionary.key_equivs.get(key,[])+[key]:
1019
+ if self.has_key(one_key):
1020
+ return one_key
1021
+ return None
1022
+ for one_set in target_keys: #loop down the categories
1023
+ true_keys = [find_key(k) for k in one_set]
1024
+ true_keys = [k for k in true_keys if k is not None]
1025
+ if len(true_keys)==len(one_set):
1026
+ truekeydict = dict([(t,keydict[k]) for t,k in zip(true_keys,one_set)])
1027
+ try:
1028
+ extra_packet = self.GetCompoundKeyedPacket(truekeydict)
1029
+ except KeyError: #one or more are missing
1030
+ continue #should try harder?
1031
+ except ValueError:
1032
+ continue
1033
+ else:
1034
+ continue
1035
+ print('Merging packet for keys ' + repr(one_set))
1036
+ p.merge_packet(extra_packet)
1037
+ # the following attributes used to calculate missing values
1038
+ p.key = true_keys
1039
+ p.cif_dictionary = self.dictionary
1040
+ p.fulldata = self
1041
+ return p
1042
+
1043
+
1044
+ def set_grammar(self,new_grammar):
1045
+ self.string_delimiters = ["'",'"',"\n;",None]
1046
+ if new_grammar in ['STAR2','2.0']:
1047
+ self.string_delimiters += ['"""',"'''"]
1048
+ if new_grammar == '2.0':
1049
+ self.list_delimiter = " "
1050
+ elif new_grammar == 'STAR2':
1051
+ self.list_delimiter = ", "
1052
+ elif new_grammar not in ['1.0','1.1']:
1053
+ raise StarError('Request to set unknown grammar %s' % new_grammar)
1054
+
1055
+ def SetOutputLength(self,wraplength=80,maxoutlength=2048):
1056
+ """Set the maximum output line length (`maxoutlength`) and the line length to
1057
+ wrap at (`wraplength`). The wrap length is a target only and may not always be
1058
+ possible."""
1059
+ if wraplength > maxoutlength:
1060
+ raise StarError("Wrap length (requested %d) must be <= Maximum line length (requested %d)" % (wraplength,maxoutlength))
1061
+ self.wraplength = wraplength
1062
+ self.maxoutlength = maxoutlength
1063
+
1064
+ def printsection(self,instring='',blockstart="",blockend="",indent=0,finish_at='',start_from=''):
1065
+ self.provide_value = False
1066
+ # first make an ordering
1067
+ self.create_ordering(finish_at,start_from) #create self.output_order
1068
+ # now do it...
1069
+ if not instring:
1070
+ outstring = CIFStringIO(target_width=80) # the returned string
1071
+ else:
1072
+ outstring = instring
1073
+ # print block delimiter
1074
+ outstring.write(blockstart,canbreak=True)
1075
+ while len(self.output_order)>0:
1076
+ #print "Remaining to output " + `self.output_order`
1077
+ itemname = self.output_order.pop(0)
1078
+ if not isinstance(itemname,int): #no loop
1079
+ item_spec = [i for i in self.formatting_hints if i['dataname'].lower()==itemname.lower()]
1080
+ if len(item_spec)>0:
1081
+ item_spec = item_spec[0]
1082
+ col_pos = item_spec.get('column',-1)
1083
+ name_pos = item_spec.get('name_pos',-1)
1084
+ else:
1085
+ col_pos = -1
1086
+ item_spec = {}
1087
+ name_pos = -1
1088
+ if col_pos < 0: col_pos = 40
1089
+ outstring.set_tab(col_pos)
1090
+ itemvalue = self[itemname]
1091
+ outstring.write(self.true_case[itemname],mustbreak=True,do_tab=False,startcol=name_pos)
1092
+ outstring.write(' ',canbreak=True,do_tab=False,delimiter=True) #space after itemname
1093
+ self.format_value(itemvalue,outstring,hints=item_spec)
1094
+ else:# we are asked to print a loop block
1095
+ outstring.set_tab(10) #guess this is OK?
1096
+ loop_spec = [i['name_pos'] for i in self.formatting_hints if i["dataname"]=='loop']
1097
+ if loop_spec:
1098
+ loop_indent = max(loop_spec[0],0)
1099
+ else:
1100
+ loop_indent = indent
1101
+ outstring.write('loop_\n',mustbreak=True,do_tab=False,startcol=loop_indent)
1102
+ self.format_names(outstring,indent+2,loop_no=itemname)
1103
+ self.format_packets(outstring,indent+2,loop_no=itemname)
1104
+ else:
1105
+ returnstring = outstring.getvalue()
1106
+ outstring.close()
1107
+ return returnstring
1108
+
1109
+ def format_names(self,outstring,indent=0,loop_no=-1):
1110
+ """Print datanames from `loop_no` one per line"""
1111
+ temp_order = self.loops[loop_no][:] #copy
1112
+ format_hints = dict([(i['dataname'],i) for i in self.formatting_hints if i['dataname'] in temp_order])
1113
+ while len(temp_order)>0:
1114
+ itemname = temp_order.pop(0)
1115
+ req_indent = format_hints.get(itemname,{}).get('name_pos',indent)
1116
+ outstring.write(' ' * req_indent,do_tab=False)
1117
+ outstring.write(self.true_case[itemname],do_tab=False)
1118
+ outstring.write("\n",do_tab=False)
1119
+
1120
+ def format_packets(self,outstring,indent=0,loop_no=-1):
1121
+ alldata = [self[a] for a in self.loops[loop_no]]
1122
+ loopnames = self.loops[loop_no]
1123
+ #print 'Alldata: %s' % `alldata`
1124
+ packet_data = list(zip(*alldata))
1125
+ #print 'Packet data: %s' % `packet_data`
1126
+ #create a dictionary for quick lookup of formatting requirements
1127
+ format_hints = dict([(i['dataname'],i) for i in self.formatting_hints if i['dataname'] in loopnames])
1128
+ for position in range(len(packet_data)):
1129
+ if position > 0:
1130
+ outstring.write("\n") #new line each packet except first
1131
+ for point in range(len(packet_data[position])):
1132
+ datapoint = packet_data[position][point]
1133
+ format_hint = format_hints.get(loopnames[point],{})
1134
+ packstring = self.format_packet_item(datapoint,indent,outstring,format_hint)
1135
+ outstring.write(' ',canbreak=True,do_tab=False,delimiter=True)
1136
+
1137
+ def format_packet_item(self,pack_item,indent,outstring,format_hint):
1138
+ # print 'Formatting %s' % `pack_item`
1139
+ # temporary check for any non-unicode items
1140
+ if isinstance(pack_item,str) and not isinstance(pack_item,unicode):
1141
+ raise StarError("Item {0!r} is not unicode".format(pack_item))
1142
+ if isinstance(pack_item,unicode):
1143
+ delimiter = format_hint.get('delimiter',None)
1144
+ startcol = format_hint.get('column',-1)
1145
+ outstring.write(self._formatstring(pack_item,delimiter=delimiter),startcol=startcol)
1146
+ else:
1147
+ self.format_value(pack_item,outstring,hints = format_hint)
1148
+
1149
+ def _formatstring(self,instring,delimiter=None,standard='CIF1',indent=0,hints={}):
1150
+ if hints.get("reformat",False) and "\n" in instring:
1151
+ instring = "\n"+self.do_wrapping(instring,hints["reformat_indent"])
1152
+ allowed_delimiters = set(self.string_delimiters)
1153
+ if len(instring)==0: allowed_delimiters.difference_update([None])
1154
+ if len(instring) > (self.maxoutlength-2) or '\n' in instring:
1155
+ allowed_delimiters.intersection_update(["\n;","'''",'"""'])
1156
+ [allowed_delimiters.difference_update([None]) for k in '[]{}\v\t ,' if k in instring]
1157
+ if len(instring)>0 and instring[0] in '_$#;(':
1158
+ allowed_delimiters.difference_update([None])
1159
+ if len(instring)>3 and (instring[:4].lower()=='data' or instring[:4].lower()=='save'):
1160
+ allowed_delimiters.difference_update([None])
1161
+ if len(instring)>5 and instring[:6].lower()=='global':
1162
+ allowed_delimiters.difference_update([None])
1163
+ if '"' in instring: allowed_delimiters.difference_update(['"',None])
1164
+ if "'" in instring: allowed_delimiters.difference_update(["'",None])
1165
+ out_delimiter = "\n;" #default (most conservative)
1166
+ if delimiter in allowed_delimiters:
1167
+ out_delimiter = delimiter
1168
+ elif "'" in allowed_delimiters: out_delimiter = "'"
1169
+ elif '"' in allowed_delimiters: out_delimiter = '"'
1170
+ if out_delimiter in ['"',"'",'"""',"'''"]: return out_delimiter + instring + out_delimiter
1171
+ elif out_delimiter is None: return instring
1172
+ # we are left with semicolon strings
1173
+ # use our protocols:
1174
+ maxlinelength = max([len(a) for a in instring.split('\n')])
1175
+ if maxlinelength > self.maxoutlength:
1176
+ protocol_string = apply_line_folding(instring)
1177
+ else:
1178
+ protocol_string = instring
1179
+ # now check for embedded delimiters
1180
+ if "\n;" in protocol_string:
1181
+ prefix = "CIF:"
1182
+ while prefix in protocol_string: prefix = prefix + ":"
1183
+ protocol_string = apply_line_prefix(protocol_string,prefix+"> ")
1184
+ return "\n;" + protocol_string + "\n;"
1185
+
1186
+ def format_value(self,itemvalue,stringsink,compound=False,hints={}):
1187
+ """Format a Star data value"""
1188
+ global have_numpy
1189
+ delimiter = hints.get('delimiter',None)
1190
+ startcol = hints.get('column',-1)
1191
+ if isinstance(itemvalue,str) and not isinstance(itemvalue,unicode): #not allowed
1192
+ raise StarError("Non-unicode value {0} found in block".format(itemvalue))
1193
+ if isinstance(itemvalue,unicode): #need to sanitize
1194
+ stringsink.write(self._formatstring(itemvalue,delimiter=delimiter,hints=hints),canbreak = True,startcol=startcol)
1195
+ elif isinstance(itemvalue,(list)) or (hasattr(itemvalue,'dtype') and hasattr(itemvalue,'__iter__')): #numpy
1196
+ stringsink.set_tab(0)
1197
+ stringsink.write('[',canbreak=True,newindent=True,mustbreak=compound,startcol=startcol)
1198
+ if len(itemvalue)>0:
1199
+ self.format_value(itemvalue[0],stringsink)
1200
+ for listval in itemvalue[1:]:
1201
+ # print 'Formatting %s' % `listval`
1202
+ stringsink.write(self.list_delimiter,do_tab=False)
1203
+ self.format_value(listval,stringsink,compound=True)
1204
+ stringsink.write(']',unindent=True)
1205
+ elif isinstance(itemvalue,dict):
1206
+ stringsink.set_tab(0)
1207
+ stringsink.write('{',newindent=True,mustbreak=compound,startcol=startcol) #start a new line inside
1208
+ items = list(itemvalue.items())
1209
+ if len(items)>0:
1210
+ stringsink.write("'"+items[0][0]+"'"+':',canbreak=True)
1211
+ self.format_value(items[0][1],stringsink)
1212
+ for key,value in items[1:]:
1213
+ stringsink.write(self.list_delimiter)
1214
+ stringsink.write("'"+key+"'"+":",canbreak=True)
1215
+ self.format_value(value,stringsink) #never break between key and value
1216
+ stringsink.write('}',unindent=True)
1217
+ elif isinstance(itemvalue,(float,int,long)) or \
1218
+ (have_numpy and isinstance(itemvalue,(numpy.number))): #TODO - handle uncertainties
1219
+ stringsink.write(str(itemvalue),canbreak=True,startcol=startcol) #numbers
1220
+ else:
1221
+ raise ValueError('Value in unexpected format for output: %s' % repr( itemvalue ))
1222
+
1223
+ def create_ordering(self,finish_at,start_from):
1224
+ """Create a canonical ordering that includes loops using our formatting hints dictionary"""
1225
+ requested_order = list([i['dataname'] for i in self.formatting_hints if i['dataname']!='loop'])
1226
+ new_order = []
1227
+ for item in requested_order:
1228
+ if isinstance(item,unicode) and item.lower() in self.item_order:
1229
+ new_order.append(item.lower())
1230
+ elif item in self: #in a loop somewhere
1231
+ target_loop = self.FindLoop(item)
1232
+ if target_loop not in new_order:
1233
+ new_order.append(target_loop)
1234
+ # adjust loop name order
1235
+ loopnames = self.loops[target_loop]
1236
+ loop_order = [i for i in requested_order if i in loopnames]
1237
+ unordered = [i for i in loopnames if i not in loop_order]
1238
+ self.loops[target_loop] = loop_order + unordered
1239
+ extras = list([i for i in self.item_order if i not in new_order])
1240
+ self.output_order = new_order + extras
1241
+ # now handle partial output
1242
+ if start_from != '':
1243
+ if start_from in requested_order:
1244
+ sfi = requested_order.index(start_from)
1245
+ loop_order = [self.FindLoop(k) for k in requested_order[sfi:] if self.FindLoop(k)>0]
1246
+ candidates = list([k for k in self.output_order if k in requested_order[sfi:]])
1247
+ cand_pos = len(new_order)
1248
+ if len(candidates)>0:
1249
+ cand_pos = self.output_order.index(candidates[0])
1250
+ if len(loop_order)>0:
1251
+ cand_pos = min(cand_pos,self.output_order.index(loop_order[0]))
1252
+ if cand_pos < len(self.output_order):
1253
+ print('Output starts from %s, requested %s' % (self.output_order[cand_pos],start_from))
1254
+ self.output_order = self.output_order[cand_pos:]
1255
+ else:
1256
+ print('Start is beyond end of output list')
1257
+ self.output_order = []
1258
+ elif start_from in extras:
1259
+ self.output_order = self.output_order[self.output_order.index(start_from):]
1260
+ else:
1261
+ self.output_order = []
1262
+ if finish_at != '':
1263
+ if finish_at in requested_order:
1264
+ fai = requested_order.index(finish_at)
1265
+ loop_order = list([self.FindLoop(k) for k in requested_order[fai:] if self.FindLoop(k)>0])
1266
+ candidates = list([k for k in self.output_order if k in requested_order[fai:]])
1267
+ cand_pos = len(new_order)
1268
+ if len(candidates)>0:
1269
+ cand_pos = self.output_order.index(candidates[0])
1270
+ if len(loop_order)>0:
1271
+ cand_pos = min(cand_pos,self.output_order.index(loop_order[0]))
1272
+ if cand_pos < len(self.output_order):
1273
+ print('Output finishes before %s, requested before %s' % (self.output_order[cand_pos],finish_at))
1274
+ self.output_order = self.output_order[:cand_pos]
1275
+ else:
1276
+ print('All of block output')
1277
+ elif finish_at in extras:
1278
+ self.output_order = self.output_order[:self.output_order.index(finish_at)]
1279
+ #print('Final order: ' + repr(self.output_order))
1280
+
1281
+ def convert_to_string(self,dataname):
1282
+ """Convert values held in dataname value fork to string version"""
1283
+ v,is_value = self.GetFullItemValue(dataname)
1284
+ if not is_value:
1285
+ return v
1286
+ if check_stringiness(v): return v #already strings
1287
+ # TODO...something else
1288
+ return v
1289
+
1290
+ def do_wrapping(self,instring,indent=3):
1291
+ """Wrap the provided string"""
1292
+ if " " in instring: #already formatted
1293
+ return instring
1294
+ self.wrapper.initial_indent = ' '*indent
1295
+ self.wrapper.subsequent_indent = ' '*indent
1296
+ # remove leading and trailing space
1297
+ instring = instring.strip()
1298
+ # split into paragraphs
1299
+ paras = instring.split("\n\n")
1300
+ wrapped_paras = [self.wrapper.fill(p) for p in paras]
1301
+ return "\n".join(wrapped_paras)
1302
+
1303
+
1304
+ def merge(self,new_block,mode="strict",match_att=[],match_function=None,
1305
+ rel_keys = []):
1306
+ if mode == 'strict':
1307
+ for key in new_block.keys():
1308
+ if key in self and key not in match_att:
1309
+ raise StarError( "Identical keys %s in strict merge mode" % key)
1310
+ elif key not in match_att: #a new dataname
1311
+ self[key] = new_block[key]
1312
+ # we get here if there are no keys in common, so we can now copy
1313
+ # the loops and not worry about overlaps
1314
+ for one_loop in new_block.loops.values():
1315
+ self.CreateLoop(one_loop)
1316
+ # we have lost case information
1317
+ self.true_case.update(new_block.true_case)
1318
+ elif mode == 'replace':
1319
+ newkeys = list(new_block.keys())
1320
+ for ma in match_att:
1321
+ try:
1322
+ newkeys.remove(ma) #don't touch the special ones
1323
+ except ValueError:
1324
+ pass
1325
+ for key in new_block.keys():
1326
+ if isinstance(key,unicode):
1327
+ self[key] = new_block[key]
1328
+ # creating the loop will remove items from other loops
1329
+ for one_loop in new_block.loops.values():
1330
+ self.CreateLoop(one_loop)
1331
+ # we have lost case information
1332
+ self.true_case.update(new_block.true_case)
1333
+ elif mode == 'overlay':
1334
+ print('Overlay mode, current overwrite is %s' % self.overwrite)
1335
+ raise StarError('Overlay block merge mode not implemented')
1336
+ save_overwrite = self.overwrite
1337
+ self.overwrite = True
1338
+ for attribute in new_block.keys():
1339
+ if attribute in match_att: continue #ignore this one
1340
+ new_value = new_block[attribute]
1341
+ #non-looped items
1342
+ if new_block.FindLoop(attribute)<0: #not looped
1343
+ self[attribute] = new_value
1344
+ my_loops = self.loops.values()
1345
+ perfect_overlaps = [a for a in new_block.loops if a in my_loops]
1346
+ for po in perfect_overlaps:
1347
+ loop_keys = [a for a in po if a in rel_keys] #do we have a key?
1348
+ try:
1349
+ newkeypos = map(lambda a:newkeys.index(a),loop_keys)
1350
+ newkeypos = newkeypos[0] #one key per loop for now
1351
+ loop_keys = loop_keys[0]
1352
+ except (ValueError,IndexError):
1353
+ newkeypos = []
1354
+ overlap_data = map(lambda a:listify(self[a]),overlaps) #old packet data
1355
+ new_data = map(lambda a:new_block[a],overlaps) #new packet data
1356
+ packet_data = transpose(overlap_data)
1357
+ new_p_data = transpose(new_data)
1358
+ # remove any packets for which the keys match between old and new; we
1359
+ # make the arbitrary choice that the old data stays
1360
+ if newkeypos:
1361
+ # get matching values in new list
1362
+ print("Old, new data:\n%s\n%s" % (repr(overlap_data[newkeypos]),repr(new_data[newkeypos])))
1363
+ key_matches = filter(lambda a:a in overlap_data[newkeypos],new_data[newkeypos])
1364
+ # filter out any new data with these key values
1365
+ new_p_data = filter(lambda a:a[newkeypos] not in key_matches,new_p_data)
1366
+ if new_p_data:
1367
+ new_data = transpose(new_p_data)
1368
+ else: new_data = []
1369
+ # wipe out the old data and enter the new stuff
1370
+ byebyeloop = self.GetLoop(overlaps[0])
1371
+ # print("Removing '%r' with overlaps '%r'" % (byebyeloop, overlaps))
1372
+ # Note that if, in the original dictionary, overlaps are not
1373
+ # looped, GetLoop will return the block itself. So we check
1374
+ # for this case...
1375
+ if byebyeloop != self:
1376
+ self.remove_loop(byebyeloop)
1377
+ self.AddLoopItem((overlaps,overlap_data)) #adding old packets
1378
+ for pd in new_p_data: #adding new packets
1379
+ if pd not in packet_data:
1380
+ for i in range(len(overlaps)):
1381
+ #don't do this at home; we are appending
1382
+ #to something in place
1383
+ self[overlaps[i]].append(pd[i])
1384
+ self.overwrite = save_overwrite
1385
+
1386
+ def assign_dictionary(self,dic):
1387
+ if not dic.diclang=="DDLm":
1388
+ print("Warning: ignoring dictionary %s" % dic.my_uri)
1389
+ return
1390
+ self.dictionary = dic
1391
+
1392
+ def unassign_dictionary(self):
1393
+ """Remove dictionary-dependent behaviour"""
1394
+ self.dictionary = None
1395
+
1396
+
1397
+
1398
+ class StarPacket(list):
1399
+ def merge_packet(self,incoming):
1400
+ """Merge contents of incoming packet with this packet"""
1401
+ new_attrs = [a for a in dir(incoming) if a[0] == '_' and a[1] != "_"]
1402
+ self.extend(incoming)
1403
+ for na in new_attrs:
1404
+ setattr(self,na,getattr(incoming,na))
1405
+
1406
+ def __getattr__(self,att_name):
1407
+ """Derive a missing attribute"""
1408
+ if att_name.lower() in self.__dict__:
1409
+ return getattr(self,att_name.lower())
1410
+ if att_name in ('cif_dictionary','fulldata','key'):
1411
+ raise AttributeError('Programming error: can only assign value of %s' % att_name)
1412
+ d = self.cif_dictionary
1413
+ c = self.fulldata
1414
+ k = self.key
1415
+ assert isinstance(k,list)
1416
+ d.derive_item(att_name,c,store_value=True)
1417
+ #
1418
+ # now pick out the new value
1419
+ # self.key is a list of the key values
1420
+ keydict = dict([(v,(getattr(self,v),True)) for v in k])
1421
+ full_pack = c.GetCompoundKeyedPacket(keydict)
1422
+ return getattr(full_pack,att_name)
1423
+
1424
+ class BlockCollection(object):
1425
+ """A container for StarBlock objects. The constructor takes
1426
+ one non-keyword argument `datasource` to set the initial data. If
1427
+ `datasource` is a Python dictionary, the values must be `StarBlock`
1428
+ objects and the keys will be blocknames in the new object. Keyword
1429
+ arguments:
1430
+
1431
+ standard:
1432
+ `CIF` or `Dic`. `CIF` enforces 75-character blocknames, and will
1433
+ print block contents before that block's save frame.
1434
+
1435
+ blocktype:
1436
+ The type of blocks held in this container. Normally `StarBlock`
1437
+ or `CifBlock`.
1438
+
1439
+ characterset:
1440
+ `ascii` or `unicode`. Blocknames and datanames appearing within
1441
+ blocks are restricted to the appropriate characterset. Note that
1442
+ only characters in the basic multilingual plane are accepted. This
1443
+ restriction will be lifted when PyCIFRW is ported to Python3.
1444
+
1445
+ scoping:
1446
+ `instance` or `dictionary`: `instance` implies that save frames are
1447
+ hidden from save frames lower in the hierarchy or in sibling
1448
+ hierarchies. `dictionary` makes all save frames visible everywhere
1449
+ within a data block. This setting is only relevant for STAR2 dictionaries and
1450
+ STAR2 data files, as save frames are currently not used in plain CIF data
1451
+ files.
1452
+
1453
+ """
1454
+ def __init__(self,datasource=None,standard='CIF',blocktype = StarBlock,
1455
+ characterset='ascii',scoping='instance',**kwargs):
1456
+ import collections
1457
+ self.dictionary = {}
1458
+ self.standard = standard
1459
+ self.lower_keys = set() # short_cuts
1460
+ self.renamed = {}
1461
+ self.PC = collections.namedtuple('PC',['block_id','parent'])
1462
+ self.child_table = {}
1463
+ self.visible_keys = [] # for efficiency
1464
+ self.block_input_order = [] # to output in same order
1465
+ self.scoping = scoping #will trigger setting of child table
1466
+ self.blocktype = blocktype
1467
+ self.master_template = {} #for outputting
1468
+ self.set_grammar('2.0')
1469
+ self.set_characterset(characterset)
1470
+ if isinstance(datasource,BlockCollection):
1471
+ self.merge_fast(datasource)
1472
+ self.scoping = scoping #reset visibility
1473
+ elif isinstance(datasource,dict):
1474
+ for key,value in datasource.items():
1475
+ self[key]= value
1476
+ self.header_comment = ''
1477
+
1478
+ def set_grammar(self,new_grammar):
1479
+ """Set the syntax and grammar for output to `new_grammar`"""
1480
+ if new_grammar not in ['1.1','1.0','2.0','STAR2']:
1481
+ raise StarError('Unrecognised output grammar %s' % new_grammar)
1482
+ self.grammar = new_grammar
1483
+
1484
+ def set_characterset(self,characterset):
1485
+ """Set the allowed characters for datanames and datablocks: may be `ascii` or `unicode`. If datanames
1486
+ have already been added to any datablocks, they are not checked."""
1487
+ self.characterset = characterset
1488
+ for one_block in self.lower_keys:
1489
+ self[one_block].set_characterset(characterset)
1490
+
1491
+ def unlock(self):
1492
+ """Allow overwriting of all blocks in this collection"""
1493
+ for a in self.lower_keys:
1494
+ self[a].overwrite=True
1495
+
1496
+ def lock(self):
1497
+ """Disallow overwriting for all blocks in this collection"""
1498
+ for a in self.lower_keys:
1499
+ self[a].overwrite = False
1500
+
1501
+ def __str__(self):
1502
+ return self.WriteOut()
1503
+
1504
+ def __setitem__(self,key,value):
1505
+ self.NewBlock(key,value,parent=None)
1506
+
1507
+ def __getitem__(self,key):
1508
+ if isinstance(key,(unicode,str)):
1509
+ lowerkey = key.lower()
1510
+ if lowerkey in self.lower_keys:
1511
+ return self.dictionary[lowerkey]
1512
+ #print 'Visible keys:' + `self.visible_keys`
1513
+ #print 'All keys' + `self.lower_keys`
1514
+ #print 'Child table' + `self.child_table`
1515
+ raise KeyError('No such item %s' % key)
1516
+
1517
+ # we have to get an ordered list of the current keys,
1518
+ # as we'll have to delete one of them anyway.
1519
+ # Deletion will delete any key regardless of visibility
1520
+
1521
+ def __delitem__(self,key):
1522
+ dummy = self[key] #raise error if not present
1523
+ lowerkey = key.lower()
1524
+ # get rid of all children recursively as well
1525
+ children = [a[0] for a in self.child_table.items() if a[1].parent == lowerkey]
1526
+ for child in children:
1527
+ del self[child] #recursive call
1528
+ del self.dictionary[lowerkey]
1529
+ del self.child_table[lowerkey]
1530
+ try:
1531
+ self.visible_keys.remove(lowerkey)
1532
+ except KeyError:
1533
+ pass
1534
+ self.lower_keys.remove(lowerkey)
1535
+ self.block_input_order.remove(lowerkey)
1536
+
1537
+ def __len__(self):
1538
+ return len(self.visible_keys)
1539
+
1540
+ def __contains__(self,item):
1541
+ """Support the 'in' operator"""
1542
+ if not isinstance(item,(unicode,str)): return False
1543
+ if item.lower() in self.visible_keys:
1544
+ return True
1545
+ return False
1546
+
1547
+ # We iterate over all visible
1548
+ def __iter__(self):
1549
+ for one_block in self.keys():
1550
+ yield self[one_block]
1551
+
1552
+ # TODO: handle different case
1553
+ def keys(self):
1554
+ return self.visible_keys
1555
+
1556
+ # Note that has_key does not exist in 3.5
1557
+ def has_key(self,key):
1558
+ return key in self
1559
+
1560
+ def get(self,key,default=None):
1561
+ if key in self: # take account of case
1562
+ return self.__getitem__(key)
1563
+ else:
1564
+ return default
1565
+
1566
+ def clear(self):
1567
+ self.dictionary.clear()
1568
+ self.lower_keys = set()
1569
+ self.child_table = {}
1570
+ self.visible_keys = []
1571
+ self.block_input_order = []
1572
+
1573
+ def copy(self):
1574
+ newcopy = self.dictionary.copy() #all blocks
1575
+ for k,v in self.dictionary.items():
1576
+ newcopy[k] = v.copy()
1577
+ newcopy = BlockCollection(newcopy)
1578
+ newcopy.child_table = self.child_table.copy()
1579
+ newcopy.lower_keys = self.lower_keys.copy()
1580
+ newcopy.block_input_order = self.block_input_order.copy()
1581
+ newcopy.characterset = self.characterset
1582
+ newcopy.SetTemplate(self.master_template.copy())
1583
+ newcopy.scoping = self.scoping #this sets visible keys
1584
+ return newcopy
1585
+
1586
+ def update(self,adict):
1587
+ for key in adict.keys():
1588
+ self[key] = adict[key]
1589
+
1590
+ def items(self):
1591
+ return [(a,self[a]) for a in self.keys()]
1592
+
1593
+ def first_block(self):
1594
+ """Return the 'first' block. This is not necessarily the first block in the file."""
1595
+ if self.keys():
1596
+ return self[self.keys()[0]]
1597
+
1598
+ def NewBlock(self,blockname,blockcontents=None,fix=True,parent=None):
1599
+ """Add a new block named `blockname` with contents `blockcontents`. If `fix`
1600
+ is True, `blockname` will have spaces and tabs replaced by underscores. `parent`
1601
+ allows a parent block to be set so that block hierarchies can be created. Depending on
1602
+ the output standard, these blocks will be printed out as nested save frames or
1603
+ ignored."""
1604
+ if blockcontents is None:
1605
+ blockcontents = self.blocktype()
1606
+ if self.standard == "CIF":
1607
+ blockcontents.setmaxnamelength(75)
1608
+ if len(blockname)>75:
1609
+ raise StarError('Blockname %s is longer than 75 characters' % blockname)
1610
+ if fix:
1611
+ newblockname = re.sub('[ \t]','_',blockname)
1612
+ else: newblockname = blockname
1613
+ new_lowerbn = newblockname.lower()
1614
+ if new_lowerbn in self.lower_keys: #already there
1615
+ if self.standard is not None:
1616
+ toplevelnames = [a[0] for a in self.child_table.items() if a[1].parent==None]
1617
+ if parent is None and new_lowerbn not in toplevelnames: #can give a new key to this one
1618
+ while new_lowerbn in self.lower_keys: new_lowerbn = new_lowerbn + '+'
1619
+ elif parent is not None and new_lowerbn in toplevelnames: #can fix a different one
1620
+ replace_name = new_lowerbn
1621
+ while replace_name in self.lower_keys: replace_name = replace_name + '+'
1622
+ self._rekey(new_lowerbn,replace_name)
1623
+ # now continue on to add in the new block
1624
+ if parent.lower() == new_lowerbn: #the new block's requested parent just got renamed!!
1625
+ parent = replace_name
1626
+ else:
1627
+ raise StarError( "Attempt to replace existing block " + blockname)
1628
+ else:
1629
+ del self[new_lowerbn]
1630
+ self.dictionary.update({new_lowerbn:blockcontents})
1631
+ self.lower_keys.add(new_lowerbn)
1632
+ self.block_input_order.append(new_lowerbn)
1633
+ if parent is None:
1634
+ self.child_table[new_lowerbn]=self.PC(newblockname,None)
1635
+ self.visible_keys.append(new_lowerbn)
1636
+ else:
1637
+ if parent.lower() in self.lower_keys:
1638
+ if self.scoping == 'instance':
1639
+ self.child_table[new_lowerbn]=self.PC(newblockname,parent.lower())
1640
+ else:
1641
+ self.child_table[new_lowerbn]=self.PC(newblockname,parent.lower())
1642
+ self.visible_keys.append(new_lowerbn)
1643
+ else:
1644
+ print('Warning:Parent block %s does not exist for child %s' % (parent,newblockname))
1645
+ self[new_lowerbn].set_grammar(self.grammar)
1646
+ self[new_lowerbn].set_characterset(self.characterset)
1647
+ self[new_lowerbn].formatting_hints = self.master_template
1648
+ return new_lowerbn #in case calling routine wants to know
1649
+
1650
+ def _rekey(self,oldname,newname,block_id=''):
1651
+ """The block with key [[oldname]] gets [[newname]] as a new key, but the printed name
1652
+ does not change unless [[block_id]] is given. Prefer [[rename]] for a safe version."""
1653
+ move_block = self[oldname] #old block
1654
+ is_visible = oldname in self.visible_keys
1655
+ move_block_info = self.child_table[oldname] #old info
1656
+ move_block_children = [a for a in self.child_table.items() if a[1].parent==oldname]
1657
+ # now rewrite the necessary bits
1658
+ self.child_table.update(dict([(a[0],self.PC(a[1].block_id,newname)) for a in move_block_children]))
1659
+ oldpos = self.block_input_order.index(oldname)
1660
+ del self[oldname] #do this after updating child table so we don't delete children
1661
+ self.dictionary.update({newname:move_block})
1662
+ self.lower_keys.add(newname)
1663
+ #print 'Block input order was: ' + `self.block_input_order`
1664
+ self.block_input_order[oldpos:oldpos]=[newname]
1665
+ if block_id == '':
1666
+ self.child_table.update({newname:move_block_info})
1667
+ else:
1668
+ self.child_table.update({newname:self.PC(block_id,move_block_info.parent)})
1669
+ if is_visible: self.visible_keys += [newname]
1670
+
1671
+ def rename(self,oldname,newname):
1672
+ """Rename datablock from [[oldname]] to [[newname]]. Both key and printed name are changed. No
1673
+ conformance checks are conducted."""
1674
+ realoldname = oldname.lower()
1675
+ realnewname = newname.lower()
1676
+ if realnewname in self.lower_keys:
1677
+ raise StarError('Cannot change blockname %s to %s as %s already present' % (oldname,newname,newname))
1678
+ if realoldname not in self.lower_keys:
1679
+ raise KeyError('Cannot find old block %s' % realoldname)
1680
+ self._rekey(realoldname,realnewname,block_id=newname)
1681
+
1682
+ def makebc(self,namelist,scoping='dictionary'):
1683
+ """Make a block collection from a list of block names"""
1684
+ newbc = BlockCollection()
1685
+ block_lower = [n.lower() for n in namelist]
1686
+ proto_child_table = [a for a in self.child_table.items() if a[0] in block_lower]
1687
+ newbc.child_table = dict(proto_child_table)
1688
+ new_top_level = [(a[0],self.PC(a[1].block_id,None)) for a in newbc.child_table.items() if a[1].parent not in block_lower]
1689
+ newbc.child_table.update(dict(new_top_level))
1690
+ newbc.lower_keys = set([a[0] for a in proto_child_table])
1691
+ newbc.dictionary = dict((a[0],self.dictionary[a[0]]) for a in proto_child_table)
1692
+ newbc.scoping = scoping
1693
+ newbc.block_input_order = block_lower
1694
+ return newbc
1695
+
1696
+
1697
+ def merge_fast(self,new_bc,parent=None):
1698
+ """Do a fast merge. WARNING: this may change one or more of its frame headers in order to
1699
+ remove duplicate frames. Please keep a handle to the block object instead of the text of
1700
+ the header."""
1701
+ if self.standard is None:
1702
+ mode = 'replace'
1703
+ else:
1704
+ mode = 'strict'
1705
+ overlap_flag = not self.lower_keys.isdisjoint(new_bc.lower_keys)
1706
+ if parent is not None:
1707
+ parent_name = [a[0] for a in self.dictionary.items() if a[1] == parent]
1708
+ if len(parent_name)==0 or len(parent_name)>1:
1709
+ raise StarError("Unable to find unique parent block name: have %s" % str(parent_name))
1710
+ parent_name = parent_name[0]
1711
+ else:
1712
+ parent_name = None #an error will be thrown if we treat as a string
1713
+ if overlap_flag and mode != 'replace':
1714
+ double_keys = self.lower_keys.intersection(new_bc.lower_keys)
1715
+ for dup_key in double_keys:
1716
+ our_parent = self.child_table[dup_key].parent
1717
+ their_parent = new_bc.child_table[dup_key].parent
1718
+ if (our_parent is None and their_parent is not None and parent is None) or\
1719
+ parent is not None: #rename our block
1720
+ start_key = dup_key
1721
+ while start_key in self.lower_keys: start_key = start_key+'+'
1722
+ self._rekey(dup_key,start_key)
1723
+ if parent_name.lower() == dup_key: #we just renamed the prospective parent!
1724
+ parent_name = start_key
1725
+ elif our_parent is not None and their_parent is None and parent is None:
1726
+ start_key = dup_key
1727
+ while start_key in new_bc.lower_keys: start_key = start_key+'+'
1728
+ new_bc._rekey(dup_key,start_key)
1729
+ else:
1730
+ raise StarError("In strict merge mode:duplicate keys %s" % dup_key)
1731
+ self.dictionary.update(new_bc.dictionary)
1732
+ self.lower_keys.update(new_bc.lower_keys)
1733
+ self.visible_keys += (list(new_bc.lower_keys))
1734
+ self.block_input_order += new_bc.block_input_order
1735
+ #print('Block input order now:' + repr(self.block_input_order))
1736
+ self.child_table.update(new_bc.child_table)
1737
+ if parent_name is not None: #redo the child_table entries
1738
+ reparent_list = [(a[0],a[1].block_id) for a in new_bc.child_table.items() if a[1].parent==None]
1739
+ reparent_dict = [(a[0],self.PC(a[1],parent_name.lower())) for a in reparent_list]
1740
+ self.child_table.update(dict(reparent_dict))
1741
+
1742
+ def merge(self,new_bc,mode=None,parent=None,single_block=[],
1743
+ idblock="",match_att=[],match_function=None):
1744
+ if mode is None:
1745
+ if self.standard is None:
1746
+ mode = 'replace'
1747
+ else:
1748
+ mode = 'strict'
1749
+ if single_block:
1750
+ self[single_block[0]].merge(new_bc[single_block[1]],mode,
1751
+ match_att=match_att,
1752
+ match_function=match_function)
1753
+ return None
1754
+ base_keys = [a[1].block_id for a in self.child_table.items()]
1755
+ block_to_item = base_keys #default
1756
+ new_keys = [a[1].block_id for a in new_bc.child_table.items()] #get list of incoming blocks
1757
+ if match_att:
1758
+ #make a blockname -> item name map
1759
+ if match_function:
1760
+ block_to_item = [match_function(self[a]) for a in self.keys()]
1761
+ else:
1762
+ block_to_item = [self[a].get(match_att[0],None) for a in self.keys()]
1763
+ #print `block_to_item`
1764
+ for key in new_keys: #run over incoming blocknames
1765
+ if key == idblock: continue #skip dictionary id
1766
+ basekey = key #default value
1767
+ if len(match_att)>0:
1768
+ attval = new_bc[key].get(match_att[0],0) #0 if ignoring matching
1769
+ else:
1770
+ attval = 0
1771
+ for ii in range(len(block_to_item)): #do this way to get looped names
1772
+ thisatt = block_to_item[ii] #keyname in old block
1773
+ #print "Looking for %s in %s" % (attval,thisatt)
1774
+ if attval == thisatt or \
1775
+ (isinstance(thisatt,list) and attval in thisatt):
1776
+ basekey = base_keys.pop(ii)
1777
+ block_to_item.remove(thisatt)
1778
+ break
1779
+ if not basekey in self or mode=="replace":
1780
+ new_parent = new_bc.get_parent(key)
1781
+ if parent is not None and new_parent is None:
1782
+ new_parent = parent
1783
+ self.NewBlock(basekey,new_bc[key],parent=new_parent) #add the block
1784
+ else:
1785
+ if mode=="strict":
1786
+ raise StarError( "In strict merge mode: block %s in old and block %s in new files" % (basekey,key))
1787
+ elif mode=="overlay":
1788
+ # print "Merging block %s with %s" % (basekey,key)
1789
+ self[basekey].merge(new_bc[key],mode,match_att=match_att)
1790
+ else:
1791
+ raise StarError( "Merge called with unknown mode %s" % mode)
1792
+
1793
+ def checknamelengths(self,target_block,maxlength=-1):
1794
+ if maxlength < 0:
1795
+ return
1796
+ else:
1797
+ toolong = [a for a in target_block.keys() if len(a)>maxlength]
1798
+ outstring = ""
1799
+ if toolong:
1800
+ outstring = "\n".join(toolong)
1801
+ raise StarError( 'Following data names too long:' + outstring)
1802
+
1803
+ def get_all(self,item_name):
1804
+ raw_values = [self[a].get(item_name) for a in self.keys()]
1805
+ raw_values = [a for a in raw_values if a != None]
1806
+ ret_vals = []
1807
+ for rv in raw_values:
1808
+ if isinstance(rv,list):
1809
+ for rvv in rv:
1810
+ if rvv not in ret_vals: ret_vals.append(rvv)
1811
+ else:
1812
+ if rv not in ret_vals: ret_vals.append(rv)
1813
+ return ret_vals
1814
+
1815
+ def __setattr__(self,attr_name,newval):
1816
+ if attr_name == 'scoping':
1817
+ if newval not in ('dictionary','instance'):
1818
+ raise StarError("Star file may only have 'dictionary' or 'instance' scoping, not %s" % newval)
1819
+ if newval == 'dictionary':
1820
+ self.visible_keys = [a for a in self.lower_keys]
1821
+ else:
1822
+ #only top-level datablocks visible
1823
+ self.visible_keys = [a[0] for a in self.child_table.items() if a[1].parent==None]
1824
+ object.__setattr__(self,attr_name,newval)
1825
+
1826
+ def get_parent(self,blockname):
1827
+ """Return the name of the block enclosing [[blockname]] in canonical form (lower case)"""
1828
+ possibles = (a for a in self.child_table.items() if a[0] == blockname.lower())
1829
+ try:
1830
+ first = next(possibles) #get first one
1831
+ except:
1832
+ raise StarError('no parent for %s' % blockname)
1833
+ try:
1834
+ second = next(possibles)
1835
+ except StopIteration:
1836
+ return first[1].parent
1837
+ raise StarError('More than one parent for %s' % blockname)
1838
+
1839
+ def get_roots(self):
1840
+ """Get the top-level blocks"""
1841
+ return [a for a in self.child_table.items() if a[1].parent==None]
1842
+
1843
+ def get_children(self,blockname,include_parent=False,scoping='dictionary'):
1844
+ """Get all children of [[blockname]] as a block collection. If [[include_parent]] is
1845
+ True, the parent block will also be included in the block collection as the root."""
1846
+ newbc = BlockCollection()
1847
+ block_lower = blockname.lower()
1848
+ proto_child_table = [a for a in self.child_table.items() if self.is_child_of_parent(block_lower,a[1].block_id)]
1849
+ newbc.child_table = dict(proto_child_table)
1850
+ if not include_parent:
1851
+ newbc.child_table.update(dict([(a[0],self.PC(a[1].block_id,None)) for a in proto_child_table if a[1].parent == block_lower]))
1852
+ newbc.lower_keys = set([a[0] for a in proto_child_table])
1853
+ newbc.dictionary = dict((a[0],self.dictionary[a[0]]) for a in proto_child_table)
1854
+ if include_parent:
1855
+ newbc.child_table.update({block_lower:self.PC(self.child_table[block_lower].block_id,None)})
1856
+ newbc.lower_keys.add(block_lower)
1857
+ newbc.dictionary.update({block_lower:self.dictionary[block_lower]})
1858
+ newbc.scoping = scoping
1859
+ return newbc
1860
+
1861
+ def get_immediate_children(self,parentname):
1862
+ """Get the next level of children of the given block as a list, without nested levels"""
1863
+ child_handles = [a for a in self.child_table.items() if a[1].parent == parentname.lower()]
1864
+ return child_handles
1865
+
1866
+ # This takes time
1867
+ def get_child_list(self,parentname):
1868
+ """Get a list of all child categories in alphabetical order"""
1869
+ child_handles = [a[0] for a in self.child_table.items() if self.is_child_of_parent(parentname.lower(),a[0])]
1870
+ child_handles.sort()
1871
+ return child_handles
1872
+
1873
+ def is_child_of_parent(self,parentname,blockname):
1874
+ """Return `True` if `blockname` is a child of `parentname`"""
1875
+ checkname = parentname.lower()
1876
+ more_children = [a[0] for a in self.child_table.items() if a[1].parent == checkname]
1877
+ if blockname.lower() in more_children:
1878
+ return True
1879
+ else:
1880
+ for one_child in more_children:
1881
+ if self.is_child_of_parent(one_child,blockname): return True
1882
+ return False
1883
+
1884
+ def set_parent(self,parentname,childname):
1885
+ """Set the parent block"""
1886
+ # first check that both blocks exist
1887
+ if parentname.lower() not in self.lower_keys:
1888
+ raise KeyError('Parent block %s does not exist' % parentname)
1889
+ if childname.lower() not in self.lower_keys:
1890
+ raise KeyError('Child block %s does not exist' % childname)
1891
+ old_entry = self.child_table[childname.lower()]
1892
+ self.child_table[childname.lower()]=self.PC(old_entry.block_id,
1893
+ parentname.lower())
1894
+ self.scoping = self.scoping #reset visibility
1895
+
1896
+ def SetTemplate(self,template_file):
1897
+ """Use `template_file` as a template for all block output"""
1898
+ self.master_template = process_template(template_file)
1899
+ for b in self.dictionary.values():
1900
+ b.formatting_hints = self.master_template
1901
+
1902
+ def WriteOut(self,comment='',wraplength=80,maxoutlength=0,blockorder=None,saves_after=None):
1903
+ """Return the contents of this file as a string, wrapping if possible at `wraplength`
1904
+ characters and restricting maximum line length to `maxoutlength`. Delimiters and
1905
+ save frame nesting are controlled by `self.grammar`. If `blockorder` is
1906
+ provided, blocks are output in this order unless nested save frames have been
1907
+ requested (STAR2). The default block order is the order in which blocks were input.
1908
+ `saves_after` inserts all save frames after the given dataname,
1909
+ which allows less important items to appear later. Useful in conjunction with a
1910
+ template for dictionary files."""
1911
+ if maxoutlength != 0:
1912
+ self.SetOutputLength(maxoutlength)
1913
+ if not comment:
1914
+ comment = self.header_comment
1915
+ outstring = StringIO()
1916
+ if self.grammar == "2.0" and comment[0:10] != r"#\#CIF_2.0":
1917
+ outstring.write(r"#\#CIF_2.0" + "\n")
1918
+ outstring.write(comment)
1919
+ # prepare all blocks
1920
+ for b in self.dictionary.values():
1921
+ b.set_grammar(self.grammar)
1922
+ b.formatting_hints = self.master_template
1923
+ b.SetOutputLength(wraplength,self.maxoutlength)
1924
+ # loop over top-level
1925
+ # monitor output
1926
+ all_names = list(self.child_table.keys()) #i.e. lower case
1927
+ if blockorder is None:
1928
+ blockorder = self.block_input_order
1929
+ top_block_names = [(a,self.child_table[a].block_id) for a in blockorder if self.child_table[a].parent is None]
1930
+ for blockref,blockname in top_block_names:
1931
+ print('Writing %s, ' % blockname + repr(self[blockref]))
1932
+ outstring.write('\n' + 'data_' +blockname+'\n')
1933
+ all_names.remove(blockref)
1934
+ if self.standard == 'Dic': #put contents before save frames
1935
+ outstring.write(self[blockref].printsection(finish_at='_dictionary_valid.application'))
1936
+ if self.grammar == 'STAR2': #nested save frames
1937
+ child_refs = self.get_immediate_children(blockref)
1938
+ for child_ref,child_info in child_refs:
1939
+ child_name = child_info.block_id
1940
+ outstring.write('\n\n' + 'save_' + child_name + '\n')
1941
+ self.block_to_string_nested(child_ref,child_name,outstring,4)
1942
+ outstring.write('\n' + 'save_'+ '\n')
1943
+ elif self.grammar in ('1.0','1.1','2.0'): #non-nested save frames
1944
+ child_refs = [a for a in blockorder if self.is_child_of_parent(blockref,a)]
1945
+ for child_ref in child_refs:
1946
+ child_name = self.child_table[child_ref].block_id
1947
+ outstring.write('\n\n' + 'save_' + child_name + '\n')
1948
+ outstring.write(str(self[child_ref]))
1949
+ outstring.write('\n\n' + 'save_' + '\n')
1950
+ all_names.remove(child_ref.lower())
1951
+ else:
1952
+ raise StarError('Grammar %s is not recognised for output' % self.grammar)
1953
+ if self.standard != 'Dic': #put contents after save frames
1954
+ outstring.write(str(self[blockref]))
1955
+ else:
1956
+ outstring.write(self[blockref].printsection(start_from='_dictionary_valid.application'))
1957
+ returnstring = outstring.getvalue()
1958
+ outstring.close()
1959
+ if len(all_names)>0:
1960
+ print('WARNING: following blocks not output: %s' % repr(all_names))
1961
+ else:
1962
+ print('All blocks output.')
1963
+ return returnstring
1964
+
1965
+ def block_to_string_nested(self,block_ref,block_id,outstring,indentlevel=0):
1966
+ """Output a complete datablock indexed by [[block_ref]] and named [[block_id]], including children,
1967
+ and syntactically nesting save frames"""
1968
+ child_refs = self.get_immediate_children(block_ref)
1969
+ self[block_ref].set_grammar(self.grammar)
1970
+ if self.standard == 'Dic':
1971
+ outstring.write(str(self[block_ref]))
1972
+ for child_ref,child_info in child_refs:
1973
+ child_name = child_info.block_id
1974
+ outstring.write('\n' + 'save_' + child_name + '\n')
1975
+ self.block_to_string_nested(child_ref,child_name,outstring,indentlevel)
1976
+ outstring.write('\n' + ' '*indentlevel + 'save_' + '\n')
1977
+ if self.standard != 'Dic':
1978
+ outstring.write(str(self[block_ref]))
1979
+
1980
+
1981
+ class StarFile(BlockCollection):
1982
+ def __init__(self,datasource=None,maxinlength=-1,maxoutlength=0,
1983
+ scoping='instance',grammar='1.1',scantype='standard',
1984
+ permissive=False,**kwargs):
1985
+ super(StarFile,self).__init__(datasource=datasource,**kwargs)
1986
+ self.my_uri = getattr(datasource,'my_uri','')
1987
+ if maxoutlength == 0:
1988
+ self.maxoutlength = 2048
1989
+ else:
1990
+ self.maxoutlength = maxoutlength
1991
+ self.scoping = scoping
1992
+ if isinstance(datasource,(unicode,str)) or hasattr(datasource,"read"):
1993
+ ReadStar(datasource,prepared=self,grammar=grammar,scantype=scantype,
1994
+ maxlength = maxinlength,permissive=permissive)
1995
+ self.header_comment = \
1996
+ """#\\#STAR
1997
+ ##########################################################################
1998
+ # STAR Format file
1999
+ # Produced by PySTARRW module
2000
+ #
2001
+ # This is a STAR file. STAR is a superset of the CIF file type. For
2002
+ # more information, please refer to International Tables for Crystallography,
2003
+ # Volume G, Chapter 2.1
2004
+ #
2005
+ ##########################################################################
2006
+ """
2007
+ def set_uri(self,my_uri): self.my_uri = my_uri
2008
+
2009
+
2010
+ class CIFStringIO(StringIO):
2011
+ def __init__(self,target_width=80,**kwargs):
2012
+ StringIO.__init__(self,**kwargs)
2013
+ self.currentpos = 0
2014
+ self.target_width = target_width
2015
+ self.tabwidth = -1
2016
+ self.indentlist = [0]
2017
+ self.last_char = ""
2018
+
2019
+ def write(self,outstring,canbreak=False,mustbreak=False,do_tab=True,newindent=False,unindent=False,
2020
+ delimiter=False,startcol=-1):
2021
+ """Write a string with correct linebreak, tabs and indents"""
2022
+ # do we need to break?
2023
+ if delimiter:
2024
+ if len(outstring)>1:
2025
+ raise ValueError('Delimiter %s is longer than one character' % repr( outstring ))
2026
+ output_delimiter = True
2027
+ if mustbreak: #insert a new line and indent
2028
+ temp_string = '\n' + ' ' * self.indentlist[-1]
2029
+ StringIO.write(self,temp_string)
2030
+ self.currentpos = self.indentlist[-1]
2031
+ self.last_char = temp_string[-1]
2032
+ if self.currentpos+len(outstring)>self.target_width: #try to break
2033
+ if not delimiter and outstring[0]!='\n': #ie <cr>;
2034
+ if canbreak:
2035
+ temp_string = '\n' + ' ' * self.indentlist[-1]
2036
+ StringIO.write(self,temp_string)
2037
+ self.currentpos = self.indentlist[-1]
2038
+ self.last_char = temp_string[-1]
2039
+ else: #assume a break will be forced on next value
2040
+ output_delimiter = False #the line break becomes the delimiter
2041
+ #try to match requested column
2042
+ if startcol > 0:
2043
+ if self.currentpos < startcol:
2044
+ StringIO.write(self,(startcol - self.currentpos)* ' ')
2045
+ self.currentpos = startcol
2046
+ self.last_char = ' '
2047
+ else:
2048
+ print('Could not format %s at column %d as already at %d' % (outstring,startcol,self.currentpos))
2049
+ startcol = -1 #so that tabbing works as a backup
2050
+ #handle tabs
2051
+ if self.tabwidth >0 and do_tab and startcol < 0:
2052
+ next_stop = ((self.currentpos//self.tabwidth)+1)*self.tabwidth
2053
+ #print 'Currentpos %d: Next tab stop at %d' % (self.currentpos,next_stop)
2054
+ if self.currentpos < next_stop:
2055
+ StringIO.write(self,(next_stop-self.currentpos)*' ')
2056
+ self.currentpos = next_stop
2057
+ self.last_char = ' '
2058
+ #calculate indentation after tabs and col setting applied
2059
+ if newindent: #indent by current amount
2060
+ if self.indentlist[-1] == 0: #first time
2061
+ self.indentlist.append(self.currentpos)
2062
+ # print 'Indentlist: ' + `self.indentlist`
2063
+ else:
2064
+ self.indentlist.append(self.indentlist[-1]+2)
2065
+ elif unindent:
2066
+ if len(self.indentlist)>1:
2067
+ self.indentlist.pop()
2068
+ else:
2069
+ print('Warning: cannot unindent any further')
2070
+ #check that we still need a delimiter
2071
+ if self.last_char in [' ','\n','\t']:
2072
+ output_delimiter = False
2073
+ #now output the string - every invocation comes through here
2074
+ if (delimiter and output_delimiter) or not delimiter:
2075
+ StringIO.write(self,outstring)
2076
+ last_line_break = outstring.rfind('\n')
2077
+ if last_line_break >=0:
2078
+ self.currentpos = len(outstring)-last_line_break
2079
+ else:
2080
+ self.currentpos = self.currentpos + len(outstring)
2081
+ #remember the last character
2082
+ if len(outstring)>0:
2083
+ self.last_char = outstring[-1]
2084
+
2085
+ def set_tab(self,tabwidth):
2086
+ """Set the tab stop position"""
2087
+ self.tabwidth = tabwidth
2088
+
2089
+ class StarError(Exception):
2090
+ def __init__(self,value):
2091
+ self.value = value
2092
+ def __str__(self):
2093
+ return '\nStar Format error: '+ self.value
2094
+
2095
+ class StarLengthError(Exception):
2096
+ def __init__(self,value):
2097
+ self.value = value
2098
+ def __str__(self):
2099
+ return '\nStar length error: ' + self.value
2100
+
2101
+ class StarDerivationError(Exception):
2102
+ def __init__(self,fail_name):
2103
+ self.fail_name = fail_name
2104
+ def __str__(self):
2105
+ return "Derivation of %s failed, None returned" % self.fail_name
2106
+
2107
+ #
2108
+ # This is subclassed from AttributeError in order to allow hasattr
2109
+ # to work.
2110
+ #
2111
+ class StarDerivationFailure(AttributeError):
2112
+ def __init__(self,fail_name):
2113
+ self.fail_name = fail_name
2114
+ def __str__(self):
2115
+ return "Derivation of %s failed" % self.fail_name
2116
+
2117
+ def ReadStar(filename,prepared = None, maxlength=-1,
2118
+ scantype='standard',grammar='STAR2',CBF=False, permissive=False):
2119
+
2120
+ """ Read in a STAR file, returning the contents in the `prepared` object.
2121
+
2122
+ * `filename` may be a URL, a file
2123
+ path on the local system, or any object with a `read` method.
2124
+
2125
+ * `prepared` provides a `StarFile` or `CifFile` object that the contents of `filename`
2126
+ will be added to.
2127
+
2128
+ * `maxlength` is the maximum allowable line length in the input file. This has been set at
2129
+ 2048 characters for CIF but is unlimited (-1) for STAR files.
2130
+
2131
+ * `grammar` chooses the STAR grammar variant. `1.0` is the original 1992 CIF/STAR grammar and `1.1`
2132
+ is identical except for the exclusion of square brackets as the first characters in
2133
+ undelimited datanames. `2.0` will read files in the CIF2.0 standard, and `STAR2` will
2134
+ read files according to the STAR2 publication. If grammar is `None` or `auto`, autodetection
2135
+ will be attempted in the order `2.0`, `1.1` and `1.0`. This will always succeed for conformant CIF2.0 files.
2136
+ Note that (nested) save frames are read in all grammar variations and then flagged afterwards if
2137
+ they do not match the requested grammar.
2138
+
2139
+ * `scantype` can be `standard` or `flex`. `standard` provides pure Python parsing at the
2140
+ cost of a factor of 10 or so in speed. `flex` will tokenise the input CIF file using
2141
+ fast C routines. Note that running PyCIFRW in Jython uses native Java regular expressions
2142
+ to provide a speedup regardless of this argument.
2143
+
2144
+ * `CBF` flags that the input file is in Crystallographic Binary File format. The binary block is
2145
+ excised from the input data stream before parsing and is not available in the returned object.
2146
+
2147
+ * `permissive` allows non UTF8 encodings (currently only latin1) in the input file. These are a
2148
+ violation of the standard.
2149
+
2150
+ """
2151
+
2152
+ # save desired scoping
2153
+ save_scoping = prepared.scoping
2154
+ from . import YappsStarParser_1_1 as Y11
2155
+ from . import YappsStarParser_1_0 as Y10
2156
+ from . import YappsStarParser_2_0 as Y20
2157
+ from . import YappsStarParser_STAR2 as YST
2158
+ if prepared is None:
2159
+ prepared = StarFile()
2160
+ if grammar == "auto" or grammar is None:
2161
+ try_list = [('2.0',Y20),('1.1',Y11),('1.0',Y10)]
2162
+ elif grammar == '1.0':
2163
+ try_list = [('1.0',Y10)]
2164
+ elif grammar == '1.1':
2165
+ try_list = [('1.1',Y11)]
2166
+ elif grammar == '2.0':
2167
+ try_list = [('2.0',Y20)]
2168
+ elif grammar == 'STAR2':
2169
+ try_list = [('STAR2',YST)]
2170
+ else:
2171
+ raise AttributeError('Unknown STAR/CIF grammar requested, %s' % repr( grammar ))
2172
+ if isinstance(filename,(unicode,str)):
2173
+ # create an absolute URL
2174
+ relpath = urlparse(filename)
2175
+ if len(relpath.scheme) <= 1:
2176
+ if not os.path.isabs(filename):
2177
+ fullpath = os.path.join(os.getcwd(),filename)
2178
+ else:
2179
+ fullpath = filename
2180
+ if have_pathlib: # Python > 3.4
2181
+ my_uri = Path(fullpath).as_uri()
2182
+ else: # works on Linux/Mac only
2183
+ newrel = list(relpath)
2184
+ newrel[0] = "file"
2185
+ newrel[2] = fullpath
2186
+ my_uri = urlunparse(newrel)
2187
+ else:
2188
+ my_uri = urlunparse(relpath)
2189
+ # print("Full URL is: " + my_uri)
2190
+ filestream = urlopen(my_uri)
2191
+ try:
2192
+ text = filestream.read().decode('utf-8-sig')
2193
+ except UnicodeDecodeError:
2194
+ if permissive:
2195
+ text = filestream.read().decode('latin1')
2196
+ print("WARNING: %s violates standard (latin1 encoding instead of UTF8)." % filename)
2197
+ else:
2198
+ raise StarError("%s: bad encoding (must be utf8 or ascii)" % filename)
2199
+ filestream.close()
2200
+ else:
2201
+ filestream = filename #already opened for us
2202
+ text = filestream.read()
2203
+ if not isinstance(text,unicode):
2204
+ try:
2205
+ text = text.decode('utf-8-sig') #CIF is always ascii/utf8
2206
+ except UnicodeDecodeError:
2207
+ if permissive:
2208
+ text = filestream.read().decode('latin1')
2209
+ print("WARNING: text violates CIF standard (latin1 encoding instead of UTF8)")
2210
+ else:
2211
+ raise StarError("Bad input encoding (must be utf8 or ascii)")
2212
+ my_uri = ""
2213
+ if not text: # empty file, return empty block
2214
+ return prepared.set_uri(my_uri)
2215
+ # filter out non-ASCII characters in CBF files if required. We assume
2216
+ # that the binary is enclosed in a fixed string that occurs
2217
+ # nowhere else.
2218
+ if CBF:
2219
+ text_bits = text.split("-BINARY-FORMAT-SECTION-")
2220
+ text = text_bits[0]
2221
+ for section in range(2,len(text_bits),2):
2222
+ text = text+" (binary omitted)"+text_bits[section]
2223
+ # we recognise ctrl-Z as end of file
2224
+ endoffile = text.find(chr(26))
2225
+ if endoffile >= 0:
2226
+ text = text[:endoffile]
2227
+ split = text.split('\n')
2228
+ if maxlength > 0:
2229
+ toolong = [a for a in split if len(a)>maxlength]
2230
+ if toolong:
2231
+ pos = split.index(toolong[0])
2232
+ raise StarError( 'Line %d contains more than %d characters' % (pos+1,maxlength))
2233
+ # honour the header string
2234
+ if text[:10] != r"#\#CIF_2.0" and ('2.0',Y20) in try_list:
2235
+ try_list.remove(('2.0',Y20),)
2236
+ if not try_list:
2237
+ raise StarError('File %s missing CIF2.0 header' % (filename))
2238
+ for grammar_name,Y in try_list:
2239
+ if scantype == 'standard' or grammar_name in ['2.0','STAR2']:
2240
+ parser = Y.StarParser(Y.StarParserScanner(text))
2241
+ else:
2242
+ parser = Y.StarParser(Y.yappsrt.Scanner(None,[],text,scantype='flex'))
2243
+ # handle encoding switch
2244
+ if grammar_name in ['2.0','STAR2']:
2245
+ prepared.set_characterset('unicode')
2246
+ else:
2247
+ prepared.set_characterset('ascii')
2248
+ proto_star = None
2249
+ try:
2250
+ proto_star = getattr(parser,"input")(prepared)
2251
+ except Y.yappsrt.YappsSyntaxError as e:
2252
+ input = parser._scanner.input
2253
+ Y.yappsrt.print_error(input, e, parser._scanner)
2254
+ except Y.yappsrt.NoMoreTokens:
2255
+ print('Could not complete parsing; stopped around here:',file=sys.stderr)
2256
+ print(parser._scanner,file=sys.stderr)
2257
+ except ValueError:
2258
+ print('Unexpected error:')
2259
+ import traceback
2260
+ traceback.print_exc()
2261
+ if proto_star is not None:
2262
+ proto_star.set_grammar(grammar_name) #remember for output
2263
+ break
2264
+ if proto_star is None:
2265
+ errorstring = 'Syntax error in input file: last value parsed was %s' % Y.lastval
2266
+ errorstring = errorstring + '\nParser status: %s' % repr( parser._scanner )
2267
+ raise StarError( errorstring)
2268
+ # set visibility correctly
2269
+ proto_star.scoping = 'dictionary'
2270
+ proto_star.set_uri(my_uri)
2271
+ proto_star.scoping = save_scoping
2272
+ return proto_star
2273
+
2274
+ def get_dim(dataitem,current=0,packlen=0):
2275
+ zerotypes = [int, float, str]
2276
+ if type(dataitem) in zerotypes:
2277
+ return current, packlen
2278
+ if not dataitem.__class__ == ().__class__ and \
2279
+ not dataitem.__class__ == [].__class__:
2280
+ return current, packlen
2281
+ elif len(dataitem)>0:
2282
+ # print "Get_dim: %d: %s" % (current,`dataitem`)
2283
+ return get_dim(dataitem[0],current+1,len(dataitem))
2284
+ else: return current+1,0
2285
+
2286
+ def apply_line_folding(instring,minwraplength=60,maxwraplength=80):
2287
+ """Insert line folding characters into instring between min/max wraplength"""
2288
+ # first check that we need to do this
2289
+ lines = instring.split('\n')
2290
+ line_len = [len(l) for l in lines]
2291
+ if max(line_len) < maxwraplength and re.match("\\[ \v\t\f]*\n",instring) is None:
2292
+ return instring
2293
+ outstring = "\\\n" #header
2294
+ for l in lines:
2295
+ if len(l) < maxwraplength:
2296
+ outstring = outstring + l
2297
+ if len(l) > 0 and l[-1]=='\\': #who'da thunk it? A line ending with a backslash
2298
+ outstring = outstring + "\\\n" #
2299
+ outstring = outstring + "\n" # put back the split character
2300
+ else:
2301
+ current_bit = l
2302
+ while len(current_bit) > maxwraplength:
2303
+ space_pos = re.search('[ \v\f\t]+',current_bit[minwraplength:])
2304
+ if space_pos is not None and space_pos.start()<maxwraplength-1:
2305
+ outstring = outstring + current_bit[:minwraplength+space_pos.start()] + "\\\n"
2306
+ current_bit = current_bit[minwraplength+space_pos.start():]
2307
+ else: #just blindly insert
2308
+ outstring = outstring + current_bit[:maxwraplength-1] + "\\\n"
2309
+ current_bit = current_bit[maxwraplength-1:]
2310
+ outstring = outstring + current_bit
2311
+ if current_bit[-1] == '\\': #a backslash just happens to be here
2312
+ outstring = outstring + "\\\n"
2313
+ outstring = outstring + '\n'
2314
+ outstring = outstring[:-1] #remove final newline
2315
+ return outstring
2316
+
2317
+ def remove_line_folding(instring):
2318
+ """Remove line folding from instring"""
2319
+ if re.match(r"\\[ \v\t\f]*" +"\n",instring) is not None:
2320
+ return re.sub(r"\\[ \v\t\f]*$" + "\n?","",instring,flags=re.M)
2321
+ else:
2322
+ return instring
2323
+
2324
+ def apply_line_prefix(instring,prefix):
2325
+ """Prefix every line in instring with prefix"""
2326
+ if prefix[0] != ";" and "\\" not in prefix:
2327
+ header = re.match(r"(\\[ \v\t\f]*" +"\n)",instring)
2328
+ if header is not None:
2329
+ print('Found line folded string for prefixing...')
2330
+ not_header = instring[header.end():]
2331
+ outstring = prefix + "\\\\\n" + prefix
2332
+ else:
2333
+ print('No folding in input string...')
2334
+ not_header = instring
2335
+ outstring = prefix + "\\\n" + prefix
2336
+ outstring = outstring + not_header.replace("\n","\n"+prefix)
2337
+ return outstring
2338
+ raise StarError("Requested prefix starts with semicolon or contains a backslash: " + prefix)
2339
+
2340
+ def remove_line_prefix(instring):
2341
+ """Remove prefix from every line if present"""
2342
+ prefix_match = re.match("(?P<prefix>[^;\\\n][^\n\\\\]+)(?P<folding>\\\\{1,2}[ \t\v\f]*\n)",instring)
2343
+ if prefix_match is not None:
2344
+ prefix_text = prefix_match.group('prefix')
2345
+ print('Found prefix %s' % prefix_text)
2346
+ prefix_end = prefix_match.end('folding')
2347
+ # keep any line folding instructions
2348
+ if prefix_match.group('folding')[:2]=='\\\\': #two backslashes
2349
+ outstring = instring[prefix_match.end('folding')-1:].replace("\n"+prefix_text,"\n")
2350
+ return "\\" + outstring #keep line folding first line
2351
+ else:
2352
+ outstring = instring[prefix_match.end('folding')-1:].replace("\n"+prefix_text,"\n")
2353
+ return outstring[1:] #drop first line ending, no longer necessary
2354
+ else:
2355
+ return instring
2356
+
2357
+
2358
+ def listify(item):
2359
+ if isinstance(item,unicode): return [item]
2360
+ else: return item
2361
+
2362
+ #Transpose the list of lists passed to us
2363
+ def transpose(base_list):
2364
+ new_lofl = []
2365
+ full_length = len(base_list)
2366
+ opt_range = range(full_length)
2367
+ for i in range(len(base_list[0])):
2368
+ new_packet = []
2369
+ for j in opt_range:
2370
+ new_packet.append(base_list[j][i])
2371
+ new_lofl.append(new_packet)
2372
+ return new_lofl
2373
+
2374
+ # This routine optimised to return as quickly as possible
2375
+ # as it is called a lot.
2376
+ def not_none(itemlist):
2377
+ """Return true only if no values of None are present"""
2378
+ if itemlist is None:
2379
+ return False
2380
+ if not isinstance(itemlist,(tuple,list)):
2381
+ return True
2382
+ for x in itemlist:
2383
+ if not not_none(x): return False
2384
+ return True
2385
+
2386
+
2387
+ def check_stringiness(data):
2388
+ """Check that the contents of data are all strings"""
2389
+ if not hasattr(data,'dtype'): #so not Numpy
2390
+ from numbers import Number
2391
+ if isinstance(data,Number): return False
2392
+ elif isinstance(data,(unicode,str)): return True
2393
+ elif data is None:return False #should be data are None :)
2394
+ else:
2395
+ for one_item in data:
2396
+ if not check_stringiness(one_item): return False
2397
+ return True #all must be strings
2398
+ else: #numerical python
2399
+ import numpy
2400
+ if data.ndim == 0: #a bare value
2401
+ if data.dtype.kind in ['S','U']: return True
2402
+ else: return False
2403
+ else:
2404
+ for one_item in numpy.nditer(data):
2405
+ print('numpy data: ' + repr( one_item ))
2406
+ if not check_stringiness(one_item): return False
2407
+ return True
2408
+
2409
+ def process_template(template_file):
2410
+ """Process a template datafile to formatting instructions"""
2411
+ template_as_cif = StarFile(template_file,grammar="2.0").first_block()
2412
+ if isinstance(template_file,(unicode,str)):
2413
+ template_string = open(template_file).read()
2414
+ else: #a StringIO object
2415
+ template_file.seek(0) #reset
2416
+ template_string = template_file.read()
2417
+ #template_as_lines = template_string.split("\n")
2418
+ #template_as_lines = [l for l in template_as_lines if len(l)>0 and l[0]!='#']
2419
+ #template_as_lines = [l for l in template_as_lines if l.split()[0] != 'loop_']
2420
+ #template_full_lines = dict([(l.split()[0],l) for l in template_as_lines if len(l.split())>0])
2421
+ form_hints = [] #ordered array of hint dictionaries
2422
+ find_indent = "^ +"
2423
+ for item in template_as_cif.item_order: #order of input
2424
+ if not isinstance(item,int): #not nested
2425
+ hint_dict = {"dataname":item}
2426
+ # find the line in the file
2427
+ start_pos = re.search("(^[ \t]*(?P<name>" + item + ")[ \t\n]+)(?P<spec>([\\S]+)|(^;))",template_string,re.I|re.M)
2428
+ if start_pos.group("spec") != None:
2429
+ spec_pos = start_pos.start("spec")-start_pos.start(0)
2430
+ spec_char = template_string[start_pos.start("spec"):start_pos.start("spec")+3]
2431
+ if spec_char[0] in '\'";':
2432
+ hint_dict.update({"delimiter":spec_char[0]})
2433
+ if spec_char == '"""' or spec_char == "'''":
2434
+ hint_dict.update({"delimiter":spec_char})
2435
+ if spec_char[0] != ";": #so we need to work out the column number
2436
+ hint_dict.update({"column":spec_pos})
2437
+ else: #need to put in the carriage return
2438
+ hint_dict.update({"delimiter":"\n;"})
2439
+ # can we format the text?
2440
+ text_val = template_as_cif[item]
2441
+ hint_dict["reformat"] = "\n\t" in text_val or "\n " in text_val
2442
+ if hint_dict["reformat"]: #find the indentation
2443
+ p = re.search(find_indent,text_val,re.M)
2444
+ if p.group() is not None:
2445
+ hint_dict["reformat_indent"]=p.end() - p.start()
2446
+ if start_pos.group('name') != None:
2447
+ name_pos = start_pos.start('name') - start_pos.start(0)
2448
+ hint_dict.update({"name_pos":name_pos})
2449
+ #print '%s: %s' % (item,`hint_dict`)
2450
+ form_hints.append(hint_dict)
2451
+ else: #loop block
2452
+ testnames = template_as_cif.loops[item]
2453
+ total_items = len(template_as_cif.loops[item])
2454
+ testname = testnames[0]
2455
+ #find the loop spec line in the file
2456
+ loop_regex = "(^[ \t]*(?P<loop>loop_)[ \t\n\r]+(?P<name>" + testname + ")([ \t\n\r]+_[\\S]+){%d}[ \t]*$(?P<packet>(.(?!_loop|_[\\S]+))*))" % (total_items - 1)
2457
+ loop_line = re.search(loop_regex,template_string,re.I|re.M|re.S)
2458
+ loop_so_far = loop_line.end()
2459
+ packet_text = loop_line.group('packet')
2460
+ loop_indent = loop_line.start('loop') - loop_line.start(0)
2461
+ form_hints.append({"dataname":'loop','name_pos':loop_indent})
2462
+ packet_regex = "[ \t]*(?P<all>(?P<sqqq>'''([^\n\r\f']*)''')|(?P<sq>'([^\n\r\f']*)'+)|(?P<dq>\"([^\n\r\"]*)\"+)|(?P<none>[^\\s]+))"
2463
+ packet_pos = re.finditer(packet_regex,packet_text)
2464
+ line_end_pos = re.finditer("^",packet_text,re.M)
2465
+ next_end = next(line_end_pos).end()
2466
+ last_end = next_end
2467
+ for loopname in testnames:
2468
+ #find the name in the file for name pos
2469
+ name_regex = "(^[ \t]*(?P<name>" + loopname + "))"
2470
+ name_match = re.search(name_regex,template_string,re.I|re.M|re.S)
2471
+ loop_name_indent = name_match.start('name')-name_match.start(0)
2472
+ hint_dict = {"dataname":loopname,"name_pos":loop_name_indent}
2473
+ #find the value
2474
+ thismatch = next(packet_pos)
2475
+ while thismatch.start('all') > next_end:
2476
+ try:
2477
+ last_end = next_end
2478
+ next_end = next(line_end_pos).start()
2479
+ print('next end %d' % next_end)
2480
+ except StopIteration:
2481
+ break
2482
+ print('Start %d, last_end %d' % (thismatch.start('all'),last_end))
2483
+ col_pos = thismatch.start('all') - last_end + 1
2484
+ if thismatch.group('none') is None:
2485
+ if thismatch.group('sqqq') is not None:
2486
+ hint_dict.update({'delimiter':"'''"})
2487
+ else:
2488
+ hint_dict.update({'delimiter':thismatch.groups()[0][0]})
2489
+ hint_dict.update({'column':col_pos})
2490
+ print('%s: %s' % (loopname,repr( hint_dict )))
2491
+ form_hints.append(hint_dict)
2492
+ return form_hints
2493
+
2494
+
2495
+ #No documentation flags
2496
+