Skip to content

Commit 2b1eddb

Browse files
Merge pull request #1 from Olemi-llm-apprentice/feature/excel-to-xml
Feature/excel to xml
2 parents 85843d8 + 8357700 commit 2b1eddb

14 files changed

+608
-76
lines changed

_assets/icon.svg

Lines changed: 17 additions & 5 deletions
Loading

manifest.yaml

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,17 @@
1-
version: 0.0.1
1+
version: 0.0.2
22
type: plugin
33
author: lemilemio
4-
name: dify-plugin-markdown-to-excel
4+
name: excel-plugin
55
label:
6-
en_US: dify-plugin-markdown-to-excel
7-
ja_JP: dify-plugin-markdown-to-excel
8-
zh_Hans: dify-plugin-markdown-to-excel
9-
pt_BR: dify-plugin-markdown-to-excel
6+
en_US: excel-plugin
7+
ja_JP: excel-plugin
8+
zh_Hans: excel-plugin
9+
pt_BR: excel-plugin
1010
description:
11-
en_US: markdown_to_excel
12-
ja_JP: markdown_to_excel
13-
zh_Hans: markdown_to_excel
14-
pt_BR: markdown_to_excel
11+
en_US: excel-plugin
12+
ja_JP: excel-plugin
13+
zh_Hans: excel-plugin
14+
pt_BR: excel-plugin
1515
icon: icon.svg
1616
resource:
1717
memory: 268435456
@@ -25,9 +25,9 @@ resource:
2525
size: 1048576
2626
plugins:
2727
tools:
28-
- provider/dify-plugin-markdown-to-excel.yaml
28+
- provider/excel-plugin.yaml
2929
meta:
30-
version: 0.0.1
30+
version: 0.0.2
3131
arch:
3232
- amd64
3333
- arm64

provider/dify-plugin-markdown-to-excel.yaml

Lines changed: 0 additions & 17 deletions
This file was deleted.
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
from dify_plugin.errors.tool import ToolProviderCredentialValidationError
55

66

