Spaces:
Running
Running
change api
Browse files- .env +1 -1
- test.ipynb +377 -4
.env
CHANGED
@@ -1,2 +1,2 @@
|
|
1 |
-
GEMINI_API_KEY =
|
2 |
MONGODB_URI = mongodb+srv://admin:[email protected]/?retryWrites=true&w=majority&appName=Cluster0
|
|
|
1 |
+
GEMINI_API_KEY = AIzaSyDIPbH7zKoeTS5aKMQuMjzBBiVlWadcmr8
|
2 |
MONGODB_URI = mongodb+srv://admin:[email protected]/?retryWrites=true&w=majority&appName=Cluster0
|
test.ipynb
CHANGED
@@ -252,12 +252,12 @@
|
|
252 |
"output_type": "stream",
|
253 |
"text": [
|
254 |
"✅ Đã xóa 0 file trong collection 'root_file' của db 'word'\n",
|
255 |
-
"✅ Đã xóa
|
256 |
-
"✅ Đã xóa
|
257 |
"✅ Đã xóa 0 file trong collection 'root_file' của db 'csv'\n",
|
258 |
"✅ Đã xóa 0 file trong collection 'final_file' của db 'word'\n",
|
259 |
-
"✅ Đã xóa
|
260 |
-
"✅ Đã xóa
|
261 |
"✅ Đã xóa 0 file trong collection 'final_file' của db 'csv'\n"
|
262 |
]
|
263 |
}
|
@@ -864,6 +864,379 @@
|
|
864 |
"str = 'Kiểm tra ngoại quan giá đỡ'\n",
|
865 |
"len(str)"
|
866 |
]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
867 |
}
|
868 |
],
|
869 |
"metadata": {
|
|
|
252 |
"output_type": "stream",
|
253 |
"text": [
|
254 |
"✅ Đã xóa 0 file trong collection 'root_file' của db 'word'\n",
|
255 |
+
"✅ Đã xóa 8 file trong collection 'root_file' của db 'excel'\n",
|
256 |
+
"✅ Đã xóa 0 file trong collection 'root_file' của db 'pptx'\n",
|
257 |
"✅ Đã xóa 0 file trong collection 'root_file' của db 'csv'\n",
|
258 |
"✅ Đã xóa 0 file trong collection 'final_file' của db 'word'\n",
|
259 |
+
"✅ Đã xóa 7 file trong collection 'final_file' của db 'excel'\n",
|
260 |
+
"✅ Đã xóa 0 file trong collection 'final_file' của db 'pptx'\n",
|
261 |
"✅ Đã xóa 0 file trong collection 'final_file' của db 'csv'\n"
|
262 |
]
|
263 |
}
|
|
|
864 |
"str = 'Kiểm tra ngoại quan giá đỡ'\n",
|
865 |
"len(str)"
|
866 |
]
|
867 |
+
},
|
868 |
+
{
|
869 |
+
"cell_type": "code",
|
870 |
+
"execution_count": null,
|
871 |
+
"metadata": {},
|
872 |
+
"outputs": [],
|
873 |
+
"source": [
|
874 |
+
"import os\n",
|
875 |
+
"import zipfile\n",
|
876 |
+
"import copy\n",
|
877 |
+
"import time\n",
|
878 |
+
"import xml.etree.ElementTree as ET\n",
|
879 |
+
"from typing import List, Dict, Any, Optional, Tuple\n",
|
880 |
+
"from utils.utils import translate_text, unzip_office_file, preprocess_text, postprocess_text, translate_single_text\n",
|
881 |
+
"from pymongo import MongoClient\n",
|
882 |
+
"import gridfs\n",
|
883 |
+
"from io import BytesIO\n",
|
884 |
+
"import shutil\n",
|
885 |
+
"import io\n",
|
886 |
+
"import re\n",
|
887 |
+
"from typing import Dict\n",
|
888 |
+
"\n",
|
889 |
+
"\n",
|
890 |
+
"NS_MAIN = {'main': 'http://schemas.openxmlformats.org/spreadsheetml/2006/main'}\n",
|
891 |
+
"NS_DRAWING = {'xdr': \"http://schemas.openxmlformats.org/drawingml/2006/spreadsheetDrawing\"}\n",
|
892 |
+
"NS_A = {'a': \"http://schemas.openxmlformats.org/drawingml/2006/main\"}\n",
|
893 |
+
"\n",
|
894 |
+
"# --- Hàm đăng ký namespace (quan trọng khi ghi file) ---\n",
|
895 |
+
"def register_namespaces(xml_file):\n",
|
896 |
+
" \"\"\"Đọc và đăng ký các namespace từ file XML.\"\"\"\n",
|
897 |
+
" namespaces = dict([\n",
|
898 |
+
" node for _, node in ET.iterparse(xml_file, events=['start-ns'])\n",
|
899 |
+
" ])\n",
|
900 |
+
" for ns, uri in namespaces.items():\n",
|
901 |
+
" ET.register_namespace(ns, uri)\n",
|
902 |
+
"\n",
|
903 |
+
" # Đăng ký thêm namespace phổ biến nếu chưa có\n",
|
904 |
+
" if 'main' not in namespaces and '' not in namespaces and NS_MAIN['main'] not in namespaces.values():\n",
|
905 |
+
" ET.register_namespace('', NS_MAIN['main'])\n",
|
906 |
+
" elif 'main' not in namespaces and NS_MAIN['main'] not in namespaces.values():\n",
|
907 |
+
" ET.register_namespace('main', NS_MAIN['main'])\n",
|
908 |
+
"\n",
|
909 |
+
" # Đăng ký namespaces cho drawing nếu cần\n",
|
910 |
+
" if 'xdr' not in namespaces and NS_DRAWING['xdr'] not in namespaces.values():\n",
|
911 |
+
" ET.register_namespace('xdr', NS_DRAWING['xdr'])\n",
|
912 |
+
" if 'a' not in namespaces and NS_A['a'] not in namespaces.values():\n",
|
913 |
+
" ET.register_namespace('a', NS_A['a'])\n",
|
914 |
+
"\n",
|
915 |
+
"\n",
|
916 |
+
"def extract_text_from_sheet(unzipped_folder_path: str) -> Optional[Tuple[List[Dict[str, Any]], Dict[str, Any]]]:\n",
|
917 |
+
" \"\"\"\n",
|
918 |
+
" Trích xuất text, lưu lại định dạng của run đầu tiên nếu là Rich Text,\n",
|
919 |
+
" bao gồm cả text từ TextBoxes trong drawings.\n",
|
920 |
+
" \"\"\"\n",
|
921 |
+
" modifiable_nodes = []\n",
|
922 |
+
" shared_strings_path = os.path.join(unzipped_folder_path, \"xl\", \"sharedStrings.xml\")\n",
|
923 |
+
" worksheets_folder = os.path.join(unzipped_folder_path, \"xl\", \"worksheets\")\n",
|
924 |
+
" drawings_folder = os.path.join(unzipped_folder_path, \"xl\", \"drawings\") # Thêm dòng này\n",
|
925 |
+
"\n",
|
926 |
+
" shared_tree = None\n",
|
927 |
+
" sheet_trees = {}\n",
|
928 |
+
" drawing_trees = {} # Thêm dòng này\n",
|
929 |
+
"\n",
|
930 |
+
" # --- Xử lý sharedStrings.xml ---\n",
|
931 |
+
" if os.path.exists(shared_strings_path):\n",
|
932 |
+
" try:\n",
|
933 |
+
" register_namespaces(shared_strings_path) # Đảm bảo register_namespaces được gọi\n",
|
934 |
+
" shared_tree = ET.parse(shared_strings_path)\n",
|
935 |
+
" root_shared = shared_tree.getroot()\n",
|
936 |
+
"\n",
|
937 |
+
" for si_element in root_shared.findall('main:si', NS_MAIN):\n",
|
938 |
+
" text_parts = []\n",
|
939 |
+
" # Tìm tất cả <t> con, bất kể chúng nằm trong <r> hay không\n",
|
940 |
+
" t_elements = si_element.findall('.//main:t', NS_MAIN)\n",
|
941 |
+
"\n",
|
942 |
+
" first_r = si_element.find('./main:r', NS_MAIN)\n",
|
943 |
+
" first_rpr_clone = None\n",
|
944 |
+
" is_rich_text = first_r is not None # Rich text nếu có ít nhất một <r>\n",
|
945 |
+
"\n",
|
946 |
+
" if is_rich_text:\n",
|
947 |
+
" # Cố gắng tìm <rPr> bên trong <r> đầu tiên\n",
|
948 |
+
" first_rpr_candidate = si_element.find('./main:r/main:rPr', NS_MAIN)\n",
|
949 |
+
" if first_rpr_candidate is not None:\n",
|
950 |
+
" first_rpr_clone = copy.deepcopy(first_rpr_candidate)\n",
|
951 |
+
" else:\n",
|
952 |
+
" # Nếu <r> đầu tiên không có <rPr>, kiểm tra <si><rPh><rPr> (Phonetic properties, ít gặp hơn)\n",
|
953 |
+
" # Hoặc có thể không có định dạng nào cụ thể ở run đầu\n",
|
954 |
+
" pass\n",
|
955 |
+
"\n",
|
956 |
+
"\n",
|
957 |
+
" for t_node in t_elements:\n",
|
958 |
+
" if t_node.text:\n",
|
959 |
+
" text_parts.append(t_node.text)\n",
|
960 |
+
" full_text = \"\".join(text_parts)\n",
|
961 |
+
"\n",
|
962 |
+
" if not full_text or full_text.isspace(): continue\n",
|
963 |
+
"\n",
|
964 |
+
" # Logic xác định type dựa trên sự hiện diện của <r> và <rPr> đã được điều chỉnh\n",
|
965 |
+
" if is_rich_text : # Chỉ cần có <r> là đủ, first_rpr_clone có thể là None\n",
|
966 |
+
" modifiable_nodes.append({\n",
|
967 |
+
" 'type': 'shared_rich',\n",
|
968 |
+
" 'original_text': full_text,\n",
|
969 |
+
" 'element': si_element,\n",
|
970 |
+
" 'first_format': first_rpr_clone, # Sẽ là None nếu <r> đầu không có <rPr>\n",
|
971 |
+
" 'source_file': os.path.join(\"xl\", \"sharedStrings.xml\"),\n",
|
972 |
+
" 'sheet_name': None\n",
|
973 |
+
" })\n",
|
974 |
+
" elif t_elements:\n",
|
975 |
+
" direct_t = si_element.find('./main:t', NS_MAIN)\n",
|
976 |
+
" if direct_t is not None:\n",
|
977 |
+
" modifiable_nodes.append({\n",
|
978 |
+
" 'type': 'shared_simple',\n",
|
979 |
+
" 'original_text': full_text,\n",
|
980 |
+
" 'element': direct_t, # Tham chiếu <t>\n",
|
981 |
+
" 'first_format': None,\n",
|
982 |
+
" 'source_file': os.path.join(\"xl\", \"sharedStrings.xml\"),\n",
|
983 |
+
" 'sheet_name': None\n",
|
984 |
+
" })\n",
|
985 |
+
" # else: ít khả năng xảy ra nếu t_elements có phần tử\n",
|
986 |
+
"\n",
|
987 |
+
" except Exception as e:\n",
|
988 |
+
" print(f\"Lỗi xử lý sharedStrings: {e}\")\n",
|
989 |
+
" import traceback\n",
|
990 |
+
" traceback.print_exc()\n",
|
991 |
+
"\n",
|
992 |
+
"\n",
|
993 |
+
" # --- Xử lý các file sheetX.xml (Inline Strings) ---\n",
|
994 |
+
" if os.path.isdir(worksheets_folder):\n",
|
995 |
+
" for sheet_filename in sorted(os.listdir(worksheets_folder)):\n",
|
996 |
+
" if sheet_filename.lower().endswith(\".xml\"):\n",
|
997 |
+
" sheet_file_path = os.path.join(worksheets_folder, sheet_filename)\n",
|
998 |
+
" try:\n",
|
999 |
+
" register_namespaces(sheet_file_path) # Đảm bảo register_namespaces được gọi\n",
|
1000 |
+
" sheet_tree = ET.parse(sheet_file_path)\n",
|
1001 |
+
" sheet_trees[sheet_filename] = sheet_tree\n",
|
1002 |
+
" root_sheet = sheet_tree.getroot()\n",
|
1003 |
+
" for cell in root_sheet.findall('.//main:c[@t=\"inlineStr\"]', NS_MAIN):\n",
|
1004 |
+
" t_element = cell.find('.//main:is/main:t', NS_MAIN) # Sửa lại tìm kiếm <t>\n",
|
1005 |
+
" if t_element is not None and t_element.text is not None and t_element.text.strip():\n",
|
1006 |
+
" modifiable_nodes.append({\n",
|
1007 |
+
" 'type': 'inline',\n",
|
1008 |
+
" 'original_text': t_element.text,\n",
|
1009 |
+
" 'element': t_element,\n",
|
1010 |
+
" 'first_format': None,\n",
|
1011 |
+
" 'source_file': os.path.join(\"xl\", \"worksheets\", sheet_filename),\n",
|
1012 |
+
" 'sheet_name': sheet_filename\n",
|
1013 |
+
" })\n",
|
1014 |
+
" except Exception as e:\n",
|
1015 |
+
" print(f\"Lỗi xử lý sheet {sheet_filename}: {e}\")\n",
|
1016 |
+
" import traceback\n",
|
1017 |
+
" traceback.print_exc()\n",
|
1018 |
+
" else:\n",
|
1019 |
+
" print(f\"Cảnh báo: Không tìm thấy thư mục worksheets: {worksheets_folder}\")\n",
|
1020 |
+
"\n",
|
1021 |
+
"\n",
|
1022 |
+
" # --- Xử lý các file drawingX.xml (Text Boxes, Shapes with Text) ---\n",
|
1023 |
+
" if os.path.isdir(drawings_folder):\n",
|
1024 |
+
" for drawing_filename in sorted(os.listdir(drawings_folder)):\n",
|
1025 |
+
" if drawing_filename.lower().endswith(\".xml\"):\n",
|
1026 |
+
" drawing_file_path = os.path.join(drawings_folder, drawing_filename)\n",
|
1027 |
+
" try:\n",
|
1028 |
+
" register_namespaces(drawing_file_path) # Đảm bảo register_namespaces được gọi\n",
|
1029 |
+
" drawing_tree = ET.parse(drawing_file_path)\n",
|
1030 |
+
" drawing_trees[drawing_filename] = drawing_tree\n",
|
1031 |
+
" root_drawing = drawing_tree.getroot()\n",
|
1032 |
+
"\n",
|
1033 |
+
" # TextBoxes và Shapes có text thường nằm trong <xdr:sp> (shape) -> <xdr:txBody> (text body)\n",
|
1034 |
+
" # Bên trong <xdr:txBody> là các <a:p> (paragraph)\n",
|
1035 |
+
" for p_element in root_drawing.findall('.//xdr:txBody/a:p', {**NS_DRAWING, **NS_A}):\n",
|
1036 |
+
" text_parts = []\n",
|
1037 |
+
" # Lấy text từ tất cả <a:t> trong paragraph này\n",
|
1038 |
+
" t_elements = p_element.findall('.//a:t', NS_A)\n",
|
1039 |
+
"\n",
|
1040 |
+
" 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>\n",
|
1041 |
+
" first_rpr_clone = None # Định dạng của run đầu tiên trong paragraph\n",
|
1042 |
+
"\n",
|
1043 |
+
" is_rich_text_paragraph = first_r is not None # Coi là rich nếu có <a:r>\n",
|
1044 |
+
"\n",
|
1045 |
+
" if is_rich_text_paragraph:\n",
|
1046 |
+
" # Tìm <a:rPr> bên trong <a:r> đầu tiên của <a:p>\n",
|
1047 |
+
" first_rpr = first_r.find('./a:rPr', NS_A)\n",
|
1048 |
+
" if first_rpr is not None:\n",
|
1049 |
+
" first_rpr_clone = copy.deepcopy(first_rpr)\n",
|
1050 |
+
"\n",
|
1051 |
+
" for t_node in t_elements:\n",
|
1052 |
+
" if t_node.text:\n",
|
1053 |
+
" text_parts.append(t_node.text)\n",
|
1054 |
+
" full_text = \"\".join(text_parts)\n",
|
1055 |
+
"\n",
|
1056 |
+
" if not full_text or full_text.isspace(): continue\n",
|
1057 |
+
"\n",
|
1058 |
+
" # Lưu node là <a:p> vì chúng ta sẽ thay thế toàn bộ nội dung của nó\n",
|
1059 |
+
" # (các <a:r> và <a:t> bên trong)\n",
|
1060 |
+
" modifiable_nodes.append({\n",
|
1061 |
+
" 'type': 'drawing_text', # Loại mới cho text trong drawing\n",
|
1062 |
+
" 'original_text': full_text,\n",
|
1063 |
+
" 'element': p_element, # Tham chiếu đến <a:p>\n",
|
1064 |
+
" 'first_format': first_rpr_clone, # Lưu định dạng <a:rPr> của <a:r> đầu tiên (hoặc None)\n",
|
1065 |
+
" 'source_file': os.path.join(\"xl\", \"drawings\", drawing_filename),\n",
|
1066 |
+
" 'sheet_name': None # Có thể tìm cách liên kết ngược lại sheet nếu cần\n",
|
1067 |
+
" })\n",
|
1068 |
+
" except Exception as e:\n",
|
1069 |
+
" print(f\"Lỗi xử lý drawing {drawing_filename}: {e}\")\n",
|
1070 |
+
" import traceback\n",
|
1071 |
+
" traceback.print_exc()\n",
|
1072 |
+
" else:\n",
|
1073 |
+
" print(f\"Thông tin: Không tìm thấy thư mục drawings: {drawings_folder}\")\n",
|
1074 |
+
"\n",
|
1075 |
+
"\n",
|
1076 |
+
" global_data = {\n",
|
1077 |
+
" \"shared_tree\": shared_tree,\n",
|
1078 |
+
" \"sheet_trees\": sheet_trees,\n",
|
1079 |
+
" \"drawing_trees\": drawing_trees, # Thêm dòng này\n",
|
1080 |
+
" \"shared_strings_path\": shared_strings_path,\n",
|
1081 |
+
" \"worksheets_folder\": worksheets_folder,\n",
|
1082 |
+
" \"drawings_folder\": drawings_folder # Thêm dòng này\n",
|
1083 |
+
" }\n",
|
1084 |
+
" return modifiable_nodes, global_data\\\n",
|
1085 |
+
"\n",
|
1086 |
+
"\n",
|
1087 |
+
"\n",
|
1088 |
+
"def zip_folder_to_excel_file(folder_path, file_name):\n",
|
1089 |
+
" try:\n",
|
1090 |
+
" # Nén thư mục thành file .xlsx trong RAM\n",
|
1091 |
+
" xlsx_buffer = io.BytesIO()\n",
|
1092 |
+
" with zipfile.ZipFile(xlsx_buffer, 'w', zipfile.ZIP_DEFLATED) as zipf:\n",
|
1093 |
+
" for root, _, files in os.walk(folder_path):\n",
|
1094 |
+
" for file in files:\n",
|
1095 |
+
" file_path = os.path.join(root, file)\n",
|
1096 |
+
" archive_path = os.path.relpath(file_path, folder_path)\n",
|
1097 |
+
" zipf.write(file_path, archive_path)\n",
|
1098 |
+
"\n",
|
1099 |
+
" xlsx_buffer.seek(0)\n",
|
1100 |
+
"\n",
|
1101 |
+
" client = MongoClient(\"mongodb+srv://admin:[email protected]/?retryWrites=true&w=majority&appName=Cluster0\")\n",
|
1102 |
+
" db = client['excel'] \n",
|
1103 |
+
" fs = gridfs.GridFS(db, collection='final_file')\n",
|
1104 |
+
"\n",
|
1105 |
+
" file_id = fs.put(xlsx_buffer.read(), filename=file_name)\n",
|
1106 |
+
" print(f\"✅ Đã lưu file Excel vào MongoDB với ID: {file_id}\")\n",
|
1107 |
+
" return file_id\n",
|
1108 |
+
"\n",
|
1109 |
+
" except Exception as e:\n",
|
1110 |
+
" print(f\"❌ Lỗi khi nén và lưu Excel vào MongoDB: {e}\")\n",
|
1111 |
+
" return None\n",
|
1112 |
+
" \n",
|
1113 |
+
"\n",
|
1114 |
+
"\n",
|
1115 |
+
"def translate_xlsx(file_id, file_name, source_lang='en', target_lang='vi', batch_size_segments=50, max_words_per_segment=100, delay_between_requests=1):\n",
|
1116 |
+
" \n",
|
1117 |
+
" client = MongoClient(\"mongodb+srv://admin:[email protected]/?retryWrites=true&w=majority&appName=Cluster0\")\n",
|
1118 |
+
" db = client['excel']\n",
|
1119 |
+
" fs = gridfs.GridFS(db, collection='root_file')\n",
|
1120 |
+
" \n",
|
1121 |
+
" ppt_file = fs.get(file_id)\n",
|
1122 |
+
" excel_file = BytesIO(ppt_file.read())\n",
|
1123 |
+
"\n",
|
1124 |
+
" xml_folder = unzip_office_file(excel_file)\n",
|
1125 |
+
" path_to_workbook_xml = os.path.join(xml_folder, \"xl\", \"workbook.xml\")\n",
|
1126 |
+
" translate_sheet_names_via_regex(path_to_workbook_xml, source_lang, target_lang)\n",
|
1127 |
+
"\n",
|
1128 |
+
" modifiable_nodes, global_data = extract_text_from_sheet(xml_folder)\n",
|
1129 |
+
"\n",
|
1130 |
+
" original_texts = get_text_list_from_nodes(modifiable_nodes)\n",
|
1131 |
+
"\n",
|
1132 |
+
" all_results = [None] * len(original_texts)\n",
|
1133 |
+
" current_index = 0\n",
|
1134 |
+
" processed_count = 0\n",
|
1135 |
+
" api_call_counter = 0 # Track API calls for delay logic\n",
|
1136 |
+
"\n",
|
1137 |
+
" while current_index < len(original_texts):\n",
|
1138 |
+
" batch_texts_to_translate = []\n",
|
1139 |
+
" batch_original_indices = [] # 0-based indices for assignment\n",
|
1140 |
+
" batch_end_index = min(current_index + batch_size_segments, len(original_texts))\n",
|
1141 |
+
" found_long_segment_at = -1 # 0-based index in original_texts\n",
|
1142 |
+
"\n",
|
1143 |
+
" # 1. Build the next potential batch, stopping if a long segment is found\n",
|
1144 |
+
" for i in range(current_index, batch_end_index):\n",
|
1145 |
+
" segment = original_texts[i]\n",
|
1146 |
+
" word_count = count_words(segment)\n",
|
1147 |
+
"\n",
|
1148 |
+
" if word_count <= max_words_per_segment:\n",
|
1149 |
+
" batch_texts_to_translate.append(segment)\n",
|
1150 |
+
" batch_original_indices.append(i)\n",
|
1151 |
+
" else:\n",
|
1152 |
+
" found_long_segment_at = i\n",
|
1153 |
+
" break # Stop building this batch\n",
|
1154 |
+
"\n",
|
1155 |
+
" # --- Process the findings ---\n",
|
1156 |
+
"\n",
|
1157 |
+
" # 2. Translate the VALID batch collected *before* the long segment (if any)\n",
|
1158 |
+
" if batch_texts_to_translate:\n",
|
1159 |
+
" # Add delay BEFORE the API call if it's not the very first call\n",
|
1160 |
+
" if api_call_counter > 0 and delay_between_requests > 0:\n",
|
1161 |
+
" time.sleep(delay_between_requests)\n",
|
1162 |
+
"\n",
|
1163 |
+
" translated_batch = _translate_batch_helper(\n",
|
1164 |
+
" batch_texts_to_translate,\n",
|
1165 |
+
" [idx + 1 for idx in batch_original_indices], # 1-based for logging\n",
|
1166 |
+
" source_lang,\n",
|
1167 |
+
" target_lang\n",
|
1168 |
+
" )\n",
|
1169 |
+
" api_call_counter += 1\n",
|
1170 |
+
" # Assign results back\n",
|
1171 |
+
" for batch_idx, original_idx in enumerate(batch_original_indices):\n",
|
1172 |
+
" all_results[original_idx] = translated_batch[batch_idx]\n",
|
1173 |
+
" processed_count += len(batch_texts_to_translate)\n",
|
1174 |
+
"\n",
|
1175 |
+
" # 3. Handle the long segment INDIVIDUALLY (if one was found)\n",
|
1176 |
+
" if found_long_segment_at != -1:\n",
|
1177 |
+
" long_segment_index = found_long_segment_at\n",
|
1178 |
+
" long_segment_text = str(original_texts[long_segment_index])\n",
|
1179 |
+
" # word_count = count_words(long_segment_text) # Recalculate for log clarity\n",
|
1180 |
+
"\n",
|
1181 |
+
" try:\n",
|
1182 |
+
" translated = translate_single_text(long_segment_text, source_lang, target_lang)\n",
|
1183 |
+
" \n",
|
1184 |
+
" final = [translated]\n",
|
1185 |
+
" api_call_counter += 1\n",
|
1186 |
+
"\n",
|
1187 |
+
" if len(final) == 1:\n",
|
1188 |
+
" all_results[long_segment_index] = final[0]\n",
|
1189 |
+
" else:\n",
|
1190 |
+
" print(f\" *** CRITICAL ERROR: Long segment translation result count mismatch! Expected 1, got {len(final)}. Marking as failed.\")\n",
|
1191 |
+
" all_results[long_segment_index] = \"<translation_length_mismatch_error>\"\n",
|
1192 |
+
"\n",
|
1193 |
+
" except Exception as e:\n",
|
1194 |
+
" print(f\" *** ERROR during translation of long segment {long_segment_index + 1}: {e}. Marking as failed.\")\n",
|
1195 |
+
" # traceback.print_exc() # Uncomment for detailed debug\n",
|
1196 |
+
" all_results[long_segment_index] = \"<translation_api_error>\"\n",
|
1197 |
+
" # Do not increment api_call_counter if the API call itself failed before returning\n",
|
1198 |
+
"\n",
|
1199 |
+
" processed_count += 1\n",
|
1200 |
+
" # Update current_index to start AFTER this long segment\n",
|
1201 |
+
" current_index = long_segment_index + 1\n",
|
1202 |
+
"\n",
|
1203 |
+
" else:\n",
|
1204 |
+
" # No long segment was found in the range checked.\n",
|
1205 |
+
" # Move current_index to the end of the range examined.\n",
|
1206 |
+
" current_index = batch_end_index\n",
|
1207 |
+
"\n",
|
1208 |
+
" missing_count = 0\n",
|
1209 |
+
" final_texts_for_nodes = []\n",
|
1210 |
+
" for i, res in enumerate(all_results):\n",
|
1211 |
+
" if res is None:\n",
|
1212 |
+
" print(f\"LỖI LOGIC: Segment {i+1} không được xử lý! Giữ lại text gốc: '{original_texts[i]}'\")\n",
|
1213 |
+
" final_texts_for_nodes.append(original_texts[i])\n",
|
1214 |
+
" missing_count += 1\n",
|
1215 |
+
" else:\n",
|
1216 |
+
" final_texts_for_nodes.append(res)\n",
|
1217 |
+
"\n",
|
1218 |
+
" if missing_count > 0:\n",
|
1219 |
+
" print(f\"CẢNH BÁO NGHIÊM TRỌNG: {missing_count} segments bị bỏ lỡ trong quá trình xử lý.\")\n",
|
1220 |
+
"\n",
|
1221 |
+
" if len(final_texts_for_nodes) != len(original_texts):\n",
|
1222 |
+
" print(f\"LỖI NGHIÊM TRỌNG: Số lượng text cuối cùng ({len(final_texts_for_nodes)}) không khớp với gốc ({len(original_texts)}). Hủy bỏ cập nhật.\")\n",
|
1223 |
+
" else:\n",
|
1224 |
+
" # Gán vào node\n",
|
1225 |
+
" for i, node_info in enumerate(modifiable_nodes):\n",
|
1226 |
+
" node_info['modified_text'] = final_texts_for_nodes[i]\n",
|
1227 |
+
" \n",
|
1228 |
+
" save_success = apply_and_save_changes(modifiable_nodes, global_data)\n",
|
1229 |
+
" if not save_success:\n",
|
1230 |
+
" print(\"LỖI NGHIÊM TRỌNG: Không thể lưu thay đổi vào file XML.\")\n",
|
1231 |
+
" else:\n",
|
1232 |
+
" # Only zip if saving XML was successful\n",
|
1233 |
+
" final_id = zip_folder_to_excel_file(xml_folder, file_name)\n",
|
1234 |
+
" if final_id:\n",
|
1235 |
+
" shutil.rmtree(xml_folder) # Mark folder as 'handled' by zipping\n",
|
1236 |
+
" else:\n",
|
1237 |
+
" print(\"LỖI NGHIÊM TRỌNG: Không thể tạo file XLSX đã dịch cuối cùng.\")\n",
|
1238 |
+
" return final_id"
|
1239 |
+
]
|
1240 |
}
|
1241 |
],
|
1242 |
"metadata": {
|