Repository navigation
Expand file tree
/
Copy pathexcel2dataMaiML2.py
More file actions
437 lines (391 loc) · 22.2 KB
/
Copy pathexcel2dataMaiML2.py
File metadata and controls
437 lines (391 loc) · 22.2 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
import os, sys, uuid, copy, mimetypes, hashlib
from pathlib import Path
from datetime import datetime
import xml.etree.ElementTree as ET
import pandas as pd
from USERS.usersettings import filePath, time
################################################################################################################
### nanを空文字に変換 ###
def nan_to_empty_string(value):
if pd.isna(value):
return ""
return str(value)
### uuid要素をバージョン4で生成 ###
def create_uuid():
return str(uuid.uuid4())
### id属性の値がnanの場合、デフォルト値を設定 ###
def setID(value, tag, prefix=""):
if pd.isna(value) or value == "":
return f"def{tag}{prefix}ID"
return str(value)
## valueの数値をフォーマット
def formatter_num(format_string, number):
if '.' in format_string:
decimal_places = len(format_string.split('.')[1]) # 小数点以下の桁数
else:
decimal_places = 0
# 小数点以下の桁数に基づいて数値をフォーマット
if decimal_places == 0:
formatted = "{:.0f}".format(int(number)) # 整数としてフォーマット
elif decimal_places == 1:
formatted = "{:.1f}".format(float(number)) # 小数点以下1桁
elif decimal_places == 2:
formatted = "{:.2f}".format(float(number)) # 小数点以下2桁
elif decimal_places == 3:
formatted = "{:.3f}".format(float(number)) # 小数点以下3桁
elif decimal_places == 4:
formatted = "{:.4f}".format(float(number)) # 小数点以下4桁
else:
formatted = number # それ以外の場合
return formatted
### instruction要素のidを基に、親要素のprogram要素のidを検索して返す ###
def find_programID_by_instructionID(root, instruction_id):
for program_elem in root.findall(".//program"):
for instruction_elem in program_elem.findall(".//instruction"):
if instruction_elem is not None and instruction_elem.get('id') == instruction_id:
return program_elem.get("id"), instruction_elem.find("./uuid").text
return None
### insertion要素のコンテンツを作成 ###
def make_insertion(value, instance_element, others_path=None):
if pd.isna(value) or value == "":
return instance_element
filename = str(value)
if others_path is not None:
file_path = others_path / filename
else:
file_path = filePath().INPUT_OTHER_PATH + filename
# formatの取得
extension = os.path.splitext(filename)[1]
mime_type=''
hash_sha256 = ''
mimetypes.init()
try:
mime_type = mimetypes.types_map[extension]
except Exception as e:
print("[INFO]insertion file's mime_type is not exist.: ", filename)
mime_type = 'none'
try:
with open(file_path, 'rb') as f:
hash_sha256 = hashlib.sha256()
while chunk := f.read(8192): # 8KBごとにファイルを読み込む
hash_sha256.update(chunk)
hash_sha256 = hash_sha256.hexdigest()
print("insertion file is loaded.: ", file_path)
except FileNotFoundError as e:
print("Skip hash value generation because the input file does not exist in the directory. ", filename)
except Exception as e:
print(e)
exit(1)
## insertion要素を追加
insertion_element = ET.SubElement(instance_element, "insertion")
insertion_uri_element = ET.SubElement(insertion_element, "uri")
insertion_uri_element.text = './'+ filename # MaiMLと同じフォルダを設定
insertion_hash_element = ET.SubElement(insertion_element, "hash")
insertion_hash_element.text = str(hash_sha256)
insertion_format_element = ET.SubElement(insertion_element, "format")
insertion_format_element.text = mime_type
return instance_element
### 再帰的にproperty/content要素を探索し、keyが一致する要素を返す
def find_general_elements_by_key(element, key):
matched_elements = []
if element.tag in ("property", "content") and element.get("key") == key:
matched_elements.append(element)
for child in element:
matched_elements.extend(find_general_elements_by_key(child, key))
return matched_elements
### エクセルの日付のフォーマットをMaiMLのフォーマットに変換 ###
def change_time_format(e_datetime):
e_datetime = pd.to_datetime(e_datetime).tz_localize(time().TIME_ZONE)
return e_datetime.strftime(time().TIME_FORMAT)[:22] + ':' + e_datetime.strftime('%z')[3:]
### document要素のdate要素用に現在の時刻をxs:dateTime形式にフォーマットして返す ###
def get_current_datetime():
# 現在のローカル時刻を取得
local_time = datetime.now().astimezone()
return local_time.strftime(time().TIME_FORMAT)[:22] + ':' + local_time.strftime('%z')[3:]
################################################################################################################
### MaiMLファイルを読み込む ###
def read_maiml(maiml_file):
namespaces = {}
## 名前空間を保持しておく関数 ##
def get_namespaces(xml_str):
# ルート要素から名前空間の定義を取得する
import re
namespaces = {}
match = re.findall(r'xmlns:([^=]+)="([^"]+)"', xml_str)
for prefix, uri in match:
namespaces[prefix] = uri
default_ns = re.search(r'xmlns="([^"]+)"', xml_str)
if default_ns:
namespaces[""] = default_ns.group(1) # デフォルト名前空間
return namespaces
## 名前空間を除去してタグを簡略化する関数 ##
def strip_namespace(element):
for elem in element.iter():
elem.tag = elem.tag.split("}")[-1] # 名前空間を削除
# XMLを文字列で読み込む
xml_str = ''
with open(maiml_file, "r", encoding="utf-8") as f:
xml_str = f.read()
# 名前空間を保存
namespaces = get_namespaces(xml_str)
## maimlタグの'xsi:type'属性を削除
for key in ['xsi:type="maimlRootType"','xsi:type="protocolFileRootType"', "xsi:type='maimlRootType'", "xsi:type='protocolFileRootTyp'"]:
xml_str = xml_str.replace(key, '')
# XML文字列をElementTreeで読み込む
tree = ET.ElementTree(ET.fromstring(xml_str))
root = tree.getroot()
# タグから名前空間プレフィックスを削除(名前空間を気にせずに処理するため/xmlns:xsiは残る)
strip_namespace(root)
return tree, root, namespaces
## Excelファイルを読み込む ##
def read_excel(excel_file):
df = pd.read_excel(excel_file)
return df
## MaiMLデータとExcelデータをmerge ##
# 1シート→1method要素、1log要素
# 1行→1results,1trace要素
def merge_data(root, xls, others_path=None):
# 入力MaiMLデータに<data>要素と<eventLog>要素が存在する場合は削除
data_elem = root.find("./data")
if data_elem is not None:
root.remove(data_elem)
eventLog_elem = root.find("./eventLog")
if eventLog_elem is not None:
root.remove(eventLog_elem)
## <data>要素を追加(id属性,<uuid>要素に自動生成した値を設定)
data_element = ET.Element("data")
data_element.set("id", setID("","data"))
data_uuid_element = ET.SubElement(data_element, "uuid")
data_uuid_element.text = create_uuid()
## <eventLog>要素を追加(id属性,<uuid>要素に自動生成した値を設定)
eventLog_element = ET.Element("eventLog")
eventLog_element.set("id", setID("","eventLog"))
eventLog_uuid_element = ET.SubElement(eventLog_element, "uuid")
eventLog_uuid_element.text = create_uuid()
## XMLツリーからtemplate・instructionのid属性のリストをそれぞれ作成
protocol = root.find("./protocol")
templates = protocol.findall(".//materialTemplate") + protocol.findall(".//conditionTemplate") + protocol.findall(".//resultTemplate")
template_ids = [template.get("id") for template in templates]
instructions = protocol.findall(".//instruction")
instruction_ids = [instruction.get("id") for instruction in instructions]
## <method>要素のid属性値とエクセルデータのシート名を照合し、<log>要素を追加
methods = protocol.findall(".//method")
method_ids = [method.get("id") for method in methods]
sheet_num = 0
for method_id in method_ids:
df_method = pd.DataFrame()
try:
df_method = xls.parse(f"@{method_id}", header=None) # ヘッダー無し
except Exception as e:
print("Error while reading the sheet: ", e)
exit(1)
if not df_method.empty:
sheet_num += 1
## <eventLog>要素に<log>要素を追加(id属性に自動生成した値、ref属性に<method>要素のid属性値を設定)
log_element = ET.SubElement(eventLog_element, "log")
log_element.set("id", setID("","log", sheet_num))
log_element.set("ref", method_id)
log_uuid_element = ET.SubElement(log_element, "uuid")
log_uuid_element.text = create_uuid()
## sheetの値を取得し、<trace>,<event>要素を追加
## 1行目:instruction,templateのID
row1 = df_method.iloc[0]
template_list = {}
instruction_list = {}
for col_num, col in row1.items():
# 1行目の値がtemplateのid属性値の場合(複数列存在)
if col in template_ids:
if col in template_list.keys():
template_list[col].append(col_num)
else:
template_list[col] = [col_num] # 新しいリストを作成
# 1行目の値が<instruction>要素のid属性値の場合(1列のみ存在)
if col in instruction_ids:
if col in instruction_list.keys():
## エラー処理
print("Instruction ID is duplicated.")
exit(1)
else:
instruction_list[col] = col_num # 新しいリストを作成
## 2行目:1行目がtemplateのID列にkeyの値もしくは'INSERTION'
row2 = df_method.iloc[1]
## 3行目以降:1行目がtemplateのID列→<results>要素を作成/1行目が<instruction>のID列に日付→<event>要素のコンテンツに追加
row3over = df_method.iloc[2:]
for _rownum, row in row3over.iterrows():
results_id = setID(row[0],"results")
## <results>要素を追加(id属性に1列目の値を設定)
results_element = ET.SubElement(data_element, "results")
results_element.set("id", results_id)
results_uuid_element = ET.SubElement(results_element, "uuid")
results_uuid_element.text = create_uuid()
## MaiMLデータの全てのtemplateを取得し、<results>要素にインスタンス(material/condition/result)として追加
for template in templates:
#print("template: ", template)
# results要素にinstance要素を追加
template_id = template.get("id")
element_name = template.tag
instance_element = ''
if element_name == "materialTemplate": # materialTemplate->material
instance_element = ET.SubElement(results_element, "material")
elif element_name == "conditionTemplate": # conditionTemplate->condition
instance_element = ET.SubElement(results_element, "condition")
elif element_name == "resultTemplate": # resultTemplate->result
instance_element = ET.SubElement(results_element, "result")
#instance_element.set("id", setID("",element_name,_rownum))
instance_element.set("id", setID("",element_name[:-8]+'_'+template_id,_rownum))
instance_element.set("ref", template_id)
instance_uuid_element = ET.SubElement(instance_element, "uuid")
instance_uuid_element.text = create_uuid()
# template直下のpropertyとcontentのみ取得
template_properties = list(template.findall("./property")) + list(template.findall("./content"))
if template_properties:
instance_properties = [copy.deepcopy(prop) for prop in template_properties]
# 2行目の値が、汎用データコンテナのkey属性値の場合は<value>要素の値を上書き/"INSERTION"の場合は<insertion>要素を追加
if template_id in template_list.keys():
for template_col_num in template_list[template_id]:
# row2[template_col_num]がinstance_propertiesのkeyに存在する場合、3行目以降のその列の値で上書き
key = row2[template_col_num]
# "INSERTION"の場合、insertion要素を追加
if key == "INSERTION":
instance_element = make_insertion(row[template_col_num], instance_element, others_path)
continue
for general_element in instance_properties:
# key一致する要素を再起的に探す
matched_elements = find_general_elements_by_key(general_element, key)
for match in matched_elements:
value_element = match.find("./value")
if value_element is None:
value_element = ET.SubElement(match, "value")
value = nan_to_empty_string(row[template_col_num])
if pd.notna(match.get("formatString")):
value = formatter_num(match.get("formatString"), value)
value_element.text = value
instance_element.extend(instance_properties)
## <instruction>要素のid属性値から、<trace>,<event>要素を生成する
for instruction_id, instruction_colnum in instruction_list.items():
program_id , instruction_uuid = find_programID_by_instructionID(root,instruction_id)
## <trace>要素を追加(idに自動生成した値、refに<program>要素のid値を設定)
trace_element = ET.SubElement(log_element, "trace")
trace_element.set("id", setID("","trace", _rownum))
trace_element.set("ref", program_id) # <trace>要素のref属性値は<program>要素のid属性値
trace_uuid_element = ET.SubElement(trace_element, "uuid")
trace_uuid_element.text = create_uuid()
## <event>要素を追加(idに自動生成した値、refに<instruction>要素のid値を設定)
event_element = ET.SubElement(trace_element, "event")
event_element.set("id", setID("","event_"+instruction_id, _rownum))
event_element.set("ref", instruction_id) # <event>要素のref属性値は<instruction>要素のid属性値
event_uuid_element = ET.SubElement(event_element, "uuid")
event_uuid_element.text = create_uuid()
## 3行目以降の値(日付)を取得し、<event>要素に必要なコンテンツを追加
for col_num, col in row.items():
if col_num == instruction_colnum:
property_element1 = ET.SubElement(event_element, "property")
property_element1.set("xsi:type", "uuidType")
property_element1.set("key", "concept:instance")
value_element1 = ET.SubElement(property_element1, "value")
#value_element1.text = instruction_uuid
value_element1.text = results_uuid_element.text # instructionのuuidではなく、resultsのuuidを設定
property_element2 = ET.SubElement(event_element, "property")
property_element2.set("xsi:type", "stringType")
property_element2.set("key", "lifecycle:transition")
value_element2 = ET.SubElement(property_element2, "value")
value_element2.text = "complete"
property_element3 = ET.SubElement(event_element, "property")
property_element3.set("xsi:type", "dateTimeType")
property_element3.set("key", "time:timestamp")
property_element3.set("formatString", "YYYY-MM-DDThh:mm:ssTZD")
value_element3 = ET.SubElement(property_element3, "value")
value_element3.text = change_time_format(col) # 日付のフォーマットを変換してから追加
## resultsRef要素を追加
resultsRef_element = ET.SubElement(event_element, "resultsRef")
resultsRef_element.set("id", f"{instruction_id}_{event_element.get('id')}_resultref")
resultsRef_element.set("ref", results_id)
root.append(data_element)
root.append(eventLog_element)
return root
## 生成したMaiMLデータをファイルに書き出す ##
def write_maiml(root, output_file, namespaces):
try:
# ルート要素にすでにある名前空間を取得(ElementTreeが残してしまった名前空間への対処)
existing_ns = {k: v for k, v in ET.ElementTree(root).getroot().attrib.items() if k.startswith("xmlns")}
## 元の名前空間を復元
for prefix, uri in namespaces.items():
# 取得した名前空間をルート要素に設定(重複がある場合はスキップ)
attr_name = f"xmlns:{prefix}" if prefix else "xmlns"
if attr_name not in existing_ns: # 名前空間がルート要素にすでに存在している場合は追加しない
if attr_name == "xmlns:xsi": ## xmlns:xsiは隠れて存在するので追加しない(応急処置)
continue
else:
root.set(attr_name, uri)
## MaiMLデータの更新
## maimlタグにxsi:type属性を追加
root.set("xsi:type", "maimlRootType")
## documentのuuid,date要素を更新
root.find("./document").find("./uuid").text = create_uuid()
root.find("./document").find("./date").text = get_current_datetime() # 現在時刻をフォーマットして設定
## ファイルに書き出す
tree = ET.ElementTree(root)
ET.indent(tree, space=" ", level=0)
tree.write(output_file, encoding="utf-8", xml_declaration=True)
except Exception as e:
print('Error while writing to the file.',e)
exit(1)
## main処理 ##
def main(maiml_input, excel_input, maiml_output, rootdir=None):
## MAiMLファイルの読み込み
tree = ''
root = ''
try:
tree, root, namespaces = read_maiml(maiml_input)
print("MaiML file is loaded.: "+ maiml_input)
except Exception as e:
print("An error occurred while reading the input file.: "+ maiml_input)
print(e)
exit(1)
## Excelファイルの読み込み
xls = ''
try:
xls = pd.ExcelFile(excel_input)
print("Excel file is loaded.: "+ excel_input)
except Exception as e:
print("An error occurred while reading the input file.: "+ excel_input)
print(e)
exit(1)
## MaiMLデータとエクセルデータをマージ
root = merge_data(root, xls, rootdir)
## ファイルへ書き出し
write_maiml(root, maiml_output, namespaces)
## python実行関数 ##
if __name__ == "__main__":
if len(sys.argv) > 1: ## 引数ありで実行
# 引数で指定したディレクトリ内のエクセルファイルを取得
rootdir = Path( filePath().INPUT_DIR + sys.argv[1])
if rootdir.exists() and rootdir.is_dir():
inputMaimlpath = ''
inputExfilepath = ''
outputMaimlpath = ''
for file in rootdir.rglob('*'): # rglob('*') で再帰的にすべてのファイルを取得
if file.is_file(): # ファイルかどうかを確認
# ファイル名と拡張子を分けて取得
file_extension = file.suffix # 拡張子を取得
if file_extension == '.xlsx':
exfilename = file
inputExfilepath = rootdir / exfilename
outputMaimlpath = rootdir / f"{os.path.splitext(os.path.basename(exfilename))[0]}_output.maiml"
elif file_extension == '.maiml':
maimlefilename = file
inputMaimlpath = rootdir / maimlefilename
try:
main(str(inputMaimlpath), str(inputExfilepath), str(outputMaimlpath), rootdir=rootdir)
print("Successfully created the MaiML data file. ", outputMaimlpath)
except Exception as e:
print('Error : ',e)
else: ## 引数なしで実行
inputMaimlpath = filePath().INPUT_MaiML_PATH
inputExfilepath = filePath().INPUT_EXCEL_PATH
outputMaimlpath = filePath().OUTPUT_FILE_PATH
try:
main(inputMaimlpath, inputExfilepath, outputMaimlpath)
print("Successfully created the MaiML data file. "+ outputMaimlpath)
except Exception as e:
print('Error : ',e)