7-
class DifyPluginMarkdownToExcelProvider(ToolProvider):
7+
class ExcelPluginProvider(ToolProvider):
88
def _validate_credentials(self, credentials: dict[str, Any]) -> None:
99
try:
1010
"""

provider/excel-plugin.yaml

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
identity:
2+
author: lemilemio
3+
name: excel-plugin
4+
label:
5+
en_US: Excel-Plugin
6+
zh_Hans: Excel-Plugin
7+
pt_BR: Excel-Plugin
8+
description:
9+
en_US: Excel-Plugin
10+
zh_Hans: Excel-Plugin
11+
pt_BR: Excel-Plugin
12+
icon: icon.svg
13+
tools:
14+
- tools/markdown-to-excel.yaml
15+
- tools/excel-to-xml.yaml
16+
- tools/xml-to-excel.yaml
17+
extra:
18+
python:
19+
source: provider/excel-plugin.py

requirements.txt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,5 @@
11
dify_plugin
2+
openpyxl
3+
pandas
4+
requests
5+
lxml

tools/dify-plugin-markdown-to-excel.py

Lines changed: 0 additions & 11 deletions
This file was deleted.

tools/dify-plugin-markdown-to-excel.yaml

Lines changed: 0 additions & 30 deletions
This file was deleted.

tools/excel-to-xml.py

Lines changed: 208 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,208 @@
1+
from collections.abc import Generator
2+
from typing import Any
3+
import requests
4+
import openpyxl
5+
from openpyxl.utils import get_column_letter
6+
from io import BytesIO
7+
8+
from dify_plugin import Tool
9+
from dify_plugin.entities.tool import ToolInvokeMessage
10+
11+
def get_cell_styles(cell) -> dict:
12+
"""セルのすべてのスタイル情報を取得"""
13+
styles = {}
14+
15+
# セル結合情報の取得
16+
if cell.parent.merged_cells:
17+
for merged_range in cell.parent.merged_cells.ranges:
18+
if cell.coordinate in merged_range:
19+
styles['merge'] = {
20+
'start': merged_range.coord.split(':')[0],
21+
'end': merged_range.coord.split(':')[1]
22+
}
23+
break
24+
25+
# 罫線情報(すべての罫線情報を取得)
26+
if cell.border:
27+
borders = {}
28+
for side in ['top', 'bottom', 'left', 'right']:
29+
border = getattr(cell.border, side)
30+
if border and border.style:
31+
borders[side] = border.style
32+
if borders:
33+
styles['borders'] = borders
34+
35+
# 背景色(パターンタイプと色情報を取得)
36+
if cell.fill:
37+
if hasattr(cell.fill, 'patternType') and cell.fill.patternType:
38+
if cell.fill.patternType == 'solid':
39+
if (cell.fill.start_color and
40+
cell.fill.start_color.rgb and
41+
isinstance(cell.fill.start_color.rgb, str)):
42+
# 透明の場合は除外
43+
if cell.fill.start_color.rgb != '00000000':
44+
styles['background'] = cell.fill.start_color.rgb
45+
46+
# 文字色(すべての文字色を取得)
47+
if (cell.font and cell.font.color and
48+
cell.font.color.rgb and
49+
isinstance(cell.font.color.rgb, str)):
50+
styles['color'] = cell.font.color.rgb
51+
52+
return styles
53+
54+
def create_xml_with_styles(wb) -> str:
55+
"""ワークシートの内容とすべてのスタイル情報をXMLに変換"""
56+
ws = wb.active
57+
58+
# 結合セルの範囲を保存
59+
merged_cells_ranges = ws.merged_cells.ranges.copy()
60+
61+
# 結合セルを一時的に解除
62+
for merged_cell_range in merged_cells_ranges:
63+
ws.unmerge_cells(str(merged_cell_range))
64+
65+
xml_parts = ['<?xml version="1.0" encoding="UTF-8"?>\n<workbook>']
66+
xml_parts.append(' <worksheet>')
67+
68+
# すべての列幅情報を追加
69+
for col_letter, col in ws.column_dimensions.items():
70+
if hasattr(col, 'width') and col.width is not None:
71+
width = float(col.width)
72+
xml_parts.append(f' <column letter="{col_letter}" width="{width:.2f}"/>')
73+
74+
# 結合セルを再設定
75+
for merged_cell_range in merged_cells_ranges:
76+
ws.merge_cells(str(merged_cell_range))
77+
78+
# すべての行高さ情報を追加
79+
for row_idx, row in ws.row_dimensions.items():
80+
if row.height:
81+
xml_parts.append(f' <row index="{row_idx}" height="{row.height:.2f}"/>')
82+
83+
# データ範囲を取得
84+
data_rows = list(ws.rows)
85+
if not data_rows:
86+
return "<workbook></workbook>"
87+
88+
# セルの値をXMLエスケープする関数
89+
def escape_xml(text):
90+
if not isinstance(text, str):
91+
text = str(text)
92+
return (text.replace('&', '&amp;')
93+
.replace('<', '&lt;')
94+
.replace('>', '&gt;')
95+
.replace('"', '&quot;')
96+
.replace("'", '&apos;'))
97+
98+
# すべてのセル情報を処理
99+
for row in data_rows:
100+
row_idx = row[0].row
101+
xml_parts.append(f' <row index="{row_idx}">')
102+
103+
for cell in row:
104+
# 値の有無に関わらずすべてのセルを出力
105+
xml_parts.append(f' <cell ref="{cell.coordinate}">')
106+
107+
if cell.value is not None:
108+
value = escape_xml(cell.value)
109+
xml_parts.append(f' <value>{value}</value>')
110+
111+
# スタイル情報を追加
112+
styles = get_cell_styles(cell)
113+
if styles:
114+
for style_name, style_value in styles.items():
115+
if style_name == 'merge':
116+
xml_parts.append(' <merge>')
117+
xml_parts.append(f' <start>{style_value["start"]}</start>')
118+
xml_parts.append(f' <end>{style_value["end"]}</end>')
119+
xml_parts.append(' </merge>')
120+
elif style_name == 'borders':
121+
xml_parts.append(' <borders>')
122+
for border_side, border_style in style_value.items():
123+
xml_parts.append(f' <border side="{border_side}" style="{border_style}"/>')
124+
xml_parts.append(' </borders>')
125+
else:
126+
xml_parts.append(f' <{style_name}>{style_value}</{style_name}>')
127+
128+
xml_parts.append(' </cell>')
129+
130+
xml_parts.append(' </row>')
131+
132+
xml_parts.append(' </worksheet>')
133+
xml_parts.append('</workbook>')
134+
return '\n'.join(xml_parts)
135+
136+
def get_url_from_file_data(file_data: Any) -> str:
137+
"""ファイルデータからURLを抽出する"""
138+
if isinstance(file_data, str):
139+
# 直接URLが渡された場合
140+
return file_data
141+
elif isinstance(file_data, list) and len(file_data) > 0:
142+
# 配列形式で渡された場合
143+
first_item = file_data[0]
144+
if isinstance(first_item, dict) and 'url' in first_item:
145+
return first_item['url']
146+
elif isinstance(file_data, dict) and 'url' in file_data:
147+
# 辞書形式で渡された場合
148+
return file_data['url']
149+
elif hasattr(file_data, 'url'):
150+
# Fileオブジェクトの場合
151+
return file_data.url
152+
return None
153+
154+
class DifyPluginExcelToXMLTool(Tool):
155+
def _invoke(self, tool_parameters: dict[str, Any]) -> Generator[ToolInvokeMessage, None, None]:
156+
157+
# ファイルオブジェクトを取得
158+
file_data = tool_parameters.get("file_url")
159+
160+
if not file_data:
161+
yield ToolInvokeMessage(
162+
type="text",
163+
message={"text": "ファイルが提供されていません。"}
164+
)
165+
return
166+
167+
try:
168+
# ファイルのURLを取得
169+
file_url = get_url_from_file_data(file_data)
170+
171+
if not file_url:
172+
yield ToolInvokeMessage(
173+
type="text",
174+
message={"text": "ファイルのURLが見つかりません。"}
175+
)
176+
return
177+
178+
# ファイルをダウンロード
179+
try:
180+
response = requests.get(file_url)
181+
response.raise_for_status()
182+
file_content = response.content
183+
except Exception as e:
184+
yield ToolInvokeMessage(
185+
type="text",
186+
message={"text": f"ファイルのダウンロードに失敗しました: {str(e)}"}
187+
)
188+
return
189+
190+
# ExcelファイルをOpenpyxlで読み込む
191+
excel_data = BytesIO(file_content)
192+
wb = openpyxl.load_workbook(excel_data)
193+
194+
# XMLに変換(罫線情報を含む)
195+
xml_data = create_xml_with_styles(wb)
196+
197+
# XML形式のテキストを返す
198+
yield ToolInvokeMessage(
199+
type="text",
200+
message={"text": xml_data}
201+
)
202+
203+
except Exception as e:
204+
yield ToolInvokeMessage(
205+
type="text",
206+
message={"text": f"エラーが発生しました: {str(e)}"}
207+
)
208+
return

0 commit comments

Comments
 (0)