From 22850053c57d374a7500f57b6ee94cd57fefc416 Mon Sep 17 00:00:00 2001 From: Daniel Kratzert Date: Fri, 1 Mar 2024 18:20:55 +0100 Subject: [PATCH] Revert "Squashed commit of the following:" This reverts commit b0ca7b065c7c99c000c98df8ce29468961e92272. --- .gitignore | 1 - finalcif/appwindow.py | 14 +- finalcif/report/references.py | 26 -- finalcif/report/report_text.py | 14 +- finalcif/report/templated_report.py | 571 ++++++++----------------- finalcif/template/report.tmpl | 621 ++++++---------------------- finalcif/tools/space_groups.py | 6 +- tests/test_templated_report.py | 85 ++-- 8 files changed, 347 insertions(+), 991 deletions(-) diff --git a/.gitignore b/.gitignore index badaa78b..74000842 100644 --- a/.gitignore +++ b/.gitignore @@ -217,4 +217,3 @@ olex2 x64 test-data/1000007-multi-multitable.docx test-data/report_1000007-multi-finalcif.docx -test.html diff --git a/finalcif/appwindow.py b/finalcif/appwindow.py index 55744ddb..fec4b821 100644 --- a/finalcif/appwindow.py +++ b/finalcif/appwindow.py @@ -47,7 +47,6 @@ from finalcif.gui.plaintextedit import MyQPlainTextEdit from finalcif.gui.text_value_editor import MyTextTemplateEdit, TextEditItem from finalcif.gui.vrf_classes import MyVRFContainer, VREF -from finalcif.report.templated_report import TextFormat from finalcif.template.templates import ReportTemplates from finalcif.tools.download import MyDownloader from finalcif.tools.dsrmath import my_isnumeric @@ -1126,15 +1125,10 @@ def make_report_tables(self) -> None: make_multi_tables(cif=self.cif, output_filename=str(multi_table_document)) else: print('Report with templates') - t = TemplatedReport(format=TextFormat.RICHTEXT, options=self.options, cif=self.cif) - ok = t.make_templated_docx_report(options=self.options, - output_filename=str(report_filename), - picfile=picfile, - template_path=Path(self.get_checked_templates_list_text())) - t = TemplatedReport(format=TextFormat.HTML, options=self.options, cif=self.cif) - t.make_templated_html_report( - options=self.options, - picfile=picfile) + t = TemplatedReport() + ok = t.make_templated_report(options=self.options, cif=self.cif, + output_filename=str(report_filename), picfile=picfile, + template_path=Path(self.get_checked_templates_list_text())) if not ok: return None except FileNotFoundError as e: diff --git a/finalcif/report/references.py b/finalcif/report/references.py index 722c11d4..4e114d14 100644 --- a/finalcif/report/references.py +++ b/finalcif/report/references.py @@ -191,32 +191,6 @@ def richtext(self) -> RichText: return r @property - def html(self) -> str: - txt = '' - if self.authors: - txt += f'{self.authors}, ' - if self.journal: - txt += f'{self.journal}' - if not self.journal.endswith('.'): - txt += ', ' - else: - txt += ' ' - if self.year: - txt += f'{self.year}' - txt += ', ' - if self.volume: - txt += f'{self.volume}' - txt += ', ' - if self.pages: - txt += f'{self.pages}' - if self.doi: - txt += ', ' - if self.doi: - txt += f'{self.doi}' - if any([self.journal, self.pages, self.year, self.volume, self.doi]): - txt += '.' - return txt - @property def short_ref(self) -> RichText: """ Adds a reference with (name year) instead of a number. diff --git a/finalcif/report/report_text.py b/finalcif/report/report_text.py index 8b231d81..22df9233 100644 --- a/finalcif/report/report_text.py +++ b/finalcif/report/report_text.py @@ -18,7 +18,7 @@ SHELXLReference, SHELXTReference, SHELXSReference, FinalCifReference, ShelXleReference, Olex2Reference, \ SHELXDReference, SadabsTwinabsReference, CrysalisProReference, Nosphera2Reference, XDSReference, DSRReference2015, \ DSRReference2018, XRedReference -from finalcif.tools.misc import protected_space, angstrom, zero_width_space, remove_line_endings, flatten, minus_sign +from finalcif.tools.misc import protected_space, angstrom, zero_width_space, remove_line_endings, flatten def math_to_word(eq: str) -> BaseOxmlElement: @@ -524,18 +524,6 @@ def get_inf_article(next_word: str) -> str: return 'an' if next_word[0].lower() in voc else 'a' -def align_by_dot(number: str) -> str: - """ - The intention of this filter is to align the number - on the dot, but this doesn't work as expected. - https://codepen.io/dominiccomtois/pen/YZdred - """ - if '.' in number: - num, dec = number.split('.') - return (f'{num}' - f'.{dec}') - - def format_radiation(radiation_type: str) -> list: radtype = list(radiation_type.partition("K")) if len(radtype) > 2: diff --git a/finalcif/report/templated_report.py b/finalcif/report/templated_report.py index 2254dc49..35fa8f61 100644 --- a/finalcif/report/templated_report.py +++ b/finalcif/report/templated_report.py @@ -1,41 +1,31 @@ import dataclasses -import enum import itertools -import pathlib import re -import sys from collections import namedtuple from contextlib import suppress from math import sin, radians from pathlib import Path -from typing import List, Dict, Union, Iterator, Protocol, Any +from typing import List, Dict, Union import jinja2 from docx.enum.text import WD_PARAGRAPH_ALIGNMENT from docx.shared import Cm from docx.text.paragraph import Paragraph from docxtpl import DocxTemplate, RichText, InlineImage, Subdoc +from shelxfile.misc.dsrmath import my_isnumeric -from finalcif import app_path from finalcif.cif.cif_file_io import CifContainer from finalcif.gui.dialogs import show_general_warning from finalcif.report.references import SAINTReference, SHELXLReference, SadabsTwinabsReference, SHELXTReference, \ SHELXSReference, SHELXDReference, SORTAVReference, FinalCifReference, CCDCReference, \ CrysalisProReference, Nosphera2Reference, Olex2Reference -from finalcif.report.report_text import math_to_word, gstr, format_radiation, get_inf_article, MachineType, align_by_dot +from finalcif.report.report_text import math_to_word, gstr, format_radiation, get_inf_article, MachineType from finalcif.report.symm import SymmetryElement from finalcif.tools.misc import isnumeric, this_or_quest, timessym, angstrom, protected_space, less_or_equal, \ halbgeviert, minus_sign, ellipsis_mid from finalcif.tools.options import Options from finalcif.tools.space_groups import SpaceGroups -AdpWithMinus = namedtuple('AdpWithMinus', ('label', 'U11', 'U22', 'U33', 'U23', 'U13', 'U12')) - - -class TextFormat(enum.StrEnum): - RICHTEXT = 'richtext' - HTML = 'html' - @dataclasses.dataclass(frozen=True) class Bond(): @@ -85,13 +75,13 @@ def __init__(self, cif: CifContainer, without_h: bool): self.bonds_as_string: List[Bond] = [] self.angles_as_string: List[Angle] = [] # These can be used as Richtext for python-docx-tpl: - self.bonds_richtext: List[Dict[str, RichText]] = self._get_bonds_list(without_h) - self.angles_richtext: List[Dict[str, RichText]] = self._get_angles_list(without_h) + self.bonds: List[dict] = self._get_bonds_list(without_h) + self.angles: List[dict] = self._get_angles_list(without_h) # The list of symmetry elements at the table end used for generated atoms: self.symminfo: str = get_symminfo(self._symmlist) def __len__(self): - return len(self.bonds_richtext) + len(self.angles_richtext) + return len(self.bonds) + len(self.angles) @property def symmetry_generated_atoms_used(self): @@ -154,12 +144,12 @@ def __init__(self, cif: CifContainer, without_h: bool): self.without_h = without_h self._symmlist = {} self.torsion_angles_as_string: List[Torsion] = [] - self.torsion_angles_as_richtext = self._get_torsion_angles_list(without_h) + self.torsion_angles = self._get_torsion_angles_list(without_h) # The list of symmetry elements at the table end used for generated atoms: self.symminfo: str = get_symminfo(self._symmlist) def __len__(self): - return len(self.torsion_angles_as_richtext) + return len(self.torsion_angles) @property def symmetry_generated_atoms_used(self): @@ -298,8 +288,9 @@ def symmsearch(cif: CifContainer, newsymms: Dict[int, str], num: int, return num -class Formatter(Protocol): - def __init__(self, options: Options, cif: CifContainer) -> None: +class TemplatedReport(): + + def __init__(self): self.literature = {'finalcif' : FinalCifReference(), 'ccdc' : CCDCReference(), 'absorption' : '[no reference found]', @@ -307,42 +298,49 @@ def __init__(self, options: Options, cif: CifContainer) -> None: 'refinement' : '[no reference found]', 'integration': '[no reference found]', } - self._bonds_angles = BondsAndAngles(cif, without_h=options.without_h) - self._torsions = TorsionAngles(cif, without_h=options.without_h) - self._hydrogens = HydrogenBonds(cif) - - def get_bonds(self): - raise NotImplementedError - - def get_angles(self): - raise NotImplementedError - - def get_torsion_angles(self) -> list[dict[str, RichText]] | list[Torsion]: - raise NotImplementedError - - def get_hydrogen_bonds(self) -> list[dict[str, RichText]] | list[HydrogenBond]: - raise NotImplementedError - def get_bonds_angles_symminfo(self) -> str: - return self._bonds_angles.symminfo - - def get_torsion_symminfo(self) -> str: - return self._torsions.symminfo - - def get_hydrogen_symminfo(self) -> str: - return self._hydrogens.symminfo - - def get_crystallization_method(self, cif) -> str: - return gstr(cif['_exptl_crystal_recrystallization_method']) or '[No crystallization method given!]' - - def get_radiation(self, cif: CifContainer) -> str | RichText: - raise NotImplementedError - - def hkl_index_limits(self, cif: CifContainer) -> str: - raise NotImplementedError + def format_sum_formula(self, sum_formula: str) -> RichText: + sum_formula_group = [''.join(x[1]) for x in itertools.groupby(sum_formula, lambda x: x.isalpha())] + richtext = RichText('') + if sum_formula_group: + for _, word in enumerate(sum_formula_group): + if isnumeric(word): + richtext.add(word, subscript=True) + elif ')' in word: + richtext.add(word.split(')')[0], subscript=True) + richtext.add(')') + elif ']' in word: + richtext.add(word.split(']')[0], subscript=True) + richtext.add(']') + else: + richtext.add(word) + if word == ',': + richtext.add(' ') + return richtext + else: + return RichText('no formula') - def make_3d(self, cif: CifContainer, options: Options) -> str: - raise NotImplementedError + @staticmethod + def space_group_subdoc(tpl_doc: DocxTemplate, cif: CifContainer) -> Subdoc: + """ + Generates a Subdoc subdocument with the xml code for a math element in MSWord. + """ + s = SpaceGroups() + try: + spgrxml = s.to_mathml(cif.space_group) + except KeyError: + spgrxml = '?' + spgr_word = math_to_word(spgrxml) + # I have to create a subdocument in order to add the xml: + sd = tpl_doc.new_subdoc() + p: Paragraph = sd.add_paragraph() + p.alignment = WD_PARAGRAPH_ALIGNMENT.LEFT + p._element.append(spgr_word) + try: + p.add_run(f' ({cif.spgr_number})') + except AttributeError: + pass + return sd @staticmethod def get_from_to_theta_range(cif: CifContainer) -> str: @@ -357,6 +355,37 @@ def get_from_to_theta_range(cif: CifContainer) -> str: except ValueError: return '? to ?' + @staticmethod + def hkl_index_limits(cif: CifContainer) -> str: + limit_h_min = cif['_diffrn_reflns_limit_h_min'] + limit_h_max = cif['_diffrn_reflns_limit_h_max'] + limit_k_min = cif['_diffrn_reflns_limit_k_min'] + limit_k_max = cif['_diffrn_reflns_limit_k_max'] + limit_l_min = cif['_diffrn_reflns_limit_l_min'] + limit_l_max = cif['_diffrn_reflns_limit_l_max'] + return f'{minus_sign if limit_h_min != "0" else ""}{limit_h_min.replace("-", "")} ' \ + f'{less_or_equal} h {less_or_equal} {limit_h_max}\n' \ + + f'{minus_sign if limit_k_min != "0" else ""}{limit_k_min.replace("-", "")} ' \ + f'{less_or_equal} k {less_or_equal} {limit_k_max}\n' \ + + f'{minus_sign if limit_l_min != "0" else ""}{limit_l_min.replace("-", "")} ' \ + f'{less_or_equal} l {less_or_equal} {limit_l_max}' + + @staticmethod + def get_radiation(cif: CifContainer) -> RichText: + rad_element, radtype, radline = format_radiation(cif['_diffrn_radiation_type']) + radiation = RichText(rad_element) + radiation.add(radtype, italic=True) + radiation.add(radline, italic=True, subscript=True) + return radiation + + @staticmethod + def get_completeness(cif: CifContainer) -> str: + try: + completeness = f"{float(cif['_diffrn_measured_fraction_theta_full']) * 100:.1f}{protected_space}%" + except ValueError: + completeness = '?' + return completeness + @staticmethod def get_diff_density_min(cif: CifContainer) -> str: try: @@ -413,21 +442,6 @@ def get_integration_program(self, cif: CifContainer) -> str: self.literature['absorption'] = CrysalisProReference(version=version, year=year) return integration_prog - def _get_scaling_program(self, absdetails: str) -> tuple[str, str]: - scale_prog = '' - version = '' - if 'SADABS' in absdetails.upper() or 'TWINABS' in absdetails.upper(): - if len(absdetails.split()) > 1: - version = absdetails.split()[1] - else: - version = 'unknown version' - if 'SADABS' in absdetails: - scale_prog = 'SADABS' - else: - scale_prog = 'TWINABS' - self.literature['absorption'] = SadabsTwinabsReference() - return scale_prog, version - def get_absortion_correction_program(self, cif: CifContainer) -> str: absdetails = cif['_exptl_absorpt_process_details'].replace('-', ' ').replace(':', '') bruker_scaling = cif['_computing_bruker_data_scaling'].replace('-', ' ').replace(':', '') @@ -444,21 +458,25 @@ def get_absortion_correction_program(self, cif: CifContainer) -> str: return scale_prog + def _get_scaling_program(self, absdetails: str) -> tuple[str, str]: + scale_prog = '' + version = '' + if 'SADABS' in absdetails.upper() or 'TWINABS' in absdetails.upper(): + if len(absdetails.split()) > 1: + version = absdetails.split()[1] + else: + version = 'unknown version' + if 'SADABS' in absdetails: + scale_prog = 'SADABS' + else: + scale_prog = 'TWINABS' + self.literature['absorption'] = SadabsTwinabsReference() + return scale_prog, version + def solution_method(self, cif: CifContainer) -> str: solution_method = gstr(cif['_atom_sites_solution_primary']) or '??' return solution_method.strip('\n\r') - def refinement_prog(self, cif: CifContainer) -> str: - refined = gstr(cif['_computing_structure_refinement']) or '??' - if 'SHELXL' in refined.upper() or 'XL' in refined.upper(): - self.literature['refinement'] = SHELXLReference() - if 'OLEX' in refined.upper(): - self.literature['refinement'] = Olex2Reference() - if ('NOSPHERA2' in refined.upper() or 'NOSPHERA2' in cif['_refine_special_details'].upper() or - 'NOSPHERAT2' in cif['_olex2_refine_details'].upper()): - self.literature['refinement'] = Nosphera2Reference() - return refined.split()[0] - def solution_program(self, cif: CifContainer) -> str: solution_prog = gstr(cif['_computing_structure_solution']) or '??' if solution_prog.upper().startswith(('SHELXT', 'XT')): @@ -469,12 +487,18 @@ def solution_program(self, cif: CifContainer) -> str: self.literature['solution'] = SHELXDReference() return solution_prog.split()[0] - def _tvalue(self, tval: str) -> str: - with suppress(ValueError): - return f'{float(tval):.3f}' - return tval + def refinement_prog(self, cif: CifContainer) -> str: + refined = gstr(cif['_computing_structure_refinement']) or '??' + if 'SHELXL' in refined.upper() or 'XL' in refined.upper(): + self.literature['refinement'] = SHELXLReference() + if 'OLEX' in refined.upper(): + self.literature['refinement'] = Olex2Reference() + if ('NOSPHERA2' in refined.upper() or 'NOSPHERA2' in cif['_refine_special_details'].upper() or + 'NOSPHERAT2' in cif['_olex2_refine_details'].upper()): + self.literature['refinement'] = Nosphera2Reference() + return refined.split()[0] - def get_atomic_coordinates(self, cif: CifContainer) -> Iterator[dict[str, str]]: + def get_atomic_coordinates(self, cif: CifContainer): for at in cif.atoms(without_h=False): yield {'label': at.label, 'type' : at.type, @@ -485,225 +509,37 @@ def get_atomic_coordinates(self, cif: CifContainer) -> Iterator[dict[str, str]]: 'occ' : at.occ.replace('-', minus_sign), 'u_eq' : at.u_eq.replace('-', minus_sign)} - def get_displacement_parameters(self, cif: CifContainer) -> Iterator[AdpWithMinus]: + def get_displacement_parameters(self, cif: CifContainer): """ Yields the anisotropic displacement parameters. With hypehens replaced to minus signs. """ + adp = namedtuple('adp', ('label', 'U11', 'U22', 'U33', 'U23', 'U13', 'U12')) for label, u11, u22, u33, u23, u13, u12 in cif.displacement_parameters(): - yield AdpWithMinus(label=label, - U11=u11.replace('-', minus_sign), - U22=u22.replace('-', minus_sign), - U33=u33.replace('-', minus_sign), - U12=u12.replace('-', minus_sign), - U13=u13.replace('-', minus_sign), - U23=u23.replace('-', minus_sign)) - - def get_completeness(self, cif: CifContainer) -> str: - try: - completeness = f"{float(cif['_diffrn_measured_fraction_theta_full']) * 100:.1f}{protected_space}%" - except ValueError: - completeness = '?' - return completeness - - -class HtmlFormatter(Formatter): - - def get_bonds(self) -> list[Bond]: - return self._bonds_angles.bonds_as_string - - def get_angles(self) -> list[Angle]: - return self._bonds_angles.angles_as_string - - def get_torsion_angles(self) -> list[Torsion]: - return self._torsions.torsion_angles_as_string - - def get_hydrogen_bonds(self) -> list[HydrogenBond]: - return self._hydrogens.hydrogen_bonds_as_str - - def _format_symminfo(self, txt): - return (txt.replace('#1:', '
#1:') - .replace(', ', ', ') - .replace(': ', ': ') - .replace('-', minus_sign)) - - def get_bonds_angles_symminfo(self) -> str: - return self._format_symminfo(self._bonds_angles.symminfo) - - def get_torsion_symminfo(self): - return self._format_symminfo(self._torsions.symminfo) - - def get_hydrogen_symminfo(self): - return self._format_symminfo(self._hydrogens.symminfo) - - def make_3d(self, cif: CifContainer, options: Options) -> str: - return '[3D representation of the structure in html/javascript not implemented]' - - def space_group_subdoc(self, cif: CifContainer, _: None) -> str: - s = SpaceGroups() - try: - spgrxml = s.to_mathml(cif.space_group) - except KeyError: - spgrxml = '?' - return f'{spgrxml} ({cif.spgr_number})' - - def make_picture(self, options: Options, picfile: Path, _: None): - picture_path = '' - if options.report_text and picfile and picfile.exists(): - picture_path = str(picfile.resolve()) - return picture_path # (f'') - - def format_sum_formula(self, sum_formula: str) -> str: - sum_formula_group = [''.join(x[1]) for x in itertools.groupby(sum_formula, lambda x: x.isalpha())] - html_text = '' - if sum_formula_group: - for _, word in enumerate(sum_formula_group): - if isnumeric(word): - html_text += f'{word}' - elif ')' in word: - html_text += f"{word.split(')')[0]})" - elif ']' in word: - html_text += f"{word.split(']')[0]})" - else: - html_text += word - if word == ',': - html_text += '; ' - return html_text - else: - return 'no formula' - - def get_radiation(self, cif: CifContainer) -> str: - rad_element, radtype, radline = format_radiation(cif['_diffrn_radiation_type']) - radiation = f'{rad_element}{radtype}{radline}' - return radiation - - def hkl_index_limits(self, cif: CifContainer) -> str: - limit_h_min = cif['_diffrn_reflns_limit_h_min'] - limit_h_max = cif['_diffrn_reflns_limit_h_max'] - limit_k_min = cif['_diffrn_reflns_limit_k_min'] - limit_k_max = cif['_diffrn_reflns_limit_k_max'] - limit_l_min = cif['_diffrn_reflns_limit_l_min'] - limit_l_max = cif['_diffrn_reflns_limit_l_max'] - return (f'{minus_sign if limit_h_min != "0" else ""}{limit_h_min.replace("-", "")} ' - f'{less_or_equal} h {less_or_equal} {limit_h_max}
' - f'{minus_sign if limit_k_min != "0" else ""}{limit_k_min.replace("-", "")} ' - f'{less_or_equal} k {less_or_equal} {limit_k_max}
' - f'{minus_sign if limit_l_min != "0" else ""}{limit_l_min.replace("-", "")} ' - f'{less_or_equal} l {less_or_equal} {limit_l_max}') - - -class RichTextFormatter(Formatter): - - def get_bonds(self) -> List[Dict[str, RichText]]: - return self._bonds_angles.bonds_richtext - - def get_angles(self) -> List[Dict[str, RichText]]: - return self._bonds_angles.angles_richtext - - def get_torsion_angles(self) -> List[Dict[str, RichText]]: - return self._torsions.torsion_angles_as_richtext - - def get_hydrogen_bonds(self) -> list[dict[str, RichText]]: - return self._hydrogens.hydrogen_bonds - - def make_3d(self, cif: CifContainer, options: Options) -> str: - return '[3D representation not implemented for .docx files]' - - def format_sum_formula(self, sum_formula: str) -> RichText: - sum_formula_group = [''.join(x[1]) for x in itertools.groupby(sum_formula, lambda x: x.isalpha())] - richtext = RichText('') - if sum_formula_group: - for _, word in enumerate(sum_formula_group): - if isnumeric(word): - richtext.add(word, subscript=True) - elif ')' in word: - richtext.add(word.split(')')[0], subscript=True) - richtext.add(')') - elif ']' in word: - richtext.add(word.split(']')[0], subscript=True) - richtext.add(']') - else: - richtext.add(word) - if word == ',': - richtext.add(' ') - return richtext - else: - return RichText('no formula') + yield adp(label=label, + U11=u11.replace('-', minus_sign), + U22=u22.replace('-', minus_sign), + U33=u33.replace('-', minus_sign), + U12=u12.replace('-', minus_sign), + U13=u13.replace('-', minus_sign), + U23=u23.replace('-', minus_sign)) - @staticmethod - def space_group_subdoc(cif: CifContainer, tpl_doc: DocxTemplate) -> Subdoc: - """ - Generates a Subdoc subdocument with the xml code for a math element in MSWord. - """ - s = SpaceGroups() - try: - spgrxml = s.to_mathml(cif.space_group) - except KeyError: - spgrxml = '?' - spgr_word = math_to_word(spgrxml) - # I have to create a subdocument in order to add the xml: - sd = tpl_doc.new_subdoc() - p: Paragraph = sd.add_paragraph() - p.alignment = WD_PARAGRAPH_ALIGNMENT.LEFT - p._element.append(spgr_word) - try: - p.add_run(f' ({cif.spgr_number})') - except AttributeError: - pass - return sd + def _tvalue(self, tval: str) -> str: + with suppress(ValueError): + return f'{float(tval):.3f}' + return tval - def get_radiation(self, cif: CifContainer) -> RichText: - rad_element, radtype, radline = format_radiation(cif['_diffrn_radiation_type']) - radiation = RichText(rad_element) - radiation.add(radtype, italic=True) - radiation.add(radline, italic=True, subscript=True) - return radiation + def get_crystallization_method(self, cif): + return gstr(cif['_exptl_crystal_recrystallization_method']) or '[No crystallization method given!]' def make_picture(self, options: Options, picfile: Path, tpl_doc: DocxTemplate): if options.report_text and picfile and picfile.exists(): return InlineImage(tpl_doc, str(picfile.resolve()), width=Cm(options.picture_width)) return None - def prepare_report(self, options: Options, cif: CifContainer, output_filename: str, picfile: Path, - template_path: Path) -> bool: - context = self._get_context(cif, options, picfile, tpl_doc) - - def hkl_index_limits(self, cif: CifContainer) -> str: - limit_h_min = cif['_diffrn_reflns_limit_h_min'] - limit_h_max = cif['_diffrn_reflns_limit_h_max'] - limit_k_min = cif['_diffrn_reflns_limit_k_min'] - limit_k_max = cif['_diffrn_reflns_limit_k_max'] - limit_l_min = cif['_diffrn_reflns_limit_l_min'] - limit_l_max = cif['_diffrn_reflns_limit_l_max'] - return (f'{minus_sign if limit_h_min != "0" else ""}{limit_h_min.replace("-", "")} ' - f'{less_or_equal} h {less_or_equal} {limit_h_max}\n' - f'{minus_sign if limit_k_min != "0" else ""}{limit_k_min.replace("-", "")} ' - f'{less_or_equal} k {less_or_equal} {limit_k_max}\n' - f'{minus_sign if limit_l_min != "0" else ""}{limit_l_min.replace("-", "")} ' - f'{less_or_equal} l {less_or_equal} {limit_l_max}') - - -def text_factory(options: Options, cif: CifContainer) -> dict[str, Formatter]: - factory = { - TextFormat.RICHTEXT: RichTextFormatter(options, cif), - TextFormat.HTML : HtmlFormatter(options, cif), - # 'plaintext': StringFormatter(), - } - return factory - - -class TemplatedReport(): - def __init__(self, format: str, options: Options, cif: CifContainer) -> None: - self.format = format - self.cif = cif - self.text_formatter = text_factory(options, cif)[self.format] - - def make_templated_docx_report(self, options: Options, - output_filename: str, - picfile: Path, - template_path: Path) -> bool: + def make_templated_report(self, options: Options, cif: CifContainer, output_filename: str, picfile: Path, + template_path: Path) -> bool: tpl_doc = DocxTemplate(template_path) - context, tpl_doc = self.prepare_report_data(self.cif, options, picfile, tpl_doc) + context, tpl_doc = self.prepare_report_data(cif, options, picfile, tpl_doc) # Filter definition for {{foobar|filter}} things: jinja_env = jinja2.Environment() jinja_env.filters['inv_article'] = get_inf_article @@ -716,52 +552,20 @@ def make_templated_docx_report(self, options: Options, info_text=str(e)) return False - def make_templated_html_report(self, options: Options, - output_filename: str = 'test.html', - picfile: Path = None, - template_file: str = "report.tmpl") -> bool: - maincontext = self.get_context(self.cif, options, picfile, None) - template_path = app_path.application_path / 'template' - print(template_path.resolve()) - jinja_env = jinja2.Environment( - loader=jinja2.FileSystemLoader(searchpath=template_path.resolve()), - autoescape=False) - jinja_env.filters['inv_article'] = get_inf_article - jinja_env.filters['align_dot'] = align_by_dot - template = jinja_env.get_template(template_file) - try: - outputText = template.render(maincontext) - print(outputText) - p = Path(output_filename) - p.write_text(outputText, encoding="utf-8") - import subprocess - if sys.platform == 'darwin': - subprocess.call(['open', str(p.resolve())]) - else: - subprocess.Popen(['explorer', str(p.resolve())], shell=True) - return True - except Exception as e: - show_general_warning(parent=None, window_title='Warning', warn_text='Document generation failed', - info_text=str(e)) - return False - - def prepare_report_data(self, cif: CifContainer, - options: Options, - picfile: Path, - tpl_doc: DocxTemplate | None) -> tuple[dict[str, Any], DocxTemplate]: + def prepare_report_data(self, cif: CifContainer, options: Options, picfile: Path, tpl_doc: DocxTemplate): maincontext = {} if not cif.is_multi_cif: - maincontext = self.get_context(cif, options, picfile, tpl_doc) + maincontext = self._get_context(cif, options, picfile, tpl_doc) else: current_block = cif.block.name current_file = cif.fileobj - maincontext.update(self.get_context(cif, options, picfile, tpl_doc)) + maincontext.update(self._get_context(cif, options, picfile, tpl_doc)) block_list = [] blocks = {} for block in cif.doc: cif2 = CifContainer(file=current_file) cif2.load_block_by_name(block.name) - context = self.get_context(cif2, options, picfile, tpl_doc) + context = self._get_context(cif2, options, picfile, tpl_doc) block_list.append(context) blocks[block.name] = context maincontext['blocklist'] = block_list @@ -769,18 +573,20 @@ def prepare_report_data(self, cif: CifContainer, cif.load_block_by_name(current_block) return maincontext, tpl_doc - def get_context(self, cif: CifContainer, options: Options, picfile: Path, tpl_doc: DocxTemplate = None): + def _get_context(self, cif: CifContainer, options: Options, picfile: Path, tpl_doc: DocxTemplate): + ba = BondsAndAngles(cif, without_h=options.without_h) + t = TorsionAngles(cif, without_h=options.without_h) + h = HydrogenBonds(cif) context = {'options' : options, # {'without_h': True, 'atoms_table': True, 'text': True, 'bonds_table': True}, 'cif' : cif, 'name' : cif.block.name, - 'space_group' : self.text_formatter.space_group_subdoc(cif, tpl_doc), - 'structure_figure' : self.text_formatter.make_picture(options, picfile, tpl_doc), - '3d_structure' : self.text_formatter.make_3d(cif, options), - 'crystallization_method' : self.text_formatter.get_crystallization_method(cif), - 'sum_formula' : self.text_formatter.format_sum_formula( + 'space_group' : self.space_group_subdoc(tpl_doc, cif), + 'structure_figure' : self.make_picture(options, picfile, tpl_doc), + 'crystallization_method' : self.get_crystallization_method(cif), + 'sum_formula' : self.format_sum_formula( cif['_chemical_formula_sum'].replace(" ", "")), - 'moiety_formula' : self.text_formatter.format_sum_formula( + 'moiety_formula' : self.format_sum_formula( cif['_chemical_formula_moiety'].replace(" ", "")), 'itnum' : cif['_space_group_IT_number'], 'crystal_size' : this_or_quest(cif['_exptl_crystal_size_min']) + timessym + @@ -788,9 +594,9 @@ def get_context(self, cif: CifContainer, options: Options, picfile: Path, tpl_do this_or_quest(cif['_exptl_crystal_size_max']), 'crystal_colour' : this_or_quest(cif['_exptl_crystal_colour']), 'crystal_shape' : this_or_quest(cif['_exptl_crystal_description']), - 'radiation' : self.text_formatter.get_radiation(cif), + 'radiation' : self.get_radiation(cif), 'wavelength' : cif['_diffrn_radiation_wavelength'], - 'theta_range' : self.text_formatter.get_from_to_theta_range(cif), + 'theta_range' : self.get_from_to_theta_range(cif), 'diffr_type' : gstr(cif['_diffrn_measurement_device_type']) or '[No measurement device type given]', 'diffr_device' : gstr(cif['_diffrn_measurement_device']) @@ -802,91 +608,54 @@ def get_context(self, cif: CifContainer, options: Options, picfile: Path, tpl_do 'detector' : gstr(cif['_diffrn_detector_type']) \ or '[No detector type given]', 'lowtemp_dev' : MachineType._get_cooling_device(cif), - 'index_ranges' : self.text_formatter.hkl_index_limits(cif), + 'index_ranges' : self.hkl_index_limits(cif), 'indepentent_refl' : this_or_quest(cif['_reflns_number_total']), 'r_int' : this_or_quest(cif['_diffrn_reflns_av_R_equivalents']), 'r_sigma' : this_or_quest(cif['_diffrn_reflns_av_unetI/netI']), - 'completeness' : self.text_formatter.get_completeness(cif), + 'completeness' : self.get_completeness(cif), 'theta_full' : cif['_diffrn_reflns_theta_full'], 'data' : this_or_quest(cif['_refine_ls_number_reflns']), 'restraints' : this_or_quest(cif['_refine_ls_number_restraints']), 'parameters' : this_or_quest(cif['_refine_ls_number_parameters']), 'goof' : this_or_quest(cif['_refine_ls_goodness_of_fit_ref']), - 't_min' : self.text_formatter._tvalue( - this_or_quest(cif['_exptl_absorpt_correction_T_min'])), - 't_max' : self.text_formatter._tvalue( - this_or_quest(cif['_exptl_absorpt_correction_T_max'])), + 't_min' : self._tvalue(this_or_quest(cif['_exptl_absorpt_correction_T_min'])), + 't_max' : self._tvalue(this_or_quest(cif['_exptl_absorpt_correction_T_max'])), 'ls_R_factor_gt' : this_or_quest(cif['_refine_ls_R_factor_gt']), 'ls_wR_factor_gt' : this_or_quest(cif['_refine_ls_wR_factor_gt']), 'ls_R_factor_all' : this_or_quest(cif['_refine_ls_R_factor_all']), 'ls_wR_factor_ref' : this_or_quest(cif['_refine_ls_wR_factor_ref']), - 'diff_dens_min' : self.text_formatter.get_diff_density_min(cif).replace('-', minus_sign), - 'diff_dens_max' : self.text_formatter.get_diff_density_max(cif).replace('-', minus_sign), - 'exti' : self.text_formatter.get_exti(cif), - 'flack_x' : self.text_formatter.get_flackx(cif), - 'integration_progr' : self.text_formatter.get_integration_program(cif), + 'diff_dens_min' : self.get_diff_density_min(cif).replace('-', minus_sign), + 'diff_dens_max' : self.get_diff_density_max(cif).replace('-', minus_sign), + 'exti' : self.get_exti(cif), + 'flack_x' : self.get_flackx(cif), + 'integration_progr' : self.get_integration_program(cif), 'abstype' : gstr(cif['_exptl_absorpt_correction_type']) or '??', - 'abs_details' : self.text_formatter.get_absortion_correction_program(cif), - 'solution_method' : self.text_formatter.solution_method(cif), - 'solution_program' : self.text_formatter.solution_program(cif), + 'abs_details' : self.get_absortion_correction_program(cif), + 'solution_method' : self.solution_method(cif), + 'solution_program' : self.solution_program(cif), 'refinement_details' : ' '.join( cif['_refine_special_details'].splitlines(keepends=False)).strip(), - 'refinement_prog' : self.text_formatter.refinement_prog(cif), - 'atomic_coordinates' : self.text_formatter.get_atomic_coordinates(cif), - 'displacement_parameters': self.text_formatter.get_displacement_parameters(cif), - 'bonds' : self.text_formatter.get_bonds(), - 'angles' : self.text_formatter.get_angles(), - 'ba_symminfo' : self.text_formatter.get_bonds_angles_symminfo(), - 'torsions' : self.text_formatter.get_torsion_angles(), - 'torsion_symminfo' : self.text_formatter.get_torsion_symminfo(), - 'hydrogen_bonds' : self.text_formatter.get_hydrogen_bonds(), - 'hydrogen_symminfo' : self.text_formatter.get_hydrogen_symminfo(), - 'literature' : self.text_formatter.literature + 'refinement_prog' : self.refinement_prog(cif), + 'atomic_coordinates' : self.get_atomic_coordinates(cif), + 'displacement_parameters': self.get_displacement_parameters(cif), + 'bonds' : ba.bonds, + 'angles' : ba.angles, + 'ba_symminfo' : ba.symminfo, + 'torsions' : t.torsion_angles, + 'torsion_symminfo' : t.symminfo, + 'hydrogen_bonds' : h.hydrogen_bonds, + 'hydrogen_symminfo' : h.symminfo, + 'literature' : self.literature } return context if __name__ == '__main__': from unittest import mock - import subprocess - + from pprint import pprint data = Path('tests') - testcif = Path(data / 'examples/1979688.cif').absolute() - # testcif = Path(r'test-data/p31c.cif').absolute() + testcif = (data / 'examples/1979688.cif').absolute() cif = CifContainer(testcif) - - - class MOptions(): - report_text = True - report_adp = True - without_h = False - picture_width = 7.6 - checkcif_url = '' - atoms_table = True - bonds_table = True - hydrogen_bonds = True - current_report_template = '' - cod_url = '' - track_changes = False - - - options = MOptions() - t = TemplatedReport(format=TextFormat.HTML, options=options, cif=cif) - pic = pathlib.Path("tests/examples/work/cu_BruecknerJK_153F40_0m-finalcif.png") - maincontext = t.get_context(cif, options=options, picfile=pic, tpl_doc=None) - # pprint(maincontext) - templateLoader = jinja2.FileSystemLoader(searchpath=r"finalcif/template") - jinja_env = jinja2.Environment(loader=templateLoader, autoescape=False, line_comment_prefix='##') - jinja_env.filters['inv_article'] = get_inf_article - jinja_env.filters['align_dot'] = align_by_dot - TEMPLATE_FILE = "report.tmpl" - template = jinja_env.get_template(TEMPLATE_FILE) - outputText = template.render(maincontext) - - # print(outputText) - p = Path('test.html') - p.write_text(outputText, encoding="utf-8") - if sys.platform == 'darwin': - subprocess.call(['open', str(p.resolve())]) - else: - subprocess.Popen(['explorer', str(p.resolve())], shell=True) + t = TemplatedReport() + maincontext = t._get_context(cif, options=mock.Mock(), picfile=None, tpl_doc=mock.Mock()) + pprint(maincontext) diff --git a/finalcif/template/report.tmpl b/finalcif/template/report.tmpl index 972da019..76a93fea 100644 --- a/finalcif/template/report.tmpl +++ b/finalcif/template/report.tmpl @@ -2,505 +2,136 @@ - Structure Tables - - - -
- -
-
- -

