"""
CIF format file I/O operations.
"""
import shlex
from datetime import date
[docs]class CIF(object):
[docs] def __init__(self, name="structure", file=None):
self.name = name
self._data = {}
self._headings = {}
self._element_labels = {}
self.non_loops = ["data", "cell", "sym", "end"]
self.block_order = ["data", "sym", "sym_loop", "cell", "atoms", "bonds"]
if file is not None:
self.read(file)
[docs] def read(self, filename):
filestream = open(filename, 'r')
filelines = filestream.readlines()
blocks = []
loopcount = 0
loopentries = {}
loopread = False
blockread = False
self.block_order = []
for line in filelines:
#line=line.replace("\n", "")
# why not strip here?
line = line.strip()
if line.startswith("data_"):
self.name = line[5:]
self.insert_block_order("data")
self.add_data("data", data_=self.name)
if loopread and line.startswith("_"):
# change block names, easier for adding data to structure graph
if ('_atom_' in line and '_bond_' not in line):
self.insert_block_order('atoms', loopcount, _REPLACE=True)
elif ('_geom_bond' in line):
self.insert_block_order('bonds', loopcount, _REPLACE=True)
loopentries[loopcount].append(line)
elif loopread and not line.startswith("_"):
loopread = False
blockread = True
elif not loopread and line.startswith("_"):
block = self.get_non_loop_block(line)
self.insert_block_order(block)
# hopefully all non-loop entries are just single value entries,
# otherwise this is invalid.
try:
key, val = line.strip().split()
except ValueError:
key, val = line.strip().split()[:2]
if val.endswith("(0)"):
val = val[:-3]
self.add_data(block, **{key.strip():self.general_label(val)})
if blockread and (line.startswith("loop_") or line.startswith("_") or not line):
blockread = False
if line == "loop_":
loopcount += 1
loopentries[loopcount] = []
loopread = True
blockread = False
self.insert_block_order(loopcount)
if blockread:
#split_line = line.strip().split()
split_line = shlex.split(line)
assert len(loopentries[loopcount]) == len(split_line)
for key, val in zip(loopentries[loopcount], split_line):
self.add_data(loopcount, **{key:self.general_label(val)})
filestream.close()
[docs] def get_time(self):
t = date.today()
return t.strftime("%A %d %B %Y")
[docs] def insert_block_order(self, name, index=None, _REPLACE=False):
"""Adds a block to the cif file in a specified order, unless index is specified,
will not override existing order"""
if index is None and name in self.block_order:
return
elif index is not None and (self.block_order[index] == name):
return
elif index is None and name not in self.block_order:
index = len(self.block_order)
elif index is not None and name in self.block_order and index < len(self.block_order) and not _REPLACE:
old = self.block_order.index(name)
self.block_order.pop(old)
elif index is not None and name not in self.block_order and index < len(self.block_order) and _REPLACE:
self.block_order.pop(index)
elif index is not None and name in self.block_order and index >= len(self.block_order):
old = self.block_order.index(name)
self.block_order.pop(old)
index = len(self.block_order)
self.block_order = self.block_order[:index] + [name] + \
self.block_order[index:]
[docs] def add_data(self, block, **kwargs):
self._headings.setdefault(block, [])
for key, val in kwargs.items():
try:
self._data[key].append(val)
except KeyError:
self._headings[block].append(key)
if block in self.non_loops:
self._data[key] = val
else:
self._data[key] = [val]
except:
print(self._data.keys())
[docs] def get_element_label(self, el):
self._element_labels.setdefault(el, 0)
self._element_labels[el] += 1
return el + str(self._element_labels[el])
[docs] def __str__(self):
line = ""
for block in self.block_order:
# NOTE still be able to write CIFS if blond bock not specified
if block in self._headings:
heads = self._headings[block]
if block in self.non_loops:
vals = zip([CIF.label(i) for i in heads], [self._data[i] for i in heads])
else:
line += "loop_\n"+"\n".join([CIF.label(i) for i in heads])+"\n"
vals = zip(*[self._data[i] for i in heads])
for ll in vals:
line += "".join(ll) + "\n"
return line
[docs] def get_non_loop_block(self, line):
if line.startswith("_cell"):
return "cell"
elif line.startswith("_symmetry"):
return "sym"
elif line.startswith("_audit"):
return "data"
# terrible idea for formatting.. but oh well :)
[docs] @staticmethod
def atom_site_fract_x(x):
return "%10.5f "%(x)
[docs] @staticmethod
def atom_site_fract_y(x):
return "%10.5f "%(x)
[docs] @staticmethod
def atom_site_fract_z(x):
return "%10.5f "%(x)
[docs] @staticmethod
def atom_type_partial_charge(x):
return "%10.5f "%(x)
[docs] @staticmethod
def atom_site_label(x):
return "%-7s "%(x)
[docs] @staticmethod
def atom_site_type_symbol(x):
return "%-6s "%(x)
[docs] @staticmethod
def atom_site_description(x):
return "%-5s "%(x)
[docs] @staticmethod
def geom_bond_atom_site_label_1(x):
return "%-7s "%(x)
[docs] @staticmethod
def geom_bond_atom_site_label_2(x):
return "%-7s "%(x)
[docs] @staticmethod
def geom_bond_distance(x):
return "%7.3f "%(x)
[docs] @staticmethod
def geom_bond_site_symmetry_2(x):
return "%-5s "%(x)
[docs] @staticmethod
def ccdc_geom_bond_type(x):
return "%5s "%(x)
[docs] @staticmethod
def cell_length_a(x):
return "%-7.4f "%(x)
[docs] @staticmethod
def cell_length_b(x):
return "%-7.4f "%(x)
[docs] @staticmethod
def cell_length_c(x):
return "%-7.4f "%(x)
[docs] @staticmethod
def cell_angle_alpha(x):
return "%-7.4f "%(x)
[docs] @staticmethod
def cell_angle_beta(x):
return "%-7.4f "%(x)
[docs] @staticmethod
def cell_angle_gamma(x):
return "%-7.4f "%(x)
[docs] @staticmethod
def atom_site_fragment(x):
return "%-4i "%(x)
[docs] @staticmethod
def atom_site_constraints(x):
return "%-4i "%(x)
[docs] @staticmethod
def label(x):
"""special cases"""
if x == "data_":
return x
elif x == "_symmetry_space_group_name_H_M":
# replace H_M with H-M.
x = x[:28] + "-" + x[29:]
return "%-34s"%(x)
[docs] @staticmethod
def general_label(x):
return "%s "%(x)
[docs]def get_time():
t = date.today()
return t.strftime("%A %d %B %Y")