excelToPPT.py 33 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916
  1. from flask import Flask, request, jsonify, Response,send_file,abort
  2. import os
  3. import json
  4. import re
  5. from datetime import datetime
  6. app = Flask(__name__)
  7. # 配置上传文件夹
  8. UPLOAD_FOLDER = 'uploads'
  9. if not os.path.exists(UPLOAD_FOLDER):
  10. os.makedirs(UPLOAD_FOLDER)
  11. app.config['UPLOAD_FOLDER'] = UPLOAD_FOLDER
  12. from pptx import Presentation
  13. from pptx.util import Inches,Pt
  14. from pptx.enum.text import PP_ALIGN,PP_PARAGRAPH_ALIGNMENT
  15. from pptx.enum.dml import MSO_THEME_COLOR
  16. from lxml import etree
  17. import uuid
  18. def generate_random_key():
  19. """生成一个 UUID 密钥"""
  20. return str(uuid.uuid4())
  21. # 获取当前时间
  22. def getCurrentDate():
  23. # 获取当前日期和时间
  24. current_time = datetime.now()
  25. # 格式化输出
  26. formatted_time = current_time.strftime("%Y-%m-%d")
  27. return formatted_time
  28. # 导出PPT
  29. def excelToPPT(data):
  30. # 创建一个新的PPT
  31. prs = Presentation()
  32. slide_width = prs.slide_width # 返回EMU单位
  33. slide_height = prs.slide_height
  34. font_list = ["latin", "ea", "cs"]
  35. # 设置主题字体
  36. for item in data:
  37. order = item['order']
  38. title_name = item['title']
  39. patentNo = item.get('appNo','') or item.get('publicNo','')
  40. appDate = item.get('appDate','')
  41. publicDate = item.get('publicDate','')
  42. appPerson = item.get('appPerson','')
  43. files = item.get('files',[])
  44. technical_problem = item.get('technical_problem','')
  45. technical_means = item.get('technical_means','')
  46. technical_efficacy = item.get('technical_efficacy','')
  47. application_field_classification = item.get('application_field_classification','')
  48. # 添加一个幻灯片
  49. slide_layout = prs.slide_layouts[5] # 使用一个空白布局
  50. slide = prs.slides.add_slide(slide_layout)
  51. # 添加标题
  52. title = slide.shapes.title
  53. title.height = Pt(40)
  54. title.width = slide_width
  55. title.text = f"{order}、{title_name}-{patentNo}"
  56. # 设置标题字体
  57. title_frame = title.text_frame
  58. title_paragraph = title_frame.paragraphs[0]
  59. title_paragraph.alignment = PP_PARAGRAPH_ALIGNMENT.LEFT
  60. title_run = title_paragraph.runs[0]
  61. # 设置标题字体属性
  62. title_run.font.name = '微软雅黑'
  63. title_run.font.size = Pt(20)
  64. title_run.font.bold = True
  65. r_element = title_run._r
  66. rPr = r_element.find('.//a:rPr', namespaces={'a': 'http://schemas.openxmlformats.org/drawingml/2006/main'})
  67. if rPr is not None:
  68. for tag in font_list:
  69. # 获取或创建 ea 节点
  70. node = rPr.find(f'.//a:{tag}', namespaces={'a': 'http://schemas.openxmlformats.org/drawingml/2006/main'})
  71. if node is None:
  72. # 如果 ea 节点不存在,创建一个新的 ea 节点
  73. node = etree.Element('{http://schemas.openxmlformats.org/drawingml/2006/main}'+tag)
  74. rPr.append(node)
  75. node.set('typeface', '微软雅黑') # 你可以替换为你想要的字体名称
  76. # 更新 _r 属性
  77. title_run._r = r_element
  78. # 添加图片 (替换为实际图片路径)
  79. if len(files)>0:
  80. left = Inches(0.5)
  81. top = Inches(1.3)
  82. width = Inches(4.5)
  83. height = Inches(3)
  84. num = 1
  85. for image in files:
  86. if num == 1:
  87. slide.shapes.add_picture(image, left, top, width, height)
  88. else:
  89. left_second_image = Inches(5 * (num - 1))
  90. slide.shapes.add_picture(image, left_second_image, top, width, height)
  91. num += 1
  92. # 添加文本框
  93. left_text = Inches(0.5)
  94. top_text = Inches(4.5)
  95. width_text = slide_width - Inches(0.5).emu
  96. height_text = Inches(2)
  97. text_box = slide.shapes.add_textbox(left_text, top_text, width_text, height_text)
  98. text_frame = text_box.text_frame
  99. # 关键配置:启用自动换行
  100. text_frame.word_wrap = True
  101. text_frame.auto_size = None
  102. paragraph_list = [
  103. {
  104. 'field':'申请人',
  105. 'content':appPerson
  106. },
  107. {
  108. 'field':'申请日',
  109. 'content':appDate
  110. },
  111. {
  112. 'field':'公开公告日',
  113. 'content':publicDate
  114. },
  115. {
  116. 'field':'解决问题',
  117. 'content':technical_problem
  118. },
  119. {
  120. 'field':'技术手段',
  121. 'content':technical_means
  122. },
  123. {
  124. 'field':'技术效果',
  125. 'content':technical_efficacy
  126. },
  127. {
  128. 'field':'应用领域',
  129. 'content':application_field_classification.replace('|','、')
  130. }
  131. ]
  132. for item in paragraph_list:
  133. paragraph = text_frame.add_paragraph()
  134. paragraph.text = f"{item['field']}:"
  135. run = paragraph.runs[0]
  136. run.font.bold = True
  137. run = paragraph.add_run()
  138. run.text = item['content']
  139. for run_item in paragraph.runs:
  140. run_item.font.size = Pt(15)
  141. run_item.font.name = '微软雅黑'
  142. r_element = run_item._r
  143. rPr = r_element.find('.//a:rPr', namespaces={'a': 'http://schemas.openxmlformats.org/drawingml/2006/main'})
  144. if rPr is not None:
  145. for tag in font_list:
  146. # 获取或创建 ea 节点
  147. node = rPr.find(f'.//a:{tag}', namespaces={'a': 'http://schemas.openxmlformats.org/drawingml/2006/main'})
  148. if node is None:
  149. # 如果 ea 节点不存在,创建一个新的 ea 节点
  150. node = etree.Element('{http://schemas.openxmlformats.org/drawingml/2006/main}'+tag)
  151. rPr.append(node)
  152. node.set('typeface', '微软雅黑') # 你可以替换为你想要的字体名称
  153. # 更新 _r 属性
  154. run_item._r = r_element
  155. paragraph.alignment = PP_PARAGRAPH_ALIGNMENT.LEFT
  156. # 保存PPT
  157. uuid = generate_random_key()
  158. formatted_time = getCurrentDate()
  159. file_name = f"result/{formatted_time}_{uuid}.pptx"
  160. prs.save(file_name)
  161. return file_name
  162. # 读取Excel文件
  163. from openpyxl import load_workbook
  164. def read_excel_file(file_path):
  165. try:
  166. # 读取Excel文件
  167. # df = pd.read_excel(file_path)
  168. wb = load_workbook(file_path)
  169. sheet = wb[wb.sheetnames[0]]
  170. df = []
  171. for row in sheet.iter_rows(values_only=True):
  172. df.append(row)
  173. # 获取所有图片
  174. images = {}
  175. for img in sheet._images:
  176. # 图片位置信息
  177. anchor = img.anchor
  178. # 图片数据
  179. if hasattr(img, '_data'):
  180. img_data = img._data() if callable(img._data) else img._data
  181. elif hasattr(img, 'image'):
  182. from io import BytesIO
  183. img.image.save(BytesIO(), format='png')
  184. img_data = BytesIO().getvalue()
  185. else:
  186. continue
  187. images[(anchor._from.row, anchor._from.col)] = img_data
  188. # 返回数据框
  189. return df,images
  190. except Exception as e:
  191. print(f"读取Excel文件时出错: {e}")
  192. return None
  193. # 格式化excel数据
  194. def getExcelData(excel_data,field_obj={}):
  195. fieldList = excel_data[0]
  196. data = []
  197. for index,item in enumerate(excel_data):
  198. if index == 0:
  199. continue
  200. obj = {}
  201. for fieldIndex,field in enumerate(fieldList):
  202. if field in field_obj:
  203. obj[field_obj[field]] = item[fieldIndex]
  204. # else:
  205. # obj[field] = item[fieldIndex]
  206. data.append(obj)
  207. return data
  208. # 读取excel文件
  209. def read_excel(file_path,current_filename,field_obj):
  210. #1.读取文件拿到信息
  211. excel_data,images = read_excel_file(file_path)
  212. data = getExcelData(excel_data,field_obj)
  213. uuid = generate_random_key()
  214. # 打印数据
  215. for i, row in enumerate(data):
  216. # 检查该行是否有图片
  217. for (r, c), img_data in images.items():
  218. if r == i+1:
  219. # 可以保存图片
  220. with open(f'image/{uuid}_row_{i+1}_col_{c+1}.png', 'wb') as f:
  221. f.write(img_data)
  222. if 'files' not in row:
  223. row['files'] = []
  224. row['files'].append(f'image/{uuid}_row_{i+1}_col_{c+1}.png')
  225. return data
  226. # 根据excel导出PPT接口
  227. @app.route(f'/api/exportPPT', methods=['POST'])
  228. def exportPPT():
  229. # 检查请求中是否有文件
  230. if 'file' not in request.files:
  231. return jsonify({'error': 'No file part'}), 400
  232. file = request.files['file']
  233. filename = ''
  234. # 检查是否选择了文件
  235. if file.filename == '':
  236. return jsonify({'error': 'No selected file'}), 400
  237. if file:
  238. current_filename = file.filename
  239. # filename = f'uploads\{current_filename}'
  240. # if os.path.exists(filename):
  241. # pass
  242. # else:
  243. # 安全地获取文件名
  244. filename = os.path.join(app.config['UPLOAD_FOLDER'], file.filename)
  245. # 确保不会覆盖现有文件
  246. counter = 1
  247. while os.path.exists(filename):
  248. name, ext = os.path.splitext(file.filename)
  249. filename = os.path.join(app.config['UPLOAD_FOLDER'], f"{name}_{counter}{ext}")
  250. counter += 1
  251. # 保存文件
  252. file.save(filename)
  253. # 文件存在时直接处理
  254. file_path = ''
  255. # 1. 获取文件内容
  256. field_obj = {
  257. '公开(公告)号':'publicNo',
  258. '摘要附图':'image',
  259. '序号':'order',
  260. '标题':'title',
  261. '申请号':'appNo',
  262. '申请日':'appDate',
  263. '当前申请(专利权)人':'appPerson',
  264. '公开(公告)日':'publicDate',
  265. '技术手段':'technical_means',
  266. '技术功效':'technical_efficacy',
  267. '应用领域分类':'application_field_classification',
  268. '技术问题':'technical_problem',
  269. }
  270. excel_data = read_excel(filename,current_filename,field_obj)
  271. if excel_data:
  272. # 2. 生成并导出PPT
  273. file_path = excelToPPT(excel_data)
  274. else:
  275. return jsonify({'error': 'No file part'}), 400
  276. return jsonify(file_path) ,200
  277. #下载文件
  278. @app.route(f'/api/download',methods=['GET'])
  279. async def download():
  280. get_params = request.args
  281. filename = get_params.get('filePath')
  282. file_path = os.path.join(os.getcwd(), '', filename)
  283. if not os.path.exists(file_path):
  284. abort(404) # 返回 404 错误
  285. try:
  286. # 返回文件给用户下载
  287. return send_file(file_path, as_attachment=True)
  288. except Exception as e:
  289. # 处理可能的异常(例如文件权限问题)
  290. return str(e), 500
  291. # 根据excel导出xmind接口
  292. @app.route(f'/api/exportXmind', methods=['POST'])
  293. def exportXmind():
  294. # 检查请求中是否有文件
  295. if 'file' not in request.files:
  296. return jsonify({'error': 'No file part'}), 400
  297. file = request.files['file']
  298. filename = ''
  299. # 检查是否选择了文件
  300. if file.filename == '':
  301. return jsonify({'error': 'No selected file'}), 400
  302. if file:
  303. current_filename = file.filename
  304. # filename = f'uploads\{current_filename}'
  305. # if os.path.exists(filename):
  306. # pass
  307. # else:
  308. # 安全地获取文件名
  309. filename = os.path.join(app.config['UPLOAD_FOLDER'], file.filename)
  310. # 确保不会覆盖现有文件
  311. counter = 1
  312. while os.path.exists(filename):
  313. name, ext = os.path.splitext(file.filename)
  314. filename = os.path.join(app.config['UPLOAD_FOLDER'], f"{name}_{counter}{ext}")
  315. counter += 1
  316. # 保存文件
  317. file.save(filename)
  318. # 文件存在时直接处理
  319. file_path = ''
  320. # 分类节点
  321. classify_nodes = [
  322. {
  323. 'name':'AI智能化',
  324. 'path':'AI智能化',
  325. 'parent_name':'',
  326. 'order':1
  327. },
  328. {
  329. 'name':'多模态交互、传感器处理(例如佩戴检测、语音识别)',
  330. 'path':'AI智能化\多模态交互、传感器处理(例如佩戴检测、语音识别)',
  331. 'parent_name':'AI智能化',
  332. 'parent_order':1,
  333. 'order':2
  334. },
  335. {
  336. 'name':'功能控制',
  337. 'path':'AI智能化\多模态交互、传感器处理(例如佩戴检测、语音识别)\功能控制',
  338. 'parent_name':'多模态交互、传感器处理(例如佩戴检测、语音识别)',
  339. 'parent_order':2,
  340. 'order':3
  341. },
  342. {
  343. 'name':'语音控制',
  344. 'path':'AI智能化\多模态交互、传感器处理(例如佩戴检测、语音识别)\语音控制',
  345. 'parent_name':'多模态交互、传感器处理(例如佩戴检测、语音识别)',
  346. 'parent_order':2,
  347. 'order':4
  348. },
  349. {
  350. 'name':'手势识别',
  351. 'path':'AI智能化\多模态交互、传感器处理(例如佩戴检测、语音识别)\手势识别',
  352. 'parent_name':'多模态交互、传感器处理(例如佩戴检测、语音识别)',
  353. 'parent_order':2,
  354. 'order':5
  355. },
  356. {
  357. 'name':'佩戴检测',
  358. 'path':'AI智能化\多模态交互、传感器处理(例如佩戴检测、语音识别)\佩戴检测',
  359. 'parent_name':'多模态交互、传感器处理(例如佩戴检测、语音识别)',
  360. 'parent_order':2,
  361. 'order':6
  362. },
  363. {
  364. 'name':'其它',
  365. 'path':'AI智能化\多模态交互、传感器处理(例如佩戴检测、语音识别)\其它',
  366. 'parent_name':'多模态交互、传感器处理(例如佩戴检测、语音识别)',
  367. 'parent_order':2,
  368. 'order':7
  369. },
  370. {
  371. 'name':'音频信号处理',
  372. 'path':'AI智能化\音频信号处理',
  373. 'parent_name':'AI智能化',
  374. 'parent_order':1,
  375. 'order':8
  376. },
  377. {
  378. 'name':'音质',
  379. 'path':'AI智能化\音频信号处理\音质',
  380. 'parent_name':'音频信号处理',
  381. 'parent_order':8,
  382. 'order':9
  383. },
  384. {
  385. 'name':'空间音频',
  386. 'path':'AI智能化\音频信号处理\空间音频',
  387. 'parent_name':'音频信号处理',
  388. 'parent_order':8,
  389. 'order':10
  390. },
  391. {
  392. 'name':'降噪',
  393. 'path':'AI智能化\音频信号处理\降噪',
  394. 'parent_name':'音频信号处理',
  395. 'parent_order':8,
  396. 'order':11
  397. },
  398. {
  399. 'name':'高清编码',
  400. 'path':'AI智能化\音频信号处理\高清编码',
  401. 'parent_name':'音频信号处理',
  402. 'parent_order':8,
  403. 'order':12
  404. },
  405. {
  406. 'name':'其它',
  407. 'path':'AI智能化\音频信号处理\其它',
  408. 'parent_name':'音频信号处理',
  409. 'parent_order':8,
  410. 'order':13
  411. },
  412. {
  413. 'name':'大模型简化',
  414. 'path':'AI智能化\大模型简化',
  415. 'parent_name':'AI智能化',
  416. 'parent_order':1,
  417. 'order':14
  418. },
  419. {
  420. 'name':'智能化应用',
  421. 'path':'AI智能化\智能化应用',
  422. 'parent_name':'AI智能化',
  423. 'parent_order':1,
  424. 'order':15
  425. },
  426. {
  427. 'name':'翻译',
  428. 'path':'AI智能化\智能化应用\翻译',
  429. 'parent_name':'智能化应用',
  430. 'parent_order':15,
  431. 'order':16
  432. },
  433. {
  434. 'name':'会议纪要',
  435. 'path':'AI智能化\智能化应用\会议纪要',
  436. 'parent_name':'智能化应用',
  437. 'parent_order':15,
  438. 'order':17
  439. },
  440. {
  441. 'name':'其它',
  442. 'path':'AI智能化\智能化应用\其它',
  443. 'parent_name':'智能化应用',
  444. 'parent_order':15,
  445. 'order':18
  446. },
  447. {
  448. 'name':'运动/健康信号处理',
  449. 'path':'运动/健康信号处理',
  450. 'parent_name':'',
  451. 'order':19
  452. },
  453. {
  454. 'name':'传感器元件',
  455. 'path':'运动/健康信号处理\传感器元件',
  456. 'parent_name':'运动/健康信号处理',
  457. 'parent_order':19,
  458. 'order':20
  459. },
  460. {
  461. 'name':'健康状态识别与分析',
  462. 'path':'运动/健康信号处理\健康状态识别与分析',
  463. 'parent_name':'运动/健康信号处理',
  464. 'parent_order':19,
  465. 'order':21
  466. },
  467. {
  468. 'name':'无线技术',
  469. 'path':'无线技术',
  470. 'parent_name':'',
  471. 'order':22
  472. },
  473. {
  474. 'name':'蓝牙/WiFi协议、连接',
  475. 'path':'无线技术\蓝牙/WiFi协议、连接',
  476. 'parent_name':'无线技术',
  477. 'parent_order':22,
  478. 'order':23
  479. },
  480. {
  481. 'name':'低延迟',
  482. 'path':'无线技术\蓝牙/WiFi协议、连接\低延迟',
  483. 'parent_name':'蓝牙/WiFi协议、连接',
  484. 'parent_order':23,
  485. 'order':24
  486. },
  487. {
  488. 'name':'其它',
  489. 'path':'无线技术\蓝牙/WiFi协议、连接\其它',
  490. 'parent_name':'蓝牙/WiFi协议、连接',
  491. 'parent_order':23,
  492. 'order':25
  493. },
  494. {
  495. 'name':'信号传输质量',
  496. 'path':'无线技术\信号传输质量',
  497. 'parent_name':'无线技术',
  498. 'parent_order':22,
  499. 'order':26
  500. },
  501. {
  502. 'name':'高可靠性',
  503. 'path':'无线技术\信号传输质量\高可靠性',
  504. 'parent_name':'信号传输质量',
  505. 'parent_order':26,
  506. 'order':27
  507. },
  508. {
  509. 'name':'其它',
  510. 'path':'无线技术\信号传输质量\其它',
  511. 'parent_name':'信号传输质量',
  512. 'parent_order':26,
  513. 'order':28
  514. },
  515. {
  516. 'name':'天线',
  517. 'path':'无线技术\天线',
  518. 'parent_name':'无线技术',
  519. 'parent_order':22,
  520. 'order':29
  521. },
  522. {
  523. 'name':'结构/材料/工艺',
  524. 'path':'结构/材料/工艺',
  525. 'parent_name':'',
  526. 'order':30
  527. },
  528. {
  529. 'name':'MIC/扬声器',
  530. 'path':'结构/材料/工艺\MIC/扬声器',
  531. 'parent_name':'结构/材料/工艺',
  532. 'parent_order':30,
  533. 'order':31
  534. },
  535. {
  536. 'name':'舒适度',
  537. 'path':'结构/材料/工艺\舒适度',
  538. 'parent_name':'结构/材料/工艺',
  539. 'parent_order':30,
  540. 'order':32
  541. },
  542. {
  543. 'name':'封装',
  544. 'path':'结构/材料/工艺\封装',
  545. 'parent_name':'结构/材料/工艺',
  546. 'parent_order':30,
  547. 'order':33
  548. },
  549. {
  550. 'name':'电池',
  551. 'path':'结构/材料/工艺\电池',
  552. 'parent_name':'结构/材料/工艺',
  553. 'parent_order':30,
  554. 'order':34
  555. },
  556. {
  557. 'name':'可调节结构/可拆卸结构/气囊结构',
  558. 'path':'结构/材料/工艺\可调节结构/可拆卸结构/气囊结构',
  559. 'parent_name':'结构/材料/工艺',
  560. 'parent_order':30,
  561. 'order':35
  562. },
  563. {
  564. 'name':'传感器件与产品耦合的材料/结构',
  565. 'path':'结构/材料/工艺\传感器件与产品耦合的材料/结构',
  566. 'parent_name':'结构/材料/工艺',
  567. 'parent_order':30,
  568. 'order':36
  569. },
  570. {
  571. 'name':'耳机特殊形态',
  572. 'path':'结构/材料/工艺\耳机特殊形态',
  573. 'parent_name':'结构/材料/工艺',
  574. 'parent_order':30,
  575. 'order':37
  576. },
  577. {
  578. 'name':'其它',
  579. 'path':'结构/材料/工艺\其它',
  580. 'parent_name':'结构/材料/工艺',
  581. 'parent_order':30,
  582. 'order':38
  583. },
  584. {
  585. 'name':'其它',
  586. 'path':'其它',
  587. 'parent_name':'',
  588. 'order':39
  589. }
  590. ]
  591. # 1. 获取文件内容
  592. field_obj = {
  593. '公开(公告)号':'publicNo',
  594. '摘要附图':'image',
  595. '序号':'order',
  596. '标题':'title',
  597. '申请号':'appNo',
  598. '申请日':'appDate',
  599. '当前申请(专利权)人':'appPerson',
  600. '公开(公告)日':'publicDate',
  601. '分类':'classify',
  602. '法律状态/事件':'status',
  603. '工作空间注释':'annotation'
  604. }
  605. excel_data = read_excel(filename,current_filename,field_obj)
  606. if excel_data:
  607. # 2. 获取待导出的数据结构
  608. exportXmindData = getExportXmindData(excel_data,classify_nodes)
  609. # 3. 生成并导出xmind
  610. file_path = excelToXmind(exportXmindData)
  611. else:
  612. return jsonify({'error': 'No file part'}), 400
  613. return jsonify(file_path) ,200
  614. # 获取待导出的数据结构
  615. def getExportXmindData(excel_data,classify_nodes):
  616. for item in excel_data:
  617. if 'classify' in item and item['classify']:
  618. item['classify_list'] = item['classify'].split('>')
  619. # 将专利添加到分类节点中去
  620. for node_item in classify_nodes:
  621. path = node_item['path']
  622. for item in excel_data:
  623. if 'classify_list' in item and item['classify_list']:
  624. classify_list = item['classify_list']
  625. if path in classify_list:
  626. if 'patents' not in node_item:
  627. node_item['patents'] = []
  628. node_item['patents'].append(item)
  629. node_dict = {node['order']: node for node in classify_nodes}
  630. tree = []
  631. # 生成树级结构
  632. for node in classify_nodes:
  633. if node['parent_name'] == '':
  634. # 如果没有父节点,说明是根节点
  635. tree.append(node)
  636. else:
  637. # 否则,找到父节点并添加当前节点为子节点
  638. parent = node_dict.get(node['parent_order'])
  639. if parent:
  640. if 'children' not in parent:
  641. parent['children'] = []
  642. parent['children'].append(node)
  643. return tree
  644. import shutil
  645. # 复制模板文件
  646. def copy_file_with_relative_path(relative_source_path, destination_path):
  647. try:
  648. # 获取当前工作目录
  649. current_dir = os.getcwd()
  650. # 构建完整的源文件路径
  651. full_source_path = os.path.join(current_dir, relative_source_path)
  652. # 检查源文件是否存在
  653. if not os.path.exists(full_source_path):
  654. raise FileNotFoundError(f"源文件不存在:{full_source_path}")
  655. # 使用 shutil.copy2 复制文件,同时保留元数据
  656. shutil.copy2(full_source_path, destination_path)
  657. print(f"文件已成功复制到:{destination_path}")
  658. except Exception as e:
  659. print(f"复制文件时发生错误:{e}")
  660. # 导出xmind
  661. def excelToXmind(tree):
  662. # 1. 创建空白xmind文件
  663. uuid = generate_random_key()
  664. formatted_time = getCurrentDate()
  665. file_name = f"result/{formatted_time}_{uuid}.xmind"
  666. create_xmind_with_images(file_name,tree)
  667. return file_name
  668. import os
  669. import zipfile
  670. import xml.etree.ElementTree as ET
  671. from xml.dom import minidom
  672. def create_xmind_with_images(output_file,data):
  673. """创建包含三级节点和图片的 XMind 文件"""
  674. # 临时工作目录
  675. temp_dir = "xmind_temp"
  676. # 清理并创建目录结构
  677. if os.path.exists(temp_dir):
  678. shutil.rmtree(temp_dir)
  679. os.makedirs(os.path.join(temp_dir, "content"))
  680. os.makedirs(os.path.join(temp_dir, "META-INF"))
  681. os.makedirs(os.path.join(temp_dir, "resources"))
  682. os.makedirs(os.path.join(temp_dir, "Thumbnails"))
  683. create_metadata_json(temp_dir)
  684. # 1. 创建 content.xml 文件
  685. images_list = create_content_xml(temp_dir,data)
  686. # 2. 创建 manifest.xml 文件
  687. create_manifest_xml(temp_dir,images_list)
  688. # 3. 添加示例图片到 resources 目录 (实际使用中替换为你的图片)
  689. create_sample_images(temp_dir,images_list)
  690. # 4. 打包成 XMind 文件
  691. with zipfile.ZipFile(output_file, 'w', zipfile.ZIP_DEFLATED) as zipf:
  692. for root, _, files in os.walk(temp_dir):
  693. for file in files:
  694. file_path = os.path.join(root, file)
  695. arcname = os.path.relpath(file_path, temp_dir)
  696. zipf.write(file_path, arcname)
  697. # 5. 清理临时文件
  698. shutil.rmtree(temp_dir)
  699. print(f"XMind 文件已创建: {output_file}")
  700. return output_file
  701. def create_metadata_json(temp_dir):
  702. """创建 XMind 2022 的 metadata.json"""
  703. metadata_content = '''{
  704. "activeSheetId": "sheet1",
  705. "creator": {
  706. "name": "XMind Python Generator",
  707. "version": "1.0"
  708. },
  709. "fileStructure": {
  710. "markerIds": [],
  711. "numberingFormat": null,
  712. "theme": "Default",
  713. "version": "2.0"
  714. },
  715. "modifiedBy": "XMind Python Generator"
  716. }'''
  717. # 写入文件
  718. metadata_path = os.path.join(temp_dir, "metadata.json")
  719. with open(metadata_path, 'w', encoding='utf-8') as f:
  720. f.write(metadata_content)
  721. def add_topic_recursive(parent_element, node_data, ns,num,images_list):
  722. """
  723. 递归添加主题节点
  724. 参数:
  725. parent_element: 父XML元素
  726. node_data: 当前节点数据
  727. ns: XML命名空间
  728. """
  729. node_name = ''
  730. classify_name = node_data.get('name','')
  731. if not classify_name:#不是分类节点,是专利
  732. patentNo = node_data.get('appNo','') or node_data.get('publicNo','')
  733. title = node_data.get('title','')
  734. status = node_data.get('status','')
  735. annotation = node_data.get('annotation','')
  736. node_name = node_name + patentNo
  737. if title:
  738. node_name = node_name + f'({title})'
  739. node_name = node_name + status + '\n'
  740. node_name = node_name + annotation
  741. else:
  742. node_name = classify_name
  743. # 创建当前主题节点
  744. topic = ET.SubElement(parent_element, "topic", id=f"topic{num}_{generate_random_key()}")
  745. ET.SubElement(topic, "title").text = node_name
  746. # 添加图片(如果存在)
  747. if 'files' in node_data:
  748. for file in node_data['files']:
  749. file_index = file.rfind('/')
  750. file_name = file[file_index+1:]
  751. images_list.append(file_name)
  752. image_elem = ET.SubElement(topic, "image", src=f"resources/{file_name}", align="center")
  753. image_elem.set("width", "200")
  754. image_elem.set("height", "150")
  755. break
  756. # 如果有子节点,递归添加
  757. if "children" in node_data and node_data["children"]:
  758. num += 1
  759. children_container = ET.SubElement(topic, "children")
  760. topics_container = ET.SubElement(children_container, "topics", type="attached")
  761. for child in node_data["children"]:
  762. add_topic_recursive(topics_container, child, ns,num,images_list)
  763. # 如果有子节点,递归添加
  764. if "patents" in node_data and node_data["patents"]:
  765. num += 1
  766. children_container = ET.SubElement(topic, "children")
  767. topics_container = ET.SubElement(children_container, "topics", type="attached")
  768. for child in node_data["patents"]:
  769. add_topic_recursive(topics_container, child, ns,num,images_list)
  770. def create_content_xml(temp_dir,data):
  771. """创建 content.xml 文件"""
  772. # XML 命名空间
  773. ns = "urn:xmind:xmap:xmlns:content:2.0"
  774. # 创建根元素
  775. xmap_content = ET.Element("xmap-content", xmlns=ns, version="2.0")
  776. # 创建工作表
  777. sheet = ET.SubElement(xmap_content, "sheet", id="sheet1")
  778. # 创建主题
  779. topic = ET.SubElement(sheet, "topic", id="root", timestamp="0",structure="org.xmind.ui.logic.right")
  780. ET.SubElement(topic, "title").text = "**厂商**品类的专利布局"
  781. structure = ET.SubElement(topic, "structure")
  782. structure.set("type", "str")
  783. structure.text = "org.xmind.ui.logic.right"
  784. # 添加子节点容器
  785. children_container = ET.SubElement(topic, "children")
  786. topics_container = ET.SubElement(children_container, "topics", type="attached")
  787. images_list = []
  788. # 递归添加子节点
  789. for child in data:
  790. add_topic_recursive(topics_container, child, ns,1,images_list)
  791. # 美化XML输出
  792. xml_str = ET.tostring(xmap_content, encoding='utf-8')
  793. pretty_xml = minidom.parseString(xml_str).toprettyxml(indent=" ")
  794. pretty_xml = pretty_xml.replace('<?xml version="1.0" ?>', '<?xml version="1.0" encoding="UTF-8" standalone="no"?>')
  795. # 写入文件
  796. content_path = os.path.join(temp_dir, "content.xml")
  797. with open(content_path, 'w', encoding='utf-8') as f:
  798. f.write(pretty_xml)
  799. return images_list
  800. def create_topic(parent, title, topic_id):
  801. """创建主题节点"""
  802. topic = ET.SubElement(parent, "topic", id=topic_id)
  803. ET.SubElement(topic, "title").text = title
  804. return topic
  805. def create_manifest_xml(temp_dir,images_list):
  806. """创建 manifest.xml 文件"""
  807. str = ''
  808. if images_list:
  809. for file in images_list:
  810. str = str + f'<file-entry full-path="resources/{file}" media-type="image/png"/>' + '\n'
  811. manifest_content = f'''<?xml version="1.0" encoding="UTF-8" standalone="no"?>
  812. <manifest xmlns="urn:xmind:xmap:xmlns:manifest:1.0">
  813. <file-entry full-path="content.xml" media-type="text/xml"/>
  814. <file-entry full-path="META-INF/" media-type=""/>
  815. <file-entry full-path="META-INF/manifest.xml" media-type="text/xml"/>
  816. <file-entry full-path="resources/" media-type=""/>
  817. {str}
  818. </manifest>'''
  819. manifest_path = os.path.join(temp_dir, "META-INF", "manifest.xml")
  820. with open(manifest_path, 'w', encoding='utf-8') as f:
  821. f.write(manifest_content)
  822. return True
  823. def create_sample_images(temp_dir,images_list):
  824. """创建示例图片(实际使用中应替换为你的图片)"""
  825. # 创建两个示例图片(实际项目中应使用真实图片)
  826. resources_dir = os.path.join(temp_dir, "resources")
  827. if images_list:
  828. for file in images_list:
  829. shutil.copy(f"image/{file}", os.path.join(resources_dir, file))
  830. return True
  831. from xmindparser import xmind_to_xml
  832. def etxe1():
  833. xml = xmind_to_xml('result/2025-06-26_2906a7b1-baf4-4b09-b668-b80c3a8264cb.xmind')
  834. print(xml)
  835. if __name__ == '__main__':
  836. # etxe1()
  837. app.run(debug=True, host='0.0.0.0', port=2500)