mintlee commited on
Commit
b149fbe
·
1 Parent(s): 1685fcb

add textbox

Browse files
Files changed (3) hide show
  1. excel/excel_translate.py +2 -0
  2. excel/xlsx.py +199 -73
  3. pages/upload.py +2 -2
excel/excel_translate.py CHANGED
@@ -10,6 +10,8 @@ import gridfs
10
  import tempfile
11
  import os
12
 
 
 
13
  def translate_xlsx(file_id: str, target_lang: str = ""):
14
  # Kết nối MongoDB
15
  client = pymongo.MongoClient("mongodb+srv://admin:[email protected]/?retryWrites=true&w=majority&appName=Cluster0")
 
10
  import tempfile
11
  import os
12
 
13
+
14
+
15
  def translate_xlsx(file_id: str, target_lang: str = ""):
16
  # Kết nối MongoDB
17
  client = pymongo.MongoClient("mongodb+srv://admin:[email protected]/?retryWrites=true&w=majority&appName=Cluster0")
excel/xlsx.py CHANGED
@@ -11,7 +11,10 @@ from io import BytesIO
11
  import shutil
12
  import io
13
 
 
14
  NS_MAIN = {'main': 'http://schemas.openxmlformats.org/spreadsheetml/2006/main'}
 
 
15
 
16
  # --- Hàm đăng ký namespace (quan trọng khi ghi file) ---
17
  def register_namespaces(xml_file):
@@ -21,99 +24,124 @@ def register_namespaces(xml_file):
21
  ])
22
  for ns, uri in namespaces.items():
23
  ET.register_namespace(ns, uri)
 
24
  # Đăng ký thêm namespace phổ biến nếu chưa có
25
- if 'main' not in namespaces and '' not in namespaces: # Kiểm tra cả prefix rỗng
26
- ET.register_namespace('', NS_MAIN['main']) # Đăng ký default namespace
27
- elif 'main' not in namespaces:
28
- ET.register_namespace('main', NS_MAIN['main']) # Đăng ký với prefix 'main'
 
 
 
 
 
 
29
 
30
 
31
  def extract_text_from_sheet(unzipped_folder_path: str) -> Optional[Tuple[List[Dict[str, Any]], Dict[str, Any]]]:
32
  """
33
- Trích xuất text, lưu lại định dạng của run đầu tiên nếu là Rich Text.
 
34
  """
35
  modifiable_nodes = []
36
  shared_strings_path = os.path.join(unzipped_folder_path, "xl", "sharedStrings.xml")
37
  worksheets_folder = os.path.join(unzipped_folder_path, "xl", "worksheets")
 
 
38
  shared_tree = None
39
  sheet_trees = {}
 
40
 
41
  # --- Xử lý sharedStrings.xml ---
42
  if os.path.exists(shared_strings_path):
43
  try:
44
- register_namespaces(shared_strings_path)
45
  shared_tree = ET.parse(shared_strings_path)
46
  root_shared = shared_tree.getroot()
47
 
48
  for si_element in root_shared.findall('main:si', NS_MAIN):
49
  text_parts = []
50
- t_elements = si_element.findall('.//main:t', NS_MAIN) # Tìm tất cả <t> con
 
51
 
52
- # Tìm run đầu tiên (<r>) và properties (<rPr>) của nó
53
- first_r = si_element.find('./main:r', NS_MAIN) # Tìm <r> con trực tiếp đầu tiên
54
- first_rpr_clone = None # Lưu bản sao của <rPr> đầu tiên
55
- is_rich_text = first_r is not None
56
 
57
  if is_rich_text:
58
- # Tìm <rPr> bên trong <r> đầu tiên
59
- first_rpr = first_r.find('./main:rPr', NS_MAIN)
60
- if first_rpr is not None:
61
- # Sao chép sâu để không ảnh hưởng cây gốc và để dùng sau
62
- first_rpr_clone = copy.deepcopy(first_rpr)
 
 
 
 
63
 
64
- # Lấy toàn bộ text
65
  for t_node in t_elements:
66
  if t_node.text:
67
  text_parts.append(t_node.text)
68
  full_text = "".join(text_parts)
69
 
70
- if not full_text: continue # Bỏ qua nếu không có text
71
 
