|
@@ -0,0 +1,916 @@
|
|
|
|
+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('<?xml version="1.0" ?>', '<?xml version="1.0" encoding="UTF-8" standalone="no"?>')
|
|
|
|
+
|
|
|
|
+ # 写入文件
|
|
|
|
+ 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'<file-entry full-path="resources/{file}" media-type="image/png"/>' + '\n'
|
|
|
|
+ manifest_content = f'''<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
|
|
|
+ <manifest xmlns="urn:xmind:xmap:xmlns:manifest:1.0">
|
|
|
|
+ <file-entry full-path="content.xml" media-type="text/xml"/>
|
|
|
|
+ <file-entry full-path="META-INF/" media-type=""/>
|
|
|
|
+ <file-entry full-path="META-INF/manifest.xml" media-type="text/xml"/>
|
|
|
|
+ <file-entry full-path="resources/" media-type=""/>
|
|
|
|
+ {str}
|
|
|
|
+ </manifest>'''
|
|
|
|
+
|
|
|
|
+ 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)
|