libreoffice-python-script/BookmarkOP.py
2025-03-27 10:13:08 +08:00

636 lines
20 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import json
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 ReplaceWithJSON(json_str):
"""
替换所有书签
Args:
json_str: 书签名称和新值的映射的JSON字符串
"""
bookmark_name_value_map = json.loads(json_str)
Replace(bookmark_name_value_map)
def DebugCallReplace():
"""
调试调用
"""
Replace({"合同编号_A": "11111", "合同名称_A": "11111"})
def DebugCallReplaceList():
"""
调试调用替换list
"""
Replace({"list": "1\r2\r3\r4"})
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):
"""
查询书签在表格中的位置
{'bk1': {'tableIndex': 0, 'row': 0, 'col': 0}, 'bk2': {'tableIndex': 0, 'row': 0, 'col': 2}}
"""
bookmark_names = bookmarks.getElementNames()
bookmark_position = {}
for table_index in range(tables.getCount()):
table = tables.getByIndex(table_index)
col_count = table.getColumns().getCount()
row_count = table.getRows().getCount()
for row in range(row_count):
for col in range(col_count):
cell = table.getCellByPosition(col, row)
cell_text_obj = cell.getText()
for bk_name in bookmark_names:
bookmark_obj = bookmarks.getByName(bk_name)
bookmark_text_obj = bookmark_obj.getAnchor().getText()
if cell_text_obj == bookmark_text_obj:
bookmark_position[bk_name] = {
"tableIndex": table_index,
"col": col,
"row": row,
}
return bookmark_position
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
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"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)
ReplaceBookmarksWithControls(data["text"])
BatchInsertRowWithContentControl(data["table"])
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 DebugCallReplaceBookmarksWithControls():
ReplaceBookmarksWithControls({"合同编号_A": "11111", "合同名称_A": "22222"})
def ReplaceBookmarksWithControls(bookmark_name_value_map={}):
doc = XSCRIPTCONTEXT.getDocument()
bookmarks = doc.getBookmarks()
for i in reversed(range(bookmarks.getCount())):
bookmark = bookmarks.getByIndex(i)
name = bookmark.getName()
anchor = bookmark.getAnchor()
if not anchor:
continue
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)
# === 第1步清理旧书签 ===
bookmark.dispose()
# === 第2步创建新书签 ===
# 获取控件实际范围
cc_anchor = content_control.getText().getAnchor()
new_bookmark = doc.createInstance("com.sun.star.text.Bookmark")
new_bookmark.setName(name)
new_bookmark.attach(cc_anchor)
def NextEditableZone(current_index):
"""
根据索引定位可编辑的内容控件(支持循环)
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()
# 收集所有内容控件的锚点
text_content = doc.getText().createEnumeration()
editable_anchors = []
while text_content.hasMoreElements():
element = text_content.nextElement()
# 关键修改点:检查内容控件服务
if element.supportsService("com.sun.star.text.ContentControl"):
try:
anchor = element.getAnchor()
editable_anchors.append(anchor)
except Exception:
continue
# 无控件时直接返回
if not editable_anchors:
print("文档中未找到内容控件")
return
# 计算有效索引(循环逻辑)
total = len(editable_anchors)
effective_index = current_index % total # 自动处理越界
# 定位到目标控件
target_anchor = editable_anchors[effective_index]
view_cursor.gotoRange(target_anchor.getStart(), False) # 跳转到控件起始位置
controller.select(target_anchor) # 选中整个控件范围
g_exportedScripts = (
Replace,
ReplaceWithJSON,
QueryAll,
QueryAllWithJSON,
InsertRow,
InsertRowWithJSON,
DeleteRow,
DeleteRowWithJSON,
ReplaceTextAndInsertTableRow,
DebugCallReplace,
DebugCallReplaceList,
ReplaceBookmarksWithControls,
ReplaceTextAndInsertTableRowWithContentControl,
DebugCallReplaceBookmarksWithControls,
SaveDocument,
NextEditableZone,
)