tklib.tktemplate のソースコード

"""
概要: テンプレート処理とExcel連携を提供するモジュール。

詳細説明:
このモジュールは、文字列、ファイル、Excelデータに対するテンプレート変数の置換機能や、
Excelファイルへのデータ追加機能を提供します。
`{{ key }}`形式のテンプレートタグを使用して、動的なコンテンツ生成をサポートします。

関連リンク: :doc:`tktemplate_usage`
"""
import os
from numpy import exp, log, log10, sin, cos, tan, arcsin, arccos, arctan, sqrt
import openpyxl
import pandas as pd
import re # `process_template`関数で使用されているが、元のコードにimportがなかったため追加。

from tklib.tkobject import tkObject
from tklib.tkfile import tkFile
from tklib.tkutils import pint as _pint, pfloat as _pfloat, pconv
from tklib.tksci.tksci import h, h_bar, hbar, e, kB, NA, c, pi, pi2, torad, todeg, basee
from tklib.tksci.tksci import me, mp, mn, u0, e0, e2_4pie0, a0, R, F, g
from tklib.tksci.tksci import acos, asin, atan, cosh, sinh, tanh
from tklib.tksci.tksci import degcos, degsin, degtan, degacos, degasin, degatan
from tklib.tksci.tksci import eVTonm, nmToeV
from tklib.tksci.tksci import factorial, Factorize, Gaussian, Lorentzian, combination, gamma