Structure Tables

- - {% if structure_figure %} -
- Structure Figure -
- {% endif %} - -

- A {{ crystal_colour }}, {{ crystal_shape }} shaped crystal was mounted on - a {{ cif._diffrn_measurement_specimen_support }} - with perfluoroether oil. {{ crystallization_method }}. Data for {{ cif.block.name }} - were collected from a shock-cooled single crystal at {{ cif._diffrn_ambient_temperature }} K - on {{ diffr_type|inv_article }} - {{ diffr_type }} {{ diffr_device }} with {{ diffr_source|inv_article }} {{ diffr_source }} - using a {{ monochromator }} as monochromator and {{ detector|inv_article }} {{ detector }} - detector. The diffractometer - {% if lowtemp_dev %}was equipped with {{ lowtemp_dev|inv_article }} {{ lowtemp_dev }} - low temperature device and - {% endif %} - used {{ radiation }} radiation {% if wavelength %}(λ = {{ wavelength }} Å){% endif %}. - All data were integrated with {{ integration_progr }} yielding {{ cif._diffrn_reflns_number }} - reflections of which {{ cif._reflns_number_total }} where independent and - {{ '%0.1f'| format((cif._reflns_number_gt|float() / cif._reflns_number_total|float()) * 100) }} % - were greater than 2σ(F2). A - {{ abstype }} absorption correction using {{ abs_details }} was applied.[1,2] - The structure was solved by - {{ solution_method }} methods with {{ solution_program }} and refined by full-matrix - least-squares methods against - F2 using {{ refinement_prog }}.[3,4] All non-hydrogen atoms - were refined with anisotropic displacement parameters. - {% if cif.hydrogen_atoms_present %}The hydrogen atoms were refined isotropically on - calculated positions using a riding model with their Uiso values - constrained to 1.5 times - the Ueq of their pivot atoms for terminal sp3 carbon atoms - and 1.2 times for all other carbon atoms. - {% endif %} - Crystallographic data for the structures reported in this paper have been deposited - with the Cambridge Crystallographic Data Centre.[5] CCDC - {% if cif._database_code_depnum_ccdc_archive %} - {{ cif._database_code_depnum_ccdc_archive|replace('CCDC ', '') }}{% else %} - ?????? - {% endif %} - contain the supplementary crystallographic data for this paper. - These data can be obtained free of charge from The Cambridge Crystallographic Data - Centre via www.ccdc.cam.ac.uk/structures. - This report and the CIF file were generated using FinalCif.[6] -