72
- if is_rich_text:
 
73
  modifiable_nodes.append({
74
  'type': 'shared_rich',
75
  'original_text': full_text,
76
- 'element': si_element, # Tham chiếu <si>
77
- 'first_format': first_rpr_clone, # Lưu định dạng <rPr> đầu tiên (hoặc None)
78
  'source_file': os.path.join("xl", "sharedStrings.xml"),
79
  'sheet_name': None
80
  })
81
- elif t_elements: # Không phải rich text, tìm thẻ <t> đơn giản
82
- first_t = si_element.find('./main:t', NS_MAIN)
83
- if first_t is not None:
84
- modifiable_nodes.append({
 
 
 
 
 
 
 
 
 
85
  'type': 'shared_simple',
86
  'original_text': full_text,
87
- 'element': first_t, # Tham chiếu <t>
88
- 'first_format': None, # Không có định dạng đặc biệt
89
  'source_file': os.path.join("xl", "sharedStrings.xml"),
90
  'sheet_name': None
91
  })
 
92
 
93
  except Exception as e:
94
  print(f"Lỗi xử lý sharedStrings: {e}")
95
  import traceback
96
  traceback.print_exc()
97
 
98
- # --- Xử lý các file sheetX.xml (Inline Strings - không có định dạng phức tạp) ---
 
99
  if os.path.isdir(worksheets_folder):
100
  for sheet_filename in sorted(os.listdir(worksheets_folder)):
101
  if sheet_filename.lower().endswith(".xml"):
102
- # ... (phần đọc và parse sheet tree như cũ) ...
103
  sheet_file_path = os.path.join(worksheets_folder, sheet_filename)
104
  try:
105
- register_namespaces(sheet_file_path)
106
  sheet_tree = ET.parse(sheet_file_path)
107
  sheet_trees[sheet_filename] = sheet_tree
108
  root_sheet = sheet_tree.getroot()
109
  for cell in root_sheet.findall('.//main:c[@t="inlineStr"]', NS_MAIN):
110
- t_element = cell.find('.//main:is/main:t', NS_MAIN)
111
- if t_element is not None and t_element.text is not None:
112
  modifiable_nodes.append({
113
  'type': 'inline',
114
  'original_text': t_element.text,
115
- 'element': t_element, # Tham chiếu <t>
116
- 'first_format': None, # Inline string không có định dạng <rPr>
117
  'source_file': os.path.join("xl", "worksheets", sheet_filename),
118
  'sheet_name': sheet_filename
119
  })
