1+ from collections .abc import Generator
2+ from typing import Any
3+ import openpyxl
4+ from openpyxl .utils import get_column_letter
5+ from openpyxl .styles import Border , Side
6+ from io import BytesIO
7+ import xml .etree .ElementTree as ET
8+ import base64
9+
10+ from dify_plugin import Tool
11+ from dify_plugin .entities .tool import ToolInvokeMessage
12+
13+ def apply_cell_styles (cell , style_elements ):
14+ """セルにスタイルを適用"""
15+ for style_elem in style_elements :
16+ if style_elem .tag == 'borders' :
17+ for border in style_elem .findall ('border' ):
18+ side = border .get ('side' )
19+ style = border .get ('style' )
20+ if side and style :
21+ border_style = Side (style = style )
22+ # 既存の罫線情報を保持しながら更新
23+ border_args = {
24+ 'top' : cell .border .top if cell .border else None ,
25+ 'bottom' : cell .border .bottom if cell .border else None ,
26+ 'left' : cell .border .left if cell .border else None ,
27+ 'right' : cell .border .right if cell .border else None
28+ }
29+ border_args [side ] = border_style
30+ cell .border = Border (** border_args )
31+
32+ elif style_elem .tag == 'merge' :
33+ # mergeタグの中身を直接取得
34+ merge_info = style_elem .attrib
35+ if 'start' in merge_info and 'end' in merge_info :
36+ start = merge_info ['start' ]
37+ end = merge_info ['end' ]
38+ try :
39+ cell .parent .merge_cells (f'{ start } :{ end } ' )
40+ except ValueError :
41+ # 既に結合されているセルの場合はスキップ
42+ pass
43+
44+ elif style_elem .tag == 'background' :
45+ from openpyxl .styles import PatternFill
46+ if style_elem .text : # テキストが存在する場合のみ適用
47+ cell .fill = PatternFill (start_color = style_elem .text ,
48+ end_color = style_elem .text ,
49+ fill_type = 'solid' )
50+
51+ elif style_elem .tag == 'color' :
52+ from openpyxl .styles import Font
53+ if style_elem .text : # テキストが存在する場合のみ適用
54+ current_font = cell .font or Font ()
55+ cell .font = Font (color = style_elem .text ,
56+ name = current_font .name ,
57+ size = current_font .size ,
58+ bold = current_font .bold ,
59+ italic = current_font .italic )
60+
61+ def create_excel_from_xml (xml_text : str ) -> BytesIO :
62+ """XML形式のテキストからExcelファイルを生成"""
63+ # 新しいワークブックを作成
64+ wb = openpyxl .Workbook ()
65+ ws = wb .active
66+
67+ try :
68+ # XMLをパース
69+ root = ET .fromstring (xml_text )
70+ worksheet = root .find ('worksheet' )
71+ if worksheet is None :
72+ raise ValueError ("worksheetタグが見つかりません" )
73+
74+ # 各行を処理
75+ for row_elem in worksheet .findall ('row' ):
76+ try :
77+ row_idx = int (row_elem .get ('index' ))
78+ except (ValueError , TypeError ):
79+ continue
80+
81+ # 各セルを処理
82+ for cell_elem in row_elem .findall ('cell' ):
83+ ref = cell_elem .get ('ref' )
84+ if not ref :
85+ continue
86+
87+ try :
88+ # セルの値を設定
89+ value_elem = cell_elem .find ('value' )
90+ if value_elem is not None and value_elem .text is not None :
91+ ws [ref ] = value_elem .text
92+
93+ # スタイルを適用
94+ apply_cell_styles (ws [ref ], cell_elem )
95+ except Exception as e :
96+ print (f"セル { ref } の処理中にエラー: { str (e )} " )
97+ continue
98+
99+ # メモリ上にExcelファイルを保存
100+ excel_io = BytesIO ()
101+ wb .save (excel_io )
102+ excel_io .seek (0 )
103+ return excel_io
104+
105+ except Exception as e :
106+ raise ValueError (f"XMLの処理中にエラー: { str (e )} " )
107+
108+ class DifyPluginXMLToExcelTool (Tool ):
109+ def _invoke (self , tool_parameters : dict [str , Any ]) -> Generator [ToolInvokeMessage , None , None ]:
110+ try :
111+ # XMLテキストを取得
112+ xml_text = tool_parameters .get ("xml_text" )
113+ if not xml_text :
114+ yield ToolInvokeMessage (
115+ type = "text" ,
116+ message = {"text" : "XMLテキストが提供されていません。" }
117+ )
118+ return
119+
120+ # XMLからExcelファイルを生成
121+ excel_io = create_excel_from_xml (xml_text )
122+ excel_data = excel_io .getvalue ()
123+
124+ # blobメッセージとしてファイルを返す
125+ yield self .create_blob_message (
126+ blob = excel_data ,
127+ meta = {
128+ "mime_type" : "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet" ,
129+ "filename" : "converted.xlsx"
130+ }
131+ )
132+
133+ except Exception as e :
134+ yield ToolInvokeMessage (
135+ type = "text" ,
136+ message = {"text" : f"エラーが発生しました: { str (e )} " }
137+ )
138+ return
0 commit comments