|
8 | 8 | from dify_plugin import Tool |
9 | 9 | from dify_plugin.entities.tool import ToolInvokeMessage |
10 | 10 |
|
11 | | -def get_important_cell_styles(cell) -> dict: |
12 | | - """デフォルトから変更のある重要なスタイル情報のみを取得""" |
| 11 | +def get_cell_styles(cell) -> dict: |
| 12 | + """セルのすべてのスタイル情報を取得""" |
13 | 13 | styles = {} |
14 | 14 |
|
15 | 15 | # セル結合情報の取得 |
16 | | - if cell.parent.merged_cells: # merged_cellsはワークシートの属性 |
| 16 | + if cell.parent.merged_cells: |
17 | 17 | for merged_range in cell.parent.merged_cells.ranges: |
18 | 18 | if cell.coordinate in merged_range: |
19 | 19 | styles['merge'] = { |
20 | | - 'start': merged_range.coord.split(':')[0], # 結合開始セル |
21 | | - 'end': merged_range.coord.split(':')[1] # 結合終了セル |
| 20 | + 'start': merged_range.coord.split(':')[0], |
| 21 | + 'end': merged_range.coord.split(':')[1] |
22 | 22 | } |
23 | 23 | break |
24 | 24 |
|
25 | | - # 罫線情報(デフォルトのNoneまたはthinは除外) |
26 | | - borders = {} |
27 | | - for side in ['top', 'bottom', 'left', 'right']: |
28 | | - border = getattr(cell.border, side) |
29 | | - if border.style and border.style not in ['thin', None]: |
30 | | - borders[side] = border.style |
31 | | - if borders: |
32 | | - styles['borders'] = borders |
| 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 |
33 | 34 |
|
34 | | - # 背景色(デフォルトの白や透明以外で、かつ有効な値の場合のみ) |
35 | | - if (cell.fill and cell.fill.start_color and |
36 | | - cell.fill.start_color.rgb and |
37 | | - cell.fill.start_color.rgb not in ['FFFFFFFF', '00000000'] and # 白と透明を除外 |
38 | | - isinstance(cell.fill.start_color.rgb, str)): |
39 | | - styles['background'] = cell.fill.start_color.rgb |
| 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 |
40 | 45 |
|
41 | | - # 文字色(デフォルトの黒以外で、かつ有効な値の場合のみ) |
| 46 | + # 文字色(すべての文字色を取得) |
42 | 47 | if (cell.font and cell.font.color and |
43 | 48 | cell.font.color.rgb and |
44 | | - cell.font.color.rgb != 'FF000000' and |
45 | 49 | isinstance(cell.font.color.rgb, str)): |
46 | 50 | styles['color'] = cell.font.color.rgb |
47 | 51 |
|
48 | | - return styles if styles else None |
| 52 | + return styles |
49 | 53 |
|
50 | 54 | def create_xml_with_styles(wb) -> str: |
51 | | - """ワークシートの内容と変更のあるスタイル情報のみをXMLに変換""" |
| 55 | + """ワークシートの内容とすべてのスタイル情報をXMLに変換""" |
52 | 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 | + |
53 | 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}"/>') |
54 | 82 |
|
55 | 83 | # データ範囲を取得 |
56 | 84 | data_rows = list(ws.rows) |
57 | 85 | if not data_rows: |
58 | 86 | return "<workbook></workbook>" |
59 | 87 |
|
60 | | - xml_parts.append(' <worksheet>') |
| 88 | + # セルの値をXMLエスケープする関数 |
| 89 | + def escape_xml(text): |
| 90 | + if not isinstance(text, str): |
| 91 | + text = str(text) |
| 92 | + return (text.replace('&', '&') |
| 93 | + .replace('<', '<') |
| 94 | + .replace('>', '>') |
| 95 | + .replace('"', '"') |
| 96 | + .replace("'", ''')) |
61 | 97 |
|
62 | | - for row_idx, row in enumerate(data_rows, 1): |
63 | | - has_content = False |
64 | | - row_parts = [] |
| 98 | + # すべてのセル情報を処理 |
| 99 | + for row in data_rows: |
| 100 | + row_idx = row[0].row |
| 101 | + xml_parts.append(f' <row index="{row_idx}">') |
65 | 102 |
|
66 | | - for col_idx, cell in enumerate(row, 1): |
67 | | - col_letter = get_column_letter(col_idx) |
68 | | - value = cell.value if cell.value is not None else "" |
69 | | - styles = get_important_cell_styles(cell) |
| 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>') |
70 | 110 |
|
71 | | - # 値かスタイルがある場合のみ出力 |
72 | | - if value or styles: |
73 | | - has_content = True |
74 | | - cell_parts = [f' <cell ref="{col_letter}{row_idx}">'] |
75 | | - |
76 | | - if value: |
77 | | - cell_parts.append(f' <value>{value}</value>') |
78 | | - |
79 | | - if styles: |
80 | | - for style_type, style_value in styles.items(): |
81 | | - if isinstance(style_value, dict): |
82 | | - # 罫線情報の場合 |
83 | | - if style_value: # 空の辞書は出力しない |
84 | | - cell_parts.append(f' <{style_type}>') |
85 | | - for side, style in style_value.items(): |
86 | | - cell_parts.append(f' <border side="{side}" style="{style}"/>') |
87 | | - cell_parts.append(f' </{style_type}>') |
88 | | - else: |
89 | | - # 背景色や文字色の場合(有効な値の場合のみ) |
90 | | - cell_parts.append(f' <{style_type}>{style_value}</{style_type}>') |
91 | | - |
92 | | - cell_parts.append(' </cell>') |
93 | | - row_parts.extend(cell_parts) |
| 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>') |
94 | 129 |
|
95 | | - # 行に内容がある場合のみ出力 |
96 | | - if has_content: |
97 | | - xml_parts.append(f' <row index="{row_idx}">') |
98 | | - xml_parts.extend(row_parts) |
99 | | - xml_parts.append(' </row>') |
| 130 | + xml_parts.append(' </row>') |
100 | 131 |
|
101 | 132 | xml_parts.append(' </worksheet>') |
102 | 133 | xml_parts.append('</workbook>') |
103 | | - |
104 | 134 | return '\n'.join(xml_parts) |
105 | 135 |
|
106 | 136 | def get_url_from_file_data(file_data: Any) -> str: |
|
0 commit comments