@@ -121,23 +149,91 @@ def extract_text_from_sheet(unzipped_folder_path: str) -> Optional[Tuple[List[Di
121
  print(f"Lỗi xử lý sheet {sheet_filename}: {e}")
122
  import traceback
123
  traceback.print_exc()
 
 
 
124
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
125
  else:
126
- print(f"Lỗi: Không tìm thấy thư mục worksheets: {worksheets_folder}")
127
 
128
 
129
- global_data = {"shared_tree": shared_tree, "sheet_trees": sheet_trees, "shared_strings_path": shared_strings_path, "worksheets_folder": worksheets_folder}
130
- return modifiable_nodes, global_data
 
 
 
 
 
 
 
131
 
132
  def apply_and_save_changes(modified_nodes_data: List[Dict[str, Any]], global_data: Dict[str, Any]) -> bool:
133
  """
134
- Cập nhật text, giữ lại định dạng đầu tiên cho Rich Text, và lưu file XML.
135
  """
136
  if not global_data: print("Lỗi: Thiếu global_data."); return False
137
 
138
  updated_files = set()
139
- try: ET.register_namespace('xml', "http://www.w3.org/XML/1998/namespace")
140
- except ValueError: pass
 
 
 
 
 
 
 
 
141
 
142
  for node_info in modified_nodes_data:
143
  if 'modified_text' in node_info and node_info['element'] is not None:
@@ -145,59 +241,75 @@ def apply_and_save_changes(modified_nodes_data: List[Dict[str, Any]], global_dat
145
  modified_text = node_info['modified_text']
146
  original_text = node_info.get('original_text', '')
147
  node_type = node_info.get('type', '')
148
- first_format = node_info.get('first_format') # Lấy <rPr> đã lưu (hoặc None)
149
 
150
  if original_text != modified_text:
151
- # --- Xử lý Rich Text: Tạo lại cấu trúc <si><r>[<rPr>]<t></r></si> ---
152
  if node_type == 'shared_rich':
153
  si_element = element
154
- # Xóa con cũ
155
- for child in list(si_element):
156
  si_element.remove(child)
157
 
158
- # Tạo run mới <r>
159
  new_r = ET.Element(f"{{{NS_MAIN['main']}}}r")
160
-
161
- # Nếu có định dạng đầu tiên (<rPr>), thêm nó vào <r> mới
162
  if first_format is not None:
163
- new_r.append(first_format) # Thêm bản sao <rPr> đã lưu
164
 
165
- # Tạo thẻ text mới <t>
166
  new_t = ET.Element(f"{{{NS_MAIN['main']}}}t")
167
  new_t.text = modified_text
168
- xml_space_attr = '{http://www.w3.org/XML/1998/namespace}space'
169
- new_t.set(xml_space_attr, 'preserve')
170
-
171
- # Thêm <t> vào <r>
172
  new_r.append(new_t)
173
- # Thêm <r> vào <si>
174
  si_element.append(new_r)
175
-
176
  updated_files.add(node_info['source_file'])
177
- # print(f"Applied first format to Rich Text in {node_info['source_file']}")
178
 
179
- # --- Xử lý Simple/Inline Text: Cập nhật thẻ <t> ---
180
  elif node_type in ['shared_simple', 'inline']:
181
- t_element = element
182
  t_element.text = modified_text
183
- xml_space_attr = '{http://www.w3.org/XML/1998/namespace}space'
184
- if xml_space_attr not in t_element.attrib or t_element.attrib[xml_space_attr] != 'preserve':
185
- t_element.set(xml_space_attr, 'preserve')
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
186
  updated_files.add(node_info['source_file'])
187
- # print(f"Updated Simple/Inline Text in {node_info['source_file']}")
 
188
  else:
189
- print(f"Cảnh báo: Loại node không xác định '{node_type}'")
190
 
191
- # --- Lưu lại các file XML đã thay đổi (Giữ nguyên) ---
192
  success = True
193
- # ... (Phần code lưu file như cũ) ...
194
  shared_tree = global_data.get("shared_tree"); shared_strings_path = global_data.get("shared_strings_path")
195
  sheet_trees = global_data.get("sheet_trees", {}); worksheets_folder = global_data.get("worksheets_folder")
 
196
 
197
  shared_strings_relative_path = os.path.join("xl", "sharedStrings.xml")
198
  if shared_tree and shared_strings_path and shared_strings_relative_path in updated_files:
199
  try:
200
- # print(f"Saving modified file: {shared_strings_path}")
201
  shared_tree.write(shared_strings_path, encoding='utf-8', xml_declaration=True)
202
  except Exception as e: print(f"Lỗi lưu {shared_strings_path}: {e}"); success = False
203
 
@@ -207,12 +319,29 @@ def apply_and_save_changes(modified_nodes_data: List[Dict[str, Any]], global_dat
207
  if sheet_relative_path in updated_files:
208
  sheet_file_path = os.path.join(worksheets_folder, sheet_filename)
209
  try:
210
- # print(f"Saving modified file: {sheet_file_path}")
211
  sheet_tree.write(sheet_file_path, encoding='utf-8', xml_declaration=True)
212
  except Exception as e: print(f"Lỗi lưu {sheet_file_path}: {e}"); success = False
213
 
214
- if success and updated_files: print(f"Đã lưu thành công {len(updated_files)} file XML đã sửa đổi (đã giữ lại định dạng đầu tiên cho Rich Text).")
215
- elif not updated_files: print("Không có file XML nào cần cập nhật.") ; return True
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
216
  return success
217
 
218
  def zip_folder_to_excel_file(folder_path, file_name):
@@ -424,7 +553,4 @@ def translate_xlsx(file_id, file_name, source_lang='en', target_lang='vi', batch
424
  shutil.rmtree(xml_folder) # Mark folder as 'handled' by zipping
425
  else:
426
  print("LỖI NGHIÊM TRỌNG: Không thể tạo file XLSX đã dịch cuối cùng.")
427
- return final_id
428
-
429
-
430
-
 
11
  import shutil
12
  import io
13
 
14
+
15
  NS_MAIN = {'main': 'http://schemas.openxmlformats.org/spreadsheetml/2006/main'}
16
+ NS_DRAWING = {'xdr': "http://schemas.openxmlformats.org/drawingml/2006/spreadsheetDrawing"}
17
+ NS_A = {'a': "http://schemas.openxmlformats.org/drawingml/2006/main"}
18
 
19
  # --- Hàm đăng ký namespace (quan trọng khi ghi file) ---
20
  def register_namespaces(xml_file):
 
24
  ])
25
  for ns, uri in namespaces.items():
26
  ET.register_namespace(ns, uri)
27
+
28
  # Đăng ký thêm namespace phổ biến nếu chưa có
29
+ if 'main' not in namespaces and '' not in namespaces and NS_MAIN['main'] not in namespaces.values():
30
+ ET.register_namespace('', NS_MAIN['main'])
31
+ elif 'main' not in namespaces and NS_MAIN['main'] not in namespaces.values():
32
+ ET.register_namespace('main', NS_MAIN['main'])
33
+
34
+ # Đăng ký namespaces cho drawing nếu cần
35
+ if 'xdr' not in namespaces and NS_DRAWING['xdr'] not in namespaces.values():
36
+ ET.register_namespace('xdr', NS_DRAWING['xdr'])
37
+ if 'a' not in namespaces and NS_A['a'] not in namespaces.values():
38
+ ET.register_namespace('a', NS_A['a'])
39
 
40
 
41
  def extract_text_from_sheet(unzipped_folder_path: str) -> Optional[Tuple[List[Dict[str, Any]], Dict[str, Any]]]:
42
  """
43
+ Trích xuất text, lưu lại định dạng của run đầu tiên nếu là Rich Text,
44
+ bao gồm cả text từ TextBoxes trong drawings.
45
  """
46
  modifiable_nodes = []
47
  shared_strings_path = os.path.join(unzipped_folder_path, "xl", "sharedStrings.xml")
48
  worksheets_folder = os.path.join(unzipped_folder_path, "xl", "worksheets")
49
+ drawings_folder = os.path.join(unzipped_folder_path, "xl", "drawings") # Thêm dòng này
50
+
51
  shared_tree = None
52
  sheet_trees = {}
53
+ drawing_trees = {} # Thêm dòng này
54
 
55
  # --- Xử lý sharedStrings.xml ---
56
  if os.path.exists(shared_strings_path):
57
  try:
58
+ register_namespaces(shared_strings_path) # Đảm bảo register_namespaces được gọi
59
  shared_tree = ET.parse(shared_strings_path)
60
  root_shared = shared_tree.getroot()
61
 
62
  for si_element in root_shared.findall('main:si', NS_MAIN):
63
  text_parts = []
64
+ # Tìm tất cả <t> con, bất kể chúng nằm trong <r> hay không
65
+ t_elements = si_element.findall('.//main:t', NS_MAIN)
66
 
67
+ first_r = si_element.find('./main:r', NS_MAIN)
68
+ first_rpr_clone = None
69
+ is_rich_text = first_r is not None # Rich text nếu ít nhất một <r>
 
70
 
71
  if is_rich_text:
72
+ # Cố gắng tìm <rPr> bên trong <r> đầu tiên
73
+ first_rpr_candidate = si_element.find('./main:r/main:rPr', NS_MAIN)
74
+ if first_rpr_candidate is not None:
75
+ first_rpr_clone = copy.deepcopy(first_rpr_candidate)
76
+ else:
77
+ # Nếu <r> đầu tiên không có <rPr>, kiểm tra <si><rPh><rPr> (Phonetic properties, ít gặp hơn)
78
+ # Hoặc có thể không có định dạng nào cụ thể ở run đầu
79
+ pass
80
+
81
 
 
82
  for t_node in t_elements:
83
  if t_node.text:
84
  text_parts.append(t_node.text)
85
  full_text = "".join(text_parts)
86
 
87
+ if not full_text or full_text.isspace(): continue
88
 
89
+ # Logic xác định type dựa trên sự hiện diện của <r> và <rPr> đã được điều chỉnh
90
+ if is_rich_text : # Chỉ cần có <r> là đủ, first_rpr_clone có thể là None
91
  modifiable_nodes.append({
92
  'type': 'shared_rich',
93
  'original_text': full_text,
94
+ 'element': si_element,
95
+ 'first_format': first_rpr_clone, # Sẽ None nếu <r> đầu không <rPr>
96
  'source_file': os.path.join("xl", "sharedStrings.xml"),
97
  'sheet_name': None
98
  })
99
+ elif t_elements: # Trường hợp <t> nhưng không <r> nào (simple shared string)
100
+ # Tìm <t> đầu tiên trực tiếp dưới <si> nếu không phải rich text
101
+ # Hoặc nếu cấu trúc là <si><t>...</t></si>
102
+ # Trong trường hợp này, element nên là si_element để khi apply_changes,
103
+ # ta sẽ tạo cấu trúc <si><r><t>...</t></r></si> nếu có định dạng
104
+ # hoặc <si><t>...</t></si> nếu không.
105
+ # Tuy nhiên, để đơn giản hóa, nếu không có <r>, ta coi element là <t> đầu tiên
106
+ # và không áp dụng "first_format" (vì nó sẽ là None).
107
+ # Hoặc, ta có thể luôn coi <si> là element cho shared strings.
108
+ # Lựa chọn hiện tại: nếu không có <r>, element là <t> đầu tiên tìm thấy.
109
+ direct_t = si_element.find('./main:t', NS_MAIN)
110
+ if direct_t is not None:
111
+ modifiable_nodes.append({
112
  'type': 'shared_simple',
113
  'original_text': full_text,
114
+ 'element': direct_t, # Tham chiếu <t>
115
+ 'first_format': None,
116
  'source_file': os.path.join("xl", "sharedStrings.xml"),
117
  'sheet_name': None
118
  })
119
+ # else: ít khả năng xảy ra nếu t_elements có phần tử
120
 
121
  except Exception as e:
122
  print(f"Lỗi xử lý sharedStrings: {e}")
123
  import traceback
124
  traceback.print_exc()
125
 
126
+
127
+ # --- Xử lý các file sheetX.xml (Inline Strings) ---
128
  if os.path.isdir(worksheets_folder):
129
  for sheet_filename in sorted(os.listdir(worksheets_folder)):
130
  if sheet_filename.lower().endswith(".xml"):
 
131
  sheet_file_path = os.path.join(worksheets_folder, sheet_filename)
132
  try:
133
+ register_namespaces(sheet_file_path) # Đảm bảo register_namespaces được gọi
134
  sheet_tree = ET.parse(sheet_file_path)
135
  sheet_trees[sheet_filename] = sheet_tree
136
  root_sheet = sheet_tree.getroot()
137
  for cell in root_sheet.findall('.//main:c[@t="inlineStr"]', NS_MAIN):
138
+ t_element = cell.find('.//main:is/main:t', NS_MAIN) # Sửa lại tìm kiếm <t>
139
+ if t_element is not None and t_element.text is not None and t_element.text.strip():
140
  modifiable_nodes.append({
141
  'type': 'inline',
142
  'original_text': t_element.text,
143
+ 'element': t_element,
144
+ 'first_format': None,
145
  'source_file': os.path.join("xl", "worksheets", sheet_filename),
146
  'sheet_name': sheet_filename
147
  })
 
149
  print(f"Lỗi xử lý sheet {sheet_filename}: {e}")
150
  import traceback
151
  traceback.print_exc()
152
+ else:
153
+ print(f"Cảnh báo: Không tìm thấy thư mục worksheets: {worksheets_folder}")
154
+
155
 
156
+ # --- Xử lý các file drawingX.xml (Text Boxes, Shapes with Text) ---
157
+ if os.path.isdir(drawings_folder):
158
+ for drawing_filename in sorted(os.listdir(drawings_folder)):
159
+ if drawing_filename.lower().endswith(".xml"):
160
+ drawing_file_path = os.path.join(drawings_folder, drawing_filename)
161
+ try:
162
+ register_namespaces(drawing_file_path) # Đảm bảo register_namespaces được gọi
163
+ drawing_tree = ET.parse(drawing_file_path)
164
+ drawing_trees[drawing_filename] = drawing_tree
165
+ root_drawing = drawing_tree.getroot()
166
+
167
+ # TextBoxes và Shapes có text thường nằm trong <xdr:sp> (shape) -> <xdr:txBody> (text body)
168
+ # Bên trong <xdr:txBody> là các <a:p> (paragraph)
169
+ for p_element in root_drawing.findall('.//xdr:txBody/a:p', {**NS_DRAWING, **NS_A}):
170
+ text_parts = []
171
+ # Lấy text từ tất cả <a:t> trong paragraph này
172
+ t_elements = p_element.findall('.//a:t', NS_A)
173
+
174
+ first_r = p_element.find('./a:r', NS_A) # Tìm <a:r> con trực tiếp đầu tiên của <a:p>
175
+ first_rpr_clone = None # Định dạng của run đầu tiên trong paragraph
176
+
177
+ is_rich_text_paragraph = first_r is not None # Coi là rich nếu có <a:r>
178
+
179
+ if is_rich_text_paragraph:
180
+ # Tìm <a:rPr> bên trong <a:r> đầu tiên của <a:p>
181
+ first_rpr = first_r.find('./a:rPr', NS_A)
182
+ if first_rpr is not None:
183
+ first_rpr_clone = copy.deepcopy(first_rpr)
184
+
185
+ for t_node in t_elements:
186
+ if t_node.text:
187
+ text_parts.append(t_node.text)
188
+ full_text = "".join(text_parts)
189
+
190
+ if not full_text or full_text.isspace(): continue
191
+
192
+ # Lưu node là <a:p> vì chúng ta sẽ thay thế toàn bộ nội dung của nó
193
+ # (các <a:r> và <a:t> bên trong)
194
+ modifiable_nodes.append({
195
+ 'type': 'drawing_text', # Loại mới cho text trong drawing
196
+ 'original_text': full_text,
197
+ 'element': p_element, # Tham chiếu đến <a:p>
198
+ 'first_format': first_rpr_clone, # Lưu định dạng <a:rPr> của <a:r> đầu tiên (hoặc None)
199
+ 'source_file': os.path.join("xl", "drawings", drawing_filename),
200
+ 'sheet_name': None # Có thể tìm cách liên kết ngược lại sheet nếu cần
201
+ })
202
+ except Exception as e:
203
+ print(f"Lỗi xử lý drawing {drawing_filename}: {e}")
204
+ import traceback
205
+ traceback.print_exc()
206
  else:
207
+ print(f"Thông tin: Không tìm thấy thư mục drawings: {drawings_folder}")
208
 
209
 
210
+ global_data = {
211
+ "shared_tree": shared_tree,
212
+ "sheet_trees": sheet_trees,
213
+ "drawing_trees": drawing_trees, # Thêm dòng này
214
+ "shared_strings_path": shared_strings_path,
215
+ "worksheets_folder": worksheets_folder,
216
+ "drawings_folder": drawings_folder # Thêm dòng này
217
+ }
218
+ return modifiable_nodes, global_data\
219
 
220
  def apply_and_save_changes(modified_nodes_data: List[Dict[str, Any]], global_data: Dict[str, Any]) -> bool:
221
  """
222
+ Cập nhật text, giữ lại định dạng đầu tiên cho Rich Text / Drawing Text, và lưu file XML.
223
  """
224
  if not global_data: print("Lỗi: Thiếu global_data."); return False
225
 
226
  updated_files = set()
227
+ try:
228
+ ET.register_namespace('xml', "http://www.w3.org/XML/1998/namespace")
229
+ # Đảm bảo các namespace chính được đăng ký trước khi thao tác
230
+ ET.register_namespace('', NS_MAIN['main']) # Default cho spreadsheet
231
+ ET.register_namespace('main', NS_MAIN['main']) # Hoặc với prefix 'main'
232
+ ET.register_namespace('xdr', NS_DRAWING['xdr'])
233
+ ET.register_namespace('a', NS_A['a'])
234
+ except ValueError: # Có thể đã được đăng ký
235
+ pass
236
+
237
 
238
  for node_info in modified_nodes_data:
239
  if 'modified_text' in node_info and node_info['element'] is not None:
 
241
  modified_text = node_info['modified_text']
242
  original_text = node_info.get('original_text', '')
243
  node_type = node_info.get('type', '')
244
+ first_format = node_info.get('first_format')
245
 
246
  if original_text != modified_text:
247
+ # --- Xử lý Rich Text (sharedStrings): Tạo lại cấu trúc <si><r>[<rPr>]<t></r></si> ---
248
  if node_type == 'shared_rich':
249
  si_element = element
250
+ for child in list(si_element): # Xóa con cũ của <si>
 
251
  si_element.remove(child)
252
 
 
253
  new_r = ET.Element(f"{{{NS_MAIN['main']}}}r")
 
 
254
  if first_format is not None:
255
+ new_r.append(first_format)
256
 
 
257
  new_t = ET.Element(f"{{{NS_MAIN['main']}}}t")
258
  new_t.text = modified_text
259
+ new_t.set('{http://www.w3.org/XML/1998/namespace}space', 'preserve')
 
 
 
260
  new_r.append(new_t)
 
261
  si_element.append(new_r)
 
262
  updated_files.add(node_info['source_file'])
 
263
 
264
+ # --- Xử lý Simple Text (sharedStrings) hoặc Inline Text: Cập nhật thẻ <t> ---
265
  elif node_type in ['shared_simple', 'inline']:
266
+ t_element = element # element ở đây là thẻ <t>
267
  t_element.text = modified_text
268
+ t_element.set('{http://www.w3.org/XML/1998/namespace}space', 'preserve')
269
+ updated_files.add(node_info['source_file'])
270
+
271
+ # --- Xử lý Text trong Drawing (TextBoxes, Shapes): Tạo lại cấu trúc <a:p><a:r>[<a:rPr>]<a:t></a:t></a:r></a:p> ---
272
+ elif node_type == 'drawing_text':
273
+ p_element = element # element ở đây là thẻ <a:p>
274
+ for child in list(p_element): # Xóa con cũ của <a:p> (thường là các <a:r> hoặc <a:endParaRPr>)
275
+ p_element.remove(child)
276
+
277
+ # Tạo run mới <a:r>
278
+ new_r = ET.Element(f"{{{NS_A['a']}}}r")
279
+
280
+ # Nếu có định dạng <a:rPr> đã lưu, thêm nó vào <a:r> mới
281
+ if first_format is not None: # first_format ở đây là <a:rPr>
282
+ new_r.append(first_format)
283
+
284
+ # Tạo thẻ text mới <a:t>
285
+ new_t = ET.Element(f"{{{NS_A['a']}}}t")
286
+ new_t.text = modified_text
287
+ # Trong DrawingML, xml:space="preserve" thường không cần thiết cho <a:t>
288
+ # vì việc xuống dòng được kiểm soát bởi <a:br> hoặc các paragraph <a:p> riêng biệt.
289
+ # Tuy nhiên, việc thêm nó không gây hại.
290
+
291
+ new_r.append(new_t) # Thêm <a:t> vào <a:r>
292
+ p_element.append(new_r) # Thêm <a:r> vào <a:p>
293
+
294
+ # Một số text box có thể có <a:endParaRPr> để định dạng cuối paragraph.
295
+ # Nếu muốn giữ lại, cần logic phức tạp hơn.
296
+ # Hiện tại, chúng ta chỉ tạo lại với một run duy nhất.
297
+
298
  updated_files.add(node_info['source_file'])
299
+ # print(f"Applied first format to Drawing Text in {node_info['source_file']}")
300
+
301
  else:
302
+ print(f"Cảnh báo: Loại node không xác định '{node_type}' cho text '{original_text}'")
303
 
304
+ # --- Lưu lại các file XML đã thay đổi ---
305
  success = True
 
306
  shared_tree = global_data.get("shared_tree"); shared_strings_path = global_data.get("shared_strings_path")
307
  sheet_trees = global_data.get("sheet_trees", {}); worksheets_folder = global_data.get("worksheets_folder")
308
+ drawing_trees = global_data.get("drawing_trees", {}); drawings_folder = global_data.get("drawings_folder") # Thêm
309
 
310
  shared_strings_relative_path = os.path.join("xl", "sharedStrings.xml")
311
  if shared_tree and shared_strings_path and shared_strings_relative_path in updated_files:
312
  try:
 
313
  shared_tree.write(shared_strings_path, encoding='utf-8', xml_declaration=True)
314
  except Exception as e: print(f"Lỗi lưu {shared_strings_path}: {e}"); success = False
315
 
 
319
  if sheet_relative_path in updated_files:
320
  sheet_file_path = os.path.join(worksheets_folder, sheet_filename)
321
  try:
 
322
  sheet_tree.write(sheet_file_path, encoding='utf-8', xml_declaration=True)
323
  except Exception as e: print(f"Lỗi lưu {sheet_file_path}: {e}"); success = False
324
 
325
+ # Lưu các file drawing đã thay đổi
326
+ if drawings_folder and os.path.exists(drawings_folder):
327
+ for drawing_filename, drawing_tree in drawing_trees.items():
328
+ drawing_relative_path = os.path.join("xl", "drawings", drawing_filename)
329
+ if drawing_relative_path in updated_files:
330
+ drawing_file_path = os.path.join(drawings_folder, drawing_filename)
331
+ try:
332
+ # Đảm bảo namespaces được đăng ký đúng cách TRƯỚC KHI GHI
333
+ # register_namespaces(drawing_file_path) # Có thể không cần nếu đã làm ở extract
334
+ # Hoặc đăng ký cứng các namespace cần thiết:
335
+ # ET.register_namespace('xdr', NS_DRAWING['xdr'])
336
+ # ET.register_namespace('a', NS_A['a'])
337
+ # (Đã chuyển lên đầu hàm apply_and_save_changes)
338
+
339
+ drawing_tree.write(drawing_file_path, encoding='utf-8', xml_declaration=True)
340
+ except Exception as e: print(f"Lỗi lưu {drawing_file_path}: {e}"); success = False
341
+
342
+
343
+ if success and updated_files: print(f"Đã lưu thành công {len(updated_files)} file XML đã sửa đổi.")
344
+ elif not updated_files: print("Không có file XML nào cần cập nhật.") ; return True # Vẫn coi là success nếu không có gì thay đổi
345
  return success
346
 
347
  def zip_folder_to_excel_file(folder_path, file_name):
 
553
  shutil.rmtree(xml_folder) # Mark folder as 'handled' by zipping
554
  else:
555
  print("LỖI NGHIÊM TRỌNG: Không thể tạo file XLSX đã dịch cuối cùng.")
556
+ return final_id
 
 
 
pages/upload.py CHANGED
@@ -75,11 +75,11 @@ with st.container():
75
 
76
  with col1:
77
  st.markdown('<p style="font-size:16px; font-weight:bold; margin-bottom:4px;">🌐 Ngôn ngữ của tài liệu</p>', unsafe_allow_html=True)
78
- source_lang = st.selectbox(" ", ["english", "vietnamese"], key="source_lang")
79
 
80
  with col2:
81
  st.markdown('<p style="font-size:16px; font-weight:bold; margin-bottom:4px;">🌐 Ngôn ngữ muốn dịch sang</p>', unsafe_allow_html=True)
82
- target_lang = st.selectbox(" ", ["english", "vietnamese"], key="target_lang")
83
 
84
  def process_file(file, file_type):
85
  progress_bar = st.progress(0)
 
75
 
76
  with col1:
77
  st.markdown('<p style="font-size:16px; font-weight:bold; margin-bottom:4px;">🌐 Ngôn ngữ của tài liệu</p>', unsafe_allow_html=True)
78
+ source_lang = st.selectbox(" ", ["chinese", "english", "vietnamese"], key="source_lang")
79
 
80
  with col2:
81
  st.markdown('<p style="font-size:16px; font-weight:bold; margin-bottom:4px;">🌐 Ngôn ngữ muốn dịch sang</p>', unsafe_allow_html=True)
82
+ target_lang = st.selectbox(" ", ["chinese", "english", "vietnamese"], key="target_lang")
83
 
84
  def process_file(file, file_type):
85
  progress_bar = st.progress(0)