import json import uno import base64 import io from com.sun.star.text import XTextContent from com.sun.star.text import XTextRange from com.sun.star.text import XTextDocument from com.sun.star.text import XTextCursor from com.sun.star.text import XText from com.sun.star.text import XBookmarksSupplier from com.sun.star.text import XTextGraphicObjectsSupplier from com.sun.star.graphic import XGraphicProvider from com.sun.star.graphic import XGraphic from com.sun.star.beans import PropertyValue def Replace(bookmark_name_value_map): """ 替换所有书签 Args: bookmark_name_value_map: 书签名称和新值的映射 """ doc = XSCRIPTCONTEXT.getDocument() bookmarks = doc.getBookmarks() for bookmark_name, new_value in bookmark_name_value_map.items(): if bookmarks.hasByName(bookmark_name): bookmark = bookmarks.getByName(bookmark_name) anchor = bookmark.getAnchor() anchor.setString(new_value) def ReplaceOne(bookmark_obj_collection, bookmark_name, new_value): """ 替换一个书签 Args: bookmark_obj_collection: 书签对象集合 bookmark_name: 书签名称 new_value: 新值 """ if bookmark_obj_collection.hasByName(bookmark_name): bookmark = bookmark_obj_collection.getByName(bookmark_name) anchor = bookmark.getAnchor() anchor.setString(new_value) def ReplaceWithJSON(json_str): """ 替换所有书签 Args: json_str: 书签名称和新值的映射的JSON字符串 """ bookmark_name_value_map = json.loads(json_str) Replace(bookmark_name_value_map) def QueryAll(): """ 查询所有书签,表格中的书签获取对应表格index 返回结果格式: { "text": [], "table": [ { "tableIndex": 表格索引, "bookmark": [书签名称列表] } ] } """ doc = XSCRIPTCONTEXT.getDocument() bookmarks = doc.getBookmarks() bookmark_names = bookmarks.getElementNames() filtered_bookmarks = [ bk_name for bk_name in bookmark_names if not bk_name.startswith("_") and ":" not in bk_name ] result = {"text": [], "table": []} for bk_name in filtered_bookmarks: result["text"].append(bk_name) return result def QueryAllWithJSON(): return returnWithJSON(QueryAll()) def QueryBookmarkPositionInTable(tables, bookmarks): """ 查询书签在表格中的位置。 参数: tables: 表格集合 (XIndexAccess) bookmarks: 书签集合 (XIndexAccess) 返回: 一个字典,格式为 {'书签名': {'tableIndex': 表格索引, 'row': 行号}} """ result = {} # 遍历所有书签 for b in range(bookmarks.getCount()): bookmark = bookmarks.getByIndex(b) bookmark_name = bookmark.getName() x_range = bookmark.getAnchor() # 获取书签的锚点 x_text = x_range.getText() # 获取书签所在的文本对象 # 遍历所有表格 for t in range(tables.getCount()): table = tables.getByIndex(t) # 遍历表格中的每一行 rows = table.getRows().getCount() for i in range(rows): # 动态确定当前行的最大列数 max_cols = 0 while True: try: cell = table.getCellByPosition(max_cols, i) max_cols += 1 except Exception: break for j in range(max_cols): cell = table.getCellByPosition(j, i) cell_text = cell.getText() if cell_text == x_text: result[bookmark_name] = { 'tableIndex': t, 'row': i, 'col': j } break else: continue break else: continue break return result def InsertRowWithJSON(json_str): """ 插入行 """ data = json.loads(json_str) InsertRow(data["location_bookmark_name"], data["data"], data["start_row_index"]) def InsertRow(location_bookmark_name, data, start_row_index=-1): """ 表格插入行 Args: location_bookmark_name: 用于定位表格的书签 data: 二维数组,用于填充表格数据 start_row_index: 起始行位置,默认为-1,即在表格末尾插入 """ doc = XSCRIPTCONTEXT.getDocument() tables = doc.getTextTables() bookmarks = doc.getBookmarks() bookmark_in_table_position = QueryBookmarkPositionInTable(tables, bookmarks) if location_bookmark_name not in bookmark_in_table_position: raise ValueError(f"未找到书签 {location_bookmark_name} 对应的表格") handle_table_index = bookmark_in_table_position[location_bookmark_name][ "tableIndex" ] try: handle_table = tables.getByIndex(handle_table_index) except IndexError: raise IndexError(f"表格索引 {handle_table_index} 超出范围") col_count = handle_table.getColumns().getCount() row_count = handle_table.getRows().getCount() if not data or len(data) == 0: return try: row_count = handle_table.getRows().getCount() rows_to_insert = len(data) if rows_to_insert > 0: # 确定插入位置 if start_row_index != -1 and 0 <= start_row_index < row_count: insert_pos = start_row_index + 1 # 在指定行下方插入 else: insert_pos = row_count # 插入到表格末尾 # 批量插入所有新行 handle_table.getRows().insertByIndex(insert_pos, rows_to_insert) # 填充数据到新插入的行 for data_row_idx, row_data in enumerate(data): target_row = insert_pos + data_row_idx # 计算目标行索引 for col_idx, cell_value in enumerate(row_data): cell = handle_table.getCellByPosition(col_idx, target_row) cell.setString(str(cell_value)) except Exception as e: raise RuntimeError(f"err: {str(e)}") def BatchInsertRowWithContentControl(data_array): """ 批量插入行 """ doc = XSCRIPTCONTEXT.getDocument() tables = doc.getTextTables() bookmarks = doc.getBookmarks() bookmark_in_table_position = QueryBookmarkPositionInTable(tables, bookmarks) for arr_obj in data_array: location_bookmark_name = arr_obj.get("location_bookmark_name") data = arr_obj.get("data") start_row_index = arr_obj.get("start_row_index", -1) # 默认值为 -1 if location_bookmark_name not in bookmark_in_table_position: raise ValueError(f"未找到书签 {location_bookmark_name} 对应的表格") handle_table_index = bookmark_in_table_position[location_bookmark_name][ "tableIndex" ] try: handle_table = tables.getByIndex(handle_table_index) except IndexError: raise IndexError(f"表格索引 {handle_table_index} 超出范围") col_count = handle_table.getColumns().getCount() row_count = handle_table.getRows().getCount() if not data or len(data) == 0: return # if any(len(row) != col_count for row in data): # raise ValueError(f"数据列数不匹配,表格有 {col_count} 列") try: row_count = handle_table.getRows().getCount() rows_to_insert = len(data) if rows_to_insert > 0: # 确定插入位置 if start_row_index != -1 and 0 <= start_row_index < row_count: insert_pos = start_row_index + 1 # 在指定行下方插入 else: insert_pos = row_count # 插入到表格末尾 # 批量插入所有新行 handle_table.getRows().insertByIndex(insert_pos, rows_to_insert) # 填充数据到新插入的行 for data_row_idx, row_data in enumerate(data): target_row = insert_pos + data_row_idx # 计算目标行索引 for col_idx, cell_value in enumerate(row_data): cell = handle_table.getCellByPosition(col_idx, target_row) cell_text = cell.getText() # === 清空单元格 === cell_text.setString("") # === 创建内容控件 === content_control = doc.createInstance( "com.sun.star.text.ContentControl" ) # === 插入控件到单元格 === cursor = cell_text.createTextCursor() cell_text.insertTextContent(cursor, content_control, False) # === 设置控件内容 === cc_text = content_control.getText() cc_cursor = cc_text.createTextCursor() cc_cursor.setString(str(cell_value)) except Exception as e: raise RuntimeError(f"插入行时发生错误: {str(e)}") def BatchInsertRow(data_array): """ 批量插入行 """ doc = XSCRIPTCONTEXT.getDocument() tables = doc.getTextTables() bookmarks = doc.getBookmarks() bookmark_in_table_position = QueryBookmarkPositionInTable(tables, bookmarks) for arr_obj in data_array: location_bookmark_name = arr_obj.get("location_bookmark_name") data = arr_obj.get("data") start_row_index = arr_obj.get("start_row_index", -1) # 默认值为 -1 if location_bookmark_name not in bookmark_in_table_position: raise ValueError(f"未找到书签 {location_bookmark_name} 对应的表格") handle_table_index = bookmark_in_table_position[location_bookmark_name][ "tableIndex" ] try: handle_table = tables.getByIndex(handle_table_index) except IndexError: raise IndexError(f"表格索引 {handle_table_index} 超出范围") col_count = handle_table.getColumns().getCount() row_count = handle_table.getRows().getCount() if not data or len(data) == 0: return if any(len(row) != col_count for row in data): raise ValueError(f"数据列数不匹配,表格有 {col_count} 列") try: row_count = handle_table.getRows().getCount() rows_to_insert = len(data) if rows_to_insert > 0: # 确定插入位置 if start_row_index != -1 and 0 <= start_row_index < row_count: insert_pos = start_row_index + 1 # 在指定行下方插入 else: insert_pos = row_count # 插入到表格末尾 # 批量插入所有新行 handle_table.getRows().insertByIndex(insert_pos, rows_to_insert) # 填充数据到新插入的行 for data_row_idx, row_data in enumerate(data): target_row = insert_pos + data_row_idx # 计算目标行索引 for col_idx, cell_value in enumerate(row_data): cell = handle_table.getCellByPosition(col_idx, target_row) cell.setString(str(cell_value)) except Exception as e: raise RuntimeError(f"插入行时发生错误: {str(e)}") def DeleteRowWithJSON(json_str): """ 删除行 """ data = json.loads(json_str) DeleteRow( data["location_bookmark_name"], data["start_row_index"], data["delete_row_count"], ) def DeleteRow(location_bookmark_name, start_row_index, delete_row_count=-1): """ 删除表格行 Args: location_bookmark_name: 用于定位表格的书签 start_row_index: 起始行位置(删除行包含本行) delete_row_count: 删除的行数 """ doc = XSCRIPTCONTEXT.getDocument() tables = doc.getTextTables() bookmarks = doc.getBookmarks() bookmark_in_table_position = QueryBookmarkPositionInTable(tables, bookmarks) if location_bookmark_name not in bookmark_in_table_position: raise ValueError(f"未找到书签 {location_bookmark_name} 对应的表格") handle_table_index = bookmark_in_table_position[location_bookmark_name][ "tableIndex" ] handle_table = tables.getByIndex(handle_table_index) row_count = handle_table.getRows().getCount() if start_row_index < 0 or start_row_index >= row_count: raise ValueError(f"起始行索引 {start_row_index} 超出范围") # 如果未指定删除行数或超出范围,则删除剩余所有行 if start_row_index + delete_row_count > row_count or delete_row_count == -1: delete_row_count = row_count - start_row_index try: handle_table.getRows().removeByIndex(start_row_index, delete_row_count) except Exception as e: raise RuntimeError(f"err: {str(e)}") def LocateBookmark(bookmark_name): """ 定位书签并选中内容 Args: bookmark_name: 要定位的书签名称 """ doc = XSCRIPTCONTEXT.getDocument() if False == doc.getBookmarks().hasByName(bookmark_name): return bookmark = doc.getBookmarks().getByName(bookmark_name) try: # 获取书签的文本范围锚点 anchor = bookmark.getAnchor() # 获取文档控制器 controller = doc.getCurrentController() # 将视图光标移动到书签位置并选中 view_cursor = controller.getViewCursor() view_cursor.gotoRange(anchor, False) # False表示不扩展选区 # 如果需要高亮显示(可选) controller.select(anchor) # 确保窗口可见(针对隐藏窗口的情况) try: window = controller.getFrame().getContainerWindow() window.setVisible(True) window.toFront() except Exception: pass except Exception as e: raise RuntimeError(f"定位书签失败: {str(e)}") from e def InsertBookmark(bookmark_name): """ 选中内容插入书签,如果没有选中任何内容,插入点书签 Args: bookmark_name: 要插入的书签名称 """ doc = XSCRIPTCONTEXT.getDocument() controller = doc.getCurrentController() # 获取当前选区 selection = controller.getSelection() # 检查是否已存在同名书签,如果存在则先删除 bookmarks = doc.getBookmarks() if bookmarks.hasByName(bookmark_name): bookmarks.getByName(bookmark_name).dispose() try: # 创建书签 if selection.getCount() > 0: # 尝试获取选区的第一个元素 range_to_bookmark = selection.getByIndex(0) # 创建书签并附加到选区 bookmark = doc.createInstance("com.sun.star.text.Bookmark") bookmark.attach(range_to_bookmark) bookmark.setName(bookmark_name) else: # 没有选中内容,创建点书签 cursor = controller.getViewCursor() text_cursor = cursor.getText().createTextCursorByRange(cursor) # 创建书签并附加到光标位置 bookmark = doc.createInstance("com.sun.star.text.Bookmark") bookmark.attach(text_cursor) bookmark.setName(bookmark_name) except Exception as e: raise RuntimeError(f"插入书签失败: {str(e)}") from e def UpdateBookmark(bookmark_name, new_value): """ 修改书签名称(通过删除旧书签 + 插入新书签实现) Args: bookmark_name: 原书签名称 new_value: 新书签名称 """ doc = XSCRIPTCONTEXT.getDocument() bookmarks = doc.getBookmarks() if not bookmarks.hasByName(bookmark_name): raise ValueError(f"书签 '{bookmark_name}' 不存在") if bookmarks.hasByName(new_value): raise ValueError(f"书签 '{new_value}' 已存在") # 获取旧书签的锚点位置 old_bookmark = bookmarks.getByName(bookmark_name) old_bookmark.setName(new_value) def DeleteBookmark(bookmark_name): """ 删除书签但保留内容(通过断开书签与锚点的关联) """ doc = XSCRIPTCONTEXT.getDocument() bookmarks = doc.getBookmarks() if not bookmarks.hasByName(bookmark_name): return bookmark = bookmarks.getByName(bookmark_name) if hasattr(bookmark, "Anchor"): try: anchor = bookmark.Anchor anchor.getText().removeTextContent(bookmark) except Exception as e: pass def CreateTable(location_bookmark_name, data): """ 创建表格 Args: location_bookmark_name: 用于定位表格的书签 data: 二维数组,用于填充表格数据 """ def ReplaceTextAndInsertTableRow(json_str): """ 替换文本和表格 """ data = json.loads(json_str) Replace(data["text"]) BatchInsertRow(data["table"]) def ReplaceTextAndInsertTableRowWithContentControl(json_str): data = json.loads(json_str) SetDocumentToFormMode() Replace(data["text"]) BatchInsertRowWithContentControl(data["table"]) ReplaceBookmarksWithControls({}) def returnWithJSON(data): return json.dumps(data, ensure_ascii=False) def SaveDocument(): # 获取当前文档对象 model = XSCRIPTCONTEXT.getDocument() try: # 检查文档是否支持保存(XStorable 接口) from com.sun.star.frame import XStorable xstorable = model.uno_getAdapter(XStorable) # 调用保存方法(覆盖原文件) xstorable.store() return True except Exception as e: print("err:", e) return False def SetDocumentToFormMode(): """ 将文档设置为限制编辑模式,启用强制保护,仅允许填写窗体 """ doc = XSCRIPTCONTEXT.getDocument() # 获取文档的控制器 controller = doc.getCurrentController() # 获取文档的保护设置 protectable = doc.Protectable # 启用强制保护 if not protectable.isProtected(): protectable.protect("") # 空字符串表示无密码保护 # 设置编辑限制为仅允许填写窗体 text_doc = doc.TextDocument text_doc.setPropertyValue("EnableFormEdit", True) text_doc.setPropertyValue("EnableFormFieldsOnly", True) # 刷新视图 controller.refresh() def ReplaceBookmarksWithControls(bookmark_name_value_map={}): # 获取文档对象 doc = XSCRIPTCONTEXT.getDocument() bookmarks = doc.getBookmarks() # 过滤包含下划线前缀的书签 bookmark_names = [name for name in bookmarks.getElementNames() if not name.startswith("_")] # 遍历所有需要处理的书签 for name in bookmark_names: try: # 检查书签是否存在 if not bookmarks.hasByName(name): continue # 获取书签对象及其锚点 bookmark = bookmarks.getByName(name) anchor = bookmark.getAnchor() # 获取书签的原始内容 original_text = anchor.getString() replace_value = bookmark_name_value_map.get(name, original_text) # === 第1步:创建内容控件 === content_control = doc.createInstance("com.sun.star.text.ContentControl") # 获取书签所在文本对象并清空原内容 text = anchor.getText() anchor.setString("") # 插入内容控件 text.insertTextContent(anchor, content_control, True) # 设置控件内容 cc_cursor = content_control.getText().createTextCursor() cc_cursor.setString(replace_value) # === 第2步:清理旧书签 === bookmark.dispose() # === 第3步:创建新书签 === cc_anchor = content_control.getText().getAnchor() new_bookmark = doc.createInstance("com.sun.star.text.Bookmark") new_bookmark.setName(name) new_bookmark.attach(cc_anchor) except Exception as e: print(f"处理书签 '{name}' 时出错: {e}") def NextEditableZone(current_index): """ 根据索引定位名称包含'permission'的书签(支持循环) Args: current_index (int/str): 从 0 开始的索引,支持字符串或整数类型 """ # 强制转换为整数 ----------------------------- try: current_index = int(current_index) except (ValueError, TypeError): print(f"错误:无法将 {current_index} 转换为整数") return doc = XSCRIPTCONTEXT.getDocument() controller = doc.getCurrentController() view_cursor = controller.getViewCursor() bookmarks = doc.getBookmarks() bookmark_names = bookmarks.getElementNames() # 过滤包含permission的书签(不区分大小写) permission_bookmarks = [name for name in bookmark_names if not name.startswith("_")] editable_anchors = [] for bm_name in permission_bookmarks: try: bookmark = bookmarks.getByName(bm_name) anchor = bookmark.getAnchor() editable_anchors.append(anchor) except Exception as e: print(f"err '{bm_name}' e: {e}") continue # 无匹配书签时直接返回 if not editable_anchors: return # 计算有效索引(循环逻辑) total = len(editable_anchors) effective_index = current_index % total # 自动处理越界 # 定位到目标书签 target_anchor = editable_anchors[effective_index] try: # 跳转到书签起始位置 view_cursor.gotoRange(target_anchor, False) # 选中整个书签范围 controller.select(target_anchor) # 窗口可见性处理 try: window = controller.getFrame().getContainerWindow() window.setVisible(True) window.toFront() except Exception: pass except Exception as e: raise RuntimeError(f"err: {str(e)}") from e def ReplaceBookmarkWithImage(data_array): """ 替换书签为图片 """ # 获取当前文档 doc = XSCRIPTCONTEXT.getDocument() # 获取书签供应商 bookmarks = doc.getBookmarks() # 获取服务管理器 smgr = XSCRIPTCONTEXT.getComponentContext().getServiceManager() # 创建图形提供者服务 graphic_provider = smgr.createInstanceWithContext("com.sun.star.graphic.GraphicProvider", XSCRIPTCONTEXT.getComponentContext()) for data in data_array: bookmark_name = data["bookmarkName"] image_data = data["imageData"] width = data["width"] height = data["height"] file_type = data["fileType"] if bookmark_name in bookmarks: # 获取书签的锚点 bookmark = bookmarks[bookmark_name] text_range = bookmark.getAnchor() text_cursor = text_range.getText().createTextCursorByRange(text_range) # 解码Base64图片数据 image_bytes = base64.b64decode(image_data) image_stream = io.BytesIO(image_bytes) # 创建PropertyValue对象 url_prop = PropertyValue() url_prop.Name = "URL" url_prop.Value = "private:stream" input_stream_prop = PropertyValue() input_stream_prop.Name = "InputStream" input_stream_prop.Value = image_stream mime_type_prop = PropertyValue() mime_type_prop.Name = "MimeType" mime_type_prop.Value = f"image/{file_type}" # 将PropertyValue对象放入序列中 graphic_properties = (url_prop, input_stream_prop, mime_type_prop) # 查询图形 graphic = graphic_provider.queryGraphic(graphic_properties) # 创建图形对象 graphic_object = doc.createInstance("com.sun.star.text.TextGraphicObject") graphic_object.Graphic = graphic # 设置图形属性 graphic_object.Width = width # 设置宽度 graphic_object.Height = height # 设置高度 # 替换书签为图片 text_cursor.getText().insertTextContent(text_cursor, graphic_object, False) def GetBookmarksInLists(): """ 返回文档中所有位于列表中的书签 Returns: list: 位于列表中的书签名称列表 """ doc = XSCRIPTCONTEXT.getDocument() bookmarks = doc.getBookmarks() bookmarks_in_lists = [] # 遍历所有书签 for bookmark in bookmarks: bookmark_name = bookmark.getName() anchor = bookmark.getAnchor() # 判断书签的锚点是否在列表中 if anchor.supportsService("com.sun.star.text.Paragraph"): paragraph = anchor if paragraph.getPropertySetInfo().hasPropertyByName("NumberingStyleName"): numbering_style = paragraph.getPropertyValue("NumberingStyleName") if numbering_style: bookmarks_in_lists.append(bookmark_name) return bookmarks_in_lists g_exportedScripts = ( Replace, ReplaceWithJSON, QueryAll, QueryAllWithJSON, InsertRow, InsertRowWithJSON, DeleteRow, DeleteRowWithJSON, ReplaceTextAndInsertTableRow, ReplaceBookmarksWithControls, ReplaceTextAndInsertTableRowWithContentControl, SaveDocument, NextEditableZone, GetBookmarksInLists, SetDocumentToFormMode )