- {% if refinement_details %} -

- Refinement details for {{ cif.block.name }} -

-

- {{ refinement_details }} -

- {% endif %} -
-
- {# Just an empty column to separate the other #} -
-
-

- Table 1. Crystal data and structure refinement for {{ cif.block.name }} -

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - {% if exti %} - - - - - {% endif %} - {% if flack_x %} - - - - - {% endif %} - -
CCDC number{{ cif._database_code_depnum_ccdc_archive }}
Empirical formula{{ sum_formula }}
Formula weight{{ cif._chemical_formula_weight }}
Temperature [K]{{ cif._diffrn_ambient_temperature }}
Crystal system{{ cif._space_group_crystal_system }}
Space group (number){{ space_group }}
a [Å]{{ cif._cell_length_a }}
b [Å]{{ cif._cell_length_b }}
c [Å]{{ cif._cell_length_c }}
α [°]{{ cif._cell_angle_alpha }}
β [°]{{ cif._cell_angle_beta }}
γ [°]{{ cif._cell_angle_gamma }}
Volume [Å3]{{ cif._cell_volume }}
Z{{ cif._cell_formula_units_Z }}
ρcalc [gcm−3]{{ cif._exptl_crystal_density_diffrn }}
μ [mm−1]{{ cif._exptl_absorpt_coefficient_mu }}
F(000){{ cif._exptl_crystal_F_000 }}
Crystal size [mm3]{{ crystal_size }}
Crystal colour{{ crystal_colour }}
Crystal shape{{ crystal_shape }}
Radiation{{ radiation }}{% if wavelength %} (λ={{ wavelength }} Å){% endif %}
2θ range [°]{{ theta_range }}
Index ranges{{ index_ranges }}
Reflections collected{{ cif._diffrn_reflns_number }}
Independent reflections{{ indepentent_refl }}
- Rint = {{ r_int }}
- Rsigma = {{ r_sigma }} -
Completeness{% if theta_full %} to - θ = {{ theta_full }}°{% endif %} - {{ completeness }}
Data / Restraints / Parameters{{ data }} / {{ restraints }} / {{ parameters }}
Absorption correction Tmin/Tmax (method) - {{ t_min }} / {{ t_max }} {% if abstype %}({{ abstype }}){% endif %}
Goodness-of-fit on F2{{ goof }}
Final R indexes
- [I≥2σ(I)] -
R1 = {{ ls_R_factor_gt }}
- wR2 = {{ ls_wR_factor_gt }} -
Final R indexes
- [all data] - -
R1 = {{ ls_R_factor_all }}
- wR2 = {{ ls_wR_factor_ref }} -
Largest peak/hole [eÅ−3]{{ diff_dens_max }}/{{ diff_dens_min }}
Extinction coefficient{{ exti }}
Flack X parameter{{ flack_x }}
-
-
- - {% if atomic_coordinates %} -

- Table 2. Atomic coordinates and Ueq2] - for {{ cif.block.name }} -