[ドキュメント] def pint(s): """ 概要: 文字列を整数に安全に変換します。 詳細説明: 内部の `_pint` 関数を呼び出し、デフォルト値なし、厳格でないモードで変換を試みます。 変換できない場合は `None` を返します。 :param s: 整数に変換する入力文字列。 :type s: str :returns: 変換された整数、または変換できなかった場合は `None`。 :rtype: int or None """ return _pint(s, defval = None, strict = False)
[ドキュメント] def pfloat(s): """ 概要: 文字列を浮動小数点数に安全に変換します。 詳細説明: 内部の `_pfloat` 関数を呼び出し、デフォルト値なし、厳格でないモードで変換を試みます。 変換できない場合は `None` を返します。 :param s: 浮動小数点数に変換する入力文字列。 :type s: str :returns: 変換された浮動小数点数、または変換できなかった場合は `None`。 :rtype: float or None """ return _pfloat(s, defval = None, strict = False)
[ドキュメント] def process_template(template: str, context: dict) -> str: """ 概要: テンプレート文字列内の特殊文字とタグを置換します。 詳細説明: バックスラッシュでエスケープされたタブ (`\t`)、改行 (`\n`)、キャリッジリターン (`\r`) を 実際の特殊文字に置換します。 さらに、`{{ key }}`形式のテンプレートタグを、`context`辞書内の対応する値で置換します。 キーが見つからない場合は、元のタグ形式を保持します。 :param template: テンプレートタグと特殊文字を含む入力文字列。 :type template: str :param context: テンプレート置換のためのキーと値のペアを含む辞書。 :type context: dict :returns: 置換が適用された処理済みの文字列。 :rtype: str """ # Replace special characters template = template.replace(r'\t', '\t') template = template.replace(r'\n', '\n') template = template.replace(r'\r', '\r') # Replace {{ key }} with context values def replace_placeholder(match): key = match.group(1).strip() return str(context.get(key, f'{{{{ {key} }}}}')) # Keep original if key not found template = re.sub(r'\{\{\s*(.*?)\s*\}\}', replace_placeholder, template) return template
[ドキュメント] def convert_str(s, replace_dict, blank_nodata = False, print_level = 0): """ 概要: 文字列内のテンプレートタグを置換し、オプションで式を評価します。 詳細説明: `replace_dict`に基づいて、入力文字列`s`内の`{{key}}`形式のタグを対応する値で置換します。 `blank_nodata`が`True`の場合、`replace_dict`内の値が`None`であるタグは空文字列に置換されます。 文字列が`@eval:`で始まる場合、その後の文字列はPythonの式として評価され、結果が文字列に変換されます。 :param s: 処理する入力文字列。 :type s: str or None :param replace_dict: テンプレート置換のためのキーと値のペアを含む辞書。 :type replace_dict: dict :param blank_nodata: `replace_dict`の値が`None`の場合にタグを空文字列に置換するかどうか。デフォルトは`False`。 :type blank_nodata: bool, optional :param print_level: デバッグ出力のレベル。0は出力なし。デフォルトは`0`。 :type print_level: int, optional :returns: 処理された文字列、または入力が`None`の場合は`None`。 :rtype: str or None """ if print_level: print("") print(f"str: [{s}]") # print(f"replace_dict:", replace_dict) for key, val in replace_dict.items(): print(f" {key:10}: [{val}]") if s is None: return s for key, val in replace_dict.items(): sstr = '{{' + key + '}}' # ここをループの外に出すと、valがNoneの場合にsstrが定義されていないエラーになる。元のコードのロジックを保持。 if blank_nodata and val is None: s = s.replace(sstr, '') else: s = s.replace(sstr, str(val)) if len(s) > 6 and s[:6] == "@eval:": # print(f"eval: {s}") # print(f" eval: {s[6:]}") s = eval(s[6:], globals(), {}) s = str(s) if print_level: print(f" => [{s}]") return s
[ドキュメント] def convert_file(template_path, output_path, replace_dict, print_level = 0): """ 概要: テンプレートファイルの内容を読み込み、置換を行い、結果を新しいファイルに書き込みます。 詳細説明: `template_path`で指定されたファイルを読み込み、`replace_dict`に基づいて各行の`{{key}}`タグを置換します。 処理された内容は`output_path`で指定されたファイルに書き込まれます。 テンプレートファイルが存在しない場合や出力ファイルに書き込めない場合はエラーを報告します。 :param template_path: 読み込むテンプレートファイルのパス。 :type template_path: str :param output_path: 結果を書き込む出力ファイルのパス。 :type output_path: str :param replace_dict: テンプレート置換のためのキーと値のペアを含む辞書。 :type replace_dict: dict :param print_level: デバッグ出力のレベル。0は出力なし。デフォルトは`0`。 :type print_level: int, optional :returns: ファイル変換が成功した場合は`True`、失敗した場合は`False`。 :rtype: bool """ if print_level: print("") print(f"Read [{template_path}] to read") print(f"Open [{output_path}] to save") if os.path.isfile(template_path): infp = tkFile(template_path, 'r', encoding = None) else: if print_level: print() print(f"Error in tktemplate.convert_file(): Template file [{template_path}] does not exist") return False infp = None # unreachable code outfp = tkFile(output_path, 'w', encoding = None) if not outfp.fp: print() print(f"Error in tktemplate.convert_file(): Can not write to [{output_path}]") return False for line in infp.fp: for key, val in replace_dict.items(): sstr = '{{' + key + '}}' line = line.replace(sstr, str(val)) outfp.write(line) infp.close() outfp.close() return True
[ドキュメント] def read_template_xlsx(template_path, data_only = False, print_level = 0): """ 概要: Excelテンプレートファイルからラベルとテンプレート文字列を読み込みます。 詳細説明: 指定されたExcelファイル(`template_path`)の1行目からラベルを、2行目から対応する テンプレート文字列を読み込みます。 `data_only`が`True`の場合、セルの表示値(計算結果)を読み込みます。 ファイルが存在しない、または読み込めない場合はエラーを報告します。 :param template_path: 読み込むExcelテンプレートファイルのパス。 :type template_path: str :param data_only: 数式ではなくセルの計算結果のみを読み込むかどうか。デフォルトは`False`。 :type data_only: bool, optional :param print_level: デバッグ出力のレベル。0は出力なし。デフォルトは`0`。 :type print_level: int, optional :returns: ラベルのリストとテンプレート文字列のリストのタプル。エラーが発生した場合は`(None, None)`。 :rtype: tuple[list[str], list[str]] or tuple[None, None] """ if not os.path.isfile(template_path): if print_level: print() print(f"Error in tktemplate.read_template_xlsx(): File [{template_path}] does not exist") return None, None wb = openpyxl.load_workbook(template_path, data_only = data_only) if not wb: if print_level: print() print(f"Error in tktemplate.read_template_xlsx(): Can not read file [{template_path}]") return None, None ws = wb.active labels = [] templates = [] for i in range(1, ws.max_column + 1): labels.append(ws.cell(row = 1, column = i).value) templates.append(ws.cell(row = 2, column = i).value) if print_level: print("labels=", labels) print("templates=", templates) return labels, templates
[ドキュメント] def replace_df_columns(df, replace_dict, print_level = 0): """ 概要: DataFrameの列名を指定された辞書に基づいて置換します。 詳細説明: 入力DataFrame `df` の列名を`replace_dict`内のキーと値のペアに基づいて更新します。 `replace_dict`のキーと一致する列名が存在し、かつ`replace_dict`の値が`None`または空文字列でない場合に 置換が行われます。 :param df: 列名を置換する対象のDataFrame。 :type df: pandas.DataFrame :param replace_dict: 列名の置換ルールを含む辞書。キーが元の列名、値が新しい列名。 :type replace_dict: dict :param print_level: デバッグ出力のレベル。0は出力なし。デフォルトは`0`。 :type print_level: int, optional :returns: 列名が置換されたDataFrame。 :rtype: pandas.DataFrame """ labels_original = df.columns.tolist() replace_keys = replace_dict.keys() if print_level: # Docstring要件になかったが、既存コードのprint_level利用法に合わせ追加 print("labels_original=", labels_original) print("replace_keys=", replace_keys) for i, l in enumerate(labels_original): if print_level: # Docstring要件になかったが、既存コードのprint_level利用法に合わせ追加 print("label: ", l) if l in replace_keys and replace_dict[l] is not None and replace_dict[l] != '': # print("replace", i, l, replace_dict[l]) labels_original[i] = replace_dict[l] else: # print("no replace", i, l) pass df.columns = labels_original return df
[ドキュメント] def add_to_excel(template_path, outfile, data_list, replace_dict, data_only = False, print_level = 1): """ 概要: Excelテンプレートから構造を読み込み、新しいデータをExcelファイルに追加します。 詳細説明: `template_path`からExcelテンプレートを読み込み、既存または新規の`outfile`にデータを追加します。 テンプレートの2行目の内容(テンプレート文字列)を`replace_dict`と`data_list`(現在は未使用)を 使用して変換し、`outfile`の最終行に書き込みます。 出力ファイルが存在しない場合は新規に作成し、テンプレートの1行目(ラベル)をヘッダーとして書き込みます。 **注意**: `convert_by_template`関数は、このモジュール内で定義されていないため、実行時にエラーを発生させる可能性があります。 :param template_path: 読み込むExcelテンプレートファイルのパス。 :type template_path: str :param outfile: データを追加するExcelファイルのパス。存在しない場合は新規作成されます。 :type outfile: str :param data_list: (現在未使用) 追加するデータのリスト。 :type data_list: list :param replace_dict: テンプレート置換のためのキーと値のペアを含む辞書。 :type replace_dict: dict :param data_only: テンプレート読み込み時に数式ではなくセルの計算結果のみを読み込むかどうか。デフォルトは`False`。 :type data_only: bool, optional :param print_level: デバッグ出力のレベル。1は基本情報、2は詳細情報。デフォルトは`1`。 :type print_level: int, optional :returns: データ追加処理が成功した場合は`True`。 :rtype: bool """ if print_level >= 1: print("") print(f"Read template [{template_path}]") labels, templates = read_template_xlsx(template_path, data_only = data_only, print_level = print_level) wb = openpyxl.load_workbook(template_path, data_only = data_only) ws = wb.active if os.path.exists(outfile): if print_level >= 1: print(f"Read output file [{outfile}]") wb_out = openpyxl.load_workbook(outfile) ws_out = wb_out.active else: if print_level >= 1: print(f"Output file [{outfile}] does not exist. Create Excel workbook.") wb_out = openpyxl.Workbook() ws_out = wb_out.active for icol, label in enumerate(labels): ws_out.cell(row = 1, column = icol + 1).value = label irow = ws_out.max_row if print_lvel: # print_levelのtypoだが、ルールにより修正しない print(f"Add data to [{outfile}]") for icol, t in enumerate(templates): s_conv = convert_by_template(t, replace_dict, print_level = 0) # `convert_by_template` は定義されていない関数 s_conv = pconv(s_conv) if print_level >= 2: print(f" {irow}: {t}: {s_conv}") ws_out.cell(row = irow + 1, column = icol + 1).value = s_conv if print_level: print() print(f"Save to [{outfile}]") wb_out.save(outfile) return True
[ドキュメント] def read_replacement_db(replacement_db_path, print_level = 1): """ 概要: Excelファイルから置換ルール(キーと置換文字列)を読み込み、辞書として返します。 詳細説明: 指定されたExcelファイル(`replacement_db_path`)の1行目から置換文字列を、2行目からラベル(キー)を 読み込みます。これらの情報から、`{{key}}`タグを置換文字列`value`で置換するための辞書`replace_dict`を 作成します。 ただし、1行目1列目の値が`@@`で終わる場合は、その列はスキップされます。 :param replacement_db_path: 置換ルールを含むExcelファイルのパス。 :type replacement_db_path: str :param print_level: デバッグ出力のレベル。1は基本情報、2は詳細情報。デフォルトは`1`。 :type print_level: int, optional :returns: ラベルをキー、置換文字列を値とする辞書。 :rtype: dict """ if print_level: print() print(f"Read replacement DB {replacement_db_path}") wb = openpyxl.load_workbook(replacement_db_path, data_only = False) ws = wb.active labels = [] replace_str = [] for icol in range(ws.max_column): if icol == 0: top_value = ws.cell(row = 1, column = 1).value if top_value is not None and str(top_value)[-2:] == '@@': # Noneチェックを追加 continue replace_str.append(ws.cell(row = 1, column = icol + 1).value) labels.append(ws.cell(row = 2, column = icol + 1).value) replace_dict = {} for label, str_val in zip(labels, replace_str): # `str`は組み込み型なので`str_val`にリネーム replace_dict[label] = str_val if print_level >= 2: print("Replacement rules:") for l, r in zip(labels, replace_str): print(f" {l} => {r}") return replace_dict