from flask import Flask, request, jsonify, Response,send_file,abort import os import json import re from datetime import datetime app = Flask(__name__) # 配置上传文件夹 UPLOAD_FOLDER = 'uploads' if not os.path.exists(UPLOAD_FOLDER): os.makedirs(UPLOAD_FOLDER) app.config['UPLOAD_FOLDER'] = UPLOAD_FOLDER from pptx import Presentation from pptx.util import Inches,Pt from pptx.enum.text import PP_ALIGN,PP_PARAGRAPH_ALIGNMENT from pptx.enum.dml import MSO_THEME_COLOR from lxml import etree import uuid def generate_random_key(): """生成一个 UUID 密钥""" return str(uuid.uuid4()) # 获取当前时间 def getCurrentDate(): # 获取当前日期和时间 current_time = datetime.now() # 格式化输出 formatted_time = current_time.strftime("%Y-%m-%d") return formatted_time # 导出PPT def excelToPPT(data): # 创建一个新的PPT prs = Presentation() slide_width = prs.slide_width # 返回EMU单位 slide_height = prs.slide_height font_list = ["latin", "ea", "cs"] # 设置主题字体 for item in data: order = item['order'] title_name = item['title'] patentNo = item.get('appNo','') or item.get('publicNo','') appDate = item.get('appDate','') publicDate = item.get('publicDate','') appPerson = item.get('appPerson','') files = item.get('files',[]) technical_problem = item.get('technical_problem','') technical_means = item.get('technical_means','') technical_efficacy = item.get('technical_efficacy','') application_field_classification = item.get('application_field_classification','') # 添加一个幻灯片 slide_layout = prs.slide_layouts[5] # 使用一个空白布局 slide = prs.slides.add_slide(slide_layout) # 添加标题 title = slide.shapes.title title.height = Pt(40) title.width = slide_width title.text = f"{order}、{title_name}-{patentNo}" # 设置标题字体 title_frame = title.text_frame title_paragraph = title_frame.paragraphs[0] title_paragraph.alignment = PP_PARAGRAPH_ALIGNMENT.LEFT title_run = title_paragraph.runs[0] # 设置标题字体属性 title_run.font.name = '微软雅黑' title_run.font.size = Pt(20) title_run.font.bold = True r_element = title_run._r rPr = r_element.find('.//a:rPr', namespaces={'a': 'http://schemas.openxmlformats.org/drawingml/2006/main'}) if rPr is not None: for tag in font_list: # 获取或创建 ea 节点 node = rPr.find(f'.//a:{tag}', namespaces={'a': 'http://schemas.openxmlformats.org/drawingml/2006/main'}) if node is None: # 如果 ea 节点不存在,创建一个新的 ea 节点 node = etree.Element('{http://schemas.openxmlformats.org/drawingml/2006/main}'+tag) rPr.append(node) node.set('typeface', '微软雅黑') # 你可以替换为你想要的字体名称 # 更新 _r 属性 title_run._r = r_element # 添加图片 (替换为实际图片路径) if len(files)>0: left = Inches(0.5) top = Inches(1.3) width = Inches(4.5) height = Inches(3) num = 1 for image in files: if num == 1: slide.shapes.add_picture(image, left, top, width, height) else: left_second_image = Inches(5 * (num - 1)) slide.shapes.add_picture(image, left_second_image, top, width, height) num += 1 # 添加文本框 left_text = Inches(0.5) top_text = Inches(4.5) width_text = slide_width - Inches(0.5).emu height_text = Inches(2) text_box = slide.shapes.add_textbox(left_text, top_text, width_text, height_text) text_frame = text_box.text_frame # 关键配置:启用自动换行 text_frame.word_wrap = True text_frame.auto_size = None paragraph_list = [ { 'field':'申请人', 'content':appPerson }, { 'field':'申请日', 'content':appDate }, { 'field':'公开公告日', 'content':publicDate }, { 'field':'解决问题', 'content':technical_problem }, { 'field':'技术手段', 'content':technical_means }, { 'field':'技术效果', 'content':technical_efficacy }, { 'field':'应用领域', 'content':application_field_classification.replace('|','、') } ] for item in paragraph_list: paragraph = text_frame.add_paragraph() paragraph.text = f"{item['field']}:" run = paragraph.runs[0] run.font.bold = True run = paragraph.add_run() run.text = item['content'] for run_item in paragraph.runs: run_item.font.size = Pt(15) run_item.font.name = '微软雅黑' r_element = run_item._r rPr = r_element.find('.//a:rPr', namespaces={'a': 'http://schemas.openxmlformats.org/drawingml/2006/main'}) if rPr is not None: for tag in font_list: # 获取或创建 ea 节点 node = rPr.find(f'.//a:{tag}', namespaces={'a': 'http://schemas.openxmlformats.org/drawingml/2006/main'}) if node is None: # 如果 ea 节点不存在,创建一个新的 ea 节点 node = etree.Element('{http://schemas.openxmlformats.org/drawingml/2006/main}'+tag) rPr.append(node) node.set('typeface', '微软雅黑') # 你可以替换为你想要的字体名称 # 更新 _r 属性 run_item._r = r_element paragraph.alignment = PP_PARAGRAPH_ALIGNMENT.LEFT # 保存PPT uuid = generate_random_key() formatted_time = getCurrentDate() file_name = f"result/{formatted_time}_{uuid}.pptx" prs.save(file_name) return file_name # 读取Excel文件 from openpyxl import load_workbook def read_excel_file(file_path): try: # 读取Excel文件 # df = pd.read_excel(file_path) wb = load_workbook(file_path) sheet = wb[wb.sheetnames[0]] df = [] for row in sheet.iter_rows(values_only=True): df.append(row) # 获取所有图片 images = {} for img in sheet._images: # 图片位置信息 anchor = img.anchor # 图片数据 if hasattr(img, '_data'): img_data = img._data() if callable(img._data) else img._data elif hasattr(img, 'image'): from io import BytesIO img.image.save(BytesIO(), format='png') img_data = BytesIO().getvalue() else: continue images[(anchor._from.row, anchor._from.col)] = img_data # 返回数据框 return df,images except Exception as e: print(f"读取Excel文件时出错: {e}") return None # 格式化excel数据 def getExcelData(excel_data,field_obj={}): fieldList = excel_data[0] data = [] for index,item in enumerate(excel_data): if index == 0: continue obj = {} for fieldIndex,field in enumerate(fieldList): if field in field_obj: obj[field_obj[field]] = item[fieldIndex] # else: # obj[field] = item[fieldIndex] data.append(obj) return data # 读取excel文件 def read_excel(file_path,current_filename,field_obj): #1.读取文件拿到信息 excel_data,images = read_excel_file(file_path) data = getExcelData(excel_data,field_obj) uuid = generate_random_key() # 打印数据 for i, row in enumerate(data): # 检查该行是否有图片 for (r, c), img_data in images.items(): if r == i+1: # 可以保存图片 with open(f'image/{uuid}_row_{i+1}_col_{c+1}.png', 'wb') as f: f.write(img_data) if 'files' not in row: row['files'] = [] row['files'].append(f'image/{uuid}_row_{i+1}_col_{c+1}.png') return data # 根据excel导出PPT接口 @app.route(f'/api/exportPPT', methods=['POST']) def exportPPT(): # 检查请求中是否有文件 if 'file' not in request.files: return jsonify({'error': 'No file part'}), 400 file = request.files['file'] filename = '' # 检查是否选择了文件 if file.filename == '': return jsonify({'error': 'No selected file'}), 400 if file: current_filename = file.filename # filename = f'uploads\{current_filename}' # if os.path.exists(filename): # pass # else: # 安全地获取文件名 filename = os.path.join(app.config['UPLOAD_FOLDER'], file.filename) # 确保不会覆盖现有文件 counter = 1 while os.path.exists(filename): name, ext = os.path.splitext(file.filename) filename = os.path.join(app.config['UPLOAD_FOLDER'], f"{name}_{counter}{ext}") counter += 1 # 保存文件 file.save(filename) # 文件存在时直接处理 file_path = '' # 1. 获取文件内容 field_obj = { '公开(公告)号':'publicNo', '摘要附图':'image', '序号':'order', '标题':'title', '申请号':'appNo', '申请日':'appDate', '当前申请(专利权)人':'appPerson', '公开(公告)日':'publicDate', '技术手段':'technical_means', '技术功效':'technical_efficacy', '应用领域分类':'application_field_classification', '技术问题':'technical_problem', } excel_data = read_excel(filename,current_filename,field_obj) if excel_data: # 2. 生成并导出PPT file_path = excelToPPT(excel_data) else: return jsonify({'error': 'No file part'}), 400 return jsonify(file_path) ,200 #下载文件 @app.route(f'/api/download',methods=['GET']) async def download(): get_params = request.args filename = get_params.get('filePath') file_path = os.path.join(os.getcwd(), '', filename) if not os.path.exists(file_path): abort(404) # 返回 404 错误 try: # 返回文件给用户下载 return send_file(file_path, as_attachment=True) except Exception as e: # 处理可能的异常(例如文件权限问题) return str(e), 500 # 根据excel导出xmind接口 @app.route(f'/api/exportXmind', methods=['POST']) def exportXmind(): # 检查请求中是否有文件 if 'file' not in request.files: return jsonify({'error': 'No file part'}), 400 file = request.files['file'] filename = '' # 检查是否选择了文件 if file.filename == '': return jsonify({'error': 'No selected file'}), 400 if file: current_filename = file.filename # filename = f'uploads\{current_filename}' # if os.path.exists(filename): # pass # else: # 安全地获取文件名 filename = os.path.join(app.config['UPLOAD_FOLDER'], file.filename) # 确保不会覆盖现有文件 counter = 1 while os.path.exists(filename): name, ext = os.path.splitext(file.filename) filename = os.path.join(app.config['UPLOAD_FOLDER'], f"{name}_{counter}{ext}") counter += 1 # 保存文件 file.save(filename) # 文件存在时直接处理 file_path = '' # 分类节点 classify_nodes = [ { 'name':'AI智能化', 'path':'AI智能化', 'parent_name':'', 'order':1 }, { 'name':'多模态交互、传感器处理(例如佩戴检测、语音识别)', 'path':'AI智能化\多模态交互、传感器处理(例如佩戴检测、语音识别)', 'parent_name':'AI智能化', 'parent_order':1, 'order':2 }, { 'name':'功能控制', 'path':'AI智能化\多模态交互、传感器处理(例如佩戴检测、语音识别)\功能控制', 'parent_name':'多模态交互、传感器处理(例如佩戴检测、语音识别)', 'parent_order':2, 'order':3 }, { 'name':'语音控制', 'path':'AI智能化\多模态交互、传感器处理(例如佩戴检测、语音识别)\语音控制', 'parent_name':'多模态交互、传感器处理(例如佩戴检测、语音识别)', 'parent_order':2, 'order':4 }, { 'name':'手势识别', 'path':'AI智能化\多模态交互、传感器处理(例如佩戴检测、语音识别)\手势识别', 'parent_name':'多模态交互、传感器处理(例如佩戴检测、语音识别)', 'parent_order':2, 'order':5 }, { 'name':'佩戴检测', 'path':'AI智能化\多模态交互、传感器处理(例如佩戴检测、语音识别)\佩戴检测', 'parent_name':'多模态交互、传感器处理(例如佩戴检测、语音识别)', 'parent_order':2, 'order':6 }, { 'name':'其它', 'path':'AI智能化\多模态交互、传感器处理(例如佩戴检测、语音识别)\其它', 'parent_name':'多模态交互、传感器处理(例如佩戴检测、语音识别)', 'parent_order':2, 'order':7 }, { 'name':'音频信号处理', 'path':'AI智能化\音频信号处理', 'parent_name':'AI智能化', 'parent_order':1, 'order':8 }, { 'name':'音质', 'path':'AI智能化\音频信号处理\音质', 'parent_name':'音频信号处理', 'parent_order':8, 'order':9 }, { 'name':'空间音频', 'path':'AI智能化\音频信号处理\空间音频', 'parent_name':'音频信号处理', 'parent_order':8, 'order':10 }, { 'name':'降噪', 'path':'AI智能化\音频信号处理\降噪', 'parent_name':'音频信号处理', 'parent_order':8, 'order':11 }, { 'name':'高清编码', 'path':'AI智能化\音频信号处理\高清编码', 'parent_name':'音频信号处理', 'parent_order':8, 'order':12 }, { 'name':'其它', 'path':'AI智能化\音频信号处理\其它', 'parent_name':'音频信号处理', 'parent_order':8, 'order':13 }, { 'name':'大模型简化', 'path':'AI智能化\大模型简化', 'parent_name':'AI智能化', 'parent_order':1, 'order':14 }, { 'name':'智能化应用', 'path':'AI智能化\智能化应用', 'parent_name':'AI智能化', 'parent_order':1, 'order':15 }, { 'name':'翻译', 'path':'AI智能化\智能化应用\翻译', 'parent_name':'智能化应用', 'parent_order':15, 'order':16 }, { 'name':'会议纪要', 'path':'AI智能化\智能化应用\会议纪要', 'parent_name':'智能化应用', 'parent_order':15, 'order':17 }, { 'name':'其它', 'path':'AI智能化\智能化应用\其它', 'parent_name':'智能化应用', 'parent_order':15, 'order':18 }, { 'name':'运动/健康信号处理', 'path':'运动/健康信号处理', 'parent_name':'', 'order':19 }, { 'name':'传感器元件', 'path':'运动/健康信号处理\传感器元件', 'parent_name':'运动/健康信号处理', 'parent_order':19, 'order':20 }, { 'name':'健康状态识别与分析', 'path':'运动/健康信号处理\健康状态识别与分析', 'parent_name':'运动/健康信号处理', 'parent_order':19, 'order':21 }, { 'name':'无线技术', 'path':'无线技术', 'parent_name':'', 'order':22 }, { 'name':'蓝牙/WiFi协议、连接', 'path':'无线技术\蓝牙/WiFi协议、连接', 'parent_name':'无线技术', 'parent_order':22, 'order':23 }, { 'name':'低延迟', 'path':'无线技术\蓝牙/WiFi协议、连接\低延迟', 'parent_name':'蓝牙/WiFi协议、连接', 'parent_order':23, 'order':24 }, { 'name':'其它', 'path':'无线技术\蓝牙/WiFi协议、连接\其它', 'parent_name':'蓝牙/WiFi协议、连接', 'parent_order':23, 'order':25 }, { 'name':'信号传输质量', 'path':'无线技术\信号传输质量', 'parent_name':'无线技术', 'parent_order':22, 'order':26 }, { 'name':'高可靠性', 'path':'无线技术\信号传输质量\高可靠性', 'parent_name':'信号传输质量', 'parent_order':26, 'order':27 }, { 'name':'其它', 'path':'无线技术\信号传输质量\其它', 'parent_name':'信号传输质量', 'parent_order':26, 'order':28 }, { 'name':'天线', 'path':'无线技术\天线', 'parent_name':'无线技术', 'parent_order':22, 'order':29 }, { 'name':'结构/材料/工艺', 'path':'结构/材料/工艺', 'parent_name':'', 'order':30 }, { 'name':'MIC/扬声器', 'path':'结构/材料/工艺\MIC/扬声器', 'parent_name':'结构/材料/工艺', 'parent_order':30, 'order':31 }, { 'name':'舒适度', 'path':'结构/材料/工艺\舒适度', 'parent_name':'结构/材料/工艺', 'parent_order':30, 'order':32 }, { 'name':'封装', 'path':'结构/材料/工艺\封装', 'parent_name':'结构/材料/工艺', 'parent_order':30, 'order':33 }, { 'name':'电池', 'path':'结构/材料/工艺\电池', 'parent_name':'结构/材料/工艺', 'parent_order':30, 'order':34 }, { 'name':'可调节结构/可拆卸结构/气囊结构', 'path':'结构/材料/工艺\可调节结构/可拆卸结构/气囊结构', 'parent_name':'结构/材料/工艺', 'parent_order':30, 'order':35 }, { 'name':'传感器件与产品耦合的材料/结构', 'path':'结构/材料/工艺\传感器件与产品耦合的材料/结构', 'parent_name':'结构/材料/工艺', 'parent_order':30, 'order':36 }, { 'name':'耳机特殊形态', 'path':'结构/材料/工艺\耳机特殊形态', 'parent_name':'结构/材料/工艺', 'parent_order':30, 'order':37 }, { 'name':'其它', 'path':'结构/材料/工艺\其它', 'parent_name':'结构/材料/工艺', 'parent_order':30, 'order':38 }, { 'name':'其它', 'path':'其它', 'parent_name':'', 'order':39 } ] # 1. 获取文件内容 field_obj = { '公开(公告)号':'publicNo', '摘要附图':'image', '序号':'order', '标题':'title', '申请号':'appNo', '申请日':'appDate', '当前申请(专利权)人':'appPerson', '公开(公告)日':'publicDate', '分类':'classify', '法律状态/事件':'status', '工作空间注释':'annotation' } excel_data = read_excel(filename,current_filename,field_obj) if excel_data: # 2. 获取待导出的数据结构 exportXmindData = getExportXmindData(excel_data,classify_nodes) # 3. 生成并导出xmind file_path = excelToXmind(exportXmindData) else: return jsonify({'error': 'No file part'}), 400 return jsonify(file_path) ,200 # 获取待导出的数据结构 def getExportXmindData(excel_data,classify_nodes): for item in excel_data: if 'classify' in item and item['classify']: item['classify_list'] = item['classify'].split('>') # 将专利添加到分类节点中去 for node_item in classify_nodes: path = node_item['path'] for item in excel_data: if 'classify_list' in item and item['classify_list']: classify_list = item['classify_list'] if path in classify_list: if 'patents' not in node_item: node_item['patents'] = [] node_item['patents'].append(item) node_dict = {node['order']: node for node in classify_nodes} tree = [] # 生成树级结构 for node in classify_nodes: if node['parent_name'] == '': # 如果没有父节点,说明是根节点 tree.append(node) else: # 否则,找到父节点并添加当前节点为子节点 parent = node_dict.get(node['parent_order']) if parent: if 'children' not in parent: parent['children'] = [] parent['children'].append(node) return tree import shutil # 复制模板文件 def copy_file_with_relative_path(relative_source_path, destination_path): try: # 获取当前工作目录 current_dir = os.getcwd() # 构建完整的源文件路径 full_source_path = os.path.join(current_dir, relative_source_path) # 检查源文件是否存在 if not os.path.exists(full_source_path): raise FileNotFoundError(f"源文件不存在:{full_source_path}") # 使用 shutil.copy2 复制文件,同时保留元数据 shutil.copy2(full_source_path, destination_path) print(f"文件已成功复制到:{destination_path}") except Exception as e: print(f"复制文件时发生错误:{e}") # 导出xmind def excelToXmind(tree): # 1. 创建空白xmind文件 uuid = generate_random_key() formatted_time = getCurrentDate() file_name = f"result/{formatted_time}_{uuid}.xmind" create_xmind_with_images(file_name,tree) return file_name import os import zipfile import xml.etree.ElementTree as ET from xml.dom import minidom def create_xmind_with_images(output_file,data): """创建包含三级节点和图片的 XMind 文件""" # 临时工作目录 temp_dir = "xmind_temp" # 清理并创建目录结构 if os.path.exists(temp_dir): shutil.rmtree(temp_dir) os.makedirs(os.path.join(temp_dir, "content")) os.makedirs(os.path.join(temp_dir, "META-INF")) os.makedirs(os.path.join(temp_dir, "resources")) os.makedirs(os.path.join(temp_dir, "Thumbnails")) create_metadata_json(temp_dir) # 1. 创建 content.xml 文件 images_list = create_content_xml(temp_dir,data) # 2. 创建 manifest.xml 文件 create_manifest_xml(temp_dir,images_list) # 3. 添加示例图片到 resources 目录 (实际使用中替换为你的图片) create_sample_images(temp_dir,images_list) # 4. 打包成 XMind 文件 with zipfile.ZipFile(output_file, 'w', zipfile.ZIP_DEFLATED) as zipf: for root, _, files in os.walk(temp_dir): for file in files: file_path = os.path.join(root, file) arcname = os.path.relpath(file_path, temp_dir) zipf.write(file_path, arcname) # 5. 清理临时文件 shutil.rmtree(temp_dir) print(f"XMind 文件已创建: {output_file}") return output_file def create_metadata_json(temp_dir): """创建 XMind 2022 的 metadata.json""" metadata_content = '''{ "activeSheetId": "sheet1", "creator": { "name": "XMind Python Generator", "version": "1.0" }, "fileStructure": { "markerIds": [], "numberingFormat": null, "theme": "Default", "version": "2.0" }, "modifiedBy": "XMind Python Generator" }''' # 写入文件 metadata_path = os.path.join(temp_dir, "metadata.json") with open(metadata_path, 'w', encoding='utf-8') as f: f.write(metadata_content) def add_topic_recursive(parent_element, node_data, ns,num,images_list): """ 递归添加主题节点 参数: parent_element: 父XML元素 node_data: 当前节点数据 ns: XML命名空间 """ node_name = '' classify_name = node_data.get('name','') if not classify_name:#不是分类节点,是专利 patentNo = node_data.get('appNo','') or node_data.get('publicNo','') title = node_data.get('title','') status = node_data.get('status','') annotation = node_data.get('annotation','') node_name = node_name + patentNo if title: node_name = node_name + f'({title})' node_name = node_name + status + '\n' node_name = node_name + annotation else: node_name = classify_name # 创建当前主题节点 topic = ET.SubElement(parent_element, "topic", id=f"topic{num}_{generate_random_key()}") ET.SubElement(topic, "title").text = node_name # 添加图片(如果存在) if 'files' in node_data: for file in node_data['files']: file_index = file.rfind('/') file_name = file[file_index+1:] images_list.append(file_name) image_elem = ET.SubElement(topic, "image", src=f"resources/{file_name}", align="center") image_elem.set("width", "200") image_elem.set("height", "150") break # 如果有子节点,递归添加 if "children" in node_data and node_data["children"]: num += 1 children_container = ET.SubElement(topic, "children") topics_container = ET.SubElement(children_container, "topics", type="attached") for child in node_data["children"]: add_topic_recursive(topics_container, child, ns,num,images_list) # 如果有子节点,递归添加 if "patents" in node_data and node_data["patents"]: num += 1 children_container = ET.SubElement(topic, "children") topics_container = ET.SubElement(children_container, "topics", type="attached") for child in node_data["patents"]: add_topic_recursive(topics_container, child, ns,num,images_list) def create_content_xml(temp_dir,data): """创建 content.xml 文件""" # XML 命名空间 ns = "urn:xmind:xmap:xmlns:content:2.0" # 创建根元素 xmap_content = ET.Element("xmap-content", xmlns=ns, version="2.0") # 创建工作表 sheet = ET.SubElement(xmap_content, "sheet", id="sheet1") # 创建主题 topic = ET.SubElement(sheet, "topic", id="root", timestamp="0",structure="org.xmind.ui.logic.right") ET.SubElement(topic, "title").text = "**厂商**品类的专利布局" structure = ET.SubElement(topic, "structure") structure.set("type", "str") structure.text = "org.xmind.ui.logic.right" # 添加子节点容器 children_container = ET.SubElement(topic, "children") topics_container = ET.SubElement(children_container, "topics", type="attached") images_list = [] # 递归添加子节点 for child in data: add_topic_recursive(topics_container, child, ns,1,images_list) # 美化XML输出 xml_str = ET.tostring(xmap_content, encoding='utf-8') pretty_xml = minidom.parseString(xml_str).toprettyxml(indent=" ") pretty_xml = pretty_xml.replace('', '') # 写入文件 content_path = os.path.join(temp_dir, "content.xml") with open(content_path, 'w', encoding='utf-8') as f: f.write(pretty_xml) return images_list def create_topic(parent, title, topic_id): """创建主题节点""" topic = ET.SubElement(parent, "topic", id=topic_id) ET.SubElement(topic, "title").text = title return topic def create_manifest_xml(temp_dir,images_list): """创建 manifest.xml 文件""" str = '' if images_list: for file in images_list: str = str + f'' + '\n' manifest_content = f''' {str} ''' manifest_path = os.path.join(temp_dir, "META-INF", "manifest.xml") with open(manifest_path, 'w', encoding='utf-8') as f: f.write(manifest_content) return True def create_sample_images(temp_dir,images_list): """创建示例图片(实际使用中应替换为你的图片)""" # 创建两个示例图片(实际项目中应使用真实图片) resources_dir = os.path.join(temp_dir, "resources") if images_list: for file in images_list: shutil.copy(f"image/{file}", os.path.join(resources_dir, file)) return True from xmindparser import xmind_to_xml def etxe1(): xml = xmind_to_xml('result/2025-06-26_2906a7b1-baf4-4b09-b668-b80c3a8264cb.xmind') print(xml) if __name__ == '__main__': # etxe1() app.run(debug=True, host='0.0.0.0', port=2500)