- -
-
- - - - - - - - - - - - - {% for atom in atomic_coordinates %} - - - - - - - - {% endfor %} - -
Ueq is defined as 1/3 of the trace of the orthogonalized - Uij tensor. -
AtomxyzUeq
{{ atom.label }}{{ atom.x|align_dot }}{{ atom.y|align_dot }}{{ atom.z|align_dot }}{{ atom.u_eq }}
-
-
- {% endif %} - - - {% if displacement_parameters %} -

- Table 3. Anisotropic displacement parameters [Å2] for {{ cif.block.name }}. - The anisotropic displacement factor exponent takes the form: - −2π2[ h2(a*)2U11 + k2(b*)2U22 + … + 2hka*b*U12 ] -

- -
-
- - - - - - - - - - - - - - - {% for atom in displacement_parameters %} - - - - - - - - - - {% endfor %} - -
Ueq is defined as 1/3 of the trace of the orthogonalized - Uij tensor. -
AtomU11U22U33U23U13U12
{{ atom.label }}{{ atom.U11 }}{{ atom.U22 }}{{ atom.U33 }}{{ atom.U23 }}{{ atom.U13 }}{{ atom.U12 }}
-
-
- {% endif %} - -
-
- {% if options.bonds_table %} - {% if bonds %} -

