Spaces:
Running
Running
File size: 16,350 Bytes
0e9ff78 6ae64ab 0e9ff78 6ae64ab 0e9ff78 6ae64ab 0e9ff78 |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 |
# ppt_objects.py
from pptx import Presentation
from pptx.enum.text import PP_ALIGN, MSO_ANCHOR
from pptx.enum.shapes import MSO_SHAPE_TYPE
import xml.etree.ElementTree as ET
from pptx.util import Pt
from pptx.dml.color import RGBColor
import re
import json
from pymongo import MongoClient
from gridfs import GridFS
import json
import xml.etree.ElementTree as ET
from io import BytesIO
def apply_group_properties_recursive(shape, shape_index, parent_element):
"""Recursively applies properties to shapes within groups."""
if shape.shape_type == MSO_SHAPE_TYPE.GROUP:
group_element = parent_element.find(f".//group_element[@shape_index='{shape_index}']")
if group_element is not None:
for i, sub_shape in enumerate(shape.shapes):
apply_group_properties_recursive(sub_shape, i, group_element)
# Apply properties for sub-shapes WITHIN the group, based on their type.
if sub_shape.shape_type == MSO_SHAPE_TYPE.TABLE:
table_element = group_element.find(f".//table_element[@shape_index='{i}']")
if table_element: # Use a shorter name for clarity
props_element = table_element.find("properties")
if props_element is not None and props_element.text:
try:
table_data = json.loads(props_element.text)
apply_table_properties(sub_shape.table, table_data)
except (json.JSONDecodeError, KeyError) as e:
print(f"Error applying table properties (in group): {str(e)}")
elif hasattr(sub_shape, "text_frame") and sub_shape.text_frame:
text_element = group_element.find(f".//text_element[@shape_index='{i}']")
if text_element: # Shorter name
props_element = text_element.find("properties")
if props_element is not None and props_element.text:
try:
shape_data = json.loads(props_element.text)
apply_shape_properties(sub_shape, shape_data)
except (json.JSONDecodeError, KeyError) as e:
print(f"Error applying shape properties (in group): {str(e)}")
def get_alignment_value(alignment_str):
"""Convert alignment string (with extra characters) to PP_ALIGN enum value."""
alignment_map = {
'center': PP_ALIGN.CENTER,
'left': PP_ALIGN.LEFT,
'right': PP_ALIGN.RIGHT,
'justify': PP_ALIGN.JUSTIFY
}
match = re.match(r"([A-Za-z]+)", alignment_str)
return alignment_map.get(match.group(1).lower()) if match else None
def get_vertical_anchor(value):
"""Converts vertical_anchor string to MSO_ANCHOR enum."""
mapping = {
"TOP": MSO_ANCHOR.TOP,
"MIDDLE": MSO_ANCHOR.MIDDLE,
"BOTTOM": MSO_ANCHOR.BOTTOM
}
return mapping.get(value.upper().split()[0], MSO_ANCHOR.TOP)
def get_table_properties(table):
"""Extract complete table properties."""
table_data = {
'rows': len(table.rows),
'cols': len(table.columns),
'cells': []
}
for row in table.rows:
row_data = []
for cell in row.cells:
cell_data = {
'text': cell.text.strip(),
'font_size': None,
'font_name': None,
'alignment': None,
'margin_left': cell.margin_left,
'margin_right': cell.margin_right,
'margin_top': cell.margin_top,
'margin_bottom': cell.margin_bottom,
'vertical_anchor': str(cell.vertical_anchor) if cell.vertical_anchor else None,
'font_color': None
}
if cell.text_frame.paragraphs:
paragraph = cell.text_frame.paragraphs[0]
if paragraph.runs:
run = paragraph.runs[0]
if hasattr(run.font, 'size') and run.font.size is not None:
cell_data['font_size'] = run.font.size.pt
if hasattr(run.font, 'name'):
cell_data['font_name'] = run.font.name
if hasattr(run.font, 'bold'):
cell_data['bold'] = run.font.bold
if hasattr(run.font, 'italic'):
cell_data['italic'] = run.font.italic
if (hasattr(run.font, 'color') and
run.font.color is not None and
hasattr(run.font.color, 'rgb') and
run.font.color.rgb is not None):
cell_data['font_color'] = str(run.font.color.rgb)
if hasattr(paragraph, 'alignment'):
cell_data['alignment'] = f"{paragraph.alignment}" if paragraph.alignment else None
row_data.append(cell_data)
table_data['cells'].append(row_data)
return table_data
def get_shape_properties(shape):
"""Extract all properties from a shape, with detailed debug prints."""
shape_data = {
'text': '',
'font_size': None,
'font_name': None,
'alignment': None,
'width': shape.width,
'height': shape.height,
'left': shape.left,
'top': shape.top,
'bold': None,
'italic': None,
'line_spacing_info': {
'rule': None,
'value': None
},
'space_before': None,
'space_after': None,
'font_color': None
}
if hasattr(shape, "text"):
shape_data['text'] = shape.text.strip()
if hasattr(shape, 'text_frame'):
for paragraph_index, paragraph in enumerate(shape.text_frame.paragraphs):
if paragraph.runs:
run = paragraph.runs[0] # Assuming properties are mostly consistent in the first run
if hasattr(run.font, 'size') and run.font.size is not None:
shape_data['font_size'] = run.font.size.pt
if hasattr(run.font, 'name'):
shape_data['font_name'] = run.font.name
if hasattr(run.font, 'bold'):
shape_data['bold'] = run.font.bold
if hasattr(run.font, 'italic'):
shape_data['italic'] = run.font.italic
if (hasattr(run.font, 'color') and
run.font.color is not None and
hasattr(run.font.color, 'rgb') and
run.font.color.rgb is not None):
shape_data['font_color'] = str(run.font.color.rgb)
if hasattr(paragraph, 'alignment') and paragraph.alignment is not None:
shape_data['alignment'] = str(paragraph.alignment).split('.')[-1]
if hasattr(paragraph, 'space_before'):
shape_data['space_before'] = paragraph.space_before.pt if paragraph.space_before else None
if hasattr(paragraph, 'space_after'):
shape_data['space_after'] = paragraph.space_after.pt if paragraph.space_after else None
if hasattr(paragraph, 'line_spacing') and paragraph.line_spacing:
line_spacing = paragraph.line_spacing
# Nếu line_spacing là một số lớn (ví dụ: 84.99 pt), có thể là EXACTLY
if isinstance(line_spacing, Pt) or line_spacing > 10:
line_spacing_rule = "EXACTLY"
elif isinstance(line_spacing, float):
line_spacing_rule = "MULTIPLE"
else:
line_spacing_rule = "UNKNOWN"
shape_data['line_spacing_info'] = {
'rule': line_spacing_rule,
'value': line_spacing if isinstance(line_spacing, float) else None
}
return shape_data
def apply_shape_properties(shape, shape_data):
"""Apply saved properties to a shape."""
try:
shape.width = shape_data['width']
shape.height = shape_data['height']
shape.left = shape_data['left']
shape.top = shape_data['top']
shape.text = ""
paragraph = shape.text_frame.paragraphs[0]
run = paragraph.add_run()
run.text = shape_data['text']
if shape_data['font_size']:
adjusted_size = shape_data['font_size'] * 0.9
run.font.size = Pt(adjusted_size)
if shape_data.get('font_name'):
run.font.name = shape_data['font_name']
else:
run.font.name = "Arial"
if shape_data.get('font_color'):
run.font.color.rgb = RGBColor.from_string(shape_data['font_color'])
if shape_data['bold'] is not None:
run.font.bold = shape_data['bold']
if shape_data['italic'] is not None:
run.font.italic = shape_data['italic']
if shape_data['alignment']:
paragraph.alignment = get_alignment_value(shape_data['alignment'])
line_spacing_info = shape_data.get('line_spacing_info', {})
line_spacing_rule = line_spacing_info.get('rule')
line_spacing_value = line_spacing_info.get('value')
if line_spacing_rule and line_spacing_value is not None:
if line_spacing_rule == "EXACTLY":
paragraph.line_spacing = Pt(line_spacing_value)
elif line_spacing_rule == "AT_LEAST":
paragraph.line_spacing = Pt(line_spacing_value)
elif line_spacing_rule == "MULTIPLE":
paragraph.line_spacing = line_spacing_value
else:
print(f"⚠️ Unknown line spacing rule: {line_spacing_rule}")
if shape_data['space_before']:
paragraph.space_before = shape_data['space_before']
if shape_data['space_after']:
paragraph.space_after = shape_data['space_after']
except Exception as e:
print(f"Error applying shape properties: {str(e)}")
def apply_table_properties(table, table_data):
"""Áp dụng các thuộc tính đã lưu vào bảng PowerPoint."""
for row_idx, row in enumerate(table.rows):
for col_idx, cell in enumerate(row.cells):
try:
cell_data = table_data['cells'][row_idx][col_idx]
# Áp dụng margin
cell.margin_left = cell_data.get('margin_left', 0)
cell.margin_right = cell_data.get('margin_right', 0)
cell.margin_top = cell_data.get('margin_top', 0)
cell.margin_bottom = cell_data.get('margin_bottom', 0)
# Áp dụng vertical_anchor (tránh dùng eval)
if 'vertical_anchor' in cell_data:
cell.vertical_anchor = get_vertical_anchor(cell_data['vertical_anchor'])
# Xóa nội dung cũ và thiết lập văn bản mới
cell.text = ""
paragraph = cell.text_frame.paragraphs[0]
run = paragraph.add_run()
run.text = cell_data.get('text', "")
# Thiết lập kích thước font
if 'font_size' in cell_data:
adjusted_size = cell_data['font_size'] * 0.9 # Giữ tỉ lệ font
run.font.size = Pt(adjusted_size)
# Thiết lập font chữ
run.font.name = cell_data.get('font_name', 'Arial')
# Màu chữ
if 'font_color' in cell_data:
run.font.color.rgb = RGBColor.from_string(cell_data['font_color'])
# In đậm & in nghiêng
run.font.bold = cell_data.get('bold', False)
run.font.italic = cell_data.get('italic', False)
# Căn lề văn bản
if 'alignment' in cell_data:
paragraph.alignment = get_alignment_value(cell_data['alignment'])
except Exception as e:
print(f"Lỗi khi thiết lập thuộc tính ô [{row_idx}, {col_idx}]: {str(e)}")
def get_file_from_mongodb(db_name, collection_name, file_id):
"""Tải tệp từ MongoDB GridFS"""
client = MongoClient("mongodb+srv://admin:[email protected]/?retryWrites=true&w=majority&appName=Cluster0")
db = client[db_name]
fs = GridFS(db, collection_name)
file_data = fs.get(file_id)
return BytesIO(file_data.read())
def save_file_to_mongodb(db_name, collection_name, file_name, file_data):
"""Lưu tệp vào MongoDB GridFS"""
client = MongoClient("mongodb+srv://admin:[email protected]/?retryWrites=true&w=majority&appName=Cluster0")
db = client[db_name]
fs = GridFS(db, collection_name)
file_id = fs.put(file_data, filename=file_name)
return file_id
def create_translated_ppt(db_name, original_ppt_id, translated_xml_id, output_collection):
"""Tạo PowerPoint dịch từ MongoDB và lưu vào MongoDB"""
try:
# Kết nối MongoDB và tải file
original_ppt_io = get_file_from_mongodb(db_name, "root_file", original_ppt_id)
translated_xml_io = get_file_from_mongodb(db_name, "final_xml", translated_xml_id)
# Load PowerPoint gốc và XML dịch
prs = Presentation(original_ppt_io)
tree = ET.parse(translated_xml_io)
root = tree.getroot()
# Áp dụng bản dịch
for slide_number, slide in enumerate(prs.slides, 1):
xml_slide = root.find(f".//slide[@number='{slide_number}']")
if xml_slide is None:
continue
for shape_index, shape in enumerate(slide.shapes):
if shape.shape_type == MSO_SHAPE_TYPE.GROUP:
apply_group_properties_recursive(shape, shape_index, xml_slide)
elif shape.shape_type == MSO_SHAPE_TYPE.TABLE:
table_element = xml_slide.find(f".//table_element[@shape_index='{shape_index}']")
if table_element is not None:
props_element = table_element.find("properties")
if props_element is not None and props_element.text:
try:
table_data = json.loads(props_element.text)
apply_table_properties(shape.table, table_data)
except Exception as e:
print(f"Error applying table properties: {str(e)}")
elif hasattr(shape, "text"):
text_element = xml_slide.find(f".//text_element[@shape_index='{shape_index}']")
if text_element is not None:
props_element = text_element.find("properties")
if props_element is not None and props_element.text:
try:
shape_data = json.loads(props_element.text)
apply_shape_properties(shape, shape_data)
except Exception as e:
print(f"Error applying shape properties: {str(e)}")
# Lưu PowerPoint vào MongoDB
output_io = BytesIO()
prs.save(output_io)
output_io.seek(0) # Reset vị trí đọc
file_id = save_file_to_mongodb(db_name, output_collection, "translated_presentation.pptx", output_io)
print(f"Translated PowerPoint saved to MongoDB with ID: {file_id}")
return file_id
except Exception as e:
print(f"Error creating translated PowerPoint: {str(e)}")
return None
def save_file_to_mongodb(db_name, collection_name, file_name, file_data):
"""Lưu tệp vào MongoDB GridFS"""
client = MongoClient("mongodb+srv://admin:[email protected]/?retryWrites=true&w=majority&appName=Cluster0")
db = client[db_name]
fs = GridFS(db, collection_name)
file_id = fs.put(file_data, filename=file_name)
return file_id |