libreoffice-python-script/BookmarkOP.py
2025-03-27 15:16:04 +08:00

675 lines
21 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 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)
# BatchInsertRowWithContentControl(data["table"])
ReplaceBookmarksWithControls(data["text"])
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 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
g_exportedScripts = (
Replace,
ReplaceWithJSON,
QueryAll,
QueryAllWithJSON,
InsertRow,
InsertRowWithJSON,
DeleteRow,
DeleteRowWithJSON,
ReplaceTextAndInsertTableRow,
ReplaceBookmarksWithControls,
ReplaceTextAndInsertTableRowWithContentControl,
SaveDocument,
NextEditableZone,
)