- Table 4. Bond lengths and angles for {{ cif.block.name }} -

- - - - - - - - - - {% for b in bonds %} - - - - - {% endfor %} - - - - - - - - - {% for a in angles %} - - - - - {% endfor %} - -
- {% if ba_symminfo %} - {{ ba_symminfo }} - {% endif %} - {% if options.without_h %} - Bonds and angles to hydrogen atoms were omitted. - {% endif %} -
Atom–AtomLength [Å]
{{ b.atoms }}{{ b.symm }}{{ b.dist }}
- Atom–Atom–Atom - - Angle [°] -
{{ a.atom1 }}{{ a.symm1 }}{{ a.atom2 }}{{ a.symm2 }} - {{ a.angle }}
- {% endif %} - {% endif %} -
-
- {# Just an empty column to separate the other #} -
-
- {% if torsions %} -

- Table 5. Torsion angles for {{ cif.block.name }} -

- - - - - - - - - - {% for t in torsions %} - - - - - {% endfor %} - -
- {% if torsion_symminfo %} - {{ torsion_symminfo }} - {% endif %} - {% if options.without_h %} - Bonds and angles to hydrogen atoms were omitted. - {% endif %} -
Atom–Atom–Atom–AtomTorsion Angle [°]
- {{ t.atom1 }}{{ t.symm1 }}–{{ t.atom2 }}{{ t.symm2 }}–{{ t.atom3 }}{{ t.symm3 }}–{{ t.atom4 }}{{ t.symm4 }} - - {{ t.angle }} -
- {% endif %} -
-
- - {% if options.hydrogen_bonds and hydrogen_bonds %} -

- Table 6. Hydrogen bonds for {{ cif.block.name }} -

- -
-
- - - - - - - - - - - - - {% for h in hydrogen_bonds %} - - - - - - - - {% endfor %} - -
- {% if hydrogen_symminfo %} - {{ hydrogen_symminfo }} - {% endif %} -
D–H⋯A [Å]d(D–H) [Å] d(H⋯A) [Å]d(D⋯A) [Å]<(DHA) [°]
- {{ h.atoms }} - - {{ h.dist_dh }} - - {{ h.dist_ha }} - - {{ h.dist_da }} - - {{ h.angle_dha }} -
-
-
- {% endif %} - -

- Bibliography -

- -
    -
  1. [1] {{ literature.integration.html }}
  2. -
  3. [2] {{ literature.absorption.html }}
  4. -
  5. [3] {{ literature.solution.html }}
  6. -
  7. [4] {{ literature.refinement.html }}
  8. -
  9. [5] {{ literature.ccdc.html }}
  10. -
  11. [6] {{ literature.finalcif.html }}
  12. -
+{% if structure_figure%} + +{%endif%} + +

+ A {{crystal_colour}}, {{crystal_shape}} shaped crystal was mounted on a {{ cif._diffrn_measurement_specimen_support }} with perfluoroether + oil. The sample was crystallized {{crystallization_method}}. Data for {{cif.block.name}} were collected from a shock-cooled single crystal at {{cif._diffrn_ambient_temperature}} K + on {{diffr_type|inv_article}} {{diffr_type}} {{diffr_device}} with {{diffr_source|inv_article}} {{diffr_source}} using a {{monochromator}} as monochromator and + {{detector|inv_article}} {{detector}} detector. The diffractometer {% if lowtemp_dev %}was equipped with {{lowtemp_dev|inv_article}} {{lowtemp_dev}} low temperature device and + {%endif%}used {{radiation}} radiation {%if wavelength%}(λ = {{wavelength}} Å){%endif%}. All data were integrated with {{integration_progr}} and a {{abstype}} absorption correction + using {{abs_details}} was applied. [1,2] The structure was solved by {{solution_method}} methods with {{solution_program}} and refined by full-matrix least-squares methods against + F2 using {{refinement_prog}}.[3,4] All non-hydrogen atoms were refined with anisotropic displacement parameters. {%if cif.hydrogen_atoms_present%}The hydrogen atoms were refined + isotropically on calculated positions using a riding model with their Uiso values constrained to 1.5 times the Ueq of their pivot atoms for terminal sp3 carbon atoms and 1.2 times + for all other carbon atoms. {%endif%}Crystallographic data for the structures reported in this paper have been deposited with the Cambridge Crystallographic Data Centre.[5] CCDC + {%if cif._database_code_depnum_ccdc_archive%}{{cif._database_code_depnum_ccdc_archive}}{%else%}??????{%endif%} contain the supplementary crystallographic data for this paper. These + data can be obtained free of charge from The Cambridge Crystallographic Data Centre via www.ccdc.cam.ac.uk/structures. This report and the CIF file were generated using + FinalCif.[6] +

+ +

+ Table 1. Crystal data and structure refinement for {{ cif.block.name }} +

+ + + + + + + + + + Formula weight {{cif._chemical_formula_weight }} + Temperature [K] {{cif._diffrn_ambient_temperature}} + Crystal system {{ cif._space_group_crystal_system }} + Space group (number) {{p space_group}} {{ itnum }} + a [Å] {{cif._cell_length_a }} + b [Å] {{cif._cell_length_b }} + c [Å] {{cif._cell_length_c }} + α [°] {{cif._cell_angle_alpha }} + β [°] {{cif._cell_angle_beta }} + γ [°] {{cif._cell_angle_gamma }} + Volume [Å3] {{cif._cell_volume}} + Z {{cif._cell_formula_units_Z}} + ρcalc [gcm−3] {{cif._exptl_crystal_density_diffrn}} + μ [mm−1] {{cif._exptl_absorpt_coefficient_mu}} + F(000) {{cif._exptl_crystal_F_000}} + Crystal size [mm3] {{crystal_size}} + Crystal colour {{crystal_colour}} + Crystal shape {{crystal_shape }} + Radiation {{radiation}}{%if wavelength%} (λ={{wavelength}} Å){%endif%} + 2θ range [°] {{theta_range}} + Index ranges {{index_ranges}} + Reflections collected {{cif._diffrn_reflns_number}} + Independent reflections {{indepentent_refl}} + Rint = {{r_int}} + Rsigma = {{r_sigma}} + Completeness{%if theta_full%} to + θ = {{theta_full}}°{%endif%} {{completeness}} + Data / Restraints / Parameters {{data}} / {{restraints}} / {{parameters}} + Goodness-of-fit on F2 {{ goof }} + Final R indexes + [I≥2σ(I)] R1 = {{ls_R_factor_gt}} + wR2 = {{ls_wR_factor_gt}} + Final R indexes + [all data] R1 = {{ls_R_factor_all}} + wR2 = {{ls_wR_factor_ref}} + Largest peak/hole [eÅ−3] {{diff_dens_max}}/{{diff_dens_min}} + {%tr if exti %} + Extinction coefficient {{exti}} + {%tr endif %} + {%tr if flack_x %} + Flack X parameter {{flack_x}} + {%tr endif %} +
CCDC number{{ cif._database_code_depnum_ccdc_archive
Empirical formula{{ sum_formula }}
+{% if refinement_details %}Refinement details for {{ cif.block.name }} +{{ refinement_details }} +{% endif %}{% if atomic_coordinates %} Table 2. Atomic coordinates and Ueq [Å2] for {{ cif.block.name }} +Atom x y z Ueq +{%tr for atom in atomic_coordinates %} +{{ atom.label }} {{ atom.x }} {{ atom.y }} {{ atom.z }} {{ atom.u_eq }} +{%tr endfor %} +Ueq is defined as 1/3 of the trace of the orthogonalized Uij tensor. +{% endif %}{% if displacement_parameters %} Table 3. Anisotropic displacement parameters (Å2) for {{ cif.block.name }}. The anisotropic displacement factor exponent takes the form: +−2π2[ h2(a*)2U11 + k2(b*)2U22 + … + 2hka*b*U12 ] +Atom U11 U22 U33 U23 U13 U12 +{%tr for atom in displacement_parameters %} +{{ atom.label }} {{ atom.U11 }} {{ atom.U22 }} {{ atom.U33 }} {{ atom.U23 }} {{ atom.U13 }} {{ atom.U12 }} +{%tr endfor %} +{% endif %}{% if options.bonds_table %}{%if bonds%} +Table 4. Bond lengths and angles for {{ cif.block.name }} +Atom–Atom Length [Å] +{%tr for b in bonds %} +{{b.atoms}} {{b.dist}} +{%tr endfor %} + +Atom–Atom–Atom Angle [°] +{%tr for a in angles %} +{{a.atoms}} {{a.angle}} +{%tr endfor %} +{% if options.without_h %}Bonds and angles to hydrogen atoms were omitted.{% endif %}{%if ba_symminfo%} +{{ ba_symminfo}}{%endif%}{%endif%} +{% endif %}{%if torsions%} +Table 5. Torsion angles for {{ cif.block.name }} + +Atom–Atom–Atom–Atom Torsion Angle [°] +{%tr for t in torsions %} +{{t.atoms}} {{t.angle}} +{%tr endfor %} +{% if options.without_h %}Bonds and angles to hydrogen atoms were omitted.{% endif %}{%if torsion_symminfo%} +{{ torsion_symminfo}}{%endif%}{%endif%} + + +{% if options.hydrogen_bonds and hydrogen_bonds %} +Table 6. Hydrogen bonds for {{ cif.block.name }} +D–H⋯A [Å] d(D–H) [Å] d(H⋯A) [Å] d(D⋯A) [Å] <(DHA) [°] +{%tr for h in hydrogen_bonds %} +{{h.atoms}} {{h.dist_dh}} {{h.dist_ha}} {{h.dist_da}} {{h.angle_dha}} +{%tr endfor %} +{%if hydrogen_symminfo%}{{hydrogen_symminfo}}{%endif%}{%endif%} +Bibliography +[1] {{literature.integration.richtext}} +[2] {{literature.absorption.richtext}} +[3] {{literature.solution.richtext}} +[4] {{literature.refinement.richtext}} +[5] {{literature.ccdc.richtext}} +[6] {{literature.finalcif.richtext}} -
\ No newline at end of file diff --git a/finalcif/tools/space_groups.py b/finalcif/tools/space_groups.py index 17ebb979..0f785738 100644 --- a/finalcif/tools/space_groups.py +++ b/finalcif/tools/space_groups.py @@ -65,8 +65,8 @@ def to_mathml(self, space_group: str) -> str: def to_html(self, space_group: str) -> str: if not space_group: return '' - txt = self.to_html_without_body(space_group) - return f'{txt}  ({self.spgrps[space_group][1].get("itnumber")})' + txt = self._to_html_without_body(space_group) + return '{}  ({})'.format(txt, self.spgrps[space_group][1].get('itnumber')) def to_plain_text(self, space_group: str) -> str: """ @@ -76,7 +76,7 @@ def to_plain_text(self, space_group: str) -> str: return '' return ''.join([x[0] for x in self.spgrps[space_group][0]]) - def to_html_without_body(self, space_group: str) -> str: + def _to_html_without_body(self, space_group: str) -> str: """ Uses the general format dictionary in order to output a space group as html. """ diff --git a/tests/test_templated_report.py b/tests/test_templated_report.py index 4b245cb1..934df89e 100644 --- a/tests/test_templated_report.py +++ b/tests/test_templated_report.py @@ -14,7 +14,7 @@ from finalcif import VERSION from finalcif.appwindow import AppWindow from finalcif.cif.cif_file_io import CifContainer -from finalcif.report.templated_report import TemplatedReport, TextFormat +from finalcif.report.templated_report import TemplatedReport data = Path('tests') test_data = Path('test-data') @@ -92,10 +92,12 @@ def tearDown(self) -> None: self.report_zip.unlink(missing_ok=True) def test_ccdc_num_in_table(self): - t = TemplatedReport(cif=CifContainer(self.testcif), format=TextFormat.RICHTEXT, options=self.options) - ok = t.make_templated_docx_report(options=self.options, - output_filename=str(self.reportdoc), picfile=self.report_pic, - template_path=self.text_template) + t = TemplatedReport() + ok = t.make_templated_report(options=self.options, + cif=CifContainer(self.testcif), + output_filename=str(self.reportdoc), + picfile=self.report_pic, + template_path=self.text_template) self.assertTrue(ok) doc = Document(str(self.reportdoc.absolute())) table: Table = doc.tables[0] @@ -107,23 +109,26 @@ def test_picture_has_correct_size(self): For this test, self.myapp.set_report_picture(Path('finalcif/icon/finalcif.png')) has to be set correctly. """ - t = TemplatedReport(cif=CifContainer(self.testcif), format=TextFormat.RICHTEXT, options=self.options) - ok = t.make_templated_docx_report(options=self.options, - output_filename=str(self.reportdoc), picfile=self.report_pic, - template_path=self.text_template) + t = TemplatedReport() + ok = t.make_templated_report(options=self.options, + cif=CifContainer(self.testcif), + output_filename=self.reportdoc, + picfile=self.report_pic, + template_path=self.text_template) self.assertTrue(ok) - doc = Document(self.reportdoc.absolute().__str__()) + doc = Document(self.reportdoc.absolute()) shapes: InlineShapes = doc.inline_shapes self.assertEqual(WD_INLINE_SHAPE.PICTURE, shapes[0].type) self.assertEqual(Cm(7.43).emu, shapes[0].width) def test_citations(self): - t = TemplatedReport(cif=CifContainer(self.testcif), format=TextFormat.RICHTEXT, options=self.options) - t.make_templated_docx_report(options=self.options, - output_filename=self.reportdoc.__str__(), - picfile=self.report_pic, - template_path=self.text_template) - doc = Document(self.reportdoc.absolute().__str__()) + t = TemplatedReport() + t.make_templated_report(options=self.options, + cif=CifContainer(self.testcif), + output_filename=self.reportdoc, + picfile=self.report_pic, + template_path=self.text_template) + doc = Document(self.reportdoc.absolute()) # for num, p in enumerate(doc.paragraphs): # print(num, p.text) self.assertEqual('Bibliography', doc.paragraphs[20].text) @@ -136,18 +141,18 @@ def setUp(self): self.docx_templ = test_data / 'templates/test_template_for_multitable_dist.docx' self.multi_cif = test_data / '1000007-multi.cif' self.cif = CifContainer(file=self.multi_cif) - self.options = Mock() - self.options.without_h = False - self.options.picture_width = 7.43 def tearDown(self): self.reportdoc.unlink(missing_ok=True) def test_get_distance_from_atoms(self): - t = TemplatedReport(cif=self.cif, format=TextFormat.RICHTEXT, options=self.options) - ok = t.make_templated_docx_report(options=self.options, - output_filename='test.docx', picfile=Path(), - template_path=self.docx_templ) + t = TemplatedReport() + mock = Mock() + mock.without_h = False + mock.picture_width = 7.43 + ok = t.make_templated_report(options=mock, cif=self.cif, + output_filename='test.docx', picfile=Path(), + template_path=self.docx_templ) self.assertTrue(ok) doc = Document(self.reportdoc.resolve().__str__()) self.assertEqual('C1-C2 in p-1 distance: 1.5123(17)', doc.paragraphs[0].text) @@ -160,55 +165,51 @@ class TestData(unittest.TestCase): def setUp(self) -> None: # creating a new CIF with a new block: self.cif = CifContainer('foo.cif', 'testblock') - self.options = Mock() - self.options.picture_width = 7.43 - self.options.without_h = False def test_get_integration_program_with_spaces(self): # Here we have the special case, that there is no space character between the version number and the bracket. self.cif['_computing_data_reduction'] = 'CrysAlisPro 1.171.39.20a (Rigaku OD, 2015)' self.assertEqual('CrysAlisPro 1.171.39.20a (Rigaku OD, 2015)', self.cif['_computing_data_reduction']) - r = TemplatedReport(cif=self.cif, format=TextFormat.RICHTEXT, options=self.options) - result = r.text_formatter.get_integration_program(self.cif) + r = TemplatedReport() + result = r.get_integration_program(self.cif) self.assertEqual('CrysAlisPro', result) - self.assertEqual('Crysalispro, 1.171.39.20a, 2015, Rigaku OD.', str(r.text_formatter.literature['integration'])) + self.assertEqual('Crysalispro, 1.171.39.20a, 2015, Rigaku OD.', str(r.literature['integration'])) def test_get_integration_program_with_line_break(self): # Here we have the special case, that there is no space character between the version number and the bracket. self.cif['_computing_data_reduction'] = "CrysAlisPro 1.171.39.20a\n" \ "(Rigaku OD, 2015)\n" self.assertEqual('CrysAlisPro 1.171.39.20a\n(Rigaku OD, 2015)\n', self.cif['_computing_data_reduction']) - r = TemplatedReport(cif=self.cif, format=TextFormat.RICHTEXT, options=self.options) - result = r.text_formatter.get_integration_program(self.cif) + r = TemplatedReport() + result = r.get_integration_program(self.cif) self.assertEqual('CrysAlisPro', result) - self.assertEqual('Crysalispro, 1.171.39.20a, 2015, Rigaku OD.', str(r.text_formatter.literature['integration'])) + self.assertEqual('Crysalispro, 1.171.39.20a, 2015, Rigaku OD.', str(r.literature['integration'])) def test_get_integration_program_with_missing_information(self): # Here we have all in one line with spaces inbetween: self.cif['_computing_data_reduction'] = 'CrysAlisPro 1.171.39.20a (Rigaku OD)' self.assertEqual('CrysAlisPro 1.171.39.20a (Rigaku OD)', self.cif['_computing_data_reduction']) - r = TemplatedReport(cif=self.cif, format=TextFormat.RICHTEXT, options=self.options) - result = r.text_formatter.get_integration_program(self.cif) + r = TemplatedReport() + result = r.get_integration_program(self.cif) self.assertEqual('CrysAlisPro', result) - self.assertEqual('Crysalispro, unknown version, Rigaku OD.', str(r.text_formatter.literature['integration'])) + self.assertEqual('Crysalispro, unknown version, Rigaku OD.', str(r.literature['integration'])) def test_get_integration_program_saint(self): # Here we have all in one line with spaces inbetween: self.cif['_computing_data_reduction'] = 'SAINT V8.40A' - r = TemplatedReport(cif=self.cif, format=TextFormat.RICHTEXT, options=self.options) - result = r.text_formatter.get_integration_program(self.cif) + r = TemplatedReport() + result = r.get_integration_program(self.cif) self.assertEqual('SAINT V8.40A', result) self.assertEqual('Bruker, SAINT, V8.40A, Bruker AXS Inc., Madison, Wisconsin, USA.', - str(r.text_formatter.literature['integration'])) + str(r.literature['integration'])) def test_get_integration_program_saint_without_version(self): # Here we have all in one line with spaces inbetween: self.cif['_computing_data_reduction'] = 'SAINT' - r = TemplatedReport(cif=self.cif, format=TextFormat.RICHTEXT, options=self.options) - result = r.text_formatter.get_integration_program(self.cif) + r = TemplatedReport() + result = r.get_integration_program(self.cif) self.assertEqual('SAINT', result) - self.assertEqual('Bruker, SAINT, Bruker AXS Inc., Madison, Wisconsin, USA.', - str(r.text_formatter.literature['integration'])) + self.assertEqual('Bruker, SAINT, Bruker AXS Inc., Madison, Wisconsin, USA.', str(r.literature['integration'])) if __name__ == '__main__':