Skip to content

Commit 2990211

Browse files
committed
refactor: improve file handling and path resolution in zip processing
--bug=1071166@tapd-62980211 --user=刘瑞斌 【知识库】上传包含视频文件的文档(zip),视频文件没有上传 https://www.tapd.cn/62980211/s/1963968
1 parent f60b2ca commit 2990211

2 files changed

Lines changed: 171 additions & 145 deletions

File tree

Lines changed: 82 additions & 85 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
11
# coding=utf-8
22
"""
3-
@project: maxkb
4-
@Author:虎
5-
@file: text_split_handle.py
6-
@date:2024/3/27 18:19
7-
@desc:
3+
@project: maxkb
4+
@Author:虎
5+
@file: text_split_handle.py
6+
@date:2024/3/27 18:19
7+
@desc:
88
"""
9+
910
import io
1011
import os
1112
import re
@@ -16,6 +17,7 @@
1617
import uuid_utils.compat as uuid
1718
from charset_normalizer import detect
1819
from django.utils.translation import gettext_lazy as _
20+
from knowledge.models import File
1921

2022
from common.handle.base_split_handle import BaseSplitHandle
2123
from common.handle.impl.text.csv_split_handle import CsvSplitHandle
@@ -25,8 +27,7 @@
2527
from common.handle.impl.text.text_split_handle import TextSplitHandle
2628
from common.handle.impl.text.xls_split_handle import XlsSplitHandle
2729
from common.handle.impl.text.xlsx_split_handle import XlsxSplitHandle
28-
from common.utils.common import parse_md_image
29-
from knowledge.models import File
30+
from common.utils.common import parse_md_file_link, parse_md_image
3031

3132

3233
class FileBufferHandle:
@@ -46,7 +47,7 @@ def get_buffer(self, file):
4647
XlsxSplitHandle(),
4748
XlsSplitHandle(),
4849
CsvSplitHandle(),
49-
default_split_handle
50+
default_split_handle,
5051
]
5152

5253

@@ -55,7 +56,7 @@ def file_to_paragraph(file, pattern_list: List, with_filter: bool, limit: int, s
5556
for split_handle in split_handles:
5657
if split_handle.support(file, get_buffer):
5758
return split_handle.handle(file, pattern_list, with_filter, limit, get_buffer, save_inner_image)
58-
raise Exception(_('Unsupported file format'))
59+
raise Exception(_("Unsupported file format"))
5960

6061

6162
def is_valid_uuid(uuid_str: str):
@@ -66,102 +67,97 @@ def is_valid_uuid(uuid_str: str):
6667
return True
6768

6869

70+
def _collect_file_refs(tokens: list, base_name: str, zip_files: List[str], content: str, update_content):
71+
"""
72+
Process a list of markdown/HTML tokens (image or file-link syntax), resolve paths against
73+
zip_files, and return (file_list, updated_content). update_content is a callable(old, new)
74+
used to patch paths in the paragraph text.
75+
"""
76+
file_list = []
77+
for token in tokens:
78+
# For HTML src tags extract the src value; for markdown extract the (...) part
79+
src_match = re.search(r'\bsrc=["\']([^"\']+)["\']', token)
80+
paren_match = re.search(r"\(([^)]*)\)", token)
81+
if src_match:
82+
source_path = src_match.group(1).strip()
83+
elif paren_match:
84+
source_path = paren_match.group(1).strip().split(" ")[0]
85+
else:
86+
continue
87+
new_id = str(uuid.uuid7())
88+
file_path = urljoin(base_name, "." + source_path if source_path.startswith("/") else source_path)
89+
if file_path not in zip_files:
90+
continue
91+
if file_path.startswith("oss/file/") or file_path.startswith("oss/image/"):
92+
file_id = file_path.replace("oss/file/", "").replace("oss/image/", "")
93+
if is_valid_uuid(file_id):
94+
file_list.append({"source_file": file_path, "image_id": file_id})
95+
else:
96+
file_list.append({"source_file": file_path, "image_id": new_id})
97+
content = update_content(content, source_path, f"./oss/file/{new_id}")
98+
else:
99+
file_list.append({"source_file": file_path, "image_id": new_id})
100+
content = update_content(content, source_path, f"./oss/file/{new_id}")
101+
return file_list, content
102+
103+
69104
def get_image_list(result_list: list, zip_files: List[str]):
70105
image_file_list = []
71106
for result in result_list:
72-
for p in result.get('content', []):
73-
content: str = p.get('content', '')
74-
image_list = parse_md_image(content)
75-
for image in image_list:
76-
search = re.search("\(.*\)", image)
77-
if search:
78-
new_image_id = str(uuid.uuid7())
79-
source_image_path = search.group().replace('(', '').replace(')', '')
80-
source_image_path = source_image_path.strip().split(" ")[0]
81-
image_path = urljoin(result.get('name'), '.' + source_image_path if source_image_path.startswith(
82-
'/') else source_image_path)
83-
if not zip_files.__contains__(image_path):
84-
continue
85-
if image_path.startswith('oss/file/') or image_path.startswith('oss/image/'):
86-
image_id = image_path.replace('oss/file/', '').replace('oss/image/', '')
87-
if is_valid_uuid(image_id):
88-
image_file_list.append({'source_file': image_path,
89-
'image_id': image_id})
90-
else:
91-
image_file_list.append({'source_file': image_path,
92-
'image_id': new_image_id})
93-
content = content.replace(source_image_path, f'./oss/file/{new_image_id}')
94-
p['content'] = content
95-
else:
96-
image_file_list.append({'source_file': image_path,
97-
'image_id': new_image_id})
98-
content = content.replace(source_image_path, f'./oss/file/{new_image_id}')
99-
p['content'] = content
107+
for p in result.get("content", []):
108+
content: str = p.get("content", "")
109+
tokens = parse_md_image(content) + parse_md_file_link(content)
110+
111+
def _update(c, old, new):
112+
return c.replace(old, new)
113+
114+
refs, content = _collect_file_refs(tokens, result.get("name"), zip_files, content, _update)
115+
image_file_list.extend(refs)
116+
p["content"] = content
100117

101118
return image_file_list
102119

103120

104121
def get_image_list_by_content(name: str, content: str, zip_files: List[str]):
105-
image_file_list = []
106-
image_list = parse_md_image(content)
107-
for image in image_list:
108-
search = re.search("\(.*\)", image)
109-
if search:
110-
new_image_id = str(uuid.uuid7())
111-
source_image_path = search.group().replace('(', '').replace(')', '')
112-
source_image_path = source_image_path.strip().split(" ")[0]
113-
image_path = urljoin(name, '.' + source_image_path if source_image_path.startswith(
114-
'/') else source_image_path)
115-
if not zip_files.__contains__(image_path):
116-
continue
117-
if image_path.startswith('oss/file/') or image_path.startswith('oss/image/'):
118-
image_id = image_path.replace('oss/file/', '').replace('oss/image/', '')
119-
if is_valid_uuid(image_id):
120-
image_file_list.append({'source_file': image_path,
121-
'image_id': image_id})
122-
else:
123-
image_file_list.append({'source_file': image_path,
124-
'image_id': new_image_id})
125-
content = content.replace(source_image_path, f'./oss/file/{new_image_id}')
122+
tokens = parse_md_image(content) + parse_md_file_link(content)
126123

127-
else:
128-
image_file_list.append({'source_file': image_path,
129-
'image_id': new_image_id})
130-
content = content.replace(source_image_path, f'./oss/file/{new_image_id}')
124+
def _update(c, old, new):
125+
return c.replace(old, new)
131126

132-
return image_file_list, content
127+
file_list, content = _collect_file_refs(tokens, name, zip_files, content, _update)
128+
return file_list, content
133129

134130

135131
def get_file_name(file_name):
136132
try:
137-
file_name_code = file_name.encode('cp437')
138-
charset = detect(file_name_code)['encoding']
133+
file_name_code = file_name.encode("cp437")
134+
charset = detect(file_name_code)["encoding"]
139135
return file_name_code.decode(charset)
140136
except Exception as e:
141137
return file_name
142138

143139

144140
def filter_image_file(result_list: list, image_list):
145-
image_source_file_list = [image.get('source_file') for image in image_list]
146-
return [r for r in result_list if not image_source_file_list.__contains__(r.get('name', ''))]
141+
image_source_file_list = [image.get("source_file") for image in image_list]
142+
return [r for r in result_list if not image_source_file_list.__contains__(r.get("name", ""))]
147143

148144

149145
class ZipSplitHandle(BaseSplitHandle):
150146
def handle(self, file, pattern_list: List, with_filter: bool, limit: int, get_buffer, save_image):
151147
if type(limit) is str:
152148
limit = int(limit)
153149
if type(with_filter) is str:
154-
with_filter = with_filter.lower() == 'true'
150+
with_filter = with_filter.lower() == "true"
155151
buffer = get_buffer(file)
156152
bytes_io = io.BytesIO(buffer)
157153
result = []
158154
# 打开zip文件
159-
with zipfile.ZipFile(bytes_io, 'r') as zip_ref:
155+
with zipfile.ZipFile(bytes_io, "r") as zip_ref:
160156
# 获取压缩包中的文件名列表
161157
files = zip_ref.namelist()
162158
# 读取压缩包中的文件内容
163159
for file in files:
164-
if file.endswith('/') or file.startswith('__MACOSX'):
160+
if file.endswith("/") or file.startswith("__MACOSX"):
165161
continue
166162
with zip_ref.open(file) as f:
167163
# 对文件内容进行处理
@@ -179,11 +175,11 @@ def handle(self, file, pattern_list: List, with_filter: bool, limit: int, get_bu
179175
result = filter_image_file(result, image_list)
180176
image_mode_list = []
181177
for image in image_list:
182-
with zip_ref.open(image.get('source_file')) as f:
178+
with zip_ref.open(image.get("source_file")) as f:
183179
i = File(
184-
id=image.get('image_id'),
185-
file_name=os.path.basename(image.get('source_file')),
186-
meta={'debug': False, 'content': f.read()} # 这里的content是二进制数据
180+
id=image.get("image_id"),
181+
file_name=os.path.basename(image.get("source_file")),
182+
meta={"debug": False, "content": f.read()}, # 这里的content是二进制数据
187183
)
188184
image_mode_list.append(i)
189185
save_image(image_mode_list)
@@ -200,16 +196,16 @@ def get_content(self, file, save_image):
200196
从 zip 中提取并返回拼接的 md 文本,同时收集并保存内嵌图片(通过 save_image 回调)。
201197
使用 posixpath 来正确处理 zip 内部的路径拼接与规范化。
202198
"""
203-
buffer = file.read() if hasattr(file, 'read') else None
199+
buffer = file.read() if hasattr(file, "read") else None
204200
bytes_io = io.BytesIO(buffer) if buffer is not None else io.BytesIO(file)
205201
image_list = []
206202
content_parts = []
207203

208-
with zipfile.ZipFile(bytes_io, 'r') as zip_ref:
204+
with zipfile.ZipFile(bytes_io, "r") as zip_ref:
209205
files = zip_ref.namelist()
210206
file_content_list = []
211207
for inner_name in files:
212-
if inner_name.endswith('/') or inner_name.startswith('__MACOSX'):
208+
if inner_name.endswith("/") or inner_name.startswith("__MACOSX"):
213209
continue
214210
with zip_ref.open(inner_name) as zf:
215211
try:
@@ -223,11 +219,12 @@ def get_content(self, file, save_image):
223219
if split_handle.support(zf, get_buffer):
224220
row = get_buffer(zf)
225221
md_text = split_handle.get_content(io.BytesIO(row), save_image)
226-
file_content_list.append({'content': md_text, 'name': real_name})
222+
file_content_list.append({"content": md_text, "name": real_name})
227223
break
228224
for file_content in file_content_list:
229-
_image_list, content = get_image_list_by_content(file_content.get('name'), file_content.get("content"),
230-
files)
225+
_image_list, content = get_image_list_by_content(
226+
file_content.get("name"), file_content.get("content"), files
227+
)
231228
content_parts.append(content)
232229
for image in _image_list:
233230
image_list.append(image)
@@ -236,13 +233,13 @@ def get_content(self, file, save_image):
236233
if image_list:
237234
image_mode_list = []
238235
for image in image_list:
239-
with zip_ref.open(image.get('source_file')) as f:
236+
with zip_ref.open(image.get("source_file")) as f:
240237
i = File(
241-
id=image.get('image_id'),
242-
file_name=os.path.basename(image.get('source_file')),
243-
meta={'debug': False, 'content': f.read()} # 这里的content是二进制数据
238+
id=image.get("image_id"),
239+
file_name=os.path.basename(image.get("source_file")),
240+
meta={"debug": False, "content": f.read()}, # 这里的content是二进制数据
244241
)
245242
image_mode_list.append(i)
246243
save_image(image_mode_list)
247244

248-
return '\n\n'.join(content_parts)
245+
return "\n\n".join(content_parts)

0 commit comments

Comments